On Sun, 12 Jun 2005 14:22:53 -0400, D Goel wrote:
> Let's try the tar-trick to copy the contents of dir1 to dir2: 
> (cd dir1 && tar -clpsf- *) | (cd dir2 & tar -xpsf -)
> 
> This copies the contents, including correct timestamps and ownership,
> except that if there is a symlink (named, say s) in dir1.  Then the
> copied file dir2/s fails to get the same timestamp as dir1/s. 

Hi DG,

Thank you for the bug report. I've confirmed that this bug still exists
in the latest version of tar packaged in Debian (1.22-1.1). And I almost
replied saying that Linux doesn't provide the necessary functionality to
do what you want, (I noticed that "cp -a" also exhibits the same
bug---see bug #26766).

But rsync does not have the bug, so I looked a little closer and
found... the necessary magic is in the flags argument to the POSIX
utimensat function call:

       The flags field is a bit mask that may be 0, or include  the  following
       constant, defined in <fcntl.h>:

       AT_SYMLINK_NOFOLLOW
              If  pathname  specifies  a  symbolic link, then update the time‐
              stamps of the link, rather than the file to which it refers.

The tar program already prefers to call utimensat if available, but
currently always passes 0 for the flags argument. It also avoids ever
calling this function for symlinks, (which would update the timestamp of
the target to the archived time stamp of the link which is certainly not
desired).

I've attached a patch that fixes the bug when utimensat is available,
and should have no effect when the function is not available, (though I
didn't test on any such system). I also didn't write a new test case for
this behavior, (I'm not sure how to make a test case that would not be
expected to pass on some systems.)

Any feedback would be appreciated,

-Carl



From 2fe8827ad1874abf3295a5aa96eba18372b98c12 Mon Sep 17 00:00:00 2001
From: Carl Worth <cwo...@cworth.org>
Date: Tue, 4 Aug 2009 17:00:35 -0700
Subject: [PATCH] Preserve timestamp of symlinks when extracting (if utimensat available)

If the utimensat function is not available, then do nothing with
symlink time stamps, (which is the same as the current code).
---
 lib/utimens.c |   26 +++++++++++++++++++----
 lib/utimens.h |    4 +-
 src/extract.c |   61 +++++++++++++++++++++++++++-----------------------------
 src/misc.c    |    2 +-
 4 files changed, 53 insertions(+), 40 deletions(-)

diff --git a/lib/utimens.c b/lib/utimens.c
index 708de10..ae8b0a6 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -72,11 +72,16 @@ struct utimbuf
    use just futimes (or equivalent) instead of utimes (or equivalent),
    and fail if on an old system without futimes (or equivalent).
    If TIMESPEC is null, set the time stamps to the current time.
+   If the file is a symlink and IS_SYMLINK is set, then the
+   time stamps of the symlink itself will be updated if
+   possible, (but if not supported by the operating system
+   then no change will occur).
    Return 0 on success, -1 (setting errno) on failure.  */
 
 int
 gl_futimens (int fd ATTRIBUTE_UNUSED,
-	     char const *file, struct timespec const timespec[2])
+	     char const *file, struct timespec const timespec[2],
+	     int is_symlink)
 {
   /* Some Linux-based NFS clients are buggy, and mishandle time stamps
      of files in NFS file systems in some cases.  We have no
@@ -102,7 +107,8 @@ gl_futimens (int fd ATTRIBUTE_UNUSED,
 #if HAVE_UTIMENSAT
   if (fd < 0)
     {
-      int result = utimensat (AT_FDCWD, file, timespec, 0);
+      int flags = is_symlink ? AT_SYMLINK_NOFOLLOW : 0;
+      int result = utimensat (AT_FDCWD, file, timespec, flags);
 # ifdef __linux__
       /* Work around what might be a kernel bug:
          http://bugzilla.redhat.com/442352
@@ -119,6 +125,12 @@ gl_futimens (int fd ATTRIBUTE_UNUSED,
         return result;
     }
 #endif
+
+  /* Without utimensat we have no way to update a symlink rather than
+   * the target, so just return immediately. */
+  if (is_symlink)
+      return 0;
+
 #if HAVE_FUTIMENS
   {
     int result = futimens (fd, timespec);
@@ -219,9 +231,13 @@ gl_futimens (int fd ATTRIBUTE_UNUSED,
 }
 
 /* Set the access and modification time stamps of FILE to be
-   TIMESPEC[0] and TIMESPEC[1], respectively.  */
+   TIMESPEC[0] and TIMESPEC[1], respectively.
+   If the file is a symlink and is_symlink is set, then the
+   time stamps of the symlink itself will be updated if
+   possible, (but if not supported by the operating system
+   then no change will occur). */
 int
-utimens (char const *file, struct timespec const timespec[2])
+utimens (char const *file, struct timespec const timespec[2], int is_symlink)
 {
-  return gl_futimens (-1, file, timespec);
+  return gl_futimens (-1, file, timespec, is_symlink);
 }
diff --git a/lib/utimens.h b/lib/utimens.h
index 169521d..625785c 100644
--- a/lib/utimens.h
+++ b/lib/utimens.h
@@ -1,3 +1,3 @@
 #include <time.h>
-int gl_futimens (int, char const *, struct timespec const [2]);
-int utimens (char const *, struct timespec const [2]);
+int gl_futimens (int, char const *, struct timespec const [2], int flags);
+int utimens (char const *, struct timespec const [2], int flags);
diff --git a/src/extract.c b/src/extract.c
index 6d70398..5ca192d 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -239,43 +239,40 @@ set_stat (char const *file_name,
 	  mode_t invert_permissions, enum permstatus permstatus,
 	  char typeflag)
 {
-  if (typeflag != SYMTYPE)
+  /* We do the utime before the chmod because some versions of utime are
+     broken and trash the modes of the file.  */
+
+  if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
     {
-      /* We do the utime before the chmod because some versions of utime are
-	 broken and trash the modes of the file.  */
+      /* We set the accessed time to `now', which is really the time we
+	 started extracting files, unless incremental_option is used, in
+	 which case .st_atime is used.  */
 
-      if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
-	{
-	  /* We set the accessed time to `now', which is really the time we
-	     started extracting files, unless incremental_option is used, in
-	     which case .st_atime is used.  */
-
-	  /* FIXME: incremental_option should set ctime too, but how?  */
-
-	  struct timespec ts[2];
-	  if (incremental_option)
-	    ts[0] = st->atime;
-	  else
-	    ts[0] = start_time;
-	  ts[1] = st->mtime;
-
-	  if (utimens (file_name, ts) != 0)
-	    utime_error (file_name);
-	  else
-	    {
-	      check_time (file_name, ts[0]);
-	      check_time (file_name, ts[1]);
-	    }
-	}
+      /* FIXME: incremental_option should set ctime too, but how?  */
 
-      /* Some systems allow non-root users to give files away.  Once this
-	 done, it is not possible anymore to change file permissions.
-	 However, setting file permissions now would be incorrect, since
-	 they would apply to the wrong user, and there would be a race
-	 condition.  So, don't use systems that allow non-root users to
-	 give files away.  */
+      struct timespec ts[2];
+      if (incremental_option)
+	ts[0] = st->atime;
+      else
+	ts[0] = start_time;
+      ts[1] = st->mtime;
+
+      if (utimens (file_name, ts, typeflag == SYMTYPE) != 0)
+	utime_error (file_name);
+      else
+      {
+	check_time (file_name, ts[0]);
+	check_time (file_name, ts[1]);
+      }
     }
 
+  /* Some systems allow non-root users to give files away.  Once this
+     done, it is not possible anymore to change file permissions.
+     However, setting file permissions now would be incorrect, since
+     they would apply to the wrong user, and there would be a race
+     condition.  So, don't use systems that allow non-root users to
+     give files away.  */
+
   if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
     {
       /* When lchown exists, it should be used to change the attributes of
diff --git a/src/misc.c b/src/misc.c
index 951449e..d8255f7 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -516,7 +516,7 @@ set_file_atime (int fd, char const *file, struct timespec const timespec[2])
     }
 #endif
 
-  return gl_futimens (fd, file, timespec);
+  return gl_futimens (fd, file, timespec, 0);
 }
 
 /* A description of a working directory.  */
-- 
1.6.3.3

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to