-/*
- * 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>
+/* 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/ctype.h"
+#include "iolibio.h"
+#include <assert.h>
+#include <fcntl.h>
+#include <stddef.h>
#include <stdio.h>
-#include <stdio_ext.h>
-#include <ctype.h>
-#include <stdlib.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 = nextshell;
+ if (nextshell == NULL)
+ return curshell;
+
+ /* init_shells guarantees we have a \0 at the end */
+ char *next0 = memchr (nextshell, '\0', shellend - nextshell);
+ assert (next0 != NULL);
+ nextshell = memchr (next0, '/', shellend - next0);
+ return curshell;
}
-void
-endusershell (void)
+/* Read /etc/shells, strip unnecessary bytes, and setup nextshell */
+static void
+init_shells (void)
{
+ free (shellbuf);
+ shellbuf = NULL;
+ shellend = NULL;
+ nextshell = NULL;
+
+ struct __stat64_t64 fstat;
+ FILE *fp = fopen (_PATH_SHELLS, "rce");
+ if (fp == NULL)
+ goto default_out;
+ int rc = __fstat64_time64 (__fileno (fp), &fstat);
+ if (rc == -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). */
+ size_t buflen = fstat.st_size + 1;
+ shellbuf = malloc (buflen);
+ if (shellbuf == NULL)
+ goto close_out;
+ shellbuf[buflen - 1] = '\n';
+ _IO_setbuf (fp, NULL);
+ size_t cnt = _IO_fread (shellbuf, 1, fstat.st_size, fp);
+ if (cnt != fstat.st_size)
+ goto free_out;
- free(shells);
- shells = NULL;
- free(strings);
- strings = NULL;
- curshell = NULL;
+ /* Loop through file and only keep shell names.
+ The rest characters are set to \0 to reduce addressing workload. */
+ char *top;
+ char *line_start, *line_end;
+ char *slash, *discard;
+
+ 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_l ((unsigned char) *discard, _nl_C_locobj_ptr))
+ discard++;
+
+ wipe_line:
+ (void) memset (discard, '\0', line_end - discard);
+ line_start = line_end;
+ }
+
+ nextshell = memchr (shellbuf, '/', top - shellbuf);
+ if (nextshell == NULL)
+ goto free_out;
+ shellend = top;
+ (void) fclose (fp);
+ return;
+
+free_out:
+ free (shellbuf);
+ shellbuf = NULL;
+close_out:
+ (void) fclose (fp);
+default_out:
+ /* Fallback plan: use builtin read-only default shells.
+ shellbuf is NULL here so it can be freed.*/
+ nextshell = (char *) DEFAULT_SHELLS;
+ shellend = (char *) DEFAULT_SHELLS + 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;
}
--- /dev/null
+/* 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>