]> git.cworth.org Git - tar/blobdiff - lib/utimens.c
Preserve timestamp of symlinks when extracting (if utimensat available)
[tar] / lib / utimens.c
index e12821962c6927bca06849810718a629b8c2b98c..ae8b0a6ff0ad6f7de46a54beab5f6fd0bad6f9b0 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 #include <unistd.h>
 
@@ -71,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
@@ -94,95 +100,144 @@ gl_futimens (int fd ATTRIBUTE_UNUSED,
     fsync (fd);
 #endif
 
-  /* There's currently no interface to set file timestamps with
-     nanosecond resolution, so do the best we can, discarding any
-     fractional part of the timestamp.  */
-#if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
-  struct timeval timeval[2];
-  struct timeval const *t;
-  if (timespec)
+  /* POSIX 200x added two interfaces to set file timestamps with
+     nanosecond resolution.  We provide a fallback for ENOSYS (for
+     example, compiling against Linux 2.6.25 kernel headers and glibc
+     2.7, but running on Linux 2.6.18 kernel).  */
+#if HAVE_UTIMENSAT
+  if (fd < 0)
     {
-      timeval[0].tv_sec = timespec[0].tv_sec;
-      timeval[0].tv_usec = timespec[0].tv_nsec / 1000;
-      timeval[1].tv_sec = timespec[1].tv_sec;
-      timeval[1].tv_usec = timespec[1].tv_nsec / 1000;
-      t = timeval;
+      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
+         http://bugzilla.redhat.com/449910
+         It appears that utimensat can mistakenly return 280 rather
+         than -1 upon failure.
+         FIXME: remove in 2010 or whenever the offending kernels
+         are no longer in common use.  */
+      if (0 < result)
+        errno = ENOSYS;
+# endif
+
+      if (result == 0 || errno != ENOSYS)
+        return result;
     }
-  else
-    t = NULL;
+#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 (fd < 0)
-    {
+#if HAVE_FUTIMENS
+  {
+    int result = futimens (fd, timespec);
+# ifdef __linux__
+    /* Work around the same bug as above.  */
+    if (0 < result)
+      errno = ENOSYS;
+# endif
+    if (result == 0 || errno != ENOSYS)
+      return result;
+  }
+#endif
+
+  /* The platform lacks an interface to set file timestamps with
+     nanosecond resolution, so do the best we can, discarding any
+     fractional part of the timestamp.  */
+  {
+#if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
+    struct timeval timeval[2];
+    struct timeval const *t;
+    if (timespec)
+      {
+       timeval[0].tv_sec = timespec[0].tv_sec;
+       timeval[0].tv_usec = timespec[0].tv_nsec / 1000;
+       timeval[1].tv_sec = timespec[1].tv_sec;
+       timeval[1].tv_usec = timespec[1].tv_nsec / 1000;
+       t = timeval;
+      }
+    else
+      t = NULL;
+
+    if (fd < 0)
+      {
 # if HAVE_FUTIMESAT
-      return futimesat (AT_FDCWD, file, t);
+       return futimesat (AT_FDCWD, file, t);
 # endif
-    }
-  else
-    {
-      /* If futimesat or futimes fails here, don't try to speed things
-        up by returning right away.  glibc can incorrectly fail with
-        errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
-        in high security mode doesn't allow ordinary users to read
-        /proc/self, so glibc incorrectly fails with errno == EACCES.
-        If errno == EIO, EPERM, or EROFS, it's probably safe to fail
-        right away, but these cases are rare enough that they're not
-        worth optimizing, and who knows what other messed-up systems
-        are out there?  So play it safe and fall back on the code
-        below.  */
+      }
+    else
+      {
+       /* If futimesat or futimes fails here, don't try to speed things
+          up by returning right away.  glibc can incorrectly fail with
+          errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
+          in high security mode doesn't allow ordinary users to read
+          /proc/self, so glibc incorrectly fails with errno == EACCES.
+          If errno == EIO, EPERM, or EROFS, it's probably safe to fail
+          right away, but these cases are rare enough that they're not
+          worth optimizing, and who knows what other messed-up systems
+          are out there?  So play it safe and fall back on the code
+          below.  */
 # if HAVE_FUTIMESAT
-      if (futimesat (fd, NULL, t) == 0)
-       return 0;
+       if (futimesat (fd, NULL, t) == 0)
+         return 0;
 # elif HAVE_FUTIMES
-      if (futimes (fd, t) == 0)
-       return 0;
+       if (futimes (fd, t) == 0)
+         return 0;
 # endif
-    }
-#endif
+      }
+#endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
 
-  if (!file)
-    {
+    if (!file)
+      {
 #if ! (HAVE_FUTIMESAT || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
-      errno = ENOSYS;
+       errno = ENOSYS;
 #endif
 
-      /* Prefer EBADF to ENOSYS if both error numbers apply.  */
-      if (errno == ENOSYS)
-       {
-         int fd2 = dup (fd);
-         int dup_errno = errno;
-         if (0 <= fd2)
-           close (fd2);
-         errno = (fd2 < 0 && dup_errno == EBADF ? EBADF : ENOSYS);
-       }
-
-      return -1;
-    }
+       /* Prefer EBADF to ENOSYS if both error numbers apply.  */
+       if (errno == ENOSYS)
+         {
+           int fd2 = dup (fd);
+           int dup_errno = errno;
+           if (0 <= fd2)
+             close (fd2);
+           errno = (fd2 < 0 && dup_errno == EBADF ? EBADF : ENOSYS);
+         }
+
+       return -1;
+      }
 
 #if HAVE_WORKING_UTIMES
-  return utimes (file, t);
+    return utimes (file, t);
 #else
-  {
-    struct utimbuf utimbuf;
-    struct utimbuf const *ut;
-    if (timespec)
-      {
-       utimbuf.actime = timespec[0].tv_sec;
-       utimbuf.modtime = timespec[1].tv_sec;
-       ut = &utimbuf;
-      }
-    else
-      ut = NULL;
+    {
+      struct utimbuf utimbuf;
+      struct utimbuf const *ut;
+      if (timespec)
+       {
+         utimbuf.actime = timespec[0].tv_sec;
+         utimbuf.modtime = timespec[1].tv_sec;
+         ut = &utimbuf;
+       }
+      else
+       ut = NULL;
 
-    return utime (file, ut);
+      return utime (file, ut);
+    }
+#endif /* !HAVE_WORKING_UTIMES */
   }
-#endif
 }
 
 /* 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);
 }