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