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