changeset 12160:d0732ed2dd55

utimens: add UTIME_NOW and UTIME_OMIT support These flags make it possible to implement futimens and utimensat; they also make touch(1) more efficient, by avoiding stat or gettime if native utimensat works. * lib/utimens.c (validate_timespec, update_timespec): New helper functions. (gl_futimens, lutimens): Use them. * modules/utimens (Depends-on): Add gettime, lstat, stat-time, stdbool, sys_stat. (Link): Mention resulting library dependency. * modules/utimecmp (Link): Likewise. * modules/utimens-tests (Depends-on): Drop stat-time, stdbool. (Makefile.am): Pick up library dependency. * lib/sys_stat.in.h (UTIME_NOW, UTIME_OMIT): Guarantee a definition. * tests/test-sys_stat.c: Test the definitions. * doc/posix-headers/sys_stat.texi (sys/stat.h): Document this. * NEWS: Document library dependency. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Wed, 07 Oct 2009 16:05:34 -0600
parents 0ae159ba1adf
children c4a167621772
files ChangeLog NEWS doc/posix-headers/sys_stat.texi lib/sys_stat.in.h lib/utimens.c modules/utimecmp modules/utimens modules/utimens-tests tests/test-sys_stat.c
diffstat 9 files changed, 189 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
 2009-10-10  Eric Blake  <ebb9@byu.net>
 
+	utimens: add UTIME_NOW and UTIME_OMIT support
+	* lib/utimens.c (validate_timespec, update_timespec): New helper
+	functions.
+	(gl_futimens, lutimens): Use them.
+	* modules/utimens (Depends-on): Add gettime, lstat, stat-time,
+	stdbool, sys_stat.
+	(Link): Mention resulting library dependency.
+	* modules/utimecmp (Link): Likewise.
+	* modules/utimens-tests (Depends-on): Drop stat-time, stdbool.
+	(Makefile.am): Pick up library dependency.
+	* lib/sys_stat.in.h (UTIME_NOW, UTIME_OMIT): Guarantee a
+	definition.
+	* tests/test-sys_stat.c: Test the definitions.
+	* doc/posix-headers/sys_stat.texi (sys/stat.h): Document this.
+	* NEWS: Document library dependency.
+
 	utimecmp: support symlink timestamps
 	* lib/utimecmp.c (utimecmp): Use new interface.  Skip effort of
 	hashing when possible.  Use pathconf when available.
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,9 @@
 
 Date        Modules         Changes
 
+2009-10-10  utimens         The use of this module now requires linking with
+                            $(LIB_CLOCK_GETTIME).
+
 2009-09-16  canonicalize-lgpl
                             The include file is changed from "canonicalize.h"
                             to <stdlib.h>.
--- a/doc/posix-headers/sys_stat.texi
+++ b/doc/posix-headers/sys_stat.texi
@@ -17,6 +17,9 @@
 on other platforms.
 @item
 The functions @code{lstat} and @code{mkdir} are not declared on mingw.
+@item
+The macros @code{UTIME_NOW} and @code{UTIME_OMIT} are missing on some
+platforms.
 @end itemize
 
 Portability problems not fixed by Gnulib:
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -278,6 +278,12 @@
 # define S_IRWXUGO (S_IRWXU | S_IRWXG | S_IRWXO)
 #endif
 
+/* Macros for futimens and utimensat.  */
+#ifndef UTIME_NOW
+# define UTIME_NOW (-1)
+# define UTIME_OMIT (-2)
+#endif
+
 
 #ifdef __cplusplus
 extern "C" {
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -24,12 +24,17 @@
 
 #include "utimens.h"
 
+#include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <unistd.h>
 
+#include "stat-time.h"
+#include "timespec.h"
+
 #if HAVE_UTIME_H
 # include <utime.h>
 #endif
@@ -44,6 +49,78 @@
 };
 #endif
 
+/* 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
+   further adjustment based on stat results for utimes or other less
+   powerful interfaces.  Return -1, with errno set to EINVAL, if
+   timespec is out of range.  */
+static int
+validate_timespec (struct timespec timespec[2])
+{
+  int result = 0;
+  assert (timespec);
+  if ((timespec[0].tv_nsec != UTIME_NOW
+       && timespec[0].tv_nsec != UTIME_OMIT
+       && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec))
+      || (timespec[1].tv_nsec != UTIME_NOW
+          && timespec[1].tv_nsec != UTIME_OMIT
+          && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec)))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
+     EINVAL if tv_sec is not 0 when using the flag values of
+     tv_nsec.  */
+  if (timespec[0].tv_nsec == UTIME_NOW
+      || timespec[0].tv_nsec == UTIME_OMIT)
+    {
+      timespec[0].tv_sec = 0;
+      result = 1;
+    }
+  if (timespec[1].tv_nsec == UTIME_NOW
+      || timespec[1].tv_nsec == UTIME_OMIT)
+    {
+      timespec[1].tv_sec = 0;
+      result = 1;
+    }
+  return result;
+}
+
+/* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat
+   buffer STATBUF to obtain the current timestamps of the file.  If
+   both times are UTIME_NOW, set *TS to NULL (as this can avoid some
+   permissions issues).  If both times are UTIME_OMIT, return true
+   (nothing further beyond the prior collection of STATBUF is
+   necessary); otherwise return false.  */
+static bool
+update_timespec (struct stat const *statbuf, struct timespec *ts[2])
+{
+  struct timespec *timespec = *ts;
+  if (timespec[0].tv_nsec == UTIME_OMIT
+      && timespec[1].tv_nsec == UTIME_OMIT)
+    return true;
+  if (timespec[0].tv_nsec == UTIME_NOW
+      && timespec[1].tv_nsec == UTIME_NOW)
+    {
+      *ts = NULL;
+      return false;
+    }
+
+  if (timespec[0].tv_nsec == UTIME_OMIT)
+    timespec[0] = get_stat_atime (statbuf);
+  else if (timespec[0].tv_nsec == UTIME_NOW)
+    gettime (&timespec[0]);
+
+  if (timespec[1].tv_nsec == UTIME_OMIT)
+    timespec[1] = get_stat_mtime (statbuf);
+  else if (timespec[1].tv_nsec == UTIME_NOW)
+    gettime (&timespec[1]);
+
+  return false;
+}
+
 /* 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 --
@@ -57,6 +134,19 @@
 int
 gl_futimens (int fd, char const *file, struct timespec const timespec[2])
 {
+  struct timespec adjusted_timespec[2];
+  struct timespec *ts = timespec ? adjusted_timespec : NULL;
+  int adjustment_needed = 0;
+
+  if (ts)
+    {
+      adjusted_timespec[0] = timespec[0];
+      adjusted_timespec[1] = timespec[1];
+      adjustment_needed = validate_timespec (ts);
+    }
+  if (adjustment_needed < 0)
+    return -1;
+
   /* Require that at least one of FD or FILE are valid.  Works around
      a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
      than failing.  */
@@ -88,16 +178,16 @@
     fsync (fd);
 #endif
 
-  /* POSIX 200x added two interfaces to set file timestamps with
+  /* 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)
     {
-      int result = utimensat (AT_FDCWD, file, timespec, 0);
+      int result = utimensat (AT_FDCWD, file, ts, 0);
 # ifdef __linux__
-      /* Work around what might be a kernel bug:
+      /* Work around a kernel bug:
          http://bugzilla.redhat.com/442352
          http://bugzilla.redhat.com/449910
          It appears that utimensat can mistakenly return 280 rather
@@ -128,16 +218,26 @@
   /* 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 (adjustment_needed)
+    {
+      struct stat st;
+      if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
+        return -1;
+      if (update_timespec (&st, &ts))
+        return 0;
+    }
+
   {
 #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
     struct timeval timeval[2];
     struct timeval const *t;
-    if (timespec)
+    if (ts)
       {
-        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;
+        timeval[0].tv_sec = ts[0].tv_sec;
+        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
+        timeval[1].tv_sec = ts[1].tv_sec;
+        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
         t = timeval;
       }
     else
@@ -185,10 +285,10 @@
     {
       struct utimbuf utimbuf;
       struct utimbuf *ut;
-      if (timespec)
+      if (ts)
         {
-          utimbuf.actime = timespec[0].tv_sec;
-          utimbuf.modtime = timespec[1].tv_sec;
+          utimbuf.actime = ts[0].tv_sec;
+          utimbuf.modtime = ts[1].tv_sec;
           ut = &utimbuf;
         }
       else
@@ -212,9 +312,21 @@
    be TIMESPEC[0] and TIMESPEC[1], respectively.  Fail with ENOSYS if
    the platform does not support changing symlink timestamps.  */
 int
-lutimens (char const *file _UNUSED_PARAMETER_,
-          struct timespec const timespec[2] _UNUSED_PARAMETER_)
+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;
+
+  if (ts)
+    {
+      adjusted_timespec[0] = timespec[0];
+      adjusted_timespec[1] = timespec[1];
+      adjustment_needed = validate_timespec (ts);
+    }
+  if (adjustment_needed < 0)
+    return -1;
+
   /* The Linux kernel did not support symlink timestamps until
      utimensat, in version 2.6.22, so we don't need to mimic
      gl_futimens' worry about buggy NFS clients.  But we do have to
@@ -222,7 +334,7 @@
 
 #if HAVE_UTIMENSAT
   {
-    int result = utimensat (AT_FDCWD, file, timespec, AT_SYMLINK_NOFOLLOW);
+    int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
 # ifdef __linux__
     /* Work around a kernel bug:
        http://bugzilla.redhat.com/442352
@@ -243,16 +355,26 @@
   /* 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 (adjustment_needed)
+    {
+      struct stat st;
+      if (lstat (file, &st))
+        return -1;
+      if (update_timespec (&st, &ts))
+        return 0;
+    }
+
 #if HAVE_LUTIMES
   {
     struct timeval timeval[2];
     struct timeval const *t;
-    if (timespec)
+    if (ts)
       {
-        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;
+        timeval[0].tv_sec = ts[0].tv_sec;
+        timeval[0].tv_usec = ts[0].tv_nsec / 1000;
+        timeval[1].tv_sec = ts[1].tv_sec;
+        timeval[1].tv_usec = ts[1].tv_nsec / 1000;
         t = timeval;
       }
     else
--- a/modules/utimecmp
+++ b/modules/utimecmp
@@ -26,6 +26,9 @@
 Include:
 "utimecmp.h"
 
+Link:
+$(LIB_CLOCK_GETTIME)
+
 License:
 GPL
 
--- a/modules/utimens
+++ b/modules/utimens
@@ -11,6 +11,11 @@
 Depends-on:
 dup2
 errno
+lstat
+gettime
+stat-time
+stdbool
+sys_stat
 sys_time
 time
 
@@ -22,8 +27,11 @@
 Include:
 "utimens.h"
 
+Link:
+$(LIB_CLOCK_GETTIME)
+
 License:
 GPL
 
 Maintainer:
-Paul Eggert, Jim Meyering
+Paul Eggert, Jim Meyering, Eric Blake
--- a/modules/utimens-tests
+++ b/modules/utimens-tests
@@ -5,8 +5,6 @@
 tests/test-utimens.c
 
 Depends-on:
-stat-time
-stdbool
 symlink
 timespec
 utimecmp
@@ -17,4 +15,4 @@
 Makefile.am:
 TESTS += test-utimens
 check_PROGRAMS += test-utimens
-test_utimens_LDADD = $(LDADD) @LIBINTL@
+test_utimens_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) @LIBINTL@
--- a/tests/test-sys_stat.c
+++ b/tests/test-sys_stat.c
@@ -1,5 +1,5 @@
 /* Test of <sys/stat.h> substitute.
-   Copyright (C) 2007-2008 Free Software Foundation, Inc.
+   Copyright (C) 2007-2009 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -253,6 +253,12 @@
 verify (!S_ISWHT (S_IFSOCK));
 #endif
 
+#if ((0 <= UTIME_NOW && UTIME_NOW < 1000000000)           \
+     || (0 <= UTIME_OMIT && UTIME_OMIT < 1000000000)      \
+     || UTIME_NOW == UTIME_OMIT)
+invalid UTIME macros
+#endif
+
 /* Check the existence of some types.  */
 nlink_t t1;