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
