implement from scratch, using utimes(2), for POSIX 1003.2
authorKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Sun, 7 Mar 1993 05:34:38 +0000 (21:34 -0800)
committerKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Sun, 7 Mar 1993 05:34:38 +0000 (21:34 -0800)
SCCS-vsn: usr.bin/touch/touch.1 6.5
SCCS-vsn: usr.bin/touch/touch.c 5.2

usr/src/usr.bin/touch/touch.1
usr/src/usr.bin/touch/touch.c

index a095e0c..a22b835 100644 (file)
 .\"
 .\" %sccs.include.redist.man%
 .\"
 .\"
 .\" %sccs.include.redist.man%
 .\"
-.\"     @(#)touch.1    6.4 (Berkeley) %G%
+.\"     @(#)touch.1    6.5 (Berkeley) %G%
 .\"
 .Dd 
 .Dt TOUCH 1
 .Os
 .Sh NAME
 .Nm touch
 .\"
 .Dd 
 .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
 .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
 .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
 .Pp
-Available options:
+The following options are available:
 .Bl -tag -width Ds
 .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
 .It Fl c
-Do not create a specified file if it does not exist.
-Do not write any diagnostic messages concerning this
-condition.
+Do not create the file if it does not exist.
+The
+.Nm touch
+utility does not treat this as an error.
+No error messages are displayed and the exit value is not affected.
 .It Fl f
 .It Fl f
-Attempt to force the
+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
 .Nm touch
-in spite of read and write permissions on a
-.Ar file .
+utility exits 0 on success, and >0 if an error occurs.
 .Sh SEE ALSO
 .Xr utimes 2
 .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
 .Sh HISTORY
 A
-.Nm
+.Nm touch
 command appeared in
 .At v7 .
 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.
index f43685f..f905264 100644 (file)
@@ -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.
  *
  * %sccs.include.redist.c%
  * All rights reserved.
  *
  * %sccs.include.redist.c%
 
 #ifndef lint
 char copyright[] =
 
 #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
  All rights reserved.\n";
 #endif /* not lint */
 
 #ifndef lint
-static char sccsid[] = "@(#)touch.c    5.1 (Berkeley) %G%";
+static char sccsid[] = "@(#)touch.c    5.2 (Berkeley) %G%";
 #endif /* not lint */
 
 #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 <sys/types.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 
 
+#include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 
 #include <unistd.h>
 
-static int     dontcreate;     /* set if -c option */
-static int     force;          /* set if -f option */
-
-void   err __P((const char *, ...));
-int    readwrite __P((char *, off_t));
-int    touch __P((char *));
-__dead void usage __P((void));
+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));
 
 int
 main(argc, argv)
        int argc;
        char *argv[];
 {
 
 int
 main(argc, argv)
        int argc;
        char *argv[];
 {
-       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 = mflag = 1;
+       cflag = fflag = timeset = 0;
+
+       if (gettimeofday(&tv[0], NULL))
+               err(1, "gettimeofday");
+
+       while ((ch = getopt(argc, argv, "acfmr:t:")) != EOF)
+               switch(ch) {
+               case 'a':
+                       aflag = 1;
+                       mflag = 0;
+                       break;
                case 'c':
                case 'c':
-                       dontcreate = 1;
+                       cflag = 1;
                        break;
                case 'f':
                        break;
                case 'f':
-                       force = 1;
+                       fflag = 1;
+                       break;
+               case 'm':
+                       aflag = 0;
+                       mflag = 1;
+                       break;
+               case 'r':
+                       timeset = 1;
+                       stime_file(optarg, tv);
+                       break;
+               case 't':
+                       timeset = 1;
+                       stime_arg1(optarg, tv);
                        break;
                case '?':
                default:
                        usage();
                }
                        break;
                case '?':
                default:
                        usage();
                }
-       if (!*(argv += optind))
+       argc -= optind;
+       argv += optind;
+
+       /*
+        * 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();
                usage();
-       do {
-               retval |= touch(*argv);
-       } while (*++argv);
-       exit(retval);
-}
 
 
-int
-touch(filename)
-       char *filename;
-{
-       struct stat sb;
+       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 | O_TRUNC, DEFFILEMODE);
+                               if (fd == -1 || fstat(fd, &sb) || close(fd)) {
+                                       rval = 1;
+                                       warn("%s", *argv);
+                                       continue;
+                               }
 
 
-       if (stat(filename, &sb) == -1) {
-               if (!dontcreate)
-                       return (readwrite(filename, (off_t)0));
-               err("%s: %s", filename, strerror(ENOENT));
-               return (1);
+                               /* If using the current time, we're done. */
+                               if (!timeset)
+                                       continue;
+                       } else
+                               continue;
+
+               if (!aflag)
+                       TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec);
+               if (!mflag)
+                       TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
+
+               /* 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);
+               }
+
+               /* Try reading/writing. */
+               if (rw(*argv, &sb, fflag))
+                       rval = 1;
        }
        }
-       if ((sb.st_mode & S_IFMT) != S_IFREG) {
-               err("%s: %s", filename, strerror(EFTYPE));
-               return (1);
+       exit(rval);
+}
+
+#define        ATOI2(ar)       ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
+
+void
+stime_arg1(arg, tvp)
+       char *arg;
+       struct timeval *tvp;
+{
+       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 (!access(filename, R_OK | W_OK))
-               return (readwrite(filename, sb.st_size));
-       if (force) {
-               int retval;
-
-               if (chmod(filename, DEFFILEMODE)) {
-                       err("%s: add permissions: %s",
-                           filename, strerror(errno));
-                       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, sb.st_size);
-               if (chmod(filename, sb.st_mode)) {
-                       err("%s: restore permissions: %s",
-                           filename, strerror(errno));
-                       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;
        }
        }
-       err("%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;
+}
+
+void
+stime_arg2(arg, year, tvp)
+       char *arg;
+       int year;
+       struct timeval *tvp;
+{
+       struct tm *t;
+                                       /* Start with the current time. */
+       if ((t = localtime(&tvp[0].tv_sec)) == NULL)
+               err(1, "localtime");
+
+       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);
+       TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtimespec);
 }
 
 int
 }
 
 int
-readwrite(filename, size)
-       char *filename;
-       off_t size;
+rw(fname, sbp, force)
+       char *fname;
+       struct stat *sbp;
+       int force;
 {
 {
-       int fd;
-       char first;
-
-       if (size) {
-               fd = open(filename, O_RDWR, 0);
-               if (fd == -1)
-                       goto error;
-               if (read(fd, &first, 1) != 1)
-                       goto error;
+       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)
                if (lseek(fd, (off_t)0, SEEK_SET) == -1)
-                       goto error;
-               if (write(fd, &first, 1) != 1)
-                       goto error;
+                       goto err;
+               if (write(fd, &byte, sizeof(byte)) != sizeof(byte))
+                       goto err;
        } else {
        } else {
-               fd = creat(filename, DEFFILEMODE);
-               if (fd == -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(fd) == -1) {
-error:         err("%s: %s", filename, strerror(errno));
-               return (1);
+
+       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 (0);
+       return (rval);
 }
 
 __dead void
 usage()
 {
 }
 
 __dead void
 usage()
 {
-       fprintf(stderr, "usage: touch [-cf] file ...\n");
+       (void)fprintf(stderr,
+           "usage: touch [-acfm] [-r file] [-t time] file ...\n");
        exit(1);
 }
        exit(1);
 }
-
-#if __STDC__
-#include <stdarg.h>
-#else
-#include <varargs.h>
-#endif
-
-void
-#if __STDC__
-err(const char *fmt, ...)
-#else
-err(fmt, va_alist)
-       char *fmt;
-        va_dcl
-#endif
-{
-       va_list ap;
-#if __STDC__
-       va_start(ap, fmt);
-#else
-       va_start(ap);
-#endif
-       (void)fprintf(stderr, "touch: ");
-       (void)vfprintf(stderr, fmt, ap);
-       va_end(ap);
-       (void)fprintf(stderr, "\n");
-}