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