bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

utimensat round 1


From: Eric Blake
Subject: utimensat round 1
Date: Thu, 08 Oct 2009 07:13:53 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Here's the current state of my utimensat series.  It's now stable enough
to post for review, although I reserve the right to rebase as I find
tweaks are needed in the testsuite:

git pull git://repo.or.cz/gnulib/ericb.git master

Eric Blake (7):
      [1/7] doc: mention timestamp portability issues
Start by improving the docs of what we know.

      [2/7] utimens: add test
A good test makes it easier to justify improvements.  For now, this test
fails due to an interface bug in gnulib, and due to various platform bugs.

      [3/7] utimens: validate futimens usage
The testsuite can now pass on at least cygwin 1.7, and gl_futimens now
exposes an interface more conducive to writing futimens.

      [4/7] utimens: add lutimens interface
Coreutils should probably use this - this adds support for BSD machines
with lutimes but not utimensat.  However, I did discover that cygwin has a
bug where lstat() modifies atime of symlinks (per POSIX, lstat() should
leave times alone, and only readlink() should modify atime).

      [5/7] utimecmp: support symlink timestamps
Now that we have lutimens, we can make utimecmp use it.

      [6/7] utimens: add UTIME_NOW and UTIME_OMIT support
With this, the test now passes on cygwin 1.5.  I can also simplify
coreutils' touch to use UTIME_OMIT for fewer stat() calls on systems with
native utimensat.

      [7/7] utimens: introduce fdutimens
To use at-func.c, the filename argument has to be first.  I also plan on
adding fdutimensat(dfd,name,fd,times), where name and fd refer to the same
file, so that coreutils' copy.c can switch to fts and still get the
benefits of race avoidance by preferring futimens over utimensat when we
have both a name and an fd for the same file.

Still to go:
implement futimens
implement utimensat
implement fdutimensat
work around Linux bugs in ENOSYS handling
work around Solaris bugs in trailing slash handling

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrN5ZEACgkQ84KuGfSFAYDlTwCfVEk3gLMbO/MEqvck8ZDOPNM8
lNcAoKzYtl3X3tWWOGe5cjawb0BxboGs
=bMpi
-----END PGP SIGNATURE-----
>From 4cf94063f7816dba9a29698f58fa4d5ca8b3ee24 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 7 Oct 2009 11:58:54 -0600
Subject: [PATCH 1/7] doc: mention timestamp portability issues

* doc/glibc-functions/lutimes.texi (lutimes): Refer to utimensat
instead.
* doc/posix-functions/utime.texi (utime): Likewise.
* doc/posix-functions/utimes.texi (utimes): Likewise.
* doc/glibc-functions/futimes.texi (futimes): Refer to futimens
instead.
* doc/posix-functions/futimens.texi (futimens): Mention utimens
module.
* doc/posix-functions/utimensat.texi (utimensat): Likewise.
Mention weakness with symlink timestamps.
* doc/glibc-functions/futimesat.texi (futimesat): New file; refer
to utimensat/futimens instead.
* doc/gnulib.texi (Glibc sys/time.h): Include new file.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                          |   17 +++++++++++++++++
 doc/glibc-functions/futimes.texi   |    6 +++++-
 doc/glibc-functions/futimesat.texi |   21 +++++++++++++++++++++
 doc/glibc-functions/lutimes.texi   |   11 ++++++++++-
 doc/gnulib.texi                    |    2 ++
 doc/posix-functions/futimens.texi  |   12 +++++++++++-
 doc/posix-functions/utime.texi     |    3 +++
 doc/posix-functions/utimensat.texi |   18 +++++++++++++++++-
 doc/posix-functions/utimes.texi    |   13 +++++++++++--
 9 files changed, 97 insertions(+), 6 deletions(-)
 create mode 100644 doc/glibc-functions/futimesat.texi

diff --git a/ChangeLog b/ChangeLog
index dedcfc8..12b6934 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2009-10-08  Eric Blake  <address@hidden>
+
+       doc: mention timestamp portability issues
+       * doc/glibc-functions/lutimes.texi (lutimes): Refer to utimensat
+       instead.
+       * doc/posix-functions/utime.texi (utime): Likewise.
+       * doc/posix-functions/utimes.texi (utimes): Likewise.
+       * doc/glibc-functions/futimes.texi (futimes): Refer to futimens
+       instead.
+       * doc/posix-functions/futimens.texi (futimens): Mention utimens
+       module.
+       * doc/posix-functions/utimensat.texi (utimensat): Likewise.
+       Mention weakness with symlink timestamps.
+       * doc/glibc-functions/futimesat.texi (futimesat): New file; refer
+       to utimensat/futimens instead.
+       * doc/gnulib.texi (Glibc sys/time.h): Include new file.
+
 2009-10-07  Eric Blake  <address@hidden>

        areadlinkat-with-size: new module
diff --git a/doc/glibc-functions/futimes.texi b/doc/glibc-functions/futimes.texi
index e53b6ed..0df4706 100644
--- a/doc/glibc-functions/futimes.texi
+++ b/doc/glibc-functions/futimes.texi
@@ -12,5 +12,9 @@ futimes
 @itemize
 @item
 This function is missing on some platforms:
-AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw, Interix 3.5, BeOS.
+AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw, Interix
+3.5, BeOS.
address@hidden
+This function cannot set full timestamp resolution.  Use
address@hidden(fd,times)} instead.
 @end itemize
diff --git a/doc/glibc-functions/futimesat.texi 
b/doc/glibc-functions/futimesat.texi
new file mode 100644
index 0000000..dbd3d1d
--- /dev/null
+++ b/doc/glibc-functions/futimesat.texi
@@ -0,0 +1,21 @@
address@hidden futimesat
address@hidden @code{futimesat}
address@hidden futimesat
+
+Gnulib module: ---
+
+Portability problems fixed by Gnulib:
address@hidden
address@hidden itemize
+
+Portability problems not fixed by Gnulib:
address@hidden
address@hidden
+This function is missing on some platforms:
+glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 8, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
address@hidden
+This function cannot set full timestamp resolution.  Use
address@hidden ? utimensat(fd,file,times,0) : futimens(fd,times)} instead.
address@hidden itemize
diff --git a/doc/glibc-functions/lutimes.texi b/doc/glibc-functions/lutimes.texi
index 96308e0..2971c99 100644
--- a/doc/glibc-functions/lutimes.texi
+++ b/doc/glibc-functions/lutimes.texi
@@ -12,5 +12,14 @@ lutimes
 @itemize
 @item
 This function is missing on some platforms:
-MacOS X 10.3, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, 
mingw, Interix 3.5, BeOS.
+MacOS X 10.3, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1,
+Solaris 10, mingw, Interix 3.5, BeOS.
address@hidden
+This function cannot set full timestamp resolution.  Use
address@hidden(AT_FDCWD,file,times,AT_SYMLINK_NOFOLLOW)} instead.
address@hidden
+The mere act of using @code{lstat} modifies the access time of
+symlinks on some platforms, so @code{lutimes} can only effectively
+change modification time:
+Cygwin.
 @end itemize
diff --git a/doc/gnulib.texi b/doc/gnulib.texi
index 37cec41..5bf8f4a 100644
--- a/doc/gnulib.texi
+++ b/doc/gnulib.texi
@@ -5467,12 +5467,14 @@ Glibc sys/time.h
 @menu
 * adjtime::
 * futimes::
+* futimesat::
 * lutimes::
 * settimeofday::
 @end menu

 @include glibc-functions/adjtime.texi
 @include glibc-functions/futimes.texi
address@hidden glibc-functions/futimesat.texi
 @include glibc-functions/lutimes.texi
 @include glibc-functions/settimeofday.texi

diff --git a/doc/posix-functions/futimens.texi 
b/doc/posix-functions/futimens.texi
index 0fb5935..9200653 100644
--- a/doc/posix-functions/futimens.texi
+++ b/doc/posix-functions/futimens.texi
@@ -15,5 +15,15 @@ futimens
 @item
 This function is missing on some platforms:
 glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 
3.5, BeOS.
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
address@hidden
+This function returns a bogus value instead of failing with
address@hidden on some platforms:
+Linux kernel 2.6.21.
address@hidden
+Some platforms lack the ability to change the timestamps of a file
+descriptor, so this function can fail with @code{ENOSYS}.
 @end itemize
+
+The gnulib module utimens provides a similar interface.
diff --git a/doc/posix-functions/utime.texi b/doc/posix-functions/utime.texi
index 9b5a7ee..a97693d 100644
--- a/doc/posix-functions/utime.texi
+++ b/doc/posix-functions/utime.texi
@@ -15,4 +15,7 @@ utime

 Portability problems not fixed by Gnulib:
 @itemize
address@hidden
+This function cannot set full timestamp resolution.  Use
address@hidden(AT_FDCWD,file,times,0)} instead.
 @end itemize
diff --git a/doc/posix-functions/utimensat.texi 
b/doc/posix-functions/utimensat.texi
index 23a5582..e7678f9 100644
--- a/doc/posix-functions/utimensat.texi
+++ b/doc/posix-functions/utimensat.texi
@@ -15,5 +15,21 @@ utimensat
 @item
 This function is missing on some platforms:
 glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
-5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 
3.5, BeOS.
+5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
+Interix 3.5, BeOS.
address@hidden
+This function returns a bogus value instead of failing with
address@hidden on some platforms:
+Linux kernel 2.6.21.
address@hidden
+On some platforms, timestamps of symbolic links cannot be modified, so
+this function fails with @code{ENOSYS} if passed the flag
address@hidden
address@hidden
+The mere act of using @code{lstat} modifies the access time of
+symlinks on some platforms, so @code{utimensat} with
address@hidden can only effectively change modification time:
+Cygwin.
 @end itemize
+
+The gnulib module utimens provides a similar interface.
diff --git a/doc/posix-functions/utimes.texi b/doc/posix-functions/utimes.texi
index 8ab90d9..5f6cb7b 100644
--- a/doc/posix-functions/utimes.texi
+++ b/doc/posix-functions/utimes.texi
@@ -16,6 +16,15 @@ utimes
 This function is missing on some platforms:
 mingw, Interix 3.5, BeOS.
 @item
-This function is marked as ``legacy'' in POSIX.  Better use @code{utime}
-instead.
+This function cannot set full timestamp resolution.  In particular,
+some platforms incorrectly round rather than truncate.  Use
address@hidden(AT_FDCWD,file,times,0)} instead.
address@hidden
+On some platforms, @code{utimes (file, NULL)} fails to set the
+file's timestamp to the current time:
+glibc 2.3.3.
address@hidden
+On some platforms, @code{utimes} failed on read-only files when
address@hidden worked fine.
+glibc 2.2.5.
 @end itemize
-- 
1.6.5.rc1


>From 6591f0b74c682e01344b90a4a2f21132a2f6f021 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 7 Oct 2009 17:08:17 -0600
Subject: [PATCH 2/7] utimens: add test

Exposes holes in our API, and several platform bugs.

* modules/utimens-tests: New test.
* tests/test-utimens.h: New file.
* tests/test-utimens.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog             |    5 ++
 modules/utimens-tests |   15 ++++
 tests/test-utimens.c  |   66 ++++++++++++++++++
 tests/test-utimens.h  |  179 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 265 insertions(+), 0 deletions(-)
 create mode 100644 modules/utimens-tests
 create mode 100644 tests/test-utimens.c
 create mode 100644 tests/test-utimens.h

diff --git a/ChangeLog b/ChangeLog
index 12b6934..141faea 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-08  Eric Blake  <address@hidden>

+       utimens: add test
+       * modules/utimens-tests: New test.
+       * tests/test-utimens.h: New file.
+       * tests/test-utimens.c: Likewise.
+
        doc: mention timestamp portability issues
        * doc/glibc-functions/lutimes.texi (lutimes): Refer to utimensat
        instead.
diff --git a/modules/utimens-tests b/modules/utimens-tests
new file mode 100644
index 0000000..7e54ca1
--- /dev/null
+++ b/modules/utimens-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-utimens.h
+tests/test-utimens.c
+
+Depends-on:
+stat-time
+stdbool
+timespec
+utimecmp
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-utimens
+check_PROGRAMS += test-utimens
diff --git a/tests/test-utimens.c b/tests/test-utimens.c
new file mode 100644
index 0000000..105c81b
--- /dev/null
+++ b/tests/test-utimens.c
@@ -0,0 +1,66 @@
+/* Tests of utimens.
+   Copyright (C) 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
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include "utimens.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "stat-time.h"
+#include "timespec.h"
+#include "utimecmp.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-utimens.t"
+
+#include "test-utimens.h"
+
+/* Wrap gl_futimens to behave like futimens.  */
+static int
+do_futimens (int fd, struct timespec const times[2])
+{
+  return gl_futimens (fd, NULL, times);
+}
+
+int
+main ()
+{
+  /* Clean up any trash from prior testsuite runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  ASSERT (test_utimens (utimens) == 0);
+  return test_futimens (do_futimens, true);
+}
diff --git a/tests/test-utimens.h b/tests/test-utimens.h
new file mode 100644
index 0000000..db9d685
--- /dev/null
+++ b/tests/test-utimens.h
@@ -0,0 +1,179 @@
+/* Test of file timestamp modification functions.
+   Copyright (C) 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
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* This file assumes that BASE and ASSERT are already defined, and
+   that appropriate headers are already included.  */
+
+enum {
+  BILLION = 1000 * 1000 * 1000,
+
+  Y2K = 946684800, /* Jan 1, 2000, in seconds since epoch.  */
+
+  /* Bogus positive and negative tv_nsec values, but without colliding
+     with UTIME_NOW or UTIME_OMIT.  */
+  UTIME_BOGUS_POS = BILLION + ((UTIME_NOW == BILLION)
+                               + (UTIME_NOW == BILLION + 1)
+                               + (UTIME_OMIT == BILLION)
+                               + (UTIME_OMIT == BILLION + 1)),
+  UTIME_BOGUS_NEG = -1 - ((UTIME_NOW == -1)
+                          + (UTIME_NOW == -2)
+                          + (UTIME_OMIT == -1)
+                          + (UTIME_OMIT == -2))
+};
+
+/* This function is designed to test both utimens(a,b) and
+   utimensat(AT_FDCWD,a,b,0).  FUNC is the function to test.  */
+static int
+test_utimens (int (*func) (char const *, struct timespec const *))
+{
+  struct stat st1;
+  struct stat st2;
+
+  ASSERT (close (creat (BASE "file", 0600)) == 0);
+  ASSERT (stat (BASE "file", &st1) == 0);
+
+  /* Invalid arguments.  */
+  errno = 0;
+  ASSERT (func ("no_such", NULL) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("", NULL) == -1);
+  ASSERT (errno == ENOENT);
+  {
+    struct timespec ts[2] = { { Y2K, UTIME_BOGUS_POS }, { Y2K, 0 } };
+    errno = 0;
+    ASSERT (func (BASE "file", ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, UTIME_BOGUS_NEG } };
+    errno = 0;
+    ASSERT (func (BASE "file", ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  ASSERT (stat (BASE "file", &st2) == 0);
+  ASSERT (st1.st_atime == st2.st_atime);
+  ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2));
+  ASSERT (utimecmp (BASE "file", &st1, &st2, 0) == 0);
+
+  /* Set both times.  */
+  {
+    struct timespec ts[2] = { { Y2K, BILLION / 2 }, { Y2K, BILLION / 2 } };
+    ASSERT (func (BASE "file", ts) == 0);
+    ASSERT (stat (BASE "file", &st2) == 0);
+    ASSERT (st2.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st2));
+    ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+    ASSERT (st2.st_mtime == Y2K);
+    ASSERT (0 <= get_stat_mtime_ns (&st2));
+    ASSERT (get_stat_mtime_ns (&st2) <= BILLION / 2);
+  }
+
+  /* Play with UTIME_OMIT, UTIME_NOW.  */
+  {
+    struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    ASSERT (func (BASE "file", ts) == 0);
+    ASSERT (stat (BASE "file", &st2) == 0);
+    ASSERT (st2.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st2));
+    ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+    ASSERT (utimecmp (BASE "file", &st1, &st2, 0) <= 0);
+  }
+
+  /* Cleanup.  */
+  ASSERT (unlink (BASE "file") == 0);
+  return 0;
+}
+
+/* This function is designed to test both gl_futimens(a,NULL,b) and
+   futimens(a,b).  FUNC is the function to test.  If PRINT, warn
+   before skipping tests with status 77.  */
+static int
+test_futimens (int (*func) (int, struct timespec const *),
+               bool print)
+{
+  int fd = creat (BASE "file", 0600);
+  int result;
+  struct stat st1;
+  struct stat st2;
+  ASSERT (0 <= fd);
+
+  /* Sanity check.  */
+  errno = 0;
+  result = func (fd, NULL);
+  if (result == -1 && errno == ENOSYS)
+    {
+      ASSERT (close (fd) == 0);
+      ASSERT (unlink (BASE "file") == 0);
+      if (print)
+        fputs ("skipping test: "
+               "setting fd time not supported on this file system\n",
+               stderr);
+      return 77;
+    }
+  ASSERT (!result);
+  ASSERT (fstat (fd, &st1) == 0);
+
+  /* Invalid arguments.  */
+  errno = 0;
+  ASSERT (func (AT_FDCWD, NULL) == -1);
+  ASSERT (errno == EBADF);
+  {
+    struct timespec ts[2] = { { Y2K, UTIME_BOGUS_POS }, { Y2K, 0 } };
+    errno = 0;
+    ASSERT (func (fd, ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, UTIME_BOGUS_NEG } };
+    errno = 0;
+    ASSERT (func (fd, ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  ASSERT (fstat (fd, &st2) == 0);
+  ASSERT (st1.st_atime == st2.st_atime);
+  ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2));
+  ASSERT (utimecmp (BASE "file", &st1, &st2, 0) == 0);
+
+  /* Set both times.  */
+  {
+    struct timespec ts[2] = { { Y2K, BILLION / 2 }, { Y2K, BILLION / 2 } };
+    ASSERT (func (fd, ts) == 0);
+    ASSERT (fstat (fd, &st2) == 0);
+    ASSERT (st2.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st2));
+    ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+    ASSERT (st2.st_mtime == Y2K);
+    ASSERT (0 <= get_stat_mtime_ns (&st2));
+    ASSERT (get_stat_mtime_ns (&st2) <= BILLION / 2);
+  }
+
+  /* Play with UTIME_OMIT, UTIME_NOW.  */
+  {
+    struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    ASSERT (func (fd, ts) == 0);
+    ASSERT (fstat (fd, &st2) == 0);
+    ASSERT (st2.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st2));
+    ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+    ASSERT (utimecmp (BASE "file", &st1, &st2, 0) <= 0);
+  }
+
+  /* Cleanup.  */
+  ASSERT (close (fd) == 0);
+  ASSERT (unlink (BASE "file") == 0);
+  return 0;
+}
-- 
1.6.5.rc1


>From c2d5aa89dad10e6d8e9711c09bd51eccbaa891da Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Tue, 6 Oct 2009 12:23:32 -0600
Subject: [PATCH 3/7] utimens: validate futimens usage

Using gl_futimens(fd,NULL,times) as an implementation for futimens
won't work unless we reliably detect EBADF for out-of-range fd.

* lib/utimens.c (gl_futimens): Require valid fd up front, using
fewer syscalls on failure later on.
* modules/utimens (Depends-on): Add dup2.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog       |    5 +++++
 lib/utimens.c   |   18 +++++-------------
 modules/utimens |    1 +
 3 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 141faea..a8f1303 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-08  Eric Blake  <address@hidden>

+       utimens: validate futimens usage
+       * lib/utimens.c (gl_futimens): Require valid fd up front, using
+       fewer syscalls on failure later on.
+       * modules/utimens (Depends-on): Add dup2.
+
        utimens: add test
        * modules/utimens-tests: New test.
        * tests/test-utimens.h: New file.
diff --git a/lib/utimens.c b/lib/utimens.c
index 10d2c82..85e91ee 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -55,9 +55,12 @@ struct utimbuf
    Return 0 on success, -1 (setting errno) on failure.  */

 int
-gl_futimens (int fd _UNUSED_PARAMETER_,
-             char const *file, struct timespec const timespec[2])
+gl_futimens (int fd, char const *file, struct timespec const timespec[2])
 {
+  /* Require that at least one of FD or FILE are valid.  */
+  if (!file && dup2 (fd, fd) != fd)
+    return -1;
+
   /* Some Linux-based NFS clients are buggy, and mishandle time stamps
      of files in NFS file systems in some cases.  We have no
      configure-time test for this, but please see
@@ -163,17 +166,6 @@ gl_futimens (int fd _UNUSED_PARAMETER_,
 #if ! (HAVE_FUTIMESAT || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
         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;
       }

diff --git a/modules/utimens b/modules/utimens
index 4251485..48fbe8e 100644
--- a/modules/utimens
+++ b/modules/utimens
@@ -9,6 +9,7 @@ m4/utimens.m4
 m4/utimes.m4

 Depends-on:
+dup2
 errno
 sys_time
 time
-- 
1.6.5.rc1


>From 0180202bca9707bd96c573c95fad435ddcd22141 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 7 Oct 2009 13:36:45 -0600
Subject: [PATCH 4/7] utimens: add lutimens interface

Wraps utimensat(,AT_SYMLINK_NOFOLLOW) or lutimes, when supported;
otherwise fail with ENOSYS.

* lib/utimens.c (lutimens): New function.
* m4/utimens.m4 (gl_UTIMENS): Check for lutimes.
* lib/utimens.h (lutimens): Declare new interface.
* modules/utimens-tests (Depends-on): Add nanosleep, symlink.
* tests/test-utimens.c (main): Enhance test.
* tests/test-utimens.h (test_lutimens): New function.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog             |    8 ++++
 lib/utimens.c         |   60 ++++++++++++++++++++++++++++
 lib/utimens.h         |    1 +
 m4/utimens.m4         |    8 ++--
 modules/utimens-tests |    2 +
 tests/test-utimens.c  |   12 +++++-
 tests/test-utimens.h  |  105 +++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 191 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index a8f1303..4019475 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2009-10-08  Eric Blake  <address@hidden>

+       utimens: add lutimens interface
+       * lib/utimens.c (lutimens): New function.
+       * m4/utimens.m4 (gl_UTIMENS): Check for lutimes.
+       * lib/utimens.h (lutimens): Declare new interface.
+       * modules/utimens-tests (Depends-on): Add nanosleep, symlink.
+       * tests/test-utimens.c (main): Enhance test.
+       * tests/test-utimens.h (test_lutimens): New function.
+
        utimens: validate futimens usage
        * lib/utimens.c (gl_futimens): Require valid fd up front, using
        fewer syscalls on failure later on.
diff --git a/lib/utimens.c b/lib/utimens.c
index 85e91ee..7f28a93 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -197,3 +197,63 @@ utimens (char const *file, struct timespec const 
timespec[2])
 {
   return gl_futimens (-1, file, 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.  */
+int
+lutimens (char const *file _UNUSED_PARAMETER_,
+          struct timespec const timespec[2] _UNUSED_PARAMETER_)
+{
+  /* 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
+     worry about bogus return values.  */
+
+#if HAVE_UTIMENSAT
+  {
+    int result = utimensat (AT_FDCWD, file, timespec, 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;
+# endif
+
+    if (result == 0 || errno != ENOSYS)
+      return result;
+  }
+#endif /* HAVE_UTIMENSAT */
+
+  /* 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_LUTIMES
+  {
+    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;
+
+    return lutimes (file, t);
+  }
+#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.  */
+  errno = ENOSYS;
+  return -1;
+}
diff --git a/lib/utimens.h b/lib/utimens.h
index 169521d..8b3ccef 100644
--- a/lib/utimens.h
+++ b/lib/utimens.h
@@ -1,3 +1,4 @@
 #include <time.h>
 int gl_futimens (int, char const *, struct timespec const [2]);
 int utimens (char const *, struct timespec const [2]);
+int lutimens (char const *, struct timespec const [2]);
diff --git a/m4/utimens.m4 b/m4/utimens.m4
index 01a3184..381d087 100644
--- a/m4/utimens.m4
+++ b/m4/utimens.m4
@@ -1,10 +1,10 @@
-dnl Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software
-dnl Foundation, Inc.
+dnl Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free
+dnl Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
 dnl with or without modifications, as long as this notice is preserved.

-dnl serial 2
+dnl serial 3

 AC_DEFUN([gl_UTIMENS],
 [
@@ -14,5 +14,5 @@ AC_DEFUN([gl_UTIMENS],
   AC_REQUIRE([gl_FUNC_UTIMES])
   AC_REQUIRE([gl_CHECK_TYPE_STRUCT_TIMESPEC])
   AC_REQUIRE([gl_CHECK_TYPE_STRUCT_UTIMBUF])
-  AC_CHECK_FUNCS_ONCE([futimes futimesat futimens utimensat])
+  AC_CHECK_FUNCS_ONCE([futimes futimesat futimens utimensat lutimes])
 ])
diff --git a/modules/utimens-tests b/modules/utimens-tests
index 7e54ca1..0ecf95a 100644
--- a/modules/utimens-tests
+++ b/modules/utimens-tests
@@ -3,8 +3,10 @@ tests/test-utimens.h
 tests/test-utimens.c

 Depends-on:
+nanosleep
 stat-time
 stdbool
+symlink
 timespec
 utimecmp

diff --git a/tests/test-utimens.c b/tests/test-utimens.c
index 105c81b..a4f04ec 100644
--- a/tests/test-utimens.c
+++ b/tests/test-utimens.c
@@ -58,9 +58,19 @@ do_futimens (int fd, struct timespec const times[2])
 int
 main ()
 {
+  int result1;
+  int result2;
+
   /* Clean up any trash from prior testsuite runs.  */
   ASSERT (system ("rm -rf " BASE "*") == 0);

   ASSERT (test_utimens (utimens) == 0);
-  return test_futimens (do_futimens, true);
+  result1 = test_futimens (do_futimens, true);
+  if (result1)
+    ASSERT (result1 == 77);
+  /* Print only one skip message.  */
+  result2 = test_lutimens (lutimens, result1 == 0);
+  if (result2)
+    ASSERT (result2 == 77);
+  return result1 | result2;
 }
diff --git a/tests/test-utimens.h b/tests/test-utimens.h
index db9d685..0ce084d 100644
--- a/tests/test-utimens.h
+++ b/tests/test-utimens.h
@@ -177,3 +177,108 @@ test_futimens (int (*func) (int, struct timespec const *),
   ASSERT (unlink (BASE "file") == 0);
   return 0;
 }
+
+/* This function is designed to test both lutimens(a,b) and
+   utimensat(AT_FDCWD,a,b,AT_SYMLINK_NOFOLLOW).  FUNC is the function
+   to test.  If PRINT, warn before skipping tests with status 77.  */
+static int
+test_lutimens (int (*func) (char const *, struct timespec const *))
+{
+  int result;
+  struct stat st1;
+  struct stat st2;
+  bool atime_supported = true;
+
+  if (symlink ("nowhere", BASE "link"))
+    {
+      if (print)
+        fputs ("skipping test: symlinks not supported on this file system\n",
+               stderr);
+      return 77;
+    }
+  errno = 0;
+  result = func (BASE "link", NULL);
+  if (result == -1 && errno == ENOSYS)
+    {
+      ASSERT (unlink (BASE "link") == 0);
+      if (print)
+        fputs ("skipping test: "
+               "setting symlink time not supported on this file system\n",
+               stderr);
+      return 77;
+    }
+  ASSERT (!result);
+  ASSERT (lstat (BASE "link", &st1) == 0);
+  {
+    /* On Cygwin, the mere act of lstat changes symlink atime, even
+       though POSIX says that only readlink is allowed to do that.
+       Sleeping for one millisecond is enough to expose this.  */
+    struct timespec ts = { 0, 1000 * 1000 };
+    nanosleep (&ts, NULL);
+    ASSERT (lstat (BASE "link", &st2) == 0);
+    if (st1.st_atime != st2.st_atime
+        || get_stat_atime_ns (&st1) != get_stat_atime_ns (&st2))
+      atime_supported = false;
+  }
+
+  /* Invalid arguments.  */
+  errno = 0;
+  ASSERT (func ("no_such", NULL) == -1);
+  ASSERT (errno == ENOENT);
+  errno = 0;
+  ASSERT (func ("", NULL) == -1);
+  ASSERT (errno == ENOENT);
+  {
+    struct timespec ts[2] = { { Y2K, UTIME_BOGUS_POS }, { Y2K, 0 } };
+    errno = 0;
+    ASSERT (func (BASE "link", ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, UTIME_BOGUS_NEG } };
+    errno = 0;
+    ASSERT (func (BASE "link", ts) == -1);
+    ASSERT (errno == EINVAL);
+  }
+  ASSERT (lstat (BASE "link", &st2) == 0);
+  if (atime_supported)
+    {
+      ASSERT (st1.st_atime == st2.st_atime);
+      ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2));
+    }
+  ASSERT (utimecmp (BASE "link", &st1, &st2, 0) == 0);
+
+  /* Set both times.  */
+  {
+    struct timespec ts[2] = { { Y2K, BILLION / 2 }, { Y2K, BILLION / 2 } };
+    ASSERT (func (BASE "link", ts) == 0);
+    ASSERT (lstat (BASE "link", &st2) == 0);
+    if (atime_supported)
+      {
+        ASSERT (st2.st_atime == Y2K);
+        ASSERT (0 <= get_stat_atime_ns (&st2));
+        ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+      }
+    ASSERT (st2.st_mtime == Y2K);
+    ASSERT (0 <= get_stat_mtime_ns (&st2));
+    ASSERT (get_stat_mtime_ns (&st2) <= BILLION / 2);
+  }
+
+  /* Play with UTIME_OMIT, UTIME_NOW.  */
+  {
+    struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    ASSERT (func (BASE "link", ts) == 0);
+    ASSERT (lstat (BASE "link", &st2) == 0);
+    if (atime_supported)
+      {
+        ASSERT (st2.st_atime == Y2K);
+        ASSERT (0 <= get_stat_atime_ns (&st2));
+        ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
+      }
+    ASSERT (utimecmp (BASE "link", &st1, &st2, 0) <= 0);
+  }
+
+  /* Cleanup.  */
+  ASSERT (unlink (BASE "link") == 0);
+  return 0;
+}
-- 
1.6.5.rc1


>From e215d1479b9a9d4c6758880366a46709727aa04b Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 7 Oct 2009 22:06:49 -0600
Subject: [PATCH 5/7] utimecmp: support symlink timestamps

* lib/utimecmp.c (utimecmp): Use new interface.
* modules/utimecmp (Depends-on): Add lstat.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog            |    4 ++++
 lib/utimecmp.c       |   22 +++++++++++++---------
 modules/utimecmp     |    1 +
 tests/test-utimens.h |    2 +-
 4 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 4019475..b743841 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2009-10-08  Eric Blake  <address@hidden>

+       utimecmp: support symlink timestamps
+       * lib/utimecmp.c (utimecmp): Use new interface.
+       * modules/utimecmp (Depends-on): Add lstat.
+
        utimens: add lutimens interface
        * lib/utimens.c (lutimens): New function.
        * m4/utimens.m4 (gl_UTIMENS): Check for lutimes.
diff --git a/lib/utimecmp.c b/lib/utimecmp.c
index 6a90873..6243a44 100644
--- a/lib/utimecmp.c
+++ b/lib/utimecmp.c
@@ -1,6 +1,7 @@
 /* utimecmp.c -- compare file time stamps

-   Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+   Copyright (C) 2004, 2005, 2006, 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
@@ -258,16 +259,16 @@ utimecmp (char const *dst_name,
              /* Set the modification time.  But don't try to set the
                 modification time of symbolic links; on many hosts this sets
                 the time of the pointed-to file.  */
-             if (S_ISLNK (dst_stat->st_mode)
-                 || utimens (dst_name, timespec) != 0)
+             if ((S_ISLNK (dst_stat->st_mode)
+                  ? lutimens (dst_name, timespec)
+                  : utimens (dst_name, timespec)) != 0)
                return -2;

-             /* Read the modification time that was set.  It's safe to call
-                'stat' here instead of worrying about 'lstat'; either the
-                caller used 'stat', or the caller used 'lstat' and found
-                something other than a symbolic link.  */
+             /* Read the modification time that was set.  */
              {
-               int stat_result = stat (dst_name, &dst_status);
+               int stat_result = (S_ISLNK (dst_stat->st_mode)
+                                  ? lstat (dst_name, &dst_status)
+                                  : stat (dst_name, &dst_status));

                if (stat_result
                    | (dst_status.st_mtime ^ dst_m_s)
@@ -277,7 +278,10 @@ utimecmp (char const *dst_name,
                       it changed.  Change it back as best we can.  */
                    timespec[1].tv_sec = dst_m_s;
                    timespec[1].tv_nsec = dst_m_ns;
-                   utimens (dst_name, timespec);
+                   if (S_ISLNK (dst_stat->st_mode))
+                     lutimens (dst_name, timespec);
+                   else
+                     utimens (dst_name, timespec);
                  }

                if (stat_result != 0)
diff --git a/modules/utimecmp b/modules/utimecmp
index 12da25e..238ab30 100644
--- a/modules/utimecmp
+++ b/modules/utimecmp
@@ -13,6 +13,7 @@ time
 utimens
 xalloc
 intprops
+lstat
 stdbool
 stdint
 verify
diff --git a/tests/test-utimens.h b/tests/test-utimens.h
index 0ce084d..ff11fb8 100644
--- a/tests/test-utimens.h
+++ b/tests/test-utimens.h
@@ -182,7 +182,7 @@ test_futimens (int (*func) (int, struct timespec const *),
    utimensat(AT_FDCWD,a,b,AT_SYMLINK_NOFOLLOW).  FUNC is the function
    to test.  If PRINT, warn before skipping tests with status 77.  */
 static int
-test_lutimens (int (*func) (char const *, struct timespec const *))
+test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
 {
   int result;
   struct stat st1;
-- 
1.6.5.rc1


>From 9c500587dde97534f2b79970068ba57a7601545e Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 7 Oct 2009 16:05:34 -0600
Subject: [PATCH 6/7] 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.

* 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.
* modules/utimens-tests (Depends-on): Drop stat-time, stdbool.
* 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.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                       |   12 ++++
 doc/posix-headers/sys_stat.texi |    3 +
 lib/sys_stat.in.h               |    6 ++
 lib/utimens.c                   |  132 ++++++++++++++++++++++++++++++++++-----
 modules/utimens                 |    7 ++-
 modules/utimens-tests           |    2 -
 tests/test-sys_stat.c           |    8 ++-
 7 files changed, 151 insertions(+), 19 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b743841..e85511d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-10-08  Eric Blake  <address@hidden>

+       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.
+       * modules/utimens-tests (Depends-on): Drop stat-time, stdbool.
+       * 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.
+
        utimecmp: support symlink timestamps
        * lib/utimecmp.c (utimecmp): Use new interface.
        * modules/utimecmp (Depends-on): Add lstat.
diff --git a/doc/posix-headers/sys_stat.texi b/doc/posix-headers/sys_stat.texi
index 8fbfc06..33c8683 100644
--- a/doc/posix-headers/sys_stat.texi
+++ b/doc/posix-headers/sys_stat.texi
@@ -17,6 +17,9 @@ sys/stat.h
 on other platforms.
 @item
 The functions @code{lstat} and @code{mkdir} are not declared on mingw.
address@hidden
+The macros @code{UTIME_NOW} and @code{UTIME_OMIT} are missing on some
+platforms.
 @end itemize

 Portability problems not fixed by Gnulib:
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index cc43822..7051d53 100644
--- 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" {
diff --git a/lib/utimens.c b/lib/utimens.c
index 7f28a93..9972029 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -26,10 +26,14 @@

 #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 +48,71 @@ struct utimbuf
 };
 #endif

+/* Validate the requested timestamps.  Return 0 if the timespec can be
+   used as-is across all utime* interfaces, or 1 if the timespec needs
+   to be adjusted for any interface less powerful than utimensat.  If
+   timespec is out of range, return -1 and set errno to EINVAL.  */
+static int
+validate_timespec (struct timespec const timespec[2])
+{
+  if (!timespec)
+    return 0;
+  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;
+    }
+  return (timespec[0].tv_nsec == UTIME_NOW
+          || timespec[0].tv_nsec == UTIME_OMIT
+          || timespec[1].tv_nsec == UTIME_NOW
+          || timespec[1].tv_nsec == UTIME_OMIT);
+}
+
+/* Normalize any UTIME_NOW or UTIME_OMIT values in TIMESPEC, using ST
+   which describes the current timestamps of the file.  Place the
+   results in *ADJUSTED.  If both times are now, set *ADJUSTED to NULL
+   (as this can avoid some permissions issues).  If both times are
+   omitted, return true (nothing further beyond the collection of
+   STATBUF is necessary); otherwise return false.  */
+static bool
+update_timespec (struct stat const *st, struct timespec const timespec[2],
+                 struct timespec **adjusted)
+{
+  struct timespec *target = *adjusted;
+  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)
+    {
+      *adjusted = NULL;
+      return false;
+    }
+
+  if (timespec[0].tv_nsec == UTIME_OMIT)
+    target[0] = get_stat_atime (st);
+  else if (timespec[0].tv_nsec == UTIME_NOW)
+    gettime (&target[0]);
+  else
+    target[0] = timespec[0];
+
+  if (timespec[1].tv_nsec == UTIME_OMIT)
+    target[1] = get_stat_mtime (st);
+  else if (timespec[1].tv_nsec == UTIME_NOW)
+    gettime (&target[1]);
+  else
+    target[1] = 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 +126,12 @@ struct utimbuf
 int
 gl_futimens (int fd, char const *file, struct timespec const timespec[2])
 {
+  struct timespec adjusted_timespec[2];
+  struct timespec *ts = (struct timespec *) timespec;
+  int adjustment_needed = validate_timespec (timespec);
+  if (adjustment_needed < 0)
+    return -1;
+
   /* Require that at least one of FD or FILE are valid.  */
   if (!file && dup2 (fd, fd) != fd)
     return -1;
@@ -118,16 +193,27 @@ gl_futimens (int fd, char const *file, struct timespec 
const timespec[2])
   /* 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;
+      ts = adjusted_timespec;
+      if (update_timespec (&st, timespec, &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
@@ -175,10 +261,10 @@ gl_futimens (int fd, char const *file, struct timespec 
const timespec[2])
     {
       struct utimbuf utimbuf;
       struct utimbuf const *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
@@ -202,9 +288,14 @@ utimens (char const *file, struct timespec const 
timespec[2])
    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 = (struct timespec *) timespec;
+  int adjustment_needed = validate_timespec (timespec);
+  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
@@ -233,16 +324,27 @@ lutimens (char const *file _UNUSED_PARAMETER_,
   /* 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;
+      ts = adjusted_timespec;
+      if (update_timespec (&st, timespec, &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
diff --git a/modules/utimens b/modules/utimens
index 48fbe8e..ad3b462 100644
--- a/modules/utimens
+++ b/modules/utimens
@@ -11,6 +11,11 @@ m4/utimes.m4
 Depends-on:
 dup2
 errno
+lstat
+gettime
+stat-time
+stdbool
+sys_stat
 sys_time
 time

@@ -26,4 +31,4 @@ License:
 GPL

 Maintainer:
-Paul Eggert, Jim Meyering
+Paul Eggert, Jim Meyering, Eric Blake
diff --git a/modules/utimens-tests b/modules/utimens-tests
index 0ecf95a..a080bf2 100644
--- a/modules/utimens-tests
+++ b/modules/utimens-tests
@@ -4,8 +4,6 @@ tests/test-utimens.c

 Depends-on:
 nanosleep
-stat-time
-stdbool
 symlink
 timespec
 utimecmp
diff --git a/tests/test-sys_stat.c b/tests/test-sys_stat.c
index 4b5eb76..25fe30d 100644
--- 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_IFLNK));
 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;

-- 
1.6.5.rc1


>From 0afdbe84f8672569e9b7b0c5c70c628a2503b197 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 8 Oct 2009 06:40:08 -0600
Subject: [PATCH 7/7] utimens: introduce fdutimens

at-func.c wants the file name argument first.  This also paves the
way to add fdutimensat(dfd,name,fd,times,flag).

* lib/utimens.h (fdutimens): New prototype.
* lib/utimens.c (gl_futimens): Move guts...
(fdutimens): ...to new interface.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog     |    5 +++++
 lib/utimens.c |   18 +++++++++++++++++-
 lib/utimens.h |    1 +
 3 files changed, 23 insertions(+), 1 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e85511d..154fdcb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2009-10-08  Eric Blake  <address@hidden>

+       utimens: introduce fdutimens
+       * lib/utimens.h (fdutimens): New prototype.
+       * lib/utimens.c (gl_futimens): Move guts...
+       (fdutimens): ...to new interface.
+
        utimens: add UTIME_NOW and UTIME_OMIT support
        * lib/utimens.c (validate_timespec, update_timespec): New helper
        functions.
diff --git a/lib/utimens.c b/lib/utimens.c
index 9972029..f7304c1 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -124,7 +124,7 @@ update_timespec (struct stat const *st, struct timespec 
const timespec[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 = (struct timespec *) timespec;
@@ -276,6 +276,22 @@ 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
diff --git a/lib/utimens.h b/lib/utimens.h
index 8b3ccef..a02c6a4 100644
--- a/lib/utimens.h
+++ b/lib/utimens.h
@@ -1,4 +1,5 @@
 #include <time.h>
+int fdutimens (char const *, int, struct timespec const [2]);
 int gl_futimens (int, char const *, struct timespec const [2]);
 int utimens (char const *, struct timespec const [2]);
 int lutimens (char const *, struct timespec const [2]);
-- 
1.6.5.rc1


reply via email to

[Prev in Thread] Current Thread [Next in Thread]