1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2000,2001 Peter Wemm <peter@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __FBSDID("$FreeBSD$"); 31 32 #include <sys/param.h> 33 #include <sys/lock.h> 34 #include <sys/kenv.h> 35 #include <sys/kernel.h> 36 #include <sys/malloc.h> 37 #include <sys/mutex.h> 38 #include <sys/sysctl.h> 39 #include <sys/systm.h> 40 #include <sys/bus.h> 41 42 #define FBACK_MDENV 0 /* MD env (e.g. loader.conf) */ 43 #define FBACK_STENV 1 /* Static env */ 44 #define FBACK_STATIC 2 /* static_hints */ 45 46 /* 47 * We'll use hintenv_merged to indicate that the dynamic environment has been 48 * properly prepared for hint usage. This implies that the dynamic environment 49 * has already been setup (dynamic_kenv) and that we have added any supplied 50 * static_hints to the dynamic environment. 51 */ 52 static bool hintenv_merged; 53 /* Static environment and static hints cannot change, so we'll skip known bad */ 54 static bool stenv_skip; 55 static bool sthints_skip; 56 /* 57 * Access functions for device resources. 58 */ 59 60 static void 61 static_hints_to_env(void *data __unused) 62 { 63 const char *cp; 64 char *line, *eq; 65 int eqidx, i; 66 67 cp = static_hints; 68 while (cp && *cp != '\0') { 69 eq = strchr(cp, '='); 70 if (eq == NULL) 71 /* Bad hint value */ 72 continue; 73 eqidx = eq - cp; 74 75 i = strlen(cp); 76 line = malloc(i + 1, M_TEMP, M_WAITOK); 77 strcpy(line, cp); 78 line[eqidx] = line[i] = '\0'; 79 /* 80 * Before adding a hint to the dynamic environment, check if 81 * another value for said hint has already been added. This is 82 * needed because static environment overrides static hints and 83 * dynamic environment overrides all. 84 */ 85 if (testenv(line) == 0) 86 kern_setenv(line, line + eqidx + 1); 87 free(line, M_TEMP); 88 cp += i + 1; 89 } 90 hintenv_merged = true; 91 } 92 93 /* Any time after dynamic env is setup */ 94 SYSINIT(hintenv, SI_SUB_KMEM + 1, SI_ORDER_SECOND, static_hints_to_env, NULL); 95 96 /* 97 * Checks the environment to see if we even have any hints. If it has no hints, 98 * then res_find can take the hint that there's no point in searching it and 99 * either move on to the next environment or fail early. 100 */ 101 static bool 102 _res_checkenv(char *envp) 103 { 104 char *cp; 105 106 cp = envp; 107 while (cp) { 108 if (strncmp(cp, "hint.", 5) == 0) 109 return (true); 110 while (*cp != '\0') 111 cp++; 112 cp++; 113 if (*cp == '\0') 114 break; 115 } 116 return (false); 117 } 118 119 /* 120 * Evil wildcarding resource string lookup. 121 * This walks the supplied env string table and returns a match. 122 * The start point can be remembered for incremental searches. 123 */ 124 static int 125 res_find(char **hintp_cookie, int *line, int *startln, 126 const char *name, int *unit, const char *resname, const char *value, 127 const char **ret_name, int *ret_namelen, int *ret_unit, 128 const char **ret_resname, int *ret_resnamelen, const char **ret_value) 129 { 130 int fbacklvl = FBACK_MDENV, i = 0, n = 0; 131 char r_name[32]; 132 int r_unit; 133 char r_resname[32]; 134 char r_value[128]; 135 const char *s, *cp; 136 char *hintp, *p; 137 bool dyn_used = false; 138 139 /* 140 * We are expecting that the caller will pass us a hintp_cookie that 141 * they are tracking. Upon entry, if *hintp_cookie is *not* set, this 142 * indicates to us that we should be figuring out based on the current 143 * environment where to search. This keeps us sane throughout the 144 * entirety of a single search. 145 */ 146 if (*hintp_cookie == NULL) { 147 hintp = NULL; 148 if (hintenv_merged) { 149 /* 150 * static_hints, if it was previously used, has 151 * already been folded in to the environment 152 * by this point. 153 */ 154 mtx_lock(&kenv_lock); 155 cp = kenvp[0]; 156 for (i = 0; cp != NULL; cp = kenvp[++i]) { 157 if (!strncmp(cp, "hint.", 5)) { 158 hintp = kenvp[0]; 159 break; 160 } 161 } 162 mtx_unlock(&kenv_lock); 163 dyn_used = true; 164 } else { 165 /* 166 * We'll have a chance to keep coming back here until 167 * we've actually exhausted all of our possibilities. 168 * We might have chosen the MD/Static env because it 169 * had some kind of hints, but perhaps it didn't have 170 * the hint we are looking for. We don't provide any 171 * fallback when searching the dynamic environment. 172 */ 173 fallback: 174 if (dyn_used || fbacklvl >= FBACK_STATIC) 175 return (ENOENT); 176 177 switch (fbacklvl) { 178 case FBACK_MDENV: 179 fbacklvl++; 180 if (_res_checkenv(md_envp)) { 181 hintp = md_envp; 182 break; 183 } 184 185 /* FALLTHROUGH */ 186 case FBACK_STENV: 187 fbacklvl++; 188 if (!stenv_skip && _res_checkenv(kern_envp)) { 189 hintp = kern_envp; 190 break; 191 } else 192 stenv_skip = true; 193 194 /* FALLTHROUGH */ 195 case FBACK_STATIC: 196 fbacklvl++; 197 /* We'll fallback to static_hints if needed/can */ 198 if (!sthints_skip && 199 _res_checkenv(static_hints)) 200 hintp = static_hints; 201 else 202 sthints_skip = true; 203 204 break; 205 default: 206 return (ENOENT); 207 } 208 } 209 210 if (hintp == NULL) 211 return (ENOENT); 212 *hintp_cookie = hintp; 213 } else { 214 hintp = *hintp_cookie; 215 if (hintenv_merged && hintp == kenvp[0]) 216 dyn_used = true; 217 else 218 /* 219 * If we aren't using the dynamic environment, we need 220 * to run through the proper fallback procedure again. 221 * This is so that we do continuations right if we're 222 * working with *line and *startln. 223 */ 224 goto fallback; 225 } 226 227 if (dyn_used) { 228 mtx_lock(&kenv_lock); 229 i = 0; 230 } 231 232 cp = hintp; 233 while (cp) { 234 (*line)++; 235 if (strncmp(cp, "hint.", 5) != 0) 236 goto nexthint; 237 n = sscanf(cp, "hint.%32[^.].%d.%32[^=]=%127s", r_name, &r_unit, 238 r_resname, r_value); 239 if (n != 4) { 240 printf("CONFIG: invalid hint '%s'\n", cp); 241 p = strchr(cp, 'h'); 242 *p = 'H'; 243 goto nexthint; 244 } 245 if (startln && *startln >= 0 && *line < *startln) 246 goto nexthint; 247 if (name && strcmp(name, r_name) != 0) 248 goto nexthint; 249 if (unit && *unit != r_unit) 250 goto nexthint; 251 if (resname && strcmp(resname, r_resname) != 0) 252 goto nexthint; 253 if (value && strcmp(value, r_value) != 0) 254 goto nexthint; 255 /* Successfully found a hint matching all criteria */ 256 break; 257 nexthint: 258 if (dyn_used) { 259 cp = kenvp[++i]; 260 if (cp == NULL) 261 break; 262 } else { 263 while (*cp != '\0') 264 cp++; 265 cp++; 266 if (*cp == '\0') { 267 cp = NULL; 268 break; 269 } 270 } 271 } 272 if (dyn_used) 273 mtx_unlock(&kenv_lock); 274 if (cp == NULL) 275 goto fallback; 276 277 s = cp; 278 /* This is a bit of a hack, but at least is reentrant */ 279 /* Note that it returns some !unterminated! strings. */ 280 s = strchr(s, '.') + 1; /* start of device */ 281 if (ret_name) 282 *ret_name = s; 283 s = strchr(s, '.') + 1; /* start of unit */ 284 if (ret_namelen && ret_name) 285 *ret_namelen = s - *ret_name - 1; /* device length */ 286 if (ret_unit) 287 *ret_unit = r_unit; 288 s = strchr(s, '.') + 1; /* start of resname */ 289 if (ret_resname) 290 *ret_resname = s; 291 s = strchr(s, '=') + 1; /* start of value */ 292 if (ret_resnamelen && ret_resname) 293 *ret_resnamelen = s - *ret_resname - 1; /* value len */ 294 if (ret_value) 295 *ret_value = s; 296 if (startln) /* line number for anchor */ 297 *startln = *line + 1; 298 return 0; 299 } 300 301 /* 302 * Search all the data sources for matches to our query. We look for 303 * dynamic hints first as overrides for static or fallback hints. 304 */ 305 static int 306 resource_find(int *line, int *startln, 307 const char *name, int *unit, const char *resname, const char *value, 308 const char **ret_name, int *ret_namelen, int *ret_unit, 309 const char **ret_resname, int *ret_resnamelen, const char **ret_value) 310 { 311 int i; 312 int un; 313 char *hintp; 314 315 *line = 0; 316 hintp = NULL; 317 318 /* Search for exact unit matches first */ 319 i = res_find(&hintp, line, startln, name, unit, resname, value, 320 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 321 ret_value); 322 if (i == 0) 323 return 0; 324 if (unit == NULL) 325 return ENOENT; 326 /* If we are still here, search for wildcard matches */ 327 un = -1; 328 i = res_find(&hintp, line, startln, name, &un, resname, value, 329 ret_name, ret_namelen, ret_unit, ret_resname, ret_resnamelen, 330 ret_value); 331 if (i == 0) 332 return 0; 333 return ENOENT; 334 } 335 336 int 337 resource_int_value(const char *name, int unit, const char *resname, int *result) 338 { 339 int error; 340 const char *str; 341 char *op; 342 unsigned long val; 343 int line; 344 345 line = 0; 346 error = resource_find(&line, NULL, name, &unit, resname, NULL, 347 NULL, NULL, NULL, NULL, NULL, &str); 348 if (error) 349 return error; 350 if (*str == '\0') 351 return EFTYPE; 352 val = strtoul(str, &op, 0); 353 if (*op != '\0') 354 return EFTYPE; 355 *result = val; 356 return 0; 357 } 358 359 int 360 resource_long_value(const char *name, int unit, const char *resname, 361 long *result) 362 { 363 int error; 364 const char *str; 365 char *op; 366 unsigned long val; 367 int line; 368 369 line = 0; 370 error = resource_find(&line, NULL, name, &unit, resname, NULL, 371 NULL, NULL, NULL, NULL, NULL, &str); 372 if (error) 373 return error; 374 if (*str == '\0') 375 return EFTYPE; 376 val = strtoul(str, &op, 0); 377 if (*op != '\0') 378 return EFTYPE; 379 *result = val; 380 return 0; 381 } 382 383 int 384 resource_string_value(const char *name, int unit, const char *resname, 385 const char **result) 386 { 387 int error; 388 const char *str; 389 int line; 390 391 line = 0; 392 error = resource_find(&line, NULL, name, &unit, resname, NULL, 393 NULL, NULL, NULL, NULL, NULL, &str); 394 if (error) 395 return error; 396 *result = str; 397 return 0; 398 } 399 400 /* 401 * This is a bit nasty, but allows us to not modify the env strings. 402 */ 403 static const char * 404 resource_string_copy(const char *s, int len) 405 { 406 static char stringbuf[256]; 407 static int offset = 0; 408 const char *ret; 409 410 if (len == 0) 411 len = strlen(s); 412 if (len > 255) 413 return NULL; 414 if ((offset + len + 1) > 255) 415 offset = 0; 416 bcopy(s, &stringbuf[offset], len); 417 stringbuf[offset + len] = '\0'; 418 ret = &stringbuf[offset]; 419 offset += len + 1; 420 return ret; 421 } 422 423 /* 424 * err = resource_find_match(&anchor, &name, &unit, resname, value) 425 * Iteratively fetch a list of devices wired "at" something 426 * res and value are restrictions. eg: "at", "scbus0". 427 * For practical purposes, res = required, value = optional. 428 * *name and *unit are set. 429 * set *anchor to zero before starting. 430 */ 431 int 432 resource_find_match(int *anchor, const char **name, int *unit, 433 const char *resname, const char *value) 434 { 435 const char *found_name; 436 int found_namelen; 437 int found_unit; 438 int ret; 439 int newln; 440 441 newln = *anchor; 442 ret = resource_find(anchor, &newln, NULL, NULL, resname, value, 443 &found_name, &found_namelen, &found_unit, NULL, NULL, NULL); 444 if (ret == 0) { 445 *name = resource_string_copy(found_name, found_namelen); 446 *unit = found_unit; 447 } 448 *anchor = newln; 449 return ret; 450 } 451 452 /* 453 * err = resource_find_dev(&anchor, name, &unit, res, value); 454 * Iterate through a list of devices, returning their unit numbers. 455 * res and value are optional restrictions. eg: "at", "scbus0". 456 * *unit is set to the value. 457 * set *anchor to zero before starting. 458 */ 459 int 460 resource_find_dev(int *anchor, const char *name, int *unit, 461 const char *resname, const char *value) 462 { 463 int found_unit; 464 int newln; 465 int ret; 466 467 newln = *anchor; 468 ret = resource_find(anchor, &newln, name, NULL, resname, value, 469 NULL, NULL, &found_unit, NULL, NULL, NULL); 470 if (ret == 0) { 471 *unit = found_unit; 472 } 473 *anchor = newln; 474 return ret; 475 } 476 477 /* 478 * Check to see if a device is disabled via a disabled hint. 479 */ 480 int 481 resource_disabled(const char *name, int unit) 482 { 483 int error, value; 484 485 error = resource_int_value(name, unit, "disabled", &value); 486 if (error) 487 return (0); 488 return (value); 489 } 490 491 /* 492 * Clear a value associated with a device by removing it from 493 * the kernel environment. This only removes a hint for an 494 * exact unit. 495 */ 496 int 497 resource_unset_value(const char *name, int unit, const char *resname) 498 { 499 char varname[128]; 500 const char *retname, *retvalue; 501 int error, line; 502 size_t len; 503 504 line = 0; 505 error = resource_find(&line, NULL, name, &unit, resname, NULL, 506 &retname, NULL, NULL, NULL, NULL, &retvalue); 507 if (error) 508 return (error); 509 510 retname -= strlen("hint."); 511 len = retvalue - retname - 1; 512 if (len > sizeof(varname) - 1) 513 return (ENAMETOOLONG); 514 memcpy(varname, retname, len); 515 varname[len] = '\0'; 516 return (kern_unsetenv(varname)); 517 } 518