Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F168398840
D58362.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D58362.id.diff
View Options
diff --git a/lib/libutil/getlocalbase.3 b/lib/libutil/getlocalbase.3
--- 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
--- 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 <se@freebsd.org>
+ * 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 <sys/param.h>
#include <sys/sysctl.h>
-#include <sys/limits.h>
+
#include <errno.h>
-#include <stdlib.h>
-#include <paths.h>
#include <libutil.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdlib.h>
#include <unistd.h>
-#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
--- a/lib/libutil/tests/Makefile
+++ b/lib/libutil/tests/Makefile
@@ -7,6 +7,7 @@
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
--- /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 <sys/param.h>
+#include <sys/wait.h>
+
+#include <libutil.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#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());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Aug 29, 1:05 AM (10 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37490201
Default Alt Text
D58362.id.diff (10 KB)
Attached To
Mode
D58362: libutil: Reimplement getlocalbase()
Attached
Detach File
Event Timeline
Log In to Comment