1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021 John H. Baldwin <jhb@FreeBSD.org>
5 * Copyright 2026 Hans Rosenfeld
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 #include <assert.h>
31 #include <err.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "config.h"
37
38 static nvlist_t *config_root;
39
40 void
init_config(void)41 init_config(void)
42 {
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 *
_lookup_config_node(nvlist_t * parent,const char * path,bool create)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 /*
68 * XXX-MJ it is incorrect to cast away the const
69 * qualifier like this since the contract with nvlist
70 * says that values are immutable, and some consumers
71 * will indeed add nodes to the returned nvlist. In
72 * practice, however, it appears to be harmless with the
73 * current nvlist implementation, so we just live with
74 * it until the implementation is reworked.
75 */
76 nvl = __DECONST(nvlist_t *,
77 nvlist_get_nvlist(nvl, name));
78 else if (nvlist_exists(nvl, name)) {
79 for (copy = tofree; copy < name; copy++)
80 if (*copy == '\0')
81 *copy = '.';
82 warnx(
83 "Configuration node %s is a child of existing variable %s",
84 path, tofree);
85 nvl = NULL;
86 break;
87 } else if (create) {
88 /*
89 * XXX-MJ as with the case above, "new_nvl" shouldn't be
90 * mutated after its ownership is given to "nvl".
91 */
92 new_nvl = nvlist_create(0);
93 if (new_nvl == NULL)
94 errx(4, "Failed to allocate memory");
95 nvlist_move_nvlist(nvl, name, new_nvl);
96 nvl = new_nvl;
97 } else {
98 nvl = NULL;
99 break;
100 }
101 }
102 free(tofree);
103 return (nvl);
104 }
105
106 nvlist_t *
create_config_node(const char * path)107 create_config_node(const char *path)
108 {
109
110 return (_lookup_config_node(config_root, path, true));
111 }
112
113 nvlist_t *
find_config_node(const char * path)114 find_config_node(const char *path)
115 {
116
117 return (_lookup_config_node(config_root, path, false));
118 }
119
120 nvlist_t *
create_relative_config_node(nvlist_t * parent,const char * path)121 create_relative_config_node(nvlist_t *parent, const char *path)
122 {
123
124 return (_lookup_config_node(parent, path, true));
125 }
126
127 nvlist_t *
find_relative_config_node(nvlist_t * parent,const char * path)128 find_relative_config_node(nvlist_t *parent, const char *path)
129 {
130
131 return (_lookup_config_node(parent, path, false));
132 }
133
134 void
set_config_value_node(nvlist_t * parent,const char * name,const char * value)135 set_config_value_node(nvlist_t *parent, const char *name, const char *value)
136 {
137
138 if (strchr(name, '.') != NULL)
139 errx(4, "Invalid config node name %s", name);
140 if (parent == NULL)
141 parent = config_root;
142 if (nvlist_exists_string(parent, name))
143 nvlist_free_string(parent, name);
144 else if (nvlist_exists(parent, name))
145 errx(4,
146 "Attempting to add value %s to existing node %s of list %p",
147 value, name, parent);
148 nvlist_add_string(parent, name, value);
149 }
150
151 void
set_config_value_node_if_unset(nvlist_t * const parent,const char * const name,const char * const value)152 set_config_value_node_if_unset(nvlist_t *const parent, const char *const name,
153 const char *const value)
154 {
155 if (get_config_value_node(parent, name) != NULL) {
156 return;
157 }
158
159 set_config_value_node(parent, name, value);
160 }
161
162 void
set_config_value(const char * path,const char * value)163 set_config_value(const char *path, const char *value)
164 {
165 const char *name;
166 char *node_name;
167 nvlist_t *nvl;
168
169 /* Look for last separator. */
170 name = strrchr(path, '.');
171 if (name == NULL) {
172 nvl = config_root;
173 name = path;
174 } else {
175 node_name = strndup(path, name - path);
176 if (node_name == NULL)
177 errx(4, "Failed to allocate memory");
178 nvl = create_config_node(node_name);
179 if (nvl == NULL)
180 errx(4, "Failed to create configuration node %s",
181 node_name);
182 free(node_name);
183
184 /* Skip over '.'. */
185 name++;
186 }
187
188 if (nvlist_exists_nvlist(nvl, name))
189 errx(4, "Attempting to add value %s to existing node %s",
190 value, path);
191 set_config_value_node(nvl, name, value);
192 }
193
194 void
set_config_value_if_unset(const char * const path,const char * const value)195 set_config_value_if_unset(const char *const path, const char *const value)
196 {
197 if (get_config_value(path) != NULL) {
198 return;
199 }
200
201 set_config_value(path, value);
202 }
203
204 static const char *
get_raw_config_value(const char * path)205 get_raw_config_value(const char *path)
206 {
207 const char *name;
208 char *node_name;
209 nvlist_t *nvl;
210
211 /* Look for last separator. */
212 name = strrchr(path, '.');
213 if (name == NULL) {
214 nvl = config_root;
215 name = path;
216 } else {
217 node_name = strndup(path, name - path);
218 if (node_name == NULL)
219 errx(4, "Failed to allocate memory");
220 nvl = find_config_node(node_name);
221 free(node_name);
222 if (nvl == NULL)
223 return (NULL);
224
225 /* Skip over '.'. */
226 name++;
227 }
228
229 if (nvlist_exists_string(nvl, name))
230 return (nvlist_get_string(nvl, name));
231 if (nvlist_exists_nvlist(nvl, name))
232 warnx("Attempting to fetch value of node %s", path);
233 return (NULL);
234 }
235
236 static char *
_expand_config_value(const char * value,int depth)237 _expand_config_value(const char *value, int depth)
238 {
239 FILE *valfp;
240 const char *cp, *vp;
241 char *nestedval, *path, *valbuf;
242 size_t valsize;
243
244 valfp = open_memstream(&valbuf, &valsize);
245 if (valfp == NULL)
246 errx(4, "Failed to allocate memory");
247
248 vp = value;
249 while (*vp != '\0') {
250 switch (*vp) {
251 case '%':
252 if (depth > 15) {
253 warnx(
254 "Too many recursive references in configuration value");
255 fputc('%', valfp);
256 vp++;
257 break;
258 }
259 if (vp[1] != '(' || vp[2] == '\0')
260 cp = NULL;
261 else
262 cp = strchr(vp + 2, ')');
263 if (cp == NULL) {
264 warnx(
265 "Invalid reference in configuration value \"%s\"",
266 value);
267 fputc('%', valfp);
268 vp++;
269 break;
270 }
271 vp += 2;
272
273 if (cp == vp) {
274 warnx(
275 "Empty reference in configuration value \"%s\"",
276 value);
277 vp++;
278 break;
279 }
280
281 /* Allocate a C string holding the path. */
282 path = strndup(vp, cp - vp);
283 if (path == NULL)
284 errx(4, "Failed to allocate memory");
285
286 /* Advance 'vp' past the reference. */
287 vp = cp + 1;
288
289 /* Fetch the referenced value. */
290 cp = get_raw_config_value(path);
291 if (cp == NULL)
292 warnx(
293 "Failed to fetch referenced configuration variable %s",
294 path);
295 else {
296 nestedval = _expand_config_value(cp, depth + 1);
297 fputs(nestedval, valfp);
298 free(nestedval);
299 }
300 free(path);
301 break;
302 case '\\':
303 vp++;
304 if (*vp == '\0') {
305 warnx(
306 "Trailing \\ in configuration value \"%s\"",
307 value);
308 break;
309 }
310 /* FALLTHROUGH */
311 default:
312 fputc(*vp, valfp);
313 vp++;
314 break;
315 }
316 }
317 fclose(valfp);
318 return (valbuf);
319 }
320
321 static const char *
expand_config_value(const char * value)322 expand_config_value(const char *value)
323 {
324 static char *valbuf;
325
326 if (strchr(value, '%') == NULL)
327 return (value);
328
329 free(valbuf);
330 valbuf = _expand_config_value(value, 0);
331 return (valbuf);
332 }
333
334 const char *
get_config_value(const char * path)335 get_config_value(const char *path)
336 {
337 const char *value;
338
339 value = get_raw_config_value(path);
340 if (value == NULL)
341 return (NULL);
342 return (expand_config_value(value));
343 }
344
345 const char *
get_config_value_node(const nvlist_t * parent,const char * name)346 get_config_value_node(const nvlist_t *parent, const char *name)
347 {
348
349 if (strchr(name, '.') != NULL)
350 errx(4, "Invalid config node name %s", name);
351 if (parent == NULL)
352 parent = config_root;
353 if (nvlist_exists_nvlist(parent, name))
354 warnx("Attempt to fetch value of node %s of list %p", name,
355 parent);
356 if (!nvlist_exists_string(parent, name))
357 return (NULL);
358
359 return (expand_config_value(nvlist_get_string(parent, name)));
360 }
361
362 static bool
_bool_value(const char * name,const char * value)363 _bool_value(const char *name, const char *value)
364 {
365
366 if (strcasecmp(value, "true") == 0 ||
367 strcasecmp(value, "on") == 0 ||
368 strcasecmp(value, "yes") == 0 ||
369 strcmp(value, "1") == 0)
370 return (true);
371 if (strcasecmp(value, "false") == 0 ||
372 strcasecmp(value, "off") == 0 ||
373 strcasecmp(value, "no") == 0 ||
374 strcmp(value, "0") == 0)
375 return (false);
376 err(4, "Invalid value %s for boolean variable %s", value, name);
377 }
378
379 bool
get_config_bool(const char * path)380 get_config_bool(const char *path)
381 {
382 const char *value;
383
384 value = get_config_value(path);
385 if (value == NULL)
386 err(4, "Failed to fetch boolean variable %s", path);
387 return (_bool_value(path, value));
388 }
389
390 bool
get_config_bool_default(const char * path,bool def)391 get_config_bool_default(const char *path, bool def)
392 {
393 const char *value;
394
395 value = get_config_value(path);
396 if (value == NULL)
397 return (def);
398 return (_bool_value(path, value));
399 }
400
401 bool
get_config_bool_node(const nvlist_t * parent,const char * name)402 get_config_bool_node(const nvlist_t *parent, const char *name)
403 {
404 const char *value;
405
406 value = get_config_value_node(parent, name);
407 if (value == NULL)
408 err(4, "Failed to fetch boolean variable %s", name);
409 return (_bool_value(name, value));
410 }
411
412 bool
get_config_bool_node_default(const nvlist_t * parent,const char * name,bool def)413 get_config_bool_node_default(const nvlist_t *parent, const char *name,
414 bool def)
415 {
416 const char *value;
417
418 value = get_config_value_node(parent, name);
419 if (value == NULL)
420 return (def);
421 return (_bool_value(name, value));
422 }
423
424 void
set_config_bool(const char * path,bool value)425 set_config_bool(const char *path, bool value)
426 {
427
428 set_config_value(path, value ? "true" : "false");
429 }
430
431 void
set_config_bool_node(nvlist_t * parent,const char * name,bool value)432 set_config_bool_node(nvlist_t *parent, const char *name, bool value)
433 {
434
435 set_config_value_node(parent, name, value ? "true" : "false");
436 }
437
438 int
walk_config_nodes(const char * prefix,const nvlist_t * parent,void * arg,int (* cb)(const char *,const nvlist_t *,const char *,int,void *))439 walk_config_nodes(const char *prefix, const nvlist_t *parent, void *arg,
440 int (*cb)(const char *, const nvlist_t *, const char *, int, void *))
441 {
442 void *cookie = NULL;
443 const char *name;
444 int type;
445
446 while ((name = nvlist_next(parent, &type, &cookie)) != NULL) {
447 int ret;
448
449 ret = cb(prefix, parent, name, type, arg);
450 if (ret != 0)
451 return (ret);
452 }
453
454 return (0);
455 }
456
457 static int
dump_node_cb(const char * prefix,const nvlist_t * parent,const char * name,int type,void * arg)458 dump_node_cb(const char *prefix, const nvlist_t *parent, const char *name,
459 int type, void *arg)
460 {
461 if (type == NV_TYPE_NVLIST) {
462 char *new_prefix;
463 int ret;
464
465 asprintf(&new_prefix, "%s%s.", prefix, name);
466 ret = walk_config_nodes(new_prefix,
467 nvlist_get_nvlist(parent, name), arg, dump_node_cb);
468 free(new_prefix);
469 return (ret);
470 }
471
472 assert(type == NV_TYPE_STRING);
473 printf("%s%s=%s\n", prefix, name, nvlist_get_string(parent, name));
474 return (0);
475 }
476
477 void
dump_config(void)478 dump_config(void)
479 {
480 (void)walk_config_nodes("", config_root, NULL, dump_node_cb);
481 }
482