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