#include <sys/cdefs.h>
#include <sys/endian.h>
#ifdef _KERNEL
#include <sys/param.h>
#include <sys/ctype.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#else
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#endif
#include "bhnd_nvram_private.h"
#include "bhnd_nvram_datavar.h"
#include "bhnd_nvram_data_bcmreg.h"
struct bhnd_nvram_btxt {
struct bhnd_nvram_data nv;
struct bhnd_nvram_io *data;
size_t count;
};
BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text",
BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt))
union bhnd_nvram_btxt_ident {
uint32_t bcm_magic;
char btxt[8];
};
static void *bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
size_t io_offset);
static size_t bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt,
void *cookiep);
static int bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io,
size_t offset, size_t *line_len, size_t *env_len);
static int bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io,
size_t *offset);
static int bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io,
size_t *offset);
static int
bhnd_nvram_btxt_probe(struct bhnd_nvram_io *io)
{
union bhnd_nvram_btxt_ident ident;
char c;
int error;
if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident))))
return (error);
if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC)
return (ENXIO);
for (size_t i = 0; i < nitems(ident.btxt); i++) {
c = ident.btxt[i];
if (!bhnd_nv_isprint(c))
return (ENXIO);
}
c = ident.btxt[0];
if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#')
return (ENXIO);
return (BHND_NVRAM_DATA_PROBE_MAYBE);
}
typedef enum {
BTXT_PARSE_LINE_START,
BTXT_PARSE_KEY,
BTXT_PARSE_KEY_END,
BTXT_PARSE_NEXT_LINE,
BTXT_PARSE_VALUE_START,
BTXT_PARSE_VALUE
} btxt_parse_state;
static int
bhnd_nvram_btxt_getvar_direct(struct bhnd_nvram_io *io, const char *name,
void *outp, size_t *olen, bhnd_nvram_type otype)
{
char buf[512];
btxt_parse_state pstate;
size_t limit, offset;
size_t buflen, bufpos;
size_t namelen, namepos;
size_t vlen;
int error;
limit = bhnd_nvram_io_getsize(io);
offset = 0;
pstate = BTXT_PARSE_LINE_START;
buflen = 0;
bufpos = 0;
namelen = strlen(name);
namepos = 0;
vlen = 0;
while ((offset - bufpos) < limit) {
BHND_NV_ASSERT(bufpos <= buflen,
("buf position invalid (%zu > %zu)", bufpos, buflen));
BHND_NV_ASSERT(buflen <= sizeof(buf),
("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
if (buflen - bufpos == 0) {
BHND_NV_ASSERT(offset < limit, ("offset overrun"));
buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
bufpos = 0;
error = bhnd_nvram_io_read(io, offset, buf, buflen);
if (error)
return (error);
offset += buflen;
}
switch (pstate) {
case BTXT_PARSE_LINE_START:
BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
namepos = 0;
while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos]))
{
bufpos++;
}
if (bufpos == buflen) {
pstate = BTXT_PARSE_LINE_START;
} else if (bufpos < buflen && buf[bufpos] == '#') {
pstate = BTXT_PARSE_NEXT_LINE;
} else {
pstate = BTXT_PARSE_KEY;
}
break;
case BTXT_PARSE_KEY: {
size_t navail, nleft;
nleft = namelen - namepos;
navail = bhnd_nv_ummin(buflen - bufpos, nleft);
if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
namepos += navail;
bufpos += navail;
if (namepos == namelen) {
pstate = BTXT_PARSE_KEY_END;
} else {
pstate = BTXT_PARSE_KEY;
}
} else {
pstate = BTXT_PARSE_NEXT_LINE;
}
break;
}
case BTXT_PARSE_KEY_END:
BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
if (buf[bufpos] == '=') {
bufpos++;
pstate = BTXT_PARSE_VALUE_START;
} else {
pstate = BTXT_PARSE_NEXT_LINE;
}
break;
case BTXT_PARSE_NEXT_LINE: {
const char *p;
p = memchr(buf+bufpos, '\n', buflen - bufpos);
if (p == NULL)
p = memchr(buf+bufpos, '\r', buflen - bufpos);
if (p != NULL) {
pstate = BTXT_PARSE_LINE_START;
bufpos = (p - buf);
} else {
pstate = BTXT_PARSE_NEXT_LINE;
bufpos = buflen;
}
break;
}
case BTXT_PARSE_VALUE_START: {
const char *p;
p = memchr(buf+bufpos, '\n', buflen - bufpos);
if (p == NULL)
p = memchr(buf+bufpos, '\r', buflen - bufpos);
if (p != NULL) {
vlen = p - &buf[bufpos];
pstate = BTXT_PARSE_VALUE;
} else if (p == NULL && offset == limit) {
vlen = buflen - bufpos;
pstate = BTXT_PARSE_VALUE;
} else if (p == NULL && bufpos > 0) {
size_t nread;
memmove(buf, buf+bufpos, buflen - bufpos);
buflen = bufpos;
bufpos = 0;
nread = bhnd_nv_ummin(sizeof(buf) - buflen,
limit - offset);
error = bhnd_nvram_io_read(io, offset,
buf+buflen, nread);
if (error)
return (error);
offset += nread;
buflen += nread;
} else {
BHND_NV_LOG("cannot parse value for '%s' "
"(exceeds %zu byte limit)\n", name,
sizeof(buf));
return (ENXIO);
}
break;
}
case BTXT_PARSE_VALUE:
BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1]))
vlen--;
return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
BHND_NVRAM_TYPE_STRING, outp, olen, otype));
}
}
return (ENOENT);
}
static int
bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
bhnd_nvram_plist *options, void *outp, size_t *olen)
{
bhnd_nvram_prop *prop;
size_t limit, nbytes;
int error;
if (outp != NULL)
limit = *olen;
else
limit = 0;
nbytes = 0;
prop = NULL;
while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
const char *name;
char *p;
size_t prop_limit;
size_t name_len, value_len;
if (outp == NULL || limit < nbytes) {
p = NULL;
prop_limit = 0;
} else {
p = ((char *)outp) + nbytes;
prop_limit = limit - nbytes;
}
name = bhnd_nvram_prop_name(prop);
name_len = strlen(name) + 1;
if (prop_limit > name_len) {
memcpy(p, name, name_len - 1);
p[name_len - 1] = '=';
prop_limit -= name_len;
p += name_len;
} else {
prop_limit = 0;
p = NULL;
}
if (SIZE_MAX - nbytes < name_len)
return (EFTYPE);
nbytes += name_len;
value_len = prop_limit;
error = bhnd_nvram_prop_encode(prop, p, &value_len,
BHND_NVRAM_TYPE_STRING);
if (p != NULL && error == 0) {
BHND_NV_ASSERT(value_len > 0, ("string length missing "
"minimum required trailing NUL"));
*(p + (value_len - 1)) = '\n';
} else if (error && error != ENOMEM) {
BHND_NV_LOG("error serializing %s to required type "
"%s: %d\n", name,
bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
error);
return (error);
}
if (SIZE_MAX - nbytes < value_len)
return (EFTYPE);
nbytes += value_len;
}
*olen = nbytes;
if (limit < *olen) {
if (outp == NULL)
return (0);
return (ENOMEM);
}
return (0);
}
static int
bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src)
{
const void *ptr;
const char *name, *value;
size_t name_len, value_len;
size_t line_len, env_len;
size_t io_offset, io_size, str_size;
int error;
BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated"));
if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL)
return (ENOMEM);
io_size = bhnd_nvram_io_getsize(btxt->data);
io_offset = 0;
error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
if (error)
return (error);
str_size = strnlen(ptr, io_size);
if (str_size < io_size && str_size + 1 < io_size)
return (EINVAL);
io_size = str_size;
if ((error = bhnd_nvram_io_setsize(btxt->data, io_size)))
return (error);
btxt->count = 0;
while (io_offset < io_size) {
const void *envp;
if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset)))
return (error);
error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset,
&line_len, &env_len);
if (error)
return (error);
if (env_len == 0) {
BHND_NV_ASSERT(io_offset == io_size,
("zero-length record returned from "
"bhnd_nvram_btxt_seek_next()"));
break;
}
error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp,
env_len, NULL);
if (error)
return (error);
error = bhnd_nvram_parse_env(envp, env_len, '=', &name,
&name_len, &value, &value_len);
if (error) {
return (error);
}
error = bhnd_nvram_io_write(btxt->data, io_offset+name_len,
&(char){'\0'}, 1);
if (error)
return (error);
btxt->count++;
io_offset += line_len;
}
return (0);
}
static int
bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
{
struct bhnd_nvram_btxt *btxt;
int error;
btxt = (struct bhnd_nvram_btxt *)nv;
if ((error = bhnd_nvram_btxt_init(btxt, io))) {
bhnd_nvram_btxt_free(nv);
return (error);
}
return (0);
}
static void
bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv)
{
struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
if (btxt->data != NULL)
bhnd_nvram_io_free(btxt->data);
}
size_t
bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv)
{
struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
return (btxt->count);
}
static bhnd_nvram_plist *
bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)
{
return (NULL);
}
static uint32_t
bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)
{
return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
}
static void *
bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)
{
return (bhnd_nvram_data_generic_find(nv, name));
}
static const char *
bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep)
{
struct bhnd_nvram_btxt *btxt;
const void *nptr;
size_t io_offset, io_size;
int error;
btxt = (struct bhnd_nvram_btxt *)nv;
io_size = bhnd_nvram_io_getsize(btxt->data);
if (*cookiep == NULL) {
io_offset = 0x0;
} else {
io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep);
error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset);
if (error) {
BHND_NV_LOG("unexpected error in seek_eol(): %d\n",
error);
return (NULL);
}
}
if (io_offset == io_size)
return (NULL);
if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) {
BHND_NV_LOG("unexpected error in seek_next(): %d\n", error);
return (NULL);
}
if (io_offset == io_size)
return (NULL);
*cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset);
error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL);
if (error) {
BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
return (NULL);
}
return (nptr);
}
static int
bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
void *cookiep2)
{
if (cookiep1 < cookiep2)
return (-1);
if (cookiep1 > cookiep2)
return (1);
return (0);
}
static int
bhnd_nvram_btxt_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
size_t *len, bhnd_nvram_type type)
{
return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
}
static int
bhnd_nvram_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
bhnd_nvram_val **value)
{
return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
}
const void *
bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
size_t *len, bhnd_nvram_type *type)
{
struct bhnd_nvram_btxt *btxt;
const void *eptr;
const char *vptr;
size_t io_offset, io_size;
size_t line_len, env_len;
int error;
btxt = (struct bhnd_nvram_btxt *)nv;
io_size = bhnd_nvram_io_getsize(btxt->data);
io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
if (io_offset == io_size)
return (NULL);
error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len,
&env_len);
if (error) {
BHND_NV_LOG("unexpected error in entry_len(): %d\n", error);
return (NULL);
}
error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len,
NULL);
if (error) {
BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
return (NULL);
}
error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr,
len);
if (error) {
BHND_NV_LOG("unexpected error in parse_env(): %d\n", error);
return (NULL);
}
*type = BHND_NVRAM_TYPE_STRING;
return (vptr);
}
static const char *
bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
{
struct bhnd_nvram_btxt *btxt;
const void *ptr;
size_t io_offset, io_size;
int error;
btxt = (struct bhnd_nvram_btxt *)nv;
io_size = bhnd_nvram_io_getsize(btxt->data);
io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
if (io_offset == io_size)
BHND_NV_PANIC("invalid cookiep: %p", cookiep);
error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL);
if (error)
BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error);
return (ptr);
}
static void *
bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
size_t io_offset)
{
const void *ptr;
int error;
BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data),
("io_offset %zu out-of-range", io_offset));
BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL);
if (error)
BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
ptr = (const uint8_t *)ptr + io_offset;
return (__DECONST(void *, ptr));
}
static size_t
bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep)
{
const void *ptr;
intptr_t offset;
size_t io_size;
int error;
BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
io_size = bhnd_nvram_io_getsize(btxt->data);
error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
if (error)
BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
return ((size_t)offset);
}
static int
bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset,
size_t *line_len, size_t *env_len)
{
const uint8_t *baseptr, *p;
const void *rbuf;
size_t nbytes;
int error;
if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes)))
return (error);
p = rbuf;
baseptr = rbuf;
while ((size_t)(p - baseptr) < nbytes) {
if (*p == '#' || *p == '\n' || *p == '\r')
break;
p++;
}
*line_len = p - baseptr;
*env_len = *line_len;
for (size_t i = 0; i < *line_len; i++) {
char c = baseptr[*line_len - i - 1];
if (!bhnd_nv_isspace(c))
break;
*env_len -= 1;
}
return (0);
}
static int
bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset)
{
const uint8_t *baseptr, *p;
const void *rbuf;
size_t nbytes;
int error;
if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
return (error);
baseptr = rbuf;
p = rbuf;
while ((size_t)(p - baseptr) < nbytes) {
char c = *p;
p++;
if (c == '\r') {
if ((size_t)(p - baseptr) < nbytes) {
if (*p == '\n')
p++;
}
break;
} else if (c == '\n') {
break;
}
}
*offset += (p - baseptr);
return (0);
}
static int
bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset)
{
const uint8_t *baseptr, *p;
const void *rbuf;
size_t nbytes;
int error;
if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
return (error);
baseptr = rbuf;
p = rbuf;
while ((size_t)(p - baseptr) < nbytes) {
char c = *p;
if (bhnd_nv_isspace(c)) {
p++;
continue;
}
if (c == '#') {
size_t line_off = *offset + (p - baseptr);
if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off)))
return (error);
p = baseptr + (line_off - *offset);
continue;
}
break;
}
*offset += (p - baseptr);
return (0);
}
static int
bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
bhnd_nvram_val *value, bhnd_nvram_val **result)
{
bhnd_nvram_val *str;
const char *inp;
bhnd_nvram_type itype;
size_t ilen;
int error;
if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
return (EINVAL);
error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
value, BHND_NVRAM_VAL_DYNAMIC);
if (error)
return (error);
inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value"));
for (size_t i = 0; i < ilen; i++) {
switch (inp[i]) {
case '\n':
case '#':
BHND_NV_LOG("invalid character (%#hhx) in value\n",
inp[i]);
bhnd_nvram_val_release(str);
return (EINVAL);
}
}
*result = str;
return (0);
}
static int
bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
{
return (0);
}