1/* $NetBSD: touch.c,v 1.40 2024/02/10 00:19:30 kre Exp $ */ 2 3/* 4 * Copyright (c) 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__COPYRIGHT("@(#) Copyright (c) 1993\ 35 The Regents of the University of California. All rights reserved."); 36#endif /* not lint */ 37 38#ifndef lint 39#if 0 40static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95"; 41#endif 42__RCSID("$NetBSD: touch.c,v 1.40 2024/02/10 00:19:30 kre Exp $"); 43#endif /* not lint */ 44 45#include <sys/types.h> 46#include <sys/stat.h> 47#include <sys/time.h> 48 49#include <ctype.h> 50#include <err.h> 51#include <errno.h> 52#include <fcntl.h> 53#include <limits.h> 54#include <math.h> 55#include <stdio.h> 56#include <stdlib.h> 57#include <string.h> 58#include <locale.h> 59#include <time.h> 60#include <tzfile.h> 61#include <unistd.h> 62#include <util.h> 63#include <getopt.h> 64 65static void stime_arg0(const char *, struct timespec *); 66static void stime_arg1(char *, struct timespec *); 67static void stime_arg2(const char *, int, struct timespec *); 68static void stime_file(const char *, struct timespec *, 69 int (const char *, struct stat *)); 70static int stime_posix(const char *, struct timespec *); 71static int difftm(const struct tm *, const struct tm *); 72__dead static void usage(void); 73 74struct option touch_longopts[] = { 75 { "date", required_argument, 0, 76 'd' }, 77 { "reference", required_argument, 0, 78 'r' }, 79 { NULL, 0, 0, 80 0 }, 81}; 82 83#define YEAR_BOUNDARY 69 84#define LOW_YEAR_CENTURY 2000 /* for 2 digit years < YEAR_BOUNDARY */ 85#define HIGH_YEAR_CENTURY 1900 /* for 2 digit years >= " */ 86 87#define NO_TIME ((time_t)-1) /* time_t might be unsigned */ 88 89int 90main(int argc, char *argv[]) 91{ 92 struct stat sb; 93 struct timespec ts[2]; 94 int aflag, cflag, Dflag, hflag, mflag, ch, fd, len, rval, timeset; 95 char *p; 96 int (*change_file_times)(const char *, const struct timespec *); 97 int (*get_file_status)(const char *, struct stat *); 98 99 setlocale(LC_ALL, ""); 100 101 aflag = cflag = Dflag = hflag = mflag = timeset = 0; 102 if (clock_gettime(CLOCK_REALTIME, &ts[0])) 103 err(1, "clock_gettime"); 104 105 while ((ch = getopt_long(argc, argv, "acDd:fhmR:r:t:", touch_longopts, 106 NULL)) != -1) 107 switch (ch) { 108 case 'a': 109 aflag = 1; 110 break; 111 case 'c': 112 cflag = 1; 113 break; 114 case 'D': 115 Dflag = 1; 116 break; 117 case 'd': 118 timeset = 1; 119 if (!stime_posix(optarg, ts)) 120 stime_arg0(optarg, ts); 121 break; 122 case 'f': 123 break; 124 case 'h': 125 hflag = 1; 126 break; 127 case 'm': 128 mflag = 1; 129 break; 130 case 'R': 131 timeset = 1; 132 stime_file(optarg, ts, lstat); 133 break; 134 case 'r': 135 timeset = 1; 136 stime_file(optarg, ts, stat); 137 break; 138 case 't': 139 timeset = 1; 140 stime_arg1(optarg, ts); 141 break; 142 case '?': 143 default: 144 usage(); 145 } 146 argc -= optind; 147 argv += optind; 148 149 /* Default is both -a and -m. */ 150 if (aflag == 0 && mflag == 0) 151 aflag = mflag = 1; 152 153 if (hflag) { 154 cflag = 1; /* Don't create new file */ 155 change_file_times = lutimens; 156 get_file_status = lstat; 157 } else { 158 change_file_times = utimens; 159 get_file_status = stat; 160 } 161 162 /* 163 * If no -r or -t flag, at least two operands, the first of which 164 * is an 8 or 10 digit number, use the obsolete time specification. 165 */ 166 if (!timeset && argc > 1) { 167 (void)strtol(argv[0], &p, 10); 168 len = p - argv[0]; 169 if (*p == '\0' && (len == 8 || len == 10)) { 170 timeset = 1; 171 stime_arg2(*argv++, len == 10, ts); 172 } 173 } 174 175 /* Otherwise use the current time of day. */ 176 if (!timeset) 177 ts[1] = ts[0]; 178 179 if (*argv == NULL) 180 usage(); 181 182 for (rval = EXIT_SUCCESS; *argv; ++argv) { 183 /* See if the file exists. */ 184 if ((*get_file_status)(*argv, &sb)) { 185 if (!cflag) { 186 /* Create the file. */ 187 fd = open(*argv, 188 O_WRONLY | O_CREAT, DEFFILEMODE); 189 if (fd == -1 || fstat(fd, &sb) || close(fd)) { 190 rval = EXIT_FAILURE; 191 warn("%s", *argv); 192 continue; 193 } 194 195 /* If using the current time, we're done. */ 196 if (!timeset) 197 continue; 198 } else 199 continue; 200 } 201 if (!aflag) 202 ts[0] = sb.st_atimespec; 203 if (!mflag) 204 ts[1] = sb.st_mtimespec; 205 206 if (Dflag && 207 timespeccmp(&ts[0], &sb.st_atimespec, ==) && 208 timespeccmp(&ts[1], &sb.st_mtimespec, ==)) 209 continue; 210 211 /* Try utimes(2). */ 212 if (!(*change_file_times)(*argv, ts)) 213 continue; 214 215 /* If the user specified a time, nothing else we can do. */ 216 if (timeset) { 217 rval = EXIT_FAILURE; 218 warn("%s", *argv); 219 } 220 221 /* 222 * System V and POSIX 1003.1 require that a NULL argument 223 * set the access/modification times to the current time. 224 * The permission checks are different, too, in that the 225 * ability to write the file is sufficient. Take a shot. 226 */ 227 if (!(*change_file_times)(*argv, NULL)) 228 continue; 229 230 rval = EXIT_FAILURE; 231 warn("%s", *argv); 232 } 233 exit(rval); 234} 235 236#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 237 238static void 239stime_arg0(const char *arg, struct timespec *tsp) 240{ 241 tsp[1].tv_sec = tsp[0].tv_sec = parsedate(arg, NULL, NULL); 242 if (tsp[0].tv_sec == NO_TIME) 243 errx(EXIT_FAILURE, "Could not parse `%s'", arg); 244 tsp[0].tv_nsec = tsp[1].tv_nsec = 0; 245} 246 247static void 248stime_arg1(char *arg, struct timespec *tsp) 249{ 250 struct tm *t, tm; 251 time_t tmptime; 252 int yearset; 253 char *p; 254 char *initarg = arg; 255 256 /* Start with the current time. */ 257 tmptime = tsp[0].tv_sec; 258 if ((t = localtime(&tmptime)) == NULL) 259 err(EXIT_FAILURE, "localtime"); 260 /* [[CC]YY]MMDDhhmm[.ss] */ 261 if ((p = strchr(arg, '.')) == NULL) 262 t->tm_sec = 0; /* Seconds defaults to 0. */ 263 else { 264 if (strlen(p + 1) != 2) 265 goto terr; 266 *p++ = '\0'; 267 t->tm_sec = ATOI2(p); 268 } 269 270 yearset = 0; 271 switch (strlen(arg)) { 272 case 12: /* CCYYMMDDhhmm */ 273 t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE; 274 yearset = 1; 275 /* FALLTHROUGH */ 276 case 10: /* YYMMDDhhmm */ 277 if (yearset) { 278 t->tm_year += ATOI2(arg); 279 } else { 280 yearset = ATOI2(arg); 281 if (yearset < YEAR_BOUNDARY) 282 t->tm_year = yearset + 283 LOW_YEAR_CENTURY - TM_YEAR_BASE; 284 else 285 t->tm_year = yearset + 286 HIGH_YEAR_CENTURY - TM_YEAR_BASE; 287 } 288 /* FALLTHROUGH */ 289 case 8: /* MMDDhhmm */ 290 t->tm_mon = ATOI2(arg); 291 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 292 /* FALLTHROUGH */ 293 case 6: 294 t->tm_mday = ATOI2(arg); 295 /* FALLTHROUGH */ 296 case 4: 297 t->tm_hour = ATOI2(arg); 298 /* FALLTHROUGH */ 299 case 2: 300 t->tm_min = ATOI2(arg); 301 break; 302 default: 303 goto terr; 304 } 305 306 t->tm_isdst = -1; /* Figure out DST. */ 307 tm = *t; 308 tsp[0].tv_sec = tsp[1].tv_sec = mktime(t); 309 if (tsp[0].tv_sec == NO_TIME || difftm(t, &tm)) 310 terr: errx(EXIT_FAILURE, "out of range or bad time specification:\n" 311 "\t'%s' should be [[CC]YY]MMDDhhmm[.ss]", initarg); 312 313 tsp[0].tv_nsec = tsp[1].tv_nsec = 0; 314} 315 316static void 317stime_arg2(const char *arg, int year, struct timespec *tsp) 318{ 319 struct tm *t, tm; 320 time_t tmptime; 321 /* Start with the current time. */ 322 tmptime = tsp[0].tv_sec; 323 if ((t = localtime(&tmptime)) == NULL) 324 err(EXIT_FAILURE, "localtime"); 325 326 t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ 327 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 328 t->tm_mday = ATOI2(arg); 329 t->tm_hour = ATOI2(arg); 330 t->tm_min = ATOI2(arg); 331 if (year) { 332 year = ATOI2(arg); 333 if (year < YEAR_BOUNDARY) 334 t->tm_year = year + LOW_YEAR_CENTURY - TM_YEAR_BASE; 335 else 336 t->tm_year = year + HIGH_YEAR_CENTURY - TM_YEAR_BASE; 337 } 338 t->tm_sec = 0; 339 340 t->tm_isdst = -1; /* Figure out DST. */ 341 tm = *t; 342 tsp[0].tv_sec = tsp[1].tv_sec = mktime(t); 343 if (tsp[0].tv_sec == NO_TIME || difftm(t, &tm)) 344 errx(EXIT_FAILURE, 345 "out of range or bad time specification: MMDDhhmm[YY]"); 346 347 tsp[0].tv_nsec = tsp[1].tv_nsec = 0; 348} 349 350static void 351stime_file(const char *fname, struct timespec *tsp, 352 int statfunc(const char *, struct stat *)) 353{ 354 struct stat sb; 355 356 if (statfunc(fname, &sb)) 357 err(1, "%s", fname); 358 tsp[0] = sb.st_atimespec; 359 tsp[1] = sb.st_mtimespec; 360} 361 362static int 363stime_posix(const char *arg, struct timespec *tsp) 364{ 365 struct tm tm, tms; 366 const char *p; 367 char *ep; 368 int utc = 0; 369 long val; 370 371#define isdigch(c) (isdigit((int)((unsigned char)(c)))) 372 373 if ((p = strchr(arg, '-')) == NULL) 374 return 0; 375 if (p - arg < 4) /* at least 4 year digits required */ 376 return 0; 377 378 if (!isdigch(arg[0])) /* and the first must be a digit! */ 379 return 0; 380 381 (void)memset(&tm, 0, sizeof tm); 382 383 errno = 0; 384 val = strtol(arg, &ep, 10); /* YYYY */ 385 if (val < 0 || val > INT_MAX) 386 return 0; 387 if (*ep != '-') 388 return 0; 389 tm.tm_year = (int)val - 1900; 390 391 p = ep + 1; 392 393 if (!isdigch(*p)) 394 return 0; 395 val = strtol(p, &ep, 10); /* MM */ 396 if (val < 1 || val > 12) 397 return 0; 398 if (*ep != '-' || ep != p + 2) 399 return 0; 400 tm.tm_mon = (int)val - 1; 401 402 p = ep + 1; 403 404 if (!isdigch(*p)) 405 return 0; 406 val = strtol(p, &ep, 10); /* DD */ 407 if (val < 1 || val > 31) 408 return 0; 409 if ((*ep != 'T' && *ep != ' ') || ep != p + 2) 410 return 0; 411 tm.tm_mday = (int)val; 412 413 p = ep + 1; 414 415 if (!isdigch(*p)) 416 return 0; 417 val = strtol(p, &ep, 10); /* hh */ 418 if (val < 0 || val > 23) 419 return 0; 420 if (*ep != ':' || ep != p + 2) 421 return 0; 422 tm.tm_hour = (int)val; 423 424 p = ep + 1; 425 426 if (!isdigch(*p)) 427 return 0; 428 val = strtol(p, &ep, 10); /* mm */ 429 if (val < 0 || val > 59) 430 return 0; 431 if (*ep != ':' || ep != p + 2) 432 return 0; 433 tm.tm_min = (int)val; 434 435 p = ep + 1; 436 437 if (!isdigch(*p)) 438 return 0; 439 val = strtol(p, &ep, 10); /* ss (or in POSIX, SS) */ 440 if (val < 0 || val > 60) 441 return 0; 442 if ((*ep != '.' && *ep != ',' && *ep != 'Z' && *ep != '\0') || 443 ep != p + 2) 444 return 0; 445 tm.tm_sec = (int)val; 446 447 if (*ep == ',' || *ep == '.') { 448 double frac; 449 ptrdiff_t fdigs; 450 451 p = ep + 1; 452 if (!isdigch(*p)) 453 return 0; 454 val = strtol(p, &ep, 10); 455 if (val < 0) 456 return 0; 457 if (ep == p) /* at least 1 digit required */ 458 return 0; 459 if (*ep != 'Z' && *ep != '\0') 460 return 0; 461 462 if (errno != 0) 463 return 0; 464 465 fdigs = ep - p; 466 if (fdigs > 15) { 467 /* avoid being absurd */ 468 /* don't want to risk 10^fdigs being INF */ 469 if (val == 0) 470 fdigs = 1; 471 else while (fdigs > 15) { 472 val = (val + 5) / 10; 473 fdigs--; 474 } 475 } 476 477 frac = pow(10.0, (double)fdigs); 478 479 tsp[0].tv_nsec = tsp[1].tv_nsec = 480 (long)round(((double)val / frac) * 1000000000.0); 481 } else 482 tsp[0].tv_nsec = tsp[1].tv_nsec = 0; 483 484 if (*ep == 'Z') { 485 if (ep[1] != '\0') 486 return 0; 487 utc = 1; 488 } 489 490 if (errno != 0) 491 return 0; 492 493 tm.tm_isdst = -1; 494 tms = tm; 495 if (utc) 496 tsp[0].tv_sec = tsp[1].tv_sec = timegm(&tm); 497 else 498 tsp[0].tv_sec = tsp[1].tv_sec = mktime(&tm); 499 500 if ((errno != 0 && tsp[1].tv_sec == NO_TIME) || difftm(&tm, &tms)) 501 return 0; 502 503 return 1; 504} 505 506/* 507 * Determine whether 2 struct tn's are different 508 * return true (1) if theu are, false (0) otherwise. 509 * 510 * Note that we only consider the fields that are set 511 * for mktime() to use - if mktime() returns them 512 * differently than was set, then there was a problem 513 * with the setting. 514 */ 515static int 516difftm(const struct tm *t1, const struct tm *t2) 517{ 518#define CHK(fld) do { \ 519 if (t1->tm_##fld != t2->tm_##fld) { \ 520 return 1; \ 521 } \ 522 } while(/*CONSTCOND*/0) 523 524 CHK(year); 525 CHK(mon); 526 CHK(mday); 527 CHK(hour); 528 CHK(min); 529 CHK(sec); 530 531 return 0; 532} 533 534static void 535usage(void) 536{ 537 (void)fprintf(stderr, 538 "Usage: %s [-acDfhm] [-d|--date datetime] [-R|-r|--reference file]" 539 " [-t time] file ...\n", getprogname()); 540 exit(EXIT_FAILURE); 541} 542