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