xref: /freebsd/usr.sbin/bhyve/config.c (revision e2d7bec6bc5a124091859ad134bc9cfc2a8b4688)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 John H. Baldwin <jhb@FreeBSD.org>
5  * Copyright 2026 Hans Rosenfeld
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 #include <assert.h>
31 #include <err.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "config.h"
37 
38 static nvlist_t *config_root;
39 
40 void
init_config(void)41 init_config(void)
42 {
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(&copy, ".")) != 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 			/*
68 			 * XXX-MJ it is incorrect to cast away the const
69 			 * qualifier like this since the contract with nvlist
70 			 * says that values are immutable, and some consumers
71 			 * will indeed add nodes to the returned nvlist.  In
72 			 * practice, however, it appears to be harmless with the
73 			 * current nvlist implementation, so we just live with
74 			 * it until the implementation is reworked.
75 			 */
76 			nvl = __DECONST(nvlist_t *,
77 			    nvlist_get_nvlist(nvl, name));
78 		else if (nvlist_exists(nvl, name)) {
79 			for (copy = tofree; copy < name; copy++)
80 				if (*copy == '\0')
81 					*copy = '.';
82 			warnx(
83 		    "Configuration node %s is a child of existing variable %s",
84 			    path, tofree);
85 			nvl = NULL;
86 			break;
87 		} else if (create) {
88 			/*
89 			 * XXX-MJ as with the case above, "new_nvl" shouldn't be
90 			 * mutated after its ownership is given to "nvl".
91 			 */
92 			new_nvl = nvlist_create(0);
93 			if (new_nvl == NULL)
94 				errx(4, "Failed to allocate memory");
95 			nvlist_move_nvlist(nvl, name, new_nvl);
96 			nvl = new_nvl;
97 		} else {
98 			nvl = NULL;
99 			break;
100 		}
101 	}
102 	free(tofree);
103 	return (nvl);
104 }
105 
106 nvlist_t *
create_config_node(const char * path)107 create_config_node(const char *path)
108 {
109 
110 	return (_lookup_config_node(config_root, path, true));
111 }
112 
113 nvlist_t *
find_config_node(const char * path)114 find_config_node(const char *path)
115 {
116 
117 	return (_lookup_config_node(config_root, path, false));
118 }
119 
120 nvlist_t *
create_relative_config_node(nvlist_t * parent,const char * path)121 create_relative_config_node(nvlist_t *parent, const char *path)
122 {
123 
124 	return (_lookup_config_node(parent, path, true));
125 }
126 
127 nvlist_t *
find_relative_config_node(nvlist_t * parent,const char * path)128 find_relative_config_node(nvlist_t *parent, const char *path)
129 {
130 
131 	return (_lookup_config_node(parent, path, false));
132 }
133 
134 void
set_config_value_node(nvlist_t * parent,const char * name,const char * value)135 set_config_value_node(nvlist_t *parent, const char *name, const char *value)
136 {
137 
138 	if (strchr(name, '.') != NULL)
139 		errx(4, "Invalid config node name %s", name);
140 	if (parent == NULL)
141 		parent = config_root;
142 	if (nvlist_exists_string(parent, name))
143 		nvlist_free_string(parent, name);
144 	else if (nvlist_exists(parent, name))
145 		errx(4,
146 		    "Attempting to add value %s to existing node %s of list %p",
147 		    value, name, parent);
148 	nvlist_add_string(parent, name, value);
149 }
150 
151 void
set_config_value_node_if_unset(nvlist_t * const parent,const char * const name,const char * const value)152 set_config_value_node_if_unset(nvlist_t *const parent, const char *const name,
153     const char *const value)
154 {
155 	if (get_config_value_node(parent, name) != NULL) {
156 		return;
157 	}
158 
159 	set_config_value_node(parent, name, value);
160 }
161 
162 void
set_config_value(const char * path,const char * value)163 set_config_value(const char *path, const char *value)
164 {
165 	const char *name;
166 	char *node_name;
167 	nvlist_t *nvl;
168 
169 	/* Look for last separator. */
170 	name = strrchr(path, '.');
171 	if (name == NULL) {
172 		nvl = config_root;
173 		name = path;
174 	} else {
175 		node_name = strndup(path, name - path);
176 		if (node_name == NULL)
177 			errx(4, "Failed to allocate memory");
178 		nvl = create_config_node(node_name);
179 		if (nvl == NULL)
180 			errx(4, "Failed to create configuration node %s",
181 			    node_name);
182 		free(node_name);
183 
184 		/* Skip over '.'. */
185 		name++;
186 	}
187 
188 	if (nvlist_exists_nvlist(nvl, name))
189 		errx(4, "Attempting to add value %s to existing node %s",
190 		    value, path);
191 	set_config_value_node(nvl, name, value);
192 }
193 
194 void
set_config_value_if_unset(const char * const path,const char * const value)195 set_config_value_if_unset(const char *const path, const char *const value)
196 {
197 	if (get_config_value(path) != NULL) {
198 		return;
199 	}
200 
201 	set_config_value(path, value);
202 }
203 
204 static const char *
get_raw_config_value(const char * path)205 get_raw_config_value(const char *path)
206 {
207 	const char *name;
208 	char *node_name;
209 	nvlist_t *nvl;
210 
211 	/* Look for last separator. */
212 	name = strrchr(path, '.');
213 	if (name == NULL) {
214 		nvl = config_root;
215 		name = path;
216 	} else {
217 		node_name = strndup(path, name - path);
218 		if (node_name == NULL)
219 			errx(4, "Failed to allocate memory");
220 		nvl = find_config_node(node_name);
221 		free(node_name);
222 		if (nvl == NULL)
223 			return (NULL);
224 
225 		/* Skip over '.'. */
226 		name++;
227 	}
228 
229 	if (nvlist_exists_string(nvl, name))
230 		return (nvlist_get_string(nvl, name));
231 	if (nvlist_exists_nvlist(nvl, name))
232 		warnx("Attempting to fetch value of node %s", path);
233 	return (NULL);
234 }
235 
236 static char *
_expand_config_value(const char * value,int depth)237 _expand_config_value(const char *value, int depth)
238 {
239 	FILE *valfp;
240 	const char *cp, *vp;
241 	char *nestedval, *path, *valbuf;
242 	size_t valsize;
243 
244 	valfp = open_memstream(&valbuf, &valsize);
245 	if (valfp == NULL)
246 		errx(4, "Failed to allocate memory");
247 
248 	vp = value;
249 	while (*vp != '\0') {
250 		switch (*vp) {
251 		case '%':
252 			if (depth > 15) {
253 				warnx(
254 		    "Too many recursive references in configuration value");
255 				fputc('%', valfp);
256 				vp++;
257 				break;
258 			}
259 			if (vp[1] != '(' || vp[2] == '\0')
260 				cp = NULL;
261 			else
262 				cp = strchr(vp + 2, ')');
263 			if (cp == NULL) {
264 				warnx(
265 			    "Invalid reference in configuration value \"%s\"",
266 				    value);
267 				fputc('%', valfp);
268 				vp++;
269 				break;
270 			}
271 			vp += 2;
272 
273 			if (cp == vp) {
274 				warnx(
275 			    "Empty reference in configuration value \"%s\"",
276 				    value);
277 				vp++;
278 				break;
279 			}
280 
281 			/* Allocate a C string holding the path. */
282 			path = strndup(vp, cp - vp);
283 			if (path == NULL)
284 				errx(4, "Failed to allocate memory");
285 
286 			/* Advance 'vp' past the reference. */
287 			vp = cp + 1;
288 
289 			/* Fetch the referenced value. */
290 			cp = get_raw_config_value(path);
291 			if (cp == NULL)
292 				warnx(
293 		    "Failed to fetch referenced configuration variable %s",
294 				    path);
295 			else {
296 				nestedval = _expand_config_value(cp, depth + 1);
297 				fputs(nestedval, valfp);
298 				free(nestedval);
299 			}
300 			free(path);
301 			break;
302 		case '\\':
303 			vp++;
304 			if (*vp == '\0') {
305 				warnx(
306 			    "Trailing \\ in configuration value \"%s\"",
307 				    value);
308 				break;
309 			}
310 			/* FALLTHROUGH */
311 		default:
312 			fputc(*vp, valfp);
313 			vp++;
314 			break;
315 		}
316 	}
317 	fclose(valfp);
318 	return (valbuf);
319 }
320 
321 static const char *
expand_config_value(const char * value)322 expand_config_value(const char *value)
323 {
324 	static char *valbuf;
325 
326 	if (strchr(value, '%') == NULL)
327 		return (value);
328 
329 	free(valbuf);
330 	valbuf = _expand_config_value(value, 0);
331 	return (valbuf);
332 }
333 
334 const char *
get_config_value(const char * path)335 get_config_value(const char *path)
336 {
337 	const char *value;
338 
339 	value = get_raw_config_value(path);
340 	if (value == NULL)
341 		return (NULL);
342 	return (expand_config_value(value));
343 }
344 
345 const char *
get_config_value_node(const nvlist_t * parent,const char * name)346 get_config_value_node(const nvlist_t *parent, const char *name)
347 {
348 
349 	if (strchr(name, '.') != NULL)
350 		errx(4, "Invalid config node name %s", name);
351 	if (parent == NULL)
352 		parent = config_root;
353 	if (nvlist_exists_nvlist(parent, name))
354 		warnx("Attempt to fetch value of node %s of list %p", name,
355 		    parent);
356 	if (!nvlist_exists_string(parent, name))
357 		return (NULL);
358 
359 	return (expand_config_value(nvlist_get_string(parent, name)));
360 }
361 
362 static bool
_bool_value(const char * name,const char * value)363 _bool_value(const char *name, const char *value)
364 {
365 
366 	if (strcasecmp(value, "true") == 0 ||
367 	    strcasecmp(value, "on") == 0 ||
368 	    strcasecmp(value, "yes") == 0 ||
369 	    strcmp(value, "1") == 0)
370 		return (true);
371 	if (strcasecmp(value, "false") == 0 ||
372 	    strcasecmp(value, "off") == 0 ||
373 	    strcasecmp(value, "no") == 0 ||
374 	    strcmp(value, "0") == 0)
375 		return (false);
376 	err(4, "Invalid value %s for boolean variable %s", value, name);
377 }
378 
379 bool
get_config_bool(const char * path)380 get_config_bool(const char *path)
381 {
382 	const char *value;
383 
384 	value = get_config_value(path);
385 	if (value == NULL)
386 		err(4, "Failed to fetch boolean variable %s", path);
387 	return (_bool_value(path, value));
388 }
389 
390 bool
get_config_bool_default(const char * path,bool def)391 get_config_bool_default(const char *path, bool def)
392 {
393 	const char *value;
394 
395 	value = get_config_value(path);
396 	if (value == NULL)
397 		return (def);
398 	return (_bool_value(path, value));
399 }
400 
401 bool
get_config_bool_node(const nvlist_t * parent,const char * name)402 get_config_bool_node(const nvlist_t *parent, const char *name)
403 {
404 	const char *value;
405 
406 	value = get_config_value_node(parent, name);
407 	if (value == NULL)
408 		err(4, "Failed to fetch boolean variable %s", name);
409 	return (_bool_value(name, value));
410 }
411 
412 bool
get_config_bool_node_default(const nvlist_t * parent,const char * name,bool def)413 get_config_bool_node_default(const nvlist_t *parent, const char *name,
414     bool def)
415 {
416 	const char *value;
417 
418 	value = get_config_value_node(parent, name);
419 	if (value == NULL)
420 		return (def);
421 	return (_bool_value(name, value));
422 }
423 
424 void
set_config_bool(const char * path,bool value)425 set_config_bool(const char *path, bool value)
426 {
427 
428 	set_config_value(path, value ? "true" : "false");
429 }
430 
431 void
set_config_bool_node(nvlist_t * parent,const char * name,bool value)432 set_config_bool_node(nvlist_t *parent, const char *name, bool value)
433 {
434 
435 	set_config_value_node(parent, name, value ? "true" : "false");
436 }
437 
438 int
walk_config_nodes(const char * prefix,const nvlist_t * parent,void * arg,int (* cb)(const char *,const nvlist_t *,const char *,int,void *))439 walk_config_nodes(const char *prefix, const nvlist_t *parent, void *arg,
440     int (*cb)(const char *, const nvlist_t *, const char *, int, void *))
441 {
442 	void *cookie = NULL;
443 	const char *name;
444 	int type;
445 
446 	while ((name = nvlist_next(parent, &type, &cookie)) != NULL) {
447 		int ret;
448 
449 		ret = cb(prefix, parent, name, type, arg);
450 		if (ret != 0)
451 			return (ret);
452 	}
453 
454 	return (0);
455 }
456 
457 static int
dump_node_cb(const char * prefix,const nvlist_t * parent,const char * name,int type,void * arg)458 dump_node_cb(const char *prefix, const nvlist_t *parent, const char *name,
459     int type, void *arg)
460 {
461 	if (type == NV_TYPE_NVLIST) {
462 		char *new_prefix;
463 		int ret;
464 
465 		asprintf(&new_prefix, "%s%s.", prefix, name);
466 		ret = walk_config_nodes(new_prefix,
467 		    nvlist_get_nvlist(parent, name), arg, dump_node_cb);
468 		free(new_prefix);
469 		return (ret);
470 	}
471 
472 	assert(type == NV_TYPE_STRING);
473 	printf("%s%s=%s\n", prefix, name, nvlist_get_string(parent, name));
474 	return (0);
475 }
476 
477 void
dump_config(void)478 dump_config(void)
479 {
480 	(void)walk_config_nodes("", config_root, NULL, dump_node_cb);
481 }
482