Submitted for your consideration:

This diff adds an option '-h' to touch(1) for getting/setting timestamps
from symbolic links directly, rather than the linked-to file.  This is
useful if you want to set the timestamp on a link (e.g. to copy the
timestamp from the linked-to file, or from another link).

This implementation is not entirely compatible with FreeBSD.  I believe
the differences are improvements.  First, in FreeBSD -h imples -c, which
means you cannot create new files *and* modify existing links both in
the same command.  This seems like an artificial restriction, and not
a particularly useful one.

Second, in FreeBSD the -h option does not apply to -r, which
means you can't copy a timestamp from a symbolic link.

With the below diff, the following two examples are now possible:

Example 1: copy the timestamp of link1 to link2

        $ touch -h -r link1 link2

Example 2: set the timestamp of link1 to be the same as the timestamp
of the linked-to file:

        $ touch -r link1 -h link1

Note that in the second example, the -h option must be specified
*after* the -r option, so that the link *is* followed for the read
of the source timestamp but is *not* followed for the write of the
target timestamp.

If you want to copy a timestamp from a link to the target of a link,
that takes two steps, alas:

        $ touch -h -r link1 tmpfile
        $ touch -r tmpfile link2

-ken


Index: touch.1
===================================================================
RCS file: /cvs/src/usr.bin/touch/touch.1,v
retrieving revision 1.35
diff -u -p -r1.35 touch.1
--- touch.1     25 Sep 2015 17:02:57 -0000      1.35
+++ touch.1     26 Dec 2016 20:49:33 -0000
@@ -41,7 +41,7 @@
 .Nd change file access and modification times
 .Sh SYNOPSIS
 .Nm touch
-.Op Fl acm
+.Op Fl achm
 .Oo Fl d
 .Sm off
 .Ar ccyy No - Ar mm No - Ar dd Cm T Ar HH : MM : SS Oo \&. Ar frac Oc Op Cm Z
@@ -116,6 +116,11 @@ The timezone specifier: a capital letter
 indicating that the time is in UTC.
 If not specified, the time is in the local timezone.
 .El
+.It Fl h
+Do not follow symbolic links; modify links instead of their targets.
+This option also applies to
+.Fl r
+if it precedes it.
 .It Fl m
 Change the modification time of the file.
 The access time of the file is not changed unless the
Index: touch.c
===================================================================
RCS file: /cvs/src/usr.bin/touch/touch.c,v
retrieving revision 1.25
diff -u -p -r1.25 touch.c
--- touch.c     9 Oct 2015 01:37:09 -0000       1.25
+++ touch.c     26 Dec 2016 20:49:33 -0000
@@ -48,7 +48,7 @@
 void           stime_arg1(char *, struct timespec *);
 void           stime_arg2(char *, int, struct timespec *);
 void           stime_argd(char *, struct timespec *);
-void           stime_file(char *, struct timespec *);
+void           stime_file(char *, int, struct timespec *);
 __dead void    usage(void);

 int
@@ -56,6 +56,7 @@ main(int argc, char *argv[])
 {
        struct timespec  ts[2];
        int              aflag, cflag, mflag, ch, fd, len, rval, timeset;
+       int             at_flag;
        char            *p;

        (void)setlocale(LC_ALL, "");
@@ -63,8 +64,8 @@ main(int argc, char *argv[])
        if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
                err(1, "pledge");

-       aflag = cflag = mflag = timeset = 0;
-       while ((ch = getopt(argc, argv, "acd:fmr:t:")) != -1)
+       aflag = cflag = mflag = timeset = at_flag = 0;
+       while ((ch = getopt(argc, argv, "acd:fhmr:t:")) != -1)
                switch (ch) {
                case 'a':
                        aflag = 1;
@@ -78,12 +79,15 @@ main(int argc, char *argv[])
                        break;
                case 'f':
                        break;
+               case 'h':
+                       at_flag |= AT_SYMLINK_NOFOLLOW;
+                       break;
                case 'm':
                        mflag = 1;
                        break;
                case 'r':
                        timeset = 1;
-                       stime_file(optarg, ts);
+                       stime_file(optarg, at_flag, ts);
                        break;
                case 't':
                        timeset = 1;
@@ -126,7 +130,7 @@ main(int argc, char *argv[])

        for (rval = 0; *argv; ++argv) {
                /* Update the file's timestamp if it exists. */
-               if (! utimensat(AT_FDCWD, *argv, ts, 0))
+               if (! utimensat(AT_FDCWD, *argv, ts, at_flag))
                        continue;
                if (errno != ENOENT) {
                        rval = 1;
@@ -267,11 +271,11 @@ terr:             errx(1,
 }

 void
-stime_file(char *fname, struct timespec *tsp)
+stime_file(char *fname, int at_flag, struct timespec *tsp)
 {
        struct stat     sb;

-       if (stat(fname, &sb))
+       if (fstatat(AT_FDCWD, fname, &sb, at_flag))
                err(1, "%s", fname);
        tsp[0] = sb.st_atim;
        tsp[1] = sb.st_mtim;
@@ -328,7 +332,7 @@ __dead void
 usage(void)
 {
        (void)fprintf(stderr,
-"usage: touch [-acm] [-d ccyy-mm-ddTHH:MM:SS[.frac][Z]] [-r file]\n"
+"usage: touch [-achm] [-d ccyy-mm-ddTHH:MM:SS[.frac][Z]] [-r file]\n"
 "             [-t [[cc]yy]mmddHHMM[.SS]] file ...\n");
        exit(1);
 }

Reply via email to