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