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 *
strcats(char * s,...)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 *
new_list(void)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
add_item(void * item,eplist_t * list)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 *
new_bd(void)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 *
new_bent(char * comm,char * cmd,char * name,char * val)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
add_bent(eplist_t * list,char * comm,char * cmd,char * name,char * val)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 *
get_var(char * name,eplist_t * list)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
print_var(char * name,eplist_t * list)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
print_vars(eplist_t * list)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
put_quoted(FILE * fp,char * val)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
set_var(char * name,char * val,eplist_t * list)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
proc_var(char * name,eplist_t * list)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
init_benv(benv_des_t * bd,char * file)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
map_benv(benv_des_t * bd)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
unmap_benv(benv_des_t * bd)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
add_comm(benv_des_t * bd,char * base,char * last,char ** next,int * line)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 *
parse_cmd(benv_des_t * bd,char ** next,int * line)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 *
parse_name(benv_des_t * bd,char ** next,int * line)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 *
parse_value(benv_des_t * bd,char ** next,int * line)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
add_cmd(benv_des_t * bd,char * last,char ** next,int * line)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
parse_benv(benv_des_t * bd)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
write_benv(benv_des_t * bd)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 *
get_line(void)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
main(int argc,char ** argv)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