+2025-09-21 Paul Eggert <eggert@cs.ucla.edu>
+
+ 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 <bruno@clisp.org>
pthread-once: Fix link error on glibc < 2.34 systems (regr. yesterday).
* 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’.
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.
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 <bruno@clisp.org>
2017-11-28 Benno Schulenberg <bensberg@telfort.nl>
stat: fix compilation failure on macOS Sierra
- Reported by Marius Schamschula <mschamschula@gmail.com> in:
+ Reported by Marius Schamschula <mschamschula@gmail.com> in:
https://savannah.gnu.org/bugs/?52546
* lib/stat.c: Add missing include of stat-time.h.
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
2015-12-17 Paul Eggert <eggert@cs.ucla.edu>
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
2011-07-22 Paul Eggert <eggert@cs.ucla.edu>
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.
@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
@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.
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:
-/* 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.
#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;
#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);
|| ((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;
}
#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
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 */
-/* 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.
#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
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;
#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;
}
# 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
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
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:
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]
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:
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]