]> git.ipfire.org Git - thirdparty/gnulib.git/commitdiff
fstatat: support NULL if AT_EMPTY_PATH master
authorPaul Eggert <eggert@cs.ucla.edu>
Thu, 18 Jun 2026 20:33:58 +0000 (13:33 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Thu, 18 Jun 2026 20:42:43 +0000 (13:42 -0700)
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.

ChangeLog
doc/glibc-functions/statx.texi
doc/posix-functions/fstatat.texi
lib/fstatat.c
m4/fstatat.m4
tests/test-fstatat.c

index cc4ca635263ddac159020e0d2db44654ce80f3a2..8b4a239eaf7268b2470491cb9df25e02bf75198a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 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.
index d57b488dd283eeecc315ea8a570870d6620543c8..30c2a5cecd958a95faee6fb1a7ac9c1d43e4d91d 100644 (file)
@@ -18,7 +18,7 @@ glibc 2.27, macOS 14, FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.9, Minix 3.1.8, AIX 7
 @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.
index 1f7ab3124b0b22276cd9b53db00122565ab50987..45331beb513e8d70ad53403572dcc265f0f018c6 100644 (file)
@@ -13,9 +13,15 @@ Portability problems fixed by Gnulib:
 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}.
@@ -40,11 +46,7 @@ Portability problems not fixed by Gnulib:
 @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
index 8d26e44501a44b8b12b108262b7a338412be7d90..6b0c564a210b0164546ed152ad7dbb8e4c9e16d4 100644 (file)
@@ -46,6 +46,10 @@ orig_fstatat (int fd, char const *filename, struct stat *buf, int flags)
 #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
@@ -68,14 +72,19 @@ normal_fstatat (int fd, char const *file, struct stat *st, int flag)
    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))
@@ -85,7 +94,7 @@ rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
           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] == '/')
index d53e8d9196626edf4235f4b6a8a9bd76c7a32773..ceeb211d34acb46713556dd25881c19cd05ffbc4 100644 (file)
@@ -1,5 +1,5 @@
 # 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,
@@ -45,6 +45,11 @@ AC_DEFUN([gl_FUNC_FSTATAT],
           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) ;;
@@ -56,12 +61,39 @@ AC_DEFUN([gl_FUNC_FSTATAT],
         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
 ])
index bd25905ca0435b6e4363b6d440b3f6112bd3c53b..bbcbaae31e2ddf516727c8e4abdecc93b5a9bebf 100644 (file)
@@ -101,6 +101,18 @@ main (_GL_UNUSED int argc, _GL_UNUSED char *argv[])
   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.  */