New upstream version 1.9
[debian/gzip] / lib / utimens.c
index ffc60b699a68e014ffb1fa12e5596e971ec512af..c0b0704eb29d29a50e53bb9b35f9ef41c218249f 100644 (file)
@@ -1,7 +1,6 @@
 /* Set file access and modification times.
 
-   Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free
-   Software Foundation, Inc.
+   Copyright (C) 2003-2018 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
@@ -14,7 +13,7 @@
    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/>.  */
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
 /* Written by Paul Eggert.  */
 
 
 #include <config.h>
 
+#define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
 #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 <utime.h>
 
 #include "stat-time.h"
 #include "timespec.h"
 
-#if HAVE_UTIME_H
-# include <utime.h>
-#endif
-
-/* Some systems (even some that do have <utime.h>) don't declare this
-   structure anywhere.  */
-#ifndef HAVE_STRUCT_UTIMBUF
-struct utimbuf
-{
-  long actime;
-  long modtime;
-};
+/* On native Windows, use SetFileTime; but avoid this when compiling
+   GNU Emacs, which arranges for this in some other way and which
+   defines WIN32_LEAN_AND_MEAN itself.  */
+
+#if ((defined _WIN32 || defined __WIN32__) \
+     && ! defined __CYGWIN__ && ! defined EMACS_CONFIGURATION)
+# define USE_SETFILETIME
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# if GNULIB_MSVC_NOTHROW
+#  include "msvc-nothrow.h"
+# else
+#  include <io.h>
+# endif
 #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 */
-
 /* Solaris 9 mistakenly succeeds when given a non-directory with a
    trailing slash.  Force the use of rpl_stat for a fix.  */
 #ifndef REPLACE_FUNC_STAT_FILE
 # define REPLACE_FUNC_STAT_FILE 0
 #endif
 
+#if HAVE_UTIMENSAT || HAVE_FUTIMENS
+/* Cache variables for whether the utimensat syscall works; used to
+   avoid calling the syscall if we know it will just fail with ENOSYS,
+   and to avoid unnecessary work in massaging timestamps if the
+   syscall will work.  Multiple variables are needed, to distinguish
+   between the following scenarios on Linux:
+   utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS
+   kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW
+   kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec
+   kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT
+   utimensat completely works
+   For each cache variable: 0 = unknown, 1 = yes, -1 = no.  */
+static int utimensat_works_really;
+static int lutimensat_works_really;
+#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
+
 /* 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
+   work around bugs in utimensat).  Return a positive value if the
+   timespec needs further adjustment based on stat results: 1 if any
+   adjustment is needed for utimes, and 2 if any adjustment is needed
+   for Linux utimensat.  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);
+  int utime_omit_count = 0;
   if ((timespec[0].tv_nsec != UTIME_NOW
        && timespec[0].tv_nsec != UTIME_OMIT
-       && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec))
+       && ! (0 <= timespec[0].tv_nsec
+             && timespec[0].tv_nsec < TIMESPEC_RESOLUTION))
       || (timespec[1].tv_nsec != UTIME_NOW
           && timespec[1].tv_nsec != UTIME_OMIT
-          && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec)))
+          && ! (0 <= timespec[1].tv_nsec
+                && timespec[1].tv_nsec < TIMESPEC_RESOLUTION)))
     {
       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.  */
+     EINVAL if tv_sec is not 0 when using the flag values of tv_nsec.
+     Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT
+     fails to bump ctime.  */
   if (timespec[0].tv_nsec == UTIME_NOW
       || timespec[0].tv_nsec == UTIME_OMIT)
     {
       timespec[0].tv_sec = 0;
       result = 1;
+      if (timespec[0].tv_nsec == UTIME_OMIT)
+        utime_omit_count++;
     }
   if (timespec[1].tv_nsec == UTIME_NOW
       || timespec[1].tv_nsec == UTIME_OMIT)
     {
       timespec[1].tv_sec = 0;
       result = 1;
+      if (timespec[1].tv_nsec == UTIME_OMIT)
+        utime_omit_count++;
     }
-  return result;
+  return result + (utime_omit_count == 1);
 }
 
 /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat
@@ -138,22 +157,23 @@ update_timespec (struct stat const *statbuf, struct timespec *ts[2])
   return false;
 }
 
-/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
+/* Set the access and modification timestamps 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.
+   If TIMESPEC is null, set the timestamps to the current time.
    Return 0 on success, -1 (setting errno) on failure.  */
 
 int
-fdutimens (char const *file, int fd, struct timespec const timespec[2])
+fdutimens (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;
+  struct stat st;
 
   if (ts)
     {
@@ -164,24 +184,19 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
   if (adjustment_needed < 0)
     return -1;
 
-  /* Require that at least one of FD or FILE are valid.  Works around
+  /* Require that at least one of FD or FILE are potentially valid, to avoid
      a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
      than failing.  */
-  if (!file)
+  if (fd < 0 && !file)
     {
-      if (fd < 0)
-        {
-          errno = EBADF;
-          return -1;
-        }
-      if (dup2 (fd, fd) != fd)
-        return -1;
+      errno = EBADF;
+      return -1;
     }
 
-  /* Some Linux-based NFS clients are buggy, and mishandle time stamps
+  /* Some Linux-based NFS clients are buggy, and mishandle timestamps
      of files in NFS file systems in some cases.  We have no
      configure-time test for this, but please see
-     <http://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
+     <https://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
      some of the problems with Linux 2.6.16.  If this affects you,
      compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
      help in some cases, albeit at a cost in performance.  But you
@@ -203,14 +218,40 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
 #if HAVE_UTIMENSAT || HAVE_FUTIMENS
   if (0 <= utimensat_works_really)
     {
+      int result;
+# if __linux__ || __sun
+      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
+         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
+         but work if both times are either explicitly specified or
+         UTIME_NOW.  Work around it with a preparatory [f]stat prior
+         to calling futimens/utimensat; fortunately, there is not much
+         timing impact due to the extra syscall even on file systems
+         where UTIME_OMIT would have worked.
+
+         The same bug occurs in Solaris 11.1 (Apr 2013).
+
+         FIXME: Simplify this for Linux in 2016 and for Solaris in
+         2024, when file system bugs are no longer common.  */
+      if (adjustment_needed == 2)
+        {
+          if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
+            return -1;
+          if (ts[0].tv_nsec == UTIME_OMIT)
+            ts[0] = get_stat_atime (&st);
+          else if (ts[1].tv_nsec == UTIME_OMIT)
+            ts[1] = get_stat_mtime (&st);
+          /* Note that st is good, in case utimensat gives ENOSYS.  */
+          adjustment_needed++;
+        }
+# endif
 # if HAVE_UTIMENSAT
       if (fd < 0)
         {
-          int result = utimensat (AT_FDCWD, file, ts, 0);
+          result = utimensat (AT_FDCWD, file, ts, 0);
 #  ifdef __linux__
           /* Work around a kernel bug:
-             http://bugzilla.redhat.com/442352
-             http://bugzilla.redhat.com/449910
+             https://bugzilla.redhat.com/show_bug.cgi?id=442352
+             https://bugzilla.redhat.com/show_bug.cgi?id=449910
              It appears that utimensat can mistakenly return 280 rather
              than -1 upon ENOSYS failure.
              FIXME: remove in 2010 or whenever the offending kernels
@@ -226,32 +267,118 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
         }
 # endif /* HAVE_UTIMENSAT */
 # if HAVE_FUTIMENS
-      {
-        int result = futimens (fd, timespec);
+      if (0 <= fd)
+        {
+          result = futimens (fd, ts);
 #  ifdef __linux__
-        /* Work around the same bug as above.  */
-        if (0 < result)
-          errno = ENOSYS;
+          /* 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;
-          }
-      }
+          if (result == 0 || errno != ENOSYS)
+            {
+              utimensat_works_really = 1;
+              return result;
+            }
+        }
 # endif /* HAVE_FUTIMENS */
     }
   utimensat_works_really = -1;
+  lutimensat_works_really = -1;
 #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
 
+#ifdef USE_SETFILETIME
+  /* On native Windows, use SetFileTime(). See
+     <https://msdn.microsoft.com/en-us/library/ms724933.aspx>
+     <https://msdn.microsoft.com/en-us/library/ms724284.aspx>  */
+  if (0 <= fd)
+    {
+      HANDLE handle;
+      FILETIME current_time;
+      FILETIME last_access_time;
+      FILETIME last_write_time;
+
+      handle = (HANDLE) _get_osfhandle (fd);
+      if (handle == INVALID_HANDLE_VALUE)
+        {
+          errno = EBADF;
+          return -1;
+        }
+
+      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
+        {
+          /* GetSystemTimeAsFileTime
+             <https://msdn.microsoft.com/en-us/library/ms724397.aspx>.
+             It would be overkill to use
+             GetSystemTimePreciseAsFileTime
+             <https://msdn.microsoft.com/en-us/library/hh706895.aspx>.  */
+          GetSystemTimeAsFileTime (&current_time);
+        }
+
+      if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
+        {
+          last_access_time = current_time;
+        }
+      else if (ts[0].tv_nsec == UTIME_OMIT)
+        {
+          last_access_time.dwLowDateTime = 0;
+          last_access_time.dwHighDateTime = 0;
+        }
+      else
+        {
+          ULONGLONG time_since_16010101 =
+            (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
+          last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
+          last_access_time.dwHighDateTime = time_since_16010101 >> 32;
+        }
+
+      if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
+        {
+          last_write_time = current_time;
+        }
+      else if (ts[1].tv_nsec == UTIME_OMIT)
+        {
+          last_write_time.dwLowDateTime = 0;
+          last_write_time.dwHighDateTime = 0;
+        }
+      else
+        {
+          ULONGLONG time_since_16010101 =
+            (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
+          last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
+          last_write_time.dwHighDateTime = time_since_16010101 >> 32;
+        }
+
+      if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
+        return 0;
+      else
+        {
+          DWORD sft_error = GetLastError ();
+          #if 0
+          fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
+          #endif
+          switch (sft_error)
+            {
+            case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
+              errno = EACCES; /* not specified by POSIX */
+              break;
+            default:
+              errno = EINVAL;
+              break;
+            }
+          return -1;
+        }
+    }
+#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 (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
     {
-      struct stat st;
-      if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
+      if (adjustment_needed != 3
+          && (fd < 0 ? stat (file, &st) : fstat (fd, &st)))
         return -1;
       if (ts && update_timespec (&st, &ts))
         return 0;
@@ -260,7 +387,7 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
   {
 #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
     struct timeval timeval[2];
-    struct timeval const *t;
+    struct timeval *t;
     if (ts)
       {
         timeval[0].tv_sec = ts[0].tv_sec;
@@ -290,25 +417,69 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
            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;
-# elif HAVE_FUTIMES
+
+# if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES
+#  if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG
+#   undef futimes
+#   define futimes(fd, t) futimesat (fd, NULL, t)
+#  endif
         if (futimes (fd, t) == 0)
-          return 0;
+          {
+#  if __linux__ && __GLIBC__
+            /* Work around a longstanding glibc bug, still present as
+               of 2010-12-27.  On older Linux kernels that lack both
+               utimensat and utimes, glibc's futimes rounds instead of
+               truncating when falling back on utime.  The same bug
+               occurs in futimesat with a null 2nd arg.  */
+            if (t)
+              {
+                bool abig = 500000 <= t[0].tv_usec;
+                bool mbig = 500000 <= t[1].tv_usec;
+                if ((abig | mbig) && fstat (fd, &st) == 0)
+                  {
+                    /* If these two subtractions overflow, they'll
+                       track the overflows inside the buggy glibc.  */
+                    time_t adiff = st.st_atime - t[0].tv_sec;
+                    time_t mdiff = st.st_mtime - t[1].tv_sec;
+
+                    struct timeval *tt = NULL;
+                    struct timeval truncated_timeval[2];
+                    truncated_timeval[0] = t[0];
+                    truncated_timeval[1] = t[1];
+                    if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0)
+                      {
+                        tt = truncated_timeval;
+                        tt[0].tv_usec = 0;
+                      }
+                    if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0)
+                      {
+                        tt = truncated_timeval;
+                        tt[1].tv_usec = 0;
+                      }
+                    if (tt)
+                      futimes (fd, tt);
+                  }
+              }
+#  endif
+
+            return 0;
+          }
 # endif
       }
 #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
 
     if (!file)
       {
-#if ! (HAVE_FUTIMESAT || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
+#if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG)          \
+        || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
         errno = ENOSYS;
 #endif
         return -1;
       }
 
-#if HAVE_WORKING_UTIMES
+#ifdef USE_SETFILETIME
+    return _gl_utimens_windows (file, ts);
+#elif HAVE_WORKING_UTIMES
     return utimes (file, t);
 #else
     {
@@ -329,31 +500,15 @@ fdutimens (char const *file, int fd, 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
+/* Set the access and modification timestamps of FILE to be
    TIMESPEC[0] and TIMESPEC[1], respectively.  */
 int
 utimens (char const *file, struct timespec const timespec[2])
 {
-  return fdutimens (file, -1, timespec);
+  return fdutimens (-1, file, timespec);
 }
 
-/* Set the access and modification time stamps of FILE to be
+/* Set the access and modification timestamps 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.  */
@@ -376,17 +531,43 @@ lutimens (char const *file, struct timespec const timespec[2])
 
   /* 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
+     fdutimens' worry about buggy NFS clients.  But we do have to
      worry about bogus return values.  */
 
 #if HAVE_UTIMENSAT
-  if (0 <= utimensat_works_really)
+  if (0 <= lutimensat_works_really)
     {
-      int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
+      int result;
+# if __linux__ || __sun
+      /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
+         systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
+         but work if both times are either explicitly specified or
+         UTIME_NOW.  Work around it with a preparatory lstat prior to
+         calling utimensat; fortunately, there is not much timing
+         impact due to the extra syscall even on file systems where
+         UTIME_OMIT would have worked.
+
+         The same bug occurs in Solaris 11.1 (Apr 2013).
+
+         FIXME: Simplify this for Linux in 2016 and for Solaris in
+         2024, when file system bugs are no longer common.  */
+      if (adjustment_needed == 2)
+        {
+          if (lstat (file, &st))
+            return -1;
+          if (ts[0].tv_nsec == UTIME_OMIT)
+            ts[0] = get_stat_atime (&st);
+          else if (ts[1].tv_nsec == UTIME_OMIT)
+            ts[1] = get_stat_mtime (&st);
+          /* Note that st is good, in case utimensat gives ENOSYS.  */
+          adjustment_needed++;
+        }
+# endif
+      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
+         https://bugzilla.redhat.com/show_bug.cgi?id=442352
+         https://bugzilla.redhat.com/show_bug.cgi?id=449910
          It appears that utimensat can mistakenly return 280 rather
          than -1 upon ENOSYS failure.
          FIXME: remove in 2010 or whenever the offending kernels
@@ -397,10 +578,11 @@ lutimens (char const *file, struct timespec const timespec[2])
       if (result == 0 || errno != ENOSYS)
         {
           utimensat_works_really = 1;
+          lutimensat_works_really = 1;
           return result;
         }
     }
-  utimensat_works_really = -1;
+  lutimensat_works_really = -1;
 #endif /* HAVE_UTIMENSAT */
 
   /* The platform lacks an interface to set file timestamps with
@@ -409,16 +591,19 @@ lutimens (char const *file, struct timespec const timespec[2])
 
   if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
     {
-      if (lstat (file, &st))
+      if (adjustment_needed != 3 && lstat (file, &st))
         return -1;
       if (ts && update_timespec (&st, &ts))
         return 0;
     }
 
-#if HAVE_LUTIMES
+  /* On Linux, lutimes is a thin wrapper around utimensat, so there is
+     no point trying lutimes if utimensat failed with ENOSYS.  */
+#if HAVE_LUTIMES && !HAVE_UTIMENSAT
   {
     struct timeval timeval[2];
-    struct timeval const *t;
+    struct timeval *t;
+    int result;
     if (ts)
       {
         timeval[0].tv_sec = ts[0].tv_sec;
@@ -430,15 +615,17 @@ lutimens (char const *file, struct timespec const timespec[2])
     else
       t = NULL;
 
-    return lutimes (file, t);
+    result = lutimes (file, t);
+    if (result == 0 || errno != ENOSYS)
+      return result;
   }
-#endif /* HAVE_LUTIMES */
+#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
 
   /* Out of luck for symlinks, but we still handle regular files.  */
   if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
     return -1;
   if (!S_ISLNK (st.st_mode))
-    return fdutimens (file, -1, ts);
+    return fdutimens (-1, file, ts);
   errno = ENOSYS;
   return -1;
 }