From patchwork Mon Apr 20 13:05:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rocket Ma X-Patchwork-Id: 133384 X-Patchwork-Delegate: azanella@linux.vnet.ibm.com Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id D3AE24B35883 for ; Mon, 20 Apr 2026 13:06:29 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D3AE24B35883 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=UK2oPsEK X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-dy1-x132f.google.com (mail-dy1-x132f.google.com [IPv6:2607:f8b0:4864:20::132f]) by sourceware.org (Postfix) with ESMTPS id 7764E4A9933D for ; Mon, 20 Apr 2026 13:05:56 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7764E4A9933D Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7764E4A9933D Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::132f ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776690356; cv=none; b=pwICKgR4duFlLbHkBVKdU9XDAo7jXhYwRdAz3RjyQRvltRr428kET497kA8Jpu6Q1wUSNJkkC7ctfTghu48fNDEsejoKDO1HyatMMfgsqDHIzjOYHdfh7tID4zgTmJqZE9vktSfavL01WPV/My+L5CBbYopZrzrW4dBtdAEryP8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776690356; c=relaxed/simple; bh=RqqHtz0SqDGwxqFjIfGtuaelb409ZHNq/shqtqzI/W4=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=fRmoLNtdTaFgtxJd4u8mg9EHN3z9x4nZvT2y+9pqWhbtw3Qh5UtadTVEiCMju/z+O1FjvxFz03JHMW7hPh1UxRy4S32oFXV5HS3nregCEnidl+34x1XPGdlzitJEmvnHU4I700M7D9f/sbQUnqv4py5qlYnyI+8azhxGuuve2aI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7764E4A9933D Received: by mail-dy1-x132f.google.com with SMTP id 5a478bee46e88-2dee127b3c5so3454621eec.1 for ; Mon, 20 Apr 2026 06:05:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776690355; x=1777295155; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to; bh=PC95Vvpf9+qPN0vNdGGRQ14N+l7ZtAelTku/HLGv430=; b=UK2oPsEKG8HW5rRxxBW8if53LBgceFSR6pOCNCKbtDrFhZy10+3E5jYiToc1+BXnRJ OZDLYNA7G78ZpHWuAjJ9fR+4A+EOTgEAFIEl0L+EyAxjWFTMKC2SNBrg9dIWhWdViZlD xYnZd7npPzN6jPer0hnFDfZOc4Du0yuOmXsQgpSo5Ksp//J78asDsETkPqJ7rXlDmlS8 iMFb21oTU56Bnlo1jUKEhWHY2BZkE9XbA5ArBJytwMywPvqicUVxaNgaNFQSdIik5tuD w301Q3SRO6AmUYSf9a4os907yGS8x8kGQ5UuWXr8M+GOWtDZgDiL0ChLPiIUV56JrL9P ay1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776690355; x=1777295155; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=PC95Vvpf9+qPN0vNdGGRQ14N+l7ZtAelTku/HLGv430=; b=Ju+cNDPWuLMeY8dEQXzf21ztRdhJ5F90oHiVm2A6LDAHKOEkEgexLNSMonamX7CmHs cFtCtrKIs8hLtipCTTNCINQjfVF0XJpnqyrIowyPQXAuXN5ETQV4eudT54LKxBy0W4Ti FQyED0mCyPJax1/m84r4UHod31/WRMG3N52QlJQ6l8AOXxusHu9UnzVTd/NK0QlAhOTD PXpHVQIDUaG+Nej/Q8/l1DlcDAOagsSRwBPW6HmU1IrocLSHcvfc+uwHbilzS76/zuBL 1NqZH0i3R0KWiAuxBDZ2Q23RirD5Y/sKo0ZnIShiRgRBQ00UKAD7osVzQ4YSMgV85fQ7 87pA== X-Gm-Message-State: AOJu0YxZRcnLumnvBQ5sRkbE9N/C+83xyrmHjEK+f/OMfbzrw4hbRfh4 RM0fAEAgolUFp6SAc3MJIpE1xPpven6o+7gSl9S2CNhpHWh/BOyIQnIMgfHmYQ== X-Gm-Gg: AeBDiesOBKHRBvkxHXgloA1Ci/HAl2wbGVYyGA1PA3P9Sh1hT7wweBQt05P6an8gj+g gBzSwmzWiuLmQtc6vc+B9gyYJjD2gnrbsFcEiJb530FfQQKVm2msI4A3W9uu5kgwTm58yEHf03G ZvPSVpttMUVdu5dnMeW2ILMAeVSY8ONy+qV0AMT65j5EJeMrU+5rmw40y8FDN/+pLRCu0agCoTZ +orrI9iO11/ZK28bLjv8mrlPYTS1n2Dgq4vqpCxVxhxSQw86eFCu1eOBBWFqGgH9rkViGdwnLVx Hwq12P9JNc9Hb75dcXsBem1XYFOxp2dgb2dcxPXDzrMagTcJtAYYM9vmbBYlURZn+282fgAjTVq tXGxtjthzD1uPnUuPBljYIbg9BuoasbAdglSXoqkm2iZHWXbOm2J9MMBHOo/li8r3/+Kb/mH+Bx 2BNrJn+c3O/Yk1PHA3hOoo1ExMd3eGtffEr9tllC4LVXN2h4+DXPo16JCuh55nTthCeD8Jdfkza fGf5x3SEMVfoy84O2l+uWcL6CkIYj9nRFalsw2fAaguMCaRMRFtSn68L0KivFa1oJidxRlYXMU= X-Received: by 2002:a05:7300:fb91:b0:2de:aafb:fed5 with SMTP id 5a478bee46e88-2e465488112mr6973511eec.9.1776690354647; Mon, 20 Apr 2026 06:05:54 -0700 (PDT) Received: from localhost ([23.94.240.252]) by smtp.gmail.com with UTF8SMTPSA id 5a478bee46e88-2e53ccce440sm13885367eec.14.2026.04.20.06.05.53 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 20 Apr 2026 06:05:53 -0700 (PDT) From: Rocket Ma To: libc-alpha@sourceware.org Subject: [PATCH] misc: Optimize getusershell.c Date: Mon, 20 Apr 2026 06:05:50 -0700 Message-ID: <20260420130550.3404747-1-marocketbd@gmail.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 X-Spam-Status: No, score=-10.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org * 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 --- 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 -#include -#include -#include -#include +/* 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 + . */ + #include -#include +#include +#include +#include +#include +#include #include #include +#include +#include -/* - * 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 + . */ + +#include "support/temp_file.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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 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