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 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 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 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 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 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 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 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 * 569 bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv) 570 { 571 return (NULL); 572 } 573 574 static uint32_t 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 * 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 * 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 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 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 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 * 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 * 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 * 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 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 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 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 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 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 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