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 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include "benv.h" 27 #include "message.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 setprogname(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 /*PRINTFLIKE1*/ 190 static void 191 eeprom_error(const char *format, ...) 192 { 193 va_list ap; 194 195 va_start(ap, format); 196 (void) fprintf(stderr, "eeprom: "); 197 (void) vfprintf(stderr, format, ap); 198 va_end(ap); 199 } 200 201 static int 202 exec_cmd(char *cmdline, char *output, int64_t osize) 203 { 204 char buf[BUFSIZ]; 205 int ret; 206 size_t len; 207 FILE *ptr; 208 sigset_t set; 209 void (*disp)(int); 210 211 if (output) 212 output[0] = '\0'; 213 214 /* 215 * For security 216 * - only absolute paths are allowed 217 * - set IFS to space and tab 218 */ 219 if (*cmdline != '/') { 220 eeprom_error(ABS_PATH_REQ, cmdline); 221 return (-1); 222 } 223 (void) putenv("IFS= \t"); 224 225 /* 226 * We may have been exec'ed with SIGCHLD blocked 227 * unblock it here 228 */ 229 (void) sigemptyset(&set); 230 (void) sigaddset(&set, SIGCHLD); 231 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { 232 eeprom_error(FAILED_SIG, strerror(errno)); 233 return (-1); 234 } 235 236 /* 237 * Set SIGCHLD disposition to SIG_DFL for popen/pclose 238 */ 239 disp = sigset(SIGCHLD, SIG_DFL); 240 if (disp == SIG_ERR) { 241 eeprom_error(FAILED_SIG, strerror(errno)); 242 return (-1); 243 } 244 if (disp == SIG_HOLD) { 245 eeprom_error(BLOCKED_SIG, cmdline); 246 return (-1); 247 } 248 249 ptr = popen(cmdline, "r"); 250 if (ptr == NULL) { 251 eeprom_error(POPEN_FAIL, cmdline, strerror(errno)); 252 return (-1); 253 } 254 255 /* 256 * If we simply do a pclose() following a popen(), pclose() 257 * will close the reader end of the pipe immediately even 258 * if the child process has not started/exited. pclose() 259 * does wait for cmd to terminate before returning though. 260 * When the executed command writes its output to the pipe 261 * there is no reader process and the command dies with 262 * SIGPIPE. To avoid this we read repeatedly until read 263 * terminates with EOF. This indicates that the command 264 * (writer) has closed the pipe and we can safely do a 265 * pclose(). 266 * 267 * Since pclose() does wait for the command to exit, 268 * we can safely reap the exit status of the command 269 * from the value returned by pclose() 270 */ 271 while (fgets(buf, sizeof (buf), ptr) != NULL) { 272 if (output && osize > 0) { 273 (void) snprintf(output, osize, "%s", buf); 274 len = strlen(buf); 275 output += len; 276 osize -= len; 277 } 278 } 279 280 /* 281 * If there's a "\n" at the end, we want to chop it off 282 */ 283 if (output) { 284 len = strlen(output) - 1; 285 if (output[len] == '\n') 286 output[len] = '\0'; 287 } 288 289 ret = pclose(ptr); 290 if (ret == -1) { 291 eeprom_error(PCLOSE_FAIL, cmdline, strerror(errno)); 292 return (-1); 293 } 294 295 if (WIFEXITED(ret)) { 296 return (WEXITSTATUS(ret)); 297 } else { 298 eeprom_error(EXEC_FAIL, cmdline, ret); 299 return (-1); 300 } 301 } 302 303 #define BOOTADM_STR "bootadm: " 304 305 /* 306 * bootadm starts all error messages with "bootadm: ". 307 * Add a note so users don't get confused on how they ran bootadm. 308 */ 309 static void 310 output_error_msg(const char *msg) 311 { 312 size_t len = sizeof (BOOTADM_STR) - 1; 313 314 if (strncmp(msg, BOOTADM_STR, len) == 0) { 315 eeprom_error("error returned from %s\n", msg); 316 } else if (msg[0] != '\0') { 317 eeprom_error("%s\n", msg); 318 } 319 } 320 321 static char * 322 get_bootadm_value(char *name, const int quiet) 323 { 324 char *ptr, *ret_str, *end_ptr, *orig_ptr; 325 char output[BUFSIZ]; 326 int is_console, is_kernel = 0; 327 size_t len; 328 329 is_console = (strcmp(name, "console") == 0); 330 331 if (strcmp(name, "boot-file") == 0) { 332 is_kernel = 1; 333 ptr = "/sbin/bootadm set-menu kernel 2>&1"; 334 } else if (is_console || (strcmp(name, "boot-args") == 0)) { 335 ptr = "/sbin/bootadm set-menu args 2>&1"; 336 } else { 337 eeprom_error("Unknown value in get_bootadm_value: %s\n", name); 338 return (NULL); 339 } 340 341 if (exec_cmd(ptr, output, BUFSIZ) != 0) { 342 if (quiet == 0) { 343 output_error_msg(output); 344 } 345 return (NULL); 346 } 347 348 if (is_console) { 349 if ((ptr = strstr(output, "console=")) == NULL) { 350 return (NULL); 351 } 352 ptr += strlen("console="); 353 354 /* 355 * -B may have comma-separated values. It may also be 356 * followed by other flags. 357 */ 358 len = strcspn(ptr, " \t,"); 359 ret_str = calloc(len + 1, 1); 360 if (ret_str == NULL) { 361 eeprom_error(NO_MEM, len + 1); 362 return (NULL); 363 } 364 (void) strncpy(ret_str, ptr, len); 365 return (ret_str); 366 } else if (is_kernel) { 367 ret_str = strdup(output); 368 if (ret_str == NULL) 369 eeprom_error(NO_MEM, strlen(output) + 1); 370 return (ret_str); 371 } else { 372 /* If there's no console setting, we can return */ 373 if ((orig_ptr = strstr(output, "console=")) == NULL) { 374 return (strdup(output)); 375 } 376 len = strcspn(orig_ptr, " \t,"); 377 ptr = orig_ptr; 378 end_ptr = orig_ptr + len + 1; 379 380 /* Eat up any white space */ 381 while ((*end_ptr == ' ') || (*end_ptr == '\t')) 382 end_ptr++; 383 384 /* 385 * If there's data following the console string, copy it. 386 * If not, cut off the new string. 387 */ 388 if (*end_ptr == '\0') 389 *ptr = '\0'; 390 391 while (*end_ptr != '\0') { 392 *ptr = *end_ptr; 393 ptr++; 394 end_ptr++; 395 } 396 *ptr = '\0'; 397 if ((strchr(output, '=') == NULL) && 398 (strncmp(output, "-B ", 3) == 0)) { 399 /* 400 * Since we removed the console setting, we no 401 * longer need the initial "-B " 402 */ 403 orig_ptr = output + 3; 404 } else { 405 orig_ptr = output; 406 } 407 408 ret_str = strdup(orig_ptr); 409 if (ret_str == NULL) 410 eeprom_error(NO_MEM, strlen(orig_ptr) + 1); 411 return (ret_str); 412 } 413 } 414 415 /* 416 * If quiet is 1, print nothing if there is no value. If quiet is 0, print 417 * a message. Return 1 if the value is printed, 0 otherwise. 418 */ 419 static int 420 print_bootadm_value(char *name, const int quiet) 421 { 422 int rv = 0; 423 char *value = get_bootadm_value(name, quiet); 424 425 if ((value != NULL) && (value[0] != '\0')) { 426 (void) printf("%s=%s\n", name, value); 427 rv = 1; 428 } else if (quiet == 0) { 429 (void) printf("%s: data not available.\n", name); 430 } 431 432 if (value != NULL) 433 free(value); 434 return (rv); 435 } 436 437 static void 438 print_var(char *name, eplist_t *list) 439 { 440 benv_ent_t *p; 441 442 /* 443 * The console property is kept in both menu.lst and bootenv.rc. The 444 * menu.lst value takes precedence. 445 */ 446 if (strcmp(name, "console") == 0) { 447 if (print_bootadm_value(name, 1) == 0) { 448 if ((p = get_var(name, list)) != NULL) { 449 (void) printf("%s=%s\n", name, p->val ? 450 p->val : ""); 451 } else { 452 (void) printf("%s: data not available.\n", 453 name); 454 } 455 } 456 } else if ((strcmp(name, "boot-file") == 0) || 457 (strcmp(name, "boot-args") == 0)) { 458 (void) print_bootadm_value(name, 0); 459 } else if ((p = get_var(name, list)) == NULL) 460 (void) printf("%s: data not available.\n", name); 461 else 462 (void) printf("%s=%s\n", name, p->val ? p->val : ""); 463 } 464 465 static void 466 print_vars(eplist_t *list) 467 { 468 eplist_t *e; 469 benv_ent_t *p; 470 int console_printed = 0; 471 472 /* 473 * The console property is kept both in menu.lst and bootenv.rc. 474 * The menu.lst value takes precedence, so try printing that one 475 * first. 476 */ 477 console_printed = print_bootadm_value("console", 1); 478 479 for (e = list->next; e != list; e = e->next) { 480 p = (benv_ent_t *)e->item; 481 if (p->name != NULL) { 482 if (((strcmp(p->name, "console") == 0) && 483 (console_printed == 1)) || 484 ((strcmp(p->name, "boot-file") == 0) || 485 (strcmp(p->name, "boot-args") == 0))) { 486 /* handle these separately */ 487 continue; 488 } 489 (void) printf("%s=%s\n", p->name, p->val ? p->val : ""); 490 } 491 } 492 (void) print_bootadm_value("boot-file", 1); 493 (void) print_bootadm_value("boot-args", 1); 494 } 495 496 /* 497 * Write a string to a file, quoted appropriately. We use single 498 * quotes to prevent any variable expansion. Of course, we backslash-quote 499 * any single quotes or backslashes. 500 */ 501 static void 502 put_quoted(FILE *fp, char *val) 503 { 504 (void) putc('\'', fp); 505 while (*val) { 506 switch (*val) { 507 case '\'': 508 case '\\': 509 (void) putc('\\', fp); 510 /* FALLTHROUGH */ 511 default: 512 (void) putc(*val, fp); 513 break; 514 } 515 val++; 516 } 517 (void) putc('\'', fp); 518 } 519 520 static void 521 set_bootadm_var(char *name, char *value) 522 { 523 char buf[BUFSIZ]; 524 char output[BUFSIZ] = ""; 525 char *console, *args; 526 int is_console; 527 528 if (verbose) { 529 (void) printf("old:"); 530 (void) print_bootadm_value(name, 0); 531 } 532 533 /* 534 * For security, we single-quote whatever we run on the command line, 535 * and we don't allow single quotes in the string. 536 */ 537 if (strchr(value, '\'') != NULL) { 538 eeprom_error("Single quotes are not allowed " 539 "in the %s property.\n", name); 540 return; 541 } 542 543 is_console = (strcmp(name, "console") == 0); 544 if (strcmp(name, "boot-file") == 0) { 545 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm set-menu " 546 "kernel='%s' 2>&1", value); 547 } else if (is_console || (strcmp(name, "boot-args") == 0)) { 548 if (is_console) { 549 args = get_bootadm_value("boot-args", 1); 550 console = value; 551 } else { 552 args = value; 553 console = get_bootadm_value("console", 1); 554 } 555 if (((args == NULL) || (args[0] == '\0')) && 556 ((console == NULL) || (console[0] == '\0'))) { 557 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm set-menu " 558 "args= 2>&1"); 559 } else if ((args == NULL) || (args[0] == '\0')) { 560 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 561 "set-menu args='-B console=%s' 2>&1", 562 console); 563 } else if ((console == NULL) || (console[0] == '\0')) { 564 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 565 "set-menu args='%s' 2>&1", args); 566 } else if (strncmp(args, "-B ", 3) != 0) { 567 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 568 "set-menu args='-B console=%s %s' 2>&1", 569 console, args); 570 } else { 571 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 572 "set-menu args='-B console=%s,%s' 2>&1", 573 console, args + 3); 574 } 575 } else { 576 eeprom_error("Unknown value in set_bootadm_value: %s\n", name); 577 return; 578 } 579 580 if (exec_cmd(buf, output, BUFSIZ) != 0) { 581 output_error_msg(output); 582 return; 583 } 584 585 if (verbose) { 586 (void) printf("new:"); 587 (void) print_bootadm_value(name, 0); 588 } 589 } 590 591 /* 592 * Returns 1 if bootenv.rc was modified, 0 otherwise. 593 */ 594 static int 595 set_var(char *name, char *val, eplist_t *list) 596 { 597 benv_ent_t *p; 598 int old_verbose; 599 600 if ((strcmp(name, "boot-file") == 0) || 601 (strcmp(name, "boot-args") == 0)) { 602 set_bootadm_var(name, val); 603 return (0); 604 } 605 606 /* 607 * The console property is kept in two places: menu.lst and bootenv.rc. 608 * Update them both. We clear verbose to prevent duplicate messages. 609 */ 610 if (strcmp(name, "console") == 0) { 611 old_verbose = verbose; 612 verbose = 0; 613 set_bootadm_var(name, val); 614 verbose = old_verbose; 615 } 616 617 if (verbose) { 618 (void) printf("old:"); 619 print_var(name, list); 620 } 621 622 if ((p = get_var(name, list)) != NULL) { 623 free(p->val); 624 p->val = strdup(val); 625 } else 626 add_bent(list, NULL, "setprop", name, val); 627 628 if (verbose) { 629 (void) printf("new:"); 630 print_var(name, list); 631 } 632 return (1); 633 } 634 635 /* 636 * Returns 1 if bootenv.rc is modified or 0 if no modification was 637 * necessary. This allows us to implement non super-user look-up of 638 * variables by name without the user being yelled at for trying to 639 * modify the bootenv.rc file. 640 */ 641 static int 642 proc_var(char *name, eplist_t *list) 643 { 644 register char *val; 645 646 if ((val = strchr(name, '=')) == NULL) { 647 print_var(name, list); 648 return (0); 649 } else { 650 *val++ = '\0'; 651 return (set_var(name, val, list)); 652 } 653 } 654 655 static void 656 init_benv(benv_des_t *bd, char *file) 657 { 658 get_kbenv(); 659 660 if (test) 661 boottree = "/tmp"; 662 else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL) 663 boottree = strcats("/boot", NULL); 664 665 if (file != NULL) 666 bd->name = file; 667 else 668 bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL); 669 } 670 671 static void 672 map_benv(benv_des_t *bd) 673 { 674 if ((bd->fd = open(bd->name, O_RDONLY)) == -1) 675 if (errno == ENOENT) 676 return; 677 else 678 exit(_error(PERROR, "cannot open %s", bd->name)); 679 680 if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) { 681 if (close(bd->fd) == -1) 682 exit(_error(PERROR, "close error on %s", bd->name)); 683 return; 684 } 685 686 (void) lseek(bd->fd, 0, SEEK_SET); 687 688 if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE), 689 MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED) 690 exit(_error(PERROR, "cannot map %s", bd->name)); 691 } 692 693 static void 694 unmap_benv(benv_des_t *bd) 695 { 696 if (munmap(bd->adr, bd->len) == -1) 697 exit(_error(PERROR, "unmap error on %s", bd->name)); 698 699 if (close(bd->fd) == -1) 700 exit(_error(PERROR, "close error on %s", bd->name)); 701 } 702 703 #define NL '\n' 704 #define COMM '#' 705 706 /* 707 * Add a comment block to the benv list. 708 */ 709 static void 710 add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line) 711 { 712 int nl, lines; 713 char *p; 714 715 nl = 0; 716 for (p = base, lines = 0; p < last; p++) { 717 if (*p == NL) { 718 nl++; 719 lines++; 720 } else if (nl) { 721 if (*p != COMM) 722 break; 723 nl = 0; 724 } 725 } 726 *(p - 1) = NULL; 727 add_bent(bd->elist, base, NULL, NULL, NULL); 728 *next = p; 729 *line += lines; 730 } 731 732 /* 733 * Parse out an operator (setprop) from the boot environment 734 */ 735 static char * 736 parse_cmd(benv_des_t *bd, char **next, int *line) 737 { 738 char *strbegin; 739 char *badeof = "unexpected EOF in %s line %d"; 740 char *syntax = "syntax error in %s line %d"; 741 char *c = *next; 742 743 /* 744 * Skip spaces or tabs. New lines increase the line count. 745 */ 746 while (isspace(*c)) { 747 if (*c++ == '\n') 748 (*line)++; 749 } 750 751 /* 752 * Check for a the setprop command. Currently that's all we 753 * seem to support. 754 * 755 * XXX need support for setbinprop? 756 */ 757 758 /* 759 * Check first for end of file. Finding one now would be okay. 760 * We should also bail if we are at the start of a comment. 761 */ 762 if (*c == '\0' || *c == COMM) { 763 *next = c; 764 return (NULL); 765 } 766 767 strbegin = c; 768 while (*c && !isspace(*c)) 769 c++; 770 771 /* 772 * Check again for end of file. Finding one now would NOT be okay. 773 */ 774 if (*c == '\0') { 775 exit(_error(NO_PERROR, badeof, bd->name, *line)); 776 } 777 778 *c++ = '\0'; 779 *next = c; 780 781 /* 782 * Last check is to make sure the command is a setprop! 783 */ 784 if (strcmp(strbegin, "setprop") != 0) { 785 exit(_error(NO_PERROR, syntax, bd->name, *line)); 786 /* NOTREACHED */ 787 } 788 return (strbegin); 789 } 790 791 /* 792 * Parse out the name (LHS) of a setprop from the boot environment 793 */ 794 static char * 795 parse_name(benv_des_t *bd, char **next, int *line) 796 { 797 char *strbegin; 798 char *badeof = "unexpected EOF in %s line %d"; 799 char *syntax = "syntax error in %s line %d"; 800 char *c = *next; 801 802 /* 803 * Skip spaces or tabs. No tolerance for new lines now. 804 */ 805 while (isspace(*c)) { 806 if (*c++ == '\n') 807 exit(_error(NO_PERROR, syntax, bd->name, *line)); 808 } 809 810 /* 811 * Grab a name for the property to set. 812 */ 813 814 /* 815 * Check first for end of file. Finding one now would NOT be okay. 816 */ 817 if (*c == '\0') { 818 exit(_error(NO_PERROR, badeof, bd->name, *line)); 819 } 820 821 strbegin = c; 822 while (*c && !isspace(*c)) 823 c++; 824 825 /* 826 * At this point in parsing we have 'setprop name'. What follows 827 * is a newline, other whitespace, or EOF. Most of the time we 828 * want to replace a white space character with a NULL to terminate 829 * the name, and then continue on processing. A newline here provides 830 * the most grief. If we just replace it with a null we'll 831 * potentially get the setprop on the next line as the value of this 832 * setprop! So, if the last thing we see is a newline we'll have to 833 * dup the string. 834 */ 835 if (isspace(*c)) { 836 if (*c == '\n') { 837 *c = '\0'; 838 strbegin = strdup(strbegin); 839 *c = '\n'; 840 } else { 841 *c++ = '\0'; 842 } 843 } 844 845 *next = c; 846 return (strbegin); 847 } 848 849 /* 850 * Parse out the value (RHS) of a setprop line from the boot environment 851 */ 852 static char * 853 parse_value(benv_des_t *bd, char **next, int *line) 854 { 855 char *strbegin; 856 char *badeof = "unexpected EOF in %s line %d"; 857 char *result; 858 char *c = *next; 859 char quote; 860 861 /* 862 * Skip spaces or tabs. A newline here would indicate a 863 * NULL property value. 864 */ 865 while (isspace(*c)) { 866 if (*c++ == '\n') { 867 (*line)++; 868 *next = c; 869 return (NULL); 870 } 871 } 872 873 /* 874 * Grab the value of the property to set. 875 */ 876 877 /* 878 * Check first for end of file. Finding one now would 879 * also indicate a NULL property. 880 */ 881 if (*c == '\0') { 882 *next = c; 883 return (NULL); 884 } 885 886 /* 887 * Value may be quoted, in which case we assume the end of the value 888 * comes with a closing quote. 889 * 890 * We also allow escaped quote characters inside the quoted value. 891 * 892 * For obvious reasons we do not attempt to parse variable references. 893 */ 894 if (*c == '"' || *c == '\'') { 895 quote = *c; 896 c++; 897 strbegin = c; 898 result = c; 899 while (*c != quote) { 900 if (*c == '\\') { 901 c++; 902 } 903 if (*c == '\0') { 904 break; 905 } 906 *result++ = *c++; 907 } 908 909 /* 910 * Throw fatal exception if no end quote found. 911 */ 912 if (*c != quote) { 913 exit(_error(NO_PERROR, badeof, bd->name, *line)); 914 } 915 916 *result = '\0'; /* Terminate the result */ 917 c++; /* and step past the close quote */ 918 } else { 919 strbegin = c; 920 while (*c && !isspace(*c)) 921 c++; 922 } 923 924 /* 925 * Check again for end of file. Finding one now is okay. 926 */ 927 if (*c == '\0') { 928 *next = c; 929 return (strbegin); 930 } 931 932 *c++ = '\0'; 933 *next = c; 934 return (strbegin); 935 } 936 937 /* 938 * Add a command to the benv list. 939 */ 940 static void 941 add_cmd(benv_des_t *bd, char *last, char **next, int *line) 942 { 943 char *cmd, *name, *val; 944 945 while (*next <= last && **next != COMM) { 946 if ((cmd = parse_cmd(bd, next, line)) == NULL) 947 break; 948 name = parse_name(bd, next, line); 949 val = parse_value(bd, next, line); 950 add_bent(bd->elist, NULL, cmd, name, val); 951 (*line)++; 952 }; 953 954 add_bent(bd->elist, NULL, "getprop", "bootcmd", getbootcmd()); 955 } 956 957 /* 958 * Parse the benv (bootenv.rc) file and break it into a benv 959 * list. List entries may be comment blocks or commands. 960 */ 961 static void 962 parse_benv(benv_des_t *bd) 963 { 964 int line; 965 char *pbase, *pend; 966 char *tok, *tnext; 967 968 line = 1; 969 pbase = (char *)bd->adr; 970 pend = pbase + bd->len; 971 972 for (tok = tnext = pbase; tnext < pend && '\0' != *tnext; tok = tnext) 973 if (*tok == COMM) 974 add_comm(bd, tok, pend, &tnext, &line); 975 else 976 add_cmd(bd, pend, &tnext, &line); 977 } 978 979 static void 980 write_benv(benv_des_t *bd) 981 { 982 FILE *fp; 983 eplist_t *list, *e; 984 benv_ent_t *bent; 985 char *name; 986 987 list = bd->elist; 988 989 if (list->next == list) 990 return; 991 992 if ((fp = fopen(bd->name, "w")) == NULL) 993 exit(_error(PERROR, "cannot open %s", bd->name)); 994 995 for (e = list->next; e != list; e = e->next) { 996 bent = (benv_ent_t *)e->item; 997 name = bent->name; 998 if (name) { 999 if (bent->val) { 1000 (void) fprintf(fp, "%s %s ", 1001 bent->cmd, bent->name); 1002 put_quoted(fp, bent->val); 1003 (void) fprintf(fp, "\n"); 1004 } else { 1005 (void) fprintf(fp, "%s %s\n", 1006 bent->cmd, bent->name); 1007 } 1008 } else { 1009 (void) fprintf(fp, "%s\n", bent->cmd); 1010 } 1011 } 1012 1013 (void) fclose(fp); 1014 } 1015 1016 static char * 1017 get_line(void) 1018 { 1019 int c; 1020 char *nl; 1021 static char line[256]; 1022 1023 if (fgets(line, sizeof (line), stdin) != NULL) { 1024 /* 1025 * Remove newline if present, 1026 * otherwise discard rest of line. 1027 */ 1028 if (nl = strchr(line, '\n')) 1029 *nl = 0; 1030 else 1031 while ((c = getchar()) != '\n' && c != EOF) 1032 ; 1033 return (line); 1034 } else 1035 return (NULL); 1036 } 1037 1038 int 1039 main(int argc, char **argv) 1040 { 1041 int c; 1042 int updates = 0; 1043 char *usage = "Usage: %s [-v] [-f prom-device]" 1044 " [variable[=value] ...]"; 1045 eplist_t *elist; 1046 benv_des_t *bd; 1047 char *file = NULL; 1048 1049 setprogname(argv[0]); 1050 1051 while ((c = getopt(argc, argv, "f:Itv")) != -1) 1052 switch (c) { 1053 case 'v': 1054 verbose++; 1055 break; 1056 case 'f': 1057 file = optarg; 1058 break; 1059 case 't': 1060 test++; 1061 break; 1062 default: 1063 exit(_error(NO_PERROR, usage, argv[0])); 1064 } 1065 1066 (void) uname(&uts_buf); 1067 bd = new_bd(); 1068 init_benv(bd, file); 1069 1070 map_benv(bd); 1071 if (bd->len) { 1072 parse_benv(bd); 1073 unmap_benv(bd); 1074 } 1075 1076 elist = bd->elist; 1077 1078 if (optind >= argc) { 1079 print_vars(elist); 1080 return (0); 1081 } else 1082 while (optind < argc) { 1083 /* 1084 * If "-" specified, read variables from stdin; 1085 * otherwise, process each argument as a variable 1086 * print or set request. 1087 */ 1088 if (strcmp(argv[optind], "-") == 0) { 1089 char *line; 1090 1091 while ((line = get_line()) != NULL) 1092 updates += proc_var(line, elist); 1093 clearerr(stdin); 1094 } else 1095 updates += proc_var(argv[optind], elist); 1096 1097 optind++; 1098 } 1099 1100 /* 1101 * don't write benv if we are processing delayed writes since 1102 * it is likely that the delayed writes changes bootenv.rc anyway... 1103 */ 1104 if (updates) 1105 write_benv(bd); 1106 close_kbenv(); 1107 1108 return (0); 1109 } 1110