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