/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2016 Toomas Soome * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. */ #include "benv.h" #include #include #include #include #include #include #include #include /* * Usage: % eeprom [-v] [-f prom_dev] [-] * % eeprom [-v] [-f prom_dev] field[=value] ... */ extern void get_kbenv(void); extern void close_kbenv(void); extern caddr_t get_propval(char *name, char *node); extern void setpname(char *prog); extern char *getbootcmd(void); char *boottree; struct utsname uts_buf; static int test; int verbose; /* * Concatenate a NULL terminated list of strings into * a single string. */ char * strcats(char *s, ...) { char *cp, *ret; size_t len; va_list ap; va_start(ap, s); for (ret = NULL, cp = s; cp; cp = va_arg(ap, char *)) { if (ret == NULL) { ret = strdup(s); len = strlen(ret) + 1; } else { len += strlen(cp); ret = realloc(ret, len); (void) strcat(ret, cp); } } va_end(ap); return (ret); } eplist_t * new_list(void) { eplist_t *list; list = (eplist_t *)malloc(sizeof (eplist_t)); (void) memset(list, 0, sizeof (eplist_t)); list->next = list; list->prev = list; list->item = NULL; return (list); } void add_item(void *item, eplist_t *list) { eplist_t *entry; entry = (eplist_t *)malloc(sizeof (eplist_t)); (void) memset(entry, 0, sizeof (eplist_t)); entry->item = item; entry->next = list; entry->prev = list->prev; list->prev->next = entry; list->prev = entry; } typedef struct benv_ent { char *cmd; char *name; char *val; } benv_ent_t; typedef struct benv_des { char *name; int fd; caddr_t adr; size_t len; eplist_t *elist; } benv_des_t; static benv_des_t * new_bd(void) { benv_des_t *bd; bd = (benv_des_t *)malloc(sizeof (benv_des_t)); (void) memset(bd, 0, sizeof (benv_des_t)); bd->elist = new_list(); return (bd); } /* * Create a new entry. Comment entries have NULL names. */ static benv_ent_t * new_bent(char *comm, char *cmd, char *name, char *val) { benv_ent_t *bent; bent = (benv_ent_t *)malloc(sizeof (benv_ent_t)); (void) memset(bent, 0, sizeof (benv_ent_t)); if (comm) { bent->cmd = strdup(comm); comm = NULL; } else { bent->cmd = strdup(cmd); bent->name = strdup(name); if (val) bent->val = strdup(val); } return (bent); } /* * Add a new entry to the benv entry list. Entries can be * comments or commands. */ static void add_bent(eplist_t *list, char *comm, char *cmd, char *name, char *val) { benv_ent_t *bent; bent = new_bent(comm, cmd, name, val); add_item((void *)bent, list); } static benv_ent_t * get_var(char *name, eplist_t *list) { eplist_t *e; benv_ent_t *p; for (e = list->next; e != list; e = e->next) { p = (benv_ent_t *)e->item; if (p->name != NULL && strcmp(p->name, name) == 0) return (p); } return (NULL); } static void print_var(char *name, eplist_t *list) { benv_ent_t *p; char *bootcmd; if (strcmp(name, "bootcmd") == 0) { bootcmd = getbootcmd(); (void) printf("%s=%s\n", name, bootcmd ? bootcmd : ""); } else if ((p = get_var(name, list)) == NULL) { (void) printf("%s: data not available.\n", name); } else { (void) printf("%s=%s\n", name, p->val ? p->val : ""); } } static void print_vars(eplist_t *list) { eplist_t *e; benv_ent_t *p; for (e = list->next; e != list; e = e->next) { p = (benv_ent_t *)e->item; if (p->name != NULL) { (void) printf("%s=%s\n", p->name, p->val ? p->val : ""); } } } /* * Write a string to a file, quoted appropriately. We use single * quotes to prevent any variable expansion. Of course, we backslash-quote * any single quotes or backslashes. */ static void put_quoted(FILE *fp, char *val) { (void) putc('\'', fp); while (*val) { switch (*val) { case '\'': case '\\': (void) putc('\\', fp); /* FALLTHROUGH */ default: (void) putc(*val, fp); break; } val++; } (void) putc('\'', fp); } /* * Returns 1 if bootenv.rc was modified, 0 otherwise. */ static int set_var(char *name, char *val, eplist_t *list) { benv_ent_t *p; if (strcmp(name, "bootcmd") == 0) return (0); if (verbose) { (void) printf("old:"); print_var(name, list); } if ((p = get_var(name, list)) != NULL) { free(p->val); p->val = strdup(val); } else add_bent(list, NULL, "setprop", name, val); if (verbose) { (void) printf("new:"); print_var(name, list); } return (1); } /* * Returns 1 if bootenv.rc is modified or 0 if no modification was * necessary. This allows us to implement non super-user look-up of * variables by name without the user being yelled at for trying to * modify the bootenv.rc file. */ static int proc_var(char *name, eplist_t *list) { register char *val; if ((val = strchr(name, '=')) == NULL) { print_var(name, list); return (0); } else { *val++ = '\0'; return (set_var(name, val, list)); } } static void init_benv(benv_des_t *bd, char *file) { get_kbenv(); if (test) boottree = "/tmp"; else if ((boottree = (char *)get_propval("boottree", "chosen")) == NULL) boottree = strcats("/boot", NULL); if (file != NULL) bd->name = file; else bd->name = strcats(boottree, "/solaris/bootenv.rc", NULL); } static void map_benv(benv_des_t *bd) { if ((bd->fd = open(bd->name, O_RDONLY)) == -1) if (errno == ENOENT) return; else exit(_error(PERROR, "cannot open %s", bd->name)); if ((bd->len = (size_t)lseek(bd->fd, 0, SEEK_END)) == 0) { if (close(bd->fd) == -1) exit(_error(PERROR, "close error on %s", bd->name)); return; } (void) lseek(bd->fd, 0, SEEK_SET); if ((bd->adr = mmap((caddr_t)0, bd->len, (PROT_READ | PROT_WRITE), MAP_PRIVATE, bd->fd, 0)) == MAP_FAILED) exit(_error(PERROR, "cannot map %s", bd->name)); } static void unmap_benv(benv_des_t *bd) { if (munmap(bd->adr, bd->len) == -1) exit(_error(PERROR, "unmap error on %s", bd->name)); if (close(bd->fd) == -1) exit(_error(PERROR, "close error on %s", bd->name)); } #define NL '\n' #define COMM '#' /* * Add a comment block to the benv list. */ static void add_comm(benv_des_t *bd, char *base, char *last, char **next, int *line) { int nl, lines; char *p; nl = 0; for (p = base, lines = 0; p < last; p++) { if (*p == NL) { nl++; lines++; } else if (nl) { if (*p != COMM) break; nl = 0; } } *(p - 1) = '\0'; add_bent(bd->elist, base, NULL, NULL, NULL); *next = p; *line += lines; } /* * Parse out an operator (setprop) from the boot environment */ static char * parse_cmd(benv_des_t *bd, char **next, int *line) { char *strbegin; char *badeof = "unexpected EOF in %s line %d"; char *syntax = "syntax error in %s line %d"; char *c = *next; /* * Skip spaces or tabs. New lines increase the line count. */ while (isspace(*c)) { if (*c++ == '\n') (*line)++; } /* * Check for a the setprop command. Currently that's all we * seem to support. * * XXX need support for setbinprop? */ /* * Check first for end of file. Finding one now would be okay. * We should also bail if we are at the start of a comment. */ if (*c == '\0' || *c == COMM) { *next = c; return (NULL); } strbegin = c; while (*c && !isspace(*c)) c++; /* * Check again for end of file. Finding one now would NOT be okay. */ if (*c == '\0') { exit(_error(NO_PERROR, badeof, bd->name, *line)); } *c++ = '\0'; *next = c; /* * Last check is to make sure the command is a setprop! */ if (strcmp(strbegin, "setprop") != 0) { exit(_error(NO_PERROR, syntax, bd->name, *line)); /* NOTREACHED */ } return (strbegin); } /* * Parse out the name (LHS) of a setprop from the boot environment */ static char * parse_name(benv_des_t *bd, char **next, int *line) { char *strbegin; char *badeof = "unexpected EOF in %s line %d"; char *syntax = "syntax error in %s line %d"; char *c = *next; /* * Skip spaces or tabs. No tolerance for new lines now. */ while (isspace(*c)) { if (*c++ == '\n') exit(_error(NO_PERROR, syntax, bd->name, *line)); } /* * Grab a name for the property to set. */ /* * Check first for end of file. Finding one now would NOT be okay. */ if (*c == '\0') { exit(_error(NO_PERROR, badeof, bd->name, *line)); } strbegin = c; while (*c && !isspace(*c)) c++; /* * At this point in parsing we have 'setprop name'. What follows * is a newline, other whitespace, or EOF. Most of the time we * want to replace a white space character with a NULL to terminate * the name, and then continue on processing. A newline here provides * the most grief. If we just replace it with a null we'll * potentially get the setprop on the next line as the value of this * setprop! So, if the last thing we see is a newline we'll have to * dup the string. */ if (isspace(*c)) { if (*c == '\n') { *c = '\0'; strbegin = strdup(strbegin); *c = '\n'; } else { *c++ = '\0'; } } *next = c; return (strbegin); } /* * Parse out the value (RHS) of a setprop line from the boot environment */ static char * parse_value(benv_des_t *bd, char **next, int *line) { char *strbegin; char *badeof = "unexpected EOF in %s line %d"; char *result; char *c = *next; char quote; /* * Skip spaces or tabs. A newline here would indicate a * NULL property value. */ while (isspace(*c)) { if (*c++ == '\n') { (*line)++; *next = c; return (NULL); } } /* * Grab the value of the property to set. */ /* * Check first for end of file. Finding one now would * also indicate a NULL property. */ if (*c == '\0') { *next = c; return (NULL); } /* * Value may be quoted, in which case we assume the end of the value * comes with a closing quote. * * We also allow escaped quote characters inside the quoted value. * * For obvious reasons we do not attempt to parse variable references. */ if (*c == '"' || *c == '\'') { quote = *c; c++; strbegin = c; result = c; while (*c != quote) { if (*c == '\\') { c++; } if (*c == '\0') { break; } *result++ = *c++; } /* * Throw fatal exception if no end quote found. */ if (*c != quote) { exit(_error(NO_PERROR, badeof, bd->name, *line)); } *result = '\0'; /* Terminate the result */ c++; /* and step past the close quote */ } else { strbegin = c; while (*c && !isspace(*c)) c++; } /* * Check again for end of file. Finding one now is okay. */ if (*c == '\0') { *next = c; return (strbegin); } *c++ = '\0'; *next = c; return (strbegin); } /* * Add a command to the benv list. */ static void add_cmd(benv_des_t *bd, char *last, char **next, int *line) { char *cmd, *name, *val; while (*next <= last && **next != COMM) { if ((cmd = parse_cmd(bd, next, line)) == NULL) break; name = parse_name(bd, next, line); val = parse_value(bd, next, line); add_bent(bd->elist, NULL, cmd, name, val); (*line)++; }; } /* * Parse the benv (bootenv.rc) file and break it into a benv * list. List entries may be comment blocks or commands. */ static void parse_benv(benv_des_t *bd) { int line; char *pbase, *pend; char *tok, *tnext; line = 1; pbase = (char *)bd->adr; pend = pbase + bd->len; for (tok = tnext = pbase; tnext < pend && '\0' != *tnext; tok = tnext) if (*tok == COMM) add_comm(bd, tok, pend, &tnext, &line); else add_cmd(bd, pend, &tnext, &line); } static void write_benv(benv_des_t *bd) { FILE *fp; eplist_t *list, *e; benv_ent_t *bent; char *name; list = bd->elist; if (list->next == list) return; if ((fp = fopen(bd->name, "w")) == NULL) exit(_error(PERROR, "cannot open %s", bd->name)); for (e = list->next; e != list; e = e->next) { bent = (benv_ent_t *)e->item; name = bent->name; if (name) { if (bent->val) { (void) fprintf(fp, "%s %s ", bent->cmd, bent->name); put_quoted(fp, bent->val); (void) fprintf(fp, "\n"); } else { (void) fprintf(fp, "%s %s\n", bent->cmd, bent->name); } } else { (void) fprintf(fp, "%s\n", bent->cmd); } } (void) fclose(fp); } static char * get_line(void) { int c; char *nl; static char line[256]; if (fgets(line, sizeof (line), stdin) != NULL) { /* * Remove newline if present, * otherwise discard rest of line. */ if (nl = strchr(line, '\n')) *nl = 0; else while ((c = getchar()) != '\n' && c != EOF) ; return (line); } else return (NULL); } static int add_pair(const char *name, const char *nvlist, const char *key, const char *type, const char *value) { void *data, *nv; size_t size; int rv; char *end; rv = lzbe_nvlist_get(name, nvlist, &nv); if (rv != 0) return (rv); data = NULL; rv = EINVAL; if (strcmp(type, "DATA_TYPE_STRING") == 0) { data = (void *)(uintptr_t)value; size = strlen(data) + 1; rv = lzbe_add_pair(nv, key, type, data, size); } else if (strcmp(type, "DATA_TYPE_UINT64") == 0) { uint64_t v; v = strtoull(value, &end, 0); if (errno != 0 || *end != '\0') goto done; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_INT64") == 0) { int64_t v; v = strtoll(value, &end, 0); if (errno != 0 || *end != '\0') goto done; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_UINT32") == 0) { u_longlong_t lv; uint32_t v; lv = strtoull(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv > UINT32_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_INT32") == 0) { longlong_t lv; int32_t v; lv = strtoll(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv < INT32_MIN || lv > INT32_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_UINT16") == 0) { uint32_t lv; uint16_t v; lv = strtoul(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv > UINT16_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_INT16") == 0) { int32_t lv; int16_t v; lv = strtol(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv < INT16_MIN || lv > INT16_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_UINT8") == 0) { uint32_t lv; uint8_t v; lv = strtoul(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv > UINT8_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_INT8") == 0) { int32_t lv; int8_t v; lv = strtol(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv < INT8_MIN || lv > INT8_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_BYTE") == 0) { uint32_t lv; uint8_t v; lv = strtoul(value, &end, 0); if (errno != 0 || *end != '\0') goto done; if (lv > UINT8_MAX) goto done; v = lv; size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } else if (strcmp(type, "DATA_TYPE_BOOLEAN_VALUE") == 0) { int32_t v; v = strtol(value, &end, 0); if (errno != 0 || *end != '\0') { if (strcasecmp(value, "YES") == 0) v = 1; else if (strcasecmp(value, "NO") == 0) v = 0; else if (strcasecmp(value, "true") == 0) v = 1; else if (strcasecmp(value, "false") == 0) v = 0; else goto done; } size = sizeof (v); rv = lzbe_add_pair(nv, key, type, &v, size); } if (rv == 0) rv = lzbe_nvlist_set(name, nvlist, nv); done: lzbe_nvlist_free(nv); return (rv); } static int delete_pair(const char *name, const char *nvlist, const char *key) { void *nv; int rv; rv = lzbe_nvlist_get(name, nvlist, &nv); if (rv == 0) rv = lzbe_remove_pair(nv, key); if (rv == 0) rv = lzbe_nvlist_set(name, nvlist, nv); lzbe_nvlist_free(nv); return (rv); } static int usage(char *name) { char *usage = "Usage: %s [-v] [-f prom-device]" " [variable[=value] ...]\n" "%s [-z pool] [-d key] [-k key -t type -v value] [-p]\n" "%s [-z pool] -n nvlist [-d key] [-k key -t type -v value] [-p]\n"; return (_error(NO_PERROR, usage, name, name, name)); } int main(int argc, char **argv) { int c; int updates = 0; eplist_t *elist; benv_des_t *bd; char *file = NULL; bool bootenv, bootenv_print, bootenv_delete; char *name, *key, *type, *nvlist, *value; lzbe_flags_t flag = lzbe_add; nvlist = NULL; name = "rpool"; key = NULL; type = NULL; value = NULL; bootenv = false; bootenv_print = false; bootenv_delete = false; setpname(argv[0]); while ((c = getopt(argc, argv, "bd:f:k:n:prt:v:z:")) != -1) switch (c) { case 'b': bootenv = true; break; case 'd': if (bootenv) { bootenv_delete = true; key = optarg; } else { exit(usage(argv[0])); } break; case 'f': file = optarg; break; case 'k': if (bootenv) key = optarg; else exit(usage(argv[0])); break; case 'n': if (bootenv) nvlist = optarg; else exit(usage(argv[0])); break; case 'p': if (bootenv) bootenv_print = true; else exit(usage(argv[0])); break; case 'r': if (bootenv) flag = lzbe_replace; else exit(usage(argv[0])); break; case 't': if (bootenv) type = optarg; else test++; break; case 'v': if (bootenv) value = optarg; else verbose++; break; case 'z': if (bootenv) name = optarg; else exit(usage(argv[0])); break; default: exit(usage(argv[0])); } argc -= optind; argv += optind; optind = 0; if (bootenv) { int rv = 0; if (argc == 1) value = argv[optind]; if (bootenv_print) return (lzbe_bootenv_print(name, nvlist, stdout)); if (key != NULL || value != NULL) { if (type == NULL) type = "DATA_TYPE_STRING"; if (bootenv_delete) rv = delete_pair(name, nvlist, key); else if (key == NULL) rv = lzbe_set_boot_device(name, flag, value); else rv = add_pair(name, nvlist, key, type, value); if (rv == 0) printf("zfs bootenv is successfully written\n"); else printf("error: %s\n", strerror(rv)); } return (rv); } (void) uname(&uts_buf); bd = new_bd(); init_benv(bd, file); map_benv(bd); if (bd->len) { parse_benv(bd); unmap_benv(bd); } elist = bd->elist; if (optind >= argc) { print_vars(elist); return (0); } else { while (optind < argc) { /* * If "-" specified, read variables from stdin; * otherwise, process each argument as a variable * print or set request. */ if (strcmp(argv[optind], "-") == 0) { char *line; while ((line = get_line()) != NULL) updates += proc_var(line, elist); clearerr(stdin); } else updates += proc_var(argv[optind], elist); optind++; } } /* * don't write benv if we are processing delayed writes since * it is likely that the delayed writes changes bootenv.rc anyway... */ if (updates) write_benv(bd); close_kbenv(); return (0); }