/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1987, 1988 Microsoft Corporation */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BADTIME "bad time specification" static char *myname; static int isnumber(char *); static int atoi_for2(char *); static void usage(const int); static void touchabort(const char *); static void parse_datetime(char *, timespec_t *); static void parse_time(char *, timespec_t *); static void parse_timespec(char *, timespec_t *); int main(int argc, char *argv[]) { int c; int aflag = 0; int cflag = 0; int rflag = 0; int mflag = 0; int tflag = 0; int stflag = 0; int status = 0; int usecurrenttime = 1; int timespecified; int optc; int fd = -1; mode_t cmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); struct stat stbuf; struct stat prstbuf; timespec_t times[2]; timespec_t *tsp; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); myname = basename(argv[0]); if (strcmp(myname, "settime") == 0) { cflag++; stflag++; while ((optc = getopt(argc, argv, "f:")) != EOF) { switch (optc) { case 'f': rflag++; usecurrenttime = 0; if (stat(optarg, &prstbuf) == -1) { (void) fprintf(stderr, "%s: ", myname); perror(optarg); return (2); } break; case '?': usage(stflag); break; } } } else { while ((optc = getopt(argc, argv, "acfmr:d:t:")) != EOF) { switch (optc) { case 'a': aflag++; break; case 'c': cflag++; break; case 'f': /* silently ignore for UCB compat */ break; case 'm': mflag++; break; case 'r': /* same as settime's -f option */ rflag++; usecurrenttime = 0; if (stat(optarg, &prstbuf) == -1) { (void) fprintf(stderr, "%s: ", myname); perror(optarg); return (2); } break; case 'd': tflag++; usecurrenttime = 0; parse_datetime(optarg, &prstbuf.st_mtim); prstbuf.st_atim = prstbuf.st_mtim; break; case 't': tflag++; usecurrenttime = 0; parse_time(optarg, &prstbuf.st_mtim); prstbuf.st_atim = prstbuf.st_mtim; break; case '?': usage(stflag); break; } } } argc -= optind; argv += optind; if ((argc < 1) || (rflag + tflag > 1)) usage(stflag); if ((aflag == 0) && (mflag == 0)) { aflag = 1; mflag = 1; } if ((aflag && !mflag) || (mflag && !aflag)) usecurrenttime = 0; /* * If -r, -t or -d has been specified, * use the specified time. */ timespecified = (rflag | tflag); if (timespecified == 0 && argc >= 2 && isnumber(*argv) && (strlen(*argv) == 8 || strlen(*argv) == 10)) { /* * time is specified as an operand; use it. */ parse_timespec(*argv++, &prstbuf.st_mtim); prstbuf.st_atim = prstbuf.st_mtim; usecurrenttime = 0; timespecified = 1; argc--; } for (c = 0; c < argc; c++) { if (stat(argv[c], &stbuf)) { /* * If stat failed for reasons other than EOVERFLOW or * ENOENT, the file should not be created, since this * can clobber the contents of an existing file. */ if (errno == EOVERFLOW) { /* * Since we have EOVERFLOW, * we know the file exists. */ /* EMPTY */; } else if (errno != ENOENT) { (void) fprintf(stderr, gettext("%s: cannot stat %s: %s\n"), myname, argv[c], strerror(errno)); status++; continue; } else if (cflag) { continue; } else if ((fd = creat(argv[c], cmode)) < 0) { (void) fprintf(stderr, gettext("%s: cannot create %s: %s\n"), myname, argv[c], strerror(errno)); status++; continue; } } if (usecurrenttime) { tsp = NULL; } else { if (mflag == 0) { /* Keep the mtime of the file */ times[1].tv_nsec = UTIME_OMIT; } else if (timespecified) { /* Set the specified time */ times[1] = prstbuf.st_mtim; } else { /* Otherwise, use the current time */ times[1].tv_nsec = UTIME_NOW; } if (aflag == 0) { /* Keep the atime of the file */ times[0].tv_nsec = UTIME_OMIT; } else if (timespecified) { /* Set the specified time */ times[0] = prstbuf.st_atim; } else { /* Otherwise, use the current time */ times[0].tv_nsec = UTIME_NOW; } tsp = times; } if ((fd >= 0 && futimens(fd, tsp) != 0) || (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) { (void) fprintf(stderr, gettext("%s: cannot change times on %s: %s\n"), myname, argv[c], strerror(errno)); status++; } if (fd >= 0) { (void) close(fd); fd = -1; } } return (status); } static int isnumber(char *s) { int c; while ((c = *s++) != '\0') if (!isdigit(c)) return (0); return (1); } static void parse_datetime(char *t, timespec_t *ts) { char date[64]; char *year; char *month; char *day; char *hour; char *minute; char *second; char *fraction; int utc = 0; char *p; time_t when; int nanoseconds; struct tm tm; /* * The date string has the format (defined by the touch(1) spec): * YYYY-MM-DDThh:mm:SS[.frac][tz] * YYYY-MM-DDThh:mm:SS[,frac][tz] * T is either the literal 'T' or is a space character. * tz is either empty (local time) or the literal 'Z' (UTC). * All other fields are strings of digits. */ /* * Make a copy of the date string so it can be tokenized. */ if (strlcpy(date, t, sizeof (date)) >= sizeof (date)) touchabort(BADTIME); /* deal with the optional trailing 'Z' first */ p = date + strlen(date) - 1; if (*p == 'Z') { utc = 1; *p = '\0'; } /* break out the component tokens */ p = date; year = strsep(&p, "-"); month = strsep(&p, "-"); day = strsep(&p, "T "); hour = strsep(&p, ":"); minute = strsep(&p, ":"); second = strsep(&p, ".,"); fraction = p; /* verify the component tokens */ if (year == NULL || strlen(year) < 4 || !isnumber(year) || month == NULL || strlen(month) != 2 || !isnumber(month) || day == NULL || strlen(day) != 2 || !isnumber(day) || hour == NULL || strlen(hour) != 2 || !isnumber(hour) || minute == NULL || strlen(minute) != 2 || !isnumber(minute) || second == NULL || strlen(second) != 2 || !isnumber(second) || (fraction != NULL && (*fraction == '\0' || !isnumber(fraction)))) touchabort(BADTIME); (void) memset(&tm, 0, sizeof (struct tm)); tm.tm_year = atoi(year) - 1900; tm.tm_mon = atoi(month) - 1; tm.tm_mday = atoi(day); tm.tm_hour = atoi(hour); tm.tm_min = atoi(minute); tm.tm_sec = atoi(second); if (utc) { (void) setenv("TZ", "GMT0", 1); tzset(); } errno = 0; if ((when = mktime(&tm)) == -1 && errno != 0) touchabort(BADTIME); if (tm.tm_isdst) when -= (timezone - altzone); if (fraction == NULL) { nanoseconds = 0; } else { /* truncate beyond 9 digits (nanoseconds) */ if (strlen(fraction) > 9) fraction[9] = '\0'; nanoseconds = atoi(fraction); switch (strlen(fraction)) { case 1: nanoseconds *= 100000000; break; case 2: nanoseconds *= 10000000; break; case 3: nanoseconds *= 1000000; break; case 4: nanoseconds *= 100000; break; case 5: nanoseconds *= 10000; break; case 6: nanoseconds *= 1000; break; case 7: nanoseconds *= 100; break; case 8: nanoseconds *= 10; break; case 9: break; } } ts->tv_sec = when; ts->tv_nsec = nanoseconds; } static void parse_time(char *t, timespec_t *ts) { int century = 0; int seconds = 0; char *p; time_t when; struct tm tm; /* * time in the following format (defined by the touch(1) spec): * [[CC]YY]MMDDhhmm[.SS] */ if ((p = strchr(t, '.')) != NULL) { if (strchr(p+1, '.') != NULL) touchabort(BADTIME); seconds = atoi_for2(p+1); *p = '\0'; } (void) memset(&tm, 0, sizeof (struct tm)); when = time(0); tm.tm_year = localtime(&when)->tm_year; switch (strlen(t)) { case 12: /* CCYYMMDDhhmm */ century = atoi_for2(t); t += 2; /* FALLTHROUGH */ case 10: /* YYMMDDhhmm */ tm.tm_year = atoi_for2(t); t += 2; if (century == 0) { if (tm.tm_year < 69) tm.tm_year += 100; } else tm.tm_year += (century - 19) * 100; /* FALLTHROUGH */ case 8: /* MMDDhhmm */ tm.tm_mon = atoi_for2(t) - 1; t += 2; tm.tm_mday = atoi_for2(t); t += 2; tm.tm_hour = atoi_for2(t); t += 2; tm.tm_min = atoi_for2(t); tm.tm_sec = seconds; break; default: touchabort(BADTIME); } if ((when = mktime(&tm)) == -1) touchabort(BADTIME); if (tm.tm_isdst) when -= (timezone-altzone); ts->tv_sec = when; ts->tv_nsec = 0; } static void parse_timespec(char *t, timespec_t *ts) { time_t when; struct tm tm; /* * time in the following format (defined by the touch(1) spec): * MMDDhhmm[yy] */ (void) memset(&tm, 0, sizeof (struct tm)); when = time(0); tm.tm_year = localtime(&when)->tm_year; switch (strlen(t)) { case 10: /* MMDDhhmmyy */ tm.tm_year = atoi_for2(t+8); if (tm.tm_year < 69) tm.tm_year += 100; /* FALLTHROUGH */ case 8: /* MMDDhhmm */ tm.tm_mon = atoi_for2(t) - 1; t += 2; tm.tm_mday = atoi_for2(t); t += 2; tm.tm_hour = atoi_for2(t); t += 2; tm.tm_min = atoi_for2(t); break; default: touchabort(BADTIME); } if ((when = mktime(&tm)) == -1) touchabort(BADTIME); if (tm.tm_isdst) when -= (timezone - altzone); ts->tv_sec = when; ts->tv_nsec = 0; } static int atoi_for2(char *p) { int value; value = (*p - '0') * 10 + *(p+1) - '0'; if ((value < 0) || (value > 99)) touchabort(BADTIME); return (value); } static void touchabort(const char *message) { (void) fprintf(stderr, "%s: %s\n", myname, gettext(message)); exit(1); } static void usage(const int settime) { if (settime) { (void) fprintf(stderr, gettext( "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname); exit(2); } (void) fprintf(stderr, gettext( "usage: %s [-acm] [-r ref_file] file...\n" " %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n" " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n" " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n" " %s [-acm] [MMDDhhmm[yy]] file...\n"), myname, myname, myname, myname, myname); exit(2); }