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