commit 660067642dc57bd5820285c195ed5f240475319a from: Stefan Sperling date: Mon Jun 19 13:17:03 2023 UTC add 'show environment', 'setenv', and 'unsetenv' commands Being able to manage environment variables from within nsh will be useful, especially if nsh is used as a login shell because nsh's environment is visible to other commands run by nsh. Setting variables can now affect the behaviour of nsh itself. For example, it is possible to set NSH_MANUAL_PAGE from within a running nsh session and the new value will immediately be used by the 'manual' command. This particular variable is only useful to nsh developers themselves but the same mechanism could be used for user-facing variables in the future. If 'show environment' is run without further arguments all variables will be displayed. Otherwise the value of the specified variable will be displayed if the variable exists. All commands support tab-completion for names which already exist in the environment. The 'setenv' commands appends "=" if the name being completed does not yet exist in the environment. commit - 2d331b3f3d70a5e5bf1362ff9e23627f0ca15288 commit + 660067642dc57bd5820285c195ed5f240475319a blob - 08c1f3f3b37b555c2e7c4d573e06af93601a01d3 blob + 2c6b28c32e26056d4f1939cbe118d5492a29d10d --- commands.c +++ commands.c @@ -94,6 +94,7 @@ static int pr_conf(int, char **); static int pr_s_conf(int, char **); static int pr_a_conf(int, char **); static int pr_conf_diff(int, char **); +static int pr_environment(int, char **); static int show_hostname(int, char **); static int wr_startup(void); static int wr_conf(char *); @@ -114,6 +115,8 @@ static int int_traceroute6(char *, int, int, char **); static int int_ssh(char *, int, int, char **); static int int_telnet(char *, int, int, char **); static int int_do(char *, int, int, char **); +static int int_setenv(char *, int, int, char **); +static int int_unsetenv(char *, int, int, char **); static int int_show(char *, int, int, char **); static int int_who(char *, int, int, char **); static int int_doverbose(char *, int, int, char **); @@ -126,6 +129,8 @@ static int hostname(int, char **); static int manual(int, char**); static int nocmd(int, char **); static int docmd(int, char **); +static int setenvcmd(int, char **); +static int unsetenvcmd(int, char **); static int shell(int, char*[]); static int ping(int, char*[]); static int ping6(int, char*[]); @@ -455,6 +460,7 @@ Menu showlist[] = { { "startup-config", "Startup configuration", CMPL0 0, 0, 0, 0, pr_s_conf }, { "active-config", "Configuration of active context", CMPL0 0, 0, 0, 0, pr_a_conf }, { "diff-config", "Show differences between startup and running config", CMPL0 0, 0, 0, 0, pr_conf_diff }, + { "environment", "Show environment variables", CMPL(e) 0, 0, 0, 1, pr_environment }, { "?", "Options", CMPL0 0, 0, 0, 0, show_help }, { "help", 0, CMPL0 0, 0, 0, 0, show_help }, { 0, 0, 0, 0, 0 } @@ -1069,6 +1075,8 @@ static char crontabhelp[]; static char showhelp[]; static char whohelp[]; static char dohelp[]; +static char setenvhelp[]; +static char unsetenvhelp[]; static char verbosehelp[]; static char editinghelp[]; static char shellhelp[]; @@ -1127,6 +1135,8 @@ struct intlist Intlist[] = { { "ssh", sshhelp, CMPL0 0, 0, int_ssh, 0 }, { "telnet", telnethelp, CMPL0 0, 0, int_telnet, 0 }, { "do", dohelp, CMPL(c) 0, 0, int_do, 0 }, + { "setenv", setenvhelp, CMPL(e) 0, 0, int_setenv, 0 }, + { "unsetenv", unsetenvhelp, CMPL(e) 0, 0, int_unsetenv, 0 }, { "keepalive", "GRE tunnel keepalive", CMPL0 0, 0, intkeepalive, 1}, { "mplslabel", "MPLS local label", CMPL0 0, 0, intmpls, 1 }, { "pwe", "MPLS PWE3", CMPL0 0, 0, intpwe3, 1 }, @@ -1209,6 +1219,8 @@ struct intlist Bridgelist[] = { { "ssh", sshhelp, CMPL0 0, 0, int_ssh, 0 }, { "telnet", telnethelp, CMPL0 0, 0, int_telnet, 0 }, { "do", dohelp, CMPL(c) 0, 0, int_do, 0 }, + { "setenv", setenvhelp, CMPL(e) 0, 0, int_setenv, 0 }, + { "unsetenv", unsetenvhelp, CMPL(e) 0, 0, int_unsetenv, 0 }, { "rule", "Bridge layer 2 filtering rules", CMPL0 0, 0, brrule, 0 }, { "static", "Static bridge address entry", CMPL0 0, 0, brstatic, 1 }, { "ifpriority", "Spanning priority of a member on an 802.1D bridge", CMPL0 0, 0, brpri, 1 }, @@ -1568,6 +1580,20 @@ static int int_do(char *ifname, int ifs, int argc, char **argv) { docmd(argc, argv); + return 0; /* do not leave interface context */ +} + +static int +int_setenv(char *ifname, int ifs, int argc, char **argv) +{ + setenvcmd(argc, argv); + return 0; /* do not leave interface context */ +} + +static int +int_unsetenv(char *ifname, int ifs, int argc, char **argv) +{ + unsetenvcmd(argc, argv); return 0; /* do not leave interface context */ } @@ -1710,6 +1736,8 @@ static char confighelp[] = "Set configuration mode", whohelp[] = "Display system users", dohelp[] = "Superfluous, do is ignored and its arguments executed", + setenvhelp[] = "Set an environment variable", + unsetenvhelp[] ="Delete an environment variable", shellhelp[] = "Invoke a subshell", savehelp[] = "Save the current configuration", nreboothelp[] = "Reboot the system", @@ -1856,6 +1884,7 @@ struct ghs mantab[] = { { "sasyncd", "Search for tag sasyncd", CMPL0 NULL, 0 }, { "scheduler", "Search for tag scheduler", CMPL0 NULL, 0 }, { "sensor", "Search for tag sensor", CMPL0 NULL, 0 }, + { "setenv", "Search for tag setenv", CMPL0 NULL, 0 }, { "sh", "Search for tag sh", CMPL0 NULL, 0 }, { "shell", "Search for tag shell", CMPL0 NULL, 0 }, { "show", "Search for tag show", CMPL0 NULL, 0 }, @@ -1879,6 +1908,7 @@ struct ghs mantab[] = { { "traceroute", "Search for tag traceroute", CMPL0 NULL, 0 }, { "traceroute6", "Search for tag traceroute6", CMPL0 NULL, 0 }, { "unprivileged", "Search for tag unprivileged", CMPL0 NULL, 0 }, + { "unsetenv", "Search for tag setenv", CMPL0 NULL, 0 }, { "veb", "Search for tag veb", CMPL0 NULL, 0 }, { "verbose", "Search for tag verbose", CMPL0 NULL, 0 }, { "vlan", "Search for tag vlan", CMPL0 NULL, 0 }, @@ -1965,6 +1995,8 @@ Command cmdtab[] = { { "who", whohelp, CMPL0 0, 0, who, 0, 0, 0, 0 }, { "no", 0, CMPL(c) 0, 0, nocmd, 0, 0, 0, 0 }, { "do", dohelp, CMPL(c) 0, 0, docmd, 0, 0, 0, 0 }, + { "setenv", setenvhelp, CMPL(E) 0, 0, setenvcmd, 0, 0, 0, 0 }, + { "unsetenv", unsetenvhelp, CMPL(e) 0, 0, unsetenvcmd, 0, 0, 0, 0 }, { "!", shellhelp, CMPL0 0, 0, shell, 1, 0, 0, 0 }, { "?", helphelp, CMPL(c) 0, 0, help, 0, 0, 0, 0 }, { "manual", manhelp, CMPL(H) (char **)mantab, sizeof(struct ghs), manual,0, 0, 0, 0 }, @@ -2280,6 +2312,62 @@ docmd(int argc, char **argv) return 0; } +static void +usage_setenv(void) +{ + printf("%% setenv NAME=VALUE\n"); + printf("%% setenv NAME=\"VALUE with spaces\"\n"); + printf("%% setenv \"NAME with spaces\"=VALUE\n"); +} + +static int +setenvcmd(int argc, char **argv) +{ + char *name = NULL, *value = NULL, *eq; + + if (argc != 2) { + usage_setenv(); + return 0; + } + + eq = strchr(argv[1], '='); + if (eq == NULL) { + usage_setenv(); + return 0; + } + + name = strndup(argv[1], eq - argv[1]); + if (name == NULL) { + printf("%% setenvcmd: strndup: %s\n", strerror(errno)); + return 0; + } + + value = eq + 1; + if (setenv(name, value, 1) == -1) + printf("%% setenv %s=%s: %s\n", name, value, strerror(errno)); + + free(name); + return 0; +} + +static int +unsetenvcmd(int argc, char **argv) +{ + char *name; + + if (argc != 2) { + printf("%% unsetenv NAME\n"); + return 0; + } + + name = argv[1]; + + if (unsetenv(name) == -1) + printf("%% unsetenv %s: %s\n", name, strerror(errno)); + + return 0; +} + /* * Shell command. */ @@ -3367,4 +3455,79 @@ pr_dhcp(int argc, char **argv) } printf("%% show dhcp leases\n"); return(1); +} + +static int +envcmp(const void *item1, const void *item2) +{ + const char *a = *(const char **)item1; + const char *b = *(const char **)item2; + + return strcmp(a, b); } + +static int +pr_environment(int argc, char **argv) +{ + extern char **environ; + char **ep; + int fd; + char path[PATH_MAX]; + + if (strlcpy(path, "/tmp/nshrc.env.XXXXXXXX", sizeof(path)) >= + sizeof(path)) + return 0; + + fd = mkstemp(path); + if (fd == -1) { + printf("%% mkstemp %s: %s\n", path, strerror(errno)); + return 0; + } + + if (argc >= 3) { + char *name, *eq, *value; + + name = argv[2]; + for (ep = environ; *ep; ep++) { + eq = strchr(*ep, '='); + if (eq && strncmp(name, *ep, eq - *ep) == 0) { + value = eq + 1; + dprintf(fd, "%s\n", value); + break; + } + } + } else { + char **sorted_environ; + int nenv; + + for (nenv = 0, ep = environ; *ep; ep++) { + if (strchr(*ep, '=') != NULL) + nenv++; + } + + sorted_environ = calloc(nenv + 1, sizeof(*sorted_environ)); + if (sorted_environ == NULL) { + printf("%% pr_environment: calloc: %s\n", strerror(errno)); + goto done; + } + + for (nenv = 0, ep = environ; *ep; ep++) { + if (strchr(*ep, '=') != NULL) + sorted_environ[nenv++] = *ep; + } + + qsort(sorted_environ, nenv, sizeof(*sorted_environ), envcmp); + sorted_environ[nenv] = NULL; + + for (ep = sorted_environ; *ep; ep++) + dprintf(fd, "%s\n", *ep); + } + + fsync(fd); + + more(path); +done: + unlink(path); + close(fd); + return 0; +} blob - 1aa4e516adf58328b4cf0e36ccae19bbe005a6cc blob + b3d3ffb85ca317f5281700df862ec443d010c534 --- complete.c +++ complete.c @@ -51,7 +51,8 @@ unsigned char complete(EditLine *, char **, size_t); static int comparstr(const void *, const void *); -static unsigned char complete_ambiguous(char *, int, StringList *, EditLine *); +static unsigned char complete_ambiguous(char *, int, StringList *, EditLine *, + char *); static unsigned char complete_command(char *, int, EditLine *, char **, int); static unsigned char complete_subcommand(char *, int, EditLine *, char **, int); static unsigned char complete_local(char *, int, EditLine *); @@ -59,6 +60,7 @@ static unsigned char complete_ifname(char *, int, Edit static unsigned char complete_ifgroup(char *, int, EditLine *); static unsigned char complete_ifbridge(char *, int, EditLine *); static unsigned char complete_rtable(char *, int, EditLine *); +static unsigned char complete_environment(char *, int, EditLine *, int); static unsigned char complete_nocmd(struct ghs *, char *, int, EditLine *, char **, int, int); static unsigned char complete_docmd(struct ghs *, char *, int, EditLine *, @@ -87,9 +89,11 @@ comparstr(const void *a, const void *b) * word word which started the match * list list by default * words stringlist containing possible matches + * sep separator to insert after completed word; usually " " */ static unsigned char -complete_ambiguous(char *word, int list, StringList *words, EditLine *el) +complete_ambiguous(char *word, int list, StringList *words, EditLine *el, + char *sep) { char insertstr[MAXPATHLEN]; char *lastmatch; @@ -102,7 +106,7 @@ complete_ambiguous(char *word, int list, StringList *w if (words->sl_cur == 1) { /* only one choice available */ (void)strlcpy(insertstr, words->sl_str[0], sizeof insertstr); - (void)strlcat(insertstr, " ", sizeof insertstr); + (void)strlcat(insertstr, sep, sizeof insertstr); if (el_insertstr(el, insertstr + wordlen) == -1) return (CC_ERROR); else @@ -164,7 +168,7 @@ complete_command(char *word, int list, EditLine *el, c sl_add(words, ghs->name); } - rv = complete_ambiguous(word, list, words, el); + rv = complete_ambiguous(word, list, words, el, " "); sl_free(words, 0); return (rv); } @@ -240,7 +244,7 @@ complete_local(char *word, int list, EditLine *el) } closedir(dd); - rv = complete_ambiguous(file, list, words, el); + rv = complete_ambiguous(file, list, words, el, " "); sl_free(words, 1); return (rv); } @@ -377,7 +381,7 @@ complete_ifname(char *word, int list, EditLine *el) sl_add(words, s); } - rv = complete_ambiguous(word, list, words, el); + rv = complete_ambiguous(word, list, words, el, " "); if_freenameindex(ifn_list); sl_free(words, 0); return (rv); @@ -434,7 +438,7 @@ complete_ifgroup(char *word, int list, EditLine *el) sl_add(words, ifg->ifgrq_group); } - rv = complete_ambiguous(word, list, words, el); + rv = complete_ambiguous(word, list, words, el, " "); sl_free(words, 0); free(ifgr.ifgr_groups); close(ifs); @@ -470,7 +474,7 @@ complete_ifbridge(char *word, int list, EditLine *el) sl_add(words, ifnp->if_name); } - rv = complete_ambiguous(word, list, words, el); + rv = complete_ambiguous(word, list, words, el, " "); if_freenameindex(ifn_list); sl_free(words, 0); close(ifs); @@ -512,7 +516,7 @@ complete_rtable(char *word, int list, EditLine *el) sl_add(words, rtable); } - rv = complete_ambiguous(word, list, words, el); + rv = complete_ambiguous(word, list, words, el, " "); done: sl_free(rtables, 1); sl_free(words, 0); @@ -520,6 +524,51 @@ done: return (rv); } +static unsigned char +complete_environment(char *word, int dolist, EditLine *el, int set) +{ + StringList *words; + extern char **environ; + char **ep, *eq, *name; + size_t wordlen = strlen(word); + int rv = CC_ERROR; + + words = sl_init(); + + for (ep = environ; *ep; ep++) { + eq = strchr(*ep, '='); + if (eq == NULL) + continue; + name = strndup(*ep, eq - *ep); + if (name == NULL) { + sl_free(words, 1); + return CC_ERROR; + } + if (strncmp(word, name, wordlen) == 0) + sl_add(words, name); + else + free(name); + } + + /* + * When a new environment variable is created then hitting the + * TAB key makes '=' appear. + */ + if (set && words->sl_cur == 0 && wordlen > 0 && + word[wordlen - 1] != '=' && strchr(word, '=') == NULL) { + name = strdup(word); + if (name == NULL) { + sl_free(words, 1); + return CC_ERROR; + } + sl_add(words, name); + } + + rv = complete_ambiguous(word, dolist, words, el, set ? "=" : " "); + sl_free(words, 1); + return rv; +} + /* * Generic complete routine */ @@ -872,6 +921,10 @@ complete_args(struct ghs *c, char *word, int dolist, E return(CC_ERROR); return (complete_showhelp(word, el, c->table, c->stlen, c->name, help_vertical)); + case 'E': + return complete_environment(word, dolist, el, 1); + case 'e': + return complete_environment(word, dolist, el, 0); case 'n': /* no complete */ return (CC_ERROR); } blob - 1e1153b1ba8b2989aead1c19f5b9d61ee615af12 blob + 12f1978a735352ff8ea0aa9bb806d0832ed16d1a --- nsh.8 +++ nsh.8 @@ -2687,7 +2687,42 @@ firewall rules, and other information compiled by Display differences between the startup configuration and the running configuration. This command requires root user privileges. +.Pp +.Ic show environment Op Ar NAME +.Pp +Display environment variables. +If the +.Ar NAME +of a variable is specified then display the value of this variable. +Otherwise, display all existing environment variable names and values. .Pp +.Tg setenv +.Ic setenv Ar NAME=VALUE +.Pp +Set the environment variable +.Ar NAME +to the specified +.Ar VALUE. +If a +.Ar NAME +or +.Ar VALUE +contains whitespace then it must be quoted in double-quotes. +For example: +.Bd -literal -offset indent +nsh/setenv EDITOR=/usr/local/bin/emacs +nsh/setenv MY_VARIABLE="this value contains whitespace" +nsh/setenv "MY OTHER VARIABLE"=my-name-contains-whitespace +.Ed +.Pp +.Tg unsetenv +.Pp +.Ic unsetenv Ar NAME +.Pp +Delete the variable +.Ar NAME +from the environment. +.Pp .Tg flush .Ic flush .Op routes | arp | ndp | line | bridge-dyn | bridge-all | bridge-rule | pf | history |\&? | help