The refactoring in r335479 overlooked the fact that the dynamic kenv can also be switched to if hintmode == 0. This is problematic because the checkmethod bits are only ever ran once, but it worked previously because the use_kenv was a global state and the first lookup would enable it if occurring after the dynamic environment has been setup. Extending our local definition of use_kenv to include all non-STATIC hintmodes as long as the dynamic_kenv is setup fixes this. We still have potential issues if the dynamic kenv comes up while we're doing an anchored search through the environment, but this is not much of a concern right now because: 1.) The dynamic environment comes up super early in boot, just after kmem 2.) This is going to get rewritten to provide a safer mechanism for the anchored searches, ensuring that we continue using the same environment chain (dynamic env or static fallback) for all anchored search invocations Reported by: mmamcy X-MFC-With: r335479
480 lines
12 KiB
C
480 lines
12 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2000,2001 Peter Wemm <peter@FreeBSD.org>
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
|
|
#define HINTMODE_KENV 0
|
|
#define HINTMODE_STATIC 1
|
|
#define HINTMODE_FALLBACK 2
|
|
|
|
/*
|
|
* Access functions for device resources.
|
|
*/
|
|
|
|
static int checkmethod = 1;
|
|
static char *hintp;
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
|
|
static int
|
|
sysctl_hintmode(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
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);
|
|
}
|
|
|
|
cp = static_hints;
|
|
while (cp && *cp != '\0') {
|
|
eq = strchr(cp, '=');
|
|
if (eq == NULL)
|
|
/* Bad hint value */
|
|
continue;
|
|
eqidx = eq - cp;
|
|
|
|
i = strlen(cp);
|
|
line = malloc(i+1, M_TEMP, M_WAITOK);
|
|
strcpy(line, cp);
|
|
line[eqidx] = '\0';
|
|
kern_setenv(line, line + eqidx + 1);
|
|
free(line, M_TEMP);
|
|
cp += i + 1;
|
|
}
|
|
|
|
hintmode = value;
|
|
return (0);
|
|
}
|
|
|
|
SYSCTL_PROC(_kern, OID_AUTO, hintmode, CTLTYPE_INT|CTLFLAG_RW,
|
|
&hintmode, 0, sysctl_hintmode, "I", "Get/set current hintmode");
|
|
|
|
/*
|
|
* Evil wildcarding resource string lookup.
|
|
* This walks the supplied env string table and returns a match.
|
|
* The start point can be remembered for incremental searches.
|
|
*/
|
|
static int
|
|
res_find(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;
|
|
char r_name[32];
|
|
int r_unit, use_kenv = (hintmode != HINTMODE_STATIC && dynamic_kenv);
|
|
char r_resname[32];
|
|
char r_value[128];
|
|
const char *s, *cp;
|
|
char *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;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (hintp == NULL) {
|
|
if (dynamic_kenv) {
|
|
use_kenv = 1;
|
|
checkmethod = 0;
|
|
} else
|
|
hintp = kern_envp;
|
|
}
|
|
}
|
|
|
|
if (use_kenv) {
|
|
mtx_lock(&kenv_lock);
|
|
i = 0;
|
|
cp = kenvp[0];
|
|
if (cp == NULL) {
|
|
mtx_unlock(&kenv_lock);
|
|
return (ENOENT);
|
|
}
|
|
} else
|
|
cp = hintp;
|
|
while (cp) {
|
|
hit = 1;
|
|
(*line)++;
|
|
if (strncmp(cp, "hint.", 5) != 0)
|
|
hit = 0;
|
|
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;
|
|
}
|
|
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) {
|
|
cp = kenvp[++i];
|
|
if (cp == NULL)
|
|
break;
|
|
} else {
|
|
while (*cp != '\0')
|
|
cp++;
|
|
cp++;
|
|
if (*cp == '\0') {
|
|
cp = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (use_kenv)
|
|
mtx_unlock(&kenv_lock);
|
|
if (cp == NULL)
|
|
return ENOENT;
|
|
|
|
s = cp;
|
|
/* This is a bit of a hack, but at least is reentrant */
|
|
/* Note that it returns some !unterminated! strings. */
|
|
s = strchr(s, '.') + 1; /* start of device */
|
|
if (ret_name)
|
|
*ret_name = s;
|
|
s = strchr(s, '.') + 1; /* start of unit */
|
|
if (ret_namelen && ret_name)
|
|
*ret_namelen = s - *ret_name - 1; /* device length */
|
|
if (ret_unit)
|
|
*ret_unit = r_unit;
|
|
s = strchr(s, '.') + 1; /* start of resname */
|
|
if (ret_resname)
|
|
*ret_resname = s;
|
|
s = strchr(s, '=') + 1; /* start of value */
|
|
if (ret_resnamelen && ret_resname)
|
|
*ret_resnamelen = s - *ret_resname - 1; /* value len */
|
|
if (ret_value)
|
|
*ret_value = s;
|
|
if (startln) /* line number for anchor */
|
|
*startln = *line + 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Search all the data sources for matches to our query. We look for
|
|
* dynamic hints first as overrides for static or fallback hints.
|
|
*/
|
|
static int
|
|
resource_find(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 i;
|
|
int un;
|
|
|
|
*line = 0;
|
|
|
|
/* Search for exact unit matches first */
|
|
i = res_find(line, startln, name, unit, resname, value,
|
|
ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen,
|
|
ret_value);
|
|
if (i == 0)
|
|
return 0;
|
|
if (unit == NULL)
|
|
return ENOENT;
|
|
/* If we are still here, search for wildcard matches */
|
|
un = -1;
|
|
i = res_find(line, startln, name, &un, resname, value,
|
|
ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen,
|
|
ret_value);
|
|
if (i == 0)
|
|
return 0;
|
|
return ENOENT;
|
|
}
|
|
|
|
int
|
|
resource_int_value(const char *name, int unit, const char *resname, int *result)
|
|
{
|
|
int error;
|
|
const char *str;
|
|
char *op;
|
|
unsigned long val;
|
|
int line;
|
|
|
|
line = 0;
|
|
error = resource_find(&line, NULL, name, &unit, resname, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, &str);
|
|
if (error)
|
|
return error;
|
|
if (*str == '\0')
|
|
return EFTYPE;
|
|
val = strtoul(str, &op, 0);
|
|
if (*op != '\0')
|
|
return EFTYPE;
|
|
*result = val;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
resource_long_value(const char *name, int unit, const char *resname,
|
|
long *result)
|
|
{
|
|
int error;
|
|
const char *str;
|
|
char *op;
|
|
unsigned long val;
|
|
int line;
|
|
|
|
line = 0;
|
|
error = resource_find(&line, NULL, name, &unit, resname, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, &str);
|
|
if (error)
|
|
return error;
|
|
if (*str == '\0')
|
|
return EFTYPE;
|
|
val = strtoul(str, &op, 0);
|
|
if (*op != '\0')
|
|
return EFTYPE;
|
|
*result = val;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
resource_string_value(const char *name, int unit, const char *resname,
|
|
const char **result)
|
|
{
|
|
int error;
|
|
const char *str;
|
|
int line;
|
|
|
|
line = 0;
|
|
error = resource_find(&line, NULL, name, &unit, resname, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, &str);
|
|
if (error)
|
|
return error;
|
|
*result = str;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is a bit nasty, but allows us to not modify the env strings.
|
|
*/
|
|
static const char *
|
|
resource_string_copy(const char *s, int len)
|
|
{
|
|
static char stringbuf[256];
|
|
static int offset = 0;
|
|
const char *ret;
|
|
|
|
if (len == 0)
|
|
len = strlen(s);
|
|
if (len > 255)
|
|
return NULL;
|
|
if ((offset + len + 1) > 255)
|
|
offset = 0;
|
|
bcopy(s, &stringbuf[offset], len);
|
|
stringbuf[offset + len] = '\0';
|
|
ret = &stringbuf[offset];
|
|
offset += len + 1;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* err = resource_find_match(&anchor, &name, &unit, resname, value)
|
|
* Iteratively fetch a list of devices wired "at" something
|
|
* res and value are restrictions. eg: "at", "scbus0".
|
|
* For practical purposes, res = required, value = optional.
|
|
* *name and *unit are set.
|
|
* set *anchor to zero before starting.
|
|
*/
|
|
int
|
|
resource_find_match(int *anchor, const char **name, int *unit,
|
|
const char *resname, const char *value)
|
|
{
|
|
const char *found_name;
|
|
int found_namelen;
|
|
int found_unit;
|
|
int ret;
|
|
int newln;
|
|
|
|
newln = *anchor;
|
|
ret = resource_find(anchor, &newln, NULL, NULL, resname, value,
|
|
&found_name, &found_namelen, &found_unit, NULL, NULL, NULL);
|
|
if (ret == 0) {
|
|
*name = resource_string_copy(found_name, found_namelen);
|
|
*unit = found_unit;
|
|
}
|
|
*anchor = newln;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* err = resource_find_dev(&anchor, name, &unit, res, value);
|
|
* Iterate through a list of devices, returning their unit numbers.
|
|
* res and value are optional restrictions. eg: "at", "scbus0".
|
|
* *unit is set to the value.
|
|
* set *anchor to zero before starting.
|
|
*/
|
|
int
|
|
resource_find_dev(int *anchor, const char *name, int *unit,
|
|
const char *resname, const char *value)
|
|
{
|
|
int found_unit;
|
|
int newln;
|
|
int ret;
|
|
|
|
newln = *anchor;
|
|
ret = resource_find(anchor, &newln, name, NULL, resname, value,
|
|
NULL, NULL, &found_unit, NULL, NULL, NULL);
|
|
if (ret == 0) {
|
|
*unit = found_unit;
|
|
}
|
|
*anchor = newln;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check to see if a device is disabled via a disabled hint.
|
|
*/
|
|
int
|
|
resource_disabled(const char *name, int unit)
|
|
{
|
|
int error, value;
|
|
|
|
error = resource_int_value(name, unit, "disabled", &value);
|
|
if (error)
|
|
return (0);
|
|
return (value);
|
|
}
|
|
|
|
/*
|
|
* Clear a value associated with a device by removing it from
|
|
* the kernel environment. This only removes a hint for an
|
|
* exact unit.
|
|
*/
|
|
int
|
|
resource_unset_value(const char *name, int unit, const char *resname)
|
|
{
|
|
char varname[128];
|
|
const char *retname, *retvalue;
|
|
int error, line;
|
|
size_t len;
|
|
|
|
line = 0;
|
|
error = resource_find(&line, NULL, name, &unit, resname, NULL,
|
|
&retname, NULL, NULL, NULL, NULL, &retvalue);
|
|
if (error)
|
|
return (error);
|
|
|
|
retname -= strlen("hint.");
|
|
len = retvalue - retname - 1;
|
|
if (len > sizeof(varname) - 1)
|
|
return (ENAMETOOLONG);
|
|
memcpy(varname, retname, len);
|
|
varname[len] = '\0';
|
|
return (kern_unsetenv(varname));
|
|
}
|