From nobody Tue Jul 21 11:34:28 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4h4FfD5rp3z6lqFs for ; Tue, 21 Jul 2026 11:34:28 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h4FfD3Cv9z3JdL for ; Tue, 21 Jul 2026 11:34:28 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784633668; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=QOaRU1ZZRdxeftw5gZMYfByqBElll9oDq3DwgIOm7E8=; b=AOjBw3KEbcQKcH/mEbpKe0cc1QVNM6vusttVegTVjH63VHuNoXiKEJGVHIW2Bpjvy7ewcN JCtDuUzPq217HowTpjKnYDGQ9bVk9OKYwq87jbmHr59m6R9N2+xy0lWijGd7OLpkbvuoFZ 001IZ4n93+fXVCKFlfQTppVRg8bJ49ha/P/Muhih3u1Ra1yiCvYOWvEsk93nmH/xYEG50F dMIDdbyKSKnnZ8JSziLP1LtjC0a23Pw9vWdWxza90sRfKj8hf3hEujQnU7Rr5FcMCzihZJ cGYVCU10AC76W2673qTG7pkBNkbnV3rk9LWGRQTClSFSKu4DSjBrUzEUpsoDqA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1784633668; a=rsa-sha256; cv=none; b=VZulXHZPRjvs+2DxklJ7TepvmChh/9qi8qzdZf3QxNFPu57mGYkfyc1GGVczFfPhzeTufd rLmIuu8uAAnOm5Hb0fzLFNhKF4qZmE7UP25YxO2R5pbbO8dC1xXcDYWo/pn/0JVCO+AJcp uFrXMOchWVAivNhusI4nPnr6fr9sSX+fkqkTy8YTXASjjsjivKtaWVwn4npZawdYJm1I4C 8+0Q/9JZDAFh/uBTlGhgNURoexWtzhAKLUTxRXSz9xwgFBmbDjVgh9xRfsqLFN8QUMH6dT KQNNGCkDekcz/kCp6MGO/vT4mkyx9xcA+oPtOzZ8F97xnzP4cQvsytKiq/7WZw== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784633668; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=QOaRU1ZZRdxeftw5gZMYfByqBElll9oDq3DwgIOm7E8=; b=femKzcEnMfqJsJSz43XNxgKGDBJG+RpaRATdwyLtVBwKWcrptnWBWQBrDG6eUCBPeGflsc pha49Es4Z6E7AafpeM2O8xyiAavhuu1Sf5ghFYxeNIcpDtnfpaGBIPtSFQy4QN7kLOkatZ uHRZs/MIKg78S0Q1CCM5LSpzYs1bVlsEkTQOJJ6+oT/82wbw6xn5XJXsVABFnQwsbMHVRF SBZsabg5O8Mze0Etro8xpGejB++EIeRRr3wG/he86vjvNdcLWEHLaTI6/EKWf81hz2GocY pNad5YpIAV+F6xTwls663pK9DqKky4T9d4g4JGzOFBE4gptR5iCd1OZF8SX15Q== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h4FfD2FD5zmpP for ; Tue, 21 Jul 2026 11:34:28 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3a8ff by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 21 Jul 2026 11:34:28 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav Subject: git: c93f3b55e67f - main - libutil: Reimplement getlocalbase() List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: c93f3b55e67fe465d93f3f54ae4408f131fec3aa Auto-Submitted: auto-generated Date: Tue, 21 Jul 2026 11:34:28 +0000 Message-Id: <6a5f5944.3a8ff.647c3596@gitrepo.freebsd.org> The branch main has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=c93f3b55e67fe465d93f3f54ae4408f131fec3aa commit c93f3b55e67fe465d93f3f54ae4408f131fec3aa Author: Dag-Erling Smørgrav AuthorDate: 2026-07-21 08:31:26 +0000 Commit: Dag-Erling Smørgrav CommitDate: 2026-07-21 08:31:49 +0000 libutil: Reimplement getlocalbase() * Get rid of the pointless LOCALBASE_CTL_LEN mechanism * Apply minimal normalization to the paths obtained from the environment or sysctl variable * Turn the manual page into a manual page * Add tests MFC after: 1 week Reviewed by: se Differential Revision: https://reviews.freebsd.org/D58362 --- lib/libutil/getlocalbase.3 | 111 ++++++++++++++++------------------ lib/libutil/getlocalbase.c | 100 ++++++++++++++++++------------ lib/libutil/tests/Makefile | 1 + lib/libutil/tests/getlocalbase_test.c | 92 ++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 98 deletions(-) diff --git a/lib/libutil/getlocalbase.3 b/lib/libutil/getlocalbase.3 index 5c71a87be8a3..ea02ae801bbb 100644 --- a/lib/libutil/getlocalbase.3 +++ b/lib/libutil/getlocalbase.3 @@ -3,6 +3,7 @@ .\" .\" Copyright 2020 Scott Long .\" Copyright 2020 Stefan Eßer +.\" Copyright (c) 2026 Dag-Erling Smørgrav .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -25,97 +26,89 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 11, 2023 +.Dd July 20, 2026 .Dt GETLOCALBASE 3 .Os .Sh NAME .Nm getlocalbase -.Nd "return the path to the local software directory" +.Nd "return the path to the local software base directory" .Sh LIBRARY .Lb libutil .Sh SYNOPSIS .In libutil.h -.Ft const char* +.Ft "const char *" .Fn getlocalbase "void" .Sh DESCRIPTION The -.Fn getlocalbase -function returns the path to the local software base directory. -Normally this is the -.Pa /usr/local -directory. +.Nm +function returns the path to the local software base directory, +normally +.Pa /usr/local . +.Pp First the .Ev LOCALBASE environment variable is checked. -If that does not exist then the +If that variable is undefined or empty, the .Va user.localbase sysctl is checked. -If that also does not exist then the value of the -.Dv LOCALBASE_PATH -compile-time variable is used. -If that is undefined then the system default, -.Pa _PATH_LOCALBASE -is used. +If that returns an empty string or an error occurs, the value of +.Dv _PATH_LOCALBASE +is used as a last resort. .Pp -The contents of the string returned by the -.Fn getlocalbase -function shall not be modified. -.Sh IMPLEMENTATION NOTES -Calls to -.Fn getlocalbase -will perform a setugid check on the running binary before checking the -environment. +If the value obtained through these means is not a valid absolute path +shorter than +.Dv MAXPATHLEN , +a constant string which has been deliberately chosen to cause any file +system operation using it to fail is returned instead. .Pp -The address returned by -.Fn getlocalbase -will point into the executing processes environment if it is the result of -.Fn getenv "LOCALBASE" , -to a static buffer if it is the result of -.Fn sysctl "user.localbase" , -and to a constant string if the compiled in default value is returned. +The contents of the string returned by +.Nm +shall not be modified by the caller. +.Sh IMPLEMENTATION NOTES +The +.Ev LOCALBASE +environment variable will only be used if the process calling +.Nm +is not setugid. .Pp -The same value will be returned on successive calls during the run-time -of the program, ignoring any changes to the environment variable or the -sysctl value that might have been made. +Successive calls to +.Nm +will return the same value throughout the lifetime of the process, +regardless of any subsequent changes to the environment or sysctl +variables. .Pp The -.Fn getlocalbase -function can be compiled with a non-default value of LOCALBASE_CTL_LEN. -A value of 0 will disable fetching of the sysctl value, a value less than -MAXPATHLEN will put a limit on the maximum string length supported for -this sysctl value. -If built with a non-default value of LOCALBASE_CTL_LEN, a value of the -user.localbase sysctl variable longer than this value will make -.Fn getlocalbase -return a valid string that is not a valid path prefix in any filesystem. +.Nm +function is thread-safe if and only if it has been called at least +once already. .Sh RETURN VALUES The .Fn getlocalbase -function returns a pointer to a string, whose length may exceed MAXPATHLEN, -if it has been obtained from the environment. +function returns a pointer to a null-terminated string. .Sh ENVIRONMENT -The -.Fn getlocalbase -library function retrieves the -.Ev LOCALBASE -environment variable. -.Sh ERRORS -The -.Fn getlocalbase -function always succeeds and returns a valid pointer to a string. +.Bl -tag -width ".Ev LOCALBASE" +.It Ev LOCALBASE +Path to the local software base directory +.El .Sh SEE ALSO -.Xr env 1 , -.Xr src.conf 5 , +.Xr environ 7 , .Xr sysctl 8 .Sh HISTORY The .Nm -library function first appeared in +function first appeared in .Fx 13.0 . .Sh AUTHORS .An -nosplit -This -manual page was written by +The +.Nm +function was originally written by +.An Stefan Eßer Aq Mt se@FreeBSD.org +and was later reimplemented by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . +This manual page was originally written by .An Scott Long Aq Mt scottl@FreeBSD.org and -.An Stefan Eßer Aq Mt se@FreeBSD.org . +.An Stefan Eßer Aq Mt se@FreeBSD.org +and was later substantially rewritten by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . diff --git a/lib/libutil/getlocalbase.c b/lib/libutil/getlocalbase.c index e8aabbab1e14..d48e0dbb33fd 100644 --- a/lib/libutil/getlocalbase.c +++ b/lib/libutil/getlocalbase.c @@ -1,7 +1,7 @@ -/*- +/* * SPDX-License-Identifier: BSD-2-Clause * - * Copyright 2020 Stefan Eßer + * Copyright (c) 2026 Dag-Erling Smørgrav * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,61 +27,83 @@ #include #include -#include + #include -#include -#include #include +#include +#include +#include #include -#ifndef LOCALBASE_PATH -#define LOCALBASE_PATH _PATH_LOCALBASE +#ifndef _PATH_LOCALBASE +#define _PATH_LOCALBASE "/usr/local" #endif -#ifndef LOCALBASE_CTL_LEN -#define LOCALBASE_CTL_LEN MAXPATHLEN -#endif +/* + * Used in case of error; guaranteed to not be the start of a valid path + * name. Deliberately includes a trailing path separator so that any + * access will fail with ENOTDIR. + */ +#define INVALID_PREFIX "/dev/null/" -/* Any prefix guaranteed to not be the start of a valid path name */ -#define ILLEGAL_PREFIX "/dev/null/" +/* + * Copies a path from src to dst, which may point to the same buffer, + * while deduplicating path separators. Returns false if the path is not + * absolute or the result (including the terminating NUL) exceeds len + * characters. + */ +static bool +normalize(const char *src, char *dst, size_t len) +{ + if (*src != '/' || len == 0) + return (false); + while (*src == '/') + src++; + do { + *dst++ = '/'; + if (--len == 0) + return (false); + while (*src != '\0' && *src != '/') { + *dst++ = *src++; + if (--len == 0) + return (false); + } + while (*src == '/') + src++; + } while (*src != '\0'); + *dst = '\0'; + return (true); +} const char * getlocalbase(void) { -#if LOCALBASE_CTL_LEN > 0 - int localbase_oid[2] = {CTL_USER, USER_LOCALBASE}; - static char localpath[LOCALBASE_CTL_LEN]; - size_t localpathlen = LOCALBASE_CTL_LEN; -#endif - char *tmppath; + static const int oid[2] = { CTL_USER, USER_LOCALBASE }; + static char buf[MAXPATHLEN]; static const char *localbase = NULL; + char *path; + size_t len = sizeof(buf); if (localbase != NULL) return (localbase); - if (issetugid() == 0) { - tmppath = getenv("LOCALBASE"); - if (tmppath != NULL && tmppath[0] != '\0') { - localbase = tmppath; - return (localbase); - } + /* If not privileged, try the environment first */ + if ((path = secure_getenv("LOCALBASE")) != NULL && *path != '\0') { + if (!normalize(path, buf, sizeof(buf))) + return (localbase = INVALID_PREFIX); + return (localbase = buf); } -#if LOCALBASE_CTL_LEN > 0 - if (sysctl(localbase_oid, 2, localpath, &localpathlen, NULL, 0) != 0) { - if (errno != ENOMEM) - localbase = LOCALBASE_PATH; - else - localbase = ILLEGAL_PREFIX; - } else { - if (localpath[0] != '\0') - localbase = localpath; - else - localbase = LOCALBASE_PATH; + /* Next, try the sysctl variable */ + if (sysctl(oid, 2, buf, &len, NULL, 0) != 0) { + if (errno == ENOMEM) + return (localbase = INVALID_PREFIX); + } else if (*buf != '\0') { + if (!normalize(buf, buf, sizeof(buf))) + return (localbase = INVALID_PREFIX); + return (localbase = buf); } -#else - localbase = LOCALBASE_PATH; -#endif - return (localbase); + /* Fall back to the hardcoded default */ + return (localbase = _PATH_LOCALBASE); } diff --git a/lib/libutil/tests/Makefile b/lib/libutil/tests/Makefile index cdcf3466aec8..2523c02eb33d 100644 --- a/lib/libutil/tests/Makefile +++ b/lib/libutil/tests/Makefile @@ -7,6 +7,7 @@ TAP_TESTS_C+= trimdomain-nodomain_test ATF_TESTS_C+= cpuset_test ATF_TESTS_C+= expand_number_test ATF_TESTS_C+= forkpty_test +ATF_TESTS_C+= getlocalbase_test WARNS?= 2 LIBADD+= util diff --git a/lib/libutil/tests/getlocalbase_test.c b/lib/libutil/tests/getlocalbase_test.c new file mode 100644 index 000000000000..4f90ddf26cc5 --- /dev/null +++ b/lib/libutil/tests/getlocalbase_test.c @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2026 Dag-Erling Smørgrav + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define INVALID_PREFIX "/dev/null/" + +#define X31 "/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#define X32 "/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#define X64 X32 X32 +#define X128 X64 X64 +#define X256 X128 X128 +#define X512 X256 X256 +#define X1024 X512 X512 +#define X1023 X512 X256 X128 X64 X32 X31 + +static struct { + const char *in, *out; +} tests[] = { + { "", _PATH_LOCALBASE }, + { "/", "/" }, + { "//", "/" }, + { "///", "/" }, + { "foo", INVALID_PREFIX }, + { "/foo", "/foo" }, + { "//foo", "/foo" }, + { "/foo/", "/foo" }, + { "/foo//", "/foo" }, + { "//foo//", "/foo" }, + { "/foo/bar", "/foo/bar" }, + { "/foo/bar/", "/foo/bar" }, + { "/foo//bar", "/foo/bar" }, + { "/foo//bar/", "/foo/bar" }, + { "//foo/bar", "/foo/bar" }, + { "//foo/bar/", "/foo/bar" }, + { "//foo//bar", "/foo/bar" }, + { "//foo//bar/", "/foo/bar" }, + { _PATH_LOCALBASE, _PATH_LOCALBASE }, + { X31, X31 }, + { X32, X32 }, + { X64, X64 }, + { X128, X128 }, + { X256, X256 }, + { X512, X512 }, + { X1023, X1023 }, + { X1024, INVALID_PREFIX }, + { 0 } +}; +_Static_assert(sizeof(X1023) == MAXPATHLEN, "Unexpected MAXPATHLEN value"); + +ATF_TC(environment); +ATF_TC_HEAD(environment, tc) +{ + atf_tc_set_md_var(tc, "descr", "Tests getting the local base from the " + "environment and path normalization"); +} + +ATF_TC_BODY(environment, tc) +{ + pid_t pid; + int i, wstatus; + + for (i = 0; tests[i].in != NULL; i++) { + ATF_REQUIRE((pid = fork()) >= 0); + if (pid == 0) { + ATF_REQUIRE_EQ(setenv("LOCALBASE", tests[i].in, 1), 0); + ATF_REQUIRE_STREQ(getlocalbase(), tests[i].out); + _exit(0); + } + ATF_REQUIRE_EQ(waitpid(0, &wstatus, 0), pid); + ATF_CHECK(WIFEXITED(wstatus)); + ATF_CHECK_EQ(WEXITSTATUS(wstatus), 0); + } +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, environment); + return (atf_no_error()); +}