1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2021 John H. Baldwin <jhb@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include <assert.h> 32 #include <err.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #ifndef __FreeBSD__ 37 #include <sys/sysmacros.h> 38 #endif 39 40 #include "config.h" 41 42 static nvlist_t *config_root; 43 44 void 45 init_config(void) 46 { 47 config_root = nvlist_create(0); 48 if (config_root == NULL) 49 err(4, "Failed to create configuration root nvlist"); 50 } 51 52 static nvlist_t * 53 _lookup_config_node(nvlist_t *parent, const char *path, bool create) 54 { 55 char *copy, *name, *tofree; 56 nvlist_t *nvl, *new_nvl; 57 58 copy = strdup(path); 59 if (copy == NULL) 60 errx(4, "Failed to allocate memory"); 61 tofree = copy; 62 nvl = parent; 63 while ((name = strsep(©, ".")) != NULL) { 64 if (*name == '\0') { 65 warnx("Invalid configuration node: %s", path); 66 nvl = NULL; 67 break; 68 } 69 if (nvlist_exists_nvlist(nvl, name)) 70 /* 71 * XXX-MJ it is incorrect to cast away the const 72 * qualifier like this since the contract with nvlist 73 * says that values are immutable, and some consumers 74 * will indeed add nodes to the returned nvlist. In 75 * practice, however, it appears to be harmless with the 76 * current nvlist implementation, so we just live with 77 * it until the implementation is reworked. 78 */ 79 nvl = __DECONST(nvlist_t *, 80 nvlist_get_nvlist(nvl, name)); 81 else if (nvlist_exists(nvl, name)) { 82 for (copy = tofree; copy < name; copy++) 83 if (*copy == '\0') 84 *copy = '.'; 85 warnx( 86 "Configuration node %s is a child of existing variable %s", 87 path, tofree); 88 nvl = NULL; 89 break; 90 } else if (create) { 91 /* 92 * XXX-MJ as with the case above, "new_nvl" shouldn't be 93 * mutated after its ownership is given to "nvl". 94 */ 95 new_nvl = nvlist_create(0); 96 if (new_nvl == NULL) 97 errx(4, "Failed to allocate memory"); 98 #ifdef __FreeBSD__ 99 nvlist_move_nvlist(nvl, name, new_nvl); 100 #else 101 if (nvlist_add_nvlist(nvl, name, new_nvl) != 0) 102 errx(4, "Failed to allocate memory"); 103 (void) nvlist_free(new_nvl); 104 if (nvlist_lookup_nvlist(nvl, name, &new_nvl) != 0) 105 errx(4, "Failed to retrieve new nvlist"); 106 #endif 107 nvl = new_nvl; 108 } else { 109 nvl = NULL; 110 break; 111 } 112 } 113 free(tofree); 114 return (nvl); 115 } 116 117 nvlist_t * 118 create_config_node(const char *path) 119 { 120 121 return (_lookup_config_node(config_root, path, true)); 122 } 123 124 nvlist_t * 125 find_config_node(const char *path) 126 { 127 128 return (_lookup_config_node(config_root, path, false)); 129 } 130 131 nvlist_t * 132 create_relative_config_node(nvlist_t *parent, const char *path) 133 { 134 135 return (_lookup_config_node(parent, path, true)); 136 } 137 138 nvlist_t * 139 find_relative_config_node(nvlist_t *parent, const char *path) 140 { 141 142 return (_lookup_config_node(parent, path, false)); 143 } 144 145 void 146 set_config_value_node(nvlist_t *parent, const char *name, const char *value) 147 { 148 149 if (strchr(name, '.') != NULL) 150 errx(4, "Invalid config node name %s", name); 151 if (parent == NULL) 152 parent = config_root; 153 if (nvlist_exists_string(parent, name)) 154 nvlist_free_string(parent, name); 155 else if (nvlist_exists(parent, name)) 156 errx(4, 157 "Attemping to add value %s to existing node %s of list %p", 158 value, name, parent); 159 nvlist_add_string(parent, name, value); 160 } 161 162 void 163 set_config_value_node_if_unset(nvlist_t *const parent, const char *const name, 164 const char *const value) 165 { 166 if (get_config_value_node(parent, name) != NULL) { 167 return; 168 } 169 170 set_config_value_node(parent, name, value); 171 } 172 173 void 174 set_config_value(const char *path, const char *value) 175 { 176 const char *name; 177 char *node_name; 178 nvlist_t *nvl; 179 180 /* Look for last separator. */ 181 name = strrchr(path, '.'); 182 if (name == NULL) { 183 nvl = config_root; 184 name = path; 185 } else { 186 node_name = strndup(path, name - path); 187 if (node_name == NULL) 188 errx(4, "Failed to allocate memory"); 189 nvl = create_config_node(node_name); 190 if (nvl == NULL) 191 errx(4, "Failed to create configuration node %s", 192 node_name); 193 free(node_name); 194 195 /* Skip over '.'. */ 196 name++; 197 } 198 199 if (nvlist_exists_nvlist(nvl, name)) 200 errx(4, "Attempting to add value %s to existing node %s", 201 value, path); 202 set_config_value_node(nvl, name, value); 203 } 204 205 void 206 set_config_value_if_unset(const char *const path, const char *const value) 207 { 208 if (get_config_value(path) != NULL) { 209 return; 210 } 211 212 set_config_value(path, value); 213 } 214 215 static const char * 216 get_raw_config_value(const char *path) 217 { 218 const char *name; 219 char *node_name; 220 nvlist_t *nvl; 221 222 /* Look for last separator. */ 223 name = strrchr(path, '.'); 224 if (name == NULL) { 225 nvl = config_root; 226 name = path; 227 } else { 228 node_name = strndup(path, name - path); 229 if (node_name == NULL) 230 errx(4, "Failed to allocate memory"); 231 nvl = find_config_node(node_name); 232 free(node_name); 233 if (nvl == NULL) 234 return (NULL); 235 236 /* Skip over '.'. */ 237 name++; 238 } 239 240 if (nvlist_exists_string(nvl, name)) 241 return (nvlist_get_string(nvl, name)); 242 if (nvlist_exists_nvlist(nvl, name)) 243 warnx("Attempting to fetch value of node %s", path); 244 return (NULL); 245 } 246 247 static char * 248 _expand_config_value(const char *value, int depth) 249 { 250 FILE *valfp; 251 const char *cp, *vp; 252 char *nestedval, *path, *valbuf; 253 size_t valsize; 254 255 valfp = open_memstream(&valbuf, &valsize); 256 if (valfp == NULL) 257 errx(4, "Failed to allocate memory"); 258 259 vp = value; 260 while (*vp != '\0') { 261 switch (*vp) { 262 case '%': 263 if (depth > 15) { 264 warnx( 265 "Too many recursive references in configuration value"); 266 fputc('%', valfp); 267 vp++; 268 break; 269 } 270 if (vp[1] != '(' || vp[2] == '\0') 271 cp = NULL; 272 else 273 cp = strchr(vp + 2, ')'); 274 if (cp == NULL) { 275 warnx( 276 "Invalid reference in configuration value \"%s\"", 277 value); 278 fputc('%', valfp); 279 vp++; 280 break; 281 } 282 vp += 2; 283 284 if (cp == vp) { 285 warnx( 286 "Empty reference in configuration value \"%s\"", 287 value); 288 vp++; 289 break; 290 } 291 292 /* Allocate a C string holding the path. */ 293 path = strndup(vp, cp - vp); 294 if (path == NULL) 295 errx(4, "Failed to allocate memory"); 296 297 /* Advance 'vp' past the reference. */ 298 vp = cp + 1; 299 300 /* Fetch the referenced value. */ 301 cp = get_raw_config_value(path); 302 if (cp == NULL) 303 warnx( 304 "Failed to fetch referenced configuration variable %s", 305 path); 306 else { 307 nestedval = _expand_config_value(cp, depth + 1); 308 fputs(nestedval, valfp); 309 free(nestedval); 310 } 311 free(path); 312 break; 313 case '\\': 314 vp++; 315 if (*vp == '\0') { 316 warnx( 317 "Trailing \\ in configuration value \"%s\"", 318 value); 319 break; 320 } 321 /* FALLTHROUGH */ 322 default: 323 fputc(*vp, valfp); 324 vp++; 325 break; 326 } 327 } 328 fclose(valfp); 329 return (valbuf); 330 } 331 332 static const char * 333 expand_config_value(const char *value) 334 { 335 static char *valbuf; 336 337 if (strchr(value, '%') == NULL) 338 return (value); 339 340 free(valbuf); 341 valbuf = _expand_config_value(value, 0); 342 return (valbuf); 343 } 344 345 const char * 346 get_config_value(const char *path) 347 { 348 const char *value; 349 350 value = get_raw_config_value(path); 351 if (value == NULL) 352 return (NULL); 353 return (expand_config_value(value)); 354 } 355 356 const char * 357 get_config_value_node(const nvlist_t *parent, const char *name) 358 { 359 360 if (strchr(name, '.') != NULL) 361 errx(4, "Invalid config node name %s", name); 362 if (parent == NULL) 363 parent = config_root; 364 365 if (nvlist_exists_nvlist(parent, name)) 366 warnx("Attempt to fetch value of node %s of list %p", name, 367 parent); 368 if (!nvlist_exists_string(parent, name)) 369 return (NULL); 370 371 return (expand_config_value(nvlist_get_string(parent, name))); 372 } 373 374 static bool 375 _bool_value(const char *name, const char *value) 376 { 377 378 if (strcasecmp(value, "true") == 0 || 379 strcasecmp(value, "on") == 0 || 380 strcasecmp(value, "yes") == 0 || 381 strcmp(value, "1") == 0) 382 return (true); 383 if (strcasecmp(value, "false") == 0 || 384 strcasecmp(value, "off") == 0 || 385 strcasecmp(value, "no") == 0 || 386 strcmp(value, "0") == 0) 387 return (false); 388 err(4, "Invalid value %s for boolean variable %s", value, name); 389 } 390 391 bool 392 get_config_bool(const char *path) 393 { 394 const char *value; 395 396 value = get_config_value(path); 397 if (value == NULL) 398 err(4, "Failed to fetch boolean variable %s", path); 399 return (_bool_value(path, value)); 400 } 401 402 bool 403 get_config_bool_default(const char *path, bool def) 404 { 405 const char *value; 406 407 value = get_config_value(path); 408 if (value == NULL) 409 return (def); 410 return (_bool_value(path, value)); 411 } 412 413 bool 414 get_config_bool_node(const nvlist_t *parent, const char *name) 415 { 416 const char *value; 417 418 value = get_config_value_node(parent, name); 419 if (value == NULL) 420 err(4, "Failed to fetch boolean variable %s", name); 421 return (_bool_value(name, value)); 422 } 423 424 bool 425 get_config_bool_node_default(const nvlist_t *parent, const char *name, 426 bool def) 427 { 428 const char *value; 429 430 value = get_config_value_node(parent, name); 431 if (value == NULL) 432 return (def); 433 return (_bool_value(name, value)); 434 } 435 436 void 437 set_config_bool(const char *path, bool value) 438 { 439 440 set_config_value(path, value ? "true" : "false"); 441 } 442 443 void 444 set_config_bool_node(nvlist_t *parent, const char *name, bool value) 445 { 446 447 set_config_value_node(parent, name, value ? "true" : "false"); 448 } 449 450 static void 451 dump_tree(const char *prefix, const nvlist_t *nvl) 452 { 453 const char *name; 454 void *cookie; 455 int type; 456 457 cookie = NULL; 458 while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) { 459 if (type == NV_TYPE_NVLIST) { 460 char *new_prefix; 461 462 asprintf(&new_prefix, "%s%s.", prefix, name); 463 dump_tree(new_prefix, nvlist_get_nvlist(nvl, name)); 464 free(new_prefix); 465 } else { 466 assert(type == NV_TYPE_STRING); 467 printf("%s%s=%s\n", prefix, name, 468 nvlist_get_string(nvl, name)); 469 } 470 } 471 } 472 473 void 474 dump_config(void) 475 { 476 dump_tree("", config_root); 477 } 478