Here's the implementation of the issymlinkat() function.

Due to the large set of dependencies of 'readlinkat', I chose to implement
an issymlink() function as well, with much less dependencies.

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.


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

        utimens: Use issymlink.
        * lib/utimens.c (lutimens): Use issymlink instead of readlink.
        * modules/utimens (Depends-on): Remove readlink. Add issymlink.

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

        unlinkat: Use issymlinkat.
        * lib/unlinkat.c (rpl_unlinkat): Use issymlinkat instead of readlinkat.
        * modules/unlinkat (Depends-on): Remove readlinkat. Add issymlinkat.

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

        unlink: Use issymlink.
        * lib/unlink.c (rpl_unlink): Use issymlink instead of readlink.
        * modules/unlink (Depends-on): Remove readlink. Add issymlink.

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

        renameatu: Use issymlinkat.
        * lib/renameatu.c (renameatu): Use issymlinkat instead of readlinkat.
        * modules/renameatu (Depends-on): Remove readlinkat. Add issymlinkat.

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

        rename: Use issymlink.
        * lib/rename.c (rpl_rename): Use issymlink instead of readlink.
        * modules/rename (Depends-on): Remove readlink. Add issymlink.

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

        fchmodat: Use issymlinkat.
        * lib/fchmodat.c (fchmodat): Use issymlinkat instead of readlinkat.
        * modules/fchmodat (Depends-on): Add issymlinkat, openat.

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

        lchmod: Use issymlink, issymlinkat.
        * lib/lchmod.c (lchmod): Use issymlink instead of readlink and
        issymlinkat instead of readlinkat.
        * modules/lchmod (Depends-on): Remove readlink. Add issymlink,
        issymlinkat.

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

        chown: Avoid a redundant stat() call.
        * lib/chown.c (rpl_chown): Set stat_valid after stat() succeeded.

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

        lchown: Use issymlink.
        * lib/lchown.c (lchown): Use issymlink instead of readlink.
        * modules/lchown (Depends-on): Remove readlink. Add issymlink.

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

        chown: Use issymlink.
        * lib/chown.c (rpl_chown): Use issymlink instead of readlink.
        * modules/chown (Depends-on): Add issymlink.

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

        issymlink, issymlinkat: New modules.
        * lib/sys_stat.in.h: Invoke _GL_INLINE_HEADER_BEGIN,
        _GL_INLINE_HEADER_END. Include <errno.h>, <unistd.h>.
        (_GL_ISSYMLINK_INLINE, _GL_ISSYMLINKAT_INLINE): New macros.
        (issymlink, issymlinkat): New declarations.
        * lib/unistd.in.h: Do the #include <fcntl.h>, when needed for O_CLOEXEC,
        at the end of the file. So that when <fcntl.h> includes <sys/stat.h>,
        the declarations of readlink() and readlinkat() on native Windows are
        already present.
        * lib/issymlink.c: New file.
        * lib/issymlinkat.c: New file.
        * m4/sys_stat_h.m4 (gl_SYS_STAT_H_REQUIRE_DEFAULTS): Initialize
        GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
        * modules/sys_stat-h (Depends-on): Add extern-inline.
        (Makefile.am): Substitute GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
        * modules/issymlink: New file.
        * modules/issymlinkat: New file.

>From 16db5fb3f0c91305e901c39f92fac6798e1fdafc Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 18:36:00 +0200
Subject: [PATCH 01/11] issymlink, issymlinkat: New modules.

* lib/sys_stat.in.h: Invoke _GL_INLINE_HEADER_BEGIN,
_GL_INLINE_HEADER_END. Include <errno.h>, <unistd.h>.
(_GL_ISSYMLINK_INLINE, _GL_ISSYMLINKAT_INLINE): New macros.
(issymlink, issymlinkat): New declarations.
* lib/unistd.in.h: Do the #include <fcntl.h>, when needed for O_CLOEXEC,
at the end of the file. So that when <fcntl.h> includes <sys/stat.h>,
the declarations of readlink() and readlinkat() on native Windows are
already present.
* lib/issymlink.c: New file.
* lib/issymlinkat.c: New file.
* m4/sys_stat_h.m4 (gl_SYS_STAT_H_REQUIRE_DEFAULTS): Initialize
GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
* modules/sys_stat-h (Depends-on): Add extern-inline.
(Makefile.am): Substitute GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
* modules/issymlink: New file.
* modules/issymlinkat: New file.
---
 ChangeLog           | 20 +++++++++++
 lib/issymlink.c     | 20 +++++++++++
 lib/issymlinkat.c   | 20 +++++++++++
 lib/sys_stat.in.h   | 86 ++++++++++++++++++++++++++++++++++++++++++++-
 lib/unistd.in.h     | 15 ++++----
 m4/sys_stat_h.m4    |  4 ++-
 modules/issymlink   | 25 +++++++++++++
 modules/issymlinkat | 25 +++++++++++++
 modules/sys_stat-h  |  3 ++
 9 files changed, 210 insertions(+), 8 deletions(-)
 create mode 100644 lib/issymlink.c
 create mode 100644 lib/issymlinkat.c
 create mode 100644 modules/issymlink
 create mode 100644 modules/issymlinkat

diff --git a/ChangeLog b/ChangeLog
index 6084329d2e..09afcc04c6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	issymlink, issymlinkat: New modules.
+	* lib/sys_stat.in.h: Invoke _GL_INLINE_HEADER_BEGIN,
+	_GL_INLINE_HEADER_END. Include <errno.h>, <unistd.h>.
+	(_GL_ISSYMLINK_INLINE, _GL_ISSYMLINKAT_INLINE): New macros.
+	(issymlink, issymlinkat): New declarations.
+	* lib/unistd.in.h: Do the #include <fcntl.h>, when needed for O_CLOEXEC,
+	at the end of the file. So that when <fcntl.h> includes <sys/stat.h>,
+	the declarations of readlink() and readlinkat() on native Windows are
+	already present.
+	* lib/issymlink.c: New file.
+	* lib/issymlinkat.c: New file.
+	* m4/sys_stat_h.m4 (gl_SYS_STAT_H_REQUIRE_DEFAULTS): Initialize
+	GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
+	* modules/sys_stat-h (Depends-on): Add extern-inline.
+	(Makefile.am): Substitute GNULIB_ISSYMLINK, GNULIB_ISSYMLINKAT.
+	* modules/issymlink: New file.
+	* modules/issymlinkat: New file.
+
 2025-08-11  Paul Eggert  <[email protected]>
 
 	manywarnings: update C warnings for GCC 15.2
diff --git a/lib/issymlink.c b/lib/issymlink.c
new file mode 100644
index 0000000000..fba06fff64
--- /dev/null
+++ b/lib/issymlink.c
@@ -0,0 +1,20 @@
+/* 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/>.  */
+
+#include <config.h>
+
+#define _GL_ISSYMLINK_INLINE _GL_EXTERN_INLINE
+#include <sys/stat.h>
diff --git a/lib/issymlinkat.c b/lib/issymlinkat.c
new file mode 100644
index 0000000000..924df455da
--- /dev/null
+++ b/lib/issymlinkat.c
@@ -0,0 +1,20 @@
+/* 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 General Public License as published
+   by the Free Software Foundation, either version 3 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#define _GL_ISSYMLINKAT_INLINE _GL_EXTERN_INLINE
+#include <sys/stat.h>
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index c3c38fd653..1ffc255134 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,
-   GNULIB_POSIXCHECK, HAVE_RAW_DECL_*.  */
+   _GL_INLINE, GNULIB_POSIXCHECK, HAVE_RAW_DECL_*.  */
 #if !_GL_CONFIG_H_INCLUDED
  #error "Please include config.h first."
 #endif
@@ -91,6 +91,16 @@
 /* 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>.  */
@@ -430,6 +440,13 @@ 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)
@@ -626,6 +643,71 @@ _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.  */
@@ -1007,6 +1089,8 @@ _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/unistd.in.h b/lib/unistd.in.h
index 9f057d30cd..0cf2728020 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -104,15 +104,12 @@
 # include <direct.h>
 #endif
 
-/* FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, Solaris 11.4, and glibc 2.41
-   do not define O_CLOEXEC in <unistd.h>.  */
 /* Cygwin 1.7.1 and Android 4.3 declare unlinkat in <fcntl.h>, not in
    <unistd.h>.  */
 /* But avoid namespace pollution on glibc systems.  */
-#if ! defined O_CLOEXEC \
-    || ((@GNULIB_UNLINKAT@ || defined GNULIB_POSIXCHECK) \
-         && (defined __CYGWIN__ || defined __ANDROID__) \
-         && ! defined __GLIBC__)
+#if ((@GNULIB_UNLINKAT@ || defined GNULIB_POSIXCHECK) \
+      && (defined __CYGWIN__ || defined __ANDROID__) \
+      && ! defined __GLIBC__)
 # include <fcntl.h>
 #endif
 
@@ -2493,6 +2490,12 @@ _GL_CXXALIASWARN (write);
 
 _GL_INLINE_HEADER_END
 
+/* FreeBSD 14.0, NetBSD 10.0, OpenBSD 7.5, Solaris 11.4, and glibc 2.41
+   do not define O_CLOEXEC in <unistd.h>.  */
+#if ! defined O_CLOEXEC
+# include <fcntl.h>
+#endif
+
 #endif /* _@GUARD_PREFIX@_UNISTD_H */
 #endif /* _GL_INCLUDING_UNISTD_H */
 #endif /* _@GUARD_PREFIX@_UNISTD_H */
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index fdcc89545b..a2dd603d63 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,5 +1,5 @@
 # sys_stat_h.m4
-# serial 42   -*- Autoconf -*-
+# serial 43   -*- 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,6 +80,8 @@ 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
new file mode 100644
index 0000000000..d7977e8986
--- /dev/null
+++ b/modules/issymlink
@@ -0,0 +1,25 @@
+Description:
+Test whether a file is a symbolic link.
+
+Files:
+lib/issymlink.c
+
+Depends-on:
+sys_stat-h
+fcntl-h
+readlink
+
+configure.ac:
+gl_SYS_STAT_MODULE_INDICATOR([issymlink])
+
+Makefile.am:
+lib_SOURCES += issymlink.c
+
+Include:
+<sys/stat.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/issymlinkat b/modules/issymlinkat
new file mode 100644
index 0000000000..15740ac1be
--- /dev/null
+++ b/modules/issymlinkat
@@ -0,0 +1,25 @@
+Description:
+Test whether a file is a symbolic link.
+
+Files:
+lib/issymlinkat.c
+
+Depends-on:
+sys_stat-h
+fcntl-h
+readlinkat
+
+configure.ac:
+gl_SYS_STAT_MODULE_INDICATOR([issymlinkat])
+
+Makefile.am:
+lib_SOURCES += issymlinkat.c
+
+Include:
+<sys/stat.h>
+
+License:
+GPL
+
+Maintainer:
+all
diff --git a/modules/sys_stat-h b/modules/sys_stat-h
index 5552db8e00..5257bad181 100644
--- a/modules/sys_stat-h
+++ b/modules/sys_stat-h
@@ -13,6 +13,7 @@ include_next
 snippet/arg-nonnull
 snippet/c++defs
 snippet/warn-on-use
+extern-inline
 sys_types-h
 time-h
 
@@ -42,6 +43,8 @@ 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' \
-- 
2.50.1

>From 3cbc197fcf04a6c0982aceb3cdc3edc80e34727a Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 21:54:44 +0200
Subject: [PATCH 02/11] chown: Use issymlink.

* lib/chown.c (rpl_chown): Use issymlink instead of readlink.
* modules/chown (Depends-on): Add issymlink.
---
 ChangeLog     | 6 ++++++
 lib/chown.c   | 3 +--
 modules/chown | 1 +
 3 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 09afcc04c6..4e387893cb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	chown: Use issymlink.
+	* lib/chown.c (rpl_chown): Use issymlink instead of readlink.
+	* modules/chown (Depends-on): Add issymlink.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	issymlink, issymlinkat: New modules.
diff --git a/lib/chown.c b/lib/chown.c
index 6cfc7e52b8..2ad9972100 100644
--- a/lib/chown.c
+++ b/lib/chown.c
@@ -87,8 +87,7 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
      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.  */
-  char linkbuf[1];
-  if (0 <= readlink (file, linkbuf, sizeof linkbuf))
+  if (issymlink (file) > 0)
     {
       int open_flags = O_NONBLOCK | O_NOCTTY | O_CLOEXEC;
       int fd = open (file, O_RDONLY | open_flags);
diff --git a/modules/chown b/modules/chown
index 7fe9ebfecf..b1aedde85c 100644
--- a/modules/chown
+++ b/modules/chown
@@ -9,6 +9,7 @@ m4/chown.m4
 Depends-on:
 unistd-h
 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]
-- 
2.50.1

>From f478e006d3a4d43dbed9651a0eba41d5f5732cfa Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 21:59:20 +0200
Subject: [PATCH 03/11] lchown: Use issymlink.

* lib/lchown.c (lchown): Use issymlink instead of readlink.
* modules/lchown (Depends-on): Remove readlink. Add issymlink.
---
 ChangeLog      |  6 ++++++
 lib/lchown.c   | 23 ++++++++++++-----------
 modules/lchown |  2 +-
 3 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 4e387893cb..b072f949d7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	lchown: Use issymlink.
+	* lib/lchown.c (lchown): Use issymlink instead of readlink.
+	* modules/lchown (Depends-on): Remove readlink. Add issymlink.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	chown: Use issymlink.
diff --git a/lib/lchown.c b/lib/lchown.c
index ce7d31730a..e5e277101c 100644
--- a/lib/lchown.c
+++ b/lib/lchown.c
@@ -44,9 +44,7 @@ lchown (const char *file, uid_t uid, gid_t gid)
 {
 # if HAVE_CHOWN
 #  if ! CHOWN_MODIFIES_SYMLINK
-  char readlink_buf[1];
-
-  if (0 <= readlink (file, readlink_buf, sizeof readlink_buf))
+  if (issymlink (file) > 0)
     {
       errno = EOPNOTSUPP;
       return -1;
@@ -79,19 +77,22 @@ rpl_lchown (const char *file, uid_t uid, gid_t gid)
     {
       /* Prefer readlink to lstat+S_ISLNK, to avoid EOVERFLOW issues
          in the common case where FILE is a non-symlink.  */
-      char linkbuf[1];
-      int r = readlink (file, linkbuf, 1);
-      if (r < 0)
-        return errno == EINVAL ? chown (file, uid, gid) : r;
+      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.  */
-      r = lstat (file, &st);
-      if (r < 0)
-        return r;
-      stat_valid = true;
+      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;
     }
 # endif
 
diff --git a/modules/lchown b/modules/lchown
index 45f16d91aa..94ba7a9cdd 100644
--- a/modules/lchown
+++ b/modules/lchown
@@ -7,7 +7,7 @@ m4/lchown.m4
 
 Depends-on:
 unistd-h
-readlink        [test $HAVE_LCHOWN = 0 || test $REPLACE_LCHOWN = 1]
+issymlink       [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]
-- 
2.50.1

>From 64ac89d38d76688b53edb26ca38720d6cc9cad36 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:05:37 +0200
Subject: [PATCH 04/11] chown: Avoid a redundant stat() call.

* lib/chown.c (rpl_chown): Set stat_valid after stat() succeeded.
---
 ChangeLog   | 5 +++++
 lib/chown.c | 1 +
 2 files changed, 6 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index b072f949d7..b69ff7406c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	chown: Avoid a redundant stat() call.
+	* lib/chown.c (rpl_chown): Set stat_valid after stat() succeeded.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	lchown: Use issymlink.
diff --git a/lib/chown.c b/lib/chown.c
index 2ad9972100..97cacb82da 100644
--- a/lib/chown.c
+++ b/lib/chown.c
@@ -75,6 +75,7 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
       /* 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)
-- 
2.50.1

>From 2a98a9ae33269bf37f1825935959d6dd598eb2be Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:16:23 +0200
Subject: [PATCH 05/11] lchmod: Use issymlink, issymlinkat.

* lib/lchmod.c (lchmod): Use issymlink instead of readlink and
issymlinkat instead of readlinkat.
* modules/lchmod (Depends-on): Remove readlink. Add issymlink,
issymlinkat.
---
 ChangeLog      |  8 ++++++++
 lib/lchmod.c   | 29 +++++++++++++++--------------
 modules/lchmod |  7 ++++---
 3 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b69ff7406c..9df8c9b99b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	lchmod: Use issymlink, issymlinkat.
+	* lib/lchmod.c (lchmod): Use issymlink instead of readlink and
+	issymlinkat instead of readlinkat.
+	* modules/lchmod (Depends-on): Remove readlink. Add issymlink,
+	issymlinkat.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	chown: Avoid a redundant stat() call.
diff --git a/lib/lchmod.c b/lib/lchmod.c
index aabb15e359..4c44ca8063 100644
--- a/lib/lchmod.c
+++ b/lib/lchmod.c
@@ -37,8 +37,6 @@
 int
 lchmod (char const *file, mode_t mode)
 {
-  char readlink_buf[1];
-
 #ifdef O_PATH
   /* Open a file descriptor with O_NOFOLLOW, to make sure we don't
      follow symbolic links, if /proc is mounted.  O_PATH is used to
@@ -49,17 +47,20 @@ lchmod (char const *file, mode_t mode)
     return fd;
 
   int err;
-  if (0 <= readlinkat (fd, "", readlink_buf, sizeof readlink_buf))
-    err = EOPNOTSUPP;
-  else if (errno == EINVAL)
-    {
-      static char const fmt[] = "/proc/self/fd/%d";
-      char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
-      sprintf (buf, fmt, fd);
-      err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
-    }
-  else
-    err = errno == ENOENT ? -1 : errno;
+  {
+    int ret = issymlinkat (fd, "");
+    if (ret > 0)
+      err = EOPNOTSUPP;
+    else if (ret == 0)
+      {
+        static char const fmt[] = "/proc/self/fd/%d";
+        char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
+        sprintf (buf, fmt, fd);
+        err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
+      }
+    else
+      err = errno == ENOENT ? -1 : errno;
+  }
 
   close (fd);
 
@@ -83,7 +84,7 @@ lchmod (char const *file, mode_t mode)
 
   /* O_PATH + /proc is not supported.  */
 
-  if (0 <= readlink (file, readlink_buf, sizeof readlink_buf))
+  if (issymlink (file) > 0)
     {
       errno = EOPNOTSUPP;
       return -1;
diff --git a/modules/lchmod b/modules/lchmod
index c087bfa00c..9caa756d21 100644
--- a/modules/lchmod
+++ b/modules/lchmod
@@ -6,14 +6,15 @@ lib/lchmod.c
 m4/lchmod.m4
 
 Depends-on:
+sys_stat-h
+extensions
 c99           [test $HAVE_LCHMOD = 0]
 errno-h       [test $HAVE_LCHMOD = 0]
-extensions
 fcntl-h       [test $HAVE_LCHMOD = 0]
 intprops      [test $HAVE_LCHMOD = 0]
+issymlink     [test $HAVE_LCHMOD = 0]
+issymlinkat   [test $HAVE_LCHMOD = 0]
 lstat         [test $HAVE_LCHMOD = 0]
-readlink      [test $HAVE_LCHMOD = 0]
-sys_stat-h
 unistd-h      [test $HAVE_LCHMOD = 0]
 
 configure.ac:
-- 
2.50.1

>From 2f0b2ccec3c91a61515a2bf1d6ac9aa9d1ff30c9 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:18:57 +0200
Subject: [PATCH 06/11] fchmodat: Use issymlinkat.

* lib/fchmodat.c (fchmodat): Use issymlinkat instead of readlinkat.
* modules/fchmodat (Depends-on): Add issymlinkat, openat.
---
 ChangeLog        |  6 ++++++
 lib/fchmodat.c   | 29 +++++++++++++++--------------
 modules/fchmodat |  2 ++
 3 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9df8c9b99b..386c8bd475 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	fchmodat: Use issymlinkat.
+	* lib/fchmodat.c (fchmodat): Use issymlinkat instead of readlinkat.
+	* modules/fchmodat (Depends-on): Add issymlinkat, openat.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	lchmod: Use issymlink, issymlinkat.
diff --git a/lib/fchmodat.c b/lib/fchmodat.c
index eb82e1cafb..57222e58eb 100644
--- a/lib/fchmodat.c
+++ b/lib/fchmodat.c
@@ -84,8 +84,6 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
   if (flags == AT_SYMLINK_NOFOLLOW)
     {
 #  if HAVE_READLINKAT
-      char readlink_buf[1];
-
 #   ifdef O_PATH
       /* Open a file descriptor with O_NOFOLLOW, to make sure we don't
          follow symbolic links, if /proc is mounted.  O_PATH is used to
@@ -96,17 +94,20 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
         return fd;
 
       int err;
-      if (0 <= readlinkat (fd, "", readlink_buf, sizeof readlink_buf))
-        err = EOPNOTSUPP;
-      else if (errno == EINVAL)
-        {
-          static char const fmt[] = "/proc/self/fd/%d";
-          char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
-          sprintf (buf, fmt, fd);
-          err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
-        }
-      else
-        err = errno == ENOENT ? -1 : errno;
+      {
+        int ret = issymlinkat (fd, "");
+        if (ret > 0)
+          err = EOPNOTSUPP;
+        else if (ret == 0)
+          {
+            static char const fmt[] = "/proc/self/fd/%d";
+            char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
+            sprintf (buf, fmt, fd);
+            err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
+          }
+        else
+          err = errno == ENOENT ? -1 : errno;
+      }
 
       close (fd);
 
@@ -117,7 +118,7 @@ fchmodat (int dir, char const *file, mode_t mode, int flags)
 
       /* O_PATH + /proc is not supported.  */
 
-      if (0 <= readlinkat (dir, file, readlink_buf, sizeof readlink_buf))
+      if (issymlinkat (dir, file) > 0)
         {
           errno = EOPNOTSUPP;
           return -1;
diff --git a/modules/fchmodat b/modules/fchmodat
index 3afd0efd30..914825c1a4 100644
--- a/modules/fchmodat
+++ b/modules/fchmodat
@@ -14,6 +14,8 @@ fcntl-h         [test $HAVE_FCHMODAT = 0 || test $REPLACE_FCHMODAT = 1]
 unistd-h        [test $HAVE_FCHMODAT = 0 || test $REPLACE_FCHMODAT = 1]
 intprops        [test $HAVE_FCHMODAT = 0 || test $REPLACE_FCHMODAT = 1]
 c99             [test $REPLACE_FCHMODAT = 1]
+issymlinkat     [test $REPLACE_FCHMODAT = 1]
+openat          [test $REPLACE_FCHMODAT = 1]
 at-internal     [test $HAVE_FCHMODAT = 0]
 chmod           [test $HAVE_FCHMODAT = 0]
 extern-inline   [test $HAVE_FCHMODAT = 0]
-- 
2.50.1

>From 3dbfcbf5ed4e857eeb0106a07977d389561791b0 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:21:23 +0200
Subject: [PATCH 07/11] rename: Use issymlink.

* lib/rename.c (rpl_rename): Use issymlink instead of readlink.
* modules/rename (Depends-on): Remove readlink. Add issymlink.
---
 ChangeLog      |  6 ++++++
 lib/rename.c   | 12 ++++++------
 modules/rename |  2 +-
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 386c8bd475..6c6875f353 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	rename: Use issymlink.
+	* lib/rename.c (rpl_rename): Use issymlink instead of readlink.
+	* modules/rename (Depends-on): Remove readlink. Add issymlink.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	fchmodat: Use issymlinkat.
diff --git a/lib/rename.c b/lib/rename.c
index f4f191d61f..7efd64f992 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -389,10 +389,10 @@ rpl_rename (char const *src, char const *dst)
           goto out;
         }
       strip_trailing_slashes (src_temp);
-      char linkbuf[1];
-      if (0 <= readlink (src_temp, linkbuf, 1))
+      int ret = issymlink (src_temp);
+      if (ret > 0)
         goto out;
-      if (errno != EINVAL)
+      if (ret < 0)
         {
           rename_errno = errno;
           goto out;
@@ -407,10 +407,10 @@ rpl_rename (char const *src, char const *dst)
           goto out;
         }
       strip_trailing_slashes (dst_temp);
-      char linkbuf[1];
-      if (0 <= readlink (dst_temp, linkbuf, 1))
+      int ret = issymlink (dst_temp);
+      if (ret > 0)
         goto out;
-      if (errno != EINVAL && errno != ENOENT)
+      if (ret < 0 && errno != ENOENT)
         {
           rename_errno = errno;
           goto out;
diff --git a/modules/rename b/modules/rename
index fbc787d3e5..0f061fbf25 100644
--- a/modules/rename
+++ b/modules/rename
@@ -11,8 +11,8 @@ canonicalize-lgpl [test $REPLACE_RENAME = 1]
 chdir             [test $REPLACE_RENAME = 1]
 dirname-lgpl      [test $REPLACE_RENAME = 1]
 free-posix        [test $REPLACE_RENAME = 1]
+issymlink         [test $REPLACE_RENAME = 1]
 lstat             [test $REPLACE_RENAME = 1]
-readlink          [test $REPLACE_RENAME = 1]
 rmdir             [test $REPLACE_RENAME = 1]
 same-inode        [test $REPLACE_RENAME = 1]
 stat              [test $REPLACE_RENAME = 1]
-- 
2.50.1

>From f8b1b1628b43985a99c3e65f43525732a8a0a26e Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:23:29 +0200
Subject: [PATCH 08/11] renameatu: Use issymlinkat.

* lib/renameatu.c (renameatu): Use issymlinkat instead of readlinkat.
* modules/renameatu (Depends-on): Remove readlinkat. Add issymlinkat.
---
 ChangeLog         |  6 ++++++
 lib/renameatu.c   | 30 ++++++++++++------------------
 modules/renameatu |  2 +-
 3 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6c6875f353..d0d03f92ae 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	renameatu: Use issymlinkat.
+	* lib/renameatu.c (renameatu): Use issymlinkat instead of readlinkat.
+	* modules/renameatu (Depends-on): Remove readlinkat. Add issymlinkat.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	rename: Use issymlink.
diff --git a/lib/renameatu.c b/lib/renameatu.c
index 725e031abc..64a7f5285a 100644
--- a/lib/renameatu.c
+++ b/lib/renameatu.c
@@ -204,17 +204,14 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
           goto out;
         }
       strip_trailing_slashes (src_temp);
-      char linkbuf[1];
-      if (readlinkat (fd1, src_temp, linkbuf, sizeof linkbuf) < 0)
+      int ret = issymlinkat (fd1, src_temp);
+      if (ret > 0)
+        goto out;
+      if (ret < 0)
         {
-          if (errno != EINVAL)
-            {
-              rename_errno = errno;
-              goto out;
-            }
+          rename_errno = errno;
+          goto out;
         }
-      else
-        goto out;
     }
   if (dst_slash)
     {
@@ -225,17 +222,14 @@ renameatu (int fd1, char const *src, int fd2, char const *dst,
           goto out;
         }
       strip_trailing_slashes (dst_temp);
-      char linkbuf[1];
-      if (readlinkat (fd2, dst_temp, linkbuf, sizeof linkbuf) < 0)
+      int ret = issymlinkat (fd2, dst_temp);
+      if (ret > 0)
+        goto out;
+      if (ret < 0 && errno != ENOENT)
         {
-          if (errno != ENOENT && errno != EINVAL)
-            {
-              rename_errno = errno;
-              goto out;
-            }
+          rename_errno = errno;
+          goto out;
         }
-      else
-        goto out;
     }
 # endif /* RENAME_TRAILING_SLASH_SOURCE_BUG */
 
diff --git a/modules/renameatu b/modules/renameatu
index 2947159adb..f4f12acd13 100644
--- a/modules/renameatu
+++ b/modules/renameatu
@@ -14,7 +14,7 @@ fcntl-h
 filenamecat-lgpl [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
 openat-h         [test $HAVE_RENAMEAT = 0 || test $REPLACE_RENAMEAT = 1]
 fstatat          [test $REPLACE_RENAMEAT = 1]
-readlinkat       [test $REPLACE_RENAMEAT = 1]
+issymlinkat      [test $REPLACE_RENAMEAT = 1]
 bool             [test $REPLACE_RENAMEAT = 1]
 at-internal      [test $HAVE_RENAMEAT = 0]
 filename         [test $HAVE_RENAMEAT = 0]
-- 
2.50.1

>From e71bb18ba1814ffd9e763a9dfbdc976c0a35d7e5 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:30:18 +0200
Subject: [PATCH 09/11] unlink: Use issymlink.

* lib/unlink.c (rpl_unlink): Use issymlink instead of readlink.
* modules/unlink (Depends-on): Remove readlink. Add issymlink.
---
 ChangeLog      | 6 ++++++
 lib/unlink.c   | 4 +---
 modules/unlink | 2 +-
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index d0d03f92ae..6fcc5d6eae 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	unlink: Use issymlink.
+	* lib/unlink.c (rpl_unlink): Use issymlink instead of readlink.
+	* modules/unlink (Depends-on): Remove readlink. Add issymlink.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	renameatu: Use issymlinkat.
diff --git a/lib/unlink.c b/lib/unlink.c
index 6459e22e0b..9963ddf292 100644
--- a/lib/unlink.c
+++ b/lib/unlink.c
@@ -72,9 +72,7 @@ rpl_unlink (char const *name)
           memcpy (short_name, name, len);
           while (len && ISSLASH (short_name[len - 1]))
             short_name[--len] = '\0';
-          char linkbuf[1];
-          if (len && ! (readlink (short_name, linkbuf, 1) < 0
-                        && errno == EINVAL))
+          if (len && issymlink (short_name) != 0)
             {
               free (short_name);
               errno = EPERM;
diff --git a/modules/unlink b/modules/unlink
index 43c4f8440b..76f5cba79a 100644
--- a/modules/unlink
+++ b/modules/unlink
@@ -8,9 +8,9 @@ m4/unlink.m4
 Depends-on:
 unistd-h
 filename        [test $REPLACE_UNLINK = 1]
+issymlink       [test $REPLACE_UNLINK = 1]
 lstat           [test $REPLACE_UNLINK = 1]
 malloc-posix    [test $REPLACE_UNLINK = 1]
-readlink        [test $REPLACE_UNLINK = 1]
 
 configure.ac:
 gl_FUNC_UNLINK
-- 
2.50.1

>From 8f61201292ebc36cb531530ed40d69c768981637 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:31:48 +0200
Subject: [PATCH 10/11] unlinkat: Use issymlinkat.

* lib/unlinkat.c (rpl_unlinkat): Use issymlinkat instead of readlinkat.
* modules/unlinkat (Depends-on): Remove readlinkat. Add issymlinkat.
---
 ChangeLog        | 6 ++++++
 lib/unlinkat.c   | 4 +---
 modules/unlinkat | 4 ++--
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6fcc5d6eae..7f89069879 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	unlinkat: Use issymlinkat.
+	* lib/unlinkat.c (rpl_unlinkat): Use issymlinkat instead of readlinkat.
+	* modules/unlinkat (Depends-on): Remove readlinkat. Add issymlinkat.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	unlink: Use issymlink.
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
index f2a9d4e93c..847a379a91 100644
--- a/lib/unlinkat.c
+++ b/lib/unlinkat.c
@@ -71,9 +71,7 @@ rpl_unlinkat (int fd, char const *name, int flag)
           memcpy (short_name, name, len);
           while (len && ISSLASH (short_name[len - 1]))
             short_name[--len] = '\0';
-          char linkbuf[1];
-          if (len && ! (readlinkat (fd, short_name, linkbuf, 1) < 0
-                        && errno == EINVAL))
+          if (len && issymlinkat (fd, short_name) != 0)
             {
               free (short_name);
               errno = EPERM;
diff --git a/modules/unlinkat b/modules/unlinkat
index aaacfd936a..24232c431d 100644
--- a/modules/unlinkat
+++ b/modules/unlinkat
@@ -13,6 +13,8 @@ extensions
 fcntl-h         [test $HAVE_UNLINKAT = 0 || test $REPLACE_UNLINKAT = 1]
 openat-h        [test $HAVE_UNLINKAT = 0 || test $REPLACE_UNLINKAT = 1]
 sys_stat-h      [test $HAVE_UNLINKAT = 0 || test $REPLACE_UNLINKAT = 1]
+fstatat         [test $REPLACE_UNLINKAT = 1]
+issymlinkat     [test $REPLACE_UNLINKAT = 1]
 at-internal     [test $HAVE_UNLINKAT = 0]
 errno-h         [test $HAVE_UNLINKAT = 0]
 fchdir          [test $HAVE_UNLINKAT = 0]
@@ -21,8 +23,6 @@ openat-die      [test $HAVE_UNLINKAT = 0]
 rmdir           [test $HAVE_UNLINKAT = 0]
 save-cwd        [test $HAVE_UNLINKAT = 0]
 unlink          [test $HAVE_UNLINKAT = 0]
-fstatat         [test $REPLACE_UNLINKAT = 1]
-readlinkat      [test $REPLACE_UNLINKAT = 1]
 
 configure.ac:
 gl_FUNC_UNLINKAT
-- 
2.50.1

>From 4890a0c51a0f508f955608d39c8078ae7c35f03a Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 14 Aug 2025 22:34:17 +0200
Subject: [PATCH 11/11] utimens: Use issymlink.

* lib/utimens.c (lutimens): Use issymlink instead of readlink.
* modules/utimens (Depends-on): Remove readlink. Add issymlink.
---
 ChangeLog       | 6 ++++++
 lib/utimens.c   | 6 +++---
 modules/utimens | 2 +-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7f89069879..cf0e5f7a8e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-08-14  Bruno Haible  <[email protected]>
+
+	utimens: Use issymlink.
+	* lib/utimens.c (lutimens): Use issymlink instead of readlink.
+	* modules/utimens (Depends-on): Remove readlink. Add issymlink.
+
 2025-08-14  Bruno Haible  <[email protected]>
 
 	unlinkat: Use issymlinkat.
diff --git a/lib/utimens.c b/lib/utimens.c
index 4122e4ff22..442a1800b9 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -675,10 +675,10 @@ lutimens (char const *file, struct timespec const timespec[2])
     not_symlink = !S_ISLNK (st.st_mode);
   else
     {
-      char linkbuf[1];
-      not_symlink = readlink (file, linkbuf, 1) < 0;
-      if (not_symlink && errno != EINVAL)
+      int ret = issymlink (file);
+      if (ret < 0)
         return -1;
+      not_symlink = !ret;
     }
   if (not_symlink)
     return fdutimens (-1, file, ts);
diff --git a/modules/utimens b/modules/utimens
index aea6a47e66..e643a83a25 100644
--- a/modules/utimens
+++ b/modules/utimens
@@ -14,8 +14,8 @@ fcntl-h
 fstat
 lstat
 gettime
+issymlink
 msvc-nothrow
-readlink
 stat
 stat-time
 bool
-- 
2.50.1

Reply via email to