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