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 }