2026-06-18 Paul Eggert <eggert@cs.ucla.edu>
+ fstatat: support NULL if AT_EMPTY_PATH
+ On platforms that define AT_EMPTY_PATH, allow a null pointer to be
+ passed as a file name. This is GNU behavior as of glibc 2.41 +
+ Linux kernel 6.11, and can be supported on older kernels, older
+ glibcs, and on recent FreeBSD and z/OS.
+ * lib/fstatat.c (rpl_fstatat): Support AT_EMPTY_PATH,
+ if it exists, on null pointers.
+ * m4/fstatat.m4 (gl_FUNC_FSTATAT): Replace fstatat if
+ AT_EMPTY_PATH is defined but does not work on null pointers.
+ Also, define HAVE_WORKING_FSTATAT_ZERO_FLAG, if it is discovered,
+ regardless of whether replacing fstatat. This refactoring makes
+ config.h a bit less confusing.
+ * tests/test-fstatat.c (main): Test AT_EMPTY_PATH if defined.
+
fchmodat: fix AT_FDCWD + AT_EMPTY_PATH
* lib/fchmodat.c (fchmodat): When following symlinks and with
AT_FDCWD and an empty filename, don’t use fchmod as it will fail.
@item
When @code{AT_EMPTY_PATH} is used,
this function does not allow the file name to be a null pointer:
-Linux kernel 6.10.
+glibc 2.40, Linux kernel 6.10.
@item
There is an incompatible function of the same name on some platforms:
AIX 5.2 or newer.
This function is missing on some platforms:
glibc 2.3.6, Mac OS X 10.5, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8,
AIX 5.1, HP-UX 11, Cygwin 1.5.x, mingw, MSVC 14.
-But the replacement function is not safe to be used in libraries and is not
+But when the function is missing, its Gnulib substitute
+is not safe to be used in libraries and is not
thread-safe.
@item
+On platforms with @code{AT_EMPTY_PATH},
+this function does not work when @code{AT_EMPTY_PATH}
+is used with a null pointer file name:
+glibc 2.40, Linux kernel 6.10, Cygwin 3.6, FreeBSD 15.1.
+@item
On platforms where @code{off_t} is a 32-bit type, @code{fstatat} may
not correctly report the size of files or block devices larger than 2
GB@. @xref{Large File Support}.
@item
This function does not fail when the second argument is an empty string
on some platforms, even when @code{AT_EMPTY_PATH} is not used:
-glibc 2.7, Linux 2.6.38.
-@item
-When @code{AT_EMPTY_PATH} is used,
-this function does not allow the file name to be a null pointer:
-Linux kernel 6.10, Cygwin 3.6, FreeBSD 15.1.
+glibc 2.7, Linux kernel 2.6.38.
@item
This function sets @code{st_ino} only to the low-order 32 bits of
the inode number of a socket or pipe, which thus can disagree
#include <stdlib.h>
#include <string.h>
+#ifndef AT_EMPTY_PATH
+# define AT_EMPTY_PATH 0
+#endif
+
#if HAVE_FSTATAT && HAVE_WORKING_FSTATAT_ZERO_FLAG
# ifndef LSTAT_FOLLOWS_SLASHED_SYMLINK
Work around this bug if FSTATAT_AT_FDCWD_0_BROKEN is nonzero. */
int
-rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
+rpl_fstatat (int fd, char const *file, struct stat *st, int flags)
{
- int result = normal_fstatat (fd, file, st, flag);
+ /* Implement Linux kernel 6.11+ behavior on platforms that have
+ AT_EMPTY_PATH but do not support it on null pointers. */
+ if (flags & AT_EMPTY_PATH && !file)
+ file = "";
+
+ int result = normal_fstatat (fd, file, st, flags);
if (LSTAT_FOLLOWS_SLASHED_SYMLINK || result != 0)
return result;
size_t len = strlen (file);
- if (flag & AT_SYMLINK_NOFOLLOW)
+ if (flags & AT_SYMLINK_NOFOLLOW)
{
/* Fix lstat behavior. */
if (file[len - 1] != '/' || S_ISDIR (st->st_mode))
errno = ENOTDIR;
return -1;
}
- result = normal_fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
+ result = normal_fstatat (fd, file, st, flags & ~AT_SYMLINK_NOFOLLOW);
}
/* Fix stat behavior. */
if (result == 0 && !S_ISDIR (st->st_mode) && file[len - 1] == '/')
# fstatat.m4
-# serial 5
+# serial 6
dnl Copyright (C) 2004-2026 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
esac
])
])
+ AS_CASE([$gl_cv_func_fstatat_zero_flag],
+ [*yes],
+ [AC_DEFINE([HAVE_WORKING_FSTATAT_ZERO_FLAG], [1],
+ [Define to 1 if fstatat (..., 0) works.
+ For example, it does not work in AIX 7.1.])])
case $gl_cv_func_fstatat_zero_flag+$gl_cv_func_lstat_dereferences_slashed_symlink in
*yes+*yes) ;;
REPLACE_FSTATAT=1 ;;
esac
- case $REPLACE_FSTATAT,$gl_cv_func_fstatat_zero_flag in
- 1,*yes)
- AC_DEFINE([HAVE_WORKING_FSTATAT_ZERO_FLAG], [1],
- [Define to 1 if fstatat (..., 0) works.
- For example, it does not work in AIX 7.1.])
- ;;
- esac
+ dnl Check for the AT_EMPTY_PATH compatibility issue with null pointers
+ dnl only if not already replacing fstatat.
+ dnl There is no need to AC_DEFINE anything here, as the Gnulib
+ dnl replacement works around the compatibility bug even if the bug
+ dnl is not present, and it is not worth the trouble to tune this.
+ AS_CASE([$REPLACE_FSTATAT],
+ [0],
+ [AC_CACHE_CHECK([whether fstatat+AT_EMPTY_PATH allows null file],
+ [gl_cv_func_fstatat_null_file],
+ [gl_saved_CFLAGS=$CFLAGS
+ CFLAGS="$CFLAGS -Wno-nonnull"
+ AC_RUN_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <stddef.h>
+ #include <fcntl.h>
+ #include <sys/stat.h>
+ #ifndef AT_EMPTY_PATH
+ #define AT_EMPTY_PATH 0
+ #endif
+ #if __GLIBC__ && ! (2 < __GLIBC__ + (41 <= __GLIBC_MINOR__))
+ #error "glibc 2.40 and earlier can fail with null file"
+ #endif
+ ]],
+ [[struct stat st;
+ return
+ (AT_EMPTY_PATH
+ && fstatat (AT_FDCWD, NULL, &st, AT_EMPTY_PATH) < 0);
+ ]])],
+ [gl_cv_func_fstatat_null_file=yes],
+ [gl_cv_func_fstatat_null_file=no],
+ [gl_cv_func_fstatat_null_file="guessing no"])])
+ AS_CASE([$gl_cv_func_fstatat_null_file],
+ [*no],
+ [REPLACE_FSTATAT=1])])
fi
])
ASSERT (0 <= dfd);
ASSERT (test_stat_func (do_stat, false) == result);
ASSERT (test_lstat_func (do_lstat, false) == result);
+#ifdef AT_EMPTY_PATH
+ struct stat dotst;
+ ASSERT (stat (".", &dotst) == 0);
+ for (int i = 0; i < 2; i++)
+ for (int j = 0; j < 2; j++)
+ {
+ struct stat st;
+ ASSERT (fstatat (i ? AT_FDCWD : dfd, j ? "" : NULL, &st, AT_EMPTY_PATH)
+ == 0);
+ ASSERT (st.st_dev == dotst.st_dev && st.st_ino == dotst.st_ino);
+ }
+#endif
ASSERT (close (dfd) == 0);
/* FIXME - add additional tests of dfd not at current directory. */