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