Last week I wrote:
> Since it's an alternative to lstat()/fstatat(), I put the declarations of
> these functions into <sys/stat.h>, rather than creating a new .h file.

Unfortunately, this did not go well. On native Windows, this creates an
include file chain
  wchar.h -> sys/stat.h -> unistd.h -> winsock2.h -> windows.h,
and the latter defines
  - types such as WCHAR (that cause a compilation error in dfa.c),
  - macros such as DOMAIN (that cause a compilation error in GNU gettext).

So, really, this include file chain needs to be cut:
  wchar.h -> sys/stat.h                      should NOT include <windows.h>,
  unistd.h -> winsock2.h -> windows.h        is more or less unavoidable.

This patch does it.


2025-08-18  Bruno Haible  <[email protected]>

        sys_stat: Fix namespace pollution on native Windows.
        * lib/issymlink.h: New file, extracted from lib/sys_stat.in.h.
        * lib/issymlink.c: Include issymlink.h instead of <sys/stat.h>.
        * lib/issymlinkat.c: Likewise.
        * modules/issymlink (Files): Add lib/issymlink.h.
        (Depends-on): Add extern-inline.
        (configure.ac): Use gl_MODULE_INDICATOR.
        (Include): Set to "issymlink.h".
        * modules/issymlinkat (Files): Add lib/issymlink.h.
        (Depends-on): Add extern-inline.
        (configure.ac): Use gl_MODULE_INDICATOR.
        (Include): Set to "issymlink.h".
        * lib/sys_stat.in.h: Don't include <errno.h>, <unistd.h>.
        (_GL_ISSYMLINK_INLINE, _GL_ISSYMLINKAT_INLINE): Remove macros.
        (issymlink, issymlinkat): Remove functions.
        * m4/sys_stat_h.m4 (gl_SYS_STAT_H_REQUIRE_DEFAULTS): Don't initialize
        GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
        * modules/sys_stat-h (Depends-on): Remove extern-inline.
        (Makefile.am): Don't substitute GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
        * lib/chown.c: Include issymlink.h.
        * lib/lchown.c: Likewise.
        * lib/lchmod.c: Likewise.
        * lib/fchmodat.c: Likewise.
        * lib/rename.c: Likewise.
        * lib/renameatu.c: Likewise.
        * lib/unlink.c: Likewise.
        * lib/unlinkat.c: Likewise.
        * lib/utimens.c: Likewise.

diff --git a/lib/chown.c b/lib/chown.c
index 97cacb82da..d2af041e8e 100644
--- a/lib/chown.c
+++ b/lib/chown.c
@@ -28,6 +28,8 @@
 #include <string.h>
 #include <sys/stat.h>
 
+#include "issymlink.h"
+
 #if !HAVE_CHOWN
 
 /* Simple stub that always fails with ENOSYS, for mingw.  */
diff --git a/lib/fchmodat.c b/lib/fchmodat.c
index 57222e58eb..06a20cc345 100644
--- a/lib/fchmodat.c
+++ b/lib/fchmodat.c
@@ -52,6 +52,8 @@ orig_fchmodat (int dir, char const *file, mode_t mode, int 
flags)
 
 #include <intprops.h>
 
+#include "issymlink.h"
+
 /* Invoke chmod or lchmod on FILE, using mode MODE, in the directory
    open on descriptor FD.  If possible, do it without changing the
    working directory.  Otherwise, resort to using save_cwd/fchdir,
diff --git a/lib/issymlink.c b/lib/issymlink.c
index fba06fff64..dbf56c037c 100644
--- a/lib/issymlink.c
+++ b/lib/issymlink.c
@@ -17,4 +17,4 @@
 #include <config.h>
 
 #define _GL_ISSYMLINK_INLINE _GL_EXTERN_INLINE
-#include <sys/stat.h>
+#include "issymlink.h"
diff --git a/lib/issymlink.h b/lib/issymlink.h
new file mode 100644
index 0000000000..af6dc96564
--- /dev/null
+++ b/lib/issymlink.h
@@ -0,0 +1,103 @@
+/* Test whether a file is a symbolic link.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _ISSYMLINK_H
+#define _ISSYMLINK_H
+
+/* This file uses _GL_ARG_NONNULL, _GL_INLINE.  */
+#if !_GL_CONFIG_H_INCLUDED
+ #error "Please include config.h first."
+#endif
+
+#include <errno.h>
+#include <unistd.h> /* for readlink, readlinkat */
+
+
+_GL_INLINE_HEADER_BEGIN
+
+#ifndef _GL_ISSYMLINK_INLINE
+# define _GL_ISSYMLINK_INLINE _GL_INLINE
+#endif
+#ifndef _GL_ISSYMLINKAT_INLINE
+# define _GL_ISSYMLINKAT_INLINE _GL_INLINE
+#endif
+
+#if GNULIB_ISSYMLINK
+/* Tests whether FILENAME represents a symbolic link.
+   This function is more reliable than lstat() / fstatat() followed by S_ISLNK,
+   because it avoids possible EOVERFLOW errors.
+   Returns
+     1                      if FILENAME is a symbolic link,
+     0                      if FILENAME exists and is not a symbolic link,
+    -1 with errno set       if determination failed, in particular
+    -1 with errno = ENOENT or ENOTDIR  if FILENAME does not exist.  */
+# ifdef __cplusplus
+extern "C" {
+# endif
+_GL_ISSYMLINK_INLINE int issymlink (const char *filename)
+     _GL_ARG_NONNULL ((1));
+_GL_ISSYMLINK_INLINE int
+issymlink (const char *filename)
+{
+  char linkbuf[1];
+  if (readlink (filename, linkbuf, sizeof (linkbuf)) >= 0)
+    return 1;
+  if (errno == EINVAL)
+    return 0;
+  else
+    return -1;
+}
+# ifdef __cplusplus
+}
+# endif
+#endif
+
+#if GNULIB_ISSYMLINKAT
+/* Tests whether FILENAME represents a symbolic link.
+   This function is more reliable than lstat() / fstatat() followed by S_ISLNK,
+   because it avoids possible EOVERFLOW errors.
+   If FILENAME is a relative file name, it is interpreted as relative to the
+   directory referred to by FD (where FD = AT_FDCWD denotes the current
+   directory).
+   Returns
+     1                      if FILENAME is a symbolic link,
+     0                      if FILENAME exists and is not a symbolic link,
+    -1 with errno set       if determination failed, in particular
+    -1 with errno = ENOENT or ENOTDIR  if FILENAME does not exist.  */
+# ifdef __cplusplus
+extern "C" {
+# endif
+_GL_ISSYMLINKAT_INLINE int issymlinkat (int fd, const char *filename)
+     _GL_ARG_NONNULL ((2));
+_GL_ISSYMLINKAT_INLINE int
+issymlinkat (int fd, const char *filename)
+{
+  char linkbuf[1];
+  if (readlinkat (fd, filename, linkbuf, sizeof (linkbuf)) >= 0)
+    return 1;
+  if (errno == EINVAL)
+    return 0;
+  else
+    return -1;
+}
+# ifdef __cplusplus
+}
+# endif
+#endif
+
+_GL_INLINE_HEADER_END
+
+#endif /* _ISSYMLINK_H */
diff --git a/lib/issymlinkat.c b/lib/issymlinkat.c
index 924df455da..8286356c8a 100644
--- a/lib/issymlinkat.c
+++ b/lib/issymlinkat.c
@@ -17,4 +17,4 @@
 #include <config.h>
 
 #define _GL_ISSYMLINKAT_INLINE _GL_EXTERN_INLINE
-#include <sys/stat.h>
+#include "issymlink.h"
diff --git a/lib/lchmod.c b/lib/lchmod.c
index 4c44ca8063..deba4c50f5 100644
--- a/lib/lchmod.c
+++ b/lib/lchmod.c
@@ -29,6 +29,7 @@
 #include <unistd.h>
 
 #include <intprops.h>
+#include "issymlink.h"
 
 /* Work like chmod, except when FILE is a symbolic link.
    In that case, on systems where permissions on symbolic links are unsupported
diff --git a/lib/lchown.c b/lib/lchown.c
index e5e277101c..efcb23fe74 100644
--- a/lib/lchown.c
+++ b/lib/lchown.c
@@ -20,12 +20,15 @@
 
 #include <config.h>
 
+/* Specification.  */
 #include <unistd.h>
 
 #include <errno.h>
 #include <string.h>
 #include <sys/stat.h>
 
+#include "issymlink.h"
+
 #if !HAVE_LCHOWN
 
 /* If the system chown does not follow symlinks, we don't want it
diff --git a/lib/rename.c b/lib/rename.c
index 7efd64f992..b365fc9b23 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -19,6 +19,7 @@
 
 #include <config.h>
 
+/* Specification.  */
 #include <stdio.h>
 
 #undef rename
@@ -273,6 +274,7 @@ rpl_rename (char const *src, char const *dst)
 # include <unistd.h>
 
 # include "dirname.h"
+# include "issymlink.h"
 # include "same-inode.h"
 
 /* Rename the file SRC to DST, fixing any trailing slash bugs.  */
diff --git a/lib/renameatu.c b/lib/renameatu.c
index 64a7f5285a..89653cb3e8 100644
--- a/lib/renameatu.c
+++ b/lib/renameatu.c
@@ -18,6 +18,7 @@
 
 #include <config.h>
 
+/* Specification.  */
 #include "renameatu.h"
 
 #include <errno.h>
@@ -29,6 +30,8 @@
 # include <sys/syscall.h>
 #endif
 
+#include "issymlink.h"
+
 static int
 errno_fail (int e)
 {
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index 1ffc255134..c3c38fd653 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -56,7 +56,7 @@
 #define _@GUARD_PREFIX@_SYS_STAT_H
 
 /* This file uses _GL_ATTRIBUTE_NODISCARD, _GL_ATTRIBUTE_NOTHROW,
-   _GL_INLINE, GNULIB_POSIXCHECK, HAVE_RAW_DECL_*.  */
+   GNULIB_POSIXCHECK, HAVE_RAW_DECL_*.  */
 #if !_GL_CONFIG_H_INCLUDED
  #error "Please include config.h first."
 #endif
@@ -91,16 +91,6 @@
 /* The definition of _GL_WARN_ON_USE is copied here.  */
 
 
-_GL_INLINE_HEADER_BEGIN
-
-#ifndef _GL_ISSYMLINK_INLINE
-# define _GL_ISSYMLINK_INLINE _GL_INLINE
-#endif
-#ifndef _GL_ISSYMLINKAT_INLINE
-# define _GL_ISSYMLINKAT_INLINE _GL_INLINE
-#endif
-
-
 /* Before doing "#define mknod rpl_mknod" below, we need to include all
    headers that may declare mknod().  OS/2 kLIBC declares mknod() in
    <unistd.h>, not in <sys/stat.h>.  */
@@ -440,13 +430,6 @@ struct stat
 #endif
 
 
-#if @GNULIB_ISSYMLINK@ || @GNULIB_ISSYMLINKAT@
-/* For the inline definitions of issymlink, issymlinkat below.  */
-# include <errno.h>
-# include <unistd.h> /* for readlink, readlinkat */
-#endif
-
-
 #if @GNULIB_CHMOD@
 # if @REPLACE_CHMOD@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
@@ -643,71 +626,6 @@ _GL_WARN_ON_USE (getumask, "getumask is not portable - "
 #endif
 
 
-#if @GNULIB_ISSYMLINK@
-/* Tests whether FILENAME represents a symbolic link.
-   This function is more reliable than lstat() / fstatat() followed by S_ISLNK,
-   because it avoids possible EOVERFLOW errors.
-   Returns
-     1                      if FILENAME is a symbolic link,
-     0                      if FILENAME exists and is not a symbolic link,
-    -1 with errno set       if determination failed, in particular
-    -1 with errno = ENOENT or ENOTDIR  if FILENAME does not exist.  */
-# ifdef __cplusplus
-extern "C" {
-# endif
-_GL_ISSYMLINK_INLINE int issymlink (const char *filename)
-     _GL_ARG_NONNULL ((1));
-_GL_ISSYMLINK_INLINE int
-issymlink (const char *filename)
-{
-  char linkbuf[1];
-  if (readlink (filename, linkbuf, sizeof (linkbuf)) >= 0)
-    return 1;
-  if (errno == EINVAL)
-    return 0;
-  else
-    return -1;
-}
-# ifdef __cplusplus
-}
-# endif
-#endif
-
-
-#if @GNULIB_ISSYMLINKAT@
-/* Tests whether FILENAME represents a symbolic link.
-   This function is more reliable than lstat() / fstatat() followed by S_ISLNK,
-   because it avoids possible EOVERFLOW errors.
-   If FILENAME is a relative file name, it is interpreted as relative to the
-   directory referred to by FD (where FD = AT_FDCWD denotes the current
-   directory).
-   Returns
-     1                      if FILENAME is a symbolic link,
-     0                      if FILENAME exists and is not a symbolic link,
-    -1 with errno set       if determination failed, in particular
-    -1 with errno = ENOENT or ENOTDIR  if FILENAME does not exist.  */
-# ifdef __cplusplus
-extern "C" {
-# endif
-_GL_ISSYMLINKAT_INLINE int issymlinkat (int fd, const char *filename)
-     _GL_ARG_NONNULL ((2));
-_GL_ISSYMLINKAT_INLINE int
-issymlinkat (int fd, const char *filename)
-{
-  char linkbuf[1];
-  if (readlinkat (fd, filename, linkbuf, sizeof (linkbuf)) >= 0)
-    return 1;
-  if (errno == EINVAL)
-    return 0;
-  else
-    return -1;
-}
-# ifdef __cplusplus
-}
-# endif
-#endif
-
-
 #if @GNULIB_LCHMOD@
 /* Change the mode of FILENAME to MODE, without dereferencing it if FILENAME
    denotes a symbolic link.  */
@@ -1089,8 +1007,6 @@ _GL_WARN_ON_USE (utimensat, "utimensat is not portable - "
 #endif
 
 
-_GL_INLINE_HEADER_END
-
 #endif /* _@GUARD_PREFIX@_SYS_STAT_H */
 #endif /* _@GUARD_PREFIX@_SYS_STAT_H */
 #endif
diff --git a/lib/unlink.c b/lib/unlink.c
index 9963ddf292..ae0c7fe5dc 100644
--- a/lib/unlink.c
+++ b/lib/unlink.c
@@ -17,6 +17,7 @@
 
 #include <config.h>
 
+/* Specification.  */
 #include <unistd.h>
 
 #include <errno.h>
@@ -25,6 +26,7 @@
 #include <sys/stat.h>
 
 #include "filename.h"
+#include "issymlink.h"
 
 #undef unlink
 #if defined _WIN32 && !defined __CYGWIN__
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
index 847a379a91..3cf5fc5ea5 100644
--- a/lib/unlinkat.c
+++ b/lib/unlinkat.c
@@ -19,6 +19,7 @@
 
 #include <config.h>
 
+/* Specification.  */
 #include <unistd.h>
 
 #include <errno.h>
@@ -29,6 +30,7 @@
 #include <stdlib.h>
 
 #include "filename.h"
+#include "issymlink.h"
 #include "openat.h"
 
 #if HAVE_UNLINKAT
diff --git a/lib/utimens.c b/lib/utimens.c
index 442a1800b9..0387e9f10e 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -21,6 +21,7 @@
 
 #include <config.h>
 
+/* Specification.  */
 #define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE
 #include "utimens.h"
 
@@ -32,6 +33,7 @@
 #include <unistd.h>
 #include <utime.h>
 
+#include "issymlink.h"
 #include "stat-time.h"
 #include "timespec.h"
 
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index a2dd603d63..10636923b1 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,5 +1,5 @@
 # sys_stat_h.m4
-# serial 43   -*- Autoconf -*-
+# serial 44   -*- Autoconf -*-
 dnl Copyright (C) 2006-2025 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -80,8 +80,6 @@ AC_DEFUN([gl_SYS_STAT_H_REQUIRE_DEFAULTS]
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_FSTATAT])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_FUTIMENS])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_GETUMASK])
-    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_ISSYMLINK])
-    gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_ISSYMLINKAT])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_LCHMOD])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_LSTAT])
     gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MKDIR])
diff --git a/modules/issymlink b/modules/issymlink
index d7977e8986..fcb98927cd 100644
--- a/modules/issymlink
+++ b/modules/issymlink
@@ -2,21 +2,23 @@ Description:
 Test whether a file is a symbolic link.
 
 Files:
+lib/issymlink.h
 lib/issymlink.c
 
 Depends-on:
+extern-inline
 sys_stat-h
 fcntl-h
 readlink
 
 configure.ac:
-gl_SYS_STAT_MODULE_INDICATOR([issymlink])
+gl_MODULE_INDICATOR([issymlink])
 
 Makefile.am:
 lib_SOURCES += issymlink.c
 
 Include:
-<sys/stat.h>
+"issymlink.h"
 
 License:
 LGPLv2+
diff --git a/modules/issymlinkat b/modules/issymlinkat
index 15740ac1be..5833bafdfb 100644
--- a/modules/issymlinkat
+++ b/modules/issymlinkat
@@ -2,21 +2,23 @@ Description:
 Test whether a file is a symbolic link.
 
 Files:
+lib/issymlink.h
 lib/issymlinkat.c
 
 Depends-on:
+extern-inline
 sys_stat-h
 fcntl-h
 readlinkat
 
 configure.ac:
-gl_SYS_STAT_MODULE_INDICATOR([issymlinkat])
+gl_MODULE_INDICATOR([issymlinkat])
 
 Makefile.am:
 lib_SOURCES += issymlinkat.c
 
 Include:
-<sys/stat.h>
+"issymlink.h"
 
 License:
 GPL
diff --git a/modules/sys_stat-h b/modules/sys_stat-h
index 5257bad181..5552db8e00 100644
--- a/modules/sys_stat-h
+++ b/modules/sys_stat-h
@@ -13,7 +13,6 @@ include_next
 snippet/arg-nonnull
 snippet/c++defs
 snippet/warn-on-use
-extern-inline
 sys_types-h
 time-h
 
@@ -43,8 +42,6 @@ sys/stat.h: sys_stat.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNU
              -e 's/@''GNULIB_FSTATAT''@/$(GNULIB_FSTATAT)/g' \
              -e 's/@''GNULIB_FUTIMENS''@/$(GNULIB_FUTIMENS)/g' \
              -e 's/@''GNULIB_GETUMASK''@/$(GNULIB_GETUMASK)/g' \
-             -e 's/@''GNULIB_ISSYMLINK''@/$(GNULIB_ISSYMLINK)/g' \
-             -e 's/@''GNULIB_ISSYMLINKAT''@/$(GNULIB_ISSYMLINKAT)/g' \
              -e 's/@''GNULIB_LCHMOD''@/$(GNULIB_LCHMOD)/g' \
              -e 's/@''GNULIB_LSTAT''@/$(GNULIB_LSTAT)/g' \
              -e 's/@''GNULIB_MKDIR''@/$(GNULIB_MKDIR)/g' \




Reply via email to