Index: sys/kern/kern_environment.c =================================================================== --- sys/kern/kern_environment.c +++ sys/kern/kern_environment.c @@ -57,14 +57,21 @@ #include +static char *_getenv_dynamic_locked(const char *name, int *idx); +static char *_getenv_dynamic(const char *name, int *idx); + static MALLOC_DEFINE(M_KENV, "kenv", "kernel environment"); #define KENV_SIZE 512 /* Maximum number of environment strings */ -/* pointer to the static environment */ +/* pointer to the config-generated static environment */ char *kern_envp; -static int env_len; -static int env_pos; + +/* 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 */ @@ -220,15 +227,8 @@ * 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. * - * When a copy of an initial environment is passed in, we start by scanning that - * env for overrides to the compiled-in envmode and hintmode variables. - * - * If the global envmode is 1, the environment is initialized from the global - * static_env[], regardless of the arguments passed. This implements the env - * keyword described in config(5). In this case env_pos is set to env_len, - * causing kern_setenv() to return -1 (if len > 0) or panic (if len == 0) until - * the dynamic environment is available. The envmode and static_env variables - * are defined in env.c which is generated by config(8). + * 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 @@ -237,48 +237,53 @@ * 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(). 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. + * 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) { - char *cp; - - for (cp = buf; cp != NULL && cp[0] != '\0'; cp += strlen(cp) + 1) { - if (strcmp(cp, "static_env.disabled=1") == 0) - envmode = 0; - if (strcmp(cp, "static_hints.disabled=1") == 0) - hintmode = 0; - } - - if (envmode == 1) { + char *eval; + + md_envp = buf; + md_env_len = len; + md_env_pos = 0; + + /* + * 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. + */ + eval = kern_getenv("static_env.disabled"); + if (eval == NULL || strcmp(eval, "1") != 0) kern_envp = static_env; - env_len = len; - env_pos = len; - } else { - kern_envp = buf; - env_len = len; - env_pos = 0; - } + eval = kern_getenv("static_hints.disabled"); + if (eval == NULL || strcmp(eval, "1") != 0) + *static_hints = '\0'; } -/* - * Setup the dynamic kernel environment. - */ static void -init_dynamic_kenv(void *data __unused) +init_dynamic_kenv_from(char *init_env, int *curpos) { - char *cp, *cpnext; - size_t len; - int i; - - kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV, - M_WAITOK | M_ZERO); - i = 0; - if (kern_envp && *kern_envp != '\0') { - for (cp = kern_envp; cp != NULL; cp = cpnext) { + char *cp, *cpnext, *eqpos, *found; + size_t len, prevlen; + int i, pos; + + 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) { @@ -287,17 +292,71 @@ cp); continue; } - if (i < KENV_SIZE) { - kenvp[i] = malloc(len, M_KENV, M_WAITOK); - strcpy(kenvp[i++], cp); - explicit_bzero(cp, strlen(cp)); - } else + eqpos = strchr(cp, '='); + if (eqpos == NULL) { + printf( + "WARNING: malformed static env value, ignoring %s\n", + cp); + continue; + } + *eqpos = 0; + /* De-dupe the environment as we go. */ + found = _getenv_dynamic_locked(cp, &pos); + *eqpos = '='; + if (found == NULL && i > KENV_SIZE) { printf( "WARNING: too many kenv strings, ignoring %s\n", cp); + continue; + } else if (found != NULL) { + prevlen = strlen(kenvp[pos]); + /* + * Wipe out all traces of the bits we + * won't be overwriting. + */ + if (prevlen > len - 1) + explicit_bzero(kenvp[pos] + len, + prevlen - len + 1); + else if (prevlen < len - 1) + kenvp[pos] = realloc(kenvp[pos], len, + M_KENV, M_WAITOK); + } else { + /* + * Advance only if new slot; i.e. we didn't + * search or we searched and found nothing. + */ + pos = i++; + kenvp[pos] = malloc(len, M_KENV, + M_WAITOK); + } + if (kenvp[pos] == NULL) { + printf( + "WARNING: not enough memory for kenv, ignoring %s\n", + cp); + continue; + } + strcpy(kenvp[pos], cp); + explicit_bzero(cp, len - 1); } + *curpos = i; } - kenvp[i] = NULL; +} + +/* + * Setup the dynamic kernel environment. + */ +static void +init_dynamic_kenv(void *data __unused) +{ + int dynamic_envpos; + + kenvp = malloc((KENV_SIZE + 1) * sizeof(char *), M_KENV, + M_WAITOK | M_ZERO); + + dynamic_envpos = 0; + init_dynamic_kenv_from(kern_envp, &dynamic_envpos); + init_dynamic_kenv_from(md_envp, &dynamic_envpos); + kenvp[dynamic_envpos] = NULL; mtx_init(&kenv_lock, "kernel environment", NULL, MTX_DEF); dynamic_kenv = 1; @@ -318,12 +377,11 @@ * Internal functions for string lookup. */ static char * -_getenv_dynamic(const char *name, int *idx) +_getenv_dynamic_locked(const char *name, int *idx) { char *cp; int len, i; - mtx_assert(&kenv_lock, MA_OWNED); len = strlen(name); for (cp = kenvp[0], i = 0; cp != NULL; cp = kenvp[++i]) { if ((strncmp(cp, name, len) == 0) && @@ -337,12 +395,20 @@ } static char * -_getenv_static(const char *name) +_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 = kern_envp; cp != NULL; cp = kernenv_next(cp)) { + for (cp = chkenv; cp != NULL; cp = kernenv_next(cp)) { for (ep = cp; (*ep != '=') && (*ep != 0); ep++) ; if (*ep != '=') @@ -355,6 +421,20 @@ 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. @@ -399,20 +479,25 @@ 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 (env_pos >= env_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 < env_len - env_pos) { - len = sprintf(&kern_envp[env_pos], "%s=%s", name, value); - env_pos += len+1; - kern_envp[env_pos] = '\0'; + 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); @@ -428,7 +513,7 @@ char *buf, *cp, *oldenv; int namelen, vallen, i; - if (dynamic_kenv == 0 && env_len > 0) + if (dynamic_kenv == 0 && md_env_len > 0) return (setenv_static(name, value)); KENV_CHECK; Index: sys/kern/subr_hints.c =================================================================== --- sys/kern/subr_hints.c +++ sys/kern/subr_hints.c @@ -31,60 +31,35 @@ #include #include +#include #include #include #include #include #include -#define HINTMODE_KENV 0 -#define HINTMODE_STATIC 1 -#define HINTMODE_FALLBACK 2 +#define FBACK_MDENV 0 /* MD env (e.g. loader.conf) */ +#define FBACK_STENV 1 /* Static env */ +#define FBACK_STATIC 2 /* static_hints */ /* - * Access functions for device resources. + * We'll use hintenv_merged to indicate that the dynamic environment has been + * properly prepared for hint usage. This implies that the dynamic environment + * has already been setup (dynamic_kenv) and that we have added any supplied + * static_hints to the dynamic environment. */ - -static int checkmethod = 1; -static char *hintp; +static int hintenv_merged; /* - * Define kern.hintmode sysctl, which only accept value 2, that cause to - * switch from Static KENV mode to Dynamic KENV. So systems that have hints - * compiled into kernel will be able to see/modify KENV (and hints too). + * Access functions for device resources. */ -static int -sysctl_hintmode(SYSCTL_HANDLER_ARGS) +static void +static_hints_to_env(void *data __unused) { const char *cp; char *line, *eq; - int eqidx, error, i, value; - - value = hintmode; - - /* Fetch candidate for new hintmode value */ - error = sysctl_handle_int(oidp, &value, 0, req); - if (error || req->newptr == NULL) - return (error); - - if (value != HINTMODE_FALLBACK) - /* Only accept swithing to hintmode 2 */ - return (EINVAL); - - /* - * The rest of the sysctl handler is just making sure that our - * environment is consistent with the world we've already seen. - * If we came from kenv at all, then we have nothing to do: static - * kenv will get merged into dynamic kenv as soon as kmem becomes - * available, dynamic kenv is the environment we'd be setting these - * things in anyways. Therefore, we have nothing left to do unless - * we came from a static hints configuration. - */ - if (hintmode != HINTMODE_STATIC) { - hintmode = value; - return (0); - } + int eqidx, i; cp = static_hints; while (cp && *cp != '\0') { @@ -95,20 +70,48 @@ eqidx = eq - cp; i = strlen(cp); - line = malloc(i+1, M_TEMP, M_WAITOK); + line = malloc(i + 1, M_TEMP, M_WAITOK); strcpy(line, cp); - line[eqidx] = '\0'; - kern_setenv(line, line + eqidx + 1); + line[eqidx] = line[i] = '\0'; + /* + * Before adding a hint to the dynamic environment, check if + * another value for said hint has already been added. This is + * needed because static environment overrides static hints and + * dynamic environment overrides all. + */ + if (testenv(line) == 0) + kern_setenv(line, line + eqidx + 1); free(line, M_TEMP); cp += i + 1; } - - hintmode = value; - return (0); + hintenv_merged = 1; } -SYSCTL_PROC(_kern, OID_AUTO, hintmode, CTLTYPE_INT|CTLFLAG_RW, - &hintmode, 0, sysctl_hintmode, "I", "Get/set current hintmode"); +/* Any time after dynamic env is setup */ +SYSINIT(hintenv, SI_SUB_KMEM, SI_ORDER_ANY, static_hints_to_env, NULL); + +/* + * Checks the environment to see if we even have any hints. If it has no hints, + * then res_find can take the hint that there's no point in searching it and + * either move on to the next environment or fail early. + */ +static int +_res_checkenv(char *envp) +{ + char *cp; + + cp = envp; + while (cp) { + if (strncmp(cp, "hint.", 5) == 0) + return (0); + while (*cp != '\0') + cp++; + cp++; + if (*cp == '\0') + break; + } + return (1); +} /* * Evil wildcarding resource string lookup. @@ -116,82 +119,103 @@ * The start point can be remembered for incremental searches. */ static int -res_find(int *line, int *startln, +res_find(char **hintp_cookie, int *line, int *startln, const char *name, int *unit, const char *resname, const char *value, const char **ret_name, int *ret_namelen, int *ret_unit, const char **ret_resname, int *ret_resnamelen, const char **ret_value) { - int n = 0, hit, i = 0; + int dyn_used = 0, fbacklvl = FBACK_MDENV, hit, i = 0, n = 0; char r_name[32]; - int r_unit, use_kenv = (hintmode != HINTMODE_STATIC && dynamic_kenv); + int r_unit; char r_resname[32]; char r_value[128]; const char *s, *cp; - char *p; + char *hintp, *p; - if (checkmethod) { - hintp = NULL; - switch (hintmode) { - case HINTMODE_KENV: /* loader hints in environment only */ - break; - case HINTMODE_STATIC: /* static hints only */ - hintp = static_hints; - checkmethod = 0; - break; - case HINTMODE_FALLBACK: /* fallback mode */ - if (dynamic_kenv) { - mtx_lock(&kenv_lock); - cp = kenvp[0]; - for (i = 0; cp != NULL; cp = kenvp[++i]) { - if (!strncmp(cp, "hint.", 5)) { - use_kenv = 1; - checkmethod = 0; - break; - } - } - mtx_unlock(&kenv_lock); - } else { - cp = kern_envp; - while (cp) { - if (strncmp(cp, "hint.", 5) == 0) { - cp = NULL; - hintp = kern_envp; - break; - } - while (*cp != '\0') - cp++; - cp++; - if (*cp == '\0') { - cp = NULL; - hintp = static_hints; - break; - } + /* + * We are expecting that the caller will pass us a hintp_cookie that + * they are tracking. Upon entry, if *hintp_cookie is *not* set, this + * indicates to us that we should be figuring out based on the current + * environment where to search. This keeps us sane throughout the + * entirety of a single search. + */ + if (*hintp_cookie == NULL) { + hintp = NULL; + if (hintenv_merged) { + /* + * static_hints, if it was previously used, has + * already been folded in to the environment + * by this point. + */ + mtx_lock(&kenv_lock); + cp = kenvp[0]; + for (i = 0; cp != NULL; cp = kenvp[++i]) { + if (!strncmp(cp, "hint.", 5)) { + hintp = kenvp[0]; + break; } } - break; - default: - break; - } - if (hintp == NULL) { - if (dynamic_kenv) { - use_kenv = 1; - checkmethod = 0; - } else + mtx_unlock(&kenv_lock); + dyn_used = 1; + } else { + /* + * We'll have a chance to keep coming back here until + * we've actually exhausted all of our possibilities. + * We might have chosen the MD/Static env because it + * had some kind of hints, but perhaps it didn't have + * the hint we are looking for. We don't provide any + * fallback when searching the dynamic environment. + */ +fallback: + if (dyn_used || fbacklvl >= FBACK_STATIC) + return (ENOENT); + + if (fbacklvl <= FBACK_MDENV && + _res_checkenv(md_envp) == 0) { + hintp = md_envp; + goto found; + } + fbacklvl++; + + if (fbacklvl <= FBACK_STENV && + _res_checkenv(kern_envp) == 0) { hintp = kern_envp; + goto found; + } + fbacklvl++; + + /* We'll fallback to static_hints if needed/can */ + if (fbacklvl <= FBACK_STATIC && + _res_checkenv(static_hints) == 0) + hintp = static_hints; +found: + fbacklvl++; } + + if (hintp == NULL) + return (ENOENT); + *hintp_cookie = hintp; + } else { + hintp = *hintp_cookie; + if (hintenv_merged && hintp == kenvp[0]) + dyn_used = 1; + else + /* + * If we aren't using the dynamic environment, we need + * to run through the proper fallback procedure again. + * This is so that we do continuations right if we're + * working with *line and *startln. + */ + goto fallback; } - if (use_kenv) { + if (dyn_used) { mtx_lock(&kenv_lock); i = 0; - cp = kenvp[0]; - if (cp == NULL) { - mtx_unlock(&kenv_lock); - return (ENOENT); - } - } else - cp = hintp; + } + + cp = hintp; while (cp) { hit = 1; (*line)++; @@ -200,25 +224,28 @@ else n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s", r_name, &r_unit, r_resname, r_value); - if (hit && n != 4) { - printf("CONFIG: invalid hint '%s'\n", cp); - p = strchr(cp, 'h'); - *p = 'H'; - hit = 0; + /* We'll circumvent all of the checks if we already know */ + if (hit) { + if (n != 4) { + printf("CONFIG: invalid hint '%s'\n", cp); + p = strchr(cp, 'h'); + *p = 'H'; + hit = 0; + } + if (hit && startln && *startln >= 0 && *line < *startln) + hit = 0; + if (hit && name && strcmp(name, r_name) != 0) + hit = 0; + if (hit && unit && *unit != r_unit) + hit = 0; + if (hit && resname && strcmp(resname, r_resname) != 0) + hit = 0; + if (hit && value && strcmp(value, r_value) != 0) + hit = 0; + if (hit) + break; } - if (hit && startln && *startln >= 0 && *line < *startln) - hit = 0; - if (hit && name && strcmp(name, r_name) != 0) - hit = 0; - if (hit && unit && *unit != r_unit) - hit = 0; - if (hit && resname && strcmp(resname, r_resname) != 0) - hit = 0; - if (hit && value && strcmp(value, r_value) != 0) - hit = 0; - if (hit) - break; - if (use_kenv) { + if (dyn_used) { cp = kenvp[++i]; if (cp == NULL) break; @@ -232,10 +259,10 @@ } } } - if (use_kenv) + if (dyn_used) mtx_unlock(&kenv_lock); if (cp == NULL) - return ENOENT; + goto fallback; s = cp; /* This is a bit of a hack, but at least is reentrant */ @@ -273,11 +300,13 @@ { int i; int un; + char *hintp; *line = 0; + hintp = NULL; /* Search for exact unit matches first */ - i = res_find(line, startln, name, unit, resname, value, + i = res_find(&hintp, line, startln, name, unit, resname, value, ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, ret_value); if (i == 0) @@ -286,7 +315,7 @@ return ENOENT; /* If we are still here, search for wildcard matches */ un = -1; - i = res_find(line, startln, name, &un, resname, value, + i = res_find(&hintp, line, startln, name, &un, resname, value, ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, ret_value); if (i == 0) Index: sys/sys/systm.h =================================================================== --- sys/sys/systm.h +++ sys/sys/systm.h @@ -150,11 +150,10 @@ * XXX most of these variables should be const. */ extern int osreldate; -extern int envmode; -extern int hintmode; /* 0 = off. 1 = config, 2 = fallback */ extern int 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 */ Index: usr.sbin/config/config.h =================================================================== --- usr.sbin/config/config.h +++ usr.sbin/config/config.h @@ -178,8 +178,6 @@ extern char *env; extern char kernconfstr[]; extern int do_trace; -extern int envmode; -extern int hintmode; extern int incignore; char *get_word(FILE *); Index: usr.sbin/config/config.5 =================================================================== --- usr.sbin/config/config.5 +++ usr.sbin/config/config.5 @@ -23,7 +23,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 22, 2018 +.Dd June 25, 2018 .Dt CONFIG 5 .Os .Sh NAME @@ -114,12 +114,24 @@ .Pp .It Ic env Ar filename Specifies a filename containing a kernel environment definition. -The kernel normally uses an environment prepared for it at boot time -by +.Pp +The kernel will augment this compiled-in environment with the environment +prepared for it at boot time by .Xr loader 8 . -This directive makes the kernel ignore the boot environment and use -the compiled-in environment instead, unless the boot environment contains -.Va static_env.disabled=1 . +Environment variables specified in the +.Xr loader 8 +environment will take precedence over environment variables specified in +.Ar filename , +and environment variables specified in the dynamic environment take precedence +over both of these. +.Pp +.Va static_env.disabled=1 +may be specified in the +.Xr loader 8 +environment to disable use of this compiled-in environment. +This option has no effect if specified in any environment after the +.Xr loader 8 +environment is processed. .Pp This directive is useful for setting kernel tunables in embedded environments that do not start from @@ -156,9 +168,28 @@ .Xr device.hints 5 ) . This directive configures the kernel to use the static device configuration listed in -.Ar filename , -unless the boot environment contains -.Va static_hints.disabled=1 . +.Ar filename . +.Pp +Hints provided in this static device configuration will be overwritten in the +order in which they're encountered. +Hints in the compiled-in environment takes precedence over compiled-in hints, +and hints in the environment prepared for the kernel by +.Xr loader 8 +takes precedence over hints in the compiled-in environment. +.Pp +Once the dynamic environment becomes available, all compiled-in hints will be +added to the dynamic environment if they do not already have an override in +the dynamic environment. +The dynamic environment will then be used for all searches of hints. +.Pp +.Va static_hints.disabled=1 +may be specified in either a compiled-in environment or the +.Xr loader 8 +environment to disable use of these hints files. +This option has no effect if specified in any environment after the +.Xr loader 8 +environment is processed. +.Pp The file .Ar filename must conform to the syntax specified by Index: usr.sbin/config/config.y =================================================================== --- usr.sbin/config/config.y +++ usr.sbin/config/config.y @@ -83,8 +83,6 @@ struct device_head dtab; char *ident; char *env; -int envmode; -int hintmode; int yyline; const char *yyfile; struct file_list_head ftab; @@ -193,7 +191,6 @@ PROFILE NUMBER { profiling = $2; } | ENV ID { env = $2; - envmode = 1; } | ENVVAR ENVLINE { struct envvar *envvar; @@ -212,7 +209,6 @@ err(EXIT_FAILURE, "calloc"); hint->hint_name = $2; STAILQ_INSERT_TAIL(&hints, hint, hint_next); - hintmode = 1; } System_spec: Index: usr.sbin/config/mkmakefile.c =================================================================== --- usr.sbin/config/mkmakefile.c +++ usr.sbin/config/mkmakefile.c @@ -197,7 +197,6 @@ fprintf(ofp, "#include \n"); fprintf(ofp, "#include \n"); fprintf(ofp, "\n"); - fprintf(ofp, "int hintmode = %d;\n", hintmode); fprintf(ofp, "char static_hints[] = {\n"); STAILQ_FOREACH(hint, &hints, hint_next) { ifp = fopen(hint->hint_name, "r"); @@ -319,7 +318,6 @@ fprintf(ofp, "#include \n"); fprintf(ofp, "#include \n"); fprintf(ofp, "\n"); - fprintf(ofp, "int envmode = %d;\n", envmode); fprintf(ofp, "char static_env[] = {\n"); if (ifp) { while (fgets(line, BUFSIZ, ifp) != NULL) {