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