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