1/*-
2 * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer,
10 *    without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 *    redistribution must be conditioned upon including a substantially
14 *    similar Disclaimer requirement for further binary redistribution.
15 *
16 * NO WARRANTY
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 * THE POSSIBILITY OF SUCH DAMAGES.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/endian.h>
34
35#ifdef _KERNEL
36
37#include <sys/param.h>
38#include <sys/ctype.h>
39#include <sys/malloc.h>
40#include <sys/systm.h>
41
42#else /* !_KERNEL */
43
44#include <ctype.h>
45#include <stdint.h>
46#include <stdlib.h>
47#include <string.h>
48
49#endif /* _KERNEL */
50
51#include "bhnd_nvram_private.h"
52
53#include "bhnd_nvram_datavar.h"
54
55#include "bhnd_nvram_data_bcmreg.h"	/* for BCM_NVRAM_MAGIC */
56
57/**
58 * Broadcom "Board Text" data class.
59 *
60 * This format is used to provide external NVRAM data for some
61 * fullmac WiFi devices, and as an input format when programming
62 * NVRAM/SPROM/OTP.
63 */
64
65struct bhnd_nvram_btxt {
66	struct bhnd_nvram_data	 nv;	/**< common instance state */
67	struct bhnd_nvram_io	*data;	/**< memory-backed board text data */
68	size_t			 count;	/**< variable count */
69};
70
71BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text",
72    BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt))
73
74/** Minimal identification header */
75union bhnd_nvram_btxt_ident {
76	uint32_t	bcm_magic;
77	char		btxt[8];
78};
79
80static void	*bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
81		 size_t io_offset);
82static size_t	 bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt,
83		     void *cookiep);
84
85static int	bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io,
86		    size_t offset, size_t *line_len, size_t *env_len);
87static int	bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io,
88		    size_t *offset);
89static int	bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io,
90		    size_t *offset);
91
92static int
93bhnd_nvram_btxt_probe(struct bhnd_nvram_io *io)
94{
95	union bhnd_nvram_btxt_ident	ident;
96	char				c;
97	int				error;
98
99	/* Look at the initial header for something that looks like
100	 * an ASCII board text file */
101	if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident))))
102		return (error);
103
104	/* The BCM NVRAM format uses a 'FLSH' little endian magic value, which
105	 * shouldn't be interpreted as BTXT */
106	if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC)
107		return (ENXIO);
108
109	/* Don't match on non-ASCII/non-printable data */
110	for (size_t i = 0; i < nitems(ident.btxt); i++) {
111		c = ident.btxt[i];
112		if (!bhnd_nv_isprint(c))
113			return (ENXIO);
114	}
115
116	/* The first character should either be a valid key char (alpha),
117	 * whitespace, or the start of a comment ('#') */
118	c = ident.btxt[0];
119	if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#')
120		return (ENXIO);
121
122	/* We assert a low priority, given that we've only scanned an
123	 * initial few bytes of the file. */
124	return (BHND_NVRAM_DATA_PROBE_MAYBE);
125}
126
127
128/**
129 * Parser states for bhnd_nvram_bcm_getvar_direct_common().
130 */
131typedef enum {
132	BTXT_PARSE_LINE_START,
133	BTXT_PARSE_KEY,
134	BTXT_PARSE_KEY_END,
135	BTXT_PARSE_NEXT_LINE,
136	BTXT_PARSE_VALUE_START,
137	BTXT_PARSE_VALUE
138} btxt_parse_state;
139
140static int
141bhnd_nvram_btxt_getvar_direct(struct bhnd_nvram_io *io, const char *name,
142    void *outp, size_t *olen, bhnd_nvram_type otype)
143{
144	char				 buf[512];
145	btxt_parse_state		 pstate;
146	size_t				 limit, offset;
147	size_t				 buflen, bufpos;
148	size_t				 namelen, namepos;
149	size_t				 vlen;
150	int				 error;
151
152	limit = bhnd_nvram_io_getsize(io);
153	offset = 0;
154
155	/* Loop our parser until we find the requested variable, or hit EOF */
156	pstate = BTXT_PARSE_LINE_START;
157	buflen = 0;
158	bufpos = 0;
159	namelen = strlen(name);
160	namepos = 0;
161	vlen = 0;
162
163	while ((offset - bufpos) < limit) {
164		BHND_NV_ASSERT(bufpos <= buflen,
165		    ("buf position invalid (%zu > %zu)", bufpos, buflen));
166		BHND_NV_ASSERT(buflen <= sizeof(buf),
167		    ("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
168
169		/* Repopulate our parse buffer? */
170		if (buflen - bufpos == 0) {
171			BHND_NV_ASSERT(offset < limit, ("offset overrun"));
172
173			buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
174			bufpos = 0;
175
176			error = bhnd_nvram_io_read(io, offset, buf, buflen);
177			if (error)
178				return (error);
179
180			offset += buflen;
181		}
182
183		switch (pstate) {
184		case BTXT_PARSE_LINE_START:
185			BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
186
187			/* Reset name matching position */
188			namepos = 0;
189
190			/* Trim any leading whitespace */
191			while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos]))
192			{
193				bufpos++;
194			}
195
196			if (bufpos == buflen) {
197				/* Continue parsing the line */
198				pstate = BTXT_PARSE_LINE_START;
199			} else if (bufpos < buflen && buf[bufpos] == '#') {
200				/* Comment; skip to next line */
201				pstate = BTXT_PARSE_NEXT_LINE;
202			} else {
203				/* Start name matching */
204				pstate = BTXT_PARSE_KEY;
205			}
206
207
208			break;
209
210		case BTXT_PARSE_KEY: {
211			size_t navail, nleft;
212
213			nleft = namelen - namepos;
214			navail = bhnd_nv_ummin(buflen - bufpos, nleft);
215
216			if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
217				/* Matched */
218				namepos += navail;
219				bufpos += navail;
220
221				if (namepos == namelen) {
222					/* Matched the full variable; look for
223					 * its trailing delimiter */
224					pstate = BTXT_PARSE_KEY_END;
225				} else {
226					/* Continue matching the name */
227					pstate = BTXT_PARSE_KEY;
228				}
229			} else {
230				/* No match; advance to next entry and restart
231				 * name matching */
232				pstate = BTXT_PARSE_NEXT_LINE;
233			}
234
235			break;
236		}
237
238		case BTXT_PARSE_KEY_END:
239			BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
240
241			if (buf[bufpos] == '=') {
242				/* Key fully matched; advance past '=' and
243				 * parse the value */
244				bufpos++;
245				pstate = BTXT_PARSE_VALUE_START;
246			} else {
247				/* No match; advance to next line and restart
248				 * name matching */
249				pstate = BTXT_PARSE_NEXT_LINE;
250			}
251
252			break;
253
254		case BTXT_PARSE_NEXT_LINE: {
255			const char *p;
256
257			/* Scan for a '\r', '\n', or '\r\n' terminator */
258			p = memchr(buf+bufpos, '\n', buflen - bufpos);
259			if (p == NULL)
260				p = memchr(buf+bufpos, '\r', buflen - bufpos);
261
262			if (p != NULL) {
263				/* Found entry terminator; restart name
264				 * matching at next line */
265				pstate = BTXT_PARSE_LINE_START;
266				bufpos = (p - buf);
267			} else {
268				/* Consumed full buffer looking for newline;
269				 * force repopulation of the buffer and
270				 * retry */
271				pstate = BTXT_PARSE_NEXT_LINE;
272				bufpos = buflen;
273			}
274
275			break;
276		}
277
278		case BTXT_PARSE_VALUE_START: {
279			const char *p;
280
281			/* Scan for a terminating newline */
282			p = memchr(buf+bufpos, '\n', buflen - bufpos);
283			if (p == NULL)
284				p = memchr(buf+bufpos, '\r', buflen - bufpos);
285
286			if (p != NULL) {
287				/* Found entry terminator; parse the value */
288				vlen = p - &buf[bufpos];
289				pstate = BTXT_PARSE_VALUE;
290
291			} else if (p == NULL && offset == limit) {
292				/* Hit EOF without a terminating newline;
293				 * treat the entry as implicitly terminated */
294				vlen = buflen - bufpos;
295				pstate = BTXT_PARSE_VALUE;
296
297			} else if (p == NULL && bufpos > 0) {
298				size_t	nread;
299
300				/* Move existing value data to start of
301				 * buffer */
302				memmove(buf, buf+bufpos, buflen - bufpos);
303				buflen = bufpos;
304				bufpos = 0;
305
306				/* Populate full buffer to allow retry of
307				 * value parsing */
308				nread = bhnd_nv_ummin(sizeof(buf) - buflen,
309				    limit - offset);
310
311				error = bhnd_nvram_io_read(io, offset,
312				    buf+buflen, nread);
313				if (error)
314					return (error);
315
316				offset += nread;
317				buflen += nread;
318			} else {
319				/* Value exceeds our buffer capacity */
320				BHND_NV_LOG("cannot parse value for '%s' "
321				    "(exceeds %zu byte limit)\n", name,
322				    sizeof(buf));
323
324				return (ENXIO);
325			}
326
327			break;
328		}
329
330		case BTXT_PARSE_VALUE:
331			BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
332
333			/* Trim any trailing whitespace */
334			while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1]))
335				vlen--;
336
337			/* Write the value to the caller's buffer */
338			return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
339			    BHND_NVRAM_TYPE_STRING, outp, olen, otype));
340		}
341	}
342
343	/* Variable not found */
344	return (ENOENT);
345}
346
347static int
348bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
349    bhnd_nvram_plist *options, void *outp, size_t *olen)
350{
351	bhnd_nvram_prop	*prop;
352	size_t		 limit, nbytes;
353	int		 error;
354
355	/* Determine output byte limit */
356	if (outp != NULL)
357		limit = *olen;
358	else
359		limit = 0;
360
361	nbytes = 0;
362
363	/* Write all properties */
364	prop = NULL;
365	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
366		const char	*name;
367		char		*p;
368		size_t		 prop_limit;
369		size_t		 name_len, value_len;
370
371		if (outp == NULL || limit < nbytes) {
372			p = NULL;
373			prop_limit = 0;
374		} else {
375			p = ((char *)outp) + nbytes;
376			prop_limit = limit - nbytes;
377		}
378
379		/* Fetch and write 'name=' to output */
380		name = bhnd_nvram_prop_name(prop);
381		name_len = strlen(name) + 1;
382
383		if (prop_limit > name_len) {
384			memcpy(p, name, name_len - 1);
385			p[name_len - 1] = '=';
386
387			prop_limit -= name_len;
388			p += name_len;
389		} else {
390			prop_limit = 0;
391			p = NULL;
392		}
393
394		/* Advance byte count */
395		if (SIZE_MAX - nbytes < name_len)
396			return (EFTYPE); /* would overflow size_t */
397
398		nbytes += name_len;
399
400		/* Write NUL-terminated value to output, rewrite NUL as
401		 * '\n' record delimiter */
402		value_len = prop_limit;
403		error = bhnd_nvram_prop_encode(prop, p, &value_len,
404		    BHND_NVRAM_TYPE_STRING);
405		if (p != NULL && error == 0) {
406			/* Replace trailing '\0' with newline */
407			BHND_NV_ASSERT(value_len > 0, ("string length missing "
408			    "minimum required trailing NUL"));
409
410			*(p + (value_len - 1)) = '\n';
411		} else if (error && error != ENOMEM) {
412			/* If encoding failed for any reason other than ENOMEM
413			 * (which we'll detect and report after encoding all
414			 * properties), return immediately */
415			BHND_NV_LOG("error serializing %s to required type "
416			    "%s: %d\n", name,
417			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
418			    error);
419			return (error);
420		}
421
422		/* Advance byte count */
423		if (SIZE_MAX - nbytes < value_len)
424			return (EFTYPE); /* would overflow size_t */
425
426		nbytes += value_len;
427	}
428
429	/* Provide required length */
430	*olen = nbytes;
431	if (limit < *olen) {
432		if (outp == NULL)
433			return (0);
434
435		return (ENOMEM);
436	}
437
438	return (0);
439}
440
441/**
442 * Initialize @p btxt with the provided board text data mapped by @p src.
443 *
444 * @param btxt A newly allocated data instance.
445 */
446static int
447bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src)
448{
449	const void		*ptr;
450	const char		*name, *value;
451	size_t			 name_len, value_len;
452	size_t			 line_len, env_len;
453	size_t			 io_offset, io_size, str_size;
454	int			 error;
455
456	BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated"));
457
458	if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL)
459		return (ENOMEM);
460
461	io_size = bhnd_nvram_io_getsize(btxt->data);
462	io_offset = 0;
463
464	/* Fetch a pointer mapping the entirity of the board text data */
465	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
466	if (error)
467		return (error);
468
469	/* Determine the actual size, minus any terminating NUL. We
470	 * parse NUL-terminated C strings, but do not include NUL termination
471	 * in our internal or serialized representations */
472	str_size = strnlen(ptr, io_size);
473
474	/* If the terminating NUL is not found at the end of the buffer,
475	 * this is BCM-RAW or other NUL-delimited NVRAM format. */
476	if (str_size < io_size && str_size + 1 < io_size)
477		return (EINVAL);
478
479	/* Adjust buffer size to account for NUL termination (if any) */
480	io_size = str_size;
481	if ((error = bhnd_nvram_io_setsize(btxt->data, io_size)))
482		return (error);
483
484	/* Process the buffer */
485	btxt->count = 0;
486	while (io_offset < io_size) {
487		const void	*envp;
488
489		/* Seek to the next key=value entry */
490		if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset)))
491			return (error);
492
493		/* Determine the entry and line length */
494		error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset,
495		    &line_len, &env_len);
496		if (error)
497			return (error);
498
499		/* EOF? */
500		if (env_len == 0) {
501			BHND_NV_ASSERT(io_offset == io_size,
502		           ("zero-length record returned from "
503			    "bhnd_nvram_btxt_seek_next()"));
504			break;
505		}
506
507		/* Fetch a pointer to the line start */
508		error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp,
509		    env_len, NULL);
510		if (error)
511			return (error);
512
513		/* Parse the key=value string */
514		error = bhnd_nvram_parse_env(envp, env_len, '=', &name,
515		    &name_len, &value, &value_len);
516		if (error) {
517			return (error);
518		}
519
520		/* Insert a '\0' character, replacing the '=' delimiter and
521		 * allowing us to vend references directly to the variable
522		 * name */
523		error = bhnd_nvram_io_write(btxt->data, io_offset+name_len,
524		    &(char){'\0'}, 1);
525		if (error)
526			return (error);
527
528		/* Add to variable count */
529		btxt->count++;
530
531		/* Advance past EOL */
532		io_offset += line_len;
533	}
534
535	return (0);
536}
537
538static int
539bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
540{
541	struct bhnd_nvram_btxt	*btxt;
542	int			 error;
543
544	/* Allocate and initialize the BTXT data instance */
545	btxt = (struct bhnd_nvram_btxt *)nv;
546
547	/* Parse the BTXT input data and initialize our backing
548	 * data representation */
549	if ((error = bhnd_nvram_btxt_init(btxt, io))) {
550		bhnd_nvram_btxt_free(nv);
551		return (error);
552	}
553
554	return (0);
555}
556
557static void
558bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv)
559{
560	struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
561	if (btxt->data != NULL)
562		bhnd_nvram_io_free(btxt->data);
563}
564
565size_t
566bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv)
567{
568	struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
569	return (btxt->count);
570}
571
572static bhnd_nvram_plist *
573bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)
574{
575	return (NULL);
576}
577
578static uint32_t
579bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)
580{
581	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
582}
583
584static void *
585bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)
586{
587	return (bhnd_nvram_data_generic_find(nv, name));
588}
589
590static const char *
591bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep)
592{
593	struct bhnd_nvram_btxt	*btxt;
594	const void		*nptr;
595	size_t			 io_offset, io_size;
596	int			 error;
597
598	btxt = (struct bhnd_nvram_btxt *)nv;
599
600	io_size = bhnd_nvram_io_getsize(btxt->data);
601
602	if (*cookiep == NULL) {
603		/* Start search at initial file offset */
604		io_offset = 0x0;
605	} else {
606		/* Start search after the current entry */
607		io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep);
608
609		/* Scan past the current entry by finding the next newline */
610		error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset);
611		if (error) {
612			BHND_NV_LOG("unexpected error in seek_eol(): %d\n",
613			    error);
614			return (NULL);
615		}
616	}
617
618	/* Already at EOF? */
619	if (io_offset == io_size)
620		return (NULL);
621
622	/* Seek to the first valid entry, or EOF */
623	if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) {
624		BHND_NV_LOG("unexpected error in seek_next(): %d\n", error);
625		return (NULL);
626	}
627
628	/* Hit EOF? */
629	if (io_offset == io_size)
630		return (NULL);
631
632	/* Provide the new cookie for this offset */
633	*cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset);
634
635	/* Fetch the name pointer; it must be at least 1 byte long */
636	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL);
637	if (error) {
638		BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
639		return (NULL);
640	}
641
642	/* Return the name pointer */
643	return (nptr);
644}
645
646static int
647bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
648    void *cookiep2)
649{
650	if (cookiep1 < cookiep2)
651		return (-1);
652
653	if (cookiep1 > cookiep2)
654		return (1);
655
656	return (0);
657}
658
659static int
660bhnd_nvram_btxt_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
661    size_t *len, bhnd_nvram_type type)
662{
663	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
664}
665
666static int
667bhnd_nvram_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
668    bhnd_nvram_val **value)
669{
670	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
671}
672
673const void *
674bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
675    size_t *len, bhnd_nvram_type *type)
676{
677	struct bhnd_nvram_btxt	*btxt;
678	const void		*eptr;
679	const char		*vptr;
680	size_t			 io_offset, io_size;
681	size_t			 line_len, env_len;
682	int			 error;
683
684	btxt = (struct bhnd_nvram_btxt *)nv;
685
686	io_size = bhnd_nvram_io_getsize(btxt->data);
687	io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
688
689	/* At EOF? */
690	if (io_offset == io_size)
691		return (NULL);
692
693	/* Determine the entry length */
694	error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len,
695	    &env_len);
696	if (error) {
697		BHND_NV_LOG("unexpected error in entry_len(): %d\n", error);
698		return (NULL);
699	}
700
701	/* Fetch the entry's value pointer and length */
702	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len,
703	    NULL);
704	if (error) {
705		BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
706		return (NULL);
707	}
708
709	error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr,
710	    len);
711	if (error) {
712		BHND_NV_LOG("unexpected error in parse_env(): %d\n", error);
713		return (NULL);
714	}
715
716	/* Type is always CSTR */
717	*type = BHND_NVRAM_TYPE_STRING;
718
719	return (vptr);
720}
721
722static const char *
723bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
724{
725	struct bhnd_nvram_btxt	*btxt;
726	const void		*ptr;
727	size_t			 io_offset, io_size;
728	int			 error;
729
730	btxt = (struct bhnd_nvram_btxt *)nv;
731
732	io_size = bhnd_nvram_io_getsize(btxt->data);
733	io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
734
735	/* At EOF? */
736	if (io_offset == io_size)
737		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
738
739	/* Variable name is found directly at the given offset; trailing
740	 * NUL means we can assume that it's at least 1 byte long */
741	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL);
742	if (error)
743		BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error);
744
745	return (ptr);
746}
747
748/**
749 * Return a cookiep for the given I/O offset.
750 */
751static void *
752bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
753    size_t io_offset)
754{
755	const void	*ptr;
756	int		 error;
757
758	BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data),
759	    ("io_offset %zu out-of-range", io_offset));
760	BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
761	    ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
762
763	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL);
764	if (error)
765		BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
766
767	ptr = (const uint8_t *)ptr + io_offset;
768	return (__DECONST(void *, ptr));
769}
770
771/* Convert a cookiep back to an I/O offset */
772static size_t
773bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep)
774{
775	const void	*ptr;
776	intptr_t	 offset;
777	size_t		 io_size;
778	int		 error;
779
780	BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
781
782	io_size = bhnd_nvram_io_getsize(btxt->data);
783	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
784	if (error)
785		BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
786
787	offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
788	BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
789	BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
790	BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
791
792	return ((size_t)offset);
793}
794
795/* Determine the entry length and env 'key=value' string length of the entry
796 * at @p offset */
797static int
798bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset,
799    size_t *line_len, size_t *env_len)
800{
801	const uint8_t	*baseptr, *p;
802	const void	*rbuf;
803	size_t		 nbytes;
804	int		 error;
805
806	/* Fetch read buffer */
807	if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes)))
808		return (error);
809
810	/* Find record termination (EOL, or '#') */
811	p = rbuf;
812	baseptr = rbuf;
813	while ((size_t)(p - baseptr) < nbytes) {
814		if (*p == '#' || *p == '\n' || *p == '\r')
815			break;
816
817		p++;
818	}
819
820	/* Got line length, now trim any trailing whitespace to determine
821	 * actual env length */
822	*line_len = p - baseptr;
823	*env_len = *line_len;
824
825	for (size_t i = 0; i < *line_len; i++) {
826		char c = baseptr[*line_len - i - 1];
827		if (!bhnd_nv_isspace(c))
828			break;
829
830		*env_len -= 1;
831	}
832
833	return (0);
834}
835
836/* Seek past the next line ending (\r, \r\n, or \n) */
837static int
838bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset)
839{
840	const uint8_t	*baseptr, *p;
841	const void	*rbuf;
842	size_t		 nbytes;
843	int		 error;
844
845	/* Fetch read buffer */
846	if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
847		return (error);
848
849	baseptr = rbuf;
850	p = rbuf;
851	while ((size_t)(p - baseptr) < nbytes) {
852		char c = *p;
853
854		/* Advance to next char. The next position may be EOF, in which
855		 * case a read will be invalid */
856		p++;
857
858		if (c == '\r') {
859			/* CR, check for optional LF */
860			if ((size_t)(p - baseptr) < nbytes) {
861				if (*p == '\n')
862					p++;
863			}
864
865			break;
866		} else if (c == '\n') {
867			break;
868		}
869	}
870
871	/* Hit newline or EOF */
872	*offset += (p - baseptr);
873	return (0);
874}
875
876/* Seek to the next valid non-comment line (or EOF) */
877static int
878bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset)
879{
880	const uint8_t	*baseptr, *p;
881	const void	*rbuf;
882	size_t		 nbytes;
883	int		 error;
884
885	/* Fetch read buffer */
886	if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
887		return (error);
888
889	/* Skip leading whitespace and comments */
890	baseptr = rbuf;
891	p = rbuf;
892	while ((size_t)(p - baseptr) < nbytes) {
893		char c = *p;
894
895		/* Skip whitespace */
896		if (bhnd_nv_isspace(c)) {
897			p++;
898			continue;
899		}
900
901		/* Skip entire comment line */
902		if (c == '#') {
903			size_t line_off = *offset + (p - baseptr);
904
905			if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off)))
906				return (error);
907
908			p = baseptr + (line_off - *offset);
909			continue;
910		}
911
912		/* Non-whitespace, non-comment */
913		break;
914	}
915
916	*offset += (p - baseptr);
917	return (0);
918}
919
920static int
921bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
922    bhnd_nvram_val *value, bhnd_nvram_val **result)
923{
924	bhnd_nvram_val	*str;
925	const char	*inp;
926	bhnd_nvram_type	 itype;
927	size_t		 ilen;
928	int		 error;
929
930	/* Name (trimmed of any path prefix) must be valid */
931	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
932		return (EINVAL);
933
934	/* Value must be bcm-formatted string */
935	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
936	    value, BHND_NVRAM_VAL_DYNAMIC);
937	if (error)
938		return (error);
939
940	/* Value string must not contain our record delimiter character ('\n'),
941	 * or our comment character ('#') */
942	inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
943	BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value"));
944	for (size_t i = 0; i < ilen; i++) {
945		switch (inp[i]) {
946		case '\n':
947		case '#':
948			BHND_NV_LOG("invalid character (%#hhx) in value\n",
949			    inp[i]);
950			bhnd_nvram_val_release(str);
951			return (EINVAL);
952		}
953	}
954
955	/* Success. Transfer result ownership to the caller. */
956	*result = str;
957	return (0);
958}
959
960static int
961bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
962{
963	/* We permit deletion of any variable */
964	return (0);
965}
966