/*- * Copyright (c) 2015-2016 Landon Fuller * 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 #include #ifdef _KERNEL #include #include #include #include #else /* !_KERNEL */ #include #include #include #include #endif /* _KERNEL */ #include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_bcmreg.h" /* for BCM_NVRAM_MAGIC */ /** * Broadcom "Board Text" data class. * * This format is used to provide external NVRAM data for some * fullmac WiFi devices, and as an input format when programming * NVRAM/SPROM/OTP. */ struct bhnd_nvram_btxt { struct bhnd_nvram_data nv; /**< common instance state */ struct bhnd_nvram_io *data; /**< memory-backed board text data */ size_t count; /**< variable count */ }; BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text", BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt)) /** Minimal identification header */ 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; /* Look at the initial header for something that looks like * an ASCII board text file */ if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident)))) return (error); /* The BCM NVRAM format uses a 'FLSH' little endian magic value, which * shouldn't be interpreted as BTXT */ if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC) return (ENXIO); /* Don't match on non-ASCII/non-printable data */ for (size_t i = 0; i < nitems(ident.btxt); i++) { c = ident.btxt[i]; if (!bhnd_nv_isprint(c)) return (ENXIO); } /* The first character should either be a valid key char (alpha), * whitespace, or the start of a comment ('#') */ c = ident.btxt[0]; if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#') return (ENXIO); /* We assert a low priority, given that we've only scanned an * initial few bytes of the file. */ return (BHND_NVRAM_DATA_PROBE_MAYBE); } /** * Parser states for bhnd_nvram_bcm_getvar_direct_common(). */ 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; /* Loop our parser until we find the requested variable, or hit EOF */ 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))); /* Repopulate our parse buffer? */ 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!")); /* Reset name matching position */ namepos = 0; /* Trim any leading whitespace */ while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos])) { bufpos++; } if (bufpos == buflen) { /* Continue parsing the line */ pstate = BTXT_PARSE_LINE_START; } else if (bufpos < buflen && buf[bufpos] == '#') { /* Comment; skip to next line */ pstate = BTXT_PARSE_NEXT_LINE; } else { /* Start name matching */ 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) { /* Matched */ namepos += navail; bufpos += navail; if (namepos == namelen) { /* Matched the full variable; look for * its trailing delimiter */ pstate = BTXT_PARSE_KEY_END; } else { /* Continue matching the name */ pstate = BTXT_PARSE_KEY; } } else { /* No match; advance to next entry and restart * name matching */ pstate = BTXT_PARSE_NEXT_LINE; } break; } case BTXT_PARSE_KEY_END: BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!")); if (buf[bufpos] == '=') { /* Key fully matched; advance past '=' and * parse the value */ bufpos++; pstate = BTXT_PARSE_VALUE_START; } else { /* No match; advance to next line and restart * name matching */ pstate = BTXT_PARSE_NEXT_LINE; } break; case BTXT_PARSE_NEXT_LINE: { const char *p; /* Scan for a '\r', '\n', or '\r\n' terminator */ p = memchr(buf+bufpos, '\n', buflen - bufpos); if (p == NULL) p = memchr(buf+bufpos, '\r', buflen - bufpos); if (p != NULL) { /* Found entry terminator; restart name * matching at next line */ pstate = BTXT_PARSE_LINE_START; bufpos = (p - buf); } else { /* Consumed full buffer looking for newline; * force repopulation of the buffer and * retry */ pstate = BTXT_PARSE_NEXT_LINE; bufpos = buflen; } break; } case BTXT_PARSE_VALUE_START: { const char *p; /* Scan for a terminating newline */ p = memchr(buf+bufpos, '\n', buflen - bufpos); if (p == NULL) p = memchr(buf+bufpos, '\r', buflen - bufpos); if (p != NULL) { /* Found entry terminator; parse the value */ vlen = p - &buf[bufpos]; pstate = BTXT_PARSE_VALUE; } else if (p == NULL && offset == limit) { /* Hit EOF without a terminating newline; * treat the entry as implicitly terminated */ vlen = buflen - bufpos; pstate = BTXT_PARSE_VALUE; } else if (p == NULL && bufpos > 0) { size_t nread; /* Move existing value data to start of * buffer */ memmove(buf, buf+bufpos, buflen - bufpos); buflen = bufpos; bufpos = 0; /* Populate full buffer to allow retry of * value parsing */ 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 { /* Value exceeds our buffer capacity */ 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")); /* Trim any trailing whitespace */ while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1])) vlen--; /* Write the value to the caller's buffer */ return (bhnd_nvram_value_coerce(buf+bufpos, vlen, BHND_NVRAM_TYPE_STRING, outp, olen, otype)); } } /* Variable not found */ 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; /* Determine output byte limit */ if (outp != NULL) limit = *olen; else limit = 0; nbytes = 0; /* Write all properties */ 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; } /* Fetch and write 'name=' to output */ 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; } /* Advance byte count */ if (SIZE_MAX - nbytes < name_len) return (EFTYPE); /* would overflow size_t */ nbytes += name_len; /* Write NUL-terminated value to output, rewrite NUL as * '\n' record delimiter */ value_len = prop_limit; error = bhnd_nvram_prop_encode(prop, p, &value_len, BHND_NVRAM_TYPE_STRING); if (p != NULL && error == 0) { /* Replace trailing '\0' with newline */ BHND_NV_ASSERT(value_len > 0, ("string length missing " "minimum required trailing NUL")); *(p + (value_len - 1)) = '\n'; } else if (error && error != ENOMEM) { /* If encoding failed for any reason other than ENOMEM * (which we'll detect and report after encoding all * properties), return immediately */ BHND_NV_LOG("error serializing %s to required type " "%s: %d\n", name, bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), error); return (error); } /* Advance byte count */ if (SIZE_MAX - nbytes < value_len) return (EFTYPE); /* would overflow size_t */ nbytes += value_len; } /* Provide required length */ *olen = nbytes; if (limit < *olen) { if (outp == NULL) return (0); return (ENOMEM); } return (0); } /** * Initialize @p btxt with the provided board text data mapped by @p src. * * @param btxt A newly allocated data instance. */ 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; /* Fetch a pointer mapping the entirity of the board text data */ error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL); if (error) return (error); /* Determine the actual size, minus any terminating NUL. We * parse NUL-terminated C strings, but do not include NUL termination * in our internal or serialized representations */ str_size = strnlen(ptr, io_size); /* If the terminating NUL is not found at the end of the buffer, * this is BCM-RAW or other NUL-delimited NVRAM format. */ if (str_size < io_size && str_size + 1 < io_size) return (EINVAL); /* Adjust buffer size to account for NUL termination (if any) */ io_size = str_size; if ((error = bhnd_nvram_io_setsize(btxt->data, io_size))) return (error); /* Process the buffer */ btxt->count = 0; while (io_offset < io_size) { const void *envp; /* Seek to the next key=value entry */ if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) return (error); /* Determine the entry and line length */ error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len, &env_len); if (error) return (error); /* EOF? */ if (env_len == 0) { BHND_NV_ASSERT(io_offset == io_size, ("zero-length record returned from " "bhnd_nvram_btxt_seek_next()")); break; } /* Fetch a pointer to the line start */ error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp, env_len, NULL); if (error) return (error); /* Parse the key=value string */ error = bhnd_nvram_parse_env(envp, env_len, '=', &name, &name_len, &value, &value_len); if (error) { return (error); } /* Insert a '\0' character, replacing the '=' delimiter and * allowing us to vend references directly to the variable * name */ error = bhnd_nvram_io_write(btxt->data, io_offset+name_len, &(char){'\0'}, 1); if (error) return (error); /* Add to variable count */ btxt->count++; /* Advance past EOL */ 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; /* Allocate and initialize the BTXT data instance */ btxt = (struct bhnd_nvram_btxt *)nv; /* Parse the BTXT input data and initialize our backing * data representation */ 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) { /* Start search at initial file offset */ io_offset = 0x0; } else { /* Start search after the current entry */ io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep); /* Scan past the current entry by finding the next newline */ 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); } } /* Already at EOF? */ if (io_offset == io_size) return (NULL); /* Seek to the first valid entry, or EOF */ if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) { BHND_NV_LOG("unexpected error in seek_next(): %d\n", error); return (NULL); } /* Hit EOF? */ if (io_offset == io_size) return (NULL); /* Provide the new cookie for this offset */ *cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset); /* Fetch the name pointer; it must be at least 1 byte long */ 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 the name pointer */ 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); /* At EOF? */ if (io_offset == io_size) return (NULL); /* Determine the entry length */ 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); } /* Fetch the entry's value pointer and length */ 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 is always CSTR */ *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); /* At EOF? */ if (io_offset == io_size) BHND_NV_PANIC("invalid cookiep: %p", cookiep); /* Variable name is found directly at the given offset; trailing * NUL means we can assume that it's at least 1 byte long */ 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); } /** * Return a cookiep for the given I/O offset. */ 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)); } /* Convert a cookiep back to an I/O offset */ 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); } /* Determine the entry length and env 'key=value' string length of the entry * at @p 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; /* Fetch read buffer */ if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes))) return (error); /* Find record termination (EOL, or '#') */ p = rbuf; baseptr = rbuf; while ((size_t)(p - baseptr) < nbytes) { if (*p == '#' || *p == '\n' || *p == '\r') break; p++; } /* Got line length, now trim any trailing whitespace to determine * actual env length */ *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); } /* Seek past the next line ending (\r, \r\n, or \n) */ 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; /* Fetch read buffer */ 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; /* Advance to next char. The next position may be EOF, in which * case a read will be invalid */ p++; if (c == '\r') { /* CR, check for optional LF */ if ((size_t)(p - baseptr) < nbytes) { if (*p == '\n') p++; } break; } else if (c == '\n') { break; } } /* Hit newline or EOF */ *offset += (p - baseptr); return (0); } /* Seek to the next valid non-comment line (or EOF) */ 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; /* Fetch read buffer */ if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes))) return (error); /* Skip leading whitespace and comments */ baseptr = rbuf; p = rbuf; while ((size_t)(p - baseptr) < nbytes) { char c = *p; /* Skip whitespace */ if (bhnd_nv_isspace(c)) { p++; continue; } /* Skip entire comment line */ 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; } /* Non-whitespace, non-comment */ 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; /* Name (trimmed of any path prefix) must be valid */ if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name))) return (EINVAL); /* Value must be bcm-formatted string */ error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt, value, BHND_NVRAM_VAL_DYNAMIC); if (error) return (error); /* Value string must not contain our record delimiter character ('\n'), * or our comment character ('#') */ 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); } } /* Success. Transfer result ownership to the caller. */ *result = str; return (0); } static int bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { /* We permit deletion of any variable */ return (0); }