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