1 /* $OpenBSD: touch.c,v 1.6 2000/10/13 13:54:59 pjanzen Exp $ */ 2 /* $NetBSD: touch.c,v 1.11 1995/08/31 22:10:06 jtc Exp $ */ 3 4 /* 5 * Copyright (c) 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 static char copyright[] = 39 "@(#) Copyright (c) 1993\n\ 40 The Regents of the University of California. All rights reserved.\n"; 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95"; 46 #endif 47 static char rcsid[] = "$OpenBSD: touch.c,v 1.6 2000/10/13 13:54:59 pjanzen Exp $"; 48 #endif /* not lint */ 49 50 #include <sys/types.h> 51 #include <sys/stat.h> 52 #include <sys/time.h> 53 54 #include <err.h> 55 #include <errno.h> 56 #include <fcntl.h> 57 #include <stdio.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <locale.h> 61 #include <time.h> 62 #include <tzfile.h> 63 #include <unistd.h> 64 65 int rw __P((char *, struct stat *, int)); 66 void stime_arg1 __P((char *, struct timeval *)); 67 void stime_arg2 __P((char *, int, struct timeval *)); 68 void stime_file __P((char *, struct timeval *)); 69 void usage __P((void)); 70 71 int 72 main(argc, argv) 73 int argc; 74 char *argv[]; 75 { 76 struct stat sb; 77 struct timeval tv[2]; 78 int aflag, cflag, fflag, mflag, ch, fd, len, rval, timeset; 79 char *p; 80 81 setlocale(LC_ALL, ""); 82 83 aflag = cflag = fflag = mflag = timeset = 0; 84 if (gettimeofday(&tv[0], NULL)) 85 err(1, "gettimeofday"); 86 87 while ((ch = getopt(argc, argv, "acfmr:t:")) != -1) 88 switch(ch) { 89 case 'a': 90 aflag = 1; 91 break; 92 case 'c': 93 cflag = 1; 94 break; 95 case 'f': 96 fflag = 1; 97 break; 98 case 'm': 99 mflag = 1; 100 break; 101 case 'r': 102 timeset = 1; 103 stime_file(optarg, tv); 104 break; 105 case 't': 106 timeset = 1; 107 stime_arg1(optarg, tv); 108 break; 109 case '?': 110 default: 111 usage(); 112 } 113 argc -= optind; 114 argv += optind; 115 116 /* Default is both -a and -m. */ 117 if (aflag == 0 && mflag == 0) 118 aflag = mflag = 1; 119 120 /* 121 * If no -r or -t flag, at least two operands, the first of which 122 * is an 8 or 10 digit number, use the obsolete time specification. 123 */ 124 if (!timeset && argc > 1) { 125 (void)strtol(argv[0], &p, 10); 126 len = p - argv[0]; 127 if (*p == '\0' && (len == 8 || len == 10)) { 128 timeset = 1; 129 stime_arg2(*argv++, len == 10, tv); 130 } 131 } 132 133 /* Otherwise use the current time of day. */ 134 if (!timeset) 135 tv[1] = tv[0]; 136 137 if (*argv == NULL) 138 usage(); 139 140 for (rval = 0; *argv; ++argv) { 141 /* See if the file exists. */ 142 if (stat(*argv, &sb)) { 143 if (!cflag) { 144 /* Create the file. */ 145 fd = open(*argv, 146 O_WRONLY | O_CREAT, DEFFILEMODE); 147 if (fd == -1 || fstat(fd, &sb) || close(fd)) { 148 rval = 1; 149 warn("%s", *argv); 150 continue; 151 } 152 153 /* If using the current time, we're done. */ 154 if (!timeset) 155 continue; 156 } else 157 continue; 158 } 159 160 if (!aflag) 161 TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec); 162 if (!mflag) 163 TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec); 164 165 /* Try utimes(2). */ 166 if (!utimes(*argv, tv)) 167 continue; 168 169 /* If the user specified a time, nothing else we can do. */ 170 if (timeset) { 171 rval = 1; 172 warn("%s", *argv); 173 } 174 175 /* 176 * System V and POSIX 1003.1 require that a NULL argument 177 * set the access/modification times to the current time. 178 * The permission checks are different, too, in that the 179 * ability to write the file is sufficient. Take a shot. 180 */ 181 if (!utimes(*argv, NULL)) 182 continue; 183 184 /* Try reading/writing. */ 185 if (rw(*argv, &sb, fflag)) 186 rval = 1; 187 } 188 exit(rval); 189 } 190 191 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 192 193 void 194 stime_arg1(arg, tvp) 195 char *arg; 196 struct timeval *tvp; 197 { 198 struct tm *t; 199 time_t tmptime; 200 int yearset; 201 char *p; 202 /* Start with the current time. */ 203 tmptime = tvp[0].tv_sec; 204 if ((t = localtime(&tmptime)) == NULL) 205 err(1, "localtime"); 206 /* [[CC]YY]MMDDhhmm[.SS] */ 207 if ((p = strchr(arg, '.')) == NULL) 208 t->tm_sec = 0; /* Seconds defaults to 0. */ 209 else { 210 if (strlen(p + 1) != 2) 211 goto terr; 212 *p++ = '\0'; 213 t->tm_sec = ATOI2(p); 214 } 215 216 yearset = 0; 217 switch(strlen(arg)) { 218 case 12: /* CCYYMMDDhhmm */ 219 t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE; 220 yearset = 1; 221 /* FALLTHOUGH */ 222 case 10: /* YYMMDDhhmm */ 223 if (yearset) { 224 yearset = ATOI2(arg); 225 t->tm_year += yearset; 226 } else { 227 yearset = ATOI2(arg); 228 if (yearset < 69) 229 t->tm_year = yearset + 2000 - TM_YEAR_BASE; 230 else 231 t->tm_year = yearset + 1900 - TM_YEAR_BASE; 232 } 233 /* FALLTHROUGH */ 234 case 8: /* MMDDhhmm */ 235 t->tm_mon = ATOI2(arg); 236 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 237 t->tm_mday = ATOI2(arg); 238 t->tm_hour = ATOI2(arg); 239 t->tm_min = ATOI2(arg); 240 break; 241 default: 242 goto terr; 243 } 244 245 t->tm_isdst = -1; /* Figure out DST. */ 246 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 247 if (tvp[0].tv_sec == -1) 248 terr: errx(1, 249 "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 250 251 tvp[0].tv_usec = tvp[1].tv_usec = 0; 252 } 253 254 void 255 stime_arg2(arg, year, tvp) 256 char *arg; 257 int year; 258 struct timeval *tvp; 259 { 260 struct tm *t; 261 time_t tmptime; 262 /* Start with the current time. */ 263 tmptime = tvp[0].tv_sec; 264 if ((t = localtime(&tmptime)) == NULL) 265 err(1, "localtime"); 266 267 t->tm_mon = ATOI2(arg); /* MMDDhhmm[YY] */ 268 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 269 t->tm_mday = ATOI2(arg); 270 t->tm_hour = ATOI2(arg); 271 t->tm_min = ATOI2(arg); 272 if (year) { 273 year = ATOI2(arg); 274 if (year < 69) 275 t->tm_year = year + 2000 - TM_YEAR_BASE; 276 else 277 t->tm_year = year + 1900 - TM_YEAR_BASE; 278 } 279 t->tm_sec = 0; 280 281 t->tm_isdst = -1; /* Figure out DST. */ 282 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 283 if (tvp[0].tv_sec == -1) 284 errx(1, 285 "out of range or illegal time specification: MMDDhhmm[YY]"); 286 287 tvp[0].tv_usec = tvp[1].tv_usec = 0; 288 } 289 290 void 291 stime_file(fname, tvp) 292 char *fname; 293 struct timeval *tvp; 294 { 295 struct stat sb; 296 297 if (stat(fname, &sb)) 298 err(1, "%s", fname); 299 TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atimespec); 300 TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtimespec); 301 } 302 303 int 304 rw(fname, sbp, force) 305 char *fname; 306 struct stat *sbp; 307 int force; 308 { 309 int fd, needed_chmod, rval; 310 u_char byte; 311 312 /* Try regular files and directories. */ 313 if (!S_ISREG(sbp->st_mode) && !S_ISDIR(sbp->st_mode)) { 314 warnx("%s: %s", fname, strerror(EFTYPE)); 315 return (1); 316 } 317 318 needed_chmod = rval = 0; 319 if ((fd = open(fname, O_RDWR, 0)) == -1) { 320 if (!force || chmod(fname, DEFFILEMODE)) 321 goto err; 322 if ((fd = open(fname, O_RDWR, 0)) == -1) 323 goto err; 324 needed_chmod = 1; 325 } 326 327 if (sbp->st_size != 0) { 328 if (read(fd, &byte, sizeof(byte)) != sizeof(byte)) 329 goto err; 330 if (lseek(fd, (off_t)0, SEEK_SET) == -1) 331 goto err; 332 if (write(fd, &byte, sizeof(byte)) != sizeof(byte)) 333 goto err; 334 } else { 335 if (write(fd, &byte, sizeof(byte)) != sizeof(byte)) { 336 err: rval = 1; 337 warn("%s", fname); 338 } else if (ftruncate(fd, (off_t)0)) { 339 rval = 1; 340 warn("%s: file modified", fname); 341 } 342 } 343 344 if (close(fd) && rval != 1) { 345 rval = 1; 346 warn("%s", fname); 347 } 348 if (needed_chmod && chmod(fname, sbp->st_mode) && rval != 1) { 349 rval = 1; 350 warn("%s: permissions modified", fname); 351 } 352 return (rval); 353 } 354 355 __dead void 356 usage() 357 { 358 (void)fprintf(stderr, 359 "usage: touch [-acfm] [-r file] [-t time] file ...\n"); 360 exit(1); 361 } 362