On 12/18/20 6:13 AM, Adhemerval Zanella wrote:
The same tests I pointed out on BZ#24970 comment #2 still fails with gnulib
version 0aa8ef424.

I finally got some time free to look at this, and came up with some patches that should fix that problem, along with some others I noticed while in the neighborhood, and which should make it easier to merge into glibc. I installed the attached into Gnulib to do this. These patches also add test cases for the bugs in question.

I noticed this morning that you submitted some patches on the Glibc side, and this prompted me to finish this up on the Gnulib side. I plan to look at those Glibc patches next
>From 011dcf74d9c3bf6373a8fea1ce524b2d123b4130 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:47 -0800
Subject: [PATCH 01/10] canonicalize, canonicalize-lgpl: fix symlink bug

Problem reported by Adhemerval Zanella in:
https://lists.gnu.org/r/bug-gnulib/2020-12/msg00155.html
* lib/canonicalize-lgpl.c, lib/canonicalize.c:
(suffix_requires_dir_check, dir_check): New functions.
(GCC_BOGUS_WRETURN_LOCAL_ADDR): New macro, to put the diagnostic
closer to the related GCC diagnostics.
* lib/canonicalize-lgpl.c (realpath_stk):
* lib/canonicalize.c (canonicalize_file_mode_stk):
Use them to fix a bug with .../symlink-to-regular-file/ etc.
* lib/canonicalize-lgpl.c (__stat) [!_LIBC]: New macro.
(realpath_stk): New function,
with the contents of the old __realpath and a new scratch buffer arg.
This is needed to pacify GCC 10.1, as canonicalize.c is already doing.
(__realpath): Use it.
* tests/test-canonicalize-lgpl.c, tests/test-canonicalize.c:
Add test cases for the bugs.
---
 ChangeLog                      |  20 +++++
 lib/canonicalize-lgpl.c        | 145 ++++++++++++++++++++++++---------
 lib/canonicalize.c             |  81 ++++++++++++++----
 tests/test-canonicalize-lgpl.c |  28 +++++--
 tests/test-canonicalize.c      |  36 +++++---
 5 files changed, 237 insertions(+), 73 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 397bd7dbd..714f49354 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
+
+	canonicalize, canonicalize-lgpl: fix symlink bug
+	Problem reported by Adhemerval Zanella in:
+	https://lists.gnu.org/r/bug-gnulib/2020-12/msg00155.html
+	* lib/canonicalize-lgpl.c, lib/canonicalize.c:
+	(suffix_requires_dir_check, dir_check): New functions.
+	(GCC_BOGUS_WRETURN_LOCAL_ADDR): New macro, to put the diagnostic
+	closer to the related GCC diagnostics.
+	* lib/canonicalize-lgpl.c (realpath_stk):
+	* lib/canonicalize.c (canonicalize_file_mode_stk):
+	Use them to fix a bug with .../symlink-to-regular-file/ etc.
+	* lib/canonicalize-lgpl.c (__stat) [!_LIBC]: New macro.
+	(realpath_stk): New function,
+	with the contents of the old __realpath and a new scratch buffer arg.
+	This is needed to pacify GCC 10.1, as canonicalize.c is already doing.
+	(__realpath): Use it.
+	* tests/test-canonicalize-lgpl.c, tests/test-canonicalize.c:
+	Add test cases for the bugs.
+
 2020-12-24  Bruno Haible  <br...@clisp.org>
 
 	execute: Treat signalled processes like wait-process does.
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index 1440dad34..e4aba0e98 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -77,6 +77,7 @@ typedef ptrdiff_t idx_t;
 # define __pathconf pathconf
 # define __rawmemchr rawmemchr
 # define __readlink readlink
+# define __stat stat
 # ifndef MAXSYMLINKS
 #  ifdef SYMLOOP_MAX
 #   define MAXSYMLINKS SYMLOOP_MAX
@@ -93,6 +94,52 @@ typedef ptrdiff_t idx_t;
 
 #if !FUNC_REALPATH_WORKS || defined _LIBC
 
+/* True if concatenating END as a suffix to a file name means that the
+   code needs to check that the file name is that of a searchable
+   directory, since the canonicalize_filename_mode_stk code won't
+   check this later anyway when it checks an ordinary file name
+   component within END.  END must either be empty, or start with a
+   slash.  */
+
+static bool
+suffix_requires_dir_check (char const *end)
+{
+  /* If END does not start with a slash, the suffix is OK.  */
+  while (ISSLASH (*end))
+    {
+      /* Two or more slashes act like a single slash.  */
+      do
+        end++;
+      while (ISSLASH (*end));
+
+      switch (*end++)
+        {
+        default: return false;  /* An ordinary file name component is OK.  */
+        case '\0': return true; /* Trailing "/" is trouble.  */
+        case '.': break;        /* Possibly "." or "..".  */
+        }
+      /* Trailing "/.", or "/.." even if not trailing, is trouble.  */
+      if (!*end || (*end == '.' && (!end[1] || ISSLASH (end[1]))))
+        return true;
+    }
+
+  return false;
+}
+
+/* Return true if DIR is a directory, false (setting errno) otherwise.
+   DIREND points to the NUL byte at the end of the DIR string.
+   Store garbage into DIREND[0] and DIREND[1].  */
+
+static bool
+dir_check (char *dir, char *dirend)
+{
+  /* Append "/"; otherwise EOVERFLOW would be ambiguous.  */
+  strcpy (dirend, "/");
+
+  struct stat st;
+  return __stat (dir, &st) == 0 || errno == EOVERFLOW;
+}
+
 static idx_t
 get_path_max (void)
 {
@@ -111,19 +158,27 @@ get_path_max (void)
   return path_max < 0 ? 1024 : path_max <= IDX_MAX ? path_max : IDX_MAX;
 }
 
-/* Return the canonical absolute name of file NAME.  A canonical name
-   does not contain any ".", ".." components nor any repeated file name
-   separators ('/') or symlinks.  All file name components must exist.  If
-   RESOLVED is null, the result is malloc'd; otherwise, if the
-   canonical name is PATH_MAX chars or more, returns null with 'errno'
-   set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
-   returns the name in RESOLVED.  If the name cannot be resolved and
-   RESOLVED is non-NULL, it contains the name of the first component
-   that cannot be resolved.  If the name can be resolved, RESOLVED
-   holds the same value as the value returned.  */
-
-char *
-__realpath (const char *name, char *resolved)
+/* Act like __realpath (see below), with an additional argument
+   rname_buf that can be used as temporary storage.
+
+   If GCC_LINT is defined, do not inline this function with GCC 10.1
+   and later, to avoid creating a pointer to the stack that GCC
+   -Wreturn-local-addr incorrectly complains about.  See:
+   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644
+   Although the noinline attribute can hurt performance a bit, no better way
+   to pacify GCC is known; even an explicit #pragma does not pacify GCC.
+   When the GCC bug is fixed this workaround should be limited to the
+   broken GCC versions.  */
+#if __GNUC_PREREQ (10, 1)
+# if defined GCC_LINT || defined lint
+__attribute__ ((__noinline__))
+# elif __OPTIMIZE__ && !__NO_INLINE__
+#  define GCC_BOGUS_WRETURN_LOCAL_ADDR
+# endif
+#endif
+static char *
+realpath_stk (const char *name, char *resolved,
+              struct scratch_buffer *rname_buf)
 {
   char *dest;
   char const *start;
@@ -149,8 +204,6 @@ __realpath (const char *name, char *resolved)
     }
 
   struct scratch_buffer extra_buffer, link_buffer;
-  struct scratch_buffer rname_buffer;
-  struct scratch_buffer *rname_buf = &rname_buffer;
   scratch_buffer_init (&extra_buffer);
   scratch_buffer_init (&link_buffer);
   scratch_buffer_init (rname_buf);
@@ -208,7 +261,9 @@ __realpath (const char *name, char *resolved)
          name ends in '/'.  */
       idx_t startlen = end - start;
 
-      if (startlen == 1 && start[0] == '.')
+      if (startlen == 0)
+        break;
+      else if (startlen == 1 && start[0] == '.')
         /* nothing */;
       else if (startlen == 2 && start[0] == '.' && start[1] == '.')
         {
@@ -226,7 +281,8 @@ __realpath (const char *name, char *resolved)
           if (!ISSLASH (dest[-1]))
             *dest++ = '/';
 
-          while (rname + rname_buf->length - dest <= startlen)
+          enum { dir_check_room = sizeof "/" };
+          while (rname + rname_buf->length - dest < startlen + dir_check_room)
             {
               idx_t dest_offset = dest - rname;
               if (!scratch_buffer_grow_preserve (rname_buf))
@@ -238,28 +294,19 @@ __realpath (const char *name, char *resolved)
           dest = __mempcpy (dest, start, startlen);
           *dest = '\0';
 
-          /* If STARTLEN == 0, RNAME ends in '/'; use stat rather than
-             readlink, because readlink might fail with EINVAL without
-             checking whether RNAME sans '/' is valid.  */
-          struct stat st;
-          char *buf = NULL;
+          char *buf;
           ssize_t n;
-          if (startlen != 0)
+          while (true)
             {
-              while (true)
-                {
-                  buf = link_buffer.data;
-                  idx_t bufsize = link_buffer.length;
-                  n = __readlink (rname, buf, bufsize - 1);
-                  if (n < bufsize - 1)
-                    break;
-                  if (!scratch_buffer_grow (&link_buffer))
-                    goto error_nomem;
-                }
-              if (n < 0)
-                buf = NULL;
+              buf = link_buffer.data;
+              idx_t bufsize = link_buffer.length;
+              n = __readlink (rname, buf, bufsize - 1);
+              if (n < bufsize - 1)
+                break;
+              if (!scratch_buffer_grow (&link_buffer))
+                goto error_nomem;
             }
-          if (buf)
+          if (0 <= n)
             {
               if (++num_links > __eloop_threshold ())
                 {
@@ -315,8 +362,8 @@ __realpath (const char *name, char *resolved)
                     dest++;
                 }
             }
-          else if (! (startlen == 0
-                      ? stat (rname, &st) == 0 || errno == EOVERFLOW
+          else if (! (suffix_requires_dir_check (end)
+                      ? dir_check (rname, dest)
                       : errno == EINVAL))
             goto error;
         }
@@ -355,6 +402,28 @@ error_nomem:
   char *result = realloc (rname, rname_size);
   return result != NULL ? result : rname;
 }
+
+/* Return the canonical absolute name of file NAME.  A canonical name
+   does not contain any ".", ".." components nor any repeated file name
+   separators ('/') or symlinks.  All file name components must exist.  If
+   RESOLVED is null, the result is malloc'd; otherwise, if the
+   canonical name is PATH_MAX chars or more, returns null with 'errno'
+   set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
+   returns the name in RESOLVED.  If the name cannot be resolved and
+   RESOLVED is non-NULL, it contains the name of the first component
+   that cannot be resolved.  If the name can be resolved, RESOLVED
+   holds the same value as the value returned.  */
+
+char *
+__realpath (const char *name, char *resolved)
+{
+  #ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
+   #warning "GCC might issue a bogus -Wreturn-local-addr warning here."
+   #warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+  #endif
+  struct scratch_buffer rname_buffer;
+  return realpath_stk (name, resolved, &rname_buffer);
+}
 libc_hidden_def (__realpath)
 versioned_symbol (libc, __realpath, realpath, GLIBC_2_3);
 #endif /* !FUNC_REALPATH_WORKS || defined _LIBC */
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index bcfd52be2..62777219a 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -45,6 +45,52 @@
 # define SLASHES "/"
 #endif
 
+/* True if concatenating END as a suffix to a file name means that the
+   code needs to check that the file name is that of a searchable
+   directory, since the canonicalize_filename_mode_stk code won't
+   check this later anyway when it checks an ordinary file name
+   component within END.  END must either be empty, or start with a
+   slash.  */
+
+static bool
+suffix_requires_dir_check (char const *end)
+{
+  /* If END does not start with a slash, the suffix is OK.  */
+  while (ISSLASH (*end))
+    {
+      /* Two or more slashes act like a single slash.  */
+      do
+        end++;
+      while (ISSLASH (*end));
+
+      switch (*end++)
+        {
+        default: return false;  /* An ordinary file name component is OK.  */
+        case '\0': return true; /* Trailing "/" is trouble.  */
+        case '.': break;        /* Possibly "." or "..".  */
+        }
+      /* Trailing "/.", or "/.." even if not trailing, is trouble.  */
+      if (!*end || (*end == '.' && (!end[1] || ISSLASH (end[1]))))
+        return true;
+    }
+
+  return false;
+}
+
+/* Return true if DIR is a directory, false (setting errno) otherwise.
+   DIREND points to the NUL byte at the end of the DIR string.
+   Store garbage into DIREND[0] and DIREND[1].  */
+
+static bool
+dir_check (char *dir, char *dirend)
+{
+  /* Append "/"; otherwise EOVERFLOW would be ambiguous.  */
+  strcpy (dirend, "/");
+
+  struct stat st;
+  return stat (dir, &st) == 0 || errno == EOVERFLOW;
+}
+
 #if !((HAVE_CANONICALIZE_FILE_NAME && FUNC_REALPATH_WORKS)      \
       || GNULIB_CANONICALIZE_LGPL)
 /* Return the canonical absolute name of file NAME.  A canonical name
@@ -105,8 +151,7 @@ seen_triple (Hash_table **ht, char const *filename, struct stat const *st)
 # if defined GCC_LINT || defined lint
 __attribute__ ((__noinline__))
 # elif __OPTIMIZE__ && !__NO_INLINE__
-#  warning "GCC might issue a bogus -Wreturn-local-addr warning here."
-#  warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+#  define GCC_BOGUS_WRETURN_LOCAL_ADDR
 # endif
 #endif
 static char *
@@ -228,7 +273,9 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
          name ends in '/'.  */
       idx_t startlen = end - start;
 
-      if (startlen == 1 && start[0] == '.')
+      if (startlen == 0)
+        break;
+      else if (startlen == 1 && start[0] == '.')
         /* nothing */;
       else if (startlen == 2 && start[0] == '.' && start[1] == '.')
         {
@@ -246,7 +293,8 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
           if (!ISSLASH (dest[-1]))
             *dest++ = '/';
 
-          while (rname + rname_buf->length - dest <= startlen)
+          enum { dir_check_room = sizeof "/" };
+          while (rname + rname_buf->length - dest < startlen + dir_check_room)
             {
               idx_t dest_offset = dest - rname;
               if (!scratch_buffer_grow_preserve (rname_buf))
@@ -258,14 +306,10 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
           dest = mempcpy (dest, start, startlen);
           *dest = '\0';
 
-          /* If STARTLEN == 0, RNAME ends in '/'; use stat rather than
-             readlink, because readlink might fail with EINVAL without
-             checking whether RNAME sans '/' is valid.  */
           char discard;
-          struct stat st;
-          char *buf = NULL;
-          ssize_t n;
-          if (!logical && startlen != 0)
+          char *buf;
+          ssize_t n = -1;
+          if (!logical)
             {
               while (true)
                 {
@@ -277,10 +321,8 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
                   if (!scratch_buffer_grow (&link_buffer))
                     xalloc_die ();
                 }
-              if (n < 0)
-                buf = NULL;
             }
-          if (buf)
+          if (0 <= n)
             {
               /* A physical traversal and RNAME is a symbolic link.  */
 
@@ -293,6 +335,7 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
                      Get the device and inode of the parent directory, as
                      pre-2017 POSIX says this info is not reliable for
                      symlinks.  */
+                  struct stat st;
                   dest[- startlen] = '\0';
                   if (stat (*rname ? rname : ".", &st) != 0)
                     goto error;
@@ -361,8 +404,8 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
                 }
             }
           else if (! (can_exist == CAN_MISSING
-                      || (startlen == 0
-                          ? stat (rname, &st) == 0 || errno == EOVERFLOW
+                      || (suffix_requires_dir_check (end)
+                          ? dir_check (rname, dest)
                           : ((logical && 0 <= readlink (rname, &discard, 1))
                              || errno == EINVAL))
                       || (can_exist == CAN_ALL_BUT_LAST
@@ -408,8 +451,10 @@ error:
 char *
 canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
 {
-  /* If GCC -Wreturn-local-addr warns about this buffer, the warning
-     is bogus; see canonicalize_filename_mode_stk.  */
+  #ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
+   #warning "GCC might issue a bogus -Wreturn-local-addr warning here."
+   #warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+  #endif
   struct scratch_buffer rname_buffer;
   return canonicalize_filename_mode_stk (name, can_mode, &rname_buffer);
 }
diff --git a/tests/test-canonicalize-lgpl.c b/tests/test-canonicalize-lgpl.c
index ff829814e..8ee0970d3 100644
--- a/tests/test-canonicalize-lgpl.c
+++ b/tests/test-canonicalize-lgpl.c
@@ -166,13 +166,29 @@ main (void)
     ASSERT (errno == ENOENT);
   }
 
-  /* Check that a non-directory symlink with trailing slash yields NULL.  */
+  /* Check that a non-directory symlink with trailing slash yields NULL,
+     and likewise for other troublesome suffixes.  */
   {
-    char *result;
-    errno = 0;
-    result = canonicalize_file_name (BASE "/huk/");
-    ASSERT (result == NULL);
-    ASSERT (errno == ENOTDIR);
+    char const *const file_name[]
+      = {
+         BASE "/huk/",
+         BASE "/huk/.",
+         BASE "/huk/./",
+         BASE "/huk/./.",
+         BASE "/huk/x",
+         BASE "/huk/..",
+         BASE "/huk/../",
+         BASE "/huk/../.",
+         BASE "/huk/../x",
+         BASE "/huk/./..",
+         BASE "/huk/././../x",
+        };
+    for (int i = 0; i < sizeof file_name / sizeof *file_name; i++)
+      {
+        errno = 0;
+        ASSERT (!canonicalize_file_name (file_name[i]));
+        ASSERT (errno == ENOTDIR);
+      }
   }
 
   /* Check that a missing directory via symlink yields NULL.  */
diff --git a/tests/test-canonicalize.c b/tests/test-canonicalize.c
index e0b623f19..2af03a296 100644
--- a/tests/test-canonicalize.c
+++ b/tests/test-canonicalize.c
@@ -214,18 +214,32 @@ main (void)
     ASSERT (errno == ENOENT);
   }
 
-  /* Check that a non-directory symlink with trailing slash yields NULL.  */
+  /* Check that a non-directory symlink with trailing slash yields NULL,
+     and likewise for other troublesome suffixes.  */
   {
-    char *result1;
-    char *result2;
-    errno = 0;
-    result1 = canonicalize_file_name (BASE "/huk/");
-    ASSERT (result1 == NULL);
-    ASSERT (errno == ENOTDIR);
-    errno = 0;
-    result2 = canonicalize_filename_mode (BASE "/huk/", CAN_EXISTING);
-    ASSERT (result2 == NULL);
-    ASSERT (errno == ENOTDIR);
+    char const *const file_name[]
+      = {
+         BASE "/huk/",
+         BASE "/huk/.",
+         BASE "/huk/./",
+         BASE "/huk/./.",
+         BASE "/huk/x",
+         BASE "/huk/..",
+         BASE "/huk/../",
+         BASE "/huk/../.",
+         BASE "/huk/../x",
+         BASE "/huk/./..",
+         BASE "/huk/././../x",
+        };
+    for (int i = 0; i < sizeof file_name / sizeof *file_name; i++)
+      {
+        errno = 0;
+        ASSERT (!canonicalize_file_name (file_name[i]));
+        ASSERT (errno == ENOTDIR);
+        errno = 0;
+        ASSERT (!canonicalize_filename_mode (file_name[i], CAN_EXISTING));
+        ASSERT (errno == ENOTDIR);
+      }
   }
 
   /* Check that a missing directory via symlink yields NULL.  */
-- 
2.27.0

>From 996b5284623c67ed9b94a7baca4fcf9cb7ebe7b0 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 02/10] canonicalize-lgpl: remove freea macro

* lib/canonicalize-lgpl.c (freea) [_LIBC]: Remove; not needed.
---
 ChangeLog               | 3 +++
 lib/canonicalize-lgpl.c | 1 -
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index 714f49354..b0cad381a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	canonicalize-lgpl: remove freea macro
+	* lib/canonicalize-lgpl.c (freea) [_LIBC]: Remove; not needed.
+
 	canonicalize, canonicalize-lgpl: fix symlink bug
 	Problem reported by Adhemerval Zanella in:
 	https://lists.gnu.org/r/bug-gnulib/2020-12/msg00155.html
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index e4aba0e98..431dc5a3b 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -46,7 +46,6 @@ typedef ptrdiff_t idx_t;
 # define FILE_SYSTEM_PREFIX_LEN(name) 0
 # define IS_ABSOLUTE_FILE_NAME(name) ISSLASH(*(name))
 # define ISSLASH(c) ((c) == '/')
-# define freea(p) ((void) (p))
 #else
 # define __canonicalize_file_name canonicalize_file_name
 # define __realpath realpath
-- 
2.27.0

>From 65d54010990b8c417cd0584b338edc5ead82cbf7 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 03/10] stat failing with EOVERFLOW implies existence

* lib/euidaccess.c (euidaccess):
* lib/file-has-acl.c (file_has_acl):
* lib/link.c (link, rpl_link):
* lib/mkdir.c (rpl_mkdir):
* lib/mkfifo.c (rpl_mkfifo):
* lib/mknod.c (rpl_mknod):
* lib/ptsname_r.c (__ptsname_r):
* lib/symlink.c (rpl_symlink):
* lib/symlinkat.c (rpl_symlinkat):
* lib/unlink.c (rpl_unlink):
* lib/unlinkat.c (rpl_unlinkat):
* lib/utime.c (utime):
If stat fails with EOVERFLOW the file exists, so treat it that way
in file-existence tests that do not need struct stat values.
---
 ChangeLog          | 16 ++++++++++++++++
 lib/euidaccess.c   |  9 ++++++---
 lib/file-has-acl.c |  2 +-
 lib/link.c         |  6 +++---
 lib/mkdir.c        |  2 +-
 lib/mkfifo.c       |  2 +-
 lib/mknod.c        |  2 +-
 lib/ptsname_r.c    |  2 +-
 lib/symlink.c      |  2 +-
 lib/symlinkat.c    |  2 +-
 lib/unlink.c       |  3 ++-
 lib/unlinkat.c     |  3 ++-
 lib/utime.c        |  2 +-
 13 files changed, 37 insertions(+), 16 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b0cad381a..3d2186fb9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	stat failing with EOVERFLOW implies existence
+	* lib/euidaccess.c (euidaccess):
+	* lib/file-has-acl.c (file_has_acl):
+	* lib/link.c (link, rpl_link):
+	* lib/mkdir.c (rpl_mkdir):
+	* lib/mkfifo.c (rpl_mkfifo):
+	* lib/mknod.c (rpl_mknod):
+	* lib/ptsname_r.c (__ptsname_r):
+	* lib/symlink.c (rpl_symlink):
+	* lib/symlinkat.c (rpl_symlinkat):
+	* lib/unlink.c (rpl_unlink):
+	* lib/unlinkat.c (rpl_unlinkat):
+	* lib/utime.c (utime):
+	If stat fails with EOVERFLOW the file exists, so treat it that way
+	in file-existence tests that do not need struct stat values.
+
 	canonicalize-lgpl: remove freea macro
 	* lib/canonicalize-lgpl.c (freea) [_LIBC]: Remove; not needed.
 
diff --git a/lib/euidaccess.c b/lib/euidaccess.c
index b352123ae..a32e3366e 100644
--- a/lib/euidaccess.c
+++ b/lib/euidaccess.c
@@ -107,7 +107,10 @@ euidaccess (const char *file, int mode)
      safe.  */
 
   if (mode == F_OK)
-    return stat (file, &stats);
+    {
+      int result = stat (file, &stats);
+      return result != 0 && errno == EOVERFLOW ? 0 : result;
+    }
   else
     {
       int result;
@@ -142,8 +145,8 @@ euidaccess (const char *file, int mode)
     /* If we are not set-uid or set-gid, access does the same.  */
     return access (file, mode);
 
-  if (stat (file, &stats) != 0)
-    return -1;
+  if (stat (file, &stats) == -1)
+    return mode == F_OK && errno == EOVERFLOW ? 0 : -1;
 
   /* The super-user can read and write any file, and execute any file
      that anyone can execute.  */
diff --git a/lib/file-has-acl.c b/lib/file-has-acl.c
index c667ae9d2..4adb7f6f1 100644
--- a/lib/file-has-acl.c
+++ b/lib/file-has-acl.c
@@ -353,7 +353,7 @@ file_has_acl (char const *name, struct stat const *sb)
             {
               struct stat statbuf;
 
-              if (stat (name, &statbuf) < 0)
+              if (stat (name, &statbuf) == -1 && errno != EOVERFLOW)
                 return -1;
 
               return acl_nontrivial (count, entries);
diff --git a/lib/link.c b/lib/link.c
index 797cdf8b9..3c0c341d5 100644
--- a/lib/link.c
+++ b/lib/link.c
@@ -107,7 +107,7 @@ link (const char *file1, const char *file2)
     char *p = strchr (dir, '\0');
     while (dir < p && (*--p != '/' && *p != '\\'));
     *p = '\0';
-    if (p != dir && stat (dir, &st) == -1)
+    if (p != dir && stat (dir, &st) != 0 && errno != EOVERFLOW)
       {
         int saved_errno = errno;
         free (dir);
@@ -181,7 +181,7 @@ rpl_link (char const *file1, char const *file2)
   struct stat st;
 
   /* Don't allow IRIX to dereference dangling file2 symlink.  */
-  if (!lstat (file2, &st))
+  if (lstat (file2, &st) == 0 || errno == EOVERFLOW)
     {
       errno = EEXIST;
       return -1;
@@ -218,7 +218,7 @@ rpl_link (char const *file1, char const *file2)
       if (p)
         {
           *p = '\0';
-          if (stat (dir, &st) == -1)
+          if (stat (dir, &st) != 0 && errno != EOVERFLOW)
             {
               int saved_errno = errno;
               free (dir);
diff --git a/lib/mkdir.c b/lib/mkdir.c
index c0d4b6165..386344fcd 100644
--- a/lib/mkdir.c
+++ b/lib/mkdir.c
@@ -77,7 +77,7 @@ rpl_mkdir (char const *dir, mode_t mode maybe_unused)
                          || (last[1] == '.' && last[2] == '\0')))
       {
         struct stat st;
-        if (stat (tmp_dir, &st) == 0)
+        if (stat (tmp_dir, &st) == 0 || errno == EOVERFLOW)
           errno = EEXIST;
         return -1;
       }
diff --git a/lib/mkfifo.c b/lib/mkfifo.c
index 706297c0a..952ffc0fe 100644
--- a/lib/mkfifo.c
+++ b/lib/mkfifo.c
@@ -48,7 +48,7 @@ rpl_mkfifo (char const *name, mode_t mode)
   if (len && name[len - 1] == '/')
     {
       struct stat st;
-      if (stat (name, &st) == 0)
+      if (stat (name, &st) == 0 || errno == EOVERFLOW)
         errno = EEXIST;
       return -1;
     }
diff --git a/lib/mknod.c b/lib/mknod.c
index 4e778fa15..1de2794c6 100644
--- a/lib/mknod.c
+++ b/lib/mknod.c
@@ -56,7 +56,7 @@ rpl_mknod (char const *name, mode_t mode, dev_t dev)
       if (len && name[len - 1] == '/')
         {
           struct stat st;
-          if (stat (name, &st) == 0)
+          if (stat (name, &st) == 0 || errno == EOVERFLOW)
             errno = EEXIST;
           return -1;
         }
diff --git a/lib/ptsname_r.c b/lib/ptsname_r.c
index 12b8e66f0..99d1701dc 100644
--- a/lib/ptsname_r.c
+++ b/lib/ptsname_r.c
@@ -198,7 +198,7 @@ __ptsname_r (int fd, char *buf, size_t buflen)
     buf[sizeof (_PATH_DEV) - 1] = 't';
 # endif
 
-  if (__stat (buf, &st) < 0)
+  if (__stat (buf, &st) < 0 && errno != EOVERFLOW)
     return errno;
 
   __set_errno (save_errno);
diff --git a/lib/symlink.c b/lib/symlink.c
index e7dbd184a..b1196b9ee 100644
--- a/lib/symlink.c
+++ b/lib/symlink.c
@@ -36,7 +36,7 @@ rpl_symlink (char const *contents, char const *name)
   if (len && name[len - 1] == '/')
     {
       struct stat st;
-      if (lstat (name, &st) == 0)
+      if (lstat (name, &st) == 0 || errno == EOVERFLOW)
         errno = EEXIST;
       return -1;
     }
diff --git a/lib/symlinkat.c b/lib/symlinkat.c
index 5ce2fc9d5..2e0ff9a79 100644
--- a/lib/symlinkat.c
+++ b/lib/symlinkat.c
@@ -38,7 +38,7 @@ rpl_symlinkat (char const *contents, int fd, char const *name)
   if (len && name[len - 1] == '/')
     {
       struct stat st;
-      if (fstatat (fd, name, &st, 0) == 0)
+      if (fstatat (fd, name, &st, 0) == 0 || errno == EOVERFLOW)
         errno = EEXIST;
       return -1;
     }
diff --git a/lib/unlink.c b/lib/unlink.c
index ba5f6269c..58b1e9001 100644
--- a/lib/unlink.c
+++ b/lib/unlink.c
@@ -63,7 +63,7 @@ rpl_unlink (char const *name)
          can't delete a directory via a symlink.  */
       struct stat st;
       result = lstat (name, &st);
-      if (result == 0)
+      if (result == 0 || errno == EOVERFLOW)
         {
           /* Trailing NUL will overwrite the trailing slash.  */
           char *short_name = malloc (len);
@@ -79,6 +79,7 @@ rpl_unlink (char const *name)
               return -1;
             }
           free (short_name);
+          result = 0;
         }
     }
   if (!result)
diff --git a/lib/unlinkat.c b/lib/unlinkat.c
index 442368641..02ac851ee 100644
--- a/lib/unlinkat.c
+++ b/lib/unlinkat.c
@@ -59,7 +59,7 @@ rpl_unlinkat (int fd, char const *name, int flag)
          directory.  */
       struct stat st;
       result = lstatat (fd, name, &st);
-      if (result == 0)
+      if (result == 0 || errno == EOVERFLOW)
         {
           /* Trailing NUL will overwrite the trailing slash.  */
           char *short_name = malloc (len);
@@ -78,6 +78,7 @@ rpl_unlinkat (int fd, char const *name, int flag)
               return -1;
             }
           free (short_name);
+          result = 0;
         }
     }
   if (!result)
diff --git a/lib/utime.c b/lib/utime.c
index 6de8adbb3..bf7d7c534 100644
--- a/lib/utime.c
+++ b/lib/utime.c
@@ -276,7 +276,7 @@ utime (const char *name, const struct utimbuf *ts)
     {
       struct stat buf;
 
-      if (stat (name, &buf) < 0)
+      if (stat (name, &buf) == -1 && errno != EOVERFLOW)
         return -1;
     }
 # endif /* REPLACE_FUNC_UTIME_FILE */
-- 
2.27.0

>From 1a41f6578623ba0203514e4a6519d3cf7ad8ec51 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 04/10] faccessat: work around F_OK EOVERFLOW bug

Also, tune when LSTAT_FOLLOWS_SLASHED_SYMLINK.
* doc/posix-functions/faccessat.texi: Mention the problem.
* lib/faccessat.c (FACCESSAT_NEVER_EOVERFLOWS)
(LSTAT_FOLLOWS_SLASHED_SYMLINK): Default to 0.
(rpl_faccessat): If !FACCESSAT_NEVER_EOVERFLOWS, check
for F_OK and EOVERFLOW, which means we can return 0.
If LSTAT_FOLLOWS_SLASHED_SYMLINK, don't worry about
file names ending in slashes, as faccessat should already
do the right thing for them.
* m4/faccessat.m4 (gl_FUNC_FACCESSAT_EOVERFLOW): New macro.
(gl_FUNC_FACCESSAT): Use it.
---
 ChangeLog                          |  8 ++++++++
 doc/posix-functions/faccessat.texi | 14 ++++++++++---
 lib/faccessat.c                    | 14 ++++++++++++-
 m4/faccessat.m4                    | 33 +++++++++++++++++++++++++++---
 4 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3d2186fb9..551059e35 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	faccessat: work around F_OK EOVERFLOW bug
+	* doc/posix-functions/faccessat.texi: Mention the problem.
+	* lib/faccessat.c (FACCESSAT_NEVER_EOVERFLOWS): Default to 0.
+	(rpl_faccessat): If !FACCESSAT_NEVER_EOVERFLOWS, check
+	for F_OK and EOVERFLOW, which means we can return 0.
+	* m4/faccessat.m4 (gl_FUNC_FACCESSAT_EOVERFLOW): New macro.
+	(gl_FUNC_FACCESSAT): Use it.
+
 	stat failing with EOVERFLOW implies existence
 	* lib/euidaccess.c (euidaccess):
 	* lib/file-has-acl.c (file_has_acl):
diff --git a/doc/posix-functions/faccessat.texi b/doc/posix-functions/faccessat.texi
index 5d5165e47..07ea8e7bf 100644
--- a/doc/posix-functions/faccessat.texi
+++ b/doc/posix-functions/faccessat.texi
@@ -15,6 +15,12 @@ glibc 2.3.6, macOS 10.12, FreeBSD 7.4, NetBSD 6.1.5, OpenBSD 4.9, Minix 3.1.8, A
 On some platforms, @code{faccessat (dfd, "file/", amode, flag)}
 succeeds instead of failing when @file{file} is not a directory.
 macOS 10.13.
+@item
+On some platforms, @code{faccessat} can incorrectly fail with
+@code{EOVERFLOW} when the mode is @code{F_OK}:
+GNU/Linux with glibc 2.32, or with Linux kernel 5.7.
+@c This bug should be fixed in glibc 2.33 and kernel 5.8.  See:
+@c https://sourceware.org/bugzilla/show_bug.cgi?id=18683
 @end itemize
 
 Portability problems not fixed by Gnulib:
@@ -30,9 +36,11 @@ The replacement does not support the @code{AT_SYMLINK_NOFOLLOW} flag,
 which is supported by GNU @code{faccessat}.
 @item
 On some platforms, @code{faccessat} can mishandle @code{AT_EACCESS}
-after a process starts as root and then becomes non-root:
-GNU/Linux with glibc 2.32.
-@c This bug should be fixed in glibc 2.33.  See:
+after a process starts as root and then becomes non-root,
+or can incorrectly fail with @code{EOVERFLOW} when the mode
+is not @code{F_OK}:
+GNU/Linux with glibc 2.32, or with Linux kernel 5.7.
+@c These bugs should be fixed in glibc 2.33 and kernel 5.8.  See:
 @c https://sourceware.org/bugzilla/show_bug.cgi?id=18683
 @end itemize
 
diff --git a/lib/faccessat.c b/lib/faccessat.c
index 9f6a11bf6..330c54a0b 100644
--- a/lib/faccessat.c
+++ b/lib/faccessat.c
@@ -32,6 +32,13 @@
 #include <sys/stat.h>
 #undef _GL_INCLUDING_UNISTD_H
 
+#ifndef FACCESSAT_NEVER_EOVERFLOWS
+# define FACCESSAT_NEVER_EOVERFLOWS 0
+#endif
+#ifndef LSTAT_FOLLOWS_SLASHED_SYMLINK
+# define LSTAT_FOLLOWS_SLASHED_SYMLINK 0
+#endif
+
 #if HAVE_FACCESSAT
 static int
 orig_faccessat (int fd, char const *name, int mode, int flag)
@@ -59,7 +66,12 @@ rpl_faccessat (int fd, char const *file, int mode, int flag)
 {
   int result = orig_faccessat (fd, file, mode, flag);
 
-  if (result == 0 && file[strlen (file) - 1] == '/')
+  if (result != 0)
+    {
+      if (!FACCESSAT_NEVER_EOVERFLOWS && mode == F_OK && errno == EOVERFLOW)
+        return 0;
+    }
+  else if (!LSTAT_FOLLOWS_SLASHED_SYMLINK && file[strlen (file) - 1] == '/')
     {
       struct stat st;
       result = fstatat (fd, file, &st, 0);
diff --git a/m4/faccessat.m4 b/m4/faccessat.m4
index 7a8b979f8..a4ad31a46 100644
--- a/m4/faccessat.m4
+++ b/m4/faccessat.m4
@@ -1,4 +1,4 @@
-# serial 8
+# serial 9
 # See if we need to provide faccessat replacement.
 
 dnl Copyright (C) 2009-2020 Free Software Foundation, Inc.
@@ -8,6 +8,31 @@ dnl with or without modifications, as long as this notice is preserved.
 
 # Written by Eric Blake.
 
+AC_DEFUN([gl_FUNC_FACCESSAT_EOVERFLOW],
+[
+  AC_CHECK_FUNCS_ONCE([faccessat])
+  if test "$ac_cv_func_faccessat" = yes; then
+    AC_CACHE_CHECK([whether faccessat works when stat would EOVERFLOW],
+      [gl_cv_func_faccessat_never_eoverflows],
+      [AC_COMPILE_IFELSE(
+         [AC_LANG_PROGRAM([],
+            [[#ifdef __linux__
+               #include <linux/version.h>
+               #if (! (KERNEL_VERSION (5, 8, 0) <= LINUX_VERSION_CODE \
+                    && 2 < (__GLIBC__ + (33 <= __GLIBC_MINOR__))))
+                #error "faccessat might fail with EOVERFLOW"
+               #endif
+              #endif
+            ]])],
+         [gl_cv_func_faccessat_never_eoverflows=yes],
+         [gl_cv_func_faccessat_never_eoverflows=no])])
+    if test "$gl_cv_func_faccessat_never_eoverflows" = yes; then
+      AC_DEFINE([FACCESSAT_NEVER_EOVERFLOWS], 1,
+        [Define to 1 if faccessat is EOVERFLOW-free.])
+    fi
+  fi
+])
+
 AC_DEFUN([gl_FUNC_FACCESSAT],
 [
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
@@ -16,12 +41,14 @@ AC_DEFUN([gl_FUNC_FACCESSAT],
   dnl Persuade glibc <unistd.h> to declare faccessat().
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
 
+  AC_REQUIRE([gl_FUNC_FACCESSAT_EOVERFLOW])
+
   AC_CHECK_FUNCS_ONCE([faccessat])
   if test $ac_cv_func_faccessat = no; then
     HAVE_FACCESSAT=0
   else
-    case "$gl_cv_func_lstat_dereferences_slashed_symlink" in
-      *yes) ;;
+    case $gl_cv_func_lstat_dereferences_slashed_symlink,$gl_cv_func_faccessat_never_eoverflows in
+      *yes,*yes) ;;
       *)    REPLACE_FACCESSAT=1 ;;
     esac
   fi
-- 
2.27.0

>From 225c66111c76144e7320dfdaa831a442dece190b Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 05/10] canonicalize: prefer faccessat to stat

A proper faccessat doesn't have the EOVERFLOW problem, and can be
more efficient as it needn't gather data from the filesystem to
fill in struct stat.  So use stat only if faccessat is absent,
or when checking for symlink loops in canonicalize.c.
* lib/canonicalize-lgpl.c, lib/canonicalize.c:
Include fcntl.h, for AT_EACCESS.
(FACCESSAT_NEVER_EOVERFLOWS): Default to false.
(file_accessible): New function, based on faccessat but with
a fallback to stat and with an EOVERFLOW workaround.
(dir_check): Use it.
(dir_suffix): New static constant.
* lib/canonicalize-lgpl.c (FACCESSAT_NEVER_EOVERFLOWS) [_LIBC]:
Use __ASSUME_FACCESSAT2 to set FACCESSAT_NEVER_EOVERFLOWS
(__faccessat) [!_LIBC]: Define.
(realpath_stk): Use dir_suffix now.
* lib/canonicalize.c (canonicalize_filename_mode_stk):
If logical, don't check each component's existence; just check
at the end, as that's enough.
* m4/canonicalize.m4 (gl_FUNC_CANONICALIZE_FILENAME_MODE)
(gl_CANONICALIZE_LGPL_SEPARATE):
Require gl_FUNC_FACCESSAT_EOVERFLOW,
gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK, and check for faccessat.
(gl_CANONICALIZE_LGPL_SEPARATE): Do not check for readlink,
as the code does not use HAVE_READLINK.
* modules/canonicalize, modules/canonicalize-lgpl (Files):
Add m4/faccessat.m4, m4/lstat.m4.
(Depends-on): Add fcntl-lh.
---
 ChangeLog                 | 29 +++++++++++++++++++++
 lib/canonicalize-lgpl.c   | 55 ++++++++++++++++++++++++++++++++-------
 lib/canonicalize.c        | 53 ++++++++++++++++++++++++++++---------
 m4/canonicalize.m4        | 10 ++++---
 modules/canonicalize      |  3 +++
 modules/canonicalize-lgpl |  3 +++
 6 files changed, 128 insertions(+), 25 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 551059e35..94427d748 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,34 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	canonicalize: prefer faccessat to stat
+	A proper faccessat doesn't have the EOVERFLOW problem, and can be
+	more efficient as it needn't gather data from the filesystem to
+	fill in struct stat.  So use stat only if faccessat is absent,
+	or when checking for symlink loops in canonicalize.c.
+	* lib/canonicalize-lgpl.c, lib/canonicalize.c:
+	Include fcntl.h, for AT_EACCESS.
+	(FACCESSAT_NEVER_EOVERFLOWS): Default to false.
+	(file_accessible): New function, based on faccessat but with
+	a fallback to stat and with an EOVERFLOW workaround.
+	(dir_check): Use it.
+	(dir_suffix): New static constant.
+	* lib/canonicalize-lgpl.c (FACCESSAT_NEVER_EOVERFLOWS) [_LIBC]:
+	Use __ASSUME_FACCESSAT2 to set FACCESSAT_NEVER_EOVERFLOWS
+	(__faccessat) [!_LIBC]: Define.
+	(realpath_stk): Use dir_suffix now.
+	* lib/canonicalize.c (canonicalize_filename_mode_stk):
+	If logical, don't check each component's existence; just check
+	at the end, as that's enough.
+	* m4/canonicalize.m4 (gl_FUNC_CANONICALIZE_FILENAME_MODE)
+	(gl_CANONICALIZE_LGPL_SEPARATE):
+	Require gl_FUNC_FACCESSAT_EOVERFLOW,
+	gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK, and check for faccessat.
+	(gl_CANONICALIZE_LGPL_SEPARATE): Do not check for readlink,
+	as the code does not use HAVE_READLINK.
+	* modules/canonicalize, modules/canonicalize-lgpl (Files):
+	Add m4/faccessat.m4, m4/lstat.m4.
+	(Depends-on): Add fcntl-lh.
+
 	faccessat: work around F_OK EOVERFLOW bug
 	* doc/posix-functions/faccessat.texi: Mention the problem.
 	* lib/faccessat.c (FACCESSAT_NEVER_EOVERFLOWS): Default to 0.
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index 431dc5a3b..3d4218516 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
@@ -46,12 +47,19 @@ typedef ptrdiff_t idx_t;
 # define FILE_SYSTEM_PREFIX_LEN(name) 0
 # define IS_ABSOLUTE_FILE_NAME(name) ISSLASH(*(name))
 # define ISSLASH(c) ((c) == '/')
+# include <sysdep.h>
+# ifdef __ASSUME_FACCESSAT2
+#  define FACCESSAT_NEVER_EOVERFLOWS __ASSUME_FACCESSAT2
+# else
+#  define FACCESSAT_NEVER_EOVERFLOWS true
+# endif
 #else
 # define __canonicalize_file_name canonicalize_file_name
 # define __realpath realpath
 # include "idx.h"
 # include "pathmax.h"
 # include "filename.h"
+# define __faccessat faccessat
 # if defined _WIN32 && !defined __CYGWIN__
 #  define __getcwd _getcwd
 # elif HAVE_GETCWD
@@ -88,11 +96,30 @@ typedef ptrdiff_t idx_t;
 #endif
 
 #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
-# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT false
+#endif
+#ifndef FACCESSAT_NEVER_EOVERFLOWS
+# define FACCESSAT_NEVER_EOVERFLOWS false
 #endif
 
 #if !FUNC_REALPATH_WORKS || defined _LIBC
 
+/* Return true if FILE's existence can be shown, false (setting errno)
+   otherwise.  Follow symbolic links.  */
+static bool
+file_accessible (char const *file)
+{
+# if defined _LIBC || HAVE_FACCESSAT
+  int r = __faccessat (AT_FDCWD, file, F_OK, AT_EACCESS);
+# else
+  struct stat st;
+  int r = __stat (file, &st);
+# endif
+
+  return ((!FACCESSAT_NEVER_EOVERFLOWS && r < 0 && errno == EOVERFLOW)
+          || r == 0);
+}
+
 /* True if concatenating END as a suffix to a file name means that the
    code needs to check that the file name is that of a searchable
    directory, since the canonicalize_filename_mode_stk code won't
@@ -125,18 +152,26 @@ suffix_requires_dir_check (char const *end)
   return false;
 }
 
-/* Return true if DIR is a directory, false (setting errno) otherwise.
+/* Append this to a file name to test whether it is a searchable directory.
+   On POSIX platforms "/" suffices, but "/./" is sometimes needed on
+   macOS 10.13 <https://bugs.gnu.org/30350>, and should also work on
+   platforms like AIX 7.2 that need at least "/.".  */
+
+#if defined _LIBC || defined LSTAT_FOLLOWS_SLASHED_SYMLINK
+static char const dir_suffix[] = "/";
+#else
+static char const dir_suffix[] = "/./";
+#endif
+
+/* Return true if DIR is a searchable dir, false (setting errno) otherwise.
    DIREND points to the NUL byte at the end of the DIR string.
-   Store garbage into DIREND[0] and DIREND[1].  */
+   Store garbage into DIREND[0 .. strlen (dir_suffix)].  */
 
 static bool
 dir_check (char *dir, char *dirend)
 {
-  /* Append "/"; otherwise EOVERFLOW would be ambiguous.  */
-  strcpy (dirend, "/");
-
-  struct stat st;
-  return __stat (dir, &st) == 0 || errno == EOVERFLOW;
+  strcpy (dirend, dir_suffix);
+  return file_accessible (dir);
 }
 
 static idx_t
@@ -280,8 +315,8 @@ realpath_stk (const char *name, char *resolved,
           if (!ISSLASH (dest[-1]))
             *dest++ = '/';
 
-          enum { dir_check_room = sizeof "/" };
-          while (rname + rname_buf->length - dest < startlen + dir_check_room)
+          while (rname + rname_buf->length - dest
+                 < startlen + sizeof dir_suffix)
             {
               idx_t dest_offset = dest - rname;
               if (!scratch_buffer_grow_preserve (rname_buf))
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index 62777219a..2c88335bf 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -19,6 +19,7 @@
 #include "canonicalize.h"
 
 #include <errno.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdlib.h>
@@ -36,7 +37,10 @@
 #include "filename.h"
 
 #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
-# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT false
+#endif
+#ifndef FACCESSAT_NEVER_EOVERFLOWS
+# define FACCESSAT_NEVER_EOVERFLOWS false
 #endif
 
 #if ISSLASH ('\\')
@@ -45,6 +49,22 @@
 # define SLASHES "/"
 #endif
 
+/* Return true if FILE's existence can be shown, false (setting errno)
+   otherwise.  Follow symbolic links.  */
+static bool
+file_accessible (char const *file)
+{
+# if HAVE_FACCESSAT
+  int r = faccessat (AT_FDCWD, file, F_OK, AT_EACCESS);
+# else
+  struct stat st;
+  int r = stat (file, &st);
+# endif
+
+  return ((!FACCESSAT_NEVER_EOVERFLOWS && r < 0 && errno == EOVERFLOW)
+          || r == 0);
+}
+
 /* True if concatenating END as a suffix to a file name means that the
    code needs to check that the file name is that of a searchable
    directory, since the canonicalize_filename_mode_stk code won't
@@ -77,18 +97,26 @@ suffix_requires_dir_check (char const *end)
   return false;
 }
 
-/* Return true if DIR is a directory, false (setting errno) otherwise.
+/* Append this to a file name to test whether it is a searchable directory.
+   On POSIX platforms "/" suffices, but "/./" is sometimes needed on
+   macOS 10.13 <https://bugs.gnu.org/30350>, and should also work on
+   platforms like AIX 7.2 that need at least "/.".  */
+
+#ifdef LSTAT_FOLLOWS_SLASHED_SYMLINK
+static char const dir_suffix[] = "/";
+#else
+static char const dir_suffix[] = "/./";
+#endif
+
+/* Return true if DIR is a searchable dir, false (setting errno) otherwise.
    DIREND points to the NUL byte at the end of the DIR string.
-   Store garbage into DIREND[0] and DIREND[1].  */
+   Store garbage into DIREND[0 .. strlen (dir_suffix)].  */
 
 static bool
 dir_check (char *dir, char *dirend)
 {
-  /* Append "/"; otherwise EOVERFLOW would be ambiguous.  */
-  strcpy (dirend, "/");
-
-  struct stat st;
-  return stat (dir, &st) == 0 || errno == EOVERFLOW;
+  strcpy (dirend, dir_suffix);
+  return file_accessible (dir);
 }
 
 #if !((HAVE_CANONICALIZE_FILE_NAME && FUNC_REALPATH_WORKS)      \
@@ -293,8 +321,8 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
           if (!ISSLASH (dest[-1]))
             *dest++ = '/';
 
-          enum { dir_check_room = sizeof "/" };
-          while (rname + rname_buf->length - dest < startlen + dir_check_room)
+          while (rname + rname_buf->length - dest
+                 < startlen + sizeof dir_suffix)
             {
               idx_t dest_offset = dest - rname;
               if (!scratch_buffer_grow_preserve (rname_buf))
@@ -406,8 +434,9 @@ canonicalize_filename_mode_stk (const char *name, canonicalize_mode_t can_mode,
           else if (! (can_exist == CAN_MISSING
                       || (suffix_requires_dir_check (end)
                           ? dir_check (rname, dest)
-                          : ((logical && 0 <= readlink (rname, &discard, 1))
-                             || errno == EINVAL))
+                          : !logical
+                          ? errno == EINVAL
+                          : *end || file_accessible (rname))
                       || (can_exist == CAN_ALL_BUT_LAST
                           && errno == ENOENT
                           && !end[strspn (end, SLASHES)])))
diff --git a/m4/canonicalize.m4 b/m4/canonicalize.m4
index 14ea3e12f..c8da4dfcb 100644
--- a/m4/canonicalize.m4
+++ b/m4/canonicalize.m4
@@ -1,4 +1,4 @@
-# canonicalize.m4 serial 33
+# canonicalize.m4 serial 34
 
 dnl Copyright (C) 2003-2007, 2009-2020 Free Software Foundation, Inc.
 
@@ -11,7 +11,9 @@ dnl with or without modifications, as long as this notice is preserved.
 AC_DEFUN([gl_FUNC_CANONICALIZE_FILENAME_MODE],
 [
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
-  AC_CHECK_FUNCS_ONCE([canonicalize_file_name])
+  AC_REQUIRE([gl_FUNC_FACCESSAT_EOVERFLOW])
+  AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
+  AC_CHECK_FUNCS_ONCE([canonicalize_file_name faccessat])
   AC_REQUIRE([gl_DOUBLE_SLASH_ROOT])
   AC_REQUIRE([gl_FUNC_REALPATH_WORKS])
   if test $ac_cv_func_canonicalize_file_name = no; then
@@ -56,7 +58,9 @@ AC_DEFUN([gl_CANONICALIZE_LGPL],
 AC_DEFUN([gl_CANONICALIZE_LGPL_SEPARATE],
 [
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
-  AC_CHECK_FUNCS_ONCE([canonicalize_file_name readlink])
+  AC_REQUIRE([gl_FUNC_FACCESSAT_EOVERFLOW])
+  AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
+  AC_CHECK_FUNCS_ONCE([canonicalize_file_name faccessat])
 
   dnl On native Windows, we use _getcwd(), regardless whether getcwd() is
   dnl available through the linker option '-loldnames'.
diff --git a/modules/canonicalize b/modules/canonicalize
index e1b760704..29919bcdc 100644
--- a/modules/canonicalize
+++ b/modules/canonicalize
@@ -5,12 +5,15 @@ Files:
 lib/canonicalize.h
 lib/canonicalize.c
 m4/canonicalize.m4
+m4/faccessat.m4
+m4/lstat.m4
 
 Depends-on:
 attribute
 double-slash-root
 errno
 extensions
+fcntl-h
 file-set
 filename
 free-posix
diff --git a/modules/canonicalize-lgpl b/modules/canonicalize-lgpl
index e94a898bd..dfad9202f 100644
--- a/modules/canonicalize-lgpl
+++ b/modules/canonicalize-lgpl
@@ -5,6 +5,8 @@ Files:
 lib/canonicalize-lgpl.c
 m4/canonicalize.m4
 m4/double-slash-root.m4
+m4/faccessat.m4
+m4/lstat.m4
 
 Depends-on:
 extensions
@@ -12,6 +14,7 @@ stdlib
 nocrash
 double-slash-root [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 errno             [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
+fcntl-h           [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 filename          [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 free-posix        [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 idx               [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
-- 
2.27.0

>From 1f866f6707cb9cc0f5e76c1d88d39f629fac51c8 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 06/10] eloop-threshold: new module

* config/srclist.txt: Add a comment about it.
* lib/eloop-threshold.h, modules/eloop-threshold: New files.
---
 ChangeLog               |  4 ++
 config/srclist.txt      |  1 +
 lib/eloop-threshold.h   | 83 +++++++++++++++++++++++++++++++++++++++++
 modules/eloop-threshold | 22 +++++++++++
 4 files changed, 110 insertions(+)
 create mode 100644 lib/eloop-threshold.h
 create mode 100644 modules/eloop-threshold

diff --git a/ChangeLog b/ChangeLog
index 94427d748..b119360f3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	eloop-threshold: new module
+	* config/srclist.txt: Add a comment about it.
+	* lib/eloop-threshold.h, modules/eloop-threshold: New files.
+
 	canonicalize: prefer faccessat to stat
 	A proper faccessat doesn't have the EOVERFLOW problem, and can be
 	more efficient as it needn't gather data from the filesystem to
diff --git a/config/srclist.txt b/config/srclist.txt
index 02e843829..f33b1353f 100644
--- a/config/srclist.txt
+++ b/config/srclist.txt
@@ -60,6 +60,7 @@ $LIBCSRC posix/regex.c			lib
 #$LIBCSRC posix/regex_internal.c	lib
 #$LIBCSRC posix/regex_internal.h		lib
 $LIBCSRC posix/regexec.c		lib
+#$LIBCSRC sysdeps/generic/eloop-threshold.h	lib
 $LIBCSRC time/timegm.c			lib
 #$LIBCSRC time/mktime.c			lib
 $LIBCSRC time/mktime-internal.h		lib
diff --git a/lib/eloop-threshold.h b/lib/eloop-threshold.h
new file mode 100644
index 000000000..b61e4869d
--- /dev/null
+++ b/lib/eloop-threshold.h
@@ -0,0 +1,83 @@
+/* Threshold at which to diagnose ELOOP.  Generic version.
+   Copyright (C) 2012-2020 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library 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.
+
+   The GNU C Library 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 the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _ELOOP_THRESHOLD_H
+#define _ELOOP_THRESHOLD_H      1
+
+#include <limits.h>
+#ifdef _LIBC
+# include <sys/param.h>
+# define _GL_ATTRIBUTE_CONST __attribute__ ((const))
+#else
+# include <unistd.h>
+# include "minmax.h"
+# define __sysconf sysconf
+# if (!defined SYMLOOP_MAX \
+      && ! (defined _SC_SYMLOOP_MAX && defined _POSIX_SYMLOOP_MAX))
+#  define SYMLOOP_MAX 8
+# endif
+#endif
+
+/* POSIX specifies SYMLOOP_MAX as the "Maximum number of symbolic
+   links that can be reliably traversed in the resolution of a
+   pathname in the absence of a loop."  This makes it a minimum that
+   we should certainly accept.  But it leaves open the possibility
+   that more might sometimes work--just not "reliably".
+
+   For example, Linux implements a complex policy whereby there is a
+   small limit on the number of direct symlink traversals (a symlink
+   to a symlink to a symlink), but larger limit on the total number of
+   symlink traversals overall.  Hence the SYMLOOP_MAX number should be
+   the small one, but the limit library functions enforce on users
+   should be the larger one.
+
+   So, we use the larger of the reported SYMLOOP_MAX (if any) and our
+   own constant MIN_ELOOP_THRESHOLD, below.  This constant should be
+   large enough that it never rules out a file name and directory tree
+   that the underlying system (i.e. calls to 'open' et al) would
+   resolve successfully.  It should be small enough that actual loops
+   are detected without a huge number of iterations.  */
+
+#ifndef MIN_ELOOP_THRESHOLD
+# define MIN_ELOOP_THRESHOLD    40
+#endif
+
+/* Return the maximum number of symlink traversals to permit
+   before diagnosing ELOOP.  */
+static inline unsigned int _GL_ATTRIBUTE_CONST
+__eloop_threshold (void)
+{
+#ifdef SYMLOOP_MAX
+  const int symloop_max = SYMLOOP_MAX;
+#else
+  /* The function is marked 'const' even though we use memory and
+     call a function, because sysconf is required to return the
+     same value in every call and so it must always be safe to
+     call __eloop_threshold exactly once and reuse the value.  */
+  static long int sysconf_symloop_max;
+  if (sysconf_symloop_max == 0)
+    sysconf_symloop_max = __sysconf (_SC_SYMLOOP_MAX);
+  const unsigned int symloop_max = (sysconf_symloop_max <= 0
+                                    ? _POSIX_SYMLOOP_MAX
+                                    : sysconf_symloop_max);
+#endif
+
+  return MAX (symloop_max, MIN_ELOOP_THRESHOLD);
+}
+
+#endif  /* eloop-threshold.h */
diff --git a/modules/eloop-threshold b/modules/eloop-threshold
new file mode 100644
index 000000000..60433e530
--- /dev/null
+++ b/modules/eloop-threshold
@@ -0,0 +1,22 @@
+Description:
+Threshold for symbolic link loops and ELOOP
+
+Files:
+lib/eloop-threshold.h
+
+Depends-on:
+minmax
+unistd
+
+configure.ac:
+
+Makefile.am:
+
+Include:
+<eloop-threshold.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all, glibc
-- 
2.27.0

>From a49ce1bbe6013e82491f13612310d571a9f9139d Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 07/10] canonicalize-lgpl: use eloop-threshold

* lib/canonicalize-lgpl.c [!_LIBC]: Include eloop-threshold.h.
(MAXSYMLINKS, __eloop_threshold): Remove.
* modules/canonicalize-lgpl (Depends-on): Add eloop-threshold.
---
 ChangeLog                 |  5 +++++
 lib/canonicalize-lgpl.c   | 10 +---------
 modules/canonicalize-lgpl |  1 +
 3 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b119360f3..edca84874 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	canonicalize-lgpl: use eloop-threshold
+	* lib/canonicalize-lgpl.c [!_LIBC]: Include eloop-threshold.h.
+	(MAXSYMLINKS, __eloop_threshold): Remove.
+	* modules/canonicalize-lgpl (Depends-on): Add eloop-threshold.
+
 	eloop-threshold: new module
 	* config/srclist.txt: Add a comment about it.
 	* lib/eloop-threshold.h, modules/eloop-threshold: New files.
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index 3d4218516..c6cb50630 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -37,10 +37,10 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <eloop-threshold.h>
 #include <scratch_buffer.h>
 
 #ifdef _LIBC
-# include <eloop-threshold.h>
 # include <shlib-compat.h>
 typedef ptrdiff_t idx_t;
 # define IDX_MAX PTRDIFF_MAX
@@ -85,14 +85,6 @@ typedef ptrdiff_t idx_t;
 # define __rawmemchr rawmemchr
 # define __readlink readlink
 # define __stat stat
-# ifndef MAXSYMLINKS
-#  ifdef SYMLOOP_MAX
-#   define MAXSYMLINKS SYMLOOP_MAX
-#  else
-#   define MAXSYMLINKS 20
-#  endif
-# endif
-# define __eloop_threshold() MAXSYMLINKS
 #endif
 
 #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
diff --git a/modules/canonicalize-lgpl b/modules/canonicalize-lgpl
index dfad9202f..b5f3e7f69 100644
--- a/modules/canonicalize-lgpl
+++ b/modules/canonicalize-lgpl
@@ -13,6 +13,7 @@ extensions
 stdlib
 nocrash
 double-slash-root [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
+eloop-threshold   [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 errno             [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 fcntl-h           [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
 filename          [test $HAVE_CANONICALIZE_FILE_NAME = 0 || test $REPLACE_CANONICALIZE_FILE_NAME = 1]
-- 
2.27.0

>From b619b08ca4d78940e769b5df57768d43cf081f33 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:48 -0800
Subject: [PATCH 08/10] linkat: use eloop-threshold

* lib/linkat.c: Include eloop-threshold.h.
Do not include sys/param.h.
(MAXSYMLINKS): Remove.
(link_follow, linkat_follow): Use __eloop_threshold instead
of MAXSYMLINKS.
* m4/linkat.m4 (gl_FUNC_LINKAT): Omit sys/param.h check.
* modules/linkat (Depends-on): Add eloop-threshold.  Sort.
---
 ChangeLog      |  9 +++++++++
 lib/linkat.c   | 16 +++-------------
 m4/linkat.m4   |  3 +--
 modules/linkat | 11 ++++++-----
 4 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index edca84874..7b1bcd8ff 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	linkat: use eloop-threshold
+	* lib/linkat.c: Include eloop-threshold.h.
+	Do not include sys/param.h.
+	(MAXSYMLINKS): Remove.
+	(link_follow, linkat_follow): Use __eloop_threshold instead
+	of MAXSYMLINKS.
+	* m4/linkat.m4 (gl_FUNC_LINKAT): Omit sys/param.h check.
+	* modules/linkat (Depends-on): Add eloop-threshold.  Sort.
+
 	canonicalize-lgpl: use eloop-threshold
 	* lib/canonicalize-lgpl.c [!_LIBC]: Include eloop-threshold.h.
 	(MAXSYMLINKS, __eloop_threshold): Remove.
diff --git a/lib/linkat.c b/lib/linkat.c
index 13313e9d1..6fc963537 100644
--- a/lib/linkat.c
+++ b/lib/linkat.c
@@ -29,20 +29,10 @@
 
 #include "areadlink.h"
 #include "dirname.h"
+#include "eloop-threshold.h"
 #include "filenamecat.h"
 #include "openat-priv.h"
 
-#if HAVE_SYS_PARAM_H
-# include <sys/param.h>
-#endif
-#ifndef MAXSYMLINKS
-# ifdef SYMLOOP_MAX
-#  define MAXSYMLINKS SYMLOOP_MAX
-# else
-#  define MAXSYMLINKS 20
-# endif
-#endif
-
 #if !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP
 
 /* Create a link.  If FILE1 is a symlink, either create a hardlink to
@@ -105,7 +95,7 @@ link_follow (char const *file1, char const *file2)
   char *name = (char *) file1;
   char *target;
   int result;
-  int i = MAXSYMLINKS;
+  int i = __eloop_threshold ();
 
   /* Using realpath or canonicalize_file_name is too heavy-handed: we
      don't need an absolute name, and we don't need to resolve
@@ -231,7 +221,7 @@ linkat_follow (int fd1, char const *file1, int fd2, char const *file2)
   char *name = (char *) file1;
   char *target;
   int result;
-  int i = MAXSYMLINKS;
+  int i = __eloop_threshold ();
 
   /* There is no realpathat.  */
   while (i-- && (target = areadlinkat (fd1, name)))
diff --git a/m4/linkat.m4 b/m4/linkat.m4
index e9b3d8266..a4e61c332 100644
--- a/m4/linkat.m4
+++ b/m4/linkat.m4
@@ -1,4 +1,4 @@
-# serial 13
+# serial 14
 # See if we need to provide linkat replacement.
 
 dnl Copyright (C) 2009-2020 Free Software Foundation, Inc.
@@ -16,7 +16,6 @@ AC_DEFUN([gl_FUNC_LINKAT],
   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
   AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
   AC_CHECK_FUNCS_ONCE([linkat symlink])
-  AC_CHECK_HEADERS_ONCE([sys/param.h])
   if test $ac_cv_func_linkat = no; then
     HAVE_LINKAT=0
   else
diff --git a/modules/linkat b/modules/linkat
index e6f70ffa4..1fb4f8ff4 100644
--- a/modules/linkat
+++ b/modules/linkat
@@ -9,20 +9,21 @@ m4/linkat.m4
 Depends-on:
 unistd
 extensions
+areadlink        [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+at-internal      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 dirname-lgpl     [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+eloop-threshold  [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 errno            [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 fcntl-h          [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 filenamecat-lgpl [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-link-follow      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-areadlink        [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-at-internal      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 filename         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 fstat            [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 getcwd-lgpl      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-openat-h         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-openat-die       [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+link-follow      [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 link             [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 lstat            [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+openat-die       [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
+openat-h         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 same-inode       [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 save-cwd         [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
 stat             [test $HAVE_LINKAT = 0 || test $REPLACE_LINKAT = 1]
-- 
2.27.0

>From 68308ac97522f2239e169b416b3a8c8168d626ac Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 11:38:49 -0800
Subject: [PATCH 09/10] * canonicalize-lgpl.c: Space after paren.

---
 lib/canonicalize-lgpl.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index c6cb50630..d6dd18dcf 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -45,7 +45,7 @@
 typedef ptrdiff_t idx_t;
 # define IDX_MAX PTRDIFF_MAX
 # define FILE_SYSTEM_PREFIX_LEN(name) 0
-# define IS_ABSOLUTE_FILE_NAME(name) ISSLASH(*(name))
+# define IS_ABSOLUTE_FILE_NAME(name) ISSLASH (*(name))
 # define ISSLASH(c) ((c) == '/')
 # include <sysdep.h>
 # ifdef __ASSUME_FACCESSAT2
-- 
2.27.0

>From db9f824d01287d7b78330c7d34bc291fa6b9ccad Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Thu, 24 Dec 2020 12:06:26 -0800
Subject: [PATCH 10/10] careadlinkat: improve warning line number

This propagates an idea first used in canonicalize-lgpl.
* lib/careadlinkat.c (GCC_BOGUS_WRETURN_LOCAL_ADDR):
New macro.
(careadlinkat): Use it.
---
 ChangeLog          |  6 ++++++
 lib/careadlinkat.c | 12 ++++++------
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7b1bcd8ff..5b05bb310 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2020-12-24  Paul Eggert  <egg...@cs.ucla.edu>
 
+	careadlinkat: improve warning line number
+	This propagates an idea first used in canonicalize-lgpl.
+	* lib/careadlinkat.c (GCC_BOGUS_WRETURN_LOCAL_ADDR):
+	New macro.
+	(careadlinkat): Use it.
+
 	linkat: use eloop-threshold
 	* lib/linkat.c: Include eloop-threshold.h.
 	Do not include sys/param.h.
diff --git a/lib/careadlinkat.c b/lib/careadlinkat.c
index 26fe84df5..6aaa712b9 100644
--- a/lib/careadlinkat.c
+++ b/lib/careadlinkat.c
@@ -55,8 +55,7 @@ enum { STACK_BUF_SIZE = 1024 };
 # if defined GCC_LINT || defined lint
 __attribute__ ((__noinline__))
 # elif __OPTIMIZE__ && !__NO_INLINE__
-#  warning "GCC might issue a bogus -Wreturn-local-addr warning here."
-#  warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+#  define GCC_BOGUS_WRETURN_LOCAL_ADDR
 # endif
 #endif
 static char *
@@ -180,10 +179,11 @@ careadlinkat (int fd, char const *filename,
   /* Allocate the initial buffer on the stack.  This way, in the
      common case of a symlink of small size, we get away with a
      single small malloc instead of a big malloc followed by a
-     shrinking realloc.
-
-     If GCC -Wreturn-local-addr warns about this buffer, the warning
-     is bogus; see readlink_stk.  */
+     shrinking realloc.  */
+  #ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
+   #warning "GCC might issue a bogus -Wreturn-local-addr warning here."
+   #warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+  #endif
   char stack_buf[STACK_BUF_SIZE];
   return readlink_stk (fd, filename, buffer, buffer_size, alloc,
                        preadlinkat, stack_buf);
-- 
2.27.0

Reply via email to