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