xref: /illumos-gate/usr/src/cmd/eeprom/i386/benv.c (revision fa79a855d371dfcb29461ad6ebaf48a458bf9f14)
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 2016 Toomas Soome <tsoome@me.com>
24  * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
25  */
26 
27 #include "benv.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 static void
190 print_var(char *name, eplist_t *list)
191 {
192 	benv_ent_t *p;
193 	char *bootcmd;
194 
195 	if (strcmp(name, "bootcmd") == 0) {
196 		bootcmd = getbootcmd();
197 		(void) printf("%s=%s\n", name, bootcmd ? bootcmd : "");
198 	} else if ((p = get_var(name, list)) == NULL) {
199 		(void) printf("%s: data not available.\n", name);
200 	} else {
201 		(void) printf("%s=%s\n", name, p->val ? p->val : "");
202 	}
203 }
204 
205 static void
206 print_vars(eplist_t *list)
207 {
208 	eplist_t *e;
209 	benv_ent_t *p;
210 
211 	for (e = list->next; e != list; e = e->next) {
212 		p = (benv_ent_t *)e->item;
213 		if (p->name != NULL) {
214 			(void) printf("%s=%s\n", p->name, p->val ? p->val : "");
215 		}
216 	}
217 }
218 
219 /*
220  * Write a string to a file, quoted appropriately.  We use single
221  * quotes to prevent any variable expansion.  Of course, we backslash-quote
222  * any single quotes or backslashes.
223  */
224 static void
225 put_quoted(FILE *fp, char *val)
226 {
227 	(void) putc('\'', fp);
228 	while (*val) {
229 		switch (*val) {
230 		case '\'':
231 		case '\\':
232 			(void) putc('\\', fp);
233 			/* FALLTHROUGH */
234 		default:
235 			(void) putc(*val, fp);
236 			break;
237 		}
238 		val++;
239 	}
240 	(void) putc('\'', fp);
241 }
242 
243 /*
244  * Returns 1 if bootenv.rc was modified, 0 otherwise.
245  */
246 static int
247 set_var(char *name, char *val, eplist_t *list)
248 {
249 	benv_ent_t *p;
250 
251 	if (strcmp(name, "bootcmd") == 0)
252 		return (0);
253 
254 	if (verbose) {
255 		(void) printf("old:");
256 		print_var(name, list);
257 	}
258 
259 	if ((p = get_var(name, list)) != NULL) {
260 		free(p->val);
261 		p->val = strdup(val);
262 	} else
263 		add_bent(list, NULL, "setprop", name, val);
264 
265 	if (verbose) {
266 		(void) printf("new:");
267 		print_var(name, list);
268 	}
269 	return (1);
270 }
271 
272 /*
273  * Returns 1 if bootenv.rc is modified or 0 if no modification was
274  * necessary.  This allows us to implement non super-user look-up of
275  * variables by name without the user being yelled at for trying to
276  * modify the bootenv.rc file.
277  */
278 static int
279 proc_var(char *name, eplist_t *list)
280 {
281 	register char *val;
282 
283 	if ((val = strchr(name, '=')) == NULL) {
284 		print_var(name, list);
285 		return (0);
286 	} else {
287 		*val++ = '\0';
288 		return (set_var(name, val, list));
289 	}
290 }
291 
292 static void
293 init_benv(benv_des_t *bd, char *file)
294 {
295 	get_kbenv();
296 
297 	if (test)
298 		boottree = "/tmp";
299 	else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL)
300 		boottree = strcats("/boot", NULL);
301 
302 	if (file != NULL)
303 		bd->name = file;
304 	else
305 		bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL);
306 }
307 
308 static void
309 map_benv(benv_des_t *bd)
310 {
311 	if ((bd->fd = open(bd->name, O_RDONLY)) == -1)
312 		if (errno == ENOENT)
313 			return;
314 		else
315 			exit(_error(PERROR, "cannot open %s", bd->name));
316 
317 	if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) {
318 		if (close(bd->fd) == -1)
319 			exit(_error(PERROR, "close error on %s", bd->name));
320 		return;
321 	}
322 
323 	(void) lseek(bd->fd, 0, SEEK_SET);
324 
325 	if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE),
326 	    MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED)
327 		exit(_error(PERROR, "cannot map %s", bd->name));
328 }
329 
330 static void
331 unmap_benv(benv_des_t *bd)
332 {
333 	if (munmap(bd->adr, bd->len) == -1)
334 		exit(_error(PERROR, "unmap error on %s", bd->name));
335 
336 	if (close(bd->fd) == -1)
337 		exit(_error(PERROR, "close error on %s", bd->name));
338 }
339 
340 #define	NL	'\n'
341 #define	COMM	'#'
342 
343 /*
344  * Add a comment block to the benv list.
345  */
346 static void
347 add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line)
348 {
349 	int nl, lines;
350 	char *p;
351 
352 	nl = 0;
353 	for (p = base, lines = 0; p < last; p++) {
354 		if (*p == NL) {
355 			nl++;
356 			lines++;
357 		} else if (nl) {
358 			if (*p != COMM)
359 				break;
360 			nl = 0;
361 		}
362 	}
363 	*(p - 1) = NULL;
364 	add_bent(bd->elist, base, NULL, NULL, NULL);
365 	*next = p;
366 	*line += lines;
367 }
368 
369 /*
370  * Parse out an operator (setprop) from the boot environment
371  */
372 static char *
373 parse_cmd(benv_des_t *bd, char **next, int *line)
374 {
375 	char *strbegin;
376 	char *badeof = "unexpected EOF in %s line %d";
377 	char *syntax = "syntax error in %s line %d";
378 	char *c = *next;
379 
380 	/*
381 	 * Skip spaces or tabs. New lines increase the line count.
382 	 */
383 	while (isspace(*c)) {
384 		if (*c++ == '\n')
385 			(*line)++;
386 	}
387 
388 	/*
389 	 * Check for a the setprop command.  Currently that's all we
390 	 * seem to support.
391 	 *
392 	 * XXX need support for setbinprop?
393 	 */
394 
395 	/*
396 	 * Check first for end of file.  Finding one now would be okay.
397 	 * We should also bail if we are at the start of a comment.
398 	 */
399 	if (*c == '\0' || *c == COMM) {
400 		*next = c;
401 		return (NULL);
402 	}
403 
404 	strbegin = c;
405 	while (*c && !isspace(*c))
406 		c++;
407 
408 	/*
409 	 * Check again for end of file.  Finding one now would NOT be okay.
410 	 */
411 	if (*c == '\0') {
412 		exit(_error(NO_PERROR, badeof, bd->name, *line));
413 	}
414 
415 	*c++ = '\0';
416 	*next = c;
417 
418 	/*
419 	 * Last check is to make sure the command is a setprop!
420 	 */
421 	if (strcmp(strbegin, "setprop") != 0) {
422 		exit(_error(NO_PERROR, syntax, bd->name, *line));
423 		/* NOTREACHED */
424 	}
425 	return (strbegin);
426 }
427 
428 /*
429  * Parse out the name (LHS) of a setprop from the boot environment
430  */
431 static char *
432 parse_name(benv_des_t *bd, char **next, int *line)
433 {
434 	char *strbegin;
435 	char *badeof = "unexpected EOF in %s line %d";
436 	char *syntax = "syntax error in %s line %d";
437 	char *c = *next;
438 
439 	/*
440 	 * Skip spaces or tabs. No tolerance for new lines now.
441 	 */
442 	while (isspace(*c)) {
443 		if (*c++ == '\n')
444 			exit(_error(NO_PERROR, syntax, bd->name, *line));
445 	}
446 
447 	/*
448 	 * Grab a name for the property to set.
449 	 */
450 
451 	/*
452 	 * Check first for end of file.  Finding one now would NOT be okay.
453 	 */
454 	if (*c == '\0') {
455 		exit(_error(NO_PERROR, badeof, bd->name, *line));
456 	}
457 
458 	strbegin = c;
459 	while (*c && !isspace(*c))
460 		c++;
461 
462 	/*
463 	 * At this point in parsing we have 'setprop name'.  What follows
464 	 * is a newline, other whitespace, or EOF.  Most of the time we
465 	 * want to replace a white space character with a NULL to terminate
466 	 * the name, and then continue on processing.  A newline here provides
467 	 * the most grief.  If we just replace it with a null we'll
468 	 * potentially get the setprop on the next line as the value of this
469 	 * setprop! So, if the last thing we see is a newline we'll have to
470 	 * dup the string.
471 	 */
472 	if (isspace(*c)) {
473 		if (*c == '\n') {
474 			*c = '\0';
475 			strbegin = strdup(strbegin);
476 			*c = '\n';
477 		} else {
478 			*c++ = '\0';
479 		}
480 	}
481 
482 	*next = c;
483 	return (strbegin);
484 }
485 
486 /*
487  * Parse out the value (RHS) of a setprop line from the boot environment
488  */
489 static char *
490 parse_value(benv_des_t *bd, char **next, int *line)
491 {
492 	char *strbegin;
493 	char *badeof = "unexpected EOF in %s line %d";
494 	char *result;
495 	char *c = *next;
496 	char quote;
497 
498 	/*
499 	 * Skip spaces or tabs. A newline here would indicate a
500 	 * NULL property value.
501 	 */
502 	while (isspace(*c)) {
503 		if (*c++ == '\n') {
504 			(*line)++;
505 			*next = c;
506 			return (NULL);
507 		}
508 	}
509 
510 	/*
511 	 * Grab the value of the property to set.
512 	 */
513 
514 	/*
515 	 * Check first for end of file.  Finding one now would
516 	 * also indicate a NULL property.
517 	 */
518 	if (*c == '\0') {
519 		*next = c;
520 		return (NULL);
521 	}
522 
523 	/*
524 	 * Value may be quoted, in which case we assume the end of the value
525 	 * comes with a closing quote.
526 	 *
527 	 * We also allow escaped quote characters inside the quoted value.
528 	 *
529 	 * For obvious reasons we do not attempt to parse variable references.
530 	 */
531 	if (*c == '"' || *c == '\'') {
532 		quote = *c;
533 		c++;
534 		strbegin = c;
535 		result = c;
536 		while (*c != quote) {
537 			if (*c == '\\') {
538 				c++;
539 			}
540 			if (*c == '\0') {
541 				break;
542 			}
543 			*result++ = *c++;
544 		}
545 
546 		/*
547 		 *  Throw fatal exception if no end quote found.
548 		 */
549 		if (*c != quote) {
550 			exit(_error(NO_PERROR, badeof, bd->name, *line));
551 		}
552 
553 		*result = '\0';		/* Terminate the result */
554 		c++;			/* and step past the close quote */
555 	} else {
556 		strbegin = c;
557 		while (*c && !isspace(*c))
558 			c++;
559 	}
560 
561 	/*
562 	 * Check again for end of file.  Finding one now is okay.
563 	 */
564 	if (*c == '\0') {
565 		*next = c;
566 		return (strbegin);
567 	}
568 
569 	*c++ = '\0';
570 	*next = c;
571 	return (strbegin);
572 }
573 
574 /*
575  * Add a command to the benv list.
576  */
577 static void
578 add_cmd(benv_des_t *bd, char *last, char **next, int *line)
579 {
580 	char *cmd, *name, *val;
581 
582 	while (*next <= last && **next != COMM) {
583 		if ((cmd = parse_cmd(bd, next, line)) == NULL)
584 			break;
585 		name = parse_name(bd, next, line);
586 		val = parse_value(bd, next, line);
587 		add_bent(bd->elist, NULL, cmd, name, val);
588 		(*line)++;
589 	};
590 
591 }
592 
593 /*
594  * Parse the benv (bootenv.rc) file and break it into a benv
595  * list.  List entries may be comment blocks or commands.
596  */
597 static void
598 parse_benv(benv_des_t *bd)
599 {
600 	int line;
601 	char *pbase, *pend;
602 	char *tok, *tnext;
603 
604 	line = 1;
605 	pbase = (char *)bd->adr;
606 	pend = pbase + bd->len;
607 
608 	for (tok = tnext = pbase; tnext < pend && '\0' != *tnext; tok = tnext)
609 		if (*tok == COMM)
610 			add_comm(bd, tok, pend, &tnext, &line);
611 		else
612 			add_cmd(bd, pend, &tnext, &line);
613 }
614 
615 static void
616 write_benv(benv_des_t *bd)
617 {
618 	FILE *fp;
619 	eplist_t *list, *e;
620 	benv_ent_t *bent;
621 	char *name;
622 
623 	list = bd->elist;
624 
625 	if (list->next == list)
626 		return;
627 
628 	if ((fp = fopen(bd->name, "w")) == NULL)
629 		exit(_error(PERROR, "cannot open %s", bd->name));
630 
631 	for (e = list->next; e != list; e = e->next) {
632 		bent = (benv_ent_t *)e->item;
633 		name = bent->name;
634 		if (name) {
635 			if (bent->val) {
636 				(void) fprintf(fp, "%s %s ",
637 				    bent->cmd, bent->name);
638 				put_quoted(fp, bent->val);
639 				(void) fprintf(fp, "\n");
640 			} else {
641 				(void) fprintf(fp, "%s %s\n",
642 				    bent->cmd, bent->name);
643 			}
644 		} else {
645 			(void) fprintf(fp, "%s\n", bent->cmd);
646 		}
647 	}
648 
649 	(void) fclose(fp);
650 }
651 
652 static char *
653 get_line(void)
654 {
655 	int c;
656 	char *nl;
657 	static char line[256];
658 
659 	if (fgets(line, sizeof (line), stdin) != NULL) {
660 		/*
661 		 * Remove newline if present,
662 		 * otherwise discard rest of line.
663 		 */
664 		if (nl = strchr(line, '\n'))
665 			*nl = 0;
666 		else
667 			while ((c = getchar()) != '\n' && c != EOF)
668 				;
669 		return (line);
670 	} else
671 		return (NULL);
672 }
673 
674 int
675 main(int argc, char **argv)
676 {
677 	int c;
678 	int updates = 0;
679 	char *usage = "Usage: %s [-v] [-f prom-device]"
680 	    " [variable[=value] ...]";
681 	eplist_t *elist;
682 	benv_des_t *bd;
683 	char *file = NULL;
684 
685 	setpname(argv[0]);
686 
687 	while ((c = getopt(argc, argv, "f:Itv")) != -1)
688 		switch (c) {
689 		case 'v':
690 			verbose++;
691 			break;
692 		case 'f':
693 			file = optarg;
694 			break;
695 		case 't':
696 			test++;
697 			break;
698 		default:
699 			exit(_error(NO_PERROR, usage, argv[0]));
700 		}
701 
702 	(void) uname(&uts_buf);
703 	bd = new_bd();
704 	init_benv(bd, file);
705 
706 	map_benv(bd);
707 	if (bd->len) {
708 		parse_benv(bd);
709 		unmap_benv(bd);
710 	}
711 
712 	elist = bd->elist;
713 
714 	if (optind >= argc) {
715 		print_vars(elist);
716 		return (0);
717 	} else
718 		while (optind < argc) {
719 			/*
720 			 * If "-" specified, read variables from stdin;
721 			 * otherwise, process each argument as a variable
722 			 * print or set request.
723 			 */
724 			if (strcmp(argv[optind], "-") == 0) {
725 				char *line;
726 
727 				while ((line = get_line()) != NULL)
728 					updates += proc_var(line, elist);
729 				clearerr(stdin);
730 			} else
731 				updates += proc_var(argv[optind], elist);
732 
733 			optind++;
734 		}
735 
736 	/*
737 	 * don't write benv if we are processing delayed writes since
738 	 * it is likely that the delayed writes changes bootenv.rc anyway...
739 	 */
740 	if (updates)
741 		write_benv(bd);
742 	close_kbenv();
743 
744 	return (0);
745 }
746