[PATCH] misc: Optimize getusershell.c

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Apr 22 13:04:34 GMT 2026



On 20/04/26 10:05, Rocket Ma wrote:
> * misc/getusershell.c: Completely rewrite the unit. Only allocate one
> big buffer to store shell names. Add a missing unit test.
> 
> The new implementation read the whole file into one buffer, and wipe out
> every byte but shell names. Later when addressing shell names from first
> shell, jump to next '\0' and then jump to next '/'. This could reduce
> memory footprint and shall improve some performance.
> 
> Signed-off-by: Rocket Ma <marocketbd@gmail.com>

This has cause CI regressions [1]:

  elf-check-localplt.out
  
  Extra PLT reference: libc.so: endusershell
  Extra PLT reference: libc.so: fread
  Extra PLT reference: libc.so: setbuf

This is due the use of the non-internal function names that generated extra
PLT calls.

[1] https://www.delorie.com/trybots/32bit/59989/

> ---
>  misc/Makefile                |   1 +
>  misc/getusershell.c          | 272 +++++++++++++++++++----------------
>  misc/tst-getusershell.c      |  75 ++++++++++
>  misc/tst-getusershell.shells |   7 +
>  4 files changed, 230 insertions(+), 125 deletions(-)
>  create mode 100644 misc/tst-getusershell.c
>  create mode 100644 misc/tst-getusershell.shells
> 
> diff --git a/misc/Makefile b/misc/Makefile
> index 4395366d74..31c930ef64 100644
> --- a/misc/Makefile
> +++ b/misc/Makefile
> @@ -245,6 +245,7 @@ tests := \
>    tst-empty \
>    tst-error1 \
>    tst-fdset \
> +  tst-getusershell \
>    tst-hsearch \
>    tst-insremque \
>    tst-ioctl \
> diff --git a/misc/getusershell.c b/misc/getusershell.c
> index 4221095dca..19394852a4 100644
> --- a/misc/getusershell.c
> +++ b/misc/getusershell.c
> @@ -1,143 +1,165 @@
> -/*
> - * Copyright (c) 1985, 1993
> - *	The Regents of the University of California.  All rights reserved.
> - *
> - * Redistribution and use in source and binary forms, with or without
> - * modification, are permitted provided that the following conditions
> - * are met:
> - * 1. Redistributions of source code must retain the above copyright
> - *    notice, this list of conditions and the following disclaimer.
> - * 2. Redistributions in binary form must reproduce the above copyright
> - *    notice, this list of conditions and the following disclaimer in the
> - *    documentation and/or other materials provided with the distribution.
> - * 4. Neither the name of the University nor the names of its contributors
> - *    may be used to endorse or promote products derived from this software
> - *    without specific prior written permission.
> - *
> - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
> - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> - * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
> - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
> - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
> - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
> - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> - * SUCH DAMAGE.
> - */
> -
> -#if defined(LIBC_SCCS) && !defined(lint)
> -static char sccsid[] = "@(#)getusershell.c	8.1 (Berkeley) 6/4/93";
> -#endif /* LIBC_SCCS and not lint */
> -
> -#include <sys/param.h>
> -#include <sys/file.h>
> -#include <sys/stat.h>
> -#include <stdio.h>
> -#include <stdio_ext.h>
> +/* Copyright (C) 2026 The GNU Toolchain Authors.
> +   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/>.  */
> +
>  #include <ctype.h>
> -#include <stdlib.h>
> +#include <fcntl.h>
> +#include <stddef.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <sys/stat.h>
>  #include <unistd.h>
>  #include <paths.h>
> +#include <stdlib.h>
> +#include <string.h>
>  
> -/*
> - * Local shells should NOT be added here.  They should be added in
> - * /etc/shells.
> - */
> -
> -/* NB: we do not initialize okshells here.  The initialization needs
> -   relocations.  These interfaces are used so rarely that this is not
> -   justified.  Instead explicitly initialize the array when it is
> -   used.  */
> -#if 0
> -static const char *const okshells[] = { _PATH_BSHELL, _PATH_CSHELL, NULL };
> -#else
> -static const char *okshells[3];
> -#endif
> -static char **curshell, **shells, *strings;
> -static char **initshells (void) __THROW;
> -
> -/*
> - * Get a list of shells from _PATH_SHELLS, if it exists.
> - */
> -char *
> -getusershell (void)
> +#define DEFAULT_SHELLS _PATH_BSHELL "\0" _PATH_CSHELL
> +
> +static char *shellbuf;
> +static char *shellend;
> +static char *nextshell;
> +
> +static char *
> +address_next_shell (void)
>  {
> -	char *ret;
> -
> -	if (curshell == NULL)
> -		curshell = initshells();
> -	ret = *curshell;
> -	if (ret != NULL)
> -		curshell++;
> -	return (ret);
> +  char *curshell;
> +  char *next0;
> +
> +  curshell = nextshell;
> +  if (nextshell == NULL)
> +    return curshell;
> +
> +  /* init_shells guarantees we have a \0 at the end */
> +  next0 = memchr (nextshell, '\0', shellend - nextshell);
> +  if (next0 == NULL)
> +    /* Unexpected, perhaps user modified our buffer? */
> +    nextshell = NULL;
> +  else
> +    nextshell = memchr (next0, '/', shellend - next0);
> +  return curshell;
>  }
>  
> -void
> -endusershell (void)
> +/* Read /etc/shells, strip unnecessary bytes, and setup nextshell */
> +static void
> +init_shells (void)
>  {
> +  FILE *fp;
> +  struct __stat64_t64 fstat;
> +  size_t buflen;
> +  char *top;
> +  char *line_start, *line_end;
> +  char *slash, *discard;
> +
> +  endusershell ();
> +
> +  if ((fp = fopen (_PATH_SHELLS, "rce")) == NULL)
> +    goto default_out;
> +  if ((__fstat64_time64 (fileno (fp), &fstat)) == -1)
> +    goto close_out;
> +  /* Consider if buflen will overflow. */
> +  if (fstat.st_size < 2 || fstat.st_size > PTRDIFF_MAX - 1)
> +    goto close_out;
> +  /* 1 byte for \n (will be overwritten as \0). */
> +  buflen = fstat.st_size + 1;
> +  if ((shellbuf = malloc (buflen)) == NULL)
> +    goto close_out;
> +  shellbuf[buflen - 1] = '\n';
> +  setbuf (fp, NULL);
> +  if ((fread (shellbuf, 1, fstat.st_size, fp)) != fstat.st_size)
> +    goto free_out;
> +
> +  top = shellbuf + buflen;
> +  line_start = shellbuf;
> +  while ((line_end = memchr (line_start, '\n', top - line_start)) != NULL)
> +    {
> +      line_end++; /* include \n */
> +      discard = line_start;
> +
> +      slash = memchr (line_start, '/', line_end - line_start);
> +      if (slash == NULL)
> +	goto wipe_line;
> +
> +      discard = memchr (line_start, '#', line_end - line_start);
> +      if (discard != NULL && discard < slash)
> +	goto wipe_line;
> +
> +      (void) memset (line_start, '\0', slash - line_start);
> +      discard = slash;
> +      while (discard < line_end && *discard != '#'
> +	     && !isspace ((unsigned char) *discard))
> +	discard++;
> +
> +    wipe_line:
> +      (void) memset (discard, '\0', line_end - discard);
> +      line_start = line_end;
> +    }
> +
> +  if ((nextshell = memchr (shellbuf, '/', top - shellbuf)) == NULL)
> +    goto free_out;
> +  shellend = top;
> +  (void) fclose (fp);
> +  return;
>  
> -	free(shells);
> -	shells = NULL;
> -	free(strings);
> -	strings = NULL;
> -	curshell = NULL;
> +free_out:
> +  free (shellbuf);
> +  shellbuf = NULL;
> +close_out:
> +  (void) fclose (fp);
> +default_out:
> +  shellbuf = malloc (sizeof (DEFAULT_SHELLS));
> +  if (shellbuf == NULL)
> +    {
> +      /* Can't allocate a buffer to store default shells,
> +	 use read-only string to avoid unexpected user write.
> +	 Leave shellbuf as NULL so that it can be freed. */
> +      nextshell = (char *) DEFAULT_SHELLS;
> +      shellend = (char *) DEFAULT_SHELLS + sizeof (DEFAULT_SHELLS);
> +    }
> +  else
> +    {
> +      memcpy (shellbuf, DEFAULT_SHELLS, sizeof (DEFAULT_SHELLS));
> +      nextshell = shellbuf;
> +      shellend = shellbuf + sizeof (DEFAULT_SHELLS);
> +    }
> +}
> +
> +char *
> +getusershell (void)
> +{
> +  if (shellend == NULL)
> +    init_shells ();
> +  return address_next_shell ();
>  }
>  
>  void
>  setusershell (void)
>  {
> -
> -	curshell = initshells();
> +  if (shellend == NULL)
> +    init_shells ();
> +  else if (shellbuf != NULL)
> +    nextshell = memchr (shellbuf, '/', shellend - shellbuf);
> +  else /* shellend != NULL && shellbuf == NULL */
> +    nextshell = (char *) DEFAULT_SHELLS;
>  }
>  
> -static char **
> -initshells (void)
> +void
> +endusershell (void)
>  {
> -	char **sp, *cp;
> -	FILE *fp;
> -	struct __stat64_t64 statb;
> -	size_t flen;
> -
> -	free(shells);
> -	shells = NULL;
> -	free(strings);
> -	strings = NULL;
> -	if ((fp = fopen(_PATH_SHELLS, "rce")) == NULL)
> -		goto init_okshells_noclose;
> -	if (__fstat64_time64(fileno(fp), &statb) == -1) {
> -	init_okshells:
> -		(void)fclose(fp);
> -	init_okshells_noclose:
> -		okshells[0] = _PATH_BSHELL;
> -		okshells[1] = _PATH_CSHELL;
> -		return (char **) okshells;
> -	}
> -	if (statb.st_size > ~(size_t)0 / sizeof (char *) * 3)
> -		goto init_okshells;
> -	flen = statb.st_size + 3;
> -	if ((strings = malloc(flen)) == NULL)
> -		goto init_okshells;
> -	shells = malloc(statb.st_size / 3 * sizeof (char *));
> -	if (shells == NULL) {
> -		free(strings);
> -		strings = NULL;
> -		goto init_okshells;
> -	}
> -	sp = shells;
> -	cp = strings;
> -	while (fgets_unlocked(cp, flen - (cp - strings), fp) != NULL) {
> -		while (*cp != '#' && *cp != '/' && *cp != '\0')
> -			cp++;
> -		if (*cp == '#' || *cp == '\0' || cp[1] == '\0')
> -			continue;
> -		*sp++ = cp;
> -		while (!isspace(*cp) && *cp != '#' && *cp != '\0')
> -			cp++;
> -		*cp++ = '\0';
> -	}
> -	*sp = NULL;
> -	(void)fclose(fp);
> -	return (shells);
> +  free (shellbuf);
> +  shellbuf = NULL;
> +  shellend = NULL;
> +  nextshell = NULL;
>  }
> diff --git a/misc/tst-getusershell.c b/misc/tst-getusershell.c
> new file mode 100644
> index 0000000000..b2f71804d8
> --- /dev/null
> +++ b/misc/tst-getusershell.c
> @@ -0,0 +1,75 @@
> +/* Test the getusershell series functions.
> +   Copyright (C) 2026 The GNU Toolchain Authors.
> +   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/>.  */
> +
> +#include "support/temp_file.h"
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <paths.h>
> +#include <support/check.h>
> +#include <support/xunistd.h>
> +#include <support/support.h>
> +#include <support/namespace.h>
> +#include <support/test-driver.h>
> +
> +static void
> +test_in_chroot (void *chroot_path)
> +{
> +  xchroot (chroot_path);
> +
> +  TEST_COMPARE_STRING (getusershell (), "/bin/sh");
> +  TEST_COMPARE_STRING (getusershell (), "/bin/bash");
> +  TEST_COMPARE_STRING (getusershell (), "/usr/bin/zsh");
> +  TEST_COMPARE_STRING (getusershell (), NULL);
> +  TEST_COMPARE_STRING (getusershell (), NULL);
> +
> +  setusershell ();
> +  TEST_COMPARE_STRING (getusershell (), "/bin/sh");
> +  endusershell ();
> +
> +  xunlink ("/etc/shells");
> +  TEST_COMPARE_STRING (getusershell (), "/bin/sh");
> +  TEST_COMPARE_STRING (getusershell (), "/bin/csh");
> +  TEST_COMPARE_STRING (getusershell (), NULL);
> +  endusershell ();
> +}
> +
> +static int
> +do_test (void)
> +{
> +  support_become_root ();
> +  if (!support_can_chroot ())
> +    return EXIT_UNSUPPORTED;
> +
> +  char *chroot_dir = support_create_temp_directory("tst-getusershell-");
> +  char *etc = xasprintf("%s/etc", chroot_dir);
> +  add_temp_file(etc);
> +  xmkdir(etc, 0777);
> +  /* Don't add shells to file list as it will be deleted in test. */
> +  char *shells = xasprintf("%s/shells", etc);
> +  support_copy_file("tst-getusershell.shells", shells);
> +
> +  support_isolate_in_subprocess(test_in_chroot, chroot_dir);
> +
> +  free(etc);
> +  free(shells);
> +  free(chroot_dir);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/misc/tst-getusershell.shells b/misc/tst-getusershell.shells
> new file mode 100644
> index 0000000000..fcc7675398
> --- /dev/null
> +++ b/misc/tst-getusershell.shells
> @@ -0,0 +1,7 @@
> +# test hash
> +  # indentation
> +
> +/bin/sh
> +	/bin/bash # ...
> +
> +xx /usr/bin/zsh#...
> \ No newline at end of file



More information about the Libc-alpha mailing list