From b5891a57b586ef6ff78bb752d62915b78cd58d7e Mon Sep 17 00:00:00 2001
From: Zack Weinberg <zackw@panix.com>
Date: Sun, 2 Apr 2023 15:07:58 -0400
Subject: [PATCH 12/29] Overhaul AC_TYPE_GETGROUPS and AC_FUNC_GETGROUPS.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

AC_TYPE_GETGROUPS is the last remaining use of AC_EGREP_HEADER in
stock Autoconf macros.  It uses it only when cross compiling, as a
fallback from an AC_RUN_IFELSE check, testing for a bug in system
headers from the late 1980s or early 1990s, where gid_t *existed*
but the second argument to getgroups needed to be an array of int,
and this didn’t cause a compile error (i.e. the system headers
declare getgroups with no prototype or an incorrect prototype).
AC_FUNC_GETGROUPS also uses AC_RUN_IFELSE to test for obscure
problems specific to long-obsolete Unixes.

The downsides of AC_RUN_IFELSE and AC_EGREP_HEADER seem more severe
than the chances of someone compiling a current-generation program,
that uses getgroups, on an OS old enough to have one of the really
nasty bugs.  Accordingly, this patch changes AC_FUNC_GETGROUPS to use
a host_os-based *blacklist* both in native and cross compilation.
This is limited to the two host_os values for which either our old
code, or Gnulib, documented a serious bug: ultrix* and nextstep*.
Currently it does not try to pin down the exact version ranges subject
to the bugs — that would require research by someone with access to
the full history of these OSes.

An incorrect guess by this blacklist can be overridden by setting
ac_cv_func_getgroups_works in config.site.  AC_TYPE_GETGROUPS, for its
part, now does a series of regular old AC_COMPILE_IFELSE checks to
probe the prototype of getgroups, and considers that good enough.

While I was in there I noticed that AC_FUNC_GETGROUPS does not
AC_SUBST a documented output variable, and that the name of this
variable is misspelled in the manual.

* lib/autoconf/functions.m4 (AC_FUNC_GETGROUPS): Use AC_SEARCH_LIBS
  to probe for getgroups.  Use an AC_CANONICAL_HOST-based blacklist
  for bug detection, not AC_RUN_IFELSE.  AC_SUBST the GETGROUPS_LIB
  output variable.
* lib/autoconf/types.m4 (AC_TYPE_GETGROUPS): Check only the prototype
  of getgroups, using AC_COMPILE_IFELSE; do not use either AC_RUN_IFELSE
  or AC_EGREP_HEADER.
* doc/autoconf.texi: Update to match. Correct misspelling of
  GETGROUPS_LIB.
* tests.local.at (_AT_CHECK_ENV): Allow GETGROUPS_LIB output variable.

Upstream-Status: Backport
Signed-off-by: Khem Raj <raj.khem@gmail.com>
---
 NEWS                      | 11 +++++
 doc/autoconf.texi         | 25 ++++++----
 lib/autoconf/functions.m4 | 61 ++++++++++++------------
 lib/autoconf/types.m4     | 97 ++++++++++++++++++++++++---------------
 tests/local.at            |  2 +-
 5 files changed, 120 insertions(+), 76 deletions(-)

diff --git a/NEWS b/NEWS
index 53c57ff59..8e4ecc1bf 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,17 @@ GNU Autoconf NEWS - User visible changes.
   This matters only for uses that, contrary to the documentation
   and despite warnings, use m4_divert with numbered diversions.
 
+*** AC_FUNC_GETGROUPS and AC_TYPE_GETGROUPS no longer run test programs.
+  These macros were testing for OS bugs that we believe are at least
+  twenty years in the past.  Most operating systems are now trusted to
+  provide an accurate prototype for getgroups in unistd.h, and to
+  implement it as specified in POSIX.
+
+  AC_FUNC_GETGROUPS still includes a short blacklist of OSes with
+  known, severe bugs in getgroups.  It can be overridden using
+  config.site.  If you encounter a mistake in this blacklist
+  please report it to bug-autoconf.
+
 ** New features
 
 *** New macro AC_SYS_YEAR2038.
diff --git a/doc/autoconf.texi b/doc/autoconf.texi
index 037c8055f..5d5f613e6 100644
--- a/doc/autoconf.texi
+++ b/doc/autoconf.texi
@@ -5257,17 +5257,26 @@ and also contains workarounds for other portability problems of
 @defmac AC_FUNC_GETGROUPS
 @acindex{FUNC_GETGROUPS}
 @cvindex HAVE_GETGROUPS
-@ovindex GETGROUPS_LIBS
+@ovindex GETGROUPS_LIB
 @c @fuindex getgroups
 @prindex @code{getgroups}
 @caindex func_getgroups_works
-If the @code{getgroups} function is available and works,
-define @code{HAVE_GETGROUPS}.  Set @code{GETGROUPS_LIBS} to any libraries
-needed to get that function.  This macro runs @code{AC_TYPE_GETGROUPS}.
-
-This macro is obsolescent. New programs need not use this macro.  But
-they may want to use the Gnulib module @code{getgroups}, which provides
-workarounds to other portability problems of this function.
+Perform all the checks performed by @code{AC_TYPE_GETGROUPS}
+(@pxref{AC_TYPE_GETGROUPS}).
+Then, if the @code{getgroups} function is available
+and known to work correctly, define @code{HAVE_GETGROUPS}.
+Set the output variable @code{GETGROUPS_LIB} to any libraries
+needed to get that function.
+
+This macro relies on a list of systems with known, serious bugs in
+@code{getgroups}.  If this list mis-identifies your system's
+@code{getgroups} as buggy, or as not buggy, you can override it by
+setting the cache variable @code{ac_cv_func_getgroups_works} in a
+@file{config.site} file (@pxref{Site Defaults}).  Please also report the
+error to @email{bug-autoconf@@gnu.org, the Autoconf Bugs mailing list}.
+
+The Gnulib module @code{getgroups} provides workarounds for additional,
+less severe portability problems with this function.
 @end defmac
 
 @anchor{AC_FUNC_GETLOADAVG}
diff --git a/lib/autoconf/functions.m4 b/lib/autoconf/functions.m4
index 655d6ba8f..74512e97d 100644
--- a/lib/autoconf/functions.m4
+++ b/lib/autoconf/functions.m4
@@ -698,47 +698,46 @@ AS_IF([test "$ac_cv_func_fseeko_ftello" = "need _LARGEFILE_SOURCE"],
 # When cross-compiling, assume getgroups is broken.
 AN_FUNCTION([getgroups], [AC_FUNC_GETGROUPS])
 AC_DEFUN([AC_FUNC_GETGROUPS],
-[AC_REQUIRE([AC_TYPE_GETGROUPS])dnl
-AC_REQUIRE([AC_TYPE_SIZE_T])dnl
-AC_REQUIRE([AC_CANONICAL_HOST])dnl for cross-compiles
-AC_CHECK_FUNC(getgroups)
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_TYPE_GETGROUPS])dnl
 
-# If we don't yet have getgroups, see if it's in -lbsd.
+# On older systems getgroups might be in -lbsd.
 # This is reported to be necessary on an ITOS 3000WS running SEIUX 3.1.
 ac_save_LIBS=$LIBS
-if test $ac_cv_func_getgroups = no; then
-  AC_CHECK_LIB(bsd, getgroups, [GETGROUPS_LIB=-lbsd])
-fi
-
-# Run the program to test the functionality of the system-supplied
-# getgroups function only if there is such a function.
+LIBS=
+GETGROUPS_LIB=
+AC_SEARCH_LIBS([getgroups], [bsd],
+  [test "$ac_res" = "none required" || GETGROUPS_LIB="$ac_res"
+   ac_cv_func_getgroups=yes],
+  [ac_cv_func_getgroups=no])
+LIBS=$ac_save_LIBS
+AC_SUBST([GETGROUPS_LIB])
+
+# Known severe bugs in getgroups on particular systems.
+#  - On Ultrix 4.3 and NextSTEP 3.2, getgroups (0, 0) is reported to
+#    fail, rather than returning the number of supplementary groups as
+#    it ought to.  We do not know the exact range of releases affected
+#    in either case.
+# We currently reject all versions of the systems with known bugs, and
+# no other systems.  Please send corrections to bug-autoconf@gnu.org.
 if test $ac_cv_func_getgroups = yes; then
+  # This AC_CACHE_CHECK exists so that one may override an incorrect
+  # guess by setting ac_cv_func_getgroups_works in a config.site file.
   AC_CACHE_CHECK([for working getgroups], ac_cv_func_getgroups_works,
-   [AC_RUN_IFELSE([AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
-      [[/* On Ultrix 4.3, getgroups (0, 0) always fails.  */
-       return getgroups (0, 0) == -1;]])],
-		  [ac_cv_func_getgroups_works=yes],
-		  [ac_cv_func_getgroups_works=no],
-		  [case "$host_os" in # ((
-			     # Guess yes on glibc systems.
-		     *-gnu*) ac_cv_func_getgroups_works="guessing yes" ;;
-			     # If we don't know, assume the worst.
-		     *)      ac_cv_func_getgroups_works="guessing no" ;;
-		   esac])
-   ])
+   [AS_CASE([$host_os],
+     [ultrix* | nextstep*],
+      [ac_cv_func_getgroups_works=no # getgroups(0,0) fails
+],
+      [ac_cv_func_getgroups_works=yes])])
 else
   ac_cv_func_getgroups_works=no
 fi
-case "$ac_cv_func_getgroups_works" in
-  *yes)
-    AC_DEFINE(HAVE_GETGROUPS, 1,
-	      [Define to 1 if your system has a working 'getgroups' function.])
-    ;;
-esac
-LIBS=$ac_save_LIBS
+if test $ac_cv_func_getgroups_works = yes; then
+  AC_DEFINE(HAVE_GETGROUPS, 1,
+            [Define to 1 if your system has a working 'getgroups' function.])
+fi
 ])# AC_FUNC_GETGROUPS
 
-
 # _AC_LIBOBJ_GETLOADAVG
 # ---------------------
 # Set up the AC_LIBOBJ replacement of 'getloadavg'.
diff --git a/lib/autoconf/types.m4 b/lib/autoconf/types.m4
index ef2456135..af3872b2f 100644
--- a/lib/autoconf/types.m4
+++ b/lib/autoconf/types.m4
@@ -258,44 +258,69 @@ AN_IDENTIFIER([ptrdiff_t], [AC_CHECK_TYPES])
 # AC_TYPE_GETGROUPS
 # -----------------
 AC_DEFUN([AC_TYPE_GETGROUPS],
+dnl We now unconditionally assume that if <unistd.h> has a prototype for
+dnl getgroups, it is accurate; and that if <unistd.h> does _not_ declare
+dnl getgroups with a prototype, the second argument is an array of int.
+dnl (Older versions of Autoconf made these assumptions only when cross
+dnl compiling.)  See AC_FUNC_GETGROUPS, over in functions.m4, for why
+dnl this uses AC_COMPILE_IFELSE rather than AC_LINK_IFELSE.
 [AC_REQUIRE([AC_TYPE_UID_T])dnl
-AC_CACHE_CHECK(type of array argument to getgroups, ac_cv_type_getgroups,
-[AC_RUN_IFELSE([AC_LANG_SOURCE(
-[[/* Thanks to Mike Rendell for this test.  */
-]AC_INCLUDES_DEFAULT[
-#define NGID 256
-#undef MAX
-#define MAX(x, y) ((x) > (y) ? (x) : (y))
-
-int
-main (void)
-{
-  gid_t gidset[NGID];
-  int i, n;
-  union { gid_t gval; long int lval; }  val;
-
-  val.lval = -1;
-  for (i = 0; i < NGID; i++)
-    gidset[i] = val.gval;
-  n = getgroups (sizeof (gidset) / MAX (sizeof (int), sizeof (gid_t)) - 1,
-		 gidset);
-  /* Exit non-zero if getgroups seems to require an array of ints.  This
-     happens when gid_t is short int but getgroups modifies an array
-     of ints.  */
-  return n > 0 && gidset[n] != val.gval;
-}]])],
-	       [ac_cv_type_getgroups=gid_t],
-	       [ac_cv_type_getgroups=int],
-	       [ac_cv_type_getgroups=cross])
-if test $ac_cv_type_getgroups = cross; then
-  dnl When we can't run the test program (we are cross compiling), presume
-  dnl that <unistd.h> has either an accurate prototype for getgroups or none.
-  dnl Old systems without prototypes probably use int.
-  AC_EGREP_HEADER([getgroups.*int.*gid_t], unistd.h,
-		  ac_cv_type_getgroups=gid_t, ac_cv_type_getgroups=int)
-fi])
+AC_CACHE_CHECK([type of array argument to getgroups], ac_cv_type_getgroups,
+[# If AC_TYPE_UID_T says there isn't any gid_t typedef, then we can skip
+# everything below.
+AS_IF([test $ac_cv_type_gid_t = no],
+  [ac_cv_type_getgroups=int],
+  [# Test programs below rely on strict type checking of extern declarations:
+  # 'extern int getgroups(int, int *); extern int getgroups(int, pid_t *);'
+  # is valid in C89 if and only if pid_t is a typedef for int.  Unlike
+  # anything involving either an assignment or a function call, compilers
+  # tend to make this kind of type mismatch a hard error, not just an
+  # "incompatible pointer types" warning.
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[AC_INCLUDES_DEFAULT
+[extern int getgroups(int, gid_t *);]],
+[[return !(getgroups(0, 0) >= 0);]])],
+    [ac_getgroups_gidarray=yes],
+    [ac_getgroups_gidarray=no])
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[AC_INCLUDES_DEFAULT
+[extern int getgroups(int, int *);]],
+[[return !(getgroups(0, 0) >= 0);]])],
+    [ac_getgroups_intarray=yes],
+    [ac_getgroups_intarray=no])
+
+  AS_CASE([int:$ac_getgroups_intarray,gid:$ac_getgroups_gidarray],
+    [int:yes,gid:no], [ac_cv_type_getgroups=int],
+    [int:no,gid:yes], [ac_cv_type_getgroups=gid_t],
+    [int:yes,gid:yes], [
+      # Both programs compiled - this means *either* that getgroups
+      # was declared with no prototype, in which case we should use int,
+      # or that it was declared prototyped but gid_t is a typedef for int,
+      # in which case we should use gid_t.  Distinguish the two cases
+      # by testing if the compiler catches a blatantly incorrect function
+      # signature for getgroups.
+      AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[AC_INCLUDES_DEFAULT
+[extern int getgroups(int, float);]],
+[[return !(getgroups(0, 0) >= 0);]])], [
+        # Compiler did not catch incorrect argument list;
+        # getgroups is unprototyped.
+        ac_cv_type_getgroups=int
+      ], [
+        # Compiler caught incorrect argument list;
+        # gid_t is a typedef for int.
+        ac_cv_type_getgroups=gid_t
+      ])
+    ], [
+      # Both programs failed to compile - this probably means getgroups
+      # wasn't declared at all.  Use 'int', as this is probably a very
+      # old system where the type _would have been_ int.
+      ac_cv_type_getgroups=int
+    ])
+  ])
+])dnl AC_CACHE_CHECK
 AC_DEFINE_UNQUOTED(GETGROUPS_T, $ac_cv_type_getgroups,
-		   [Define to the type of elements in the array set by
+		   [Define to the type of elements in the array argument to
 		    'getgroups'. Usually this is either 'int' or 'gid_t'.])
 ])# AC_TYPE_GETGROUPS
 
diff --git a/tests/local.at b/tests/local.at
index 64a9fb264..db49f84a9 100644
--- a/tests/local.at
+++ b/tests/local.at
@@ -401,7 +401,7 @@ if test -f state-env.before && test -f state-env.after; then
       [interpval|PATH_SEPARATOR],
       [GFC|F77_DUMMY_MAIN|f77_(case|underscore)],
       [FC(_DUMMY_MAIN|FLAGS|LIBS|FLAGS_[fF]|_MODEXT|_MODINC|_MODOUT|_DEFINE)?],
-      [ALLOCA|GETLOADAVG_LIBS|KMEM_GROUP|NEED_SETGID|POW_LIB],
+      [ALLOCA|GETGROUPS_LIB|GETLOADAVG_LIBS|KMEM_GROUP|NEED_SETGID|POW_LIB],
       [AWK|LEX|LEXLIB|LEX_OUTPUT_ROOT|LN_S|M4|MKDIR_P|AR|RANLIB|SET_MAKE|YACC],
       [EGREP_TRADITIONAL],
       [GREP|[EF]GREP|SED],
-- 
2.41.0

