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