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