1/*-
2 * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer,
10 *    without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 *    redistribution must be conditioned upon including a substantially
14 *    similar Disclaimer requirement for further binary redistribution.
15 *
16 * NO WARRANTY
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 * THE POSSIBILITY OF SUCH DAMAGES.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/param.h>
34#include <sys/hash.h>
35#include <sys/limits.h>
36#include <sys/queue.h>
37
38#ifdef _KERNEL
39
40#include <sys/ctype.h>
41#include <sys/systm.h>
42
43#include <machine/_inttypes.h>
44
45#else /* !_KERNEL */
46
47#include <ctype.h>
48#include <errno.h>
49#include <inttypes.h>
50#include <stdbool.h>
51#include <stdio.h>
52#include <stdint.h>
53#include <stdlib.h>
54#include <string.h>
55
56#endif /* _KERNEL */
57
58#include "bhnd_nvram_private.h"
59#include "bhnd_nvram_datavar.h"
60
61#include "bhnd_nvram_storevar.h"
62
63/*
64 * BHND NVRAM Store
65 *
66 * Manages in-memory and persistent representations of NVRAM data.
67 */
68
69static int			 bhnd_nvstore_parse_data(
70				     struct bhnd_nvram_store *sc);
71
72static int			 bhnd_nvstore_parse_path_entries(
73				     struct bhnd_nvram_store *sc);
74
75static int			 bhnd_nvram_store_export_child(
76				     struct bhnd_nvram_store *sc,
77				     bhnd_nvstore_path *top,
78				     bhnd_nvstore_path *child,
79				     bhnd_nvram_plist *plist,
80				     uint32_t flags);
81
82static int			 bhnd_nvstore_export_merge(
83				     struct bhnd_nvram_store *sc,
84				     bhnd_nvstore_path *path,
85				     bhnd_nvram_plist *merged,
86				     uint32_t flags);
87
88static int			 bhnd_nvstore_export_devpath_alias(
89				     struct bhnd_nvram_store *sc,
90				     bhnd_nvstore_path *path,
91				     const char *devpath,
92				     bhnd_nvram_plist *plist,
93				     u_long *alias_val);
94
95/**
96 * Allocate and initialize a new NVRAM data store instance.
97 *
98 * The caller is responsible for deallocating the instance via
99 * bhnd_nvram_store_free().
100 *
101 * @param[out] store On success, a pointer to the newly allocated NVRAM data
102 * instance.
103 * @param data The NVRAM data to be managed by the returned NVRAM data store
104 * instance.
105 *
106 * @retval 0 success
107 * @retval non-zero if an error occurs during allocation or initialization, a
108 * regular unix error code will be returned.
109 */
110int
111bhnd_nvram_store_new(struct bhnd_nvram_store **store,
112    struct bhnd_nvram_data *data)
113{
114	struct bhnd_nvram_store *sc;
115	int			 error;
116
117	/* Allocate new instance */
118	sc = bhnd_nv_calloc(1, sizeof(*sc));
119	if (sc == NULL)
120		return (ENOMEM);
121
122	BHND_NVSTORE_LOCK_INIT(sc);
123	BHND_NVSTORE_LOCK(sc);
124
125	/* Initialize path hash table */
126	sc->num_paths = 0;
127	for (size_t i = 0; i < nitems(sc->paths); i++)
128		LIST_INIT(&sc->paths[i]);
129
130	/* Initialize alias hash table */
131	sc->num_aliases = 0;
132	for (size_t i = 0; i < nitems(sc->aliases); i++)
133		LIST_INIT(&sc->aliases[i]);
134
135	/* Retain the NVRAM data */
136	sc->data = bhnd_nvram_data_retain(data);
137	sc->data_caps = bhnd_nvram_data_caps(data);
138	sc->data_opts = bhnd_nvram_data_options(data);
139	if (sc->data_opts != NULL) {
140		bhnd_nvram_plist_retain(sc->data_opts);
141	} else {
142		sc->data_opts = bhnd_nvram_plist_new();
143		if (sc->data_opts == NULL) {
144			error = ENOMEM;
145			goto cleanup;
146		}
147	}
148
149	/* Register required root path */
150	error = bhnd_nvstore_register_path(sc, BHND_NVSTORE_ROOT_PATH,
151	    BHND_NVSTORE_ROOT_PATH_LEN);
152	if (error)
153		goto cleanup;
154
155	sc->root_path = bhnd_nvstore_get_path(sc, BHND_NVSTORE_ROOT_PATH,
156	    BHND_NVSTORE_ROOT_PATH_LEN);
157	BHND_NV_ASSERT(sc->root_path, ("missing root path"));
158
159	/* Parse all variables vended by our backing NVRAM data instance,
160	 * generating all path entries, alias entries, and variable indexes */
161	if ((error = bhnd_nvstore_parse_data(sc)))
162		goto cleanup;
163
164	*store = sc;
165
166	BHND_NVSTORE_UNLOCK(sc);
167	return (0);
168
169cleanup:
170	BHND_NVSTORE_UNLOCK(sc);
171	bhnd_nvram_store_free(sc);
172	return (error);
173}
174
175/**
176 * Allocate and initialize a new NVRAM data store instance, parsing the
177 * NVRAM data from @p io.
178 *
179 * The caller is responsible for deallocating the instance via
180 * bhnd_nvram_store_free().
181 *
182 * The NVRAM data mapped by @p io will be copied, and @p io may be safely
183 * deallocated after bhnd_nvram_store_new() returns.
184 *
185 * @param[out] store On success, a pointer to the newly allocated NVRAM data
186 * instance.
187 * @param io An I/O context mapping the NVRAM data to be copied and parsed.
188 * @param cls The NVRAM data class to be used when parsing @p io, or NULL
189 * to perform runtime identification of the appropriate data class.
190 *
191 * @retval 0 success
192 * @retval non-zero if an error occurs during allocation or initialization, a
193 * regular unix error code will be returned.
194 */
195int
196bhnd_nvram_store_parse_new(struct bhnd_nvram_store **store,
197    struct bhnd_nvram_io *io, bhnd_nvram_data_class *cls)
198{
199	struct bhnd_nvram_data	*data;
200	int			 error;
201
202
203	/* Try to parse the data */
204	if ((error = bhnd_nvram_data_new(cls, &data, io)))
205		return (error);
206
207	/* Try to create our new store instance */
208	error = bhnd_nvram_store_new(store, data);
209	bhnd_nvram_data_release(data);
210
211	return (error);
212}
213
214/**
215 * Free an NVRAM store instance, releasing all associated resources.
216 *
217 * @param sc A store instance previously allocated via
218 * bhnd_nvram_store_new().
219 */
220void
221bhnd_nvram_store_free(struct bhnd_nvram_store *sc)
222{
223
224	/* Clean up alias hash table */
225	for (size_t i = 0; i < nitems(sc->aliases); i++) {
226		bhnd_nvstore_alias *alias, *anext;
227		LIST_FOREACH_SAFE(alias, &sc->aliases[i], na_link, anext)
228			bhnd_nv_free(alias);
229	}
230
231	/* Clean up path hash table */
232	for (size_t i = 0; i < nitems(sc->paths); i++) {
233		bhnd_nvstore_path *path, *pnext;
234		LIST_FOREACH_SAFE(path, &sc->paths[i], np_link, pnext)
235			bhnd_nvstore_path_free(path);
236	}
237
238	if (sc->data != NULL)
239		bhnd_nvram_data_release(sc->data);
240
241	if (sc->data_opts != NULL)
242		bhnd_nvram_plist_release(sc->data_opts);
243
244	BHND_NVSTORE_LOCK_DESTROY(sc);
245	bhnd_nv_free(sc);
246}
247
248/**
249 * Parse all variables vended by our backing NVRAM data instance,
250 * generating all path entries, alias entries, and variable indexes.
251 *
252 * @param	sc	The NVRAM store instance to be initialized with
253 *			paths, aliases, and data parsed from its backing
254 *			data.
255 *
256 * @retval 0		success
257 * @retval non-zero	if an error occurs during parsing, a regular unix error
258 *			code will be returned.
259 */
260static int
261bhnd_nvstore_parse_data(struct bhnd_nvram_store *sc)
262{
263	const char	*name;
264	void		*cookiep;
265	int		 error;
266
267	/* Parse and register all device paths and path aliases. This enables
268	 * resolution of _forward_ references to device paths aliases when
269	 * scanning variable entries below */
270	if ((error = bhnd_nvstore_parse_path_entries(sc)))
271		return (error);
272
273	/* Calculate the per-path variable counts, and report dangling alias
274	 * references as an error. */
275	cookiep = NULL;
276	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
277		bhnd_nvstore_path	*path;
278		bhnd_nvstore_name_info	 info;
279
280		/* Parse the name info */
281		error = bhnd_nvstore_parse_name_info(name,
282		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
283		if (error)
284			return (error);
285
286		switch (info.type) {
287		case BHND_NVSTORE_VAR:
288			/* Fetch referenced path */
289			path = bhnd_nvstore_var_get_path(sc, &info);
290			if (path == NULL) {
291				BHND_NV_LOG("variable '%s' has dangling "
292					    "path reference\n", name);
293				return (EFTYPE);
294			}
295
296			/* Increment path variable count */
297			if (path->num_vars == SIZE_MAX) {
298				BHND_NV_LOG("more than SIZE_MAX variables in "
299				    "path %s\n", path->path_str);
300				return (EFTYPE);
301			}
302			path->num_vars++;
303			break;
304
305		case BHND_NVSTORE_ALIAS_DECL:
306			/* Skip -- path alias already parsed and recorded */
307			break;
308		}
309	}
310
311	/* If the backing NVRAM data instance vends only a single root ("/")
312	 * path, we may be able to skip generating an index for the root
313	 * path */
314	if (sc->num_paths == 1) {
315		bhnd_nvstore_path *path;
316
317		/* If the backing instance provides its own name-based lookup
318		 * indexing, we can skip generating a duplicate here */
319		if (sc->data_caps & BHND_NVRAM_DATA_CAP_INDEXED)
320			return (0);
321
322		/* If the sole root path contains fewer variables than the
323		 * minimum indexing threshhold, we do not need to generate an
324		 * index */
325		path = bhnd_nvstore_get_root_path(sc);
326		if (path->num_vars < BHND_NV_IDX_VAR_THRESHOLD)
327			return (0);
328	}
329
330	/* Allocate per-path index instances */
331	for (size_t i = 0; i < nitems(sc->paths); i++) {
332		bhnd_nvstore_path	*path;
333
334		LIST_FOREACH(path, &sc->paths[i], np_link) {
335			path->index = bhnd_nvstore_index_new(path->num_vars);
336			if (path->index == NULL)
337				return (ENOMEM);
338		}
339	}
340
341	/* Populate per-path indexes */
342	cookiep = NULL;
343	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
344		bhnd_nvstore_name_info	 info;
345		bhnd_nvstore_path	*path;
346
347		/* Parse the name info */
348		error = bhnd_nvstore_parse_name_info(name,
349		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
350		if (error)
351			return (error);
352
353		switch (info.type) {
354		case BHND_NVSTORE_VAR:
355			/* Fetch referenced path */
356			path = bhnd_nvstore_var_get_path(sc, &info);
357			BHND_NV_ASSERT(path != NULL,
358			    ("dangling path reference"));
359
360			/* Append to index */
361			error = bhnd_nvstore_index_append(sc, path->index,
362			    cookiep);
363			if (error)
364				return (error);
365			break;
366
367		case BHND_NVSTORE_ALIAS_DECL:
368			/* Skip */
369			break;
370		}
371	}
372
373	/* Prepare indexes for querying */
374	for (size_t i = 0; i < nitems(sc->paths); i++) {
375		bhnd_nvstore_path	*path;
376
377		LIST_FOREACH(path, &sc->paths[i], np_link) {
378			error = bhnd_nvstore_index_prepare(sc, path->index);
379			if (error)
380				return (error);
381		}
382	}
383
384	return (0);
385}
386
387
388/**
389 * Parse and register path and path alias entries for all declarations found in
390 * the NVRAM data backing @p nvram.
391 *
392 * @param sc		The NVRAM store instance.
393 *
394 * @retval 0		success
395 * @retval non-zero	If parsing fails, a regular unix error code will be
396 *			returned.
397 */
398static int
399bhnd_nvstore_parse_path_entries(struct bhnd_nvram_store *sc)
400{
401	const char	*name;
402	void		*cookiep;
403	int		 error;
404
405	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
406
407	/* Skip path registration if the data source does not support device
408	 * paths. */
409	if (!(sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)) {
410		BHND_NV_ASSERT(sc->root_path != NULL, ("missing root path"));
411		return (0);
412	}
413
414	/* Otherwise, parse and register all paths and path aliases */
415	cookiep = NULL;
416	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
417		bhnd_nvstore_name_info info;
418
419		/* Parse the name info */
420		error = bhnd_nvstore_parse_name_info(name,
421		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
422		if (error)
423			return (error);
424
425		/* Register the path */
426		error = bhnd_nvstore_var_register_path(sc, &info, cookiep);
427		if (error) {
428			BHND_NV_LOG("failed to register path for %s: %d\n",
429			    name, error);
430			return (error);
431		}
432	}
433
434	return (0);
435}
436
437
438/**
439 * Merge exported per-path variables (uncommitted, committed, or both) into
440 * the empty @p merged property list.
441 *
442 * @param	sc	The NVRAM store instance.
443 * @param	path	The NVRAM path to be exported.
444 * @param	merged	The property list to populate with the merged results.
445 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
446 *
447 * @retval 0		success
448 * @retval ENOMEM	If allocation fails.
449 * @retval non-zero	If merging the variables defined in @p path otherwise
450 *			fails, a regular unix error code will be returned.
451 */
452static int
453bhnd_nvstore_export_merge(struct bhnd_nvram_store *sc,
454    bhnd_nvstore_path *path, bhnd_nvram_plist *merged, uint32_t flags)
455{
456	void	*cookiep, *idxp;
457	int	 error;
458
459	/* Populate merged list with all pending variables */
460	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
461		bhnd_nvram_prop *prop;
462
463		prop = NULL;
464		while ((prop = bhnd_nvram_plist_next(path->pending, prop))) {
465			/* Skip variables marked for deletion */
466			if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED)) {
467				if (bhnd_nvram_prop_is_null(prop))
468					continue;
469			}
470
471			/* Append to merged list */
472			error = bhnd_nvram_plist_append(merged, prop);
473			if (error)
474				return (error);
475		}
476	}
477
478	/* Skip merging committed variables? */
479	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED))
480		return (0);
481
482	/* Merge in the committed NVRAM variables */
483	idxp = NULL;
484	while ((cookiep = bhnd_nvstore_path_data_next(sc, path, &idxp))) {
485		const char	*name;
486		bhnd_nvram_val	*val;
487
488		/* Fetch the variable name */
489		name = bhnd_nvram_data_getvar_name(sc->data, cookiep);
490
491		/* Trim device path prefix */
492		if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)
493			name = bhnd_nvram_trim_path_name(name);
494
495		/* Skip if already defined in pending updates */
496		if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
497			if (bhnd_nvram_plist_contains(path->pending, name))
498				continue;
499		}
500
501		/* Skip if higher precedence value was already defined. This
502		 * may occur if the underlying data store contains duplicate
503		 * keys; iteration will always return the definition with
504		 * the highest precedence first */
505		if (bhnd_nvram_plist_contains(merged, name))
506			continue;
507
508		/* Fetch the variable's value representation */
509		if ((error = bhnd_nvram_data_copy_val(sc->data, cookiep, &val)))
510			return (error);
511
512		/* Add to path variable list */
513		error = bhnd_nvram_plist_append_val(merged, name, val);
514		bhnd_nvram_val_release(val);
515		if (error)
516			return (error);
517	}
518
519	return (0);
520}
521
522/**
523 * Find a free alias value for @p path, and append the devpathXX alias
524 * declaration to @p plist.
525 *
526 * @param	sc		The NVRAM store instance.
527 * @param	path		The NVRAM path for which a devpath alias
528 *				variable should be produced.
529 * @param	devpath		The devpathXX path value for @p path.
530 * @param	plist		The property list to which @p path's devpath
531 *				variable will be appended.
532 * @param[out]	alias_val	On success, will be set to the alias value
533 *				allocated for @p path.
534 *
535 * @retval 0		success
536 * @retval ENOMEM	If allocation fails.
537 * @retval non-zero	If merging the variables defined in @p path otherwise
538 *			fails, a regular unix error code will be returned.
539 */
540static int
541bhnd_nvstore_export_devpath_alias(struct bhnd_nvram_store *sc,
542    bhnd_nvstore_path *path, const char *devpath, bhnd_nvram_plist *plist,
543    u_long *alias_val)
544{
545	bhnd_nvstore_alias	*alias;
546	char			*pathvar;
547	int			 error;
548
549	*alias_val = 0;
550
551	/* Prefer alias value already reserved for this path. */
552	alias = bhnd_nvstore_find_alias(sc, path->path_str);
553	if (alias != NULL) {
554		*alias_val = alias->alias;
555
556		/* Allocate devpathXX variable name */
557		bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
558		if (pathvar == NULL)
559			return (ENOMEM);
560
561		/* Append alias variable to property list */
562		error = bhnd_nvram_plist_append_string(plist, pathvar, devpath);
563
564		BHND_NV_ASSERT(error != EEXIST, ("reserved alias %lu:%s in use",
565		   * alias_val, path->path_str));
566
567		bhnd_nv_free(pathvar);
568		return (error);
569	}
570
571	/* Find the next free devpathXX alias entry */
572	while (1) {
573		/* Skip existing reserved alias values */
574		while (bhnd_nvstore_get_alias(sc, *alias_val) != NULL) {
575			if (*alias_val == ULONG_MAX)
576				return (ENOMEM);
577
578			(*alias_val)++;
579		}
580
581		/* Allocate devpathXX variable name */
582		bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
583		if (pathvar == NULL)
584			return (ENOMEM);
585
586		/* If not in-use, we can terminate the search */
587		if (!bhnd_nvram_plist_contains(plist, pathvar))
588			break;
589
590		/* Keep searching */
591		bhnd_nv_free(pathvar);
592
593		if (*alias_val == ULONG_MAX)
594			return (ENOMEM);
595
596		(*alias_val)++;
597	}
598
599	/* Append alias variable to property list */
600	error = bhnd_nvram_plist_append_string(plist, pathvar, devpath);
601
602	bhnd_nv_free(pathvar);
603	return (error);
604}
605
606/**
607 * Export a single @p child path's properties, appending the result to @p plist.
608 *
609 * @param	sc		The NVRAM store instance.
610 * @param	top		The root NVRAM path being exported.
611 * @param	child		The NVRAM path to be exported.
612 * @param	plist		The property list to which @p child's exported
613 *				properties should be appended.
614 * @param	flags		Export flags. See BHND_NVSTORE_EXPORT_*.
615 *
616 * @retval 0		success
617 * @retval ENOMEM	If allocation fails.
618 * @retval non-zero	If merging the variables defined in @p path otherwise
619 *			fails, a regular unix error code will be returned.
620 */
621static int
622bhnd_nvram_store_export_child(struct bhnd_nvram_store *sc,
623    bhnd_nvstore_path *top, bhnd_nvstore_path *child, bhnd_nvram_plist *plist,
624    uint32_t flags)
625{
626	bhnd_nvram_plist	*path_vars;
627	bhnd_nvram_prop		*prop;
628	const char		*relpath;
629	char			*prefix, *namebuf;
630	size_t			 prefix_len, relpath_len;
631	size_t			 namebuf_size, num_props;
632	bool			 emit_compact_devpath;
633	int			 error;
634
635	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
636
637	prefix = NULL;
638	num_props = 0;
639	path_vars = NULL;
640	namebuf = NULL;
641
642	/* Determine the path relative to the top-level path */
643	relpath = bhnd_nvstore_parse_relpath(top->path_str, child->path_str);
644	if (relpath == NULL) {
645		/* Skip -- not a child of the root path */
646		return (0);
647	}
648	relpath_len = strlen(relpath);
649
650	/* Skip sub-path if export of children was not requested,  */
651	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_CHILDREN) && relpath_len > 0)
652		return (0);
653
654	/* Collect all variables to be included in the export */
655	if ((path_vars = bhnd_nvram_plist_new()) == NULL)
656		return (ENOMEM);
657
658	if ((error = bhnd_nvstore_export_merge(sc, child, path_vars, flags))) {
659		bhnd_nvram_plist_release(path_vars);
660		return (error);
661	}
662
663	/* Skip if no children are to be exported */
664	if (bhnd_nvram_plist_count(path_vars) == 0) {
665		bhnd_nvram_plist_release(path_vars);
666		return (0);
667	}
668
669	/* Determine appropriate device path encoding */
670	emit_compact_devpath = false;
671	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS)) {
672		/* Re-encode as compact (if non-empty path) */
673		if (relpath_len > 0)
674			emit_compact_devpath = true;
675	} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) {
676		/* Re-encode with fully expanded device path */
677		emit_compact_devpath = false;
678	} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
679		/* Preserve existing encoding of this path */
680		if (bhnd_nvstore_find_alias(sc, child->path_str) != NULL)
681			emit_compact_devpath = true;
682	} else {
683		BHND_NV_LOG("invalid device path flag: %#" PRIx32, flags);
684		error = EINVAL;
685		goto finished;
686	}
687
688	/* Allocate variable device path prefix to use for all property names,
689	 * and if using compact encoding, emit the devpathXX= variable */
690	prefix = NULL;
691	prefix_len = 0;
692	if (emit_compact_devpath) {
693		u_long	alias_val;
694		int	len;
695
696		/* Reserve an alias value and append the devpathXX= variable to
697		 * the property list */
698		error = bhnd_nvstore_export_devpath_alias(sc, child, relpath,
699		    plist, &alias_val);
700		if (error)
701			goto finished;
702
703		/* Allocate variable name prefix */
704		len = bhnd_nv_asprintf(&prefix, "%lu:", alias_val);
705		if (prefix == NULL) {
706			error = ENOMEM;
707			goto finished;
708		}
709
710		prefix_len = len;
711	} else if (relpath_len > 0) {
712		int len;
713
714		/* Allocate the variable name prefix, appending '/' to the
715		 * relative path */
716		len = bhnd_nv_asprintf(&prefix, "%s/", relpath);
717		if (prefix == NULL) {
718			error = ENOMEM;
719			goto finished;
720		}
721
722		prefix_len = len;
723	}
724
725	/* If prefixing of variable names is required, allocate a name
726	 * formatting buffer */
727	namebuf_size = 0;
728	if (prefix != NULL) {
729		size_t	maxlen;
730
731		/* Find the maximum name length */
732		maxlen = 0;
733		prop = NULL;
734		while ((prop = bhnd_nvram_plist_next(path_vars, prop))) {
735			const char *name;
736
737			name = bhnd_nvram_prop_name(prop);
738			maxlen = bhnd_nv_ummax(strlen(name), maxlen);
739		}
740
741		/* Allocate name buffer (path-prefix + name + '\0') */
742		namebuf_size = prefix_len + maxlen + 1;
743		namebuf = bhnd_nv_malloc(namebuf_size);
744		if (namebuf == NULL) {
745			error = ENOMEM;
746			goto finished;
747		}
748	}
749
750	/* Append all path variables to the export plist, prepending the
751	 * device-path prefix to the variable names, if required */
752	prop = NULL;
753	while ((prop = bhnd_nvram_plist_next(path_vars, prop)) != NULL) {
754		const char *name;
755
756		/* Prepend device prefix to the variable name */
757		name = bhnd_nvram_prop_name(prop);
758		if (prefix != NULL) {
759			int len;
760
761			/*
762			 * Write prefixed variable name to our name buffer.
763			 *
764			 * We precalcuate the size when scanning all names
765			 * above, so this should always succeed.
766			 */
767			len = snprintf(namebuf, namebuf_size, "%s%s", prefix,
768			    name);
769			if (len < 0 || (size_t)len >= namebuf_size)
770				BHND_NV_PANIC("invalid max_name_len");
771
772			name = namebuf;
773		}
774
775		/* Add property to export plist */
776		error = bhnd_nvram_plist_append_val(plist, name,
777		    bhnd_nvram_prop_val(prop));
778		if (error)
779			goto finished;
780	}
781
782	/* Success */
783	error = 0;
784
785finished:
786	if (prefix != NULL)
787		bhnd_nv_free(prefix);
788
789	if (namebuf != NULL)
790		bhnd_nv_free(namebuf);
791
792	if (path_vars != NULL)
793		bhnd_nvram_plist_release(path_vars);
794
795	return (error);
796}
797
798/**
799 * Export a flat, ordered NVRAM property list representation of all NVRAM
800 * properties at @p path.
801 *
802 * @param	sc	The NVRAM store instance.
803 * @param	path	The NVRAM path to export, or NULL to select the root
804 *			path.
805 * @param[out]	cls	On success, will be set to the backing data class
806 *			of @p sc. If the data class is are not desired,
807 *			a NULL pointer may be provided.
808 * @param[out]	props	On success, will be set to a caller-owned property
809 *			list containing the exported properties. The caller is
810 *			responsible for releasing this value via
811 *			bhnd_nvram_plist_release().
812 * @param[out]	options	On success, will be set to a caller-owned property
813 *			list containing the current NVRAM serialization options
814 *			for @p sc. The caller is responsible for releasing this
815 *			value via bhnd_nvram_plist_release().
816 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
817 *
818 * @retval 0		success
819 * @retval EINVAL	If @p flags is invalid.
820 * @retval ENOENT	The requested path was not found.
821 * @retval ENOMEM	If allocation fails.
822 * @retval non-zero	If export of  @p path otherwise fails, a regular unix
823 *			error code will be returned.
824 */
825int
826bhnd_nvram_store_export(struct bhnd_nvram_store *sc, const char *path,
827    bhnd_nvram_data_class **cls, bhnd_nvram_plist **props,
828    bhnd_nvram_plist **options, uint32_t flags)
829{
830	bhnd_nvram_plist	*unordered;
831	bhnd_nvstore_path	*top;
832	bhnd_nvram_prop		*prop;
833	const char		*name;
834	void			*cookiep;
835	size_t			 num_dpath_flags;
836	int			 error;
837
838	*props = NULL;
839	unordered = NULL;
840	num_dpath_flags = 0;
841	if (options != NULL)
842		*options = NULL;
843
844	/* Default to exporting root path */
845	if (path == NULL)
846		path = BHND_NVSTORE_ROOT_PATH;
847
848	/* Default to exporting all properties */
849	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED) &&
850	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED))
851	{
852		flags |= BHND_NVSTORE_EXPORT_ALL_VARS;
853	}
854
855	/* Default to preserving the current device path encoding */
856	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS) &&
857	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS))
858	{
859		flags |= BHND_NVSTORE_EXPORT_PRESERVE_DEVPATHS;
860	}
861
862	/* Exactly one device path encoding flag must be set */
863	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS))
864		num_dpath_flags++;
865
866	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS))
867		num_dpath_flags++;
868
869	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS))
870		num_dpath_flags++;
871
872	if (num_dpath_flags != 1)
873		return (EINVAL);
874
875	/* If EXPORT_DELETED is set, EXPORT_UNCOMMITTED must be set too */
876	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED) &&
877	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED))
878	{
879		return (EINVAL);
880	}
881
882	/* Lock internal state before querying paths/properties */
883	BHND_NVSTORE_LOCK(sc);
884
885	/* Fetch referenced path */
886	top = bhnd_nvstore_get_path(sc, path, strlen(path));
887	if (top == NULL) {
888		error = ENOENT;
889		goto failed;
890	}
891
892	/* Allocate new, empty property list */
893	if ((unordered = bhnd_nvram_plist_new()) == NULL) {
894		error = ENOMEM;
895		goto failed;
896	}
897
898	/* Export the top-level path first */
899	error = bhnd_nvram_store_export_child(sc, top, top, unordered, flags);
900	if (error)
901		goto failed;
902
903	/* Attempt to export any children of the root path */
904	for (size_t i = 0; i < nitems(sc->paths); i++) {
905		bhnd_nvstore_path *child;
906
907		LIST_FOREACH(child, &sc->paths[i], np_link) {
908			/* Top-level path was already exported */
909			if (child == top)
910				continue;
911
912			error = bhnd_nvram_store_export_child(sc, top,
913			    child, unordered, flags);
914			if (error)
915				goto failed;
916		}
917	}
918
919	/* If requested, provide the current class and serialization options */
920	if (cls != NULL)
921		*cls = bhnd_nvram_data_get_class(sc->data);
922
923	if (options != NULL)
924		*options = bhnd_nvram_plist_retain(sc->data_opts);
925
926	/*
927	 * If we're re-encoding device paths, don't bother preserving the
928	 * existing NVRAM variable order; our variable names will not match
929	 * the existing backing NVRAM data.
930	 */
931	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
932		*props = unordered;
933		unordered = NULL;
934
935		goto finished;
936	}
937
938	/*
939	 * Re-order the flattened output to match the existing NVRAM variable
940	 * ordering.
941	 *
942	 * We append all new variables at the end of the input; this should
943	 * reduce the delta that needs to be written (e.g. to flash) when
944	 * committing NVRAM updates, and should result in a serialization
945	 * identical to the input serialization if uncommitted updates are
946	 * excluded from the export.
947	 */
948	if ((*props = bhnd_nvram_plist_new()) == NULL) {
949		error = ENOMEM;
950		goto failed;
951	}
952
953	/* Using the backing NVRAM data ordering to order all variables
954	 * currently defined in the backing store */
955	cookiep = NULL;
956	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
957		prop = bhnd_nvram_plist_get_prop(unordered, name);
958		if (prop == NULL)
959			continue;
960
961		/* Append to ordered result */
962		if ((error = bhnd_nvram_plist_append(*props, prop)))
963			goto failed;
964
965		/* Remove from unordered list */
966		bhnd_nvram_plist_remove(unordered, name);
967	}
968
969	/* Any remaining variables are new, and should be appended to the
970	 * end of the export list */
971	prop = NULL;
972	while ((prop = bhnd_nvram_plist_next(unordered, prop)) != NULL) {
973		if ((error = bhnd_nvram_plist_append(*props, prop)))
974			goto failed;
975	}
976
977	/* Export complete */
978finished:
979	BHND_NVSTORE_UNLOCK(sc);
980
981	if (unordered != NULL)
982		bhnd_nvram_plist_release(unordered);
983
984	return (0);
985
986failed:
987	BHND_NVSTORE_UNLOCK(sc);
988
989	if (unordered != NULL)
990		bhnd_nvram_plist_release(unordered);
991
992	if (options != NULL && *options != NULL)
993		bhnd_nvram_plist_release(*options);
994
995	if (*props != NULL)
996		bhnd_nvram_plist_release(*props);
997
998	return (error);
999}
1000
1001/**
1002 * Encode all NVRAM properties at @p path, using the @p store's current NVRAM
1003 * data format.
1004 *
1005 * @param	sc	The NVRAM store instance.
1006 * @param	path	The NVRAM path to export, or NULL to select the root
1007 *			path.
1008 * @param[out]	data	On success, will be set to the newly serialized value.
1009 *			The caller is responsible for freeing this value
1010 *			via bhnd_nvram_io_free().
1011 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
1012 *
1013 * @retval 0		success
1014 * @retval EINVAL	If @p flags is invalid.
1015 * @retval ENOENT	The requested path was not found.
1016 * @retval ENOMEM	If allocation fails.
1017 * @retval non-zero	If serialization of  @p path otherwise fails, a regular
1018 *			unix error code will be returned.
1019 */
1020int
1021bhnd_nvram_store_serialize(struct bhnd_nvram_store *sc, const char *path,
1022   struct bhnd_nvram_io **data,  uint32_t flags)
1023{
1024	bhnd_nvram_plist	*props;
1025	bhnd_nvram_plist	*options;
1026	bhnd_nvram_data_class	*cls;
1027	struct bhnd_nvram_io	*io;
1028	void			*outp;
1029	size_t			 olen;
1030	int			 error;
1031
1032	props = NULL;
1033	options = NULL;
1034	io = NULL;
1035
1036	/* Perform requested export */
1037	error = bhnd_nvram_store_export(sc, path, &cls, &props, &options,
1038	    flags);
1039	if (error)
1040		return (error);
1041
1042	/* Determine serialized size */
1043	error = bhnd_nvram_data_serialize(cls, props, options, NULL, &olen);
1044	if (error)
1045		goto failed;
1046
1047	/* Allocate output buffer */
1048	if ((io = bhnd_nvram_iobuf_empty(olen, olen)) == NULL) {
1049		error = ENOMEM;
1050		goto failed;
1051	}
1052
1053	/* Fetch write pointer */
1054	if ((error = bhnd_nvram_io_write_ptr(io, 0, &outp, olen, NULL)))
1055		goto failed;
1056
1057	/* Perform serialization */
1058	error = bhnd_nvram_data_serialize(cls, props, options, outp, &olen);
1059	if (error)
1060		goto failed;
1061
1062	if ((error = bhnd_nvram_io_setsize(io, olen)))
1063		goto failed;
1064
1065	/* Success */
1066	bhnd_nvram_plist_release(props);
1067	bhnd_nvram_plist_release(options);
1068
1069	*data = io;
1070	return (0);
1071
1072failed:
1073	if (props != NULL)
1074		bhnd_nvram_plist_release(props);
1075
1076	if (options != NULL)
1077		bhnd_nvram_plist_release(options);
1078
1079	if (io != NULL)
1080		bhnd_nvram_io_free(io);
1081
1082	return (error);
1083}
1084
1085/**
1086 * Read an NVRAM variable.
1087 *
1088 * @param		sc	The NVRAM parser state.
1089 * @param		name	The NVRAM variable name.
1090 * @param[out]		outp	On success, the requested value will be written
1091 *				to this buffer. This argment may be NULL if
1092 *				the value is not desired.
1093 * @param[in,out]	olen	The capacity of @p outp. On success, will be set
1094 *				to the actual size of the requested value.
1095 * @param		otype	The requested data type to be written to
1096 *				@p outp.
1097 *
1098 * @retval 0		success
1099 * @retval ENOENT	The requested variable was not found.
1100 * @retval ENOMEM	If @p outp is non-NULL and a buffer of @p olen is too
1101 *			small to hold the requested value.
1102 * @retval non-zero	If reading @p name otherwise fails, a regular unix
1103 *			error code will be returned.
1104  */
1105int
1106bhnd_nvram_store_getvar(struct bhnd_nvram_store *sc, const char *name,
1107    void *outp, size_t *olen, bhnd_nvram_type otype)
1108{
1109	bhnd_nvstore_name_info	 info;
1110	bhnd_nvstore_path	*path;
1111	bhnd_nvram_prop		*prop;
1112	void			*cookiep;
1113	int			 error;
1114
1115	BHND_NVSTORE_LOCK(sc);
1116
1117	/* Parse the variable name */
1118	error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
1119	    sc->data_caps, &info);
1120	if (error)
1121		goto finished;
1122
1123	/* Fetch the variable's enclosing path entry */
1124	if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL) {
1125		error = ENOENT;
1126		goto finished;
1127	}
1128
1129	/* Search uncommitted updates first */
1130	prop = bhnd_nvstore_path_get_update(sc, path, info.name);
1131	if (prop != NULL) {
1132		if (bhnd_nvram_prop_is_null(prop)) {
1133			/* NULL denotes a pending deletion */
1134			error = ENOENT;
1135		} else {
1136			error = bhnd_nvram_prop_encode(prop, outp, olen, otype);
1137		}
1138		goto finished;
1139	}
1140
1141	/* Search the backing NVRAM data */
1142	cookiep = bhnd_nvstore_path_data_lookup(sc, path, info.name);
1143	if (cookiep != NULL) {
1144		/* Found in backing store */
1145		error = bhnd_nvram_data_getvar(sc->data, cookiep, outp, olen,
1146		     otype);
1147		goto finished;
1148	}
1149
1150	/* Not found */
1151	error = ENOENT;
1152
1153finished:
1154	BHND_NVSTORE_UNLOCK(sc);
1155	return (error);
1156}
1157
1158/**
1159 * Common bhnd_nvram_store_set*() and bhnd_nvram_store_unsetvar()
1160 * implementation.
1161 *
1162 * If @p value is NULL, the variable will be marked for deletion.
1163 */
1164static int
1165bhnd_nvram_store_setval_common(struct bhnd_nvram_store *sc, const char *name,
1166    bhnd_nvram_val *value)
1167{
1168	bhnd_nvstore_path	*path;
1169	bhnd_nvstore_name_info	 info;
1170	int			 error;
1171
1172	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
1173
1174	/* Parse the variable name */
1175	error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
1176	    sc->data_caps, &info);
1177	if (error)
1178		return (error);
1179
1180	/* Fetch the variable's enclosing path entry */
1181	if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL)
1182		return (error);
1183
1184	/* Register the update entry */
1185	return (bhnd_nvstore_path_register_update(sc, path, info.name, value));
1186}
1187
1188/**
1189 * Set an NVRAM variable.
1190 *
1191 * @param	sc	The NVRAM parser state.
1192 * @param	name	The NVRAM variable name.
1193 * @param	value	The new value.
1194 *
1195 * @retval 0		success
1196 * @retval ENOENT	The requested variable @p name was not found.
1197 * @retval EINVAL	If @p value is invalid.
1198 */
1199int
1200bhnd_nvram_store_setval(struct bhnd_nvram_store *sc, const char *name,
1201    bhnd_nvram_val *value)
1202{
1203	int error;
1204
1205	BHND_NVSTORE_LOCK(sc);
1206	error = bhnd_nvram_store_setval_common(sc, name, value);
1207	BHND_NVSTORE_UNLOCK(sc);
1208
1209	return (error);
1210}
1211
1212/**
1213 * Set an NVRAM variable.
1214 *
1215 * @param		sc	The NVRAM parser state.
1216 * @param		name	The NVRAM variable name.
1217 * @param[out]		inp	The new value.
1218 * @param[in,out]	ilen	The size of @p inp.
1219 * @param		itype	The data type of @p inp.
1220 *
1221 * @retval 0		success
1222 * @retval ENOENT	The requested variable @p name was not found.
1223 * @retval EINVAL	If the new value is invalid.
1224 * @retval EINVAL	If @p name is read-only.
1225 */
1226int
1227bhnd_nvram_store_setvar(struct bhnd_nvram_store *sc, const char *name,
1228    const void *inp, size_t ilen, bhnd_nvram_type itype)
1229{
1230	bhnd_nvram_val	val;
1231	int		error;
1232
1233	error = bhnd_nvram_val_init(&val, NULL, inp, ilen, itype,
1234	    BHND_NVRAM_VAL_FIXED|BHND_NVRAM_VAL_BORROW_DATA);
1235	if (error) {
1236		BHND_NV_LOG("error initializing value: %d\n", error);
1237		return (EINVAL);
1238	}
1239
1240	BHND_NVSTORE_LOCK(sc);
1241	error = bhnd_nvram_store_setval_common(sc, name, &val);
1242	BHND_NVSTORE_UNLOCK(sc);
1243
1244	bhnd_nvram_val_release(&val);
1245
1246	return (error);
1247}
1248
1249/**
1250 * Unset an NVRAM variable.
1251 *
1252 * @param		sc	The NVRAM parser state.
1253 * @param		name	The NVRAM variable name.
1254 *
1255 * @retval 0		success
1256 * @retval ENOENT	The requested variable @p name was not found.
1257 * @retval EINVAL	If @p name is read-only.
1258 */
1259int
1260bhnd_nvram_store_unsetvar(struct bhnd_nvram_store *sc, const char *name)
1261{
1262	int error;
1263
1264	BHND_NVSTORE_LOCK(sc);
1265	error = bhnd_nvram_store_setval_common(sc, name, BHND_NVRAM_VAL_NULL);
1266	BHND_NVSTORE_UNLOCK(sc);
1267
1268	return (error);
1269}
1270