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