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