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