root/sys/dev/bhnd/nvram/bhnd_nvram_store.c
/*-
 * 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/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);
}