| /* Set file access and modification times. | |
| Copyright (C) 2003-2026 Free Software Foundation, Inc. | |
| This file is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as | |
| published by the Free Software Foundation, either version 3 of the | |
| License, or (at your option) any later version. | |
| This file 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 Lesser General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| /* Written by Paul Eggert. */ | |
| /* derived from a function in touch.c */ | |
| #define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE | |
| #include <config.h> | |
| /* Specification. */ | |
| #include "utimens.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/time.h> | |
| #include <unistd.h> | |
| #include <utime.h> | |
| #include "issymlink.h" | |
| #include "stat-time.h" | |
| #include "timespec.h" | |
| /* 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 __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 | |
| #if !HAVE_NEARLY_WORKING_UTIMENSAT | |
| # undef utimensat | |
| #endif | |
| /* 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 */ | |
| static bool | |
| is_valid_timespec (struct timespec const *timespec) | |
| { | |
| return (timespec->tv_nsec == UTIME_NOW | |
| || timespec->tv_nsec == UTIME_OMIT | |
| || (0 <= timespec->tv_nsec && timespec->tv_nsec < TIMESPEC_HZ)); | |
| } | |
| static bool | |
| is_valid_timespecs (struct timespec const timespec[2]) | |
| { | |
| return (is_valid_timespec (×pec[0]) | |
| && is_valid_timespec (×pec[1])); | |
| } | |
| /* 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 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]) | |
| { | |
| if (!is_valid_timespecs (timespec)) | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| int result = 0; | |
| int utime_omit_count = 0; | |
| /* 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. | |
| 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 + (utime_omit_count == 1); | |
| } | |
| /* Normalize any UTIME_NOW or UTIME_OMIT values in (*TS)[0] and (*TS)[1], | |
| using STATBUF to obtain the current timestamps of the file. If | |
| both times are UTIME_NOW, set *TS to NULL (as this can avoid some | |
| permissions issues). If both times are UTIME_OMIT, return true | |
| (nothing further beyond the prior collection of STATBUF is | |
| necessary); otherwise return false. */ | |
| static bool | |
| update_timespec (struct stat const *statbuf, struct timespec **ts) | |
| { | |
| struct timespec *timespec = *ts; | |
| if (timespec[0].tv_nsec == UTIME_OMIT | |
| && timespec[1].tv_nsec == UTIME_OMIT) | |
| return true; | |
| if (timespec[0].tv_nsec == UTIME_NOW | |
| && timespec[1].tv_nsec == UTIME_NOW) | |
| { | |
| *ts = NULL; | |
| return false; | |
| } | |
| if (timespec[0].tv_nsec == UTIME_OMIT) | |
| timespec[0] = get_stat_atime (statbuf); | |
| else if (timespec[0].tv_nsec == UTIME_NOW) | |
| gettime (×pec[0]); | |
| if (timespec[1].tv_nsec == UTIME_OMIT) | |
| timespec[1] = get_stat_mtime (statbuf); | |
| else if (timespec[1].tv_nsec == UTIME_NOW) | |
| gettime (×pec[1]); | |
| return false; | |
| } | |
| /* 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 timestamps to the current time. | |
| Return 0 on success, -1 (setting errno) on failure. */ | |
| int | |
| 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; | |
| if (ts) | |
| { | |
| adjusted_timespec[0] = timespec[0]; | |
| adjusted_timespec[1] = timespec[1]; | |
| adjustment_needed = validate_timespec (ts); | |
| } | |
| if (adjustment_needed < 0) | |
| return -1; | |
| /* Require that at least one of FD or FILE are potentially valid, to avoid | |
| a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather | |
| than failing. */ | |
| if (fd < 0 && !file) | |
| { | |
| errno = EBADF; | |
| return -1; | |
| } | |
| /* 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 | |
| <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 | |
| really should upgrade your kernel to a fixed version, since the | |
| problem affects many applications. */ | |
| #if HAVE_BUGGY_NFS_TIME_STAMPS | |
| if (fd < 0) | |
| sync (); | |
| else | |
| fsync (fd); | |
| #endif | |
| struct stat st; | |
| /* POSIX 2008 added two interfaces to set file timestamps with | |
| nanosecond resolution; newer Linux implements both functions via | |
| a single syscall. We provide a fallback for ENOSYS (for example, | |
| compiling against Linux 2.6.25 kernel headers and glibc 2.7, but | |
| running on Linux 2.6.18 kernel). */ | |
| #if HAVE_UTIMENSAT || HAVE_FUTIMENS | |
| if (0 <= utimensat_works_really) | |
| { | |
| int result; | |
| # if defined __linux__ || defined __sun || defined __NetBSD__ | |
| /* 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). | |
| The same bug occurs in NetBSD 10.0 (May 2024). | |
| FIXME: Simplify this in 2024, when these file system bugs are | |
| no longer common on Gnulib target platforms. */ | |
| 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) | |
| { | |
| # if defined __APPLE__ && defined __MACH__ | |
| size_t len = strlen (file); | |
| if (len > 0 && file[len - 1] == '/') | |
| { | |
| struct stat statbuf; | |
| if (stat (file, &statbuf) < 0) | |
| return -1; | |
| if (!S_ISDIR (statbuf.st_mode)) | |
| { | |
| errno = ENOTDIR; | |
| return -1; | |
| } | |
| } | |
| # endif | |
| result = utimensat (AT_FDCWD, file, ts, 0); | |
| # ifdef __linux__ | |
| /* Work around a kernel bug: | |
| 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 | |
| are no longer in common use. */ | |
| if (0 < result) | |
| errno = ENOSYS; | |
| # endif /* __linux__ */ | |
| if (result == 0 || errno != ENOSYS) | |
| { | |
| utimensat_works_really = 1; | |
| return result; | |
| } | |
| } | |
| # endif /* HAVE_UTIMENSAT */ | |
| # if HAVE_FUTIMENS | |
| if (0 <= fd) | |
| { | |
| result = futimens (fd, ts); | |
| # ifdef __linux__ | |
| /* Work around the same bug as above. */ | |
| if (0 < result) | |
| errno = ENOSYS; | |
| # endif /* __linux__ */ | |
| if (result == 0 || errno != ENOSYS) | |
| { | |
| utimensat_works_really = 1; | |
| return result; | |
| } | |
| } | |
| # endif /* HAVE_FUTIMENS */ | |
| } | |
| utimensat_works_really = -1; | |
| lutimensat_works_really = -1; | |
| #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */ | |
| #ifdef USE_SETFILETIME | |
| /* On native Windows, use SetFileTime(). See | |
| <https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfiletime> | |
| <https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime> */ | |
| if (0 <= fd) | |
| { | |
| HANDLE handle = (HANDLE) _get_osfhandle (fd); | |
| if (handle == INVALID_HANDLE_VALUE) | |
| { | |
| errno = EBADF; | |
| return -1; | |
| } | |
| FILETIME current_time; | |
| if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) | |
| { | |
| /* GetSystemTimeAsFileTime | |
| <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime>. | |
| It would be overkill to use | |
| GetSystemTimePreciseAsFileTime | |
| <https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime>. */ | |
| GetSystemTimeAsFileTime (¤t_time); | |
| } | |
| FILETIME last_access_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; | |
| } | |
| FILETIME last_write_time; | |
| 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)) | |
| { | |
| if (adjustment_needed != 3 | |
| && (fd < 0 ? stat (file, &st) : fstat (fd, &st))) | |
| return -1; | |
| if (ts && update_timespec (&st, &ts)) | |
| return 0; | |
| } | |
| { | |
| #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES | |
| struct timeval timeval[2]; | |
| struct timeval *t; | |
| if (ts) | |
| { | |
| timeval[0] = (struct timeval) { .tv_sec = ts[0].tv_sec, | |
| .tv_usec = ts[0].tv_nsec / 1000 }; | |
| timeval[1] = (struct timeval) { .tv_sec = ts[1].tv_sec, | |
| .tv_usec = ts[1].tv_nsec / 1000 }; | |
| t = timeval; | |
| } | |
| else | |
| t = NULL; | |
| if (fd < 0) | |
| { | |
| # if HAVE_FUTIMESAT | |
| return futimesat (AT_FDCWD, file, t); | |
| # endif | |
| } | |
| else | |
| { | |
| /* If futimesat or futimes fails here, don't try to speed things | |
| up by returning right away. glibc can incorrectly fail with | |
| errno == ENOENT if /proc isn't mounted. Also, Mandrake 10.0 | |
| in high security mode doesn't allow ordinary users to read | |
| /proc/self, so glibc incorrectly fails with errno == EACCES. | |
| If errno == EIO, EPERM, or EROFS, it's probably safe to fail | |
| right away, but these cases are rare enough that they're not | |
| 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 && !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) | |
| { | |
| # if defined __linux__ && defined __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 truncated_timeval[2]; | |
| truncated_timeval[0] = t[0]; | |
| truncated_timeval[1] = t[1]; | |
| struct timeval *tt = NULL; | |
| 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 && !FUTIMESAT_NULL_BUG) \ | |
| || (HAVE_WORKING_UTIMES && HAVE_FUTIMES)) | |
| errno = ENOSYS; | |
| #endif | |
| return -1; | |
| } | |
| #ifdef USE_SETFILETIME | |
| return _gl_utimens_windows (file, ts); | |
| #elif HAVE_WORKING_UTIMES | |
| return utimes (file, t); | |
| #else | |
| { | |
| struct utimbuf utimbuf; | |
| struct utimbuf *ut; | |
| if (ts) | |
| { | |
| utimbuf = (struct utimbuf) { .actime = ts[0].tv_sec, | |
| .modtime = ts[1].tv_sec }; | |
| ut = &utimbuf; | |
| } | |
| else | |
| ut = NULL; | |
| return utime (file, ut); | |
| } | |
| #endif /* !HAVE_WORKING_UTIMES */ | |
| } | |
| } | |
| /* 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]) | |
| #undef utimens | |
| { | |
| #if HAVE_UTIMENS | |
| /* NetBSD's native utimens() does not fulfil the Gnulib expectations: | |
| At least in NetBSD 10.0, it does not validate the timespec argument. */ | |
| if (timespec != NULL && !is_valid_timespecs (timespec)) | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| return utimens (file, timespec); | |
| #else | |
| return fdutimens (-1, file, timespec); | |
| #endif | |
| } | |
| /* 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. */ | |
| int | |
| lutimens (char const *file, struct timespec const timespec[2]) | |
| #undef lutimens | |
| { | |
| #if HAVE_LUTIMENS | |
| /* NetBSD's native lutimens() does not fulfil the Gnulib expectations: | |
| At least in NetBSD 10.0, it does not validate the timespec argument. */ | |
| if (timespec != NULL && !is_valid_timespecs (timespec)) | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| return lutimens (file, timespec); | |
| #else | |
| struct timespec adjusted_timespec[2]; | |
| struct timespec *ts = timespec ? adjusted_timespec : NULL; | |
| int adjustment_needed = 0; | |
| if (ts) | |
| { | |
| adjusted_timespec[0] = timespec[0]; | |
| adjusted_timespec[1] = timespec[1]; | |
| adjustment_needed = validate_timespec (ts); | |
| } | |
| if (adjustment_needed < 0) | |
| return -1; | |
| struct stat st; | |
| /* The Linux kernel did not support symlink timestamps until | |
| utimensat, in version 2.6.22, so we don't need to mimic | |
| fdutimens' worry about buggy NFS clients. But we do have to | |
| worry about bogus return values. */ | |
| # if HAVE_UTIMENSAT | |
| if (0 <= lutimensat_works_really) | |
| { | |
| int result; | |
| # if defined __linux__ || defined __sun || defined __NetBSD__ | |
| /* 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). | |
| The same bug occurs in NetBSD 10.0 (May 2024). | |
| 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: | |
| 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 | |
| are no longer in common use. */ | |
| if (0 < result) | |
| errno = ENOSYS; | |
| # endif | |
| if (result == 0 || errno != ENOSYS) | |
| { | |
| utimensat_works_really = 1; | |
| lutimensat_works_really = 1; | |
| return result; | |
| } | |
| } | |
| lutimensat_works_really = -1; | |
| # 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 (adjustment_needed || REPLACE_FUNC_STAT_FILE) | |
| { | |
| if (adjustment_needed != 3 && lstat (file, &st)) | |
| return -1; | |
| if (ts && update_timespec (&st, &ts)) | |
| return 0; | |
| } | |
| /* 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 *t; | |
| if (ts) | |
| { | |
| timeval[0] = (struct timeval) { .tv_sec = ts[0].tv_sec, | |
| .tv_usec = ts[0].tv_nsec / 1000 }; | |
| timeval[1] = (struct timeval) { .tv_sec = ts[1].tv_sec, | |
| .tv_usec = ts[1].tv_nsec / 1000 }; | |
| t = timeval; | |
| } | |
| else | |
| t = NULL; | |
| int result = lutimes (file, t); | |
| if (result == 0 || errno != ENOSYS) | |
| return result; | |
| } | |
| # endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */ | |
| /* Out of luck for symlinks, but we still handle regular files. */ | |
| bool not_symlink; | |
| if (adjustment_needed || REPLACE_FUNC_STAT_FILE) | |
| not_symlink = !S_ISLNK (st.st_mode); | |
| else | |
| { | |
| int ret = issymlink (file); | |
| if (ret < 0) | |
| return -1; | |
| not_symlink = !ret; | |
| } | |
| if (not_symlink) | |
| return fdutimens (-1, file, ts); | |
| errno = ENOSYS; | |
| return -1; | |
| #endif | |
| } |