autoupdate
[gnulib.git] / lib / utimens.c
index b2b25d1..b33f883 100644 (file)
@@ -49,6 +49,17 @@ struct utimbuf
 };
 #endif
 
+/* Avoid recursion with rpl_futimens or rpl_utimensat.  */
+#undef futimens
+#undef utimensat
+
+#if HAVE_UTIMENSAT || HAVE_FUTIMENS
+/* Cache variable for whether syscall works; used to avoid calling the
+   syscall if we know it will just fail with ENOSYS.  0 = unknown, 1 =
+   yes, -1 = no.  */
+static int utimensat_works_really;
+#endif /* HAVE_UTIMENSAT || HAVE_UTIMENSAT */
+
 /* Validate the requested timestamps.  Return 0 if the resulting
    timespec can be used for utimensat (after possibly modifying it to
    work around bugs in utimensat).  Return 1 if the timespec needs
@@ -132,7 +143,7 @@ update_timespec (struct stat const *statbuf, struct timespec *ts[2])
    Return 0 on success, -1 (setting errno) on failure.  */
 
 int
-gl_futimens (int fd, char const *file, struct timespec const timespec[2])
+fdutimens (char const *file, int fd, struct timespec const timespec[2])
 {
   struct timespec adjusted_timespec[2];
   struct timespec *ts = timespec ? adjusted_timespec : NULL;
@@ -179,41 +190,53 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2])
 #endif
 
   /* POSIX 2008 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)
+     nanosecond resolution; newer Linux implements both functions via
+     a single syscall.  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 || HAVE_FUTIMENS
+  if (0 <= utimensat_works_really)
     {
-      int result = utimensat (AT_FDCWD, file, ts, 0);
-# ifdef __linux__
-      /* Work around 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;
+# if HAVE_UTIMENSAT
+      if (fd < 0)
+        {
+          int result = utimensat (AT_FDCWD, file, ts, 0);
+#  ifdef __linux__
+          /* Work around 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 ENOSYS failure.
+             FIXME: remove in 2010 or whenever the offending kernels
+             are no longer in common use.  */
+          if (0 < result)
+            errno = ENOSYS;
+#  endif /* __linux__ */
+          if (result == 0 || errno != ENOSYS)
+            {
+              utimensat_works_really = 1;
+              return result;
+            }
+        }
+# endif /* HAVE_UTIMENSAT */
+# if HAVE_FUTIMENS
+      {
+        int result = futimens (fd, timespec);
+#  ifdef __linux__
+        /* Work around the same bug as above.  */
+        if (0 < result)
+          errno = ENOSYS;
+#  endif /* __linux__ */
+        if (result == 0 || errno != ENOSYS)
+          {
+            utimensat_works_really = 1;
+            return result;
+          }
+      }
+# endif /* HAVE_FUTIMENS */
     }
-#endif
-#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
+  utimensat_works_really = -1;
+#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
 
   /* The platform lacks an interface to set file timestamps with
      nanosecond resolution, so do the best we can, discarding any
@@ -300,23 +323,41 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2])
   }
 }
 
+/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
+   TIMESPEC[0] and TIMESPEC[1], respectively.
+   FD must be either negative -- in which case it is ignored --
+   or a file descriptor that is open on FILE.
+   If FD is nonnegative, then FILE can be NULL, which means
+   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.
+   Return 0 on success, -1 (setting errno) on failure.  */
+
+int
+gl_futimens (int fd, char const *file, struct timespec const timespec[2])
+{
+  return fdutimens (file, fd, timespec);
+}
+
 /* Set the access and modification time stamps of FILE to be
    TIMESPEC[0] and TIMESPEC[1], respectively.  */
 int
 utimens (char const *file, struct timespec const timespec[2])
 {
-  return gl_futimens (-1, file, timespec);
+  return fdutimens (file, -1, timespec);
 }
 
-/* Set the access and modification time stamps of the symlink FILE to
-   be TIMESPEC[0] and TIMESPEC[1], respectively.  Fail with ENOSYS if
-   the platform does not support changing symlink timestamps.  */
+/* Set the access and modification time stamps of FILE to be
+   TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
+   symlinks.  Fail with ENOSYS if the platform does not support
+   changing symlink timestamps, but FILE was a symlink.  */
 int
 lutimens (char const *file, struct timespec const timespec[2])
 {
   struct timespec adjusted_timespec[2];
   struct timespec *ts = timespec ? adjusted_timespec : NULL;
   int adjustment_needed = 0;
+  struct stat st;
 
   if (ts)
     {
@@ -333,23 +374,27 @@ lutimens (char const *file, struct timespec const timespec[2])
      worry about bogus return values.  */
 
 #if HAVE_UTIMENSAT
-  {
-    int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
+  if (0 <= utimensat_works_really)
+    {
+      int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
 # ifdef __linux__
-    /* Work around 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 ENOSYS failure.
-       FIXME: remove in 2010 or whenever the offending kernels
-       are no longer in common use.  */
-    if (0 < result)
-      errno = ENOSYS;
+      /* Work around 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 ENOSYS 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;
-  }
+      if (result == 0 || errno != ENOSYS)
+        {
+          utimensat_works_really = 1;
+          return result;
+        }
+    }
+  utimensat_works_really = -1;
 #endif /* HAVE_UTIMENSAT */
 
   /* The platform lacks an interface to set file timestamps with
@@ -358,7 +403,6 @@ lutimens (char const *file, struct timespec const timespec[2])
 
   if (adjustment_needed)
     {
-      struct stat st;
       if (lstat (file, &st))
         return -1;
       if (update_timespec (&st, &ts))
@@ -384,8 +428,11 @@ lutimens (char const *file, struct timespec const timespec[2])
   }
 #endif /* HAVE_LUTIMES */
 
-  /* Out of luck.  Symlink timestamps can't be changed.  We won't
-     bother changing the timestamps if FILE was not a symlink.  */
+  /* Out of luck for symlinks, but we still handle regular files.  */
+  if (!adjustment_needed && lstat (file, &st))
+    return -1;
+  if (!S_ISLNK (st.st_mode))
+    return fdutimens (file, -1, ts);
   errno = ENOSYS;
   return -1;
 }