1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* Copyright (c) 1987, 1988 Microsoft Corporation */ 31 /* All Rights Reserved */ 32 33 #include <sys/types.h> 34 #include <sys/stat.h> 35 #include <stdio.h> 36 #include <string.h> 37 #include <strings.h> 38 #include <stdlib.h> 39 #include <ctype.h> 40 #include <libgen.h> 41 #include <fcntl.h> 42 #include <pwd.h> 43 #include <time.h> 44 #include <unistd.h> 45 #include <locale.h> 46 #include <sys/time.h> 47 #include <errno.h> 48 49 #define BADTIME "bad time specification" 50 51 static char *myname; 52 53 static int isnumber(char *); 54 static int atoi_for2(char *); 55 static void usage(const int); 56 static void touchabort(const char *); 57 static void parse_datetime(char *, timespec_t *); 58 static void parse_time(char *, timespec_t *); 59 static void parse_timespec(char *, timespec_t *); 60 61 int 62 main(int argc, char *argv[]) 63 { 64 int c; 65 66 int aflag = 0; 67 int cflag = 0; 68 int rflag = 0; 69 int mflag = 0; 70 int tflag = 0; 71 int stflag = 0; 72 int status = 0; 73 int usecurrenttime = 1; 74 int timespecified; 75 int optc; 76 int fd = -1; 77 mode_t cmode = 78 (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 79 struct stat stbuf; 80 struct stat prstbuf; 81 timespec_t times[2]; 82 timespec_t *tsp; 83 84 (void) setlocale(LC_ALL, ""); 85 #if !defined(TEXT_DOMAIN) 86 #define TEXT_DOMAIN "SYS_TEST" 87 #endif 88 (void) textdomain(TEXT_DOMAIN); 89 90 myname = basename(argv[0]); 91 if (strcmp(myname, "settime") == 0) { 92 cflag++; 93 stflag++; 94 while ((optc = getopt(argc, argv, "f:")) != EOF) { 95 switch (optc) { 96 case 'f': 97 rflag++; 98 usecurrenttime = 0; 99 if (stat(optarg, &prstbuf) == -1) { 100 (void) fprintf(stderr, "%s: ", myname); 101 perror(optarg); 102 return (2); 103 } 104 break; 105 case '?': 106 usage(stflag); 107 break; 108 } 109 } 110 } else { 111 while ((optc = getopt(argc, argv, "acfmr:d:t:")) != EOF) { 112 switch (optc) { 113 case 'a': 114 aflag++; 115 break; 116 case 'c': 117 cflag++; 118 break; 119 case 'f': /* silently ignore for UCB compat */ 120 break; 121 case 'm': 122 mflag++; 123 break; 124 case 'r': /* same as settime's -f option */ 125 rflag++; 126 usecurrenttime = 0; 127 if (stat(optarg, &prstbuf) == -1) { 128 (void) fprintf(stderr, "%s: ", myname); 129 perror(optarg); 130 return (2); 131 } 132 break; 133 case 'd': 134 tflag++; 135 usecurrenttime = 0; 136 parse_datetime(optarg, &prstbuf.st_mtim); 137 prstbuf.st_atim = prstbuf.st_mtim; 138 break; 139 case 't': 140 tflag++; 141 usecurrenttime = 0; 142 parse_time(optarg, &prstbuf.st_mtim); 143 prstbuf.st_atim = prstbuf.st_mtim; 144 break; 145 case '?': 146 usage(stflag); 147 break; 148 } 149 } 150 } 151 152 argc -= optind; 153 argv += optind; 154 155 if ((argc < 1) || (rflag + tflag > 1)) 156 usage(stflag); 157 158 if ((aflag == 0) && (mflag == 0)) { 159 aflag = 1; 160 mflag = 1; 161 } 162 if ((aflag && !mflag) || (mflag && !aflag)) 163 usecurrenttime = 0; 164 165 /* 166 * If -r, -t or -d has been specified, 167 * use the specified time. 168 */ 169 timespecified = (rflag | tflag); 170 171 if (timespecified == 0 && argc >= 2 && isnumber(*argv) && 172 (strlen(*argv) == 8 || strlen(*argv) == 10)) { 173 /* 174 * time is specified as an operand; use it. 175 */ 176 parse_timespec(*argv++, &prstbuf.st_mtim); 177 prstbuf.st_atim = prstbuf.st_mtim; 178 usecurrenttime = 0; 179 timespecified = 1; 180 argc--; 181 } 182 183 for (c = 0; c < argc; c++) { 184 if (stat(argv[c], &stbuf)) { 185 /* 186 * If stat failed for reasons other than EOVERFLOW or 187 * ENOENT, the file should not be created, since this 188 * can clobber the contents of an existing file. 189 */ 190 if (errno == EOVERFLOW) { 191 /* 192 * Since we have EOVERFLOW, 193 * we know the file exists. 194 */ 195 /* EMPTY */; 196 } else if (errno != ENOENT) { 197 (void) fprintf(stderr, 198 gettext("%s: cannot stat %s: %s\n"), 199 myname, argv[c], strerror(errno)); 200 status++; 201 continue; 202 } else if (cflag) { 203 continue; 204 } else if ((fd = creat(argv[c], cmode)) < 0) { 205 (void) fprintf(stderr, 206 gettext("%s: cannot create %s: %s\n"), 207 myname, argv[c], strerror(errno)); 208 status++; 209 continue; 210 } 211 } 212 213 if (usecurrenttime) { 214 tsp = NULL; 215 } else { 216 if (mflag == 0) { 217 /* Keep the mtime of the file */ 218 times[1].tv_nsec = UTIME_OMIT; 219 } else if (timespecified) { 220 /* Set the specified time */ 221 times[1] = prstbuf.st_mtim; 222 } else { 223 /* Otherwise, use the current time */ 224 times[1].tv_nsec = UTIME_NOW; 225 } 226 227 if (aflag == 0) { 228 /* Keep the atime of the file */ 229 times[0].tv_nsec = UTIME_OMIT; 230 } else if (timespecified) { 231 /* Set the specified time */ 232 times[0] = prstbuf.st_atim; 233 } else { 234 /* Otherwise, use the current time */ 235 times[0].tv_nsec = UTIME_NOW; 236 } 237 238 tsp = times; 239 } 240 241 if ((fd >= 0 && futimens(fd, tsp) != 0) || 242 (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) { 243 (void) fprintf(stderr, 244 gettext("%s: cannot change times on %s: %s\n"), 245 myname, argv[c], strerror(errno)); 246 status++; 247 } 248 if (fd >= 0) { 249 (void) close(fd); 250 fd = -1; 251 } 252 } 253 return (status); 254 } 255 256 static int 257 isnumber(char *s) 258 { 259 int c; 260 261 while ((c = *s++) != '\0') 262 if (!isdigit(c)) 263 return (0); 264 return (1); 265 } 266 267 static void 268 parse_datetime(char *t, timespec_t *ts) 269 { 270 char date[64]; 271 char *year; 272 char *month; 273 char *day; 274 char *hour; 275 char *minute; 276 char *second; 277 char *fraction; 278 int utc = 0; 279 char *p; 280 time_t when; 281 int nanoseconds; 282 struct tm tm; 283 284 /* 285 * The date string has the format (defined by the touch(1) spec): 286 * YYYY-MM-DDThh:mm:SS[.frac][tz] 287 * YYYY-MM-DDThh:mm:SS[,frac][tz] 288 * T is either the literal 'T' or is a space character. 289 * tz is either empty (local time) or the literal 'Z' (UTC). 290 * All other fields are strings of digits. 291 */ 292 293 /* 294 * Make a copy of the date string so it can be tokenized. 295 */ 296 if (strlcpy(date, t, sizeof (date)) >= sizeof (date)) 297 touchabort(BADTIME); 298 299 /* deal with the optional trailing 'Z' first */ 300 p = date + strlen(date) - 1; 301 if (*p == 'Z') { 302 utc = 1; 303 *p = '\0'; 304 } 305 306 /* break out the component tokens */ 307 p = date; 308 year = strsep(&p, "-"); 309 month = strsep(&p, "-"); 310 day = strsep(&p, "T "); 311 hour = strsep(&p, ":"); 312 minute = strsep(&p, ":"); 313 second = strsep(&p, ".,"); 314 fraction = p; 315 316 /* verify the component tokens */ 317 if (year == NULL || strlen(year) < 4 || !isnumber(year) || 318 month == NULL || strlen(month) != 2 || !isnumber(month) || 319 day == NULL || strlen(day) != 2 || !isnumber(day) || 320 hour == NULL || strlen(hour) != 2 || !isnumber(hour) || 321 minute == NULL || strlen(minute) != 2 || !isnumber(minute) || 322 second == NULL || strlen(second) != 2 || !isnumber(second) || 323 (fraction != NULL && (*fraction == '\0' || !isnumber(fraction)))) 324 touchabort(BADTIME); 325 326 (void) memset(&tm, 0, sizeof (struct tm)); 327 328 tm.tm_year = atoi(year) - 1900; 329 tm.tm_mon = atoi(month) - 1; 330 tm.tm_mday = atoi(day); 331 tm.tm_hour = atoi(hour); 332 tm.tm_min = atoi(minute); 333 tm.tm_sec = atoi(second); 334 if (utc) { 335 (void) setenv("TZ", "GMT0", 1); 336 tzset(); 337 } 338 339 errno = 0; 340 if ((when = mktime(&tm)) == -1 && errno != 0) 341 touchabort(BADTIME); 342 if (tm.tm_isdst) 343 when -= (timezone - altzone); 344 345 if (fraction == NULL) { 346 nanoseconds = 0; 347 } else { 348 /* truncate beyond 9 digits (nanoseconds) */ 349 if (strlen(fraction) > 9) 350 fraction[9] = '\0'; 351 nanoseconds = atoi(fraction); 352 353 switch (strlen(fraction)) { 354 case 1: 355 nanoseconds *= 100000000; 356 break; 357 case 2: 358 nanoseconds *= 10000000; 359 break; 360 case 3: 361 nanoseconds *= 1000000; 362 break; 363 case 4: 364 nanoseconds *= 100000; 365 break; 366 case 5: 367 nanoseconds *= 10000; 368 break; 369 case 6: 370 nanoseconds *= 1000; 371 break; 372 case 7: 373 nanoseconds *= 100; 374 break; 375 case 8: 376 nanoseconds *= 10; 377 break; 378 case 9: 379 break; 380 } 381 } 382 383 ts->tv_sec = when; 384 ts->tv_nsec = nanoseconds; 385 } 386 387 static void 388 parse_time(char *t, timespec_t *ts) 389 { 390 int century = 0; 391 int seconds = 0; 392 char *p; 393 time_t when; 394 struct tm tm; 395 396 /* 397 * time in the following format (defined by the touch(1) spec): 398 * [[CC]YY]MMDDhhmm[.SS] 399 */ 400 if ((p = strchr(t, '.')) != NULL) { 401 if (strchr(p+1, '.') != NULL) 402 touchabort(BADTIME); 403 seconds = atoi_for2(p+1); 404 *p = '\0'; 405 } 406 407 (void) memset(&tm, 0, sizeof (struct tm)); 408 when = time(0); 409 tm.tm_year = localtime(&when)->tm_year; 410 411 switch (strlen(t)) { 412 case 12: /* CCYYMMDDhhmm */ 413 century = atoi_for2(t); 414 t += 2; 415 /* FALLTHROUGH */ 416 case 10: /* YYMMDDhhmm */ 417 tm.tm_year = atoi_for2(t); 418 t += 2; 419 if (century == 0) { 420 if (tm.tm_year < 69) 421 tm.tm_year += 100; 422 } else 423 tm.tm_year += (century - 19) * 100; 424 /* FALLTHROUGH */ 425 case 8: /* MMDDhhmm */ 426 tm.tm_mon = atoi_for2(t) - 1; 427 t += 2; 428 tm.tm_mday = atoi_for2(t); 429 t += 2; 430 tm.tm_hour = atoi_for2(t); 431 t += 2; 432 tm.tm_min = atoi_for2(t); 433 tm.tm_sec = seconds; 434 break; 435 default: 436 touchabort(BADTIME); 437 } 438 439 if ((when = mktime(&tm)) == -1) 440 touchabort(BADTIME); 441 if (tm.tm_isdst) 442 when -= (timezone-altzone); 443 444 ts->tv_sec = when; 445 ts->tv_nsec = 0; 446 } 447 448 static void 449 parse_timespec(char *t, timespec_t *ts) 450 { 451 time_t when; 452 struct tm tm; 453 454 /* 455 * time in the following format (defined by the touch(1) spec): 456 * MMDDhhmm[yy] 457 */ 458 459 (void) memset(&tm, 0, sizeof (struct tm)); 460 when = time(0); 461 tm.tm_year = localtime(&when)->tm_year; 462 463 switch (strlen(t)) { 464 case 10: /* MMDDhhmmyy */ 465 tm.tm_year = atoi_for2(t+8); 466 if (tm.tm_year < 69) 467 tm.tm_year += 100; 468 /* FALLTHROUGH */ 469 case 8: /* MMDDhhmm */ 470 tm.tm_mon = atoi_for2(t) - 1; 471 t += 2; 472 tm.tm_mday = atoi_for2(t); 473 t += 2; 474 tm.tm_hour = atoi_for2(t); 475 t += 2; 476 tm.tm_min = atoi_for2(t); 477 break; 478 default: 479 touchabort(BADTIME); 480 } 481 482 if ((when = mktime(&tm)) == -1) 483 touchabort(BADTIME); 484 if (tm.tm_isdst) 485 when -= (timezone - altzone); 486 487 ts->tv_sec = when; 488 ts->tv_nsec = 0; 489 } 490 491 static int 492 atoi_for2(char *p) 493 { 494 int value; 495 496 value = (*p - '0') * 10 + *(p+1) - '0'; 497 if ((value < 0) || (value > 99)) 498 touchabort(BADTIME); 499 return (value); 500 } 501 502 static void 503 touchabort(const char *message) 504 { 505 (void) fprintf(stderr, "%s: %s\n", myname, gettext(message)); 506 exit(1); 507 } 508 509 static void 510 usage(const int settime) 511 { 512 if (settime) { 513 (void) fprintf(stderr, gettext( 514 "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname); 515 exit(2); 516 } 517 (void) fprintf(stderr, gettext( 518 "usage: %s [-acm] [-r ref_file] file...\n" 519 " %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n" 520 " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n" 521 " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n" 522 " %s [-acm] [MMDDhhmm[yy]] file...\n"), 523 myname, myname, myname, myname, myname); 524 exit(2); 525 } 526