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 } 964