1 /* Set file access and modification times.
2 
3    Copyright (C) 2003-2021 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify it
6    under the terms of the GNU General Public License as published by the
7    Free Software Foundation; either version 3 of the License, or any
8    later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 /* Written by Paul Eggert.  */
19 
20 /* derived from a function in touch.c */
21 
22 #include <config.h>
23 
24 #define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
25 #include "utimens.h"
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stdbool.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <unistd.h>
34 #include <utime.h>
35 
36 #include "stat-time.h"
37 #include "timespec.h"
38 
39 /* On native Windows, use SetFileTime; but avoid this when compiling
40    GNU Emacs, which arranges for this in some other way and which
41    defines WIN32_LEAN_AND_MEAN itself.  */
42 
43 #if defined _WIN32 && ! defined __CYGWIN__ && ! defined EMACS_CONFIGURATION
44 # define USE_SETFILETIME
45 # define WIN32_LEAN_AND_MEAN
46 # include <windows.h>
47 # if GNULIB_MSVC_NOTHROW
48 #  include "msvc-nothrow.h"
49 # else
50 #  include <io.h>
51 # endif
52 #endif
53 
54 /* Avoid recursion with rpl_futimens or rpl_utimensat.  */
55 #undef futimens
56 #if !HAVE_NEARLY_WORKING_UTIMENSAT
57 # undef utimensat
58 #endif
59 
60 /* Solaris 9 mistakenly succeeds when given a non-directory with a
61    trailing slash.  Force the use of rpl_stat for a fix.  */
62 #ifndef REPLACE_FUNC_STAT_FILE
63 # define REPLACE_FUNC_STAT_FILE 0
64 #endif
65 
66 #if HAVE_UTIMENSAT || HAVE_FUTIMENS
67 /* Cache variables for whether the utimensat syscall works; used to
68    avoid calling the syscall if we know it will just fail with ENOSYS,
69    and to avoid unnecessary work in massaging timestamps if the
70    syscall will work.  Multiple variables are needed, to distinguish
71    between the following scenarios on Linux:
72    utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS
73    kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW
74    kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec
75    kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT
76    utimensat completely works
77    For each cache variable: 0 = unknown, 1 = yes, -1 = no.  */
78 static int utimensat_works_really;
79 static int lutimensat_works_really;
80 #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
81 
82 /* Validate the requested timestamps.  Return 0 if the resulting
83    timespec can be used for utimensat (after possibly modifying it to
84    work around bugs in utimensat).  Return a positive value if the
85    timespec needs further adjustment based on stat results: 1 if any
86    adjustment is needed for utimes, and 2 if any adjustment is needed
87    for Linux utimensat.  Return -1, with errno set to EINVAL, if
88    timespec is out of range.  */
89 static int
validate_timespec(struct timespec timespec[2])90 validate_timespec (struct timespec timespec[2])
91 {
92   int result = 0;
93   int utime_omit_count = 0;
94   if ((timespec[0].tv_nsec != UTIME_NOW
95        && timespec[0].tv_nsec != UTIME_OMIT
96        && ! (0 <= timespec[0].tv_nsec
97              && timespec[0].tv_nsec < TIMESPEC_HZ))
98       || (timespec[1].tv_nsec != UTIME_NOW
99           && timespec[1].tv_nsec != UTIME_OMIT
100           && ! (0 <= timespec[1].tv_nsec
101                 && timespec[1].tv_nsec < TIMESPEC_HZ)))
102     {
103       errno = EINVAL;
104       return -1;
105     }
106   /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
107      EINVAL if tv_sec is not 0 when using the flag values of tv_nsec.
108      Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT
109      fails to bump ctime.  */
110   if (timespec[0].tv_nsec == UTIME_NOW
111       || timespec[0].tv_nsec == UTIME_OMIT)
112     {
113       timespec[0].tv_sec = 0;
114       result = 1;
115       if (timespec[0].tv_nsec == UTIME_OMIT)
116         utime_omit_count++;
117     }
118   if (timespec[1].tv_nsec == UTIME_NOW
119       || timespec[1].tv_nsec == UTIME_OMIT)
120     {
121       timespec[1].tv_sec = 0;
122       result = 1;
123       if (timespec[1].tv_nsec == UTIME_OMIT)
124         utime_omit_count++;
125     }
126   return result + (utime_omit_count == 1);
127 }
128 
129 /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat
130    buffer STATBUF to obtain the current timestamps of the file.  If
131    both times are UTIME_NOW, set *TS to NULL (as this can avoid some
132    permissions issues).  If both times are UTIME_OMIT, return true
133    (nothing further beyond the prior collection of STATBUF is
134    necessary); otherwise return false.  */
135 static bool
update_timespec(struct stat const * statbuf,struct timespec * ts[2])136 update_timespec (struct stat const *statbuf, struct timespec *ts[2])
137 {
138   struct timespec *timespec = *ts;
139   if (timespec[0].tv_nsec == UTIME_OMIT
140       && timespec[1].tv_nsec == UTIME_OMIT)
141     return true;
142   if (timespec[0].tv_nsec == UTIME_NOW
143       && timespec[1].tv_nsec == UTIME_NOW)
144     {
145       *ts = NULL;
146       return false;
147     }
148 
149   if (timespec[0].tv_nsec == UTIME_OMIT)
150     timespec[0] = get_stat_atime (statbuf);
151   else if (timespec[0].tv_nsec == UTIME_NOW)
152     gettime (&timespec[0]);
153 
154   if (timespec[1].tv_nsec == UTIME_OMIT)
155     timespec[1] = get_stat_mtime (statbuf);
156   else if (timespec[1].tv_nsec == UTIME_NOW)
157     gettime (&timespec[1]);
158 
159   return false;
160 }
161 
162 /* Set the access and modification timestamps of FD (a.k.a. FILE) to be
163    TIMESPEC[0] and TIMESPEC[1], respectively.
164    FD must be either negative -- in which case it is ignored --
165    or a file descriptor that is open on FILE.
166    If FD is nonnegative, then FILE can be NULL, which means
167    use just futimes (or equivalent) instead of utimes (or equivalent),
168    and fail if on an old system without futimes (or equivalent).
169    If TIMESPEC is null, set the timestamps to the current time.
170    Return 0 on success, -1 (setting errno) on failure.  */
171 
172 int
fdutimens(int fd,char const * file,struct timespec const timespec[2])173 fdutimens (int fd, char const *file, struct timespec const timespec[2])
174 {
175   struct timespec adjusted_timespec[2];
176   struct timespec *ts = timespec ? adjusted_timespec : NULL;
177   int adjustment_needed = 0;
178   struct stat st;
179 
180   if (ts)
181     {
182       adjusted_timespec[0] = timespec[0];
183       adjusted_timespec[1] = timespec[1];
184       adjustment_needed = validate_timespec (ts);
185     }
186   if (adjustment_needed < 0)
187     return -1;
188 
189   /* Require that at least one of FD or FILE are potentially valid, to avoid
190      a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
191      than failing.  */
192   if (fd < 0 && !file)
193     {
194       errno = EBADF;
195       return -1;
196     }
197 
198   /* Some Linux-based NFS clients are buggy, and mishandle timestamps
199      of files in NFS file systems in some cases.  We have no
200      configure-time test for this, but please see
201      <https://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
202      some of the problems with Linux 2.6.16.  If this affects you,
203      compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
204      help in some cases, albeit at a cost in performance.  But you
205      really should upgrade your kernel to a fixed version, since the
206      problem affects many applications.  */
207 
208 #if HAVE_BUGGY_NFS_TIME_STAMPS
209   if (fd < 0)
210     sync ();
211   else
212     fsync (fd);
213 #endif
214 
215   /* POSIX 2008 added two interfaces to set file timestamps with
216      nanosecond resolution; newer Linux implements both functions via
217      a single syscall.  We provide a fallback for ENOSYS (for example,
218      compiling against Linux 2.6.25 kernel headers and glibc 2.7, but
219      running on Linux 2.6.18 kernel).  */
220 #if HAVE_UTIMENSAT || HAVE_FUTIMENS
221   if (0 <= utimensat_works_really)
222     {
223       int result;
224 # if __linux__ || __sun
225       /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
226          systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
227          but work if both times are either explicitly specified or
228          UTIME_NOW.  Work around it with a preparatory [f]stat prior
229          to calling futimens/utimensat; fortunately, there is not much
230          timing impact due to the extra syscall even on file systems
231          where UTIME_OMIT would have worked.
232 
233          The same bug occurs in Solaris 11.1 (Apr 2013).
234 
235          FIXME: Simplify this for Linux in 2016 and for Solaris in
236          2024, when file system bugs are no longer common.  */
237       if (adjustment_needed == 2)
238         {
239           if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
240             return -1;
241           if (ts[0].tv_nsec == UTIME_OMIT)
242             ts[0] = get_stat_atime (&st);
243           else if (ts[1].tv_nsec == UTIME_OMIT)
244             ts[1] = get_stat_mtime (&st);
245           /* Note that st is good, in case utimensat gives ENOSYS.  */
246           adjustment_needed++;
247         }
248 # endif
249 # if HAVE_UTIMENSAT
250       if (fd < 0)
251         {
252 #  if defined __APPLE__ && defined __MACH__
253           size_t len = strlen (file);
254           if (len > 0 && file[len - 1] == '/')
255             {
256               struct stat statbuf;
257               if (stat (file, &statbuf) < 0)
258                 return -1;
259               if (!S_ISDIR (statbuf.st_mode))
260                 {
261                   errno = ENOTDIR;
262                   return -1;
263                 }
264             }
265 #  endif
266           result = utimensat (AT_FDCWD, file, ts, 0);
267 #  ifdef __linux__
268           /* Work around a kernel bug:
269              https://bugzilla.redhat.com/show_bug.cgi?id=442352
270              https://bugzilla.redhat.com/show_bug.cgi?id=449910
271              It appears that utimensat can mistakenly return 280 rather
272              than -1 upon ENOSYS failure.
273              FIXME: remove in 2010 or whenever the offending kernels
274              are no longer in common use.  */
275           if (0 < result)
276             errno = ENOSYS;
277 #  endif /* __linux__ */
278           if (result == 0 || errno != ENOSYS)
279             {
280               utimensat_works_really = 1;
281               return result;
282             }
283         }
284 # endif /* HAVE_UTIMENSAT */
285 # if HAVE_FUTIMENS
286       if (0 <= fd)
287         {
288           result = futimens (fd, ts);
289 #  ifdef __linux__
290           /* Work around the same bug as above.  */
291           if (0 < result)
292             errno = ENOSYS;
293 #  endif /* __linux__ */
294           if (result == 0 || errno != ENOSYS)
295             {
296               utimensat_works_really = 1;
297               return result;
298             }
299         }
300 # endif /* HAVE_FUTIMENS */
301     }
302   utimensat_works_really = -1;
303   lutimensat_works_really = -1;
304 #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
305 
306 #ifdef USE_SETFILETIME
307   /* On native Windows, use SetFileTime(). See
308      <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime>
309      <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime>  */
310   if (0 <= fd)
311     {
312       HANDLE handle;
313       FILETIME current_time;
314       FILETIME last_access_time;
315       FILETIME last_write_time;
316 
317       handle = (HANDLE) _get_osfhandle (fd);
318       if (handle == INVALID_HANDLE_VALUE)
319         {
320           errno = EBADF;
321           return -1;
322         }
323 
324       if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
325         {
326           /* GetSystemTimeAsFileTime
327              <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>.
328              It would be overkill to use
329              GetSystemTimePreciseAsFileTime
330              <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>.  */
331           GetSystemTimeAsFileTime (&current_time);
332         }
333 
334       if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
335         {
336           last_access_time = current_time;
337         }
338       else if (ts[0].tv_nsec == UTIME_OMIT)
339         {
340           last_access_time.dwLowDateTime = 0;
341           last_access_time.dwHighDateTime = 0;
342         }
343       else
344         {
345           ULONGLONG time_since_16010101 =
346             (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
347           last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
348           last_access_time.dwHighDateTime = time_since_16010101 >> 32;
349         }
350 
351       if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
352         {
353           last_write_time = current_time;
354         }
355       else if (ts[1].tv_nsec == UTIME_OMIT)
356         {
357           last_write_time.dwLowDateTime = 0;
358           last_write_time.dwHighDateTime = 0;
359         }
360       else
361         {
362           ULONGLONG time_since_16010101 =
363             (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
364           last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
365           last_write_time.dwHighDateTime = time_since_16010101 >> 32;
366         }
367 
368       if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
369         return 0;
370       else
371         {
372           DWORD sft_error = GetLastError ();
373           #if 0
374           fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
375           #endif
376           switch (sft_error)
377             {
378             case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
379               errno = EACCES; /* not specified by POSIX */
380               break;
381             default:
382               errno = EINVAL;
383               break;
384             }
385           return -1;
386         }
387     }
388 #endif
389 
390   /* The platform lacks an interface to set file timestamps with
391      nanosecond resolution, so do the best we can, discarding any
392      fractional part of the timestamp.  */
393 
394   if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
395     {
396       if (adjustment_needed != 3
397           && (fd < 0 ? stat (file, &st) : fstat (fd, &st)))
398         return -1;
399       if (ts && update_timespec (&st, &ts))
400         return 0;
401     }
402 
403   {
404 #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
405     struct timeval timeval[2];
406     struct timeval *t;
407     if (ts)
408       {
409         timeval[0].tv_sec = ts[0].tv_sec;
410         timeval[0].tv_usec = ts[0].tv_nsec / 1000;
411         timeval[1].tv_sec = ts[1].tv_sec;
412         timeval[1].tv_usec = ts[1].tv_nsec / 1000;
413         t = timeval;
414       }
415     else
416       t = NULL;
417 
418     if (fd < 0)
419       {
420 # if HAVE_FUTIMESAT
421         return futimesat (AT_FDCWD, file, t);
422 # endif
423       }
424     else
425       {
426         /* If futimesat or futimes fails here, don't try to speed things
427            up by returning right away.  glibc can incorrectly fail with
428            errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
429            in high security mode doesn't allow ordinary users to read
430            /proc/self, so glibc incorrectly fails with errno == EACCES.
431            If errno == EIO, EPERM, or EROFS, it's probably safe to fail
432            right away, but these cases are rare enough that they're not
433            worth optimizing, and who knows what other messed-up systems
434            are out there?  So play it safe and fall back on the code
435            below.  */
436 
437 # if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES
438 #  if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG
439 #   undef futimes
440 #   define futimes(fd, t) futimesat (fd, NULL, t)
441 #  endif
442         if (futimes (fd, t) == 0)
443           {
444 #  if __linux__ && __GLIBC__
445             /* Work around a longstanding glibc bug, still present as
446                of 2010-12-27.  On older Linux kernels that lack both
447                utimensat and utimes, glibc's futimes rounds instead of
448                truncating when falling back on utime.  The same bug
449                occurs in futimesat with a null 2nd arg.  */
450             if (t)
451               {
452                 bool abig = 500000 <= t[0].tv_usec;
453                 bool mbig = 500000 <= t[1].tv_usec;
454                 if ((abig | mbig) && fstat (fd, &st) == 0)
455                   {
456                     /* If these two subtractions overflow, they'll
457                        track the overflows inside the buggy glibc.  */
458                     time_t adiff = st.st_atime - t[0].tv_sec;
459                     time_t mdiff = st.st_mtime - t[1].tv_sec;
460 
461                     struct timeval *tt = NULL;
462                     struct timeval truncated_timeval[2];
463                     truncated_timeval[0] = t[0];
464                     truncated_timeval[1] = t[1];
465                     if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0)
466                       {
467                         tt = truncated_timeval;
468                         tt[0].tv_usec = 0;
469                       }
470                     if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0)
471                       {
472                         tt = truncated_timeval;
473                         tt[1].tv_usec = 0;
474                       }
475                     if (tt)
476                       futimes (fd, tt);
477                   }
478               }
479 #  endif
480 
481             return 0;
482           }
483 # endif
484       }
485 #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
486 
487     if (!file)
488       {
489 #if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG)          \
490         || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
491         errno = ENOSYS;
492 #endif
493         return -1;
494       }
495 
496 #ifdef USE_SETFILETIME
497     return _gl_utimens_windows (file, ts);
498 #elif HAVE_WORKING_UTIMES
499     return utimes (file, t);
500 #else
501     {
502       struct utimbuf utimbuf;
503       struct utimbuf *ut;
504       if (ts)
505         {
506           utimbuf.actime = ts[0].tv_sec;
507           utimbuf.modtime = ts[1].tv_sec;
508           ut = &utimbuf;
509         }
510       else
511         ut = NULL;
512 
513       return utime (file, ut);
514     }
515 #endif /* !HAVE_WORKING_UTIMES */
516   }
517 }
518 
519 /* Set the access and modification timestamps of FILE to be
520    TIMESPEC[0] and TIMESPEC[1], respectively.  */
521 int
utimens(char const * file,struct timespec const timespec[2])522 utimens (char const *file, struct timespec const timespec[2])
523 {
524   return fdutimens (-1, file, timespec);
525 }
526 
527 /* Set the access and modification timestamps of FILE to be
528    TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
529    symlinks.  Fail with ENOSYS if the platform does not support
530    changing symlink timestamps, but FILE was a symlink.  */
531 int
lutimens(char const * file,struct timespec const timespec[2])532 lutimens (char const *file, struct timespec const timespec[2])
533 {
534   struct timespec adjusted_timespec[2];
535   struct timespec *ts = timespec ? adjusted_timespec : NULL;
536   int adjustment_needed = 0;
537   struct stat st;
538 
539   if (ts)
540     {
541       adjusted_timespec[0] = timespec[0];
542       adjusted_timespec[1] = timespec[1];
543       adjustment_needed = validate_timespec (ts);
544     }
545   if (adjustment_needed < 0)
546     return -1;
547 
548   /* The Linux kernel did not support symlink timestamps until
549      utimensat, in version 2.6.22, so we don't need to mimic
550      fdutimens' worry about buggy NFS clients.  But we do have to
551      worry about bogus return values.  */
552 
553 #if HAVE_UTIMENSAT
554   if (0 <= lutimensat_works_really)
555     {
556       int result;
557 # if __linux__ || __sun
558       /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
559          systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
560          but work if both times are either explicitly specified or
561          UTIME_NOW.  Work around it with a preparatory lstat prior to
562          calling utimensat; fortunately, there is not much timing
563          impact due to the extra syscall even on file systems where
564          UTIME_OMIT would have worked.
565 
566          The same bug occurs in Solaris 11.1 (Apr 2013).
567 
568          FIXME: Simplify this for Linux in 2016 and for Solaris in
569          2024, when file system bugs are no longer common.  */
570       if (adjustment_needed == 2)
571         {
572           if (lstat (file, &st))
573             return -1;
574           if (ts[0].tv_nsec == UTIME_OMIT)
575             ts[0] = get_stat_atime (&st);
576           else if (ts[1].tv_nsec == UTIME_OMIT)
577             ts[1] = get_stat_mtime (&st);
578           /* Note that st is good, in case utimensat gives ENOSYS.  */
579           adjustment_needed++;
580         }
581 # endif
582       result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
583 # ifdef __linux__
584       /* Work around a kernel bug:
585          https://bugzilla.redhat.com/show_bug.cgi?id=442352
586          https://bugzilla.redhat.com/show_bug.cgi?id=449910
587          It appears that utimensat can mistakenly return 280 rather
588          than -1 upon ENOSYS failure.
589          FIXME: remove in 2010 or whenever the offending kernels
590          are no longer in common use.  */
591       if (0 < result)
592         errno = ENOSYS;
593 # endif
594       if (result == 0 || errno != ENOSYS)
595         {
596           utimensat_works_really = 1;
597           lutimensat_works_really = 1;
598           return result;
599         }
600     }
601   lutimensat_works_really = -1;
602 #endif /* HAVE_UTIMENSAT */
603 
604   /* The platform lacks an interface to set file timestamps with
605      nanosecond resolution, so do the best we can, discarding any
606      fractional part of the timestamp.  */
607 
608   if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
609     {
610       if (adjustment_needed != 3 && lstat (file, &st))
611         return -1;
612       if (ts && update_timespec (&st, &ts))
613         return 0;
614     }
615 
616   /* On Linux, lutimes is a thin wrapper around utimensat, so there is
617      no point trying lutimes if utimensat failed with ENOSYS.  */
618 #if HAVE_LUTIMES && !HAVE_UTIMENSAT
619   {
620     struct timeval timeval[2];
621     struct timeval *t;
622     int result;
623     if (ts)
624       {
625         timeval[0].tv_sec = ts[0].tv_sec;
626         timeval[0].tv_usec = ts[0].tv_nsec / 1000;
627         timeval[1].tv_sec = ts[1].tv_sec;
628         timeval[1].tv_usec = ts[1].tv_nsec / 1000;
629         t = timeval;
630       }
631     else
632       t = NULL;
633 
634     result = lutimes (file, t);
635     if (result == 0 || errno != ENOSYS)
636       return result;
637   }
638 #endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
639 
640   /* Out of luck for symlinks, but we still handle regular files.  */
641   if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
642     return -1;
643   if (!S_ISLNK (st.st_mode))
644     return fdutimens (-1, file, ts);
645   errno = ENOSYS;
646   return -1;
647 }
648