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