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