1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2016 Toomas Soome <tsoome@me.com> 24 * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. 25 */ 26 27 #include "benv.h" 28 #include <ctype.h> 29 #include <stdarg.h> 30 #include <sys/mman.h> 31 #include <unistd.h> 32 #include <signal.h> 33 #include <sys/wait.h> 34 35 /* 36 * Usage: % eeprom [-v] [-f prom_dev] [-] 37 * % eeprom [-v] [-f prom_dev] field[=value] ... 38 */ 39 40 extern void get_kbenv(void); 41 extern void close_kbenv(void); 42 extern caddr_t get_propval(char *name, char *node); 43 extern void setpname(char *prog); 44 extern char *getbootcmd(void); 45 46 char *boottree; 47 struct utsname uts_buf; 48 49 static int test; 50 int verbose; 51 52 /* 53 * Concatenate a NULL terminated list of strings into 54 * a single string. 55 */ 56 char * 57 strcats(char *s, ...) 58 { 59 char *cp, *ret; 60 size_t len; 61 va_list ap; 62 63 va_start(ap, s); 64 for (ret = NULL, cp = s; cp; cp = va_arg(ap, char *)) { 65 if (ret == NULL) { 66 ret = strdup(s); 67 len = strlen(ret) + 1; 68 } else { 69 len += strlen(cp); 70 ret = realloc(ret, len); 71 (void) strcat(ret, cp); 72 } 73 } 74 va_end(ap); 75 76 return (ret); 77 } 78 79 eplist_t * 80 new_list(void) 81 { 82 eplist_t *list; 83 84 list = (eplist_t *)malloc(sizeof (eplist_t)); 85 (void) memset(list, 0, sizeof (eplist_t)); 86 87 list->next = list; 88 list->prev = list; 89 list->item = NULL; 90 91 return (list); 92 } 93 94 void 95 add_item(void *item, eplist_t *list) 96 { 97 eplist_t *entry; 98 99 entry = (eplist_t *)malloc(sizeof (eplist_t)); 100 (void) memset(entry, 0, sizeof (eplist_t)); 101 entry->item = item; 102 103 entry->next = list; 104 entry->prev = list->prev; 105 list->prev->next = entry; 106 list->prev = entry; 107 } 108 109 typedef struct benv_ent { 110 char *cmd; 111 char *name; 112 char *val; 113 } benv_ent_t; 114 115 typedef struct benv_des { 116 char *name; 117 int fd; 118 caddr_t adr; 119 size_t len; 120 eplist_t *elist; 121 } benv_des_t; 122 123 static benv_des_t * 124 new_bd(void) 125 { 126 127 benv_des_t *bd; 128 129 bd = (benv_des_t *)malloc(sizeof (benv_des_t)); 130 (void) memset(bd, 0, sizeof (benv_des_t)); 131 132 bd->elist = new_list(); 133 134 return (bd); 135 } 136 137 /* 138 * Create a new entry. Comment entries have NULL names. 139 */ 140 static benv_ent_t * 141 new_bent(char *comm, char *cmd, char *name, char *val) 142 { 143 benv_ent_t *bent; 144 145 bent = (benv_ent_t *)malloc(sizeof (benv_ent_t)); 146 (void) memset(bent, 0, sizeof (benv_ent_t)); 147 148 if (comm) { 149 bent->cmd = strdup(comm); 150 comm = NULL; 151 } else { 152 bent->cmd = strdup(cmd); 153 bent->name = strdup(name); 154 if (val) 155 bent->val = strdup(val); 156 } 157 158 return (bent); 159 } 160 161 /* 162 * Add a new entry to the benv entry list. Entries can be 163 * comments or commands. 164 */ 165 static void 166 add_bent(eplist_t *list, char *comm, char *cmd, char *name, char *val) 167 { 168 benv_ent_t *bent; 169 170 bent = new_bent(comm, cmd, name, val); 171 add_item((void *)bent, list); 172 } 173 174 static benv_ent_t * 175 get_var(char *name, eplist_t *list) 176 { 177 eplist_t *e; 178 benv_ent_t *p; 179 180 for (e = list->next; e != list; e = e->next) { 181 p = (benv_ent_t *)e->item; 182 if (p->name != NULL && strcmp(p->name, name) == 0) 183 return (p); 184 } 185 186 return (NULL); 187 } 188 189 static void 190 print_var(char *name, eplist_t *list) 191 { 192 benv_ent_t *p; 193 char *bootcmd; 194 195 if (strcmp(name, "bootcmd") == 0) { 196 bootcmd = getbootcmd(); 197 (void) printf("%s=%s\n", name, bootcmd ? bootcmd : ""); 198 } else if ((p = get_var(name, list)) == NULL) { 199 (void) printf("%s: data not available.\n", name); 200 } else { 201 (void) printf("%s=%s\n", name, p->val ? p->val : ""); 202 } 203 } 204 205 static void 206 print_vars(eplist_t *list) 207 { 208 eplist_t *e; 209 benv_ent_t *p; 210 211 for (e = list->next; e != list; e = e->next) { 212 p = (benv_ent_t *)e->item; 213 if (p->name != NULL) { 214 (void) printf("%s=%s\n", p->name, p->val ? p->val : ""); 215 } 216 } 217 } 218 219 /* 220 * Write a string to a file, quoted appropriately. We use single 221 * quotes to prevent any variable expansion. Of course, we backslash-quote 222 * any single quotes or backslashes. 223 */ 224 static void 225 put_quoted(FILE *fp, char *val) 226 { 227 (void) putc('\'', fp); 228 while (*val) { 229 switch (*val) { 230 case '\'': 231 case '\\': 232 (void) putc('\\', fp); 233 /* FALLTHROUGH */ 234 default: 235 (void) putc(*val, fp); 236 break; 237 } 238 val++; 239 } 240 (void) putc('\'', fp); 241 } 242 243 /* 244 * Returns 1 if bootenv.rc was modified, 0 otherwise. 245 */ 246 static int 247 set_var(char *name, char *val, eplist_t *list) 248 { 249 benv_ent_t *p; 250 251 if (strcmp(name, "bootcmd") == 0) 252 return (0); 253 254 if (verbose) { 255 (void) printf("old:"); 256 print_var(name, list); 257 } 258 259 if ((p = get_var(name, list)) != NULL) { 260 free(p->val); 261 p->val = strdup(val); 262 } else 263 add_bent(list, NULL, "setprop", name, val); 264 265 if (verbose) { 266 (void) printf("new:"); 267 print_var(name, list); 268 } 269 return (1); 270 } 271 272 /* 273 * Returns 1 if bootenv.rc is modified or 0 if no modification was 274 * necessary. This allows us to implement non super-user look-up of 275 * variables by name without the user being yelled at for trying to 276 * modify the bootenv.rc file. 277 */ 278 static int 279 proc_var(char *name, eplist_t *list) 280 { 281 register char *val; 282 283 if ((val = strchr(name, '=')) == NULL) { 284 print_var(name, list); 285 return (0); 286 } else { 287 *val++ = '\0'; 288 return (set_var(name, val, list)); 289 } 290 } 291 292 static void 293 init_benv(benv_des_t *bd, char *file) 294 { 295 get_kbenv(); 296 297 if (test) 298 boottree = "/tmp"; 299 else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL) 300 boottree = strcats("/boot", NULL); 301 302 if (file != NULL) 303 bd->name = file; 304 else 305 bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL); 306 } 307 308 static void 309 map_benv(benv_des_t *bd) 310 { 311 if ((bd->fd = open(bd->name, O_RDONLY)) == -1) 312 if (errno == ENOENT) 313 return; 314 else 315 exit(_error(PERROR, "cannot open %s", bd->name)); 316 317 if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) { 318 if (close(bd->fd) == -1) 319 exit(_error(PERROR, "close error on %s", bd->name)); 320 return; 321 } 322 323 (void) lseek(bd->fd, 0, SEEK_SET); 324 325 if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE), 326 MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED) 327 exit(_error(PERROR, "cannot map %s", bd->name)); 328 } 329 330 static void 331 unmap_benv(benv_des_t *bd) 332 { 333 if (munmap(bd->adr, bd->len) == -1) 334 exit(_error(PERROR, "unmap error on %s", bd->name)); 335 336 if (close(bd->fd) == -1) 337 exit(_error(PERROR, "close error on %s", bd->name)); 338 } 339 340 #define NL '\n' 341 #define COMM '#' 342 343 /* 344 * Add a comment block to the benv list. 345 */ 346 static void 347 add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line) 348 { 349 int nl, lines; 350 char *p; 351 352 nl = 0; 353 for (p = base, lines = 0; p < last; p++) { 354 if (*p == NL) { 355 nl++; 356 lines++; 357 } else if (nl) { 358 if (*p != COMM) 359 break; 360 nl = 0; 361 } 362 } 363 *(p - 1) = NULL; 364 add_bent(bd->elist, base, NULL, NULL, NULL); 365 *next = p; 366 *line += lines; 367 } 368 369 /* 370 * Parse out an operator (setprop) from the boot environment 371 */ 372 static char * 373 parse_cmd(benv_des_t *bd, char **next, int *line) 374 { 375 char *strbegin; 376 char *badeof = "unexpected EOF in %s line %d"; 377 char *syntax = "syntax error in %s line %d"; 378 char *c = *next; 379 380 /* 381 * Skip spaces or tabs. New lines increase the line count. 382 */ 383 while (isspace(*c)) { 384 if (*c++ == '\n') 385 (*line)++; 386 } 387 388 /* 389 * Check for a the setprop command. Currently that's all we 390 * seem to support. 391 * 392 * XXX need support for setbinprop? 393 */ 394 395 /* 396 * Check first for end of file. Finding one now would be okay. 397 * We should also bail if we are at the start of a comment. 398 */ 399 if (*c == '\0' || *c == COMM) { 400 *next = c; 401 return (NULL); 402 } 403 404 strbegin = c; 405 while (*c && !isspace(*c)) 406 c++; 407 408 /* 409 * Check again for end of file. Finding one now would NOT be okay. 410 */ 411 if (*c == '\0') { 412 exit(_error(NO_PERROR, badeof, bd->name, *line)); 413 } 414 415 *c++ = '\0'; 416 *next = c; 417 418 /* 419 * Last check is to make sure the command is a setprop! 420 */ 421 if (strcmp(strbegin, "setprop") != 0) { 422 exit(_error(NO_PERROR, syntax, bd->name, *line)); 423 /* NOTREACHED */ 424 } 425 return (strbegin); 426 } 427 428 /* 429 * Parse out the name (LHS) of a setprop from the boot environment 430 */ 431 static char * 432 parse_name(benv_des_t *bd, char **next, int *line) 433 { 434 char *strbegin; 435 char *badeof = "unexpected EOF in %s line %d"; 436 char *syntax = "syntax error in %s line %d"; 437 char *c = *next; 438 439 /* 440 * Skip spaces or tabs. No tolerance for new lines now. 441 */ 442 while (isspace(*c)) { 443 if (*c++ == '\n') 444 exit(_error(NO_PERROR, syntax, bd->name, *line)); 445 } 446 447 /* 448 * Grab a name for the property to set. 449 */ 450 451 /* 452 * Check first for end of file. Finding one now would NOT be okay. 453 */ 454 if (*c == '\0') { 455 exit(_error(NO_PERROR, badeof, bd->name, *line)); 456 } 457 458 strbegin = c; 459 while (*c && !isspace(*c)) 460 c++; 461 462 /* 463 * At this point in parsing we have 'setprop name'. What follows 464 * is a newline, other whitespace, or EOF. Most of the time we 465 * want to replace a white space character with a NULL to terminate 466 * the name, and then continue on processing. A newline here provides 467 * the most grief. If we just replace it with a null we'll 468 * potentially get the setprop on the next line as the value of this 469 * setprop! So, if the last thing we see is a newline we'll have to 470 * dup the string. 471 */ 472 if (isspace(*c)) { 473 if (*c == '\n') { 474 *c = '\0'; 475 strbegin = strdup(strbegin); 476 *c = '\n'; 477 } else { 478 *c++ = '\0'; 479 } 480 } 481 482 *next = c; 483 return (strbegin); 484 } 485 486 /* 487 * Parse out the value (RHS) of a setprop line from the boot environment 488 */ 489 static char * 490 parse_value(benv_des_t *bd, char **next, int *line) 491 { 492 char *strbegin; 493 char *badeof = "unexpected EOF in %s line %d"; 494 char *result; 495 char *c = *next; 496 char quote; 497 498 /* 499 * Skip spaces or tabs. A newline here would indicate a 500 * NULL property value. 501 */ 502 while (isspace(*c)) { 503 if (*c++ == '\n') { 504 (*line)++; 505 *next = c; 506 return (NULL); 507 } 508 } 509 510 /* 511 * Grab the value of the property to set. 512 */ 513 514 /* 515 * Check first for end of file. Finding one now would 516 * also indicate a NULL property. 517 */ 518 if (*c == '\0') { 519 *next = c; 520 return (NULL); 521 } 522 523 /* 524 * Value may be quoted, in which case we assume the end of the value 525 * comes with a closing quote. 526 * 527 * We also allow escaped quote characters inside the quoted value. 528 * 529 * For obvious reasons we do not attempt to parse variable references. 530 */ 531 if (*c == '"' || *c == '\'') { 532 quote = *c; 533 c++; 534 strbegin = c; 535 result = c; 536 while (*c != quote) { 537 if (*c == '\\') { 538 c++; 539 } 540 if (*c == '\0') { 541 break; 542 } 543 *result++ = *c++; 544 } 545 546 /* 547 * Throw fatal exception if no end quote found. 548 */ 549 if (*c != quote) { 550 exit(_error(NO_PERROR, badeof, bd->name, *line)); 551 } 552 553 *result = '\0'; /* Terminate the result */ 554 c++; /* and step past the close quote */ 555 } else { 556 strbegin = c; 557 while (*c && !isspace(*c)) 558 c++; 559 } 560 561 /* 562 * Check again for end of file. Finding one now is okay. 563 */ 564 if (*c == '\0') { 565 *next = c; 566 return (strbegin); 567 } 568 569 *c++ = '\0'; 570 *next = c; 571 return (strbegin); 572 } 573 574 /* 575 * Add a command to the benv list. 576 */ 577 static void 578 add_cmd(benv_des_t *bd, char *last, char **next, int *line) 579 { 580 char *cmd, *name, *val; 581 582 while (*next <= last && **next != COMM) { 583 if ((cmd = parse_cmd(bd, next, line)) == NULL) 584 break; 585 name = parse_name(bd, next, line); 586 val = parse_value(bd, next, line); 587 add_bent(bd->elist, NULL, cmd, name, val); 588 (*line)++; 589 }; 590 591 } 592 593 /* 594 * Parse the benv (bootenv.rc) file and break it into a benv 595 * list. List entries may be comment blocks or commands. 596 */ 597 static void 598 parse_benv(benv_des_t *bd) 599 { 600 int line; 601 char *pbase, *pend; 602 char *tok, *tnext; 603 604 line = 1; 605 pbase = (char *)bd->adr; 606 pend = pbase + bd->len; 607 608 for (tok = tnext = pbase; tnext < pend && '\0' != *tnext; tok = tnext) 609 if (*tok == COMM) 610 add_comm(bd, tok, pend, &tnext, &line); 611 else 612 add_cmd(bd, pend, &tnext, &line); 613 } 614 615 static void 616 write_benv(benv_des_t *bd) 617 { 618 FILE *fp; 619 eplist_t *list, *e; 620 benv_ent_t *bent; 621 char *name; 622 623 list = bd->elist; 624 625 if (list->next == list) 626 return; 627 628 if ((fp = fopen(bd->name, "w")) == NULL) 629 exit(_error(PERROR, "cannot open %s", bd->name)); 630 631 for (e = list->next; e != list; e = e->next) { 632 bent = (benv_ent_t *)e->item; 633 name = bent->name; 634 if (name) { 635 if (bent->val) { 636 (void) fprintf(fp, "%s %s ", 637 bent->cmd, bent->name); 638 put_quoted(fp, bent->val); 639 (void) fprintf(fp, "\n"); 640 } else { 641 (void) fprintf(fp, "%s %s\n", 642 bent->cmd, bent->name); 643 } 644 } else { 645 (void) fprintf(fp, "%s\n", bent->cmd); 646 } 647 } 648 649 (void) fclose(fp); 650 } 651 652 static char * 653 get_line(void) 654 { 655 int c; 656 char *nl; 657 static char line[256]; 658 659 if (fgets(line, sizeof (line), stdin) != NULL) { 660 /* 661 * Remove newline if present, 662 * otherwise discard rest of line. 663 */ 664 if (nl = strchr(line, '\n')) 665 *nl = 0; 666 else 667 while ((c = getchar()) != '\n' && c != EOF) 668 ; 669 return (line); 670 } else 671 return (NULL); 672 } 673 674 int 675 main(int argc, char **argv) 676 { 677 int c; 678 int updates = 0; 679 char *usage = "Usage: %s [-v] [-f prom-device]" 680 " [variable[=value] ...]"; 681 eplist_t *elist; 682 benv_des_t *bd; 683 char *file = NULL; 684 685 setpname(argv[0]); 686 687 while ((c = getopt(argc, argv, "f:Itv")) != -1) 688 switch (c) { 689 case 'v': 690 verbose++; 691 break; 692 case 'f': 693 file = optarg; 694 break; 695 case 't': 696 test++; 697 break; 698 default: 699 exit(_error(NO_PERROR, usage, argv[0])); 700 } 701 702 (void) uname(&uts_buf); 703 bd = new_bd(); 704 init_benv(bd, file); 705 706 map_benv(bd); 707 if (bd->len) { 708 parse_benv(bd); 709 unmap_benv(bd); 710 } 711 712 elist = bd->elist; 713 714 if (optind >= argc) { 715 print_vars(elist); 716 return (0); 717 } else 718 while (optind < argc) { 719 /* 720 * If "-" specified, read variables from stdin; 721 * otherwise, process each argument as a variable 722 * print or set request. 723 */ 724 if (strcmp(argv[optind], "-") == 0) { 725 char *line; 726 727 while ((line = get_line()) != NULL) 728 updates += proc_var(line, elist); 729 clearerr(stdin); 730 } else 731 updates += proc_var(argv[optind], elist); 732 733 optind++; 734 } 735 736 /* 737 * don't write benv if we are processing delayed writes since 738 * it is likely that the delayed writes changes bootenv.rc anyway... 739 */ 740 if (updates) 741 write_benv(bd); 742 close_kbenv(); 743 744 return (0); 745 } 746