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