#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
#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
#include "bhnd_nvram_private.h"
#include "bhnd_nvram_datavar.h"
#include "bhnd_nvram_storevar.h"
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);
int
bhnd_nvram_store_new(struct bhnd_nvram_store **store,
struct bhnd_nvram_data *data)
{
struct bhnd_nvram_store *sc;
int error;
sc = bhnd_nv_calloc(1, sizeof(*sc));
if (sc == NULL)
return (ENOMEM);
BHND_NVSTORE_LOCK_INIT(sc);
BHND_NVSTORE_LOCK(sc);
sc->num_paths = 0;
for (size_t i = 0; i < nitems(sc->paths); i++)
LIST_INIT(&sc->paths[i]);
sc->num_aliases = 0;
for (size_t i = 0; i < nitems(sc->aliases); i++)
LIST_INIT(&sc->aliases[i]);
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;
}
}
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"));
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);
}
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;
if ((error = bhnd_nvram_data_new(cls, &data, io)))
return (error);
error = bhnd_nvram_store_new(store, data);
bhnd_nvram_data_release(data);
return (error);
}
void
bhnd_nvram_store_free(struct bhnd_nvram_store *sc)
{
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);
}
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);
}
static int
bhnd_nvstore_parse_data(struct bhnd_nvram_store *sc)
{
const char *name;
void *cookiep;
int error;
if ((error = bhnd_nvstore_parse_path_entries(sc)))
return (error);
cookiep = NULL;
while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
bhnd_nvstore_path *path;
bhnd_nvstore_name_info 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:
path = bhnd_nvstore_var_get_path(sc, &info);
if (path == NULL) {
BHND_NV_LOG("variable '%s' has dangling "
"path reference\n", name);
return (EFTYPE);
}
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:
break;
}
}
if (sc->num_paths == 1) {
bhnd_nvstore_path *path;
if (sc->data_caps & BHND_NVRAM_DATA_CAP_INDEXED)
return (0);
path = bhnd_nvstore_get_root_path(sc);
if (path->num_vars < BHND_NV_IDX_VAR_THRESHOLD)
return (0);
}
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);
}
}
cookiep = NULL;
while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
bhnd_nvstore_name_info info;
bhnd_nvstore_path *path;
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:
path = bhnd_nvstore_var_get_path(sc, &info);
BHND_NV_ASSERT(path != NULL,
("dangling path reference"));
error = bhnd_nvstore_index_append(sc, path->index,
cookiep);
if (error)
return (error);
break;
case BHND_NVSTORE_ALIAS_DECL:
break;
}
}
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);
}
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);
if (!(sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)) {
BHND_NV_ASSERT(sc->root_path != NULL, ("missing root path"));
return (0);
}
cookiep = NULL;
while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
bhnd_nvstore_name_info info;
error = bhnd_nvstore_parse_name_info(name,
BHND_NVSTORE_NAME_INTERNAL, sc->data_caps, &info);
if (error)
return (error);
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);
}
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;
if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
bhnd_nvram_prop *prop;
prop = NULL;
while ((prop = bhnd_nvram_plist_next(path->pending, prop))) {
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED)) {
if (bhnd_nvram_prop_is_null(prop))
continue;
}
error = bhnd_nvram_plist_append(merged, prop);
if (error)
return (error);
}
}
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED))
return (0);
idxp = NULL;
while ((cookiep = bhnd_nvstore_path_data_next(sc, path, &idxp))) {
const char *name;
bhnd_nvram_val *val;
name = bhnd_nvram_data_getvar_name(sc->data, cookiep);
if (sc->data_caps & BHND_NVRAM_DATA_CAP_DEVPATHS)
name = bhnd_nvram_trim_path_name(name);
if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED)) {
if (bhnd_nvram_plist_contains(path->pending, name))
continue;
}
if (bhnd_nvram_plist_contains(merged, name))
continue;
if ((error = bhnd_nvram_data_copy_val(sc->data, cookiep, &val)))
return (error);
error = bhnd_nvram_plist_append_val(merged, name, val);
bhnd_nvram_val_release(val);
if (error)
return (error);
}
return (0);
}
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;
alias = bhnd_nvstore_find_alias(sc, path->path_str);
if (alias != NULL) {
*alias_val = alias->alias;
bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
if (pathvar == NULL)
return (ENOMEM);
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);
}
while (1) {
while (bhnd_nvstore_get_alias(sc, *alias_val) != NULL) {
if (*alias_val == ULONG_MAX)
return (ENOMEM);
(*alias_val)++;
}
bhnd_nv_asprintf(&pathvar, "devpath%lu", *alias_val);
if (pathvar == NULL)
return (ENOMEM);
if (!bhnd_nvram_plist_contains(plist, pathvar))
break;
bhnd_nv_free(pathvar);
if (*alias_val == ULONG_MAX)
return (ENOMEM);
(*alias_val)++;
}
error = bhnd_nvram_plist_append_string(plist, pathvar, devpath);
bhnd_nv_free(pathvar);
return (error);
}
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;
relpath = bhnd_nvstore_parse_relpath(top->path_str, child->path_str);
if (relpath == NULL) {
return (0);
}
relpath_len = strlen(relpath);
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_CHILDREN) && relpath_len > 0)
return (0);
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);
}
if (bhnd_nvram_plist_count(path_vars) == 0) {
bhnd_nvram_plist_release(path_vars);
return (0);
}
emit_compact_devpath = false;
if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS)) {
if (relpath_len > 0)
emit_compact_devpath = true;
} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS)) {
emit_compact_devpath = false;
} else if (BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
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;
}
prefix = NULL;
prefix_len = 0;
if (emit_compact_devpath) {
u_long alias_val;
int len;
error = bhnd_nvstore_export_devpath_alias(sc, child, relpath,
plist, &alias_val);
if (error)
goto finished;
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;
len = bhnd_nv_asprintf(&prefix, "%s/", relpath);
if (prefix == NULL) {
error = ENOMEM;
goto finished;
}
prefix_len = len;
}
namebuf_size = 0;
if (prefix != NULL) {
size_t maxlen;
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);
}
namebuf_size = prefix_len + maxlen + 1;
namebuf = bhnd_nv_malloc(namebuf_size);
if (namebuf == NULL) {
error = ENOMEM;
goto finished;
}
}
prop = NULL;
while ((prop = bhnd_nvram_plist_next(path_vars, prop)) != NULL) {
const char *name;
name = bhnd_nvram_prop_name(prop);
if (prefix != NULL) {
int len;
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;
}
error = bhnd_nvram_plist_append_val(plist, name,
bhnd_nvram_prop_val(prop));
if (error)
goto finished;
}
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);
}
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;
if (path == NULL)
path = BHND_NVSTORE_ROOT_PATH;
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMMITTED) &&
!BHND_NVSTORE_GET_FLAG(flags, EXPORT_UNCOMMITTED))
{
flags |= BHND_NVSTORE_EXPORT_ALL_VARS;
}
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_COMPACT_DEVPATHS) &&
!BHND_NVSTORE_GET_FLAG(flags, EXPORT_EXPAND_DEVPATHS))
{
flags |= BHND_NVSTORE_EXPORT_PRESERVE_DEVPATHS;
}
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 (BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED) &&
!BHND_NVSTORE_GET_FLAG(flags, EXPORT_DELETED))
{
return (EINVAL);
}
BHND_NVSTORE_LOCK(sc);
top = bhnd_nvstore_get_path(sc, path, strlen(path));
if (top == NULL) {
error = ENOENT;
goto failed;
}
if ((unordered = bhnd_nvram_plist_new()) == NULL) {
error = ENOMEM;
goto failed;
}
error = bhnd_nvram_store_export_child(sc, top, top, unordered, flags);
if (error)
goto failed;
for (size_t i = 0; i < nitems(sc->paths); i++) {
bhnd_nvstore_path *child;
LIST_FOREACH(child, &sc->paths[i], np_link) {
if (child == top)
continue;
error = bhnd_nvram_store_export_child(sc, top,
child, unordered, flags);
if (error)
goto failed;
}
}
if (cls != NULL)
*cls = bhnd_nvram_data_get_class(sc->data);
if (options != NULL)
*options = bhnd_nvram_plist_retain(sc->data_opts);
if (!BHND_NVSTORE_GET_FLAG(flags, EXPORT_PRESERVE_DEVPATHS)) {
*props = unordered;
unordered = NULL;
goto finished;
}
if ((*props = bhnd_nvram_plist_new()) == NULL) {
error = ENOMEM;
goto failed;
}
cookiep = NULL;
while ((name = bhnd_nvram_data_next(sc->data, &cookiep))) {
prop = bhnd_nvram_plist_get_prop(unordered, name);
if (prop == NULL)
continue;
if ((error = bhnd_nvram_plist_append(*props, prop)))
goto failed;
bhnd_nvram_plist_remove(unordered, name);
}
prop = NULL;
while ((prop = bhnd_nvram_plist_next(unordered, prop)) != NULL) {
if ((error = bhnd_nvram_plist_append(*props, prop)))
goto failed;
}
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);
}
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;
error = bhnd_nvram_store_export(sc, path, &cls, &props, &options,
flags);
if (error)
return (error);
error = bhnd_nvram_data_serialize(cls, props, options, NULL, &olen);
if (error)
goto failed;
if ((io = bhnd_nvram_iobuf_empty(olen, olen)) == NULL) {
error = ENOMEM;
goto failed;
}
if ((error = bhnd_nvram_io_write_ptr(io, 0, &outp, olen, NULL)))
goto failed;
error = bhnd_nvram_data_serialize(cls, props, options, outp, &olen);
if (error)
goto failed;
if ((error = bhnd_nvram_io_setsize(io, olen)))
goto failed;
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);
}
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);
error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
sc->data_caps, &info);
if (error)
goto finished;
if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL) {
error = ENOENT;
goto finished;
}
prop = bhnd_nvstore_path_get_update(sc, path, info.name);
if (prop != NULL) {
if (bhnd_nvram_prop_is_null(prop)) {
error = ENOENT;
} else {
error = bhnd_nvram_prop_encode(prop, outp, olen, otype);
}
goto finished;
}
cookiep = bhnd_nvstore_path_data_lookup(sc, path, info.name);
if (cookiep != NULL) {
error = bhnd_nvram_data_getvar(sc->data, cookiep, outp, olen,
otype);
goto finished;
}
error = ENOENT;
finished:
BHND_NVSTORE_UNLOCK(sc);
return (error);
}
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);
error = bhnd_nvstore_parse_name_info(name, BHND_NVSTORE_NAME_EXTERNAL,
sc->data_caps, &info);
if (error)
return (error);
if ((path = bhnd_nvstore_var_get_path(sc, &info)) == NULL)
return (error);
return (bhnd_nvstore_path_register_update(sc, path, info.name, value));
}
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);
}
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);
}
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);
}