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