Path: blob/main/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c
39536 views
/*-1* Copyright (c) 2015-2016 Landon Fuller <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer,9* without modification.10* 2. Redistributions in binary form must reproduce at minimum a disclaimer11* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any12* redistribution must be conditioned upon including a substantially13* similar Disclaimer requirement for further binary redistribution.14*15* NO WARRANTY16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY19* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL20* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,21* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF22* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS23* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER24* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)25* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF26* THE POSSIBILITY OF SUCH DAMAGES.27*/2829#include <sys/cdefs.h>30#include <sys/endian.h>3132#ifdef _KERNEL3334#include <sys/param.h>35#include <sys/ctype.h>36#include <sys/malloc.h>37#include <sys/systm.h>3839#else /* !_KERNEL */4041#include <ctype.h>42#include <stdint.h>43#include <stdlib.h>44#include <string.h>4546#endif /* _KERNEL */4748#include "bhnd_nvram_private.h"4950#include "bhnd_nvram_datavar.h"5152#include "bhnd_nvram_data_bcmreg.h" /* for BCM_NVRAM_MAGIC */5354/**55* Broadcom "Board Text" data class.56*57* This format is used to provide external NVRAM data for some58* fullmac WiFi devices, and as an input format when programming59* NVRAM/SPROM/OTP.60*/6162struct bhnd_nvram_btxt {63struct bhnd_nvram_data nv; /**< common instance state */64struct bhnd_nvram_io *data; /**< memory-backed board text data */65size_t count; /**< variable count */66};6768BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text",69BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt))7071/** Minimal identification header */72union bhnd_nvram_btxt_ident {73uint32_t bcm_magic;74char btxt[8];75};7677static void *bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,78size_t io_offset);79static size_t bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt,80void *cookiep);8182static int bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io,83size_t offset, size_t *line_len, size_t *env_len);84static int bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io,85size_t *offset);86static int bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io,87size_t *offset);8889static int90bhnd_nvram_btxt_probe(struct bhnd_nvram_io *io)91{92union bhnd_nvram_btxt_ident ident;93char c;94int error;9596/* Look at the initial header for something that looks like97* an ASCII board text file */98if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident))))99return (error);100101/* The BCM NVRAM format uses a 'FLSH' little endian magic value, which102* shouldn't be interpreted as BTXT */103if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC)104return (ENXIO);105106/* Don't match on non-ASCII/non-printable data */107for (size_t i = 0; i < nitems(ident.btxt); i++) {108c = ident.btxt[i];109if (!bhnd_nv_isprint(c))110return (ENXIO);111}112113/* The first character should either be a valid key char (alpha),114* whitespace, or the start of a comment ('#') */115c = ident.btxt[0];116if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#')117return (ENXIO);118119/* We assert a low priority, given that we've only scanned an120* initial few bytes of the file. */121return (BHND_NVRAM_DATA_PROBE_MAYBE);122}123124/**125* Parser states for bhnd_nvram_bcm_getvar_direct_common().126*/127typedef enum {128BTXT_PARSE_LINE_START,129BTXT_PARSE_KEY,130BTXT_PARSE_KEY_END,131BTXT_PARSE_NEXT_LINE,132BTXT_PARSE_VALUE_START,133BTXT_PARSE_VALUE134} btxt_parse_state;135136static int137bhnd_nvram_btxt_getvar_direct(struct bhnd_nvram_io *io, const char *name,138void *outp, size_t *olen, bhnd_nvram_type otype)139{140char buf[512];141btxt_parse_state pstate;142size_t limit, offset;143size_t buflen, bufpos;144size_t namelen, namepos;145size_t vlen;146int error;147148limit = bhnd_nvram_io_getsize(io);149offset = 0;150151/* Loop our parser until we find the requested variable, or hit EOF */152pstate = BTXT_PARSE_LINE_START;153buflen = 0;154bufpos = 0;155namelen = strlen(name);156namepos = 0;157vlen = 0;158159while ((offset - bufpos) < limit) {160BHND_NV_ASSERT(bufpos <= buflen,161("buf position invalid (%zu > %zu)", bufpos, buflen));162BHND_NV_ASSERT(buflen <= sizeof(buf),163("buf length invalid (%zu > %zu", buflen, sizeof(buf)));164165/* Repopulate our parse buffer? */166if (buflen - bufpos == 0) {167BHND_NV_ASSERT(offset < limit, ("offset overrun"));168169buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);170bufpos = 0;171172error = bhnd_nvram_io_read(io, offset, buf, buflen);173if (error)174return (error);175176offset += buflen;177}178179switch (pstate) {180case BTXT_PARSE_LINE_START:181BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));182183/* Reset name matching position */184namepos = 0;185186/* Trim any leading whitespace */187while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos]))188{189bufpos++;190}191192if (bufpos == buflen) {193/* Continue parsing the line */194pstate = BTXT_PARSE_LINE_START;195} else if (bufpos < buflen && buf[bufpos] == '#') {196/* Comment; skip to next line */197pstate = BTXT_PARSE_NEXT_LINE;198} else {199/* Start name matching */200pstate = BTXT_PARSE_KEY;201}202203break;204205case BTXT_PARSE_KEY: {206size_t navail, nleft;207208nleft = namelen - namepos;209navail = bhnd_nv_ummin(buflen - bufpos, nleft);210211if (strncmp(name+namepos, buf+bufpos, navail) == 0) {212/* Matched */213namepos += navail;214bufpos += navail;215216if (namepos == namelen) {217/* Matched the full variable; look for218* its trailing delimiter */219pstate = BTXT_PARSE_KEY_END;220} else {221/* Continue matching the name */222pstate = BTXT_PARSE_KEY;223}224} else {225/* No match; advance to next entry and restart226* name matching */227pstate = BTXT_PARSE_NEXT_LINE;228}229230break;231}232233case BTXT_PARSE_KEY_END:234BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));235236if (buf[bufpos] == '=') {237/* Key fully matched; advance past '=' and238* parse the value */239bufpos++;240pstate = BTXT_PARSE_VALUE_START;241} else {242/* No match; advance to next line and restart243* name matching */244pstate = BTXT_PARSE_NEXT_LINE;245}246247break;248249case BTXT_PARSE_NEXT_LINE: {250const char *p;251252/* Scan for a '\r', '\n', or '\r\n' terminator */253p = memchr(buf+bufpos, '\n', buflen - bufpos);254if (p == NULL)255p = memchr(buf+bufpos, '\r', buflen - bufpos);256257if (p != NULL) {258/* Found entry terminator; restart name259* matching at next line */260pstate = BTXT_PARSE_LINE_START;261bufpos = (p - buf);262} else {263/* Consumed full buffer looking for newline;264* force repopulation of the buffer and265* retry */266pstate = BTXT_PARSE_NEXT_LINE;267bufpos = buflen;268}269270break;271}272273case BTXT_PARSE_VALUE_START: {274const char *p;275276/* Scan for a terminating newline */277p = memchr(buf+bufpos, '\n', buflen - bufpos);278if (p == NULL)279p = memchr(buf+bufpos, '\r', buflen - bufpos);280281if (p != NULL) {282/* Found entry terminator; parse the value */283vlen = p - &buf[bufpos];284pstate = BTXT_PARSE_VALUE;285286} else if (p == NULL && offset == limit) {287/* Hit EOF without a terminating newline;288* treat the entry as implicitly terminated */289vlen = buflen - bufpos;290pstate = BTXT_PARSE_VALUE;291292} else if (p == NULL && bufpos > 0) {293size_t nread;294295/* Move existing value data to start of296* buffer */297memmove(buf, buf+bufpos, buflen - bufpos);298buflen = bufpos;299bufpos = 0;300301/* Populate full buffer to allow retry of302* value parsing */303nread = bhnd_nv_ummin(sizeof(buf) - buflen,304limit - offset);305306error = bhnd_nvram_io_read(io, offset,307buf+buflen, nread);308if (error)309return (error);310311offset += nread;312buflen += nread;313} else {314/* Value exceeds our buffer capacity */315BHND_NV_LOG("cannot parse value for '%s' "316"(exceeds %zu byte limit)\n", name,317sizeof(buf));318319return (ENXIO);320}321322break;323}324325case BTXT_PARSE_VALUE:326BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));327328/* Trim any trailing whitespace */329while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1]))330vlen--;331332/* Write the value to the caller's buffer */333return (bhnd_nvram_value_coerce(buf+bufpos, vlen,334BHND_NVRAM_TYPE_STRING, outp, olen, otype));335}336}337338/* Variable not found */339return (ENOENT);340}341342static int343bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,344bhnd_nvram_plist *options, void *outp, size_t *olen)345{346bhnd_nvram_prop *prop;347size_t limit, nbytes;348int error;349350/* Determine output byte limit */351if (outp != NULL)352limit = *olen;353else354limit = 0;355356nbytes = 0;357358/* Write all properties */359prop = NULL;360while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {361const char *name;362char *p;363size_t prop_limit;364size_t name_len, value_len;365366if (outp == NULL || limit < nbytes) {367p = NULL;368prop_limit = 0;369} else {370p = ((char *)outp) + nbytes;371prop_limit = limit - nbytes;372}373374/* Fetch and write 'name=' to output */375name = bhnd_nvram_prop_name(prop);376name_len = strlen(name) + 1;377378if (prop_limit > name_len) {379memcpy(p, name, name_len - 1);380p[name_len - 1] = '=';381382prop_limit -= name_len;383p += name_len;384} else {385prop_limit = 0;386p = NULL;387}388389/* Advance byte count */390if (SIZE_MAX - nbytes < name_len)391return (EFTYPE); /* would overflow size_t */392393nbytes += name_len;394395/* Write NUL-terminated value to output, rewrite NUL as396* '\n' record delimiter */397value_len = prop_limit;398error = bhnd_nvram_prop_encode(prop, p, &value_len,399BHND_NVRAM_TYPE_STRING);400if (p != NULL && error == 0) {401/* Replace trailing '\0' with newline */402BHND_NV_ASSERT(value_len > 0, ("string length missing "403"minimum required trailing NUL"));404405*(p + (value_len - 1)) = '\n';406} else if (error && error != ENOMEM) {407/* If encoding failed for any reason other than ENOMEM408* (which we'll detect and report after encoding all409* properties), return immediately */410BHND_NV_LOG("error serializing %s to required type "411"%s: %d\n", name,412bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),413error);414return (error);415}416417/* Advance byte count */418if (SIZE_MAX - nbytes < value_len)419return (EFTYPE); /* would overflow size_t */420421nbytes += value_len;422}423424/* Provide required length */425*olen = nbytes;426if (limit < *olen) {427if (outp == NULL)428return (0);429430return (ENOMEM);431}432433return (0);434}435436/**437* Initialize @p btxt with the provided board text data mapped by @p src.438*439* @param btxt A newly allocated data instance.440*/441static int442bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src)443{444const void *ptr;445const char *name, *value;446size_t name_len, value_len;447size_t line_len, env_len;448size_t io_offset, io_size, str_size;449int error;450451BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated"));452453if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL)454return (ENOMEM);455456io_size = bhnd_nvram_io_getsize(btxt->data);457io_offset = 0;458459/* Fetch a pointer mapping the entirity of the board text data */460error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);461if (error)462return (error);463464/* Determine the actual size, minus any terminating NUL. We465* parse NUL-terminated C strings, but do not include NUL termination466* in our internal or serialized representations */467str_size = strnlen(ptr, io_size);468469/* If the terminating NUL is not found at the end of the buffer,470* this is BCM-RAW or other NUL-delimited NVRAM format. */471if (str_size < io_size && str_size + 1 < io_size)472return (EINVAL);473474/* Adjust buffer size to account for NUL termination (if any) */475io_size = str_size;476if ((error = bhnd_nvram_io_setsize(btxt->data, io_size)))477return (error);478479/* Process the buffer */480btxt->count = 0;481while (io_offset < io_size) {482const void *envp;483484/* Seek to the next key=value entry */485if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset)))486return (error);487488/* Determine the entry and line length */489error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset,490&line_len, &env_len);491if (error)492return (error);493494/* EOF? */495if (env_len == 0) {496BHND_NV_ASSERT(io_offset == io_size,497("zero-length record returned from "498"bhnd_nvram_btxt_seek_next()"));499break;500}501502/* Fetch a pointer to the line start */503error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp,504env_len, NULL);505if (error)506return (error);507508/* Parse the key=value string */509error = bhnd_nvram_parse_env(envp, env_len, '=', &name,510&name_len, &value, &value_len);511if (error) {512return (error);513}514515/* Insert a '\0' character, replacing the '=' delimiter and516* allowing us to vend references directly to the variable517* name */518error = bhnd_nvram_io_write(btxt->data, io_offset+name_len,519&(char){'\0'}, 1);520if (error)521return (error);522523/* Add to variable count */524btxt->count++;525526/* Advance past EOL */527io_offset += line_len;528}529530return (0);531}532533static int534bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)535{536struct bhnd_nvram_btxt *btxt;537int error;538539/* Allocate and initialize the BTXT data instance */540btxt = (struct bhnd_nvram_btxt *)nv;541542/* Parse the BTXT input data and initialize our backing543* data representation */544if ((error = bhnd_nvram_btxt_init(btxt, io))) {545bhnd_nvram_btxt_free(nv);546return (error);547}548549return (0);550}551552static void553bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv)554{555struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;556if (btxt->data != NULL)557bhnd_nvram_io_free(btxt->data);558}559560size_t561bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv)562{563struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;564return (btxt->count);565}566567static bhnd_nvram_plist *568bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)569{570return (NULL);571}572573static uint32_t574bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)575{576return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);577}578579static void *580bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)581{582return (bhnd_nvram_data_generic_find(nv, name));583}584585static const char *586bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep)587{588struct bhnd_nvram_btxt *btxt;589const void *nptr;590size_t io_offset, io_size;591int error;592593btxt = (struct bhnd_nvram_btxt *)nv;594595io_size = bhnd_nvram_io_getsize(btxt->data);596597if (*cookiep == NULL) {598/* Start search at initial file offset */599io_offset = 0x0;600} else {601/* Start search after the current entry */602io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep);603604/* Scan past the current entry by finding the next newline */605error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset);606if (error) {607BHND_NV_LOG("unexpected error in seek_eol(): %d\n",608error);609return (NULL);610}611}612613/* Already at EOF? */614if (io_offset == io_size)615return (NULL);616617/* Seek to the first valid entry, or EOF */618if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) {619BHND_NV_LOG("unexpected error in seek_next(): %d\n", error);620return (NULL);621}622623/* Hit EOF? */624if (io_offset == io_size)625return (NULL);626627/* Provide the new cookie for this offset */628*cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset);629630/* Fetch the name pointer; it must be at least 1 byte long */631error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL);632if (error) {633BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);634return (NULL);635}636637/* Return the name pointer */638return (nptr);639}640641static int642bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,643void *cookiep2)644{645if (cookiep1 < cookiep2)646return (-1);647648if (cookiep1 > cookiep2)649return (1);650651return (0);652}653654static int655bhnd_nvram_btxt_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,656size_t *len, bhnd_nvram_type type)657{658return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));659}660661static int662bhnd_nvram_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep,663bhnd_nvram_val **value)664{665return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));666}667668const void *669bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,670size_t *len, bhnd_nvram_type *type)671{672struct bhnd_nvram_btxt *btxt;673const void *eptr;674const char *vptr;675size_t io_offset, io_size;676size_t line_len, env_len;677int error;678679btxt = (struct bhnd_nvram_btxt *)nv;680681io_size = bhnd_nvram_io_getsize(btxt->data);682io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);683684/* At EOF? */685if (io_offset == io_size)686return (NULL);687688/* Determine the entry length */689error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len,690&env_len);691if (error) {692BHND_NV_LOG("unexpected error in entry_len(): %d\n", error);693return (NULL);694}695696/* Fetch the entry's value pointer and length */697error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len,698NULL);699if (error) {700BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);701return (NULL);702}703704error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr,705len);706if (error) {707BHND_NV_LOG("unexpected error in parse_env(): %d\n", error);708return (NULL);709}710711/* Type is always CSTR */712*type = BHND_NVRAM_TYPE_STRING;713714return (vptr);715}716717static const char *718bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)719{720struct bhnd_nvram_btxt *btxt;721const void *ptr;722size_t io_offset, io_size;723int error;724725btxt = (struct bhnd_nvram_btxt *)nv;726727io_size = bhnd_nvram_io_getsize(btxt->data);728io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);729730/* At EOF? */731if (io_offset == io_size)732BHND_NV_PANIC("invalid cookiep: %p", cookiep);733734/* Variable name is found directly at the given offset; trailing735* NUL means we can assume that it's at least 1 byte long */736error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL);737if (error)738BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error);739740return (ptr);741}742743/**744* Return a cookiep for the given I/O offset.745*/746static void *747bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,748size_t io_offset)749{750const void *ptr;751int error;752753BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data),754("io_offset %zu out-of-range", io_offset));755BHND_NV_ASSERT(io_offset < UINTPTR_MAX,756("io_offset %#zx exceeds UINTPTR_MAX", io_offset));757758error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL);759if (error)760BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);761762ptr = (const uint8_t *)ptr + io_offset;763return (__DECONST(void *, ptr));764}765766/* Convert a cookiep back to an I/O offset */767static size_t768bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep)769{770const void *ptr;771intptr_t offset;772size_t io_size;773int error;774775BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));776777io_size = bhnd_nvram_io_getsize(btxt->data);778error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);779if (error)780BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);781782offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;783BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));784BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));785BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));786787return ((size_t)offset);788}789790/* Determine the entry length and env 'key=value' string length of the entry791* at @p offset */792static int793bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset,794size_t *line_len, size_t *env_len)795{796const uint8_t *baseptr, *p;797const void *rbuf;798size_t nbytes;799int error;800801/* Fetch read buffer */802if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes)))803return (error);804805/* Find record termination (EOL, or '#') */806p = rbuf;807baseptr = rbuf;808while ((size_t)(p - baseptr) < nbytes) {809if (*p == '#' || *p == '\n' || *p == '\r')810break;811812p++;813}814815/* Got line length, now trim any trailing whitespace to determine816* actual env length */817*line_len = p - baseptr;818*env_len = *line_len;819820for (size_t i = 0; i < *line_len; i++) {821char c = baseptr[*line_len - i - 1];822if (!bhnd_nv_isspace(c))823break;824825*env_len -= 1;826}827828return (0);829}830831/* Seek past the next line ending (\r, \r\n, or \n) */832static int833bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset)834{835const uint8_t *baseptr, *p;836const void *rbuf;837size_t nbytes;838int error;839840/* Fetch read buffer */841if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))842return (error);843844baseptr = rbuf;845p = rbuf;846while ((size_t)(p - baseptr) < nbytes) {847char c = *p;848849/* Advance to next char. The next position may be EOF, in which850* case a read will be invalid */851p++;852853if (c == '\r') {854/* CR, check for optional LF */855if ((size_t)(p - baseptr) < nbytes) {856if (*p == '\n')857p++;858}859860break;861} else if (c == '\n') {862break;863}864}865866/* Hit newline or EOF */867*offset += (p - baseptr);868return (0);869}870871/* Seek to the next valid non-comment line (or EOF) */872static int873bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset)874{875const uint8_t *baseptr, *p;876const void *rbuf;877size_t nbytes;878int error;879880/* Fetch read buffer */881if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))882return (error);883884/* Skip leading whitespace and comments */885baseptr = rbuf;886p = rbuf;887while ((size_t)(p - baseptr) < nbytes) {888char c = *p;889890/* Skip whitespace */891if (bhnd_nv_isspace(c)) {892p++;893continue;894}895896/* Skip entire comment line */897if (c == '#') {898size_t line_off = *offset + (p - baseptr);899900if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off)))901return (error);902903p = baseptr + (line_off - *offset);904continue;905}906907/* Non-whitespace, non-comment */908break;909}910911*offset += (p - baseptr);912return (0);913}914915static int916bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name,917bhnd_nvram_val *value, bhnd_nvram_val **result)918{919bhnd_nvram_val *str;920const char *inp;921bhnd_nvram_type itype;922size_t ilen;923int error;924925/* Name (trimmed of any path prefix) must be valid */926if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))927return (EINVAL);928929/* Value must be bcm-formatted string */930error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,931value, BHND_NVRAM_VAL_DYNAMIC);932if (error)933return (error);934935/* Value string must not contain our record delimiter character ('\n'),936* or our comment character ('#') */937inp = bhnd_nvram_val_bytes(str, &ilen, &itype);938BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value"));939for (size_t i = 0; i < ilen; i++) {940switch (inp[i]) {941case '\n':942case '#':943BHND_NV_LOG("invalid character (%#hhx) in value\n",944inp[i]);945bhnd_nvram_val_release(str);946return (EINVAL);947}948}949950/* Success. Transfer result ownership to the caller. */951*result = str;952return (0);953}954955static int956bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)957{958/* We permit deletion of any variable */959return (0);960}961962963