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