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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. 23 */ 24 25 #include "benv.h" 26 #include "message.h" 27 #include <ctype.h> 28 #include <stdarg.h> 29 #include <sys/mman.h> 30 #include <unistd.h> 31 #include <signal.h> 32 #include <sys/wait.h> 33 34 /* 35 * Usage: % eeprom [-v] [-f prom_dev] [-] 36 * % eeprom [-v] [-f prom_dev] field[=value] ... 37 */ 38 39 extern void get_kbenv(void); 40 extern void close_kbenv(void); 41 extern caddr_t get_propval(char *name, char *node); 42 extern void setprogname(char *prog); 43 extern char *getbootcmd(void); 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 char *bootcmd; 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, "bootcmd") == 0) { 457 bootcmd = getbootcmd(); 458 (void) printf("%s=%s\n", name, bootcmd ? bootcmd : ""); 459 } else if ((strcmp(name, "boot-file") == 0) || 460 (strcmp(name, "boot-args") == 0)) { 461 (void) print_bootadm_value(name, 0); 462 } else if ((p = get_var(name, list)) == NULL) { 463 (void) printf("%s: data not available.\n", name); 464 } else { 465 (void) printf("%s=%s\n", name, p->val ? p->val : ""); 466 } 467 } 468 469 static void 470 print_vars(eplist_t *list) 471 { 472 eplist_t *e; 473 benv_ent_t *p; 474 int console_printed = 0; 475 476 /* 477 * The console property is kept both in menu.lst and bootenv.rc. 478 * The menu.lst value takes precedence, so try printing that one 479 * first. 480 */ 481 console_printed = print_bootadm_value("console", 1); 482 483 for (e = list->next; e != list; e = e->next) { 484 p = (benv_ent_t *)e->item; 485 if (p->name != NULL) { 486 if (((strcmp(p->name, "console") == 0) && 487 (console_printed == 1)) || 488 ((strcmp(p->name, "boot-file") == 0) || 489 (strcmp(p->name, "boot-args") == 0))) { 490 /* handle these separately */ 491 continue; 492 } 493 (void) printf("%s=%s\n", p->name, p->val ? p->val : ""); 494 } 495 } 496 (void) print_bootadm_value("boot-file", 1); 497 (void) print_bootadm_value("boot-args", 1); 498 } 499 500 /* 501 * Write a string to a file, quoted appropriately. We use single 502 * quotes to prevent any variable expansion. Of course, we backslash-quote 503 * any single quotes or backslashes. 504 */ 505 static void 506 put_quoted(FILE *fp, char *val) 507 { 508 (void) putc('\'', fp); 509 while (*val) { 510 switch (*val) { 511 case '\'': 512 case '\\': 513 (void) putc('\\', fp); 514 /* FALLTHROUGH */ 515 default: 516 (void) putc(*val, fp); 517 break; 518 } 519 val++; 520 } 521 (void) putc('\'', fp); 522 } 523 524 static void 525 set_bootadm_var(char *name, char *value) 526 { 527 char buf[BUFSIZ]; 528 char output[BUFSIZ] = ""; 529 char *console, *args; 530 int is_console; 531 532 if (verbose) { 533 (void) printf("old:"); 534 (void) print_bootadm_value(name, 0); 535 } 536 537 /* 538 * For security, we single-quote whatever we run on the command line, 539 * and we don't allow single quotes in the string. 540 */ 541 if (strchr(value, '\'') != NULL) { 542 eeprom_error("Single quotes are not allowed " 543 "in the %s property.\n", name); 544 return; 545 } 546 547 is_console = (strcmp(name, "console") == 0); 548 if (strcmp(name, "boot-file") == 0) { 549 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm set-menu " 550 "kernel='%s' 2>&1", value); 551 } else if (is_console || (strcmp(name, "boot-args") == 0)) { 552 if (is_console) { 553 args = get_bootadm_value("boot-args", 1); 554 console = value; 555 } else { 556 args = value; 557 console = get_bootadm_value("console", 1); 558 } 559 if (((args == NULL) || (args[0] == '\0')) && 560 ((console == NULL) || (console[0] == '\0'))) { 561 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm set-menu " 562 "args= 2>&1"); 563 } else if ((args == NULL) || (args[0] == '\0')) { 564 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 565 "set-menu args='-B console=%s' 2>&1", 566 console); 567 } else if ((console == NULL) || (console[0] == '\0')) { 568 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 569 "set-menu args='%s' 2>&1", args); 570 } else if (strncmp(args, "-B ", 3) != 0) { 571 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 572 "set-menu args='-B console=%s %s' 2>&1", 573 console, args); 574 } else { 575 (void) snprintf(buf, BUFSIZ, "/sbin/bootadm " 576 "set-menu args='-B console=%s,%s' 2>&1", 577 console, args + 3); 578 } 579 } else { 580 eeprom_error("Unknown value in set_bootadm_value: %s\n", name); 581 return; 582 } 583 584 if (exec_cmd(buf, output, BUFSIZ) != 0) { 585 output_error_msg(output); 586 return; 587 } 588 589 if (verbose) { 590 (void) printf("new:"); 591 (void) print_bootadm_value(name, 0); 592 } 593 } 594 595 /* 596 * Returns 1 if bootenv.rc was modified, 0 otherwise. 597 */ 598 static int 599 set_var(char *name, char *val, eplist_t *list) 600 { 601 benv_ent_t *p; 602 int old_verbose; 603 604 if (strcmp(name, "bootcmd") == 0) 605 return (0); 606 607 if ((strcmp(name, "boot-file") == 0) || 608 (strcmp(name, "boot-args") == 0)) { 609 set_bootadm_var(name, val); 610 return (0); 611 } 612 613 /* 614 * The console property is kept in two places: menu.lst and bootenv.rc. 615 * Update them both. We clear verbose to prevent duplicate messages. 616 */ 617 if (strcmp(name, "console") == 0) { 618 old_verbose = verbose; 619 verbose = 0; 620 set_bootadm_var(name, val); 621 verbose = old_verbose; 622 } 623 624 if (verbose) { 625 (void) printf("old:"); 626 print_var(name, list); 627 } 628 629 if ((p = get_var(name, list)) != NULL) { 630 free(p->val); 631 p->val = strdup(val); 632 } else 633 add_bent(list, NULL, "setprop", name, val); 634 635 if (verbose) { 636 (void) printf("new:"); 637 print_var(name, list); 638 } 639 return (1); 640 } 641 642 /* 643 * Returns 1 if bootenv.rc is modified or 0 if no modification was 644 * necessary. This allows us to implement non super-user look-up of 645 * variables by name without the user being yelled at for trying to 646 * modify the bootenv.rc file. 647 */ 648 static int 649 proc_var(char *name, eplist_t *list) 650 { 651 register char *val; 652 653 if ((val = strchr(name, '=')) == NULL) { 654 print_var(name, list); 655 return (0); 656 } else { 657 *val++ = '\0'; 658 return (set_var(name, val, list)); 659 } 660 } 661 662 static void 663 init_benv(benv_des_t *bd, char *file) 664 { 665 get_kbenv(); 666 667 if (test) 668 boottree = "/tmp"; 669 else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL) 670 boottree = strcats("/boot", NULL); 671 672 if (file != NULL) 673 bd->name = file; 674 else 675 bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL); 676 } 677 678 static void 679 map_benv(benv_des_t *bd) 680 { 681 if ((bd->fd = open(bd->name, O_RDONLY)) == -1) 682 if (errno == ENOENT) 683 return; 684 else 685 exit(_error(PERROR, "cannot open %s", bd->name)); 686 687 if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) { 688 if (close(bd->fd) == -1) 689 exit(_error(PERROR, "close error on %s", bd->name)); 690 return; 691 } 692 693 (void) lseek(bd->fd, 0, SEEK_SET); 694 695 if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE), 696 MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED) 697 exit(_error(PERROR, "cannot map %s", bd->name)); 698 } 699 700 static void 701 unmap_benv(benv_des_t *bd) 702 { 703 if (munmap(bd->adr, bd->len) == -1) 704 exit(_error(PERROR, "unmap error on %s", bd->name)); 705 706 if (close(bd->fd) == -1) 707 exit(_error(PERROR, "close error on %s", bd->name)); 708 } 709 710 #define NL '\n' 711 #define COMM '#' 712 713 /* 714 * Add a comment block to the benv list. 715 */ 716 static void 717 add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line) 718 { 719 int nl, lines; 720 char *p; 721 722 nl = 0; 723 for (p = base, lines = 0; p < last; p++) { 724 if (*p == NL) { 725 nl++; 726 lines++; 727 } else if (nl) { 728 if (*p != COMM) 729 break; 730 nl = 0; 731 } 732 } 733 *(p - 1) = NULL; 734 add_bent(bd->elist, base, NULL, NULL, NULL); 735 *next = p; 736 *line += lines; 737 } 738 739 /* 740 * Parse out an operator (setprop) from the boot environment 741 */ 742 static char * 743 parse_cmd(benv_des_t *bd, char **next, int *line) 744 { 745 char *strbegin; 746 char *badeof = "unexpected EOF in %s line %d"; 747 char *syntax = "syntax error in %s line %d"; 748 char *c = *next; 749 750 /* 751 * Skip spaces or tabs. New lines increase the line count. 752 */ 753 while (isspace(*c)) { 754 if (*c++ == '\n') 755 (*line)++; 756 } 757 758 /* 759 * Check for a the setprop command. Currently that's all we 760 * seem to support. 761 * 762 * XXX need support for setbinprop? 763 */ 764 765 /* 766 * Check first for end of file. Finding one now would be okay. 767 * We should also bail if we are at the start of a comment. 768 */ 769 if (*c == '\0' || *c == COMM) { 770 *next = c; 771 return (NULL); 772 } 773 774 strbegin = c; 775 while (*c && !isspace(*c)) 776 c++; 777 778 /* 779 * Check again for end of file. Finding one now would NOT be okay. 780 */ 781 if (*c == '\0') { 782 exit(_error(NO_PERROR, badeof, bd->name, *line)); 783 } 784 785 *c++ = '\0'; 786 *next = c; 787 788 /* 789 * Last check is to make sure the command is a setprop! 790 */ 791 if (strcmp(strbegin, "setprop") != 0) { 792 exit(_error(NO_PERROR, syntax, bd->name, *line)); 793 /* NOTREACHED */ 794 } 795 return (strbegin); 796 } 797 798 /* 799 * Parse out the name (LHS) of a setprop from the boot environment 800 */ 801 static char * 802 parse_name(benv_des_t *bd, char **next, int *line) 803 { 804 char *strbegin; 805 char *badeof = "unexpected EOF in %s line %d"; 806 char *syntax = "syntax error in %s line %d"; 807 char *c = *next; 808 809 /* 810 * Skip spaces or tabs. No tolerance for new lines now. 811 */ 812 while (isspace(*c)) { 813 if (*c++ == '\n') 814 exit(_error(NO_PERROR, syntax, bd->name, *line)); 815 } 816 817 /* 818 * Grab a name for the property to set. 819 */ 820 821 /* 822 * Check first for end of file. Finding one now would NOT be okay. 823 */ 824 if (*c == '\0') { 825 exit(_error(NO_PERROR, badeof, bd->name, *line)); 826 } 827 828 strbegin = c; 829 while (*c && !isspace(*c)) 830 c++; 831 832 /* 833 * At this point in parsing we have 'setprop name'. What follows 834 * is a newline, other whitespace, or EOF. Most of the time we 835 * want to replace a white space character with a NULL to terminate 836 * the name, and then continue on processing. A newline here provides 837 * the most grief. If we just replace it with a null we'll 838 * potentially get the setprop on the next line as the value of this 839 * setprop! So, if the last thing we see is a newline we'll have to 840 * dup the string. 841 */ 842 if (isspace(*c)) { 843 if (*c == '\n') { 844 *c = '\0'; 845 strbegin = strdup(strbegin); 846 *c = '\n'; 847 } else { 848 *c++ = '\0'; 849 } 850 } 851 852 *next = c; 853 return (strbegin); 854 } 855 856 /* 857 * Parse out the value (RHS) of a setprop line from the boot environment 858 */ 859 static char * 860 parse_value(benv_des_t *bd, char **next, int *line) 861 { 862 char *strbegin; 863 char *badeof = "unexpected EOF in %s line %d"; 864 char *result; 865 char *c = *next; 866 char quote; 867 868 /* 869 * Skip spaces or tabs. A newline here would indicate a 870 * NULL property value. 871 */ 872 while (isspace(*c)) { 873 if (*c++ == '\n') { 874 (*line)++; 875 *next = c; 876 return (NULL); 877 } 878 } 879 880 /* 881 * Grab the value of the property to set. 882 */ 883 884 /* 885 * Check first for end of file. Finding one now would 886 * also indicate a NULL property. 887 */ 888 if (*c == '\0') { 889 *next = c; 890 return (NULL); 891 } 892 893 /* 894 * Value may be quoted, in which case we assume the end of the value 895 * comes with a closing quote. 896 * 897 * We also allow escaped quote characters inside the quoted value. 898 * 899 * For obvious reasons we do not attempt to parse variable references. 900 */ 901 if (*c == '"' || *c == '\'') { 902 quote = *c; 903 c++; 904 strbegin = c; 905 result = c; 906 while (*c != quote) { 907 if (*c == '\\') { 908 c++; 909 } 910 if (*c == '\0') { 911 break; 912 } 913 *result++ = *c++; 914 } 915 916 /* 917 * Throw fatal exception if no end quote found. 918 */ 919 if (*c != quote) { 920 exit(_error(NO_PERROR, badeof, bd->name, *line)); 921 } 922 923 *result = '\0'; /* Terminate the result */ 924 c++; /* and step past the close quote */ 925 } else { 926 strbegin = c; 927 while (*c && !isspace(*c)) 928 c++; 929 } 930 931 /* 932 * Check again for end of file. Finding one now is okay. 933 */ 934 if (*c == '\0') { 935 *next = c; 936 return (strbegin); 937 } 938 939 *c++ = '\0'; 940 *next = c; 941 return (strbegin); 942 } 943 944 /* 945 * Add a command to the benv list. 946 */ 947 static void 948 add_cmd(benv_des_t *bd, char *last, char **next, int *line) 949 { 950 char *cmd, *name, *val; 951 952 while (*next <= last && **next != COMM) { 953 if ((cmd = parse_cmd(bd, next, line)) == NULL) 954 break; 955 name = parse_name(bd, next, line); 956 val = parse_value(bd, next, line); 957 add_bent(bd->elist, NULL, cmd, name, val); 958 (*line)++; 959 }; 960 961 } 962 963 /* 964 * Parse the benv (bootenv.rc) file and break it into a benv 965 * list. List entries may be comment blocks or commands. 966 */ 967 static void 968 parse_benv(benv_des_t *bd) 969 { 970 int line; 971 char *pbase, *pend; 972 char *tok, *tnext; 973 974 line = 1; 975 pbase = (char *)bd->adr; 976 pend = pbase + bd->len; 977 978 for (tok = tnext = pbase; tnext < pend && '\0' != *tnext; tok = tnext) 979 if (*tok == COMM) 980 add_comm(bd, tok, pend, &tnext, &line); 981 else 982 add_cmd(bd, pend, &tnext, &line); 983 } 984 985 static void 986 write_benv(benv_des_t *bd) 987 { 988 FILE *fp; 989 eplist_t *list, *e; 990 benv_ent_t *bent; 991 char *name; 992 993 list = bd->elist; 994 995 if (list->next == list) 996 return; 997 998 if ((fp = fopen(bd->name, "w")) == NULL) 999 exit(_error(PERROR, "cannot open %s", bd->name)); 1000 1001 for (e = list->next; e != list; e = e->next) { 1002 bent = (benv_ent_t *)e->item; 1003 name = bent->name; 1004 if (name) { 1005 if (bent->val) { 1006 (void) fprintf(fp, "%s %s ", 1007 bent->cmd, bent->name); 1008 put_quoted(fp, bent->val); 1009 (void) fprintf(fp, "\n"); 1010 } else { 1011 (void) fprintf(fp, "%s %s\n", 1012 bent->cmd, bent->name); 1013 } 1014 } else { 1015 (void) fprintf(fp, "%s\n", bent->cmd); 1016 } 1017 } 1018 1019 (void) fclose(fp); 1020 } 1021 1022 static char * 1023 get_line(void) 1024 { 1025 int c; 1026 char *nl; 1027 static char line[256]; 1028 1029 if (fgets(line, sizeof (line), stdin) != NULL) { 1030 /* 1031 * Remove newline if present, 1032 * otherwise discard rest of line. 1033 */ 1034 if (nl = strchr(line, '\n')) 1035 *nl = 0; 1036 else 1037 while ((c = getchar()) != '\n' && c != EOF) 1038 ; 1039 return (line); 1040 } else 1041 return (NULL); 1042 } 1043 1044 int 1045 main(int argc, char **argv) 1046 { 1047 int c; 1048 int updates = 0; 1049 char *usage = "Usage: %s [-v] [-f prom-device]" 1050 " [variable[=value] ...]"; 1051 eplist_t *elist; 1052 benv_des_t *bd; 1053 char *file = NULL; 1054 1055 setprogname(argv[0]); 1056 1057 while ((c = getopt(argc, argv, "f:Itv")) != -1) 1058 switch (c) { 1059 case 'v': 1060 verbose++; 1061 break; 1062 case 'f': 1063 file = optarg; 1064 break; 1065 case 't': 1066 test++; 1067 break; 1068 default: 1069 exit(_error(NO_PERROR, usage, argv[0])); 1070 } 1071 1072 (void) uname(&uts_buf); 1073 bd = new_bd(); 1074 init_benv(bd, file); 1075 1076 map_benv(bd); 1077 if (bd->len) { 1078 parse_benv(bd); 1079 unmap_benv(bd); 1080 } 1081 1082 elist = bd->elist; 1083 1084 if (optind >= argc) { 1085 print_vars(elist); 1086 return (0); 1087 } else 1088 while (optind < argc) { 1089 /* 1090 * If "-" specified, read variables from stdin; 1091 * otherwise, process each argument as a variable 1092 * print or set request. 1093 */ 1094 if (strcmp(argv[optind], "-") == 0) { 1095 char *line; 1096 1097 while ((line = get_line()) != NULL) 1098 updates += proc_var(line, elist); 1099 clearerr(stdin); 1100 } else 1101 updates += proc_var(argv[optind], elist); 1102 1103 optind++; 1104 } 1105 1106 /* 1107 * don't write benv if we are processing delayed writes since 1108 * it is likely that the delayed writes changes bootenv.rc anyway... 1109 */ 1110 if (updates) 1111 write_benv(bd); 1112 close_kbenv(); 1113 1114 return (0); 1115 } 1116