xref: /illumos-gate/usr/src/cmd/bhyve/config.c (revision 2b948146)
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(const char *path, const char *value)
147 {
148 	const char *name;
149 	char *node_name;
150 	nvlist_t *nvl;
151 
152 	/* Look for last separator. */
153 	name = strrchr(path, '.');
154 	if (name == NULL) {
155 		nvl = config_root;
156 		name = path;
157 	} else {
158 		node_name = strndup(path, name - path);
159 		if (node_name == NULL)
160 			errx(4, "Failed to allocate memory");
161 		nvl = create_config_node(node_name);
162 		if (nvl == NULL)
163 			errx(4, "Failed to create configuration node %s",
164 			    node_name);
165 		free(node_name);
166 
167 		/* Skip over '.'. */
168 		name++;
169 	}
170 
171 	if (nvlist_exists_nvlist(nvl, name))
172 		errx(4, "Attempting to add value %s to existing node %s",
173 		    value, path);
174 	set_config_value_node(nvl, name, value);
175 }
176 
177 static const char *
178 get_raw_config_value(const char *path)
179 {
180 	const char *name;
181 	char *node_name;
182 	nvlist_t *nvl;
183 
184 	/* Look for last separator. */
185 	name = strrchr(path, '.');
186 	if (name == NULL) {
187 		nvl = config_root;
188 		name = path;
189 	} else {
190 		node_name = strndup(path, name - path);
191 		if (node_name == NULL)
192 			errx(4, "Failed to allocate memory");
193 		nvl = find_config_node(node_name);
194 		free(node_name);
195 		if (nvl == NULL)
196 			return (NULL);
197 
198 		/* Skip over '.'. */
199 		name++;
200 	}
201 
202 	if (nvlist_exists_string(nvl, name))
203 		return (nvlist_get_string(nvl, name));
204 	if (nvlist_exists_nvlist(nvl, name))
205 		warnx("Attempting to fetch value of node %s", path);
206 	return (NULL);
207 }
208 
209 static char *
210 _expand_config_value(const char *value, int depth)
211 {
212 	FILE *valfp;
213 	const char *cp, *vp;
214 	char *nestedval, *path, *valbuf;
215 	size_t valsize;
216 
217 	valfp = open_memstream(&valbuf, &valsize);
218 	if (valfp == NULL)
219 		errx(4, "Failed to allocate memory");
220 
221 	vp = value;
222 	while (*vp != '\0') {
223 		switch (*vp) {
224 		case '%':
225 			if (depth > 15) {
226 				warnx(
227 		    "Too many recursive references in configuration value");
228 				fputc('%', valfp);
229 				vp++;
230 				break;
231 			}
232 			if (vp[1] != '(' || vp[2] == '\0')
233 				cp = NULL;
234 			else
235 				cp = strchr(vp + 2, ')');
236 			if (cp == NULL) {
237 				warnx(
238 			    "Invalid reference in configuration value \"%s\"",
239 				    value);
240 				fputc('%', valfp);
241 				vp++;
242 				break;
243 			}
244 			vp += 2;
245 
246 			if (cp == vp) {
247 				warnx(
248 			    "Empty reference in configuration value \"%s\"",
249 				    value);
250 				vp++;
251 				break;
252 			}
253 
254 			/* Allocate a C string holding the path. */
255 			path = strndup(vp, cp - vp);
256 			if (path == NULL)
257 				errx(4, "Failed to allocate memory");
258 
259 			/* Advance 'vp' past the reference. */
260 			vp = cp + 1;
261 
262 			/* Fetch the referenced value. */
263 			cp = get_raw_config_value(path);
264 			if (cp == NULL)
265 				warnx(
266 		    "Failed to fetch referenced configuration variable %s",
267 				    path);
268 			else {
269 				nestedval = _expand_config_value(cp, depth + 1);
270 				fputs(nestedval, valfp);
271 				free(nestedval);
272 			}
273 			free(path);
274 			break;
275 		case '\\':
276 			vp++;
277 			if (*vp == '\0') {
278 				warnx(
279 			    "Trailing \\ in configuration value \"%s\"",
280 				    value);
281 				break;
282 			}
283 			/* FALLTHROUGH */
284 		default:
285 			fputc(*vp, valfp);
286 			vp++;
287 			break;
288 		}
289 	}
290 	fclose(valfp);
291 	return (valbuf);
292 }
293 
294 const char *
295 expand_config_value(const char *value)
296 {
297 	static char *valbuf;
298 
299 	if (strchr(value, '%') == NULL)
300 		return (value);
301 
302 	free(valbuf);
303 	valbuf = _expand_config_value(value, 0);
304 	return (valbuf);
305 }
306 
307 const char *
308 get_config_value(const char *path)
309 {
310 	const char *value;
311 
312 	value = get_raw_config_value(path);
313 	if (value == NULL)
314 		return (NULL);
315 	return (expand_config_value(value));
316 }
317 
318 const char *
319 get_config_value_node(const nvlist_t *parent, const char *name)
320 {
321 
322 	if (strchr(name, '.') != NULL)
323 		errx(4, "Invalid config node name %s", name);
324 	if (parent == NULL)
325 		parent = config_root;
326 
327 	if (nvlist_exists_nvlist(parent, name))
328 		warnx("Attempt to fetch value of node %s of list %p", name,
329 		    parent);
330 	if (!nvlist_exists_string(parent, name))
331 		return (NULL);
332 
333 	return (expand_config_value(nvlist_get_string(parent, name)));
334 }
335 
336 bool
337 _bool_value(const char *name, const char *value)
338 {
339 
340 	if (strcasecmp(value, "true") == 0 ||
341 	    strcasecmp(value, "on") == 0 ||
342 	    strcasecmp(value, "yes") == 0 ||
343 	    strcmp(value, "1") == 0)
344 		return (true);
345 	if (strcasecmp(value, "false") == 0 ||
346 	    strcasecmp(value, "off") == 0 ||
347 	    strcasecmp(value, "no") == 0 ||
348 	    strcmp(value, "0") == 0)
349 		return (false);
350 	err(4, "Invalid value %s for boolean variable %s", value, name);
351 }
352 
353 bool
354 get_config_bool(const char *path)
355 {
356 	const char *value;
357 
358 	value = get_config_value(path);
359 	if (value == NULL)
360 		err(4, "Failed to fetch boolean variable %s", path);
361 	return (_bool_value(path, value));
362 }
363 
364 bool
365 get_config_bool_default(const char *path, bool def)
366 {
367 	const char *value;
368 
369 	value = get_config_value(path);
370 	if (value == NULL)
371 		return (def);
372 	return (_bool_value(path, value));
373 }
374 
375 bool
376 get_config_bool_node(const nvlist_t *parent, const char *name)
377 {
378 	const char *value;
379 
380 	value = get_config_value_node(parent, name);
381 	if (value == NULL)
382 		err(4, "Failed to fetch boolean variable %s", name);
383 	return (_bool_value(name, value));
384 }
385 
386 bool
387 get_config_bool_node_default(const nvlist_t *parent, const char *name,
388     bool def)
389 {
390 	const char *value;
391 
392 	value = get_config_value_node(parent, name);
393 	if (value == NULL)
394 		return (def);
395 	return (_bool_value(name, value));
396 }
397 
398 void
399 set_config_bool(const char *path, bool value)
400 {
401 
402 	set_config_value(path, value ? "true" : "false");
403 }
404 
405 void
406 set_config_bool_node(nvlist_t *parent, const char *name, bool value)
407 {
408 
409 	set_config_value_node(parent, name, value ? "true" : "false");
410 }
411 
412 static void
413 dump_tree(const char *prefix, const nvlist_t *nvl)
414 {
415 	const char *name;
416 	void *cookie;
417 	int type;
418 
419 	cookie = NULL;
420 	while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
421 		if (type == NV_TYPE_NVLIST) {
422 			char *new_prefix;
423 
424 			asprintf(&new_prefix, "%s%s.", prefix, name);
425 			dump_tree(new_prefix, nvlist_get_nvlist(nvl, name));
426 			free(new_prefix);
427 		} else {
428 			assert(type == NV_TYPE_STRING);
429 			printf("%s%s=%s\n", prefix, name,
430 			    nvlist_get_string(nvl, name));
431 		}
432 	}
433 }
434 
435 void
436 dump_config(void)
437 {
438 	dump_tree("", config_root);
439 }
440