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 37 static nvlist_t *config_root; 38 39 void 40 init_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 48 static 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 105 nvlist_t * 106 create_config_node(const char *path) 107 { 108 109 return (_lookup_config_node(config_root, path, true)); 110 } 111 112 nvlist_t * 113 find_config_node(const char *path) 114 { 115 116 return (_lookup_config_node(config_root, path, false)); 117 } 118 119 nvlist_t * 120 create_relative_config_node(nvlist_t *parent, const char *path) 121 { 122 123 return (_lookup_config_node(parent, path, true)); 124 } 125 126 nvlist_t * 127 find_relative_config_node(nvlist_t *parent, const char *path) 128 { 129 130 return (_lookup_config_node(parent, path, false)); 131 } 132 133 void 134 set_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 150 void 151 set_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 161 void 162 set_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 193 void 194 set_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 203 static const char * 204 get_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 235 static 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 320 static const char * 321 expand_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 333 const char * 334 get_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 344 const char * 345 get_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 361 static 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 378 bool 379 get_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 389 bool 390 get_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 400 bool 401 get_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 411 bool 412 get_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 423 void 424 set_config_bool(const char *path, bool value) 425 { 426 427 set_config_value(path, value ? "true" : "false"); 428 } 429 430 void 431 set_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 437 static void 438 dump_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 460 void 461 dump_config(void) 462 { 463 dump_tree("", config_root); 464 } 465