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