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