diff --git a/usr.bin/touch/touch.1 b/usr.bin/touch/touch.1 index f5bfd854d568..81fd5af81e21 100644 --- a/usr.bin/touch/touch.1 +++ b/usr.bin/touch/touch.1 @@ -32,44 +32,136 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" @(#)touch.1 6.4 (Berkeley) 6/27/91 +.\" @(#)touch.1 6.5 (Berkeley) 3/6/93 .\" -.Dd June 27, 1991 +.Dd March 6, 1993 .Dt TOUCH 1 .Os .Sh NAME .Nm touch -.Nd update date last modified of a file +.Nd change file access and modification times .Sh SYNOPSIS .Nm touch -.Op Fl c -.Op Fl f +.Op Fl acfm +.Op Fl r Ar file +.Op Fl t [[CC]YY]MMDDhhmm[.SS] .Ar file ... .Sh DESCRIPTION The .Nm touch -utility -changes the modification and or access times -of the given -.Ar file . +utility sets the modification and access times of files to the +current time of day. +If the file doesn't exist, it is created with default permissions. .Pp -Available options: +The following options are available: .Bl -tag -width Ds +.It Fl a +Change the access time of the file. +The modification time of the file is not changed unless the +.Fl m +flag is also specified. .It Fl c -Do not create a specified file if it does not exist. -Do not write any diagnostic messages concerning this -condition. -.It Fl f -Attempt to force the +Do not create the file if it does not exist. +The .Nm touch -in spite of read and write permissions on a -.Ar file . +utility does not treat this as an error. +No error messages are displayed and the exit value is not affected. +.It Fl f +Attempt to force the update, even if the file permissions do not +currently permit it. +.It Fl m +Change the modification time of the file. +The access time of the file is not changed unless the +.Fl a +flag is also specified. +.It Fl r +Use the access and modifications times from the specified file +instead of the current time of day. +.It Fl t +Change the access and modification times to the specified time. +The argument should be in the form +.Dq [[CC]YY]MMDDhhmm[.SS] +where each pair of letters represents the following: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar CC +The first two digits of the year (the century). +.It Ar YY +The second two digits of the year. +If +.Dq YY +is specified, but +.Dq CC +is not, a value for +.Dq YY +between 69 and 99 results in a +.Dq YY +value of 19. +Otherwise, a +.Dq YY +value of 20 is used. +.It Ar MM +The month of the year, from 1 to 12. +.It Ar DD +the day of the month, from 1 to 31. +.It Ar hh +The hour of the day, from 0 to 23. +.It Ar mm +The minute of the hour, from 0 to 59. +.It Ar SS +The second of the minute, from 0 to 61. +.El +.Pp +If the +.Dq CC +and +.Dq YY +letter pairs are not specified, the values default to the current +year. +If the +.Dq SS +letter pair is not specified, the value defaults to 0. +.El +.Pp +The +.Nm touch +utility exits 0 on success, and >0 if an error occurs. .Sh SEE ALSO .Xr utimes 2 +.Sh COMPATIBILITY +The obsolescent form of +.Nm touch , +where a time format is specified as the first argument, is supported. +When no +.Fl r +or +.Fl t +option is specified, there are at least two arguments, and the first +argument is a string of digits either eight or ten characters in length, +the first argument is interprested as a time specification of the form +.Dq MMDDhhmm[YY] . +.Pp +The +.Dq MM , +.Dq DD , +.Dq hh +and +.Dq mm +letter pairs are treated as their counterparts specified to the +.Fl t +option. +If the +.Dq YY +letter pair is in the range 69 to 99, the year is set to 1969 to 1999, +otherwise, the year is set in the 21st century. .Sh HISTORY A -.Nm +.Nm touch command appeared in .At v7 . -This command is -.Ud . +.Sh STANDARDS +The +.Nm touch +function is expected to be a superset of the +.St -p1003.2 +specification. diff --git a/usr.bin/touch/touch.c b/usr.bin/touch/touch.c index cb7bffeaacfb..0fe54351fa1b 100644 --- a/usr.bin/touch/touch.c +++ b/usr.bin/touch/touch.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1988 Regents of the University of California. + * Copyright (c) 1993 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,129 +33,318 @@ #ifndef lint char copyright[] = -"@(#) Copyright (c) 1988 Regents of the University of California.\n\ +"@(#) Copyright (c) 1993 Regents of the University of California.\n\ All rights reserved.\n"; #endif /* not lint */ #ifndef lint -static char sccsid[] = "@(#)touch.c 4.8 (Berkeley) 6/1/90"; +static char sccsid[] = "@(#)touch.c 5.5 (Berkeley) 3/7/93"; #endif /* not lint */ -/* - * Attempt to set the modify date of a file to the current date. If the - * file exists, read and write its first character. If the file doesn't - * exist, create it, unless -c option prevents it. If the file is read-only, - * -f forces chmod'ing and touch'ing. - */ #include -#include #include +#include + +#include +#include +#include #include +#include +#include +#include +#include -static int dontcreate; /* set if -c option */ -static int force; /* set if -f option */ +int rw __P((char *, struct stat *, int)); +void stime_arg1 __P((char *, struct timeval *)); +void stime_arg2 __P((char *, int, struct timeval *)); +void stime_file __P((char *, struct timeval *)); +void usage __P((void)); +/*temporary we aren't 4.4 portability hack*/ +#define TIMET_TO_TIMEVAL(timevalp, tp) (timevalp)->tv_sec = *(tp); + +int main(argc, argv) int argc; - char **argv; + char *argv[]; { - extern int optind; - int ch, retval; + struct stat sb; + struct timeval tv[2]; + int aflag, cflag, fflag, mflag, ch, fd, len, rval, timeset; + char *p; - dontcreate = force = retval = 0; - while ((ch = getopt(argc, argv, "cf")) != EOF) - switch((char)ch) { + aflag = cflag = fflag = mflag = timeset = 0; + if (gettimeofday(&tv[0], NULL)) + err(1, "gettimeofday"); + + while ((ch = getopt(argc, argv, "acfmr:t:")) != EOF) + switch(ch) { + case 'a': + aflag = 1; + break; case 'c': - dontcreate = 1; + cflag = 1; break; case 'f': - force = 1; + fflag = 1; + break; + case 'm': + mflag = 1; + break; + case 'r': + timeset = 1; + stime_file(optarg, tv); + break; + case 't': + timeset = 1; + stime_arg1(optarg, tv); break; case '?': default: usage(); } - if (!*(argv += optind)) + argc -= optind; + argv += optind; + + /* Default is both -a and -m. */ + if (aflag == 0 && mflag == 0) + aflag = mflag = 1; + + /* + * If no -r or -t flag, at least two operands, the first of which + * is an 8 or 10 digit number, use the obsolete time specification. + */ + if (!timeset && argc > 1) { + (void)strtol(argv[0], &p, 10); + len = p - argv[0]; + if (*p == '\0' && (len == 8 || len == 10)) { + timeset = 1; + stime_arg2(argv[0], len == 10, tv); + } + } + + /* Otherwise use the current time of day. */ + if (!timeset) + tv[1] = tv[0]; + + if (*argv == NULL) usage(); - do { - retval |= touch(*argv); - } while (*++argv); - exit(retval); + + for (rval = 0; *argv; ++argv) { + /* See if the file exists. */ + if (stat(*argv, &sb)) + if (!cflag) { + /* Create the file. */ + fd = open(*argv, + O_WRONLY | O_CREAT, DEFFILEMODE); + if (fd == -1 || fstat(fd, &sb) || close(fd)) { + rval = 1; + warn("%s", *argv); + continue; + } + + /* If using the current time, we're done. */ + if (!timeset) + continue; + } else + continue; + + if (!aflag) +/* TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec);*/ + TIMET_TO_TIMEVAL(&tv[0], &sb.st_atime); + if (!mflag) +/* TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);*/ + TIMET_TO_TIMEVAL(&tv[1], &sb.st_mtime); + + + /* Try utimes(2). */ + if (!utimes(*argv, tv)) + continue; + + /* If the user specified a time, nothing else we can do. */ + if (timeset) { + rval = 1; + warn("%s", *argv); + } + + /* + * System V and POSIX 1003.1 require that a NULL argument + * set the access/modification times to the current time. + * The permission checks are different, too, in that the + * ability to write the file is sufficient. Take a shot. + */ + if (!utimes(*argv, NULL)) + continue; + + /* Try reading/writing. */ + if (rw(*argv, &sb, fflag)) + rval = 1; + } + exit(rval); } -touch(filename) - char *filename; +#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; + +void +stime_arg1(arg, tvp) + char *arg; + struct timeval *tvp; { - struct stat statbuffer; - - if (stat(filename, &statbuffer) == -1) { - if (!dontcreate) - return(readwrite(filename, 0L)); - fprintf(stderr, "touch: %s: does not exist\n", filename); - return(1); + struct tm *t; + int yearset; + char *p; + /* Start with the current time. */ + if ((t = localtime(&tvp[0].tv_sec)) == NULL) + err(1, "localtime"); + /* [[CC]YY]MMDDhhmm[.SS] */ + if ((p = strchr(arg, '.')) == NULL) + t->tm_sec = 0; /* Seconds defaults to 0. */ + else { + if (strlen(p + 1) != 2) + goto terr; + *p++ = '\0'; + t->tm_sec = ATOI2(p); } - if ((statbuffer.st_mode & S_IFMT) != S_IFREG) { - fprintf(stderr, "touch: %s: can only touch regular files\n", - filename); - return(1); - } - if (!access(filename, R_OK | W_OK)) - return(readwrite(filename,statbuffer.st_size)); - if (force) { - int retval; - - if (chmod(filename, 0666)) { - fprintf(stderr, "touch: %s: couldn't chmod: ", - filename); - perror((char *)NULL); - return(1); + + yearset = 0; + switch(strlen(arg)) { + case 12: /* CCYYMMDDhhmm */ + t->tm_year = ATOI2(arg); + t->tm_year *= 1000; + yearset = 1; + /* FALLTHOUGH */ + case 10: /* YYMMDDhhmm */ + if (yearset) { + yearset = ATOI2(arg); + t->tm_year += yearset; + } else { + yearset = ATOI2(arg); + if (yearset < 69) + t->tm_year = yearset + 2000; + else + t->tm_year = yearset + 1900; } - retval = readwrite(filename, statbuffer.st_size); - if (chmod(filename, statbuffer.st_mode)) { - fprintf(stderr, "touch: %s: couldn't chmod back: ", - filename); - perror((char *)NULL); - return(1); - } - return(retval); + t->tm_year -= 1900; /* Convert to UNIX time. */ + /* FALLTHROUGH */ + case 8: /* MMDDhhmm */ + t->tm_mon = ATOI2(arg); + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + t->tm_mday = ATOI2(arg); + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + break; + default: + goto terr; } - fprintf(stderr, "touch: %s: cannot touch\n", filename); - return(1); + + t->tm_isdst = -1; /* Figure out DST. */ + tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); + if (tvp[0].tv_sec == -1) +terr: errx(1, + "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); + + tvp[0].tv_usec = tvp[1].tv_usec = 0; } -readwrite(filename, size) - char *filename; - off_t size; +void +stime_arg2(arg, year, tvp) + char *arg; + int year; + struct timeval *tvp; { - int filedescriptor; - char first; - off_t lseek(); + struct tm *t; + /* Start with the current time. */ + if ((t = localtime(&tvp[0].tv_sec)) == NULL) + err(1, "localtime"); - if (size) { - filedescriptor = open(filename, O_RDWR, 0); - if (filedescriptor == -1) { -error: fprintf(stderr, "touch: %s: ", filename); - perror((char *)NULL); - return(1); - } - if (read(filedescriptor, &first, 1) != 1) - goto error; - if (lseek(filedescriptor, 0L, 0) == -1) - goto error; - if (write(filedescriptor, &first, 1) != 1) - goto error; + t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + t->tm_mday = ATOI2(arg); + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + if (year) + t->tm_year = ATOI2(arg); + + t->tm_isdst = -1; /* Figure out DST. */ + tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); + if (tvp[0].tv_sec == -1) + errx(1, + "out of range or illegal time specification: MMDDhhmm[yy]"); + + tvp[0].tv_usec = tvp[1].tv_usec = 0; +} + +void +stime_file(fname, tvp) + char *fname; + struct timeval *tvp; +{ + struct stat sb; + + if (stat(fname, &sb)) + err(1, "%s", fname); +/* TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atimespec);*/ + TIMET_TO_TIMEVAL(tvp, &sb.st_atime); +/* TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtimespec);*/ + TIMET_TO_TIMEVAL(tvp + 1, &sb.st_mtime); +} + +int +rw(fname, sbp, force) + char *fname; + struct stat *sbp; + int force; +{ + int fd, needed_chmod, rval; + u_char byte; + + /* Try regular files and directories. */ + if (!S_ISREG(sbp->st_mode) && !S_ISDIR(sbp->st_mode)) { + warnx("%s: %s", fname, strerror(EFTYPE)); + return (1); + } + + needed_chmod = rval = 0; + if ((fd = open(fname, O_RDWR, 0)) == -1) { + if (!force || chmod(fname, DEFFILEMODE)) + goto err; + if ((fd = open(fname, O_RDWR, 0)) == -1) + goto err; + needed_chmod = 1; + } + + if (sbp->st_size != 0) { + if (read(fd, &byte, sizeof(byte)) != sizeof(byte)) + goto err; + if (lseek(fd, (off_t)0, SEEK_SET) == -1) + goto err; + if (write(fd, &byte, sizeof(byte)) != sizeof(byte)) + goto err; } else { - filedescriptor = creat(filename, 0666); - if (filedescriptor == -1) - goto error; + if (write(fd, &byte, sizeof(byte)) != sizeof(byte)) { +err: rval = 1; + warn("%s", fname); + } else if (ftruncate(fd, (off_t)0)) { + rval = 1; + warn("%s: file modified", fname); + } } - if (close(filedescriptor) == -1) - goto error; - return(0); + + if (close(fd) && rval != 1) { + rval = 1; + warn("%s", fname); + } + if (needed_chmod && chmod(fname, sbp->st_mode) && rval != 1) { + rval = 1; + warn("%s: permissions modified", fname); + } + return (rval); } +__dead void usage() { - fprintf(stderr, "usage: touch [-cf] file ...\n"); + (void)fprintf(stderr, + "usage: touch [-acfm] [-r file] [-t time] file ...\n"); exit(1); }