root/sys/dev/bhnd/nvram/bhnd_nvram_store_subr.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/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"

static int bhnd_nvstore_idx_cmp(const void *lhs, const void *rhs, void *ctx);

/**
 * Allocate and initialize a new path instance.
 * 
 * The caller is responsible for deallocating the instance via
 * bhnd_nvstore_path_free().
 * 
 * @param       path_str        The path's canonical string representation.
 * @param       path_len        The length of @p path_str.
 * 
 * @retval non-NULL     success
 * @retval NULL         if allocation fails.
 */
bhnd_nvstore_path *
bhnd_nvstore_path_new(const char *path_str, size_t path_len)
{
        bhnd_nvstore_path *path;

        /* Allocate new entry */
        path = bhnd_nv_malloc(sizeof(*path));
        if (path == NULL)
                return (NULL);

        path->index = NULL;
        path->num_vars = 0;

        path->pending = bhnd_nvram_plist_new();
        if (path->pending == NULL)
                goto failed;

        path->path_str = bhnd_nv_strndup(path_str, path_len);
        if (path->path_str == NULL)
                goto failed;

        return (path);

failed:
        if (path->pending != NULL)
                bhnd_nvram_plist_release(path->pending);

        if (path->path_str != NULL)
                bhnd_nv_free(path->path_str);

        bhnd_nv_free(path);

        return (NULL);
}

/**
 * Free an NVRAM path instance, releasing all associated resources.
 */
void
bhnd_nvstore_path_free(struct bhnd_nvstore_path *path)
{
        /* Free the per-path index */
        if (path->index != NULL)
                bhnd_nvstore_index_free(path->index);

        bhnd_nvram_plist_release(path->pending);
        bhnd_nv_free(path->path_str);
        bhnd_nv_free(path);
}

/**
 * Allocate and initialize a new index instance with @p capacity.
 * 
 * The caller is responsible for deallocating the instance via
 * bhnd_nvstore_index_free().
 * 
 * @param       capacity        The maximum number of variables to be indexed.
 * 
 * @retval non-NULL     success
 * @retval NULL         if allocation fails.
 */
bhnd_nvstore_index *
bhnd_nvstore_index_new(size_t capacity)
{
        bhnd_nvstore_index      *index;
        size_t                   bytes;

        /* Allocate and populate variable index */
        bytes = sizeof(struct bhnd_nvstore_index) + (sizeof(void *) * capacity);
        index = bhnd_nv_malloc(bytes);
        if (index == NULL) {
                BHND_NV_LOG("error allocating %zu byte index\n", bytes);
                return (NULL);
        }

        index->count = 0;
        index->capacity = capacity;

        return (index);
}

/**
 * Free an index instance, releasing all associated resources.
 * 
 * @param       index   An index instance previously allocated via
 *                      bhnd_nvstore_index_new().
 */
void
bhnd_nvstore_index_free(bhnd_nvstore_index *index)
{
        bhnd_nv_free(index);
}

/**
 * Append a new NVRAM variable's @p cookiep value to @p index.
 * 
 * After one or more append requests, the index must be prepared via
 * bhnd_nvstore_index_prepare() before any indexed lookups are performed.
 *
 * @param       sc      The NVRAM store from which NVRAM values will be queried.
 * @param       index   The index to be modified.
 * @param       cookiep The cookiep value (as provided by the backing NVRAM
 *                      data instance of @p sc) to be included in @p index.
 * 
 * @retval 0            success
 * @retval ENOMEM       if appending an additional entry would exceed the
 *                      capacity of @p index.
 */
int
bhnd_nvstore_index_append(struct bhnd_nvram_store *sc,
    bhnd_nvstore_index *index, void *cookiep)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        if (index->count >= index->capacity)
                return (ENOMEM);

        index->cookiep[index->count] = cookiep;
        index->count++;
        return (0);
}

/* sort function for bhnd_nvstore_index_prepare() */
static int
bhnd_nvstore_idx_cmp(const void *lhs, const void *rhs, void *ctx)
{
        struct bhnd_nvram_store *sc;
        void                    *l_cookiep, *r_cookiep;
        const char              *l_str, *r_str;
        const char              *l_name, *r_name;
        int                      order;

        sc = ctx;
        l_cookiep = *(void * const *)lhs;
        r_cookiep = *(void * const *)rhs;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Fetch string pointers from the cookiep values */
        l_str = bhnd_nvram_data_getvar_name(sc->data, l_cookiep);
        r_str = bhnd_nvram_data_getvar_name(sc->data, r_cookiep);

        /* Trim device path prefixes */
        if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
                l_name = bhnd_nvram_trim_path_name(l_str);
                r_name = bhnd_nvram_trim_path_name(r_str);
        } else {
                l_name = l_str;
                r_name = r_str;
        }

        /* Perform comparison */
        order = strcmp(l_name, r_name);
        if (order != 0 || lhs == rhs)
                return (order);

        /* If the backing data incorrectly contains variables with duplicate
         * names, we need a sort order that provides stable behavior.
         * 
         * Since Broadcom's own code varies wildly on this question, we just
         * use a simple precedence rule: The first declaration of a variable
         * takes precedence. */
        return (bhnd_nvram_data_getvar_order(sc->data, l_cookiep, r_cookiep));
}

/**
 * Prepare @p index for querying via bhnd_nvstore_index_lookup().
 * 
 * After one or more append requests, the index must be prepared via
 * bhnd_nvstore_index_prepare() before any indexed lookups are performed.
 *
 * @param       sc      The NVRAM store from which NVRAM values will be queried.
 * @param       index   The index to be prepared.
 * 
 * @retval 0            success
 * @retval non-zero     if preparing @p index otherwise fails, a regular unix
 *                      error code will be returned.
 */
int
bhnd_nvstore_index_prepare(struct bhnd_nvram_store *sc,
    bhnd_nvstore_index *index)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Sort the index table */
        qsort_r(index->cookiep, index->count, sizeof(index->cookiep[0]),
            bhnd_nvstore_idx_cmp, sc);

        return (0);
}

/**
 * Return a borrowed reference to the root path node.
 * 
 * @param       sc      The NVRAM store.
 */
bhnd_nvstore_path *
bhnd_nvstore_get_root_path(struct bhnd_nvram_store *sc)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
        return (sc->root_path);
}

/**
 * Return true if @p path is the root path node.
 * 
 * @param       sc      The NVRAM store.
 * @param       path    The path to query.
 */
bool
bhnd_nvstore_is_root_path(struct bhnd_nvram_store *sc, bhnd_nvstore_path *path)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
        return (sc->root_path == path);
}

/**
 * Return the update entry matching @p name in @p path, or NULL if no entry
 * found.
 * 
 * @param sc    The NVRAM store.
 * @param path  The path to query.
 * @param name  The NVRAM variable name to search for in @p path's update list.
 * 
 * @retval non-NULL     success
 * @retval NULL         if @p name is not found in @p path.
 */
bhnd_nvram_prop *
bhnd_nvstore_path_get_update(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *path, const char *name)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
        return (bhnd_nvram_plist_get_prop(path->pending, name));
}

/**
 * Register or remove an update record for @p name in @p path.
 * 
 * @param sc    The NVRAM store.
 * @param path  The path to be modified.
 * @param name  The path-relative variable name to be modified.
 * @param value The new value. A value of BHND_NVRAM_TYPE_NULL denotes deletion.
 * 
 * @retval 0            success
 * @retval ENOMEM       if allocation fails.
 * @retval ENOENT       if @p name is unknown.
 * @retval EINVAL       if @p value is NULL, and deletion of @p is not
 *                      supported.
 * @retval EINVAL       if @p value cannot be converted to a supported value
 *                      type.
 */
int
bhnd_nvstore_path_register_update(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *path, const char *name, bhnd_nvram_val *value)
{
        bhnd_nvram_val          *prop_val;
        const char              *full_name;
        void                    *cookiep;
        char                    *namebuf;
        int                      error;
        bool                     nvram_committed;

        namebuf = NULL;
        prop_val = NULL;

        /* Determine whether the variable is currently defined in the
         * backing NVRAM data, and derive its full path-prefixed name */
        nvram_committed = false;
        cookiep = bhnd_nvstore_path_data_lookup(sc, path, name);
        if (cookiep != NULL) {
                /* Variable is defined in the backing data */
                nvram_committed = true;

                /* Use the existing variable name */
                full_name = bhnd_nvram_data_getvar_name(sc->data, cookiep);
        } else if (path == sc->root_path) {
                /* No prefix required for root path */
                full_name = name;
        } else {
                bhnd_nvstore_alias      *alias;
                int                      len;

                /* New variable is being set; we need to determine the
                 * appropriate path prefix */
                alias = bhnd_nvstore_find_alias(sc, path->path_str);
                if (alias != NULL) {
                        /* Use <alias>:name */
                        len = bhnd_nv_asprintf(&namebuf, "%lu:%s", alias->alias,
                            name);
                } else {
                        /* Use path/name */
                        len = bhnd_nv_asprintf(&namebuf, "%s/%s",
                            path->path_str, name);
                }

                if (len < 0)
                        return (ENOMEM);

                full_name = namebuf;
        }

        /* Allow the data store to filter the NVRAM operation */
        if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) {
                error = bhnd_nvram_data_filter_unsetvar(sc->data, full_name);
                if (error) {
                        BHND_NV_LOG("cannot unset property %s: %d\n", full_name,
                            error);
                        goto cleanup;
                }

                if ((prop_val = bhnd_nvram_val_copy(value)) == NULL) {
                        error = ENOMEM;
                        goto cleanup;
                }
        } else {
                error = bhnd_nvram_data_filter_setvar(sc->data, full_name,
                    value,  &prop_val);
                if (error) {
                        BHND_NV_LOG("cannot set property %s: %d\n", full_name,
                            error);
                        goto cleanup;
                }
        }

        /* Add relative variable name to the per-path update list */
        if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL &&
            !nvram_committed)
        {
                /* This is a deletion request for a variable not defined in
                 * out backing store; we can simply remove the corresponding
                 * update entry. */
                bhnd_nvram_plist_remove(path->pending, name);
        } else {
                /* Update or append a pending update entry */
                error = bhnd_nvram_plist_replace_val(path->pending, name,
                    prop_val);
                if (error)
                        goto cleanup;
        }

        /* Success */
        error = 0;

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

        if (prop_val != NULL)
                bhnd_nvram_val_release(prop_val);

        return (error);
}

/**
 * Iterate over all variable cookiep values retrievable from the backing
 * data store in @p path.
 * 
 * @warning Pending updates in @p path are ignored by this function.
 *
 * @param               sc      The NVRAM store.
 * @param               path    The NVRAM path to be iterated.
 * @param[in,out]       indexp  A pointer to an opaque indexp value previously
 *                              returned by bhnd_nvstore_path_data_next(), or a
 *                              NULL value to begin iteration.
 *
 * @return Returns the next variable name, or NULL if there are no more
 * variables defined in @p path.
 */
void *
bhnd_nvstore_path_data_next(struct bhnd_nvram_store *sc,
     bhnd_nvstore_path *path, void **indexp)
{
        void **index_ref;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* No index */
        if (path->index == NULL) {
                /* An index is required for all non-empty, non-root path
                 * instances */
                BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path),
                    ("missing index for non-root path %s", path->path_str));

                /* Iterate NVRAM data directly, using the NVRAM data's cookiep
                 * value as our indexp context */
                if ((bhnd_nvram_data_next(sc->data, indexp)) == NULL)
                        return (NULL);

                return (*indexp);
        }

        /* Empty index */
        if (path->index->count == 0)
                return (NULL);

        if (*indexp == NULL) {
                /* First index entry */
                index_ref = &path->index->cookiep[0];
        } else {
                size_t idxpos;

                /* Advance to next index entry */
                index_ref = *indexp;
                index_ref++;

                /* Hit end of index? */
                BHND_NV_ASSERT(index_ref > path->index->cookiep,
                    ("invalid indexp"));

                idxpos = (index_ref - path->index->cookiep);
                if (idxpos >= path->index->count)
                        return (NULL);
        }

        /* Provide new index position */
        *indexp = index_ref;

        /* Return the data's cookiep value */
        return (*index_ref);
}

/**
 * Perform an lookup of @p name in the backing NVRAM data for @p path,
 * returning the associated cookiep value, or NULL if the variable is not found
 * in the backing NVRAM data.
 * 
 * @warning Pending updates in @p path are ignored by this function.
 * 
 * @param       sc      The NVRAM store from which NVRAM values will be queried.
 * @param       path    The path to be queried.
 * @param       name    The variable name to be queried.
 * 
 * @retval non-NULL     success
 * @retval NULL         if @p name is not found in @p index.
 */
void *
bhnd_nvstore_path_data_lookup(struct bhnd_nvram_store *sc,
    bhnd_nvstore_path *path, const char *name)
{
        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* No index */
        if (path->index == NULL) {
                /* An index is required for all non-empty, non-root path
                 * instances */
                BHND_NV_ASSERT(bhnd_nvstore_is_root_path(sc, path),
                    ("missing index for non-root path %s", path->path_str));

                /* Look up directly in NVRAM data */
                return (bhnd_nvram_data_find(sc->data, name));
        }

        /* Otherwise, delegate to an index-based lookup */
        return (bhnd_nvstore_index_lookup(sc, path->index, name));
}

/**
 * Perform an index lookup of @p name, returning the associated cookiep
 * value, or NULL if the variable does not exist.
 * 
 * @param       sc      The NVRAM store from which NVRAM values will be queried.
 * @param       index   The index to be queried.
 * @param       name    The variable name to be queried.
 * 
 * @retval non-NULL     success
 * @retval NULL         if @p name is not found in @p index.
 */
void *
bhnd_nvstore_index_lookup(struct bhnd_nvram_store *sc,
    bhnd_nvstore_index *index, const char *name)
{
        void            *cookiep;
        const char      *indexed_name;
        size_t           min, mid, max;
        uint32_t         data_caps;
        int              order;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);
        BHND_NV_ASSERT(index != NULL, ("NULL index"));

        /*
         * Locate the requested variable using a binary search.
         */
        if (index->count == 0)
                return (NULL);

        data_caps = sc->data_caps;
        min = 0;
        max = index->count - 1;

        while (max >= min) {
                /* Select midpoint */
                mid = (min + max) / 2;
                cookiep = index->cookiep[mid];

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

                /* Trim any path prefix */
                if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)
                        indexed_name = bhnd_nvram_trim_path_name(indexed_name);

                /* Determine which side of the partition to search */
                order = strcmp(indexed_name, name);
                if (order < 0) {
                        /* Search upper partition */
                        min = mid + 1;
                } else if (order > 0) {
                        /* Search (non-empty) lower partition */
                        if (mid == 0)
                                break;
                        max = mid - 1;
                } else if (order == 0) {
                        size_t  idx;

                        /*
                         * Match found.
                         * 
                         * If this happens to be a key with multiple definitions
                         * in the backing store, we need to find the entry with
                         * the highest declaration precedence.
                         * 
                         * Duplicates are sorted in order of descending
                         * precedence; to find the highest precedence entry,
                         * we search backwards through the index.
                         */
                        idx = mid;
                        while (idx > 0) {
                                void            *dup_cookiep;
                                const char      *dup_name;

                                /* Fetch preceding index entry */
                                idx--;
                                dup_cookiep = index->cookiep[idx];
                                dup_name = bhnd_nvram_data_getvar_name(sc->data,
                                    dup_cookiep);

                                /* Trim any path prefix */
                                if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
                                        dup_name = bhnd_nvram_trim_path_name(
                                            dup_name);
                                }

                                /* If no match, current cookiep is the variable
                                 * definition with the highest precedence */
                                if (strcmp(indexed_name, dup_name) != 0)
                                        return (cookiep);

                                /* Otherwise, prefer this earlier definition,
                                 * and keep searching for a higher-precedence
                                 * definitions */
                                cookiep = dup_cookiep;
                        }

                        return (cookiep);
                }
        }

        /* Not found */
        return (NULL);
}

/**
 * Return the device path entry registered for @p path, if any.
 * 
 * @param       sc              The NVRAM store to be queried.
 * @param       path            The device path to search for.
 * @param       path_len        The length of @p path.
 *
 * @retval non-NULL     if found.
 * @retval NULL         if not found.
 */
bhnd_nvstore_path *
bhnd_nvstore_get_path(struct bhnd_nvram_store *sc, const char *path,
    size_t path_len)
{
        bhnd_nvstore_path_list  *plist;
        bhnd_nvstore_path       *p;
        uint32_t                 h;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Use hash lookup */
        h = hash32_strn(path, path_len, HASHINIT);
        plist = &sc->paths[h % nitems(sc->paths)];

        LIST_FOREACH(p, plist, np_link) {
                /* Check for prefix match */
                if (strncmp(p->path_str, path, path_len) != 0)
                        continue;

                /* Check for complete match */
                if (strnlen(path, path_len) != strlen(p->path_str))
                        continue;

                return (p);
        }

        /* Not found */
        return (NULL);
}

/**
 * Resolve @p aval to its corresponding device path entry, if any.
 * 
 * @param       sc              The NVRAM store to be queried.
 * @param       aval            The device path alias value to search for.
 *
 * @retval non-NULL     if found.
 * @retval NULL         if not found.
 */
bhnd_nvstore_path *
bhnd_nvstore_resolve_path_alias(struct bhnd_nvram_store *sc, u_long aval)
{
        bhnd_nvstore_alias *alias;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Fetch alias entry */
        if ((alias = bhnd_nvstore_get_alias(sc, aval)) == NULL)
                return (NULL);

        return (alias->path);
}

/**
 * Register a device path entry for the path referenced by variable name
 * @p info, if any.
 *
 * @param       sc              The NVRAM store to be updated.
 * @param       info            The NVRAM variable name info.
 * @param       cookiep         The NVRAM variable's cookiep value.
 *
 * @retval 0            if the path was successfully registered, or an identical
 *                      path or alias entry exists.
 * @retval EEXIST       if a conflicting entry already exists for the path or
 *                      alias referenced by @p info.
 * @retval ENOENT       if @p info contains a dangling alias reference.
 * @retval EINVAL       if @p info contains an unsupported bhnd_nvstore_var_type
 *                      and bhnd_nvstore_path_type combination.
 * @retval ENOMEM       if allocation fails.
 */
int
bhnd_nvstore_var_register_path(struct bhnd_nvram_store *sc,
    bhnd_nvstore_name_info *info, void *cookiep)
{
        switch (info->type) {
        case BHND_NVSTORE_VAR:
                /* Variable */
                switch (info->path_type) {
                case BHND_NVSTORE_PATH_STRING:
                        /* Variable contains a full path string
                         * (pci/1/1/varname); register the path */
                        return (bhnd_nvstore_register_path(sc,
                            info->path.str.value, info->path.str.value_len));

                case BHND_NVSTORE_PATH_ALIAS:
                        /* Variable contains an alias reference (0:varname).
                         * There's no path to register */
                        return (0);
                }

                BHND_NV_PANIC("unsupported path type %d", info->path_type);
                break;

        case BHND_NVSTORE_ALIAS_DECL:
                /* Alias declaration */
                return (bhnd_nvstore_register_alias(sc, info, cookiep));
        }

        BHND_NV_PANIC("unsupported var type %d", info->type);
}

/**
 * Resolve the device path entry referenced by @p info.
 *
 * @param       sc              The NVRAM store to be updated.
 * @param       info            Variable name information descriptor containing
 *                              the path or path alias to be resolved.
 *
 * @retval non-NULL     if found.
 * @retval NULL         if not found.
 */
bhnd_nvstore_path *
bhnd_nvstore_var_get_path(struct bhnd_nvram_store *sc,
    bhnd_nvstore_name_info *info)
{
        switch (info->path_type) {
        case BHND_NVSTORE_PATH_STRING:
                return (bhnd_nvstore_get_path(sc, info->path.str.value,
                    info->path.str.value_len));
        case BHND_NVSTORE_PATH_ALIAS:
                return (bhnd_nvstore_resolve_path_alias(sc,
                    info->path.alias.value));
        }

        BHND_NV_PANIC("unsupported path type %d", info->path_type);
}

/**
 * Return the device path alias entry registered for @p alias_val, if any.
 * 
 * @param       sc              The NVRAM store to be queried.
 * @param       alias_val       The alias value to search for.
 *
 * @retval non-NULL     if found.
 * @retval NULL         if not found.
 */
bhnd_nvstore_alias *
bhnd_nvstore_get_alias(struct bhnd_nvram_store *sc, u_long alias_val)
{
        bhnd_nvstore_alias_list *alist;
        bhnd_nvstore_alias      *alias;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Can use hash lookup */
        alist = &sc->aliases[alias_val % nitems(sc->aliases)];
        LIST_FOREACH(alias, alist, na_link) {
                if (alias->alias == alias_val)
                        return (alias);                 
        }

        /* Not found */
        return (NULL);
}

/**
 * Return the device path alias entry registered for @p path, if any.
 * 
 * @param       sc      The NVRAM store to be queried.
 * @param       path    The alias path to search for.
 *
 * @retval non-NULL     if found.
 * @retval NULL         if not found.
 */
bhnd_nvstore_alias *
bhnd_nvstore_find_alias(struct bhnd_nvram_store *sc, const char *path)
{
        bhnd_nvstore_alias *alias;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Have to scan the full table */
        for (size_t i = 0; i < nitems(sc->aliases); i++) {
                LIST_FOREACH(alias, &sc->aliases[i], na_link) {
                        if (strcmp(alias->path->path_str, path) == 0)
                                return (alias);                 
                }
        }

        /* Not found */
        return (NULL);
}

/**
 * Register a device path entry for @p path.
 * 
 * @param       sc              The NVRAM store to be updated.
 * @param       path_str        The absolute device path string.
 * @param       path_len        The length of @p path_str.
 * 
 * @retval 0            if the path was successfully registered, or an identical
 *                      path/alias entry already exists.
 * @retval ENOMEM       if allocation fails.
 */
int
bhnd_nvstore_register_path(struct bhnd_nvram_store *sc, const char *path_str,
    size_t path_len)
{
        bhnd_nvstore_path_list  *plist;
        bhnd_nvstore_path       *path;
        uint32_t                 h;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        /* Already exists? */
        if (bhnd_nvstore_get_path(sc, path_str, path_len) != NULL)
                return (0);

        /* Can't represent more than SIZE_MAX paths */
        if (sc->num_paths == SIZE_MAX)
                return (ENOMEM);

        /* Allocate new entry */
        path = bhnd_nvstore_path_new(path_str, path_len);
        if (path == NULL)
                return (ENOMEM);

        /* Insert in path hash table */
        h = hash32_str(path->path_str, HASHINIT);
        plist = &sc->paths[h % nitems(sc->paths)];
        LIST_INSERT_HEAD(plist, path, np_link);

        /* Increment path count */
        sc->num_paths++;

        return (0);
}

/**
 * Register a device path alias for an NVRAM 'devpathX' variable.
 * 
 * The path value for the alias will be fetched from the backing NVRAM data.
 * 
 * @param       sc      The NVRAM store to be updated.
 * @param       info    The NVRAM variable name info.
 * @param       cookiep The NVRAM variable's cookiep value.
 * 
 * @retval 0            if the alias was successfully registered, or an
 *                      identical alias entry exists.
 * @retval EEXIST       if a conflicting alias or path entry already exists.
 * @retval EINVAL       if @p info is not a BHND_NVSTORE_ALIAS_DECL or does
 *                      not contain a BHND_NVSTORE_PATH_ALIAS entry.
 * @retval ENOMEM       if allocation fails.
 */
int
bhnd_nvstore_register_alias(struct bhnd_nvram_store *sc,
    const bhnd_nvstore_name_info *info, void *cookiep)
{
        bhnd_nvstore_alias_list *alist;
        bhnd_nvstore_alias      *alias;
        bhnd_nvstore_path       *path;
        char                    *path_str;
        size_t                   path_len;
        int                      error;

        BHND_NVSTORE_LOCK_ASSERT(sc, MA_OWNED);

        path_str = NULL;
        alias = NULL;

        /* Can't represent more than SIZE_MAX aliases */
        if (sc->num_aliases == SIZE_MAX)
                return (ENOMEM);

        /* Must be an alias declaration */
        if (info->type != BHND_NVSTORE_ALIAS_DECL)
                return (EINVAL);

        if (info->path_type != BHND_NVSTORE_PATH_ALIAS)
                return (EINVAL);

        /* Fetch the devpath variable's value length */
        error = bhnd_nvram_data_getvar(sc->data, cookiep, NULL, &path_len,
            BHND_NVRAM_TYPE_STRING);
        if (error)
                return (ENOMEM);

        /* Allocate path string buffer */
        if ((path_str = bhnd_nv_malloc(path_len)) == NULL)
                return (ENOMEM);

        /* Decode to our new buffer */
        error = bhnd_nvram_data_getvar(sc->data, cookiep, path_str, &path_len,
            BHND_NVRAM_TYPE_STRING);
        if (error)
                goto failed;

        /* Trim trailing '/' character(s) from the path length */
        path_len = strnlen(path_str, path_len);
        while (path_len > 0 && path_str[path_len-1] == '/') {
                path_str[path_len-1] = '\0';
                path_len--;
        }

        /* Is a conflicting alias entry already registered for this alias
         * value? */
        alias = bhnd_nvstore_get_alias(sc, info->path.alias.value);
        if (alias != NULL) {
                if (alias->cookiep != cookiep ||
                    strcmp(alias->path->path_str, path_str) != 0)
                {
                        error = EEXIST;
                        goto failed;
                }
        }

        /* Is a conflicting entry already registered for the alias path? */
        if ((alias = bhnd_nvstore_find_alias(sc, path_str)) != NULL) {
                if (alias->alias != info->path.alias.value ||
                    alias->cookiep != cookiep ||
                    strcmp(alias->path->path_str, path_str) != 0)
                {
                        error = EEXIST;
                        goto failed;
                }
        }

        /* Get (or register) the target path entry */
        path = bhnd_nvstore_get_path(sc, path_str, path_len);
        if (path == NULL) {
                error = bhnd_nvstore_register_path(sc, path_str, path_len);
                if (error)
                        goto failed;

                path = bhnd_nvstore_get_path(sc, path_str, path_len);
                BHND_NV_ASSERT(path != NULL, ("missing registered path"));
        }

        /* Allocate alias entry */
        alias = bhnd_nv_calloc(1, sizeof(*alias));
        if (alias == NULL) {
                error = ENOMEM;
                goto failed;
        }

        alias->path = path;
        alias->cookiep = cookiep;
        alias->alias = info->path.alias.value;

        /* Insert in alias hash table */
        alist = &sc->aliases[alias->alias % nitems(sc->aliases)];
        LIST_INSERT_HEAD(alist, alias, na_link);

        /* Increment alias count */
        sc->num_aliases++;

        bhnd_nv_free(path_str);
        return (0);

failed:
        if (path_str != NULL)
                bhnd_nv_free(path_str);

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

        return (error);
}

/**
 * If @p child is equal to or a child path of @p parent, return a pointer to
 * @p child's path component(s) relative to @p parent; otherwise, return NULL.
 */
const char *
bhnd_nvstore_parse_relpath(const char *parent, const char *child)
{
        size_t prefix_len;

        /* All paths have an implicit leading '/'; this allows us to treat
         * our manufactured root path of "/" as a prefix to all NVRAM-defined
         * paths (which do not necessarily include a leading '/' */
        if (*parent == '/')
                parent++;

        if (*child == '/')
                child++;

        /* Is parent a prefix of child? */
        prefix_len = strlen(parent);
        if (strncmp(parent, child, prefix_len) != 0)
                return (NULL);

        /* A zero-length prefix matches everything */
        if (prefix_len == 0)
                return (child);

        /* Is child equal to parent? */
        if (child[prefix_len] == '\0')
                return (child + prefix_len);

        /* Is child actually a child of parent? */
        if (child[prefix_len] == '/')
                return (child + prefix_len + 1);

        /* No match (e.g. parent=/foo..., child=/fooo...) */
        return (NULL);
}

/**
 * Parse a raw NVRAM variable name and return its @p entry_type, its
 * type-specific @p prefix (e.g. '0:', 'pci/1/1', 'devpath'), and its
 * type-specific @p suffix (e.g. 'varname', '0').
 * 
 * @param       name            The NVRAM variable name to be parsed. This
 *                              value must remain valid for the lifetime of
 *                              @p info.
 * @param       type            The NVRAM name type -- either INTERNAL for names
 *                              parsed from backing NVRAM data, or EXTERNAL for
 *                              names provided by external NVRAM store clients.
 * @param       data_caps       The backing NVRAM data capabilities
 *                              (see bhnd_nvram_data_caps()).
 * @param[out]  info            On success, the parsed variable name info.
 * 
 * @retval 0            success
 * @retval non-zero     if parsing @p name otherwise fails, a regular unix
 *                      error code will be returned.
 */
int
bhnd_nvstore_parse_name_info(const char *name, bhnd_nvstore_name_type type,
    uint32_t data_caps, bhnd_nvstore_name_info *info)
{
        const char      *p;
        char            *endp;

        /* Skip path parsing? */
        if (data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS) {
                /* devpath declaration? (devpath0=pci/1/1) */
                if (strncmp(name, "devpath", strlen("devpath")) == 0) {
                        u_long alias;

                        /* Perform standard validation on the relative
                         * variable name */
                        if (type != BHND_NVSTORE_NAME_INTERNAL &&
                            !bhnd_nvram_validate_name(name))
                        {
                                return (ENOENT);
                        }

                        /* Parse alias value that should follow a 'devpath'
                         * prefix */
                        p = name + strlen("devpath");
                        alias = strtoul(p, &endp, 10);
                        if (endp != p && *endp == '\0') {
                                info->type = BHND_NVSTORE_ALIAS_DECL;
                                info->path_type = BHND_NVSTORE_PATH_ALIAS;
                                info->name = name;
                                info->path.alias.value = alias;

                                return (0);
                        }
                }

                /* device aliased variable? (0:varname) */
                if (bhnd_nv_isdigit(*name)) {
                        u_long alias;

                        /* Parse '0:' alias prefix */
                        alias = strtoul(name, &endp, 10);
                        if (endp != name && *endp == ':') {
                                /* Perform standard validation on the relative
                                 * variable name */
                                if (type != BHND_NVSTORE_NAME_INTERNAL &&
                                    !bhnd_nvram_validate_name(name))
                                {
                                        return (ENOENT);
                                }

                                info->type = BHND_NVSTORE_VAR;
                                info->path_type = BHND_NVSTORE_PATH_ALIAS;

                                /* name follows 0: prefix */
                                info->name = endp + 1;
                                info->path.alias.value = alias;

                                return (0);
                        }
                }

                /* device variable? (pci/1/1/varname) */
                if ((p = strrchr(name, '/')) != NULL) {
                        const char      *path, *relative_name;
                        size_t           path_len;

                        /* Determine the path length; 'p' points at the last
                         * path separator in 'name' */
                        path_len = p - name;
                        path = name;

                        /* The relative variable name directly follows the
                         * final path separator '/' */
                        relative_name = path + path_len + 1;

                        /* Now that we calculated the name offset, exclude all
                         * trailing '/' characters from the path length */
                        while (path_len > 0 && path[path_len-1] == '/')
                                path_len--;

                        /* Perform standard validation on the relative
                         * variable name */
                        if (type != BHND_NVSTORE_NAME_INTERNAL &&
                            !bhnd_nvram_validate_name(relative_name))
                        {
                                return (ENOENT);
                        }

                        /* Initialize result with pointers into the name
                         * buffer */
                        info->type = BHND_NVSTORE_VAR;
                        info->path_type = BHND_NVSTORE_PATH_STRING;
                        info->name = relative_name;
                        info->path.str.value = path;
                        info->path.str.value_len = path_len;

                        return (0);
                }
        }

        /* If all other parsing fails, the result is a simple variable with
         * an implicit path of "/" */
        if (type != BHND_NVSTORE_NAME_INTERNAL &&
            !bhnd_nvram_validate_name(name))
        {
                /* Invalid relative name */
                return (ENOENT);
        }

        info->type = BHND_NVSTORE_VAR;
        info->path_type = BHND_NVSTORE_PATH_STRING;
        info->name = name;
        info->path.str.value = BHND_NVSTORE_ROOT_PATH;
        info->path.str.value_len = BHND_NVSTORE_ROOT_PATH_LEN;

        return (0);
}