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