On 2026-08-09 07:11, Bruno Haible wrote:
The scenario you describe can only occur on Hurd/m68k

I thought so too, but more recently I discovered that the sysconf call can also 
return -1 on GNU/Linux, OpenBSD, Solaris, and probably other OSes. GNU/Linux 
still runs on the Motorola 68000 and 68010 architectures if you configure Linux 
with CONFIG_MMU=n. It even runs on the 68008![1] So the ice is getting a little 
thin here.

But we're in luck: last year glibc removed the __eloop_threshold function, 
presumably because there's not much point to it: nobody uses symlink loop 
counts greater than 40 in practice, so a compile-time limit of 40 conforms to 
POSIX and is simpler to maintain. So I fixed the 'const' attribute issue by 
following glibc's lead and removing the problematic __eloop_threshold function, 
resulting in the attached patch which I installed into Gnulib.

[1]: 
https://hackaday.io/project/183861-mackerel-68k-linux-sbcs/log/248289-mainline-linux-71-on-a-motorola-68008-the-slowest-still-supported-cpu
From bb4e43616575681417f7bae48792651fc6ce489f Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Mon, 10 Aug 2026 13:08:08 -0700
Subject: [PATCH] canonicalize-lgpl: sync from glibc
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Merge into lib/canonicalize-lgpl.c the changes of glibc
stdlib/canonicalize.c through glibc commit
b589bd672c529cf264dc6dfdfa11f73c7e4e1666 dated today.
The result should be copiable back to glibc byte-for-byte.
The original motivation for this merge was to make
eloop-threshold.h thread-safe on all platforms.
It turned out that glibc has already done this, trivially,
by having the include file define a constant 40 rather than a function.
This conforms to POSIX on all glibc targets, and it should also
work on all Gnulib targets that I know of,
as sysconf (_SC_SYMLOOP_MAX) <= 40 on all known platforms.
POSIX allows us to follow more symlink expansions than
any nonnegative number returned by sysconf (_SC_SYMLOOP_MAX),
and if that number is negative POSIX requires only to expand
at least 8 symlinks so the glibc value of 40 is good then too.
* NEWS: Mention the incompatible change.
* config/srclist.txt: Add lib/min-eloop-threshold.h.
* lib/canonicalize-lgpl.c [_LIBC]: Include <stdbool.h>.
(__rawmemchr): Remove.  Replace its use with strchr.
* lib/min-eloop-threshold.h: Sync byte-for-byte from gnulibc,
renaming the file from lib/eloop-threshold.h.  All uses changed.
This entails the following changes:
(_MIN_ELOOP_THRESHOLD_H): Rename from _ELOOP_THRESHOLD_H.
All uses changed.
Don’t #error out if config.h is not already included.
Don’t include <limits.h>.
[_LIBC]: Don’t include <sys/param.h>.
(_GL_ATTRIBUTE_CONST) [_LIBC]: Remove.
[!_LIBC]: Don’t include <unistd.h>, "minmax.h".
(__sysconf, SYMLOOP_MAX) [!_LIBC]: Remove.
(MIN_ELOOP_THRESHOLD): Define unconditionally.
(__eloop_threshold): Remove.  All uses replaced by MIN_ELOOP_THRESHOLD.
The new expression is of type int not unsigned, but that’s better
for callers anyway.
* lib/openat2.c (do_openat2): Symlink count is now int, not long int.
* modules/eloop-threshold (Files, Include): Adjust to file name change.
(Depends-on): Remove minmax, unistd-h.
---
 ChangeLog                 | 41 ++++++++++++++++++
 NEWS                      |  4 ++
 config/srclist.txt        |  2 +-
 lib/canonicalize-lgpl.c   |  8 ++--
 lib/eloop-threshold.h     | 88 ---------------------------------------
 lib/linkat.c              |  6 +--
 lib/min-eloop-threshold.h | 42 +++++++++++++++++++
 lib/openat2.c             |  4 +-
 modules/eloop-threshold   |  6 +--
 9 files changed, 99 insertions(+), 102 deletions(-)
 delete mode 100644 lib/eloop-threshold.h
 create mode 100644 lib/min-eloop-threshold.h

diff --git a/ChangeLog b/ChangeLog
index fade982eb5..e258e18d2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,44 @@
+2026-08-10  Paul Eggert  <[email protected]>
+
+	canonicalize-lgpl: sync from glibc
+	Merge into lib/canonicalize-lgpl.c the changes of glibc
+	stdlib/canonicalize.c through glibc commit
+	b589bd672c529cf264dc6dfdfa11f73c7e4e1666 dated today.
+	The result should be copiable back to glibc byte-for-byte.
+	The original motivation for this merge was to make
+	eloop-threshold.h thread-safe on all platforms.
+	It turned out that glibc has already done this, trivially,
+	by having the include file define a constant 40 rather than a function.
+	This conforms to POSIX on all glibc targets, and it should also
+	work on all Gnulib targets that I know of,
+	as sysconf (_SC_SYMLOOP_MAX) <= 40 on all known platforms.
+	POSIX allows us to follow more symlink expansions than
+	any nonnegative number returned by sysconf (_SC_SYMLOOP_MAX),
+	and if that number is negative POSIX requires only to expand
+	at least 8 symlinks so the glibc value of 40 is good then too.
+	* NEWS: Mention the incompatible change.
+	* config/srclist.txt: Add lib/min-eloop-threshold.h.
+	* lib/canonicalize-lgpl.c [_LIBC]: Include <stdbool.h>.
+	(__rawmemchr): Remove.  Replace its use with strchr.
+	* lib/min-eloop-threshold.h: Sync byte-for-byte from gnulibc,
+	renaming the file from lib/eloop-threshold.h.  All uses changed.
+	This entails the following changes:
+	(_MIN_ELOOP_THRESHOLD_H): Rename from _ELOOP_THRESHOLD_H.
+	All uses changed.
+	Don’t #error out if config.h is not already included.
+	Don’t include <limits.h>.
+	[_LIBC]: Don’t include <sys/param.h>.
+	(_GL_ATTRIBUTE_CONST) [_LIBC]: Remove.
+	[!_LIBC]: Don’t include <unistd.h>, "minmax.h".
+	(__sysconf, SYMLOOP_MAX) [!_LIBC]: Remove.
+	(MIN_ELOOP_THRESHOLD): Define unconditionally.
+	(__eloop_threshold): Remove.  All uses replaced by MIN_ELOOP_THRESHOLD.
+	The new expression is of type int not unsigned, but that’s better
+	for callers anyway.
+	* lib/openat2.c (do_openat2): Symlink count is now int, not long int.
+	* modules/eloop-threshold (Files, Include): Adjust to file name change.
+	(Depends-on): Remove minmax, unistd-h.
+
 2026-08-09  Paul Eggert  <[email protected]>
 
 	hamt: port to non-_Atomic C platforms
diff --git a/NEWS b/NEWS
index db7776ee78..28a5869ee8 100644
--- a/NEWS
+++ b/NEWS
@@ -78,6 +78,10 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2026-08-10  eloop-threshold Now include <min-eloop-threshold.h> and use
+                            MIN_ELOOP_THRESHOLD rather than including
+                            <eloop-threshold.h> and using __eloop_threshold ().
+
 2026-06-26  bitrotate       This module is deprecated.  Use stdc_rotate_left
                             or stdc_rotate_right instead.
 
diff --git a/config/srclist.txt b/config/srclist.txt
index c9e6b4336e..a19a8b78ba 100644
--- a/config/srclist.txt
+++ b/config/srclist.txt
@@ -89,7 +89,7 @@ $LIBCSRC stdlib/tst-stdc_leading_ones.c		tests/from-glibc
 $LIBCSRC stdlib/tst-stdc_leading_zeros.c	tests/from-glibc
 $LIBCSRC stdlib/tst-stdc_trailing_ones.c	tests/from-glibc
 $LIBCSRC stdlib/tst-stdc_trailing_zeros.c	tests/from-glibc
-#$LIBCSRC sysdeps/generic/eloop-threshold.h	lib
+$LIBCSRC sysdeps/generic/min-eloop-threshold.h	lib
 #$LIBCSRC time/timegm.c			lib
 #$LIBCSRC time/mktime.c			lib
 #$LIBCSRC time/mktime-internal.h	lib
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index a87d46a433..92c96243c2 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -34,7 +34,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include <eloop-threshold.h>
+#include <min-eloop-threshold.h>
 #include <filename.h>
 #include <idx.h>
 #include <intprops.h>
@@ -42,6 +42,7 @@
 
 #ifdef _LIBC
 # include <shlib-compat.h>
+# include <stdbool.h>
 # define GCC_LINT 1
 # define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
 #else
@@ -72,7 +73,6 @@
 # endif
 # define __mempcpy mempcpy
 # define __pathconf pathconf
-# define __rawmemchr rawmemchr
 # define __readlink readlink
 # if IN_RELOCWRAPPER
     /* When building the relocatable program wrapper, use the system's memmove
@@ -233,7 +233,7 @@ realpath_stk (const char *name, char *resolved, struct realpath_bufs *bufs)
             return NULL;
           rname = bufs->rname.data;
         }
-      dest = __rawmemchr (rname, '\0');
+      dest = strchr (rname, '\0');
       start = name;
       prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
     }
@@ -317,7 +317,7 @@ realpath_stk (const char *name, char *resolved, struct realpath_bufs *bufs)
             }
           if (0 <= n)
             {
-              if (++num_links > __eloop_threshold ())
+              if (++num_links > MIN_ELOOP_THRESHOLD)
                 {
                   __set_errno (ELOOP);
                   goto error;
diff --git a/lib/eloop-threshold.h b/lib/eloop-threshold.h
deleted file mode 100644
index 3e33a24708..0000000000
--- a/lib/eloop-threshold.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Threshold at which to diagnose ELOOP.  Generic version.
-   Copyright (C) 2012-2026 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
-
-/* This file uses _GL_ATTRIBUTE_CONST.  */
-#if !_LIBC && !_GL_CONFIG_H_INCLUDED
- #error "Please include config.h first."
-#endif
-
-#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/lib/linkat.c b/lib/linkat.c
index 0225e41d4a..8fc105fe99 100644
--- a/lib/linkat.c
+++ b/lib/linkat.c
@@ -29,8 +29,8 @@
 
 #include "areadlink.h"
 #include "dirname.h"
-#include "eloop-threshold.h"
 #include "filenamecat.h"
+#include "min-eloop-threshold.h"
 #include "openat-priv.h"
 
 #if !HAVE_LINKAT || LINKAT_SYMLINK_NOTSUP
@@ -92,7 +92,7 @@ link_follow (char const *file1, char const *file2)
 {
   char *name = (char *) file1;
   char *target;
-  int i = __eloop_threshold ();
+  int i = MIN_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
@@ -209,7 +209,7 @@ linkat_follow (int fd1, char const *file1, int fd2, char const *file2)
 {
   char *name = (char *) file1;
   char *target;
-  int i = __eloop_threshold ();
+  int i = MIN_ELOOP_THRESHOLD;
 
   /* There is no realpathat.  */
   while (i-- && (target = areadlinkat (fd1, name)))
diff --git a/lib/min-eloop-threshold.h b/lib/min-eloop-threshold.h
new file mode 100644
index 0000000000..26b0795a64
--- /dev/null
+++ b/lib/min-eloop-threshold.h
@@ -0,0 +1,42 @@
+/* Minimum threshold at which to diagnose ELOOP.  Generic version.
+   Copyright (C) 2012-2026 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 _MIN_ELOOP_THRESHOLD_H
+#define _MIN_ELOOP_THRESHOLD_H      1
+
+/* 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.
+
+   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.  */
+
+#define MIN_ELOOP_THRESHOLD    40
+
+#endif
diff --git a/lib/openat2.c b/lib/openat2.c
index ff8ce30263..e860252654 100644
--- a/lib/openat2.c
+++ b/lib/openat2.c
@@ -20,10 +20,10 @@
 
 #include <fcntl.h>
 
-#include "eloop-threshold.h"
 #include "filename.h"
 #include "ialloc.h"
 #include "idx.h"
+#include "min-eloop-threshold.h"
 #include "verify.h"
 
 #include <errno.h>
@@ -228,7 +228,7 @@ do_openat2 (int *fd, char const *filename,
   dev_t const UNKNOWN_DEV = -1;
   dev_t ddev = UNKNOWN_DEV;
 
-  long int maxlinks = resolve & RESOLVE_NO_SYMLINKS ? 0 : __eloop_threshold ();
+  int maxlinks = resolve & RESOLVE_NO_SYMLINKS ? 0 : MIN_ELOOP_THRESHOLD;
 
   int dfd = *fd;
 
diff --git a/modules/eloop-threshold b/modules/eloop-threshold
index 3cb5834cc6..9a95eb54ad 100644
--- a/modules/eloop-threshold
+++ b/modules/eloop-threshold
@@ -2,18 +2,16 @@ Description:
 Threshold for symbolic link loops and ELOOP
 
 Files:
-lib/eloop-threshold.h
+lib/min-eloop-threshold.h
 
 Depends-on:
-minmax
-unistd-h
 
 configure.ac:
 
 Makefile.am:
 
 Include:
-<eloop-threshold.h>
+<min-eloop-threshold.h>
 
 License:
 LGPLv2+
-- 
2.55.0

Reply via email to