diff --git a/bin/kenv/kenv.1 b/bin/kenv/kenv.1 index 0cadbefb41b3..980bec117515 100644 --- a/bin/kenv/kenv.1 +++ b/bin/kenv/kenv.1 @@ -1,152 +1,170 @@ .\"- .\" Copyright (c) 2000 Peter Wemm .\" .\" 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. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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. .\" .\" $FreeBSD$ .\" .Dd June 20, 2021 .Dt KENV 1 .Os .Sh NAME .Nm kenv .Nd list or modify the kernel environment .Sh SYNOPSIS .Nm +.Op Fl l | s .Op Fl hNq .Nm .Op Fl qv .Ar variable Ns Op = Ns Ar value .Nm .Op Fl q .Fl u .Ar variable .Sh DESCRIPTION The .Nm utility will list all variables in the kernel environment if invoked without arguments. +.Pp +If the +.Fl l +option is specified, then the static environment provided by +.Xr loader 8 +will be listed instead. +Similarly, the +.Fl s +option will list the static environment defined by the kernel config. +Both of the +.Fl l +and +.Fl s +options are dependent on the kernel being configured to preserve early kernel +environments. +The default kernel configuration does not preserve these environments. +.Pp If the .Fl h option is specified, it will limit the report to kernel probe hints. If an optional .Ar variable name is specified, .Nm will only report that value. If the .Fl N option is specified, .Nm will only display variable names and not their values. If the .Fl u option is specified, .Nm will delete the given environment variable. If the environment variable is followed by an optional .Ar value , .Nm will set the environment variable to this value. .Pp If the .Fl q option is set, warnings normally printed as a result of being unable to perform the requested operation will be suppressed. .Pp If the .Fl v option is set, the variable name will be printed out for the environment variable in addition to the value when .Nm is executed with a variable name. .Pp Variables can be added to the kernel environment using the .Pa /boot/loader.conf file, or also statically compiled into the kernel using the statement .Pp .Dl Ic env Ar filename .Pp in the kernel config file. The file can contain lines of the form .Pp .Dl name = "value" # this is a comment .Pp where whitespace around .Sq name and .Sq = , and everything after a .Sq # character, are ignored. Almost any printable character except .Sq = is acceptable as part of a name. Quotes are optional and necessary only if the value contains whitespace. .Sh EXAMPLES Show kernel probe hints variable names and filter for the uart device .Bd -literal -offset indent $ kenv -h -N | grep uart hint.uart.0.at hint.uart.0.flags hint.uart.0.irq hint.uart.0.port hint.uart.1.at hint.uart.1.irq hint.uart.1.port .Ed .Pp Show the value of a specific variable: .Bd -literal -offset indent $ kenv hint.uart.1.at isa .Ed .Pp Same as above but adding the name of the variable in the report: .Bd -literal -offset indent $ kenv -v hint.uart.1.at hint.uart.1.at="isa" .Ed .Pp Try to delete a variable and suppress warnings if any: .Bd -literal -offset indent $ kenv -q -u hint.uart.1.at .Ed .Pp Set the value of the .Ev verbose_loading variable .Bd -literal -offset indent $ kenv verbose_loading="YES" verbose_loading="YES" .Ed .Sh SEE ALSO .Xr kenv 2 , .Xr config 5 , .Xr loader.conf 5 , .Xr loader 8 .Sh HISTORY The .Nm utility appeared in .Fx 4.1.1 . diff --git a/bin/kenv/kenv.c b/bin/kenv/kenv.c index 77caeaf5bca2..ecf30ee6b617 100644 --- a/bin/kenv/kenv.c +++ b/bin/kenv/kenv.c @@ -1,206 +1,226 @@ /*- * Copyright (c) 2000 Peter Wemm * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include #include #include static void usage(void); -static int kdumpenv(void); +static int kdumpenv(int dump_type); static int kgetenv(const char *); static int ksetenv(const char *, char *); static int kunsetenv(const char *); static int hflag = 0; +static int lflag = 0; static int Nflag = 0; static int qflag = 0; +static int sflag = 0; static int uflag = 0; static int vflag = 0; static void usage(void) { (void)fprintf(stderr, "%s\n%s\n%s\n", - "usage: kenv [-hNq]", + "usage: kenv [-l|-s] [-hNq]", " kenv [-qv] variable[=value]", " kenv [-q] -u variable"); exit(1); } int main(int argc, char **argv) { char *env, *eq, *val; int ch, error; val = NULL; env = NULL; - while ((ch = getopt(argc, argv, "hNquv")) != -1) { + while ((ch = getopt(argc, argv, "hlNqsuv")) != -1) { switch (ch) { case 'h': hflag++; break; + case 'l': + lflag++; + break; case 'N': Nflag++; break; case 'q': qflag++; break; + case 's': + sflag++; + break; case 'u': uflag++; break; case 'v': vflag++; break; default: usage(); } } argc -= optind; argv += optind; if (argc > 0) { env = argv[0]; eq = strchr(env, '='); if (eq != NULL) { *eq++ = '\0'; val = eq; } argv++; argc--; } if ((hflag || Nflag) && env != NULL) usage(); + if (lflag && sflag) + usage(); if (argc > 0 || ((uflag || vflag) && env == NULL)) usage(); if (env == NULL) { - error = kdumpenv(); - if (error && !qflag) - warn("kdumpenv"); + if (lflag) + error = kdumpenv(KENV_DUMP_LOADER); + else if (sflag) + error = kdumpenv(KENV_DUMP_STATIC); + else + error = kdumpenv(KENV_DUMP); + if (error && !qflag) { + if (errno == ENOENT) + warnx("requested environment is unavailable"); + else + warn("kdumpenv"); + } } else if (val == NULL) { if (uflag) { error = kunsetenv(env); if (error && !qflag) warnx("unable to unset %s", env); } else { error = kgetenv(env); if (error && !qflag) warnx("unable to get %s", env); } } else { error = ksetenv(env, val); if (error && !qflag) warnx("unable to set %s to %s", env, val); } return (error); } static int -kdumpenv(void) +kdumpenv(int dump_type) { char *buf, *bp, *cp; int buflen, envlen; - envlen = kenv(KENV_DUMP, NULL, NULL, 0); + envlen = kenv(dump_type, NULL, NULL, 0); if (envlen < 0) return (-1); for (;;) { buflen = envlen * 120 / 100; buf = calloc(1, buflen + 1); if (buf == NULL) return (-1); - envlen = kenv(KENV_DUMP, NULL, buf, buflen); + envlen = kenv(dump_type, NULL, buf, buflen); if (envlen < 0) { free(buf); return (-1); } if (envlen > buflen) free(buf); else break; } for (bp = buf; *bp != '\0'; bp += strlen(bp) + 1) { if (hflag) { if (strncmp(bp, "hint.", 5) != 0) continue; } cp = strchr(bp, '='); if (cp == NULL) continue; *cp++ = '\0'; if (Nflag) printf("%s\n", bp); else printf("%s=\"%s\"\n", bp, cp); bp = cp; } free(buf); return (0); } static int kgetenv(const char *env) { char buf[1024]; int ret; ret = kenv(KENV_GET, env, buf, sizeof(buf)); if (ret == -1) return (ret); if (vflag) printf("%s=\"%s\"\n", env, buf); else printf("%s\n", buf); return (0); } static int ksetenv(const char *env, char *val) { int ret; ret = kenv(KENV_SET, env, val, strlen(val) + 1); if (ret == 0) printf("%s=\"%s\"\n", env, val); return (ret); } static int kunsetenv(const char *env) { int ret; ret = kenv(KENV_UNSET, env, NULL, 0); return (ret); } diff --git a/lib/libc/sys/kenv.2 b/lib/libc/sys/kenv.2 index 06f708170a2f..a1f994569111 100644 --- a/lib/libc/sys/kenv.2 +++ b/lib/libc/sys/kenv.2 @@ -1,175 +1,193 @@ .\" .\" Copyright (C) 2002 Chad David . 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(s), this list of conditions and the following disclaimer as .\" the first lines of this file unmodified other than the possible .\" addition of one or more copyright notices. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice(s), this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``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 COPYRIGHT HOLDER(S) 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. .\" .\" $FreeBSD$ .\" -.Dd February 20, 2017 +.Dd June 20, 2021 .Dt KENV 2 .Os .Sh NAME .Nm kenv .Nd kernel environment .Sh LIBRARY .Lb libc .Sh SYNOPSIS .In kenv.h .Ft int .Fn kenv "int action" "const char *name" "char *value" "int len" .Sh DESCRIPTION The .Fn kenv system call manipulates kernel environment variables. It supports the well known userland actions of getting, setting and unsetting environment variables, as well as the ability to dump all of the entries in the kernel environment. .Pp The .Fa action argument can be one of the following: -.Bl -tag -width ".Dv KENV_UNSET" +.Bl -tag -width ".Dv KENV_DUMP_LOADER" .It Dv KENV_GET Get the .Fa value of the variable with the given .Fa name . The size of the .Fa value buffer is given by .Fa len , which should be at least .Dv KENV_MVALLEN + 1 bytes to avoid truncation and to ensure NUL termination. .It Dv KENV_SET Set or add a variable. The .Fa name and .Fa value are limited to .Dv KENV_MNAMELEN and .Dv KENV_MVALLEN characters, respectively .Pq not including the NUL terminator. The .Fa len argument indicates the length of the .Fa value and must include the NUL terminator. This option is only available to the superuser. .It Dv KENV_UNSET Unset the variable with the given .Fa name . The .Fa value and .Fa len arguments are ignored. This option is only available to the superuser. .It Dv KENV_DUMP -Dump as much of the kernel environment as will fit in +Dump as much of the dynamic kernel environment as will fit in .Fa value , whose size is given in .Fa len . If .Fa value is .Dv NULL , .Fn kenv will return the number of bytes required to copy out the entire environment. The .Fa name is ignored. +.It Dv KENV_DUMP_LOADER +Dump the static environment provided by +.Xr loader 8 , +with semantics identical to +.Dv KENV_DUMP . +Duplicate and malformed variables originally present in this environment are +discarded by the kernel and will not appear in the output. +.It Dv KENV_DUMP_STATIC +Dump the static environment defined by the kernel +.Xr config 5 . +The semantics are identical to +.Dv KENV_DUMP_LOADER . .El .Sh RETURN VALUES The .Fn kenv system call returns 0 if successful in the case of .Dv KENV_SET and .Dv KENV_UNSET , and the number of bytes copied into .Fa value in the case of .Dv KENV_DUMP and .Dv KENV_GET . If an error occurs, a value of \-1 is returned and the global variable .Va errno is set to indicate the error. .Sh ERRORS The .Fn kenv system call will fail if: .Bl -tag -width Er .It Bq Er EINVAL The .Fa action argument is not a valid option, or the length of the .Fa value is less than 1 for a .Dv KENV_SET . .It Bq Er ENOENT No value could be found for .Fa name for a .Dv KENV_GET or .Dv KENV_UNSET . +.It Bq Er ENOENT +The requested environment is not available for a +.Dv KENV_DUMP_LOADER +or +.Dv KENV_DUMP_STATIC . +The kernel is configured to destroy these environments by default. .It Bq Er EPERM A user other than the superuser attempted to set or unset a kernel environment variable. .It Bq Er EFAULT A bad address was encountered while attempting to copy in user arguments or copy out value(s). .It Bq Er ENAMETOOLONG The .Fa name or the .Fa value is longer than .Dv KENV_MNAMELEN or .Dv KENV_MVALLEN characters, respectively, or .Fa len did not include the NUL terminator for a .Dv KENV_SET . .El .Sh SEE ALSO .Xr kenv 1 .Sh AUTHORS .An -nosplit This manual page was written by .An Chad David Aq Mt davidc@FreeBSD.org . .Pp The .Fn kenv system call was written by .An Maxime Henrion Aq Mt mux@FreeBSD.org . diff --git a/sys/kern/kern_environment.c b/sys/kern/kern_environment.c index 8dc345559e95..2a4c62d64a0f 100644 --- a/sys/kern/kern_environment.c +++ b/sys/kern/kern_environment.c @@ -1,1084 +1,1127 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998 Michael Smith * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * The unified bootloader passes us a pointer to a preserved copy of * bootstrap/kernel environment variables. We convert them to a * dynamic array of strings later when the VM subsystem is up. * * We make these available through the kenv(2) syscall for userland * and through kern_getenv()/freeenv() kern_setenv() kern_unsetenv() testenv() for * the kernel. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *_getenv_dynamic_locked(const char *name, int *idx); static char *_getenv_dynamic(const char *name, int *idx); static char *kenv_acquire(const char *name); static void kenv_release(const char *buf); static MALLOC_DEFINE(M_KENV, "kenv", "kernel environment"); #define KENV_SIZE 512 /* Maximum number of environment strings */ static uma_zone_t kenv_zone; static int kenv_mvallen = KENV_MVALLEN; /* pointer to the config-generated static environment */ char *kern_envp; /* pointer to the md-static environment */ char *md_envp; static int md_env_len; static int md_env_pos; static char *kernenv_next(char *); /* dynamic environment variables */ char **kenvp; struct mtx kenv_lock; /* * No need to protect this with a mutex since SYSINITS are single threaded. */ bool dynamic_kenv; #define KENV_CHECK if (!dynamic_kenv) \ panic("%s: called before SI_SUB_KMEM", __func__) +static int +kenv_dump(struct thread *td, char **envp, int what, char *value, int len) +{ + char *buffer, *senv; + size_t done, needed, buflen; + int error; + + error = 0; + buffer = NULL; + done = needed = 0; + + MPASS(what == KENV_DUMP || what == KENV_DUMP_LOADER || + what == KENV_DUMP_STATIC); + + /* + * For non-dynamic kernel environment, we pass in either md_envp or + * kern_envp and we must traverse with kernenv_next(). This shuffling + * of pointers simplifies the below loop by only differing in how envp + * is modified. + */ + if (what != KENV_DUMP) { + senv = (char *)envp; + envp = &senv; + } + + buflen = len; + if (buflen > KENV_SIZE * (KENV_MNAMELEN + kenv_mvallen + 2)) + buflen = KENV_SIZE * (KENV_MNAMELEN + + kenv_mvallen + 2); + if (len > 0 && value != NULL) + buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); + + /* Only take the lock for the dynamic kenv. */ + if (what == KENV_DUMP) + mtx_lock(&kenv_lock); + while (*envp != NULL) { + len = strlen(*envp) + 1; + needed += len; + len = min(len, buflen - done); + /* + * If called with a NULL or insufficiently large + * buffer, just keep computing the required size. + */ + if (value != NULL && buffer != NULL && len > 0) { + bcopy(*envp, buffer + done, len); + done += len; + } + + /* Advance the pointer depending on the kenv format. */ + if (what == KENV_DUMP) + envp++; + else + senv = kernenv_next(senv); + } + if (what == KENV_DUMP) + mtx_unlock(&kenv_lock); + if (buffer != NULL) { + error = copyout(buffer, value, done); + free(buffer, M_TEMP); + } + td->td_retval[0] = ((done == needed) ? 0 : needed); + return (error); +} + int -sys_kenv(td, uap) - struct thread *td; - struct kenv_args /* { - int what; - const char *name; - char *value; - int len; - } */ *uap; +sys_kenv(struct thread *td, struct kenv_args *uap) { - char *name, *value, *buffer = NULL; - size_t len, done, needed, buflen; - int error, i; + char *name, *value; + size_t len; + int error; KASSERT(dynamic_kenv, ("kenv: dynamic_kenv = false")); error = 0; - if (uap->what == KENV_DUMP) { + + switch (uap->what) { + case KENV_DUMP: #ifdef MAC error = mac_kenv_check_dump(td->td_ucred); if (error) return (error); #endif - done = needed = 0; - buflen = uap->len; - if (buflen > KENV_SIZE * (KENV_MNAMELEN + kenv_mvallen + 2)) - buflen = KENV_SIZE * (KENV_MNAMELEN + - kenv_mvallen + 2); - if (uap->len > 0 && uap->value != NULL) - buffer = malloc(buflen, M_TEMP, M_WAITOK|M_ZERO); - mtx_lock(&kenv_lock); - for (i = 0; kenvp[i] != NULL; i++) { - len = strlen(kenvp[i]) + 1; - needed += len; - len = min(len, buflen - done); - /* - * If called with a NULL or insufficiently large - * buffer, just keep computing the required size. - */ - if (uap->value != NULL && buffer != NULL && len > 0) { - bcopy(kenvp[i], buffer + done, len); - done += len; - } - } - mtx_unlock(&kenv_lock); - if (buffer != NULL) { - error = copyout(buffer, uap->value, done); - free(buffer, M_TEMP); - } - td->td_retval[0] = ((done == needed) ? 0 : needed); - return (error); - } - - switch (uap->what) { + return (kenv_dump(td, kenvp, uap->what, uap->value, uap->len)); + case KENV_DUMP_LOADER: + case KENV_DUMP_STATIC: +#ifdef MAC + error = mac_kenv_check_dump(td->td_ucred); + if (error) + return (error); +#endif +#ifdef PRESERVE_EARLY_KENV + return (kenv_dump(td, + uap->what == KENV_DUMP_LOADER ? (char **)md_envp : + (char **)kern_envp, uap->what, uap->value, uap->len)); +#else + return (ENOENT); +#endif case KENV_SET: error = priv_check(td, PRIV_KENV_SET); if (error) return (error); break; case KENV_UNSET: error = priv_check(td, PRIV_KENV_UNSET); if (error) return (error); break; } name = malloc(KENV_MNAMELEN + 1, M_TEMP, M_WAITOK); error = copyinstr(uap->name, name, KENV_MNAMELEN + 1, NULL); if (error) goto done; switch (uap->what) { case KENV_GET: #ifdef MAC error = mac_kenv_check_get(td->td_ucred, name); if (error) goto done; #endif value = kern_getenv(name); if (value == NULL) { error = ENOENT; goto done; } len = strlen(value) + 1; if (len > uap->len) len = uap->len; error = copyout(value, uap->value, len); freeenv(value); if (error) goto done; td->td_retval[0] = len; break; case KENV_SET: len = uap->len; if (len < 1) { error = EINVAL; goto done; } if (len > kenv_mvallen + 1) len = kenv_mvallen + 1; value = malloc(len, M_TEMP, M_WAITOK); error = copyinstr(uap->value, value, len, NULL); if (error) { free(value, M_TEMP); goto done; } #ifdef MAC error = mac_kenv_check_set(td->td_ucred, name, value); if (error == 0) #endif kern_setenv(name, value); free(value, M_TEMP); break; case KENV_UNSET: #ifdef MAC error = mac_kenv_check_unset(td->td_ucred, name); if (error) goto done; #endif error = kern_unsetenv(name); if (error) error = ENOENT; break; default: error = EINVAL; break; } done: free(name, M_TEMP); return (error); } /* * Populate the initial kernel environment. * * This is called very early in MD startup, either to provide a copy of the * environment obtained from a boot loader, or to provide an empty buffer into * which MD code can store an initial environment using kern_setenv() calls. * * kern_envp is set to the static_env generated by config(8). This implements * the env keyword described in config(5). * * If len is non-zero, the caller is providing an empty buffer. The caller will * subsequently use kern_setenv() to add up to len bytes of initial environment * before the dynamic environment is available. * * If len is zero, the caller is providing a pre-loaded buffer containing * environment strings. Additional strings cannot be added until the dynamic * environment is available. The memory pointed to must remain stable at least * until sysinit runs init_dynamic_kenv() and preferably until after SI_SUB_KMEM * is finished so that subr_hints routines may continue to use it until the * environments have been fully merged at the end of the pass. If no initial * environment is available from the boot loader, passing a NULL pointer allows * the static_env to be installed if it is configured. In this case, any call * to kern_setenv() prior to the setup of the dynamic environment will result in * a panic. */ void init_static_kenv(char *buf, size_t len) { KASSERT(!dynamic_kenv, ("kenv: dynamic_kenv already initialized")); /* * Suitably sized means it must be able to hold at least one empty * variable, otherwise things go belly up if a kern_getenv call is * made without a prior call to kern_setenv as we have a malformed * environment. */ KASSERT(len == 0 || len >= 2, ("kenv: static env must be initialized or suitably sized")); KASSERT(len == 0 || (*buf == '\0' && *(buf + 1) == '\0'), ("kenv: sized buffer must be initially empty")); /* * We may be called twice, with the second call needed to relocate * md_envp after enabling paging. md_envp is then garbage if it is * not null and the relocation will move it. Discard it so as to * not crash using its old value in our first call to kern_getenv(). * * The second call gives the same environment as the first except * in silly configurations where the static env disables itself. * * Other env calls don't handle possibly-garbage pointers, so must * not be made between enabling paging and calling here. */ md_envp = NULL; md_env_len = 0; md_env_pos = 0; /* * Give the static environment a chance to disable the loader(8) * environment first. This is done with loader_env.disabled=1. * * static_env and static_hints may both be disabled, but in slightly * different ways. For static_env, we just don't setup kern_envp and * it's as if a static env wasn't even provided. For static_hints, * we effectively zero out the buffer to stop the rest of the kernel * from being able to use it. * * We're intentionally setting this up so that static_hints.disabled may * be specified in either the MD env or the static env. This keeps us * consistent in our new world view. * * As a warning, the static environment may not be disabled in any way * if the static environment has disabled the loader environment. */ kern_envp = static_env; if (!getenv_is_true("loader_env.disabled")) { md_envp = buf; md_env_len = len; md_env_pos = 0; if (getenv_is_true("static_env.disabled")) { kern_envp[0] = '\0'; kern_envp[1] = '\0'; } } if (getenv_is_true("static_hints.disabled")) { static_hints[0] = '\0'; static_hints[1] = '\0'; } } static void init_dynamic_kenv_from(char *init_env, int *curpos) { char *cp, *cpnext, *eqpos, *found; size_t len; int i; if (init_env && *init_env != '\0') { found = NULL; i = *curpos; for (cp = init_env; cp != NULL; cp = cpnext) { cpnext = kernenv_next(cp); len = strlen(cp) + 1; if (len > KENV_MNAMELEN + 1 + kenv_mvallen + 1) { printf( "WARNING: too long kenv string, ignoring %s\n", cp); goto sanitize; } eqpos = strchr(cp, '='); if (eqpos == NULL) { printf( "WARNING: malformed static env value, ignoring %s\n", cp); goto sanitize; } *eqpos = 0; /* * De-dupe the environment as we go. We don't add the * duplicated assignments because config(8) will flip * the order of the static environment around to make * kernel processing match the order of specification * in the kernel config. */ found = _getenv_dynamic_locked(cp, NULL); *eqpos = '='; if (found != NULL) goto sanitize; if (i > KENV_SIZE) { printf( "WARNING: too many kenv strings, ignoring %s\n", cp); goto sanitize; } kenvp[i] = malloc(len, M_KENV, M_WAITOK); strcpy(kenvp[i++], cp); sanitize: #ifdef PRESERVE_EARLY_KENV continue; #else explicit_bzero(cp, len - 1); #endif } *curpos = i; } } /* * Setup the dynamic kernel environment. */ static void init_dynamic_kenv(void *data __unused) { int dynamic_envpos; int size; TUNABLE_INT_FETCH("kenv_mvallen", &kenv_mvallen); size = KENV_MNAMELEN + 1 + kenv_mvallen + 1; kenv_zone = uma_zcreate("kenv", size, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV, M_WAITOK | M_ZERO); dynamic_envpos = 0; init_dynamic_kenv_from(md_envp, &dynamic_envpos); init_dynamic_kenv_from(kern_envp, &dynamic_envpos); kenvp[dynamic_envpos] = NULL; mtx_init(&kenv_lock, "kernel environment", NULL, MTX_DEF); dynamic_kenv = true; } SYSINIT(kenv, SI_SUB_KMEM + 1, SI_ORDER_FIRST, init_dynamic_kenv, NULL); void freeenv(char *env) { if (dynamic_kenv && env != NULL) { explicit_bzero(env, strlen(env)); uma_zfree(kenv_zone, env); } } /* * Internal functions for string lookup. */ static char * _getenv_dynamic_locked(const char *name, int *idx) { char *cp; int len, i; len = strlen(name); for (cp = kenvp[0], i = 0; cp != NULL; cp = kenvp[++i]) { if ((strncmp(cp, name, len) == 0) && (cp[len] == '=')) { if (idx != NULL) *idx = i; return (cp + len + 1); } } return (NULL); } static char * _getenv_dynamic(const char *name, int *idx) { mtx_assert(&kenv_lock, MA_OWNED); return (_getenv_dynamic_locked(name, idx)); } static char * _getenv_static_from(char *chkenv, const char *name) { char *cp, *ep; int len; for (cp = chkenv; cp != NULL; cp = kernenv_next(cp)) { for (ep = cp; (*ep != '=') && (*ep != 0); ep++) ; if (*ep != '=') continue; len = ep - cp; ep++; if (!strncmp(name, cp, len) && name[len] == 0) return (ep); } return (NULL); } static char * _getenv_static(const char *name) { char *val; val = _getenv_static_from(md_envp, name); if (val != NULL) return (val); val = _getenv_static_from(kern_envp, name); if (val != NULL) return (val); return (NULL); } /* * Look up an environment variable by name. * Return a pointer to the string if found. * The pointer has to be freed with freeenv() * after use. */ char * kern_getenv(const char *name) { char *cp, *ret; int len; if (dynamic_kenv) { len = KENV_MNAMELEN + 1 + kenv_mvallen + 1; ret = uma_zalloc(kenv_zone, M_WAITOK | M_ZERO); mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, NULL); if (cp != NULL) strlcpy(ret, cp, len); mtx_unlock(&kenv_lock); if (cp == NULL) { uma_zfree(kenv_zone, ret); ret = NULL; } } else ret = _getenv_static(name); return (ret); } /* * Test if an environment variable is defined. */ int testenv(const char *name) { char *cp; cp = kenv_acquire(name); kenv_release(cp); if (cp != NULL) return (1); return (0); } /* * Set an environment variable in the MD-static environment. This cannot * feasibly be done on config(8)-generated static environments as they don't * generally include space for extra variables. */ static int setenv_static(const char *name, const char *value) { int len; if (md_env_pos >= md_env_len) return (-1); /* Check space for x=y and two nuls */ len = strlen(name) + strlen(value); if (len + 3 < md_env_len - md_env_pos) { len = sprintf(&md_envp[md_env_pos], "%s=%s", name, value); md_env_pos += len+1; md_envp[md_env_pos] = '\0'; return (0); } else return (-1); } /* * Set an environment variable by name. */ int kern_setenv(const char *name, const char *value) { char *buf, *cp, *oldenv; int namelen, vallen, i; if (!dynamic_kenv && md_env_len > 0) return (setenv_static(name, value)); KENV_CHECK; namelen = strlen(name) + 1; if (namelen > KENV_MNAMELEN + 1) return (-1); vallen = strlen(value) + 1; if (vallen > kenv_mvallen + 1) return (-1); buf = malloc(namelen + vallen, M_KENV, M_WAITOK); sprintf(buf, "%s=%s", name, value); mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, &i); if (cp != NULL) { oldenv = kenvp[i]; kenvp[i] = buf; mtx_unlock(&kenv_lock); free(oldenv, M_KENV); } else { /* We add the option if it wasn't found */ for (i = 0; (cp = kenvp[i]) != NULL; i++) ; /* Bounds checking */ if (i < 0 || i >= KENV_SIZE) { free(buf, M_KENV); mtx_unlock(&kenv_lock); return (-1); } kenvp[i] = buf; kenvp[i + 1] = NULL; mtx_unlock(&kenv_lock); } return (0); } /* * Unset an environment variable string. */ int kern_unsetenv(const char *name) { char *cp, *oldenv; int i, j; KENV_CHECK; mtx_lock(&kenv_lock); cp = _getenv_dynamic(name, &i); if (cp != NULL) { oldenv = kenvp[i]; for (j = i + 1; kenvp[j] != NULL; j++) kenvp[i++] = kenvp[j]; kenvp[i] = NULL; mtx_unlock(&kenv_lock); zfree(oldenv, M_KENV); return (0); } mtx_unlock(&kenv_lock); return (-1); } /* * Return the internal kenv buffer for the variable name, if it exists. * If the dynamic kenv is initialized and the name is present, return * with kenv_lock held. */ static char * kenv_acquire(const char *name) { char *value; if (dynamic_kenv) { mtx_lock(&kenv_lock); value = _getenv_dynamic(name, NULL); if (value == NULL) mtx_unlock(&kenv_lock); return (value); } else return (_getenv_static(name)); } /* * Undo a previous kenv_acquire() operation */ static void kenv_release(const char *buf) { if ((buf != NULL) && dynamic_kenv) mtx_unlock(&kenv_lock); } /* * Return a string value from an environment variable. */ int getenv_string(const char *name, char *data, int size) { char *cp; cp = kenv_acquire(name); if (cp != NULL) strlcpy(data, cp, size); kenv_release(cp); return (cp != NULL); } /* * Return an array of integers at the given type size and signedness. */ int getenv_array(const char *name, void *pdata, int size, int *psize, int type_size, bool allow_signed) { uint8_t shift; int64_t value; int64_t old; const char *buf; char *end; const char *ptr; int n; int rc; rc = 0; /* assume failure */ buf = kenv_acquire(name); if (buf == NULL) goto error; /* get maximum number of elements */ size /= type_size; n = 0; for (ptr = buf; *ptr != 0; ) { value = strtoq(ptr, &end, 0); /* check if signed numbers are allowed */ if (value < 0 && !allow_signed) goto error; /* check for invalid value */ if (ptr == end) goto error; /* check for valid suffix */ switch (*end) { case 't': case 'T': shift = 40; end++; break; case 'g': case 'G': shift = 30; end++; break; case 'm': case 'M': shift = 20; end++; break; case 'k': case 'K': shift = 10; end++; break; case ' ': case '\t': case ',': case 0: shift = 0; break; default: /* garbage after numeric value */ goto error; } /* skip till next value, if any */ while (*end == '\t' || *end == ',' || *end == ' ') end++; /* update pointer */ ptr = end; /* apply shift */ old = value; value <<= shift; /* overflow check */ if ((value >> shift) != old) goto error; /* check for buffer overflow */ if (n >= size) goto error; /* store value according to type size */ switch (type_size) { case 1: if (allow_signed) { if (value < SCHAR_MIN || value > SCHAR_MAX) goto error; } else { if (value < 0 || value > UCHAR_MAX) goto error; } ((uint8_t *)pdata)[n] = (uint8_t)value; break; case 2: if (allow_signed) { if (value < SHRT_MIN || value > SHRT_MAX) goto error; } else { if (value < 0 || value > USHRT_MAX) goto error; } ((uint16_t *)pdata)[n] = (uint16_t)value; break; case 4: if (allow_signed) { if (value < INT_MIN || value > INT_MAX) goto error; } else { if (value > UINT_MAX) goto error; } ((uint32_t *)pdata)[n] = (uint32_t)value; break; case 8: ((uint64_t *)pdata)[n] = (uint64_t)value; break; default: goto error; } n++; } *psize = n * type_size; if (n != 0) rc = 1; /* success */ error: kenv_release(buf); return (rc); } /* * Return an integer value from an environment variable. */ int getenv_int(const char *name, int *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (int) tmp; return (rval); } /* * Return an unsigned integer value from an environment variable. */ int getenv_uint(const char *name, unsigned int *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (unsigned int) tmp; return (rval); } /* * Return an int64_t value from an environment variable. */ int getenv_int64(const char *name, int64_t *data) { quad_t tmp; int64_t rval; rval = getenv_quad(name, &tmp); if (rval) *data = (int64_t) tmp; return (rval); } /* * Return an uint64_t value from an environment variable. */ int getenv_uint64(const char *name, uint64_t *data) { quad_t tmp; uint64_t rval; rval = getenv_quad(name, &tmp); if (rval) *data = (uint64_t) tmp; return (rval); } /* * Return a long value from an environment variable. */ int getenv_long(const char *name, long *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (long) tmp; return (rval); } /* * Return an unsigned long value from an environment variable. */ int getenv_ulong(const char *name, unsigned long *data) { quad_t tmp; int rval; rval = getenv_quad(name, &tmp); if (rval) *data = (unsigned long) tmp; return (rval); } /* * Return a quad_t value from an environment variable. */ int getenv_quad(const char *name, quad_t *data) { const char *value; char suffix, *vtp; quad_t iv; value = kenv_acquire(name); if (value == NULL) { goto error; } iv = strtoq(value, &vtp, 0); if (vtp == value || (vtp[0] != '\0' && vtp[1] != '\0')) { goto error; } suffix = vtp[0]; kenv_release(value); switch (suffix) { case 't': case 'T': iv *= 1024; /* FALLTHROUGH */ case 'g': case 'G': iv *= 1024; /* FALLTHROUGH */ case 'm': case 'M': iv *= 1024; /* FALLTHROUGH */ case 'k': case 'K': iv *= 1024; case '\0': break; default: return (0); } *data = iv; return (1); error: kenv_release(value); return (0); } /* * Return a boolean value from an environment variable. This can be in * numerical or string form, i.e. "1" or "true". */ int getenv_bool(const char *name, bool *data) { char *val; int ret = 0; if (name == NULL) return (0); val = kern_getenv(name); if (val == NULL) return (0); if ((strcmp(val, "1") == 0) || (strcasecmp(val, "true") == 0)) { *data = true; ret = 1; } else if ((strcmp(val, "0") == 0) || (strcasecmp(val, "false") == 0)) { *data = false; ret = 1; } else { /* Spit out a warning for malformed boolean variables. */ printf("Environment variable %s has non-boolean value \"%s\"\n", name, val); } freeenv(val); return (ret); } /* * Wrapper around getenv_bool to easily check for true. */ bool getenv_is_true(const char *name) { bool val; if (getenv_bool(name, &val) != 0) return (val); return (false); } /* * Wrapper around getenv_bool to easily check for false. */ bool getenv_is_false(const char *name) { bool val; if (getenv_bool(name, &val) != 0) return (!val); return (false); } /* * Find the next entry after the one which (cp) falls within, return a * pointer to its start or NULL if there are no more. */ static char * kernenv_next(char *cp) { if (cp != NULL) { while (*cp != 0) cp++; cp++; if (*cp == 0) cp = NULL; } return (cp); } void tunable_int_init(void *data) { struct tunable_int *d = (struct tunable_int *)data; TUNABLE_INT_FETCH(d->path, d->var); } void tunable_long_init(void *data) { struct tunable_long *d = (struct tunable_long *)data; TUNABLE_LONG_FETCH(d->path, d->var); } void tunable_ulong_init(void *data) { struct tunable_ulong *d = (struct tunable_ulong *)data; TUNABLE_ULONG_FETCH(d->path, d->var); } void tunable_int64_init(void *data) { struct tunable_int64 *d = (struct tunable_int64 *)data; TUNABLE_INT64_FETCH(d->path, d->var); } void tunable_uint64_init(void *data) { struct tunable_uint64 *d = (struct tunable_uint64 *)data; TUNABLE_UINT64_FETCH(d->path, d->var); } void tunable_quad_init(void *data) { struct tunable_quad *d = (struct tunable_quad *)data; TUNABLE_QUAD_FETCH(d->path, d->var); } void tunable_bool_init(void *data) { struct tunable_bool *d = (struct tunable_bool *)data; TUNABLE_BOOL_FETCH(d->path, d->var); } void tunable_str_init(void *data) { struct tunable_str *d = (struct tunable_str *)data; TUNABLE_STR_FETCH(d->path, d->var, d->size); } diff --git a/sys/sys/kenv.h b/sys/sys/kenv.h index 4c851631f343..eccdc027adcc 100644 --- a/sys/sys/kenv.h +++ b/sys/sys/kenv.h @@ -1,59 +1,61 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2002 Maxime Henrion * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _SYS_KENV_H_ #define _SYS_KENV_H_ /* * Constants for the kenv(2) syscall */ -#define KENV_GET 0 -#define KENV_SET 1 -#define KENV_UNSET 2 -#define KENV_DUMP 3 +#define KENV_GET 0 +#define KENV_SET 1 +#define KENV_UNSET 2 +#define KENV_DUMP 3 +#define KENV_DUMP_LOADER 4 +#define KENV_DUMP_STATIC 5 #define KENV_MNAMELEN 128 /* Maximum name length (for the syscall) */ #define KENV_MVALLEN 128 /* Maximum value length (for the syscall) */ #ifdef _KERNEL /* * Most of these variables should be const. */ extern bool dynamic_kenv; extern struct mtx kenv_lock; extern char *kern_envp; extern char *md_envp; extern char static_env[]; extern char static_hints[]; /* by config for now */ extern char **kenvp; #endif /* _KERNEL */ #endif /* !_SYS_KENV_H_ */