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