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 /* 593 * Returns 1 if bootenv.rc was modified, 0 otherwise. 594 */ 595 static int 596 set_var(char *name, char *val, eplist_t *list) 597 { 598 benv_ent_t *p; 599 int old_verbose; 600 601 if ((strcmp(name, "boot-file") == 0) || 602 (strcmp(name, "boot-args") == 0)) { 603 set_bootadm_var(name, val); 604 return (0); 605 } 606 607 /* 608 * The console property is kept in two places: menu.lst and bootenv.rc. 609 * Update them both. We clear verbose to prevent duplicate messages. 610 */ 611 if (strcmp(name, "console") == 0) { 612 old_verbose = verbose; 613 verbose = 0; 614 set_bootadm_var(name, val); 615 verbose = old_verbose; 616 } 617 618 if (verbose) { 619 (void) printf("old:"); 620 print_var(name, list); 621 } 622 623 if ((p = get_var(name, list)) != NULL) { 624 free(p->val); 625 p->val = strdup(val); 626 } else 627 add_bent(list, NULL, "setprop", name, val); 628 629 if (verbose) { 630 (void) printf("new:"); 631 print_var(name, list); 632 } 633 return (1); 634 } 635 636 /* 637 * Returns 1 if bootenv.rc is modified or 0 if no modification was 638 * necessary. This allows us to implement non super-user look-up of 639 * variables by name without the user being yelled at for trying to 640 * modify the bootenv.rc file. 641 */ 642 static int 643 proc_var(char *name, eplist_t *list) 644 { 645 register char *val; 646 647 if ((val = strchr(name, '=')) == NULL) { 648 print_var(name, list); 649 return (0); 650 } else { 651 *val++ = '\0'; 652 return (set_var(name, val, list)); 653 } 654 } 655 656 static void 657 init_benv(benv_des_t *bd, char *file) 658 { 659 get_kbenv(); 660 661 if (test) 662 boottree = "/tmp"; 663 else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL) 664 boottree = strcats("/boot", NULL); 665 666 if (file != NULL) 667 bd->name = file; 668 else 669 bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL); 670 } 671 672 static void 673 map_benv(benv_des_t *bd) 674 { 675 if ((bd->fd = open(bd->name, O_RDONLY)) == -1) 676 if (errno == ENOENT) 677 return; 678 else 679 exit(_error(PERROR, "cannot open %s", bd->name)); 680 681 if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) { 682 if (close(bd->fd) == -1) 683 exit(_error(PERROR, "close error on %s", bd->name)); 684 return; 685 } 686 687 (void) lseek(bd->fd, 0, SEEK_SET); 688 689 if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE), 690 MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED) 691 exit(_error(PERROR, "cannot map %s", bd->name)); 692 } 693 694 static void 695 unmap_benv(benv_des_t *bd) 696 { 697 if (munmap(bd->adr, bd->len) == -1) 698 exit(_error(PERROR, "unmap error on %s", bd->name)); 699 700 if (close(bd->fd) == -1) 701 exit(_error(PERROR, "close error on %s", bd->name)); 702 } 703 704 #define NL '\n' 705 #define COMM '#' 706 707 /* 708 * Add a comment block to the benv list. 709 */ 710 static void 711 add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line) 712 { 713 int nl, lines; 714 char *p; 715 716 nl = 0; 717 for (p = base, lines = 0; p < last; p++) { 718 if (*p == NL) { 719 nl++; 720 lines++; 721 } else if (nl) { 722 if (*p != COMM) 723 break; 724 nl = 0; 725 } 726 } 727 *(p - 1) = NULL; 728 add_bent(bd->elist, base, NULL, NULL, NULL); 729 *next = p; 730 *line += lines; 731 } 732 733 /* 734 * Parse out an operator (setprop) from the boot environment 735 */ 736 static char * 737 parse_cmd(benv_des_t *bd, char **next, int *line) 738 { 739 char *strbegin; 740 char *badeof = "unexpected EOF in %s line %d"; 741 char *syntax = "syntax error in %s line %d"; 742 char *c = *next; 743 744 /* 745 * Skip spaces or tabs. New lines increase the line count. 746 */ 747 while (isspace(*c)) { 748 if (*c++ == '\n') 749 (*line)++; 750 } 751 752 /* 753 * Check for a the setprop command. Currently that's all we 754 * seem to support. 755 * 756 * XXX need support for setbinprop? 757 */ 758 759 /* 760 * Check first for end of file. Finding one now would be okay. 761 * We should also bail if we are at the start of a comment. 762 */ 763 if (*c == '\0' || *c == COMM) { 764 *next = c; 765 return (NULL); 766 } 767 768 strbegin = c; 769 while (*c && !isspace(*c)) 770 c++; 771 772 /* 773 * Check again for end of file. Finding one now would NOT be okay. 774 */ 775 if (*c == '\0') { 776 exit(_error(NO_PERROR, badeof, bd->name, *line)); 777 } 778 779 *c++ = '\0'; 780 *next = c; 781 782 /* 783 * Last check is to make sure the command is a setprop! 784 */ 785 if (strcmp(strbegin, "setprop") != 0) { 786 exit(_error(NO_PERROR, syntax, bd->name, *line)); 787 /* NOTREACHED */ 788 } 789 return (strbegin); 790 } 791 792 /* 793 * Parse out the name (LHS) of a setprop from the boot environment 794 */ 795 static char * 796 parse_name(benv_des_t *bd, char **next, int *line) 797 { 798 char *strbegin; 799 char *badeof = "unexpected EOF in %s line %d"; 800 char *syntax = "syntax error in %s line %d"; 801 char *c = *next; 802 803 /* 804 * Skip spaces or tabs. No tolerance for new lines now. 805 */ 806 while (isspace(*c)) { 807 if (*c++ == '\n') 808 exit(_error(NO_PERROR, syntax, bd->name, *line)); 809 } 810 811 /* 812 * Grab a name for the property to set. 813 */ 814 815 /* 816 * Check first for end of file. Finding one now would NOT be okay. 817 */ 818 if (*c == '\0') { 819 exit(_error(NO_PERROR, badeof, bd->name, *line)); 820 } 821 822 strbegin = c; 823 while (*c && !isspace(*c)) 824 c++; 825 826 /* 827 * At this point in parsing we have 'setprop name'. What follows 828 * is a newline, other whitespace, or EOF. Most of the time we 829 * want to replace a white space character with a NULL to terminate 830 * the name, and then continue on processing. A newline here provides 831 * the most grief. If we just replace it with a null we'll 832 * potentially get the setprop on the next line as the value of this 833 * setprop! So, if the last thing we see is a newline we'll have to 834 * dup the string. 835 */ 836 if (isspace(*c)) { 837 if (*c == '\n') { 838 *c = '\0'; 839 strbegin = strdup(strbegin); 840 *c = '\n'; 841 } else { 842 *c++ = '\0'; 843 } 844 } 845 846 *next = c; 847 return (strbegin); 848 } 849 850 /* 851 * Parse out the value (RHS) of a setprop line from the boot environment 852 */ 853 static char * 854 parse_value(benv_des_t *bd, char **next, int *line) 855 { 856 char *strbegin; 857 char *badeof = "unexpected EOF in %s line %d"; 858 char *result; 859 char *c = *next; 860 char quote; 861 862 /* 863 * Skip spaces or tabs. A newline here would indicate a 864 * NULL property value. 865 */ 866 while (isspace(*c)) { 867 if (*c++ == '\n') { 868 (*line)++; 869 *next = c; 870 return (NULL); 871 } 872 } 873 874 /* 875 * Grab the value of the property to set. 876 */ 877 878 /* 879 * Check first for end of file. Finding one now would 880 * also indicate a NULL property. 881 */ 882 if (*c == '\0') { 883 *next = c; 884 return (NULL); 885 } 886 887 /* 888 * Value may be quoted, in which case we assume the end of the value 889 * comes with a closing quote. 890 * 891 * We also allow escaped quote characters inside the quoted value. 892 * 893 * For obvious reasons we do not attempt to parse variable references. 894 */ 895 if (*c == '"' || *c == '\'') { 896 quote = *c; 897 c++; 898 strbegin = c; 899 result = c; 900 while (*c != quote) { 901 if (*c == '\\') { 902 c++; 903 } 904 if (*c == '\0' || *c == '\n') { 905 break; 906 } 907 *result++ = *c++; 908 } 909 910 /* 911 * Throw fatal exception if no end quote found. 912 */ 913 if (*c != quote) { 914 exit(_error(NO_PERROR, badeof, bd->name, *line)); 915 } 916 917 *result = '\0'; /* Terminate the result */ 918 c++; /* and step past the close quote */ 919 } else { 920 strbegin = c; 921 while (*c && !isspace(*c)) 922 c++; 923 } 924 925 /* 926 * Check again for end of file. Finding one now is okay. 927 */ 928 if (*c == '\0') { 929 *next = c; 930 return (strbegin); 931 } 932 933 *c++ = '\0'; 934 *next = c; 935 return (strbegin); 936 } 937 938 /* 939 * Add a command to the benv list. 940 */ 941 static void 942 add_cmd(benv_des_t *bd, char *last, char **next, int *line) 943 { 944 char *cmd, *name, *val; 945 946 while (*next <= last && **next != COMM) { 947 if ((cmd = parse_cmd(bd, next, line)) == NULL) 948 break; 949 name = parse_name(bd, next, line); 950 val = parse_value(bd, next, line); 951 add_bent(bd->elist, NULL, cmd, name, val); 952 (*line)++; 953 }; 954 } 955 956 /* 957 * Parse the benv (bootenv.rc) file and break it into a benv 958 * list. List entries may be comment blocks or commands. 959 */ 960 static void 961 parse_benv(benv_des_t *bd) 962 { 963 int line; 964 char *pbase, *pend; 965 char *tok, *tnext; 966 967 line = 1; 968 pbase = (char *)bd->adr; 969 pend = pbase + bd->len; 970 971 for (tok = tnext = pbase; tnext < pend; tok = tnext) 972 if (*tok == COMM) 973 add_comm(bd, tok, pend, &tnext, &line); 974 else 975 add_cmd(bd, pend, &tnext, &line); 976 } 977 978 static void 979 write_benv(benv_des_t *bd) 980 { 981 FILE *fp; 982 eplist_t *list, *e; 983 benv_ent_t *bent; 984 char *name; 985 986 list = bd->elist; 987 988 if (list->next == list) 989 return; 990 991 if ((fp = fopen(bd->name, "w")) == NULL) 992 exit(_error(PERROR, "cannot open %s", bd->name)); 993 994 for (e = list->next; e != list; e = e->next) { 995 bent = (benv_ent_t *)e->item; 996 name = bent->name; 997 if (name) { 998 if (bent->val) { 999 (void) fprintf(fp, "%s %s ", 1000 bent->cmd, bent->name); 1001 put_quoted(fp, bent->val); 1002 (void) fprintf(fp, "\n"); 1003 } else { 1004 (void) fprintf(fp, "%s %s\n", 1005 bent->cmd, bent->name); 1006 } 1007 } else { 1008 (void) fprintf(fp, "%s\n", bent->cmd); 1009 } 1010 } 1011 1012 (void) fclose(fp); 1013 } 1014 1015 static char * 1016 get_line(void) 1017 { 1018 int c; 1019 char *nl; 1020 static char line[256]; 1021 1022 if (fgets(line, sizeof (line), stdin) != NULL) { 1023 /* 1024 * Remove newline if present, 1025 * otherwise discard rest of line. 1026 */ 1027 if (nl = strchr(line, '\n')) 1028 *nl = 0; 1029 else 1030 while ((c = getchar()) != '\n' && c != EOF) 1031 ; 1032 return (line); 1033 } else 1034 return (NULL); 1035 } 1036 1037 int 1038 main(int argc, char **argv) 1039 { 1040 int c; 1041 int updates = 0; 1042 char *usage = "Usage: %s [-v] [-f prom-device]" 1043 " [variable[=value] ...]"; 1044 eplist_t *elist; 1045 benv_des_t *bd; 1046 char *file = NULL; 1047 1048 setprogname(argv[0]); 1049 1050 while ((c = getopt(argc, argv, "f:Itv")) != -1) 1051 switch (c) { 1052 case 'v': 1053 verbose++; 1054 break; 1055 case 'f': 1056 file = optarg; 1057 break; 1058 case 't': 1059 test++; 1060 break; 1061 default: 1062 exit(_error(NO_PERROR, usage, argv[0])); 1063 } 1064 1065 (void) uname(&uts_buf); 1066 bd = new_bd(); 1067 init_benv(bd, file); 1068 1069 map_benv(bd); 1070 if (bd->len) { 1071 parse_benv(bd); 1072 unmap_benv(bd); 1073 } 1074 1075 elist = bd->elist; 1076 1077 if (optind >= argc) { 1078 print_vars(elist); 1079 return (0); 1080 } else 1081 while (optind < argc) { 1082 /* 1083 * If "-" specified, read variables from stdin; 1084 * otherwise, process each argument as a variable 1085 * print or set request. 1086 */ 1087 if (strcmp(argv[optind], "-") == 0) { 1088 char *line; 1089 1090 while ((line = get_line()) != NULL) 1091 updates += proc_var(line, elist); 1092 clearerr(stdin); 1093 } else 1094 updates += proc_var(argv[optind], elist); 1095 1096 optind++; 1097 } 1098 1099 /* 1100 * don't write benv if we are processing delayed writes since 1101 * it is likely that the delayed writes changes bootenv.rc anyway... 1102 */ 1103 if (updates) 1104 write_benv(bd); 1105 close_kbenv(); 1106 1107 return (0); 1108 } 1109