xref: /illumos-gate/usr/src/cmd/eeprom/i386/benv.c (revision 14d44f2248cc2a54490db7f7caa4da5968f90837)
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	BOOTADM_STR	"bootadm: "
304 
305 /*
306  * bootadm starts all error messages with "bootadm: ".
307  * Add a note so users don't get confused on how they ran bootadm.
308  */
309 static void
310 output_error_msg(const char *msg)
311 {
312 	size_t len = sizeof (BOOTADM_STR) - 1;
313 
314 	if (strncmp(msg, BOOTADM_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_bootadm_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/bootadm set-menu kernel 2>&1";
334 	} else if (is_console || (strcmp(name, "boot-args") == 0)) {
335 		ptr = "/sbin/bootadm set-menu args 2>&1";
336 	} else {
337 		eeprom_error("Unknown value in get_bootadm_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_bootadm_value(char *name, const int quiet)
421 {
422 	int rv = 0;
423 	char *value = get_bootadm_value(name, quiet);
424 
425 	if ((value != NULL) && (value[0] != '\0')) {
426 		(void) printf("%s=%s\n", name, value);
427 		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_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, "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_bootadm_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_bootadm_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_bootadm_value("boot-file", 1);
498 	(void) print_bootadm_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_bootadm_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_bootadm_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/bootadm set-menu "
551 		    "kernel='%s' 2>&1", value);
552 	} else if (is_console || (strcmp(name, "boot-args") == 0)) {
553 		if (is_console) {
554 			args = get_bootadm_value("boot-args", 1);
555 			console = value;
556 		} else {
557 			args = value;
558 			console = get_bootadm_value("console", 1);
559 		}
560 		if (((args == NULL) || (args[0] == '\0')) &&
561 		    ((console == NULL) || (console[0] == '\0'))) {
562 			(void) snprintf(buf, BUFSIZ, "/sbin/bootadm set-menu "
563 			    "args= 2>&1");
564 		} else if ((args == NULL) || (args[0] == '\0')) {
565 			(void) snprintf(buf, BUFSIZ, "/sbin/bootadm "
566 			    "set-menu args='-B console=%s' 2>&1",
567 			    console);
568 		} else if ((console == NULL) || (console[0] == '\0')) {
569 			(void) snprintf(buf, BUFSIZ, "/sbin/bootadm "
570 			    "set-menu args='%s' 2>&1", args);
571 		} else if (strncmp(args, "-B ", 3) != 0) {
572 			(void) snprintf(buf, BUFSIZ, "/sbin/bootadm "
573 			    "set-menu args='-B console=%s %s' 2>&1",
574 			    console, args);
575 		} else {
576 			(void) snprintf(buf, BUFSIZ, "/sbin/bootadm "
577 			    "set-menu args='-B console=%s,%s' 2>&1",
578 			    console, args + 3);
579 		}
580 	} else {
581 		eeprom_error("Unknown value in set_bootadm_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_bootadm_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_bootadm_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_bootadm_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 }
1117