| /* Set the access and modification time of a file relative to directory fd. | |
| Copyright (C) 2009-2026 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 <https://www.gnu.org/licenses/>. */ | |
| /* written by Eric Blake */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include <sys/stat.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include "stat-time.h" | |
| #include "timespec.h" | |
| #include "utimens.h" | |
| #if HAVE_NEARLY_WORKING_UTIMENSAT | |
| /* Use the original utimensat(), but correct the trailing slash handling. */ | |
| int | |
| rpl_utimensat (int fd, char const *file, struct timespec const times[2], | |
| int flag) | |
| # undef utimensat | |
| { | |
| size_t len = strlen (file); | |
| if (len && file[len - 1] == '/') | |
| { | |
| struct stat st; | |
| if (fstatat (fd, file, &st, flag & AT_SYMLINK_NOFOLLOW) < 0) | |
| return -1; | |
| if (!S_ISDIR (st.st_mode)) | |
| { | |
| errno = ENOTDIR; | |
| return -1; | |
| } | |
| } | |
| return utimensat (fd, file, times, flag); | |
| } | |
| #else | |
| # if HAVE_UTIMENSAT | |
| /* If we have a native utimensat, but are compiling this file, then | |
| utimensat was defined to rpl_utimensat by our replacement | |
| sys/stat.h. We assume the native version might fail with ENOSYS, | |
| or succeed without properly affecting ctime (as is the case when | |
| using newer glibc but older Linux kernel). In this scenario, | |
| rpl_utimensat checks whether the native version is usable, and | |
| local_utimensat provides the fallback manipulation. */ | |
| static int local_utimensat (int, char const *, struct timespec const[2], int); | |
| # define AT_FUNC_NAME local_utimensat | |
| /* Like utimensat, but work around native bugs. */ | |
| int | |
| rpl_utimensat (int fd, char const *file, struct timespec const times[2], | |
| int flag) | |
| # undef utimensat | |
| { | |
| # if defined __linux__ || defined __sun || defined __NetBSD__ | |
| struct timespec ts[2]; | |
| # endif | |
| /* See comments in utimens.c for details. */ | |
| static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */ | |
| if (0 <= utimensat_works_really) | |
| { | |
| # if defined __linux__ || defined __sun || defined __NetBSD__ | |
| struct stat st; | |
| /* 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 [l]stat 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 in 2024, when these file system bugs are | |
| no longer common on Gnulib target platforms. */ | |
| if (times && (times[0].tv_nsec == UTIME_OMIT | |
| || times[1].tv_nsec == UTIME_OMIT)) | |
| { | |
| if (fstatat (fd, file, &st, flag)) | |
| return -1; | |
| if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT) | |
| return 0; | |
| if (times[0].tv_nsec == UTIME_OMIT) | |
| ts[0] = get_stat_atime (&st); | |
| else | |
| ts[0] = times[0]; | |
| if (times[1].tv_nsec == UTIME_OMIT) | |
| ts[1] = get_stat_mtime (&st); | |
| else | |
| ts[1] = times[1]; | |
| times = ts; | |
| } | |
| # if defined __hppa || defined __NetBSD__ | |
| /* Linux kernel 2.6.22.19 on hppa does not reject invalid tv_nsec | |
| values. | |
| The same bug occurs in NetBSD 10.0 (May 2024). */ | |
| else if (times | |
| && ((times[0].tv_nsec != UTIME_NOW | |
| && ! (0 <= times[0].tv_nsec | |
| && times[0].tv_nsec < TIMESPEC_HZ)) | |
| || (times[1].tv_nsec != UTIME_NOW | |
| && ! (0 <= times[1].tv_nsec | |
| && times[1].tv_nsec < TIMESPEC_HZ)))) | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| # endif | |
| # endif | |
| # if (defined __APPLE__ && defined __MACH__) || defined __gnu_hurd__ | |
| /* macOS 10.13 and GNU Hurd do not reject invalid tv_nsec values | |
| either. */ | |
| if (times | |
| && ((times[0].tv_nsec != UTIME_OMIT | |
| && times[0].tv_nsec != UTIME_NOW | |
| && ! (0 <= times[0].tv_nsec | |
| && times[0].tv_nsec < TIMESPEC_HZ)) | |
| || (times[1].tv_nsec != UTIME_OMIT | |
| && times[1].tv_nsec != UTIME_NOW | |
| && ! (0 <= times[1].tv_nsec | |
| && times[1].tv_nsec < TIMESPEC_HZ)))) | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| # if defined __APPLE__ && defined __MACH__ | |
| size_t len = strlen (file); | |
| if (len > 0 && file[len - 1] == '/') | |
| { | |
| struct stat statbuf; | |
| if (fstatat (fd, file, &statbuf, 0) < 0) | |
| return -1; | |
| if (!S_ISDIR (statbuf.st_mode)) | |
| { | |
| errno = ENOTDIR; | |
| return -1; | |
| } | |
| } | |
| # endif | |
| # endif | |
| int result = utimensat (fd, file, times, flag); | |
| /* Linux kernel 2.6.25 has a bug where it returns EINVAL for | |
| UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which | |
| local_utimensat works around. Meanwhile, EINVAL for a bad | |
| flag is indeterminate whether the native utimensat works, but | |
| local_utimensat will also reject it. */ | |
| if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW)) | |
| return result; | |
| if (result == 0 || (errno != ENOSYS && errno != EINVAL)) | |
| { | |
| utimensat_works_really = 1; | |
| return result; | |
| } | |
| } | |
| /* No point in trying openat/futimens, since on Linux, futimens is | |
| implemented with the same syscall as utimensat. Only avoid the | |
| native utimensat due to an ENOSYS failure; an EINVAL error was | |
| data-dependent, and the next caller may pass valid data. */ | |
| if (0 <= utimensat_works_really && errno == ENOSYS) | |
| utimensat_works_really = -1; | |
| return local_utimensat (fd, file, times, flag); | |
| } | |
| # else /* !HAVE_UTIMENSAT */ | |
| # define AT_FUNC_NAME utimensat | |
| # endif /* !HAVE_UTIMENSAT */ | |
| /* Set the access and modification timestamps of FILE to be | |
| TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory | |
| FD. If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink, | |
| or fail with ENOSYS if not possible. If TIMESPEC is null, set the | |
| timestamps to the current time. If possible, do it without | |
| changing the working directory. Otherwise, resort to using | |
| save_cwd/fchdir, then utimens/restore_cwd. If either the save_cwd | |
| or the restore_cwd fails, then give a diagnostic and exit nonzero. | |
| Return 0 on success, -1 (setting errno) on failure. */ | |
| /* AT_FUNC_NAME is now utimensat or local_utimensat. */ | |
| # define AT_FUNC_F1 lutimens | |
| # define AT_FUNC_F2 utimens | |
| # define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW | |
| # define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag | |
| # define AT_FUNC_POST_FILE_ARGS , ts | |
| # include "at-func.c" | |
| # undef AT_FUNC_NAME | |
| # undef AT_FUNC_F1 | |
| # undef AT_FUNC_F2 | |
| # undef AT_FUNC_USE_F1_COND | |
| # undef AT_FUNC_POST_FILE_PARAM_DECLS | |
| # undef AT_FUNC_POST_FILE_ARGS | |
| #endif /* !HAVE_NEARLY_WORKING_UTIMENSAT */ |