This fixes some unlikely security races, where our “no-op” chmod undid some other process’s chmod. Ironically this bug occurred on OpenBSD, our most paranoid target. This patch also fixes some EOVERFLOW bugs, along with a performance bug and a CHOWN_CHANGE_TIME_BUG with fchownat. * lib/chown.c, lib/fchownat.c, lib/lchown.c: Remove unnecessary inconsistencies. Include stat-time.h. (CHOWN_CHANGE_TIME_BUG, CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE) (CHOWN_MODIFIES_SYMLINK, CHOWN_TRAILING_SLASH_BUG): Default to 0, and prefer ‘if (...)’ to ‘#ifdef ...’. (utimensat) [!HAVE_UTIMENSAT]: Default to a no-op. (rpl_chown, rpl_fchownat, rpl_lchown): Prefer ‘if (...)’ to ‘#ifdef ...’. Statically, call the stat-like and chown-like functions just once. Do not fail if the stat-like function fails with EOVERFLOW, if existence is all we care about. Use utimensat to update ctime, instead of a chmod-like function. * lib/fchownat.c (rpl_fchownat): Defend against OpenBSD’s CHOWN_CHANGE_TIME_BUG. This bug in rpl_fchownat was exposed by yesterday’s fix that caused rpl_fchownat to call fchownat instead of using the tricky old fork/chdir business. * m4/chown.m4 (gl_FUNC_CHOWN): Check for utimensat if the ctime bug is present. * modules/chown, modules/lchown, modules/fchownat: (Depends-on): Add stat-time. --- ChangeLog | 44 ++++++++-- doc/posix-functions/chown.texi | 2 +- doc/posix-functions/fchownat.texi | 4 + doc/posix-functions/lchown.texi | 5 +- lib/chown.c | 137 +++++++++++++++--------------- lib/fchownat.c | 69 +++++++++++++-- lib/lchown.c | 131 +++++++++++++++------------- m4/chown.m4 | 3 +- modules/chown | 3 +- modules/fchownat | 2 + modules/lchown | 5 +- 11 files changed, 256 insertions(+), 149 deletions(-)
diff --git a/ChangeLog b/ChangeLog index 6f0c34b90d..8e2d1a6eb9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +2025-09-21 Paul Eggert <[email protected]> + + fchownat: fix security races and other bugs + This fixes some unlikely security races, + where our “no-op” chmod undid some other process’s chmod. + Ironically this bug occurred on OpenBSD, our most paranoid target. + This patch also fixes some EOVERFLOW bugs, + along with a performance bug and a CHOWN_CHANGE_TIME_BUG with fchownat. + * lib/chown.c, lib/fchownat.c, lib/lchown.c: + Remove unnecessary inconsistencies. + Include stat-time.h. + (CHOWN_CHANGE_TIME_BUG, CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE) + (CHOWN_MODIFIES_SYMLINK, CHOWN_TRAILING_SLASH_BUG): + Default to 0, and prefer ‘if (...)’ to ‘#ifdef ...’. + (utimensat) [!HAVE_UTIMENSAT]: Default to a no-op. + (rpl_chown, rpl_fchownat, rpl_lchown): + Prefer ‘if (...)’ to ‘#ifdef ...’. + Statically, call the stat-like and chown-like functions just once. + Do not fail if the stat-like function fails with EOVERFLOW, + if existence is all we care about. + Use utimensat to update ctime, instead of a chmod-like function. + * lib/fchownat.c (rpl_fchownat): Defend against OpenBSD’s + CHOWN_CHANGE_TIME_BUG. This bug in rpl_fchownat was exposed by + yesterday’s fix that caused rpl_fchownat to call fchownat instead + of using the tricky old fork/chdir business. + * m4/chown.m4 (gl_FUNC_CHOWN): + Check for utimensat if the ctime bug is present. + * modules/chown, modules/lchown, modules/fchownat: + (Depends-on): Add stat-time. + 2025-09-21 Bruno Haible <[email protected]> pthread-once: Fix link error on glibc < 2.34 systems (regr. yesterday). @@ -9990,7 +10020,7 @@ * lib/file-has-acl.c (file_has_aclinfo): On FreeBSD, NetBSD >= 10, if we don’t follow symlinks the first time, also don’t follow them the second time, when it is typically a directory - so it - doesn’t matter whether symlinks are followed - but it might not be. + doesn’t matter whether symlinks are followed - but it might not be. file-has-acl: minor refactor of acl_get_link_np fix * lib/file-has-acl.c (file_has_aclinfo): Redo to avoid ‘else #endif’. @@ -16977,7 +17007,7 @@ tzname: document some limitations Unfortunately tzname is a vestigial interface that doesn't work <https://data.iana.org/time-zones/theory.html#vestigial>. - It's relatively useless in portable code and is planned to be removed + It's relatively useless in portable code and is planned to be removed from POSIX <https://austingroupbugs.net/view.php?id=1816>. Document this better here. @@ -24571,7 +24601,7 @@ striconveha: pacify gcc -Wcast-align * lib/striconveha.c (uniconv_register_autodetect): Rewrite to avoid the need to cast from char * to a pointer to a more strictly - aligned type. Use decls after statements to avoid some repetition. + aligned type. Use decls after statements to avoid some repetition. 2023-11-14 Bruno Haible <[email protected]> @@ -68426,7 +68456,7 @@ 2017-11-28 Benno Schulenberg <[email protected]> stat: fix compilation failure on macOS Sierra - Reported by Marius Schamschula <[email protected]> in: + Reported by Marius Schamschula <[email protected]> in: https://savannah.gnu.org/bugs/?52546 * lib/stat.c: Add missing include of stat-time.h. @@ -69858,7 +69888,7 @@ We take exact tag "v0.2-rc1" for the old format, extract the presumed tag "v0.2" from it, then run "git rev-list v0.2..HEAD" to count - commits since tha tag. Fails, because tag "v0.2" does not exist. + commits since tha tag. Fails, because tag "v0.2" does not exist. * git-version-gen: We could perhaps drop support for versions from more than a decade ago. But tightening the pattern match is easy @@ -76770,7 +76800,7 @@ 2015-12-17 Paul Eggert <[email protected]> intprops: comment fix - * lib/intprops.h: Fix comment. Reported by Pádraig Brady in: + * lib/intprops.h: Fix comment. Reported by Pádraig Brady in: http://lists.gnu.org/r/bug-gnulib/2015-12/msg00013.html intprops-test: work around GCC bug 68971 @@ -98105,7 +98135,7 @@ 2011-07-22 Paul Eggert <[email protected]> largefile: new module, replacing large-inode - Pádraig Brady suggested this in <http://debbugs.gnu.org/9140#20>. + Pádraig Brady suggested this in <http://debbugs.gnu.org/9140#20>. * MODULES.html.sh: Add largefile, remove large-inode. * modules/largefile, m4/largefile.m4: New files. * modules/large-inode, m4/large-inode.m4: Remove. diff --git a/doc/posix-functions/chown.texi b/doc/posix-functions/chown.texi index 0e3fde58c3..fb100efa04 100644 --- a/doc/posix-functions/chown.texi +++ b/doc/posix-functions/chown.texi @@ -16,7 +16,7 @@ macOS 14, FreeBSD 7.2, AIX 7.3.1, Solaris 9. @item Some platforms fail to update the change time when at least one argument was not @minus{}1, but no ownership changes resulted: -OpenBSD 7.2. +OpenBSD 7.7. @item When passed an argument of @minus{}1, some implementations really set the owner user/group id of the file to this value, rather than leaving that id of the diff --git a/doc/posix-functions/fchownat.texi b/doc/posix-functions/fchownat.texi index 4e6416f940..34c1d724f1 100644 --- a/doc/posix-functions/fchownat.texi +++ b/doc/posix-functions/fchownat.texi @@ -26,6 +26,10 @@ Some platforms fail to detect trailing slash on non-directories, as in @code{fchown(dir,"link-to-file/",uid,gid,flag)}: Solaris 9. @item +Some platforms fail to update the change time when at least one +argument was not @minus{}1, but no ownership changes resulted: +OpenBSD 7.7. +@item Some platforms mistakenly dereference symlinks when using @code{AT_SYMLINK_NOFOLLOW}: Linux kernel 2.6.17. diff --git a/doc/posix-functions/lchown.texi b/doc/posix-functions/lchown.texi index efe003a6c6..2ce88491b3 100644 --- a/doc/posix-functions/lchown.texi +++ b/doc/posix-functions/lchown.texi @@ -23,9 +23,8 @@ Some platforms fail to detect trailing slash on non-directories, as in FreeBSD 7.2, Solaris 9. @item Some platforms fail to update the change time when at least one -argument was not -1, but no ownership changes resulted. However, -without @code{lchmod}, the replacement only fixes this for non-symlinks: -OpenBSD 4.0. +argument was not @minus{}1, but no ownership changes resulted: +OpenBSD 7.7. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/chown.c b/lib/chown.c index d2af041e8e..61a0964fe1 100644 --- a/lib/chown.c +++ b/lib/chown.c @@ -1,5 +1,4 @@ -/* provide consistent interface to chown for systems that don't interpret - an ID of -1 as meaning "don't change the corresponding ID". +/* A more POSIX-compliant chown Copyright (C) 1997, 2004-2007, 2009-2025 Free Software Foundation, Inc. @@ -29,13 +28,36 @@ #include <sys/stat.h> #include "issymlink.h" +#include "stat-time.h" + +#ifndef CHOWN_CHANGE_TIME_BUG +# define CHOWN_CHANGE_TIME_BUG 0 +#endif +#ifndef CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE +# define CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE 0 +#endif +#ifndef CHOWN_MODIFIES_SYMLINK +# define CHOWN_MODIFIES_SYMLINK 0 +#endif +#ifndef CHOWN_TRAILING_SLASH_BUG +# define CHOWN_TRAILING_SLASH_BUG 0 +#endif + +/* Gnulib target platforms lacking utimensat do not need it, + because in practice the bug it works around does not occur. */ +#if !HAVE_UTIMENSAT +# undef utimensat +# define utimensat(fd, file, times, flag) \ + ((void) (fd), (void) (file), (void) (times), (void) (flag), \ + 0) +#endif #if !HAVE_CHOWN /* Simple stub that always fails with ENOSYS, for mingw. */ int -chown (_GL_UNUSED const char *file, _GL_UNUSED uid_t uid, - _GL_UNUSED gid_t gid) +chown (_GL_UNUSED const char *file, _GL_UNUSED uid_t owner, + _GL_UNUSED gid_t group) { errno = ENOSYS; return -1; @@ -43,54 +65,20 @@ chown (_GL_UNUSED const char *file, _GL_UNUSED uid_t uid, #else /* HAVE_CHOWN */ -/* Below we refer to the system's chown(). */ +/* Below we refer to the system's function. */ # undef chown -/* Provide a more-closely POSIX-conforming version of chown on - systems with one or both of the following problems: - - chown doesn't treat an ID of -1 as meaning - "don't change the corresponding ID". - - chown doesn't dereference symlinks. */ +/* Provide a more-closely POSIX-conforming version. */ int -rpl_chown (const char *file, uid_t uid, gid_t gid) +rpl_chown (const char *file, uid_t owner, gid_t group) { -# if (CHOWN_CHANGE_TIME_BUG || CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE \ - || CHOWN_TRAILING_SLASH_BUG) - struct stat st; - bool stat_valid = false; -# endif - int result; - -# if CHOWN_CHANGE_TIME_BUG /* OpenBSD 7.2 */ - if (gid != (gid_t) -1 || uid != (uid_t) -1) - { - if (stat (file, &st)) - return -1; - stat_valid = true; - } -# endif - -# if CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE /* some very old platforms */ - if (gid == (gid_t) -1 || uid == (uid_t) -1) - { - /* Stat file to get id(s) that should remain unchanged. */ - if (!stat_valid && stat (file, &st)) - return -1; - stat_valid = true; - if (gid == (gid_t) -1) - gid = st.st_gid; - if (uid == (uid_t) -1) - uid = st.st_uid; - } -# endif - -# if CHOWN_MODIFIES_SYMLINK /* some very old platforms */ - /* The system-supplied chown function does not follow symlinks. + /* In some very old platforms, the system-supplied function + does not follow symlinks. If the file is a symlink, open the file (following symlinks), and fchown the resulting descriptor. Although the open might fail due to lack of permissions, it's the best we can easily do. */ - if (issymlink (file) > 0) + if (CHOWN_MODIFIES_SYMLINK && 0 < issymlink (file)) { int open_flags = O_NONBLOCK | O_NOCTTY | O_CLOEXEC; int fd = open (file, O_RDONLY | open_flags); @@ -103,39 +91,52 @@ rpl_chown (const char *file, uid_t uid, gid_t gid) || ((fd = open (file, O_SEARCH | open_flags)) < 0))) return fd; - int r = fchown (fd, uid, gid); + int r = fchown (fd, owner, group); int err = errno; close (fd); errno = err; return r; } -# endif -# if CHOWN_TRAILING_SLASH_BUG /* macOS 12.5, FreeBSD 7.2, AIX 7.3.1, Solaris 9 */ - if (!stat_valid) + struct stat st; + gid_t no_gid = -1; + uid_t no_uid = -1; + bool gid_noop = group == no_gid; + bool uid_noop = owner == no_uid; + bool change_time_check = CHOWN_CHANGE_TIME_BUG && !(gid_noop & uid_noop); + bool negative_one_check = (CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE + && (gid_noop | uid_noop)); + if (change_time_check | negative_one_check + || (CHOWN_TRAILING_SLASH_BUG + && file[0] && file[strlen (file) - 1] == '/')) { - size_t len = strlen (file); - if (len && file[len - 1] == '/' && stat (file, &st)) - return -1; - } -# endif + int r = stat (file, &st); - result = chown (file, uid, gid); - -# if CHOWN_CHANGE_TIME_BUG /* OpenBSD 7.2 */ - if (result == 0 && stat_valid - && (uid == st.st_uid || uid == (uid_t) -1) - && (gid == st.st_gid || gid == (gid_t) -1)) - { - /* No change in ownership, but at least one argument was not -1, - so we are required to update ctime. Since chown succeeded, - we assume that chmod will do likewise. Fortunately, on all - known systems where a 'no-op' chown skips the ctime update, a - 'no-op' chmod still does the trick. */ - result = chmod (file, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO - | S_ISUID | S_ISGID | S_ISVTX)); + /* EOVERFLOW means the file exists, which is all that the + trailing slash check needs. */ + if (r < 0 && (change_time_check | negative_one_check + || errno != EOVERFLOW)) + return r; } -# endif + + gid_t uid = (CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE && uid_noop + ? st.st_uid : owner); + gid_t gid = (CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE && gid_noop + ? st.st_gid : group); + int result = chown (file, uid, gid); + + /* If no change in ownership, but at least one argument was not -1, + update ctime indirectly via a no-change update to atime and mtime. + Do not use UTIME_NOW or UTIME_OMIT as they might run into bugs + on some platforms. Do not communicate any failure to the caller + as that would be worse than communicating the ownership change. */ + if (result == 0 && change_time_check + && (((uid == st.st_uid) | uid_noop) + & ((gid == st.st_gid) | gid_noop))) + utimensat (AT_FDCWD, file, + ((struct timespec[]) { get_stat_atime (&st), + get_stat_mtime (&st) }), + 0); return result; } diff --git a/lib/fchownat.c b/lib/fchownat.c index 3e15fde1da..cce3a7abd4 100644 --- a/lib/fchownat.c +++ b/lib/fchownat.c @@ -31,8 +31,29 @@ #include <errno.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include "openat.h" +#include "stat-time.h" + +#ifndef CHOWN_CHANGE_TIME_BUG +# define CHOWN_CHANGE_TIME_BUG 0 +#endif +#ifndef CHOWN_TRAILING_SLASH_BUG +# define CHOWN_TRAILING_SLASH_BUG 0 +#endif +#ifndef FCHOWNAT_EMPTY_FILENAME_BUG +# define FCHOWNAT_EMPTY_FILENAME_BUG 0 +#endif + +/* Gnulib target platforms lacking utimensat do not need it, + because in practice the bug it works around does not occur. */ +#if !HAVE_UTIMENSAT +# undef utimensat +# define utimensat(fd, file, times, flag) \ + ((void) (fd), (void) (file), (void) (times), (void) (flag), \ + 0) +#endif #if !HAVE_FCHOWNAT @@ -88,28 +109,58 @@ local_lchownat (int fd, char const *file, uid_t owner, gid_t group); int rpl_fchownat (int fd, char const *file, uid_t owner, gid_t group, int flag) { + /* No need to worry about CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE + or CHOWN_MODIFIES_SYMLINK, as no known fchownat implementations + have these bugs. */ + # if FCHOWNAT_NOFOLLOW_BUG if (flag == AT_SYMLINK_NOFOLLOW) return local_lchownat (fd, file, owner, group); # endif -# if FCHOWNAT_EMPTY_FILENAME_BUG - if (file[0] == '\0') + + if (FCHOWNAT_EMPTY_FILENAME_BUG && file[0] == '\0') { errno = ENOENT; return -1; } -# endif -# if CHOWN_TRAILING_SLASH_BUG - if (file[0] && file[strlen (file) - 1] == '/') + + struct stat st; + gid_t no_gid = -1; + uid_t no_uid = -1; + bool gid_noop = group == no_gid; + bool uid_noop = owner == no_uid; + bool change_time_check = CHOWN_CHANGE_TIME_BUG && !(gid_noop & uid_noop); + + if (change_time_check + || (CHOWN_TRAILING_SLASH_BUG + && file[0] && file[strlen (file) - 1] == '/')) { - struct stat st; int r = fstatat (fd, file, &st, 0); - if (r < 0 && errno != EOVERFLOW) + + /* EOVERFLOW means the file exists, which is all that the + trailing slash check needs. */ + if (r < 0 && (change_time_check || errno != EOVERFLOW)) return r; + flag &= ~AT_SYMLINK_NOFOLLOW; } -# endif - return fchownat (fd, file, owner, group, flag); + + int result = fchownat (fd, file, owner, group, flag); + + /* If no change in ownership, but at least one argument was not -1, + update ctime indirectly via a no-change update to atime and mtime. + Do not use UTIME_NOW or UTIME_OMIT as they might run into bugs + on some platforms. Do not communicate any failure to the caller + as that would be worse than communicating the ownership change. */ + if (result == 0 && change_time_check + && (((owner == st.st_uid) | uid_noop) + & ((group == st.st_gid) | gid_noop))) + utimensat (fd, file, + ((struct timespec[]) { get_stat_atime (&st), + get_stat_mtime (&st) }), + flag); + + return result; } #endif /* HAVE_FCHOWNAT */ diff --git a/lib/lchown.c b/lib/lchown.c index efcb23fe74..ef92c18c8f 100644 --- a/lib/lchown.c +++ b/lib/lchown.c @@ -1,4 +1,4 @@ -/* Provide a stub lchown function for systems that lack it. +/* A more POSIX-compliant lchown Copyright (C) 1998-1999, 2002, 2004, 2006-2007, 2009-2025 Free Software Foundation, Inc. @@ -24,11 +24,31 @@ #include <unistd.h> #include <errno.h> +#include <fcntl.h> #include <string.h> #include <sys/stat.h> #include "issymlink.h" +#ifndef CHOWN_CHANGE_TIME_BUG +# define CHOWN_CHANGE_TIME_BUG 0 +#endif +#ifndef CHOWN_MODIFIES_SYMLINK +# define CHOWN_MODIFIES_SYMLINK 0 +#endif +#ifndef CHOWN_TRAILING_SLASH_BUG +# define CHOWN_TRAILING_SLASH_BUG 0 +#endif + +/* Gnulib target platforms lacking utimensat do not need it, + because in practice the bug it works around does not occur. */ +#if !HAVE_UTIMENSAT +# undef utimensat +# define utimensat(fd, file, times, flag) \ + ((void) (fd), (void) (file), (void) (times), (void) (flag), \ + 0) +#endif + #if !HAVE_LCHOWN /* If the system chown does not follow symlinks, we don't want it @@ -43,18 +63,17 @@ symlinks, then just call chown. */ int -lchown (const char *file, uid_t uid, gid_t gid) +lchown (_GL_UNUSED char const *file, _GL_UNUSED uid_t owner, + _GL_UNUSED gid_t group) { # if HAVE_CHOWN -# if ! CHOWN_MODIFIES_SYMLINK - if (issymlink (file) > 0) + if (!CHOWN_MODIFIES_SYMLINK && 0 < issymlink (file)) { errno = EOPNOTSUPP; return -1; } -# endif - return chown (file, uid, gid); + return chown (file, owner, group); # else /* !HAVE_CHOWN */ errno = ENOSYS; @@ -64,65 +83,63 @@ lchown (const char *file, uid_t uid, gid_t gid) #else /* HAVE_LCHOWN */ +/* Below we refer to the system's function. */ # undef lchown -/* Work around trailing slash bugs in lchown. */ +/* Provide a more-closely POSIX-conforming version. */ + int -rpl_lchown (const char *file, uid_t uid, gid_t gid) +rpl_lchown (const char *file, uid_t owner, gid_t group) { - bool stat_valid = false; - int result; - -# if CHOWN_CHANGE_TIME_BUG struct stat st; - - if (gid != (gid_t) -1 || uid != (uid_t) -1) + gid_t no_gid = -1; + uid_t no_uid = -1; + bool gid_noop = group == no_gid; + bool uid_noop = owner == no_uid; + bool change_time_check = CHOWN_CHANGE_TIME_BUG && !(gid_noop & uid_noop); + + if (change_time_check + || (CHOWN_TRAILING_SLASH_BUG + && file[0] && file[strlen (file) - 1] == '/')) { - /* Prefer readlink to lstat+S_ISLNK, to avoid EOVERFLOW issues - in the common case where FILE is a non-symlink. */ - int ret = issymlink (file); - if (ret < 0) - return -1; - if (ret == 0) - /* FILE is not a symlink. */ - return chown (file, uid, gid); - - /* Later code can use the status, so get it if possible. */ - ret = lstat (file, &st); - if (ret < 0) - return -1; - /* An easy check: did FILE change from a symlink to a non-symlink? */ - if (!S_ISLNK (st.st_mode)) - return chown (file, uid, gid); - - stat_valid = true; + bool file_is_symlink = false; + int r = lstat (file, &st); + if (0 <= r) + file_is_symlink = !!S_ISLNK (st.st_mode); + else if (errno != EOVERFLOW) + return r; + else + { + int s = issymlink (file); + if (s < 0) + return s; + if (0 < s) + { + errno = EOVERFLOW; + return -1; + } + /* FILE exists and is not a symbolic link; ST is unset. + Rely on Gnulib chown to work around platform chown bugs. */ + } + + if (!file_is_symlink) + return chown (file, owner, group); } -# endif -# if CHOWN_TRAILING_SLASH_BUG - if (!stat_valid) - { - size_t len = strlen (file); - if (len && file[len - 1] == '/') - return chown (file, uid, gid); - } -# endif - - result = lchown (file, uid, gid); - -# if CHOWN_CHANGE_TIME_BUG && HAVE_LCHMOD - if (result == 0 && stat_valid - && (uid == st.st_uid || uid == (uid_t) -1) - && (gid == st.st_gid || gid == (gid_t) -1)) - { - /* No change in ownership, but at least one argument was not -1, - so we are required to update ctime. Since lchown succeeded, - we assume that lchmod will do likewise. But if the system - lacks lchmod and lutimes, we are out of luck. Oh well. */ - result = lchmod (file, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO - | S_ISUID | S_ISGID | S_ISVTX)); - } -# endif + int result = lchown (file, owner, group); + + /* If no change in ownership, but at least one argument was not -1, + update ctime indirectly via a no-change update to atime and mtime. + Do not use UTIME_NOW or UTIME_OMIT as they might run into bugs + on some platforms. Do not communicate any failure to the caller + as that would be worse than communicating the ownership change. */ + if (result == 0 && change_time_check + && (((owner == st.st_uid) | uid_noop) + & ((group == st.st_gid) | gid_noop))) + utimensat (AT_FDCWD, file, + ((struct timespec[]) { get_stat_atime (&st), + get_stat_mtime (&st) }), + AT_SYMLINK_NOFOLLOW); return result; } diff --git a/m4/chown.m4 b/m4/chown.m4 index 2c06cfdd94..8105cfd3ae 100644 --- a/m4/chown.m4 +++ b/m4/chown.m4 @@ -1,5 +1,5 @@ # chown.m4 -# serial 36 +# serial 37 dnl Copyright (C) 1997-2001, 2003-2005, 2007, 2009-2025 Free Software dnl Foundation, Inc. dnl This file is free software; the Free Software Foundation @@ -163,6 +163,7 @@ AC_DEFUN_ONCE([gl_FUNC_CHOWN], case "$gl_cv_func_chown_ctime_works" in *yes) ;; *) + gl_CHECK_FUNCS_ANDROID([utimensat], [[#include <sys/stat.h>]]) AC_DEFINE([CHOWN_CHANGE_TIME_BUG], [1], [Define to 1 if chown fails to change ctime when at least one argument was not -1.]) REPLACE_CHOWN=1 diff --git a/modules/chown b/modules/chown index b1aedde85c..37b3b5eaf2 100644 --- a/modules/chown +++ b/modules/chown @@ -8,11 +8,12 @@ m4/chown.m4 Depends-on: unistd-h +bool [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] fstat [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] issymlink [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] open [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] stat [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] -bool [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] +stat-time [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] sys_stat-h [test $HAVE_CHOWN = 0 || test $REPLACE_CHOWN = 1] configure.ac: diff --git a/modules/fchownat b/modules/fchownat index e1ffb97282..31e722c89c 100644 --- a/modules/fchownat +++ b/modules/fchownat @@ -10,6 +10,7 @@ Depends-on: unistd-h extensions at-internal [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] +bool [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] errno-h [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] extern-inline [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] fchdir [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] @@ -19,6 +20,7 @@ lchown [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] openat-die [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] openat-h [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] save-cwd [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] +stat-time [test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1] fstatat [test $REPLACE_FCHOWNAT = 1] configure.ac: diff --git a/modules/lchown b/modules/lchown index 94ba7a9cdd..225e2876d3 100644 --- a/modules/lchown +++ b/modules/lchown @@ -7,10 +7,11 @@ m4/lchown.m4 Depends-on: unistd-h -issymlink [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] +bool [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] chown [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] errno-h [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] -bool [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] +issymlink [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] +stat-time [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] sys_stat-h [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1] lstat [test $REPLACE_LCHOWN = 1] -- 2.48.1
