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