/*-
 * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/hash.h>
#include <sys/limits.h>
#include <sys/queue.h>

#ifdef _KERNEL

#include <sys/ctype.h>
#include <sys/systm.h>

#include <machine/_inttypes.h>

#else /* !_KERNEL */

#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#endif /* _KERNEL */

#include "bhnd_nvram_private.h"
#include "bhnd_nvram_datavar.h"

#include "bhnd_nvram_storevar.h"

/*
 * BHND NVRAM Store
 *
 * Manages in-memory and persistent representations of NVRAM data.
 */

static int			 bhnd_nvstore_parse_data(
				     struct bhnd_nvram_store *sc);

static int			 bhnd_nvstore_parse_path_entries(
				     struct bhnd_nvram_store *sc);

static int			 bhnd_nvram_store_export_child(
				     struct bhnd_nvram_store *sc,
				     bhnd_nvstore_path *top,
				     bhnd_nvstore_path *child,
				     bhnd_nvram_plist *plist,
				     uint32_t flags);

static int			 bhnd_nvstore_export_merge(
				     struct bhnd_nvram_store *sc,
				     bhnd_nvstore_path *path,
				     bhnd_nvram_plist *merged,
				     uint32_t flags);

static int			 bhnd_nvstore_export_devpath_alias(
				     struct bhnd_nvram_store *sc,
				     bhnd_nvstore_path *path,
				     const char *devpath,
				     bhnd_nvram_plist *plist,
				     u_long *alias_val);

/**
 * Allocate and initialize a new NVRAM data store instance.
 *
 * The caller is responsible for deallocating the instance via
 * bhnd_nvram_store_free().
 * 
 * @param[out] store On success, a pointer to the newly allocated NVRAM data
 * instance.
 * @param data The NVRAM data to be managed by the returned NVRAM data store
 * instance.
 *
 * @retval 0 success
 * @retval non-zero if an error occurs during allocation or initialization, a
 * regular unix error code will be returned.
 */
int
bhnd_nvram_store_new(struct bhnd_nvram_store **store,
    struct bhnd_nvram_data *data)
{
	struct bhnd_nvram_store *sc;
	int			 error;

	/* Allocate new instance */
	sc = bhnd_nv_calloc(1, sizeof(*sc));
	if (sc == NULL)
		return (ENOMEM);

	BHND_NVSTORE_LOCK_INIT(sc);
	BHND_NVSTORE_LOCK(sc);

	/* Initialize path hash table */
	sc->num_paths = 0;
	for (size_t i = 0; i < nitems(sc->paths); i++)
		LIST_INIT(&sc->paths[i]);

	/* Initialize alias hash table */
	sc->num_aliases = 0;
	for (size_t i = 0; i < nitems(sc->aliases); i++)
		LIST_INIT(&sc->aliases[i]);

	/* Retain the NVRAM data */
	sc->data = bhnd_nvram_data_retain(data);
	sc->data_caps = bhnd_nvram_data_caps(data);
	sc->data_opts = bhnd_nvram_data_options(data);
	if (sc->data_opts != NULL) {
		bhnd_nvram_plist_retain(sc->data_opts);
	} else {
		sc->data_opts = bhnd_nvram_plist_new();
		if (sc->data_opts == NULL) {
			error = ENOMEM;
			goto cleanup;
		}
	}

	/* Register required root path */
	error = bhnd_nvstore_register_path(sc, BHND_NVSTORE_ROOT_PATH,
	    BHND_NVSTORE_ROOT_PATH_LEN);
	if (error)
		goto cleanup;

	sc->root_path = bhnd_nvstore_get_path(sc, BHND_NVSTORE_ROOT_PATH,
	    BHND_NVSTORE_ROOT_PATH_LEN);
	BHND_NV_ASSERT(sc->root_path, ("missing root path"));

	/* Parse all variables vended by our backing NVRAM data instance,
	 * generating all path entries, alias entries, and variable indexes */
	if ((error = bhnd_nvstore_parse_data(sc)))
		goto cleanup;

	*store = sc;

	BHND_NVSTORE_UNLOCK(sc);
	return (0);

cleanup:
	BHND_NVSTORE_UNLOCK(sc);
	bhnd_nvram_store_free(sc);
	return (error);
}

/**
 * Allocate and initialize a new NVRAM data store instance, parsing the
 * NVRAM data from @p io.
 *
 * The caller is responsible for deallocating the instance via
 * bhnd_nvram_store_free().
 * 
 * The NVRAM data mapped by @p io will be copied, and @p io may be safely
 * deallocated after bhnd_nvram_store_new() returns.
 * 
 * @param[out] store On success, a pointer to the newly allocated NVRAM data
 * instance.
 * @param io An I/O context mapping the NVRAM data to be copied and parsed.
 * @param cls The NVRAM data class to be used when parsing @p io, or NULL
 * to perform runtime identification of the appropriate data class.
 *
 * @retval 0 success
 * @retval non-zero if an error occurs during allocation or initialization, a
 * regular unix error code will be returned.
 */
int
bhnd_nvram_store_parse_new(struct bhnd_nvram_store **store,
    struct bhnd_nvram_io *io, bhnd_nvram_data_class *cls)
{
	struct bhnd_nvram_data	*data;
	int			 error;

	/* Try to parse the data */
	if ((error = bhnd_nvram_data_new(cls, &data, io)))
		return (error);

	/* Try to create our new store instance */
	error = bhnd_nvram_store_new(store, data);
	bhnd_nvram_data_release(data);

	return (error);
}

/**
 * Free an NVRAM store instance, releasing all associated resources.
 * 
 * @param sc A store instance previously allocated via
 * bhnd_nvram_store_new().
 */
void
bhnd_nvram_store_free(struct bhnd_nvram_store *sc)
{

	/* Clean up alias hash table */
	for (size_t i = 0; i < nitems(sc->aliases); i++) {
		bhnd_nvstore_alias *alias, *anext;
		LIST_FOREACH_SAFE(alias, &sc->aliases[i], na_link, anext)
			bhnd_nv_free(alias);
	}

	/* Clean up path hash table */
	for (size_t i = 0; i < nitems(sc->paths); i++) {
		bhnd_nvstore_path *path, *pnext;
		LIST_FOREACH_SAFE(path, &sc->paths[i], np_link, pnext)
			bhnd_nvstore_path_free(path);
	}

	if (sc->data != NULL)
		bhnd_nvram_data_release(sc->data);

	if (sc->data_opts != NULL)
		bhnd_nvram_plist_release(sc->data_opts);

	BHND_NVSTORE_LOCK_DESTROY(sc);
	bhnd_nv_free(sc);
}

/**
 * Parse all variables vended by our backing NVRAM data instance,
 * generating all path entries, alias entries, and variable indexes.
 * 
 * @param	sc	The NVRAM store instance to be initialized with
 *			paths, aliases, and data parsed from its backing
 *			data.
 *
 * @retval 0		success
 * @retval non-zero	if an error occurs during parsing, a regular unix error
 *			code will be returned.
 */
static int
bhnd_nvstore_parse_data(struct bhnd_nvram_store *sc)
{
	const char	*name;
	void		*cookiep;
	int		 error;

	/* Parse and register all device paths and path aliases. This enables
	 * resolution of _forward_ references to device paths aliases when
	 * scanning variable entries below */
	if ((error = bhnd_nvstore_parse_path_entries(sc)))
		return (error);

	/* Calculate the per-path variable counts, and report dangling alias
	 * references as an error. */
	cookiep = NULL;
	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
		bhnd_nvstore_path	*path;
		bhnd_nvstore_name_info	 info;

		/* Parse the name info */
		error = bhnd_nvstore_parse_name_info(name,
		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
		if (error)
			return (error);

		switch (info.type) {
		case BHND_NVSTORE_VAR:
			/* Fetch referenced path */
			path = bhnd_nvstore_var_get_path(sc, &info);
			if (path == NULL) {
				BHND_NV_LOG("variable '%s' has dangling "
					    "path reference\n", name);
				return (EFTYPE);
			}

			/* Increment path variable count */
			if (path->num_vars == SIZE_MAX) {
				BHND_NV_LOG("more than SIZE_MAX variables in "
				    "path %s\n", path->path_str);
				return (EFTYPE);
			}
			path->num_vars++;
			break;

		case BHND_NVSTORE_ALIAS_DECL:
			/* Skip -- path alias already parsed and recorded */
			break;
		}
	}

	/* If the backing NVRAM data instance vends only a single root ("/")
	 * path, we may be able to skip generating an index for the root
	 * path */
	if (sc->num_paths == 1) {
		bhnd_nvstore_path *path;

		/* If the backing instance provides its own name-based lookup
		 * indexing, we can skip generating a duplicate here */
		if (sc->data_caps & BHND_NVRAM_DATA_CAP_INDEXED)
			return (0);

		/* If the sole root path contains fewer variables than the
		 * minimum indexing threshhold, we do not need to generate an
		 * index */
		path = bhnd_nvstore_get_root_path(sc);
		if (path->num_vars < BHND_NV_IDX_VAR_THRESHOLD)
			return (0);
	}

	/* Allocate per-path index instances */
	for (size_t i = 0; i < nitems(sc->paths); i++) {
		bhnd_nvstore_path	*path;

		LIST_FOREACH(path, &sc->paths[i], np_link) {
			path->index = bhnd_nvstore_index_new(path->num_vars);
			if (path->index == NULL)
				return (ENOMEM);
		}
	}

	/* Populate per-path indexes */
	cookiep = NULL;
	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
		bhnd_nvstore_name_info	 info;
		bhnd_nvstore_path	*path;

		/* Parse the name info */
		error = bhnd_nvstore_parse_name_info(name,
		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
		if (error)
			return (error);

		switch (info.type) {
		case BHND_NVSTORE_VAR:
			/* Fetch referenced path */
			path = bhnd_nvstore_var_get_path(sc, &info);
			BHND_NV_ASSERT(path != NULL,
			    ("dangling path reference"));

			/* Append to index */
			error = bhnd_nvstore_index_append(sc, path->index,
			    cookiep);
			if (error)
				return (error);
			break;

		case BHND_NVSTORE_ALIAS_DECL:
			/* Skip */
			break;
		}
	}

	/* Prepare indexes for querying */
	for (size_t i = 0; i < nitems(sc->paths); i++) {
		bhnd_nvstore_path	*path;

		LIST_FOREACH(path, &sc->paths[i], np_link) {
			error = bhnd_nvstore_index_prepare(sc, path->index);
			if (error)
				return (error);
		}
	}

	return (0);
}

/**
 * Parse and register path and path alias entries for all declarations found in
 * the NVRAM data backing @p nvram.
 * 
 * @param sc		The NVRAM store instance.
 *
 * @retval 0		success
 * @retval non-zero	If parsing fails, a regular unix error code will be
 *			returned.
 */
static int
bhnd_nvstore_parse_path_entries(struct bhnd_nvram_store *sc)
{
	const char	*name;
	void		*cookiep;
	int		 error;

	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

	/* Skip path registration if the data source does not support device
	 * paths. */
	if (!(sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)) {
		BHND_NV_ASSERT(sc->root_path != NULL, ("missing root path"));
		return (0);
	}

	/* Otherwise, parse and register all paths and path aliases */
	cookiep = NULL;
	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
		bhnd_nvstore_name_info info;

		/* Parse the name info */
		error = bhnd_nvstore_parse_name_info(name,
		    BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
		if (error)
			return (error);

		/* Register the path */
		error = bhnd_nvstore_var_register_path(sc, &info, cookiep);
		if (error) {
			BHND_NV_LOG("failed to register path for %s: %d\n",
			    name, error);
			return (error);
		}
	}

	return (0);
}

/**
 * Merge exported per-path variables (uncommitted, committed, or both) into 
 * the empty @p merged property list.
 * 
 * @param	sc	The NVRAM store instance.
 * @param	path	The NVRAM path to be exported.
 * @param	merged	The property list to populate with the merged results.
 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
 * 
 * @retval 0		success
 * @retval ENOMEM	If allocation fails.
 * @retval non-zero	If merging the variables defined in @p path otherwise
 *			fails, a regular unix error code will be returned.
 */
static int
bhnd_nvstore_export_merge(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *path, bhnd_nvram_plist *merged, uint32_t flags)
{
	void	*cookiep, *idxp;
	int	 error;

	/* Populate merged list with all pending variables */
	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
		bhnd_nvram_prop *prop;

		prop = NULL;
		while ((prop = bhnd_nvram_plist_next(path->pending, prop))) {
			/* Skip variables marked for deletion */
			if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED)) {
				if (bhnd_nvram_prop_is_null(prop))
					continue;
			}

			/* Append to merged list */
			error = bhnd_nvram_plist_append(merged, prop);
			if (error)
				return (error);
		}
	}

	/* Skip merging committed variables? */
	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED))
		return (0);

	/* Merge in the committed NVRAM variables */
	idxp = NULL;
	while ((cookiep = bhnd_nvstore_path_data_next(sc, path, &idxp))) {
		const char	*name;
		bhnd_nvram_val	*val;

		/* Fetch the variable name */
		name = bhnd_nvram_data_getvar_name(sc->data, cookiep);

		/* Trim device path prefix */
		if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)
			name = bhnd_nvram_trim_path_name(name);

		/* Skip if already defined in pending updates */
		if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
			if (bhnd_nvram_plist_contains(path->pending, name))
				continue;
		}

		/* Skip if higher precedence value was already defined. This
		 * may occur if the underlying data store contains duplicate
		 * keys; iteration will always return the definition with
		 * the highest precedence first */
		if (bhnd_nvram_plist_contains(merged, name))
			continue;

		/* Fetch the variable's value representation */
		if ((error = bhnd_nvram_data_copy_val(sc->data, cookiep, &val)))
			return (error);

		/* Add to path variable list */
		error = bhnd_nvram_plist_append_val(merged, name, val);
		bhnd_nvram_val_release(val);
		if (error)
			return (error);
	}

	return (0);
}

/**
 * Find a free alias value for @p path, and append the devpathXX alias
 * declaration to @p plist.
 * 
 * @param	sc		The NVRAM store instance.
 * @param	path		The NVRAM path for which a devpath alias
 *				variable should be produced.
 * @param	devpath		The devpathXX path value for @p path.
 * @param	plist		The property list to which @p path's devpath
 *				variable will be appended.
 * @param[out]	alias_val	On success, will be set to the alias value
 *				allocated for @p path.
 * 
 * @retval 0		success
 * @retval ENOMEM	If allocation fails.
 * @retval non-zero	If merging the variables defined in @p path otherwise
 *			fails, a regular unix error code will be returned.
 */
static int
bhnd_nvstore_export_devpath_alias(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *path, const char *devpath, bhnd_nvram_plist *plist,
    u_long *alias_val)
{
	bhnd_nvstore_alias	*alias;
	char			*pathvar;
	int			 error;

	*alias_val = 0;

	/* Prefer alias value already reserved for this path. */
	alias = bhnd_nvstore_find_alias(sc, path->path_str);
	if (alias != NULL) {
		*alias_val = alias->alias;

		/* Allocate devpathXX variable name */
		bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
		if (pathvar == NULL)
			return (ENOMEM);

		/* Append alias variable to property list */
		error = bhnd_nvram_plist_append_string(plist, pathvar, devpath);

		BHND_NV_ASSERT(error != EEXIST, ("reserved alias %lu:%s in use",
		   * alias_val, path->path_str));

		bhnd_nv_free(pathvar);
		return (error);
	}

	/* Find the next free devpathXX alias entry */
	while (1) {
		/* Skip existing reserved alias values */
		while (bhnd_nvstore_get_alias(sc, *alias_val) != NULL) {
			if (*alias_val == ULONG_MAX)
				return (ENOMEM);

			(*alias_val)++;
		}

		/* Allocate devpathXX variable name */
		bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
		if (pathvar == NULL)
			return (ENOMEM);

		/* If not in-use, we can terminate the search */
		if (!bhnd_nvram_plist_contains(plist, pathvar))
			break;

		/* Keep searching */
		bhnd_nv_free(pathvar);

		if (*alias_val == ULONG_MAX)
			return (ENOMEM);

		(*alias_val)++;
	}

	/* Append alias variable to property list */
	error = bhnd_nvram_plist_append_string(plist, pathvar, devpath);

	bhnd_nv_free(pathvar);
	return (error);
}

/**
 * Export a single @p child path's properties, appending the result to @p plist.
 * 
 * @param	sc		The NVRAM store instance.
 * @param	top		The root NVRAM path being exported.
 * @param	child		The NVRAM path to be exported.
 * @param	plist		The property list to which @p child's exported
 *				properties should be appended.
 * @param	flags		Export flags. See BHND_NVSTORE_EXPORT_*.
 * 
 * @retval 0		success
 * @retval ENOMEM	If allocation fails.
 * @retval non-zero	If merging the variables defined in @p path otherwise
 *			fails, a regular unix error code will be returned.
 */
static int
bhnd_nvram_store_export_child(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *top, bhnd_nvstore_path *child, bhnd_nvram_plist *plist,
    uint32_t flags)
{
	bhnd_nvram_plist	*path_vars;
	bhnd_nvram_prop		*prop;
	const char		*relpath;
	char			*prefix, *namebuf;
	size_t			 prefix_len, relpath_len;
	size_t			 namebuf_size;
	bool			 emit_compact_devpath;
	int			 error;

	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

	prefix = NULL;
	path_vars = NULL;
	namebuf = NULL;

	/* Determine the path relative to the top-level path */
	relpath = bhnd_nvstore_parse_relpath(top->path_str, child->path_str);
	if (relpath == NULL) {
		/* Skip -- not a child of the root path */
		return (0);
	}
	relpath_len = strlen(relpath);

	/* Skip sub-path if export of children was not requested,  */
	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_CHILDREN) && relpath_len > 0)
		return (0);

	/* Collect all variables to be included in the export */
	if ((path_vars = bhnd_nvram_plist_new()) == NULL)
		return (ENOMEM);

	if ((error = bhnd_nvstore_export_merge(sc, child, path_vars, flags))) {
		bhnd_nvram_plist_release(path_vars);
		return (error);
	}

	/* Skip if no children are to be exported */
	if (bhnd_nvram_plist_count(path_vars) == 0) {
		bhnd_nvram_plist_release(path_vars);
		return (0);
	}

	/* Determine appropriate device path encoding */
	emit_compact_devpath = false;
	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS)) {
		/* Re-encode as compact (if non-empty path) */
		if (relpath_len > 0)
			emit_compact_devpath = true;
	} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) {
		/* Re-encode with fully expanded device path */
		emit_compact_devpath = false;
	} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
		/* Preserve existing encoding of this path */
		if (bhnd_nvstore_find_alias(sc, child->path_str) != NULL)
			emit_compact_devpath = true;
	} else {
		BHND_NV_LOG("invalid device path flag: %#" PRIx32, flags);
		error = EINVAL;
		goto finished;
	}

	/* Allocate variable device path prefix to use for all property names,
	 * and if using compact encoding, emit the devpathXX= variable */
	prefix = NULL;
	prefix_len = 0;
	if (emit_compact_devpath) {
		u_long	alias_val;
		int	len;

		/* Reserve an alias value and append the devpathXX= variable to
		 * the property list */
		error = bhnd_nvstore_export_devpath_alias(sc, child, relpath,
		    plist, &alias_val);
		if (error)
			goto finished;

		/* Allocate variable name prefix */
		len = bhnd_nv_asprintf(&prefix, "%lu:", alias_val);
		if (prefix == NULL) {
			error = ENOMEM;
			goto finished;
		}

		prefix_len = len;
	} else if (relpath_len > 0) {
		int len;

		/* Allocate the variable name prefix, appending '/' to the
		 * relative path */
		len = bhnd_nv_asprintf(&prefix, "%s/", relpath);
		if (prefix == NULL) {
			error = ENOMEM;
			goto finished;
		}

		prefix_len = len;
	}

	/* If prefixing of variable names is required, allocate a name
	 * formatting buffer */
	namebuf_size = 0;
	if (prefix != NULL) {
		size_t	maxlen;

		/* Find the maximum name length */
		maxlen = 0;
		prop = NULL;
		while ((prop = bhnd_nvram_plist_next(path_vars, prop))) {
			const char *name;

			name = bhnd_nvram_prop_name(prop);
			maxlen = bhnd_nv_ummax(strlen(name), maxlen);
		}

		/* Allocate name buffer (path-prefix + name + '\0') */
		namebuf_size = prefix_len + maxlen + 1;
		namebuf = bhnd_nv_malloc(namebuf_size);
		if (namebuf == NULL) {
			error = ENOMEM;
			goto finished;
		}
	}

	/* Append all path variables to the export plist, prepending the
	 * device-path prefix to the variable names, if required */
	prop = NULL;
	while ((prop = bhnd_nvram_plist_next(path_vars, prop)) != NULL) {
		const char *name;

		/* Prepend device prefix to the variable name */
		name = bhnd_nvram_prop_name(prop);
		if (prefix != NULL) {
			int len;

			/*
			 * Write prefixed variable name to our name buffer.
			 * 
			 * We precalcuate the size when scanning all names 
			 * above, so this should always succeed.
			 */
			len = snprintf(namebuf, namebuf_size, "%s%s", prefix,
			    name);
			if (len < 0 || (size_t)len >= namebuf_size)
				BHND_NV_PANIC("invalid max_name_len");

			name = namebuf;
		}

		/* Add property to export plist */
		error = bhnd_nvram_plist_append_val(plist, name,
		    bhnd_nvram_prop_val(prop));
		if (error)
			goto finished;
	}

	/* Success */
	error = 0;

finished:
	if (prefix != NULL)
		bhnd_nv_free(prefix);

	if (namebuf != NULL)
		bhnd_nv_free(namebuf);

	if (path_vars != NULL)
		bhnd_nvram_plist_release(path_vars);

	return (error);
}

/**
 * Export a flat, ordered NVRAM property list representation of all NVRAM
 * properties at @p path.
 * 
 * @param	sc	The NVRAM store instance.
 * @param	path	The NVRAM path to export, or NULL to select the root
 *			path.
 * @param[out]	cls	On success, will be set to the backing data class
 *			of @p sc. If the data class is are not desired,
 *			a NULL pointer may be provided.
 * @param[out]	props	On success, will be set to a caller-owned property
 *			list containing the exported properties. The caller is
 *			responsible for releasing this value via
 *			bhnd_nvram_plist_release().
 * @param[out]	options	On success, will be set to a caller-owned property
 *			list containing the current NVRAM serialization options
 *			for @p sc. The caller is responsible for releasing this
 *			value via bhnd_nvram_plist_release().
 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
 * 
 * @retval 0		success
 * @retval EINVAL	If @p flags is invalid.
 * @retval ENOENT	The requested path was not found.
 * @retval ENOMEM	If allocation fails.
 * @retval non-zero	If export of  @p path otherwise fails, a regular unix
 *			error code will be returned.
 */
int
bhnd_nvram_store_export(struct bhnd_nvram_store *sc, const char *path,
    bhnd_nvram_data_class **cls, bhnd_nvram_plist **props,
    bhnd_nvram_plist **options, uint32_t flags)
{
	bhnd_nvram_plist	*unordered;
	bhnd_nvstore_path	*top;
	bhnd_nvram_prop		*prop;
	const char		*name;
	void			*cookiep;
	size_t			 num_dpath_flags;
	int			 error;

	*props = NULL;
	unordered = NULL;
	num_dpath_flags = 0;
	if (options != NULL)
		*options = NULL;

	/* Default to exporting root path */
	if (path == NULL)
		path = BHND_NVSTORE_ROOT_PATH;

	/* Default to exporting all properties */
	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED) &&
	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED))
	{
		flags |= BHND_NVSTORE_EXPORT_ALL_VARS;
	}

	/* Default to preserving the current device path encoding */
	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS) &&
	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS))
	{
		flags |= BHND_NVSTORE_EXPORT_PRESERVE_DEVPATHS;
	}

	/* Exactly one device path encoding flag must be set */
	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS))
		num_dpath_flags++;

	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS))
		num_dpath_flags++;

	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS))
		num_dpath_flags++;

	if (num_dpath_flags != 1)
		return (EINVAL);

	/* If EXPORT_DELETED is set, EXPORT_UNCOMMITTED must be set too */
	if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED) &&
	    !BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED))
	{
		return (EINVAL);
	}

	/* Lock internal state before querying paths/properties */
	BHND_NVSTORE_LOCK(sc);

	/* Fetch referenced path */
	top = bhnd_nvstore_get_path(sc, path, strlen(path));
	if (top == NULL) {
		error = ENOENT;
		goto failed;
	}

	/* Allocate new, empty property list */
	if ((unordered = bhnd_nvram_plist_new()) == NULL) {
		error = ENOMEM;
		goto failed;
	}

	/* Export the top-level path first */
	error = bhnd_nvram_store_export_child(sc, top, top, unordered, flags);
	if (error)
		goto failed;

	/* Attempt to export any children of the root path */
	for (size_t i = 0; i < nitems(sc->paths); i++) {
		bhnd_nvstore_path *child;

		LIST_FOREACH(child, &sc->paths[i], np_link) {
			/* Top-level path was already exported */
			if (child == top)
				continue;

			error = bhnd_nvram_store_export_child(sc, top,
			    child, unordered, flags);
			if (error)
				goto failed;
		}
	}

	/* If requested, provide the current class and serialization options */
	if (cls != NULL)
		*cls = bhnd_nvram_data_get_class(sc->data);

	if (options != NULL)
		*options = bhnd_nvram_plist_retain(sc->data_opts);

	/*
	 * If we're re-encoding device paths, don't bother preserving the
	 * existing NVRAM variable order; our variable names will not match
	 * the existing backing NVRAM data.
	 */
	if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
		*props = unordered;
		unordered = NULL;

		goto finished;
	}

	/* 
	 * Re-order the flattened output to match the existing NVRAM variable
	 * ordering.
	 * 
	 * We append all new variables at the end of the input; this should
	 * reduce the delta that needs to be written (e.g. to flash) when
	 * committing NVRAM updates, and should result in a serialization
	 * identical to the input serialization if uncommitted updates are
	 * excluded from the export.
	 */
	if ((*props = bhnd_nvram_plist_new()) == NULL) {
		error = ENOMEM;
		goto failed;
	}

	/* Using the backing NVRAM data ordering to order all variables
	 * currently defined in the backing store */ 
	cookiep = NULL;
	while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
		prop = bhnd_nvram_plist_get_prop(unordered, name);
		if (prop == NULL)
			continue;

		/* Append to ordered result */
		if ((error = bhnd_nvram_plist_append(*props, prop)))
			goto failed;

		/* Remove from unordered list */
		bhnd_nvram_plist_remove(unordered, name);
	}

	/* Any remaining variables are new, and should be appended to the
	 * end of the export list */
	prop = NULL;
	while ((prop = bhnd_nvram_plist_next(unordered, prop)) != NULL) {
		if ((error = bhnd_nvram_plist_append(*props, prop)))
			goto failed;
	}

	/* Export complete */
finished:
	BHND_NVSTORE_UNLOCK(sc);

	if (unordered != NULL)
		bhnd_nvram_plist_release(unordered);

	return (0);

failed:
	BHND_NVSTORE_UNLOCK(sc);

	if (unordered != NULL)
		bhnd_nvram_plist_release(unordered);

	if (options != NULL && *options != NULL)
		bhnd_nvram_plist_release(*options);

	if (*props != NULL)
		bhnd_nvram_plist_release(*props);

	return (error);
}

/**
 * Encode all NVRAM properties at @p path, using the @p store's current NVRAM
 * data format.
 * 
 * @param	sc	The NVRAM store instance.
 * @param	path	The NVRAM path to export, or NULL to select the root
 *			path.
 * @param[out]	data	On success, will be set to the newly serialized value.
 *			The caller is responsible for freeing this value
 *			via bhnd_nvram_io_free().
 * @param	flags	Export flags. See BHND_NVSTORE_EXPORT_*.
 *
 * @retval 0		success
 * @retval EINVAL	If @p flags is invalid.
 * @retval ENOENT	The requested path was not found.
 * @retval ENOMEM	If allocation fails.
 * @retval non-zero	If serialization of  @p path otherwise fails, a regular
 *			unix error code will be returned.
 */
int
bhnd_nvram_store_serialize(struct bhnd_nvram_store *sc, const char *path,
   struct bhnd_nvram_io **data,  uint32_t flags)
{
	bhnd_nvram_plist	*props;
	bhnd_nvram_plist	*options;
	bhnd_nvram_data_class	*cls;
	struct bhnd_nvram_io	*io;
	void			*outp;
	size_t			 olen;
	int			 error;

	props = NULL;
	options = NULL;
	io = NULL;

	/* Perform requested export */
	error = bhnd_nvram_store_export(sc, path, &cls, &props, &options,
	    flags);
	if (error)
		return (error);

	/* Determine serialized size */
	error = bhnd_nvram_data_serialize(cls, props, options, NULL, &olen);
	if (error)
		goto failed;

	/* Allocate output buffer */
	if ((io = bhnd_nvram_iobuf_empty(olen, olen)) == NULL) {
		error = ENOMEM;
		goto failed;
	}

	/* Fetch write pointer */
	if ((error = bhnd_nvram_io_write_ptr(io, 0, &outp, olen, NULL)))
		goto failed;

	/* Perform serialization */
	error = bhnd_nvram_data_serialize(cls, props, options, outp, &olen);
	if (error)
		goto failed;

	if ((error = bhnd_nvram_io_setsize(io, olen)))
		goto failed;

	/* Success */
	bhnd_nvram_plist_release(props);
	bhnd_nvram_plist_release(options);

	*data = io;
	return (0);

failed:
	if (props != NULL)
		bhnd_nvram_plist_release(props);

	if (options != NULL)
		bhnd_nvram_plist_release(options);

	if (io != NULL)
		bhnd_nvram_io_free(io);

	return (error);
}

/**
 * Read an NVRAM variable.
 *
 * @param		sc	The NVRAM parser state.
 * @param		name	The NVRAM variable name.
 * @param[out]		outp	On success, the requested value will be written
 *				to this buffer. This argment may be NULL if
 *				the value is not desired.
 * @param[in,out]	olen	The capacity of @p outp. On success, will be set
 *				to the actual size of the requested value.
 * @param		otype	The requested data type to be written to
 *				@p outp.
 *
 * @retval 0		success
 * @retval ENOENT	The requested variable was not found.
 * @retval ENOMEM	If @p outp is non-NULL and a buffer of @p olen is too
 *			small to hold the requested value.
 * @retval non-zero	If reading @p name otherwise fails, a regular unix
 *			error code will be returned.
  */
int
bhnd_nvram_store_getvar(struct bhnd_nvram_store *sc, const char *name,
    void *outp, size_t *olen, bhnd_nvram_type otype)
{
	bhnd_nvstore_name_info	 info;
	bhnd_nvstore_path	*path;
	bhnd_nvram_prop		*prop;
	void			*cookiep;
	int			 error;

	BHND_NVSTORE_LOCK(sc);

	/* Parse the variable name */
	error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
	    sc->data_caps, &info);
	if (error)
		goto finished;

	/* Fetch the variable's enclosing path entry */
	if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL) {
		error = ENOENT;
		goto finished;
	}

	/* Search uncommitted updates first */
	prop = bhnd_nvstore_path_get_update(sc, path, info.name);
	if (prop != NULL) {
		if (bhnd_nvram_prop_is_null(prop)) {
			/* NULL denotes a pending deletion */
			error = ENOENT;
		} else {
			error = bhnd_nvram_prop_encode(prop, outp, olen, otype);
		}
		goto finished;
	}

	/* Search the backing NVRAM data */
	cookiep = bhnd_nvstore_path_data_lookup(sc, path, info.name);
	if (cookiep != NULL) {
		/* Found in backing store */
		error = bhnd_nvram_data_getvar(sc->data, cookiep, outp, olen,
		     otype);
		goto finished;
	}

	/* Not found */
	error = ENOENT;

finished:
	BHND_NVSTORE_UNLOCK(sc);
	return (error);
}

/**
 * Common bhnd_nvram_store_set*() and bhnd_nvram_store_unsetvar()
 * implementation.
 * 
 * If @p value is NULL, the variable will be marked for deletion.
 */
static int
bhnd_nvram_store_setval_common(struct bhnd_nvram_store *sc, const char *name,
    bhnd_nvram_val *value)
{
	bhnd_nvstore_path	*path;
	bhnd_nvstore_name_info	 info;
	int			 error;

	BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

	/* Parse the variable name */
	error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
	    sc->data_caps, &info);
	if (error)
		return (error);

	/* Fetch the variable's enclosing path entry */
	if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL)
		return (error);

	/* Register the update entry */
	return (bhnd_nvstore_path_register_update(sc, path, info.name, value));
}

/**
 * Set an NVRAM variable.
 * 
 * @param	sc	The NVRAM parser state.
 * @param	name	The NVRAM variable name.
 * @param	value	The new value.
 *
 * @retval 0		success
 * @retval ENOENT	The requested variable @p name was not found.
 * @retval EINVAL	If @p value is invalid.
 */
int
bhnd_nvram_store_setval(struct bhnd_nvram_store *sc, const char *name,
    bhnd_nvram_val *value)
{
	int error;

	BHND_NVSTORE_LOCK(sc);
	error = bhnd_nvram_store_setval_common(sc, name, value);
	BHND_NVSTORE_UNLOCK(sc);

	return (error);
}

/**
 * Set an NVRAM variable.
 * 
 * @param		sc	The NVRAM parser state.
 * @param		name	The NVRAM variable name.
 * @param[out]		inp	The new value.
 * @param[in,out]	ilen	The size of @p inp.
 * @param		itype	The data type of @p inp.
 *
 * @retval 0		success
 * @retval ENOENT	The requested variable @p name was not found.
 * @retval EINVAL	If the new value is invalid.
 * @retval EINVAL	If @p name is read-only.
 */
int
bhnd_nvram_store_setvar(struct bhnd_nvram_store *sc, const char *name,
    const void *inp, size_t ilen, bhnd_nvram_type itype)
{
	bhnd_nvram_val	val;
	int		error;

	error = bhnd_nvram_val_init(&val, NULL, inp, ilen, itype,
	    BHND_NVRAM_VAL_FIXED|BHND_NVRAM_VAL_BORROW_DATA);
	if (error) {
		BHND_NV_LOG("error initializing value: %d\n", error);
		return (EINVAL);
	}

	BHND_NVSTORE_LOCK(sc);
	error = bhnd_nvram_store_setval_common(sc, name, &val);
	BHND_NVSTORE_UNLOCK(sc);

	bhnd_nvram_val_release(&val);

	return (error);
}

/**
 * Unset an NVRAM variable.
 * 
 * @param		sc	The NVRAM parser state.
 * @param		name	The NVRAM variable name.
 *
 * @retval 0		success
 * @retval ENOENT	The requested variable @p name was not found.
 * @retval EINVAL	If @p name is read-only.
 */
int
bhnd_nvram_store_unsetvar(struct bhnd_nvram_store *sc, const char *name)
{
	int error;

	BHND_NVSTORE_LOCK(sc);
	error = bhnd_nvram_store_setval_common(sc, name, BHND_NVRAM_VAL_NULL);
	BHND_NVSTORE_UNLOCK(sc);

	return (error);
}
