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
37static nvlist_t *config_root;
38
39void
40init_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
48static nvlist_t *
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(&copy, ".")) != 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
105nvlist_t *
106create_config_node(const char *path)
107{
108
109	return (_lookup_config_node(config_root, path, true));
110}
111
112nvlist_t *
113find_config_node(const char *path)
114{
115
116	return (_lookup_config_node(config_root, path, false));
117}
118
119nvlist_t *
120create_relative_config_node(nvlist_t *parent, const char *path)
121{
122
123	return (_lookup_config_node(parent, path, true));
124}
125
126nvlist_t *
127find_relative_config_node(nvlist_t *parent, const char *path)
128{
129
130	return (_lookup_config_node(parent, path, false));
131}
132
133void
134set_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
150void
151set_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
161void
162set_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
193void
194set_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
203static const char *
204get_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
235static char *
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
320static const char *
321expand_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
333const char *
334get_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
344const char *
345get_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
361static bool
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
378bool
379get_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
389bool
390get_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
400bool
401get_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
411bool
412get_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
423void
424set_config_bool(const char *path, bool value)
425{
426
427	set_config_value(path, value ? "true" : "false");
428}
429
430void
431set_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
437static void
438dump_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
460void
461dump_config(void)
462{
463	dump_tree("", config_root);
464}
465