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