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