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