diff options
author | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-01 22:55:37 +0000 |
---|---|---|
committer | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-03 18:22:05 -0600 |
commit | 5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch) | |
tree | 154a22fe556b49e6927197336f8bf91b12eacd5e /bin/sh/var.c | |
download | userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2 userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip |
initial population
Diffstat (limited to 'bin/sh/var.c')
-rw-r--r-- | bin/sh/var.c | 1587 |
1 files changed, 1587 insertions, 0 deletions
diff --git a/bin/sh/var.c b/bin/sh/var.c new file mode 100644 index 0000000..378598c --- /dev/null +++ b/bin/sh/var.c @@ -0,0 +1,1587 @@ +/* $NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: var.c,v 1.75 2019/01/21 13:27:29 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <paths.h> +#include <limits.h> +#include <time.h> +#include <pwd.h> +#include <fcntl.h> +#include <inttypes.h> + +/* + * Shell variables. + */ + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#include "exec.h" +#include "syntax.h" +#include "options.h" +#include "builtins.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "show.h" +#include "machdep.h" +#ifndef SMALL +#include "myhistedit.h" +#endif + +#ifdef SMALL +#define VTABSIZE 39 +#else +#define VTABSIZE 517 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + union var_func_union v_u; +}; +#define func v_u.set_func +#define rfunc v_u.ref_func + +char *get_lineno(struct var *); + +#ifndef SMALL +char *get_tod(struct var *); +char *get_hostname(struct var *); +char *get_seconds(struct var *); +char *get_euser(struct var *); +char *get_random(struct var *); +#endif + +struct localvar *localvars; + +#ifndef SMALL +struct var vhistsize; +struct var vterm; +struct var editrc; +struct var ps_lit; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vvers; +struct var voptind; +struct var line_num; +#ifndef SMALL +struct var tod; +struct var host_name; +struct var seconds; +struct var euname; +struct var random_num; + +intmax_t sh_start_time; +#endif + +struct var line_num; +int line_number; +int funclinebase = 0; +int funclineabs = 0; + +char ifs_default[] = " \t\n"; + +const struct varinit varinit[] = { +#ifndef SMALL + { &vhistsize, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTSIZE=", + { .set_func= sethistsize } }, +#endif + { &vifs, VSTRFIXED|VTEXTFIXED, "IFS= \t\n", + { NULL } }, + { &vmail, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL=", + { NULL } }, + { &vmpath, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH=", + { NULL } }, + { &vvers, VSTRFIXED|VTEXTFIXED|VNOEXPORT, "NETBSD_SHELL=", + { NULL } }, + { &vpath, VSTRFIXED|VTEXTFIXED, "PATH=" _PATH_DEFPATH, + { .set_func= changepath } }, + /* + * vps1 depends on uid + */ + { &vps2, VSTRFIXED|VTEXTFIXED, "PS2=> ", + { NULL } }, + { &vps4, VSTRFIXED|VTEXTFIXED, "PS4=+ ", + { NULL } }, +#ifndef SMALL + { &vterm, VSTRFIXED|VTEXTFIXED|VUNSET, "TERM=", + { .set_func= setterm } }, + { &editrc, VSTRFIXED|VTEXTFIXED|VUNSET, "EDITRC=", + { .set_func= set_editrc } }, + { &ps_lit, VSTRFIXED|VTEXTFIXED|VUNSET, "PSlit=", + { .set_func= set_prompt_lit } }, +#endif + { &voptind, VSTRFIXED|VTEXTFIXED|VNOFUNC, "OPTIND=1", + { .set_func= getoptsreset } }, + { &line_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "LINENO=1", + { .ref_func= get_lineno } }, +#ifndef SMALL + { &tod, VSTRFIXED|VTEXTFIXED|VFUNCREF, "ToD=", + { .ref_func= get_tod } }, + { &host_name, VSTRFIXED|VTEXTFIXED|VFUNCREF, "HOSTNAME=", + { .ref_func= get_hostname } }, + { &seconds, VSTRFIXED|VTEXTFIXED|VFUNCREF, "SECONDS=", + { .ref_func= get_seconds } }, + { &euname, VSTRFIXED|VTEXTFIXED|VFUNCREF, "EUSER=", + { .ref_func= get_euser } }, + { &random_num, VSTRFIXED|VTEXTFIXED|VFUNCREF|VSPECIAL, "RANDOM=", + { .ref_func= get_random } }, +#endif + { NULL, 0, NULL, + { NULL } } +}; + +struct var *vartab[VTABSIZE]; + +STATIC int strequal(const char *, const char *); +STATIC struct var *find_var(const char *, struct var ***, int *); +STATIC void showvar(struct var *, const char *, const char *, int); +static void export_usage(const char *) __dead; + +/* + * Initialize the varable symbol tables and import the environment + */ + +#ifdef mkinit +INCLUDE <stdio.h> +INCLUDE <unistd.h> +INCLUDE <time.h> +INCLUDE "var.h" +INCLUDE "version.h" +MKINIT char **environ; +INIT { + char **envp; + char buf[64]; + +#ifndef SMALL + sh_start_time = (intmax_t)time((time_t *)0); +#endif + /* + * Set up our default variables and their values. + */ + initvar(); + + /* + * Import variables from the environment, which will + * override anything initialised just previously. + */ + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + /* + * Set variables which override anything read from environment. + * + * PPID is readonly + * Always default IFS + * POSIX: "Whenever the shell is invoked, OPTIND shall + * be initialized to 1." + * PSc indicates the root/non-root status of this shell. + * START_TIME belongs only to this shell. + * NETBSD_SHELL is a constant (readonly), and is never exported + * LINENO is simply magic... + */ + snprintf(buf, sizeof(buf), "%d", (int)getppid()); + setvar("PPID", buf, VREADONLY); + setvar("IFS", ifs_default, VTEXTFIXED); + setvar("OPTIND", "1", VTEXTFIXED); + setvar("PSc", (geteuid() == 0 ? "#" : "$"), VTEXTFIXED); + +#ifndef SMALL + snprintf(buf, sizeof(buf), "%jd", sh_start_time); + setvar("START_TIME", buf, VTEXTFIXED); +#endif + + setvar("NETBSD_SHELL", NETBSD_SHELL +#ifdef BUILD_DATE + " BUILD:" BUILD_DATE +#endif +#ifdef DEBUG + " DEBUG" +#endif +#if !defined(JOBS) || JOBS == 0 + " -JOBS" +#endif +#ifndef DO_SHAREDVFORK + " -VFORK" +#endif +#ifdef SMALL + " SMALL" +#endif +#ifdef TINY + " TINY" +#endif +#ifdef OLD_TTY_DRIVER + " OLD_TTY" +#endif +#ifdef SYSV + " SYSV" +#endif +#ifndef BSD + " -BSD" +#endif +#ifdef BOGUS_NOT_COMMAND + " BOGUS_NOT" +#endif + , VTEXTFIXED|VREADONLY|VNOEXPORT); + + setvar("LINENO", "1", VTEXTFIXED); +} +#endif + + +/* + * This routine initializes the builtin variables. It is called when the + * shell is initialized and again when a shell procedure is spawned. + */ + +void +initvar(void) +{ + const struct varinit *ip; + struct var *vp; + struct var **vpp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = strdup(ip->text); + vp->flags = (ip->flags & ~VTEXTFIXED) | VSTRFIXED; + vp->v_u = ip->v_u; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.flags = VSTRFIXED; + vps1.text = NULL; + choose_ps1(); + } +} + +void +choose_ps1(void) +{ + uid_t u = geteuid(); + + if ((vps1.flags & (VTEXTFIXED|VSTACK)) == 0) + free(vps1.text); + vps1.text = strdup(u != 0 ? "PS1=$ " : "PS1=# "); + vps1.flags &= ~(VTEXTFIXED|VSTACK); + + /* + * Update PSc whenever we feel the need to update PS1 + */ + setvarsafe("PSc", (u == 0 ? "#" : "$"), 0); +} + +/* + * Validate a string as a valid variable name + * nb: not parameter - special params and such are "invalid" here. + * Name terminated by either \0 or the term param (usually '=' or '\0'). + * + * If not NULL, the length of the (intended) name is returned via len + */ + +int +validname(const char *name, int term, int *len) +{ + const char *p = name; + int ok = 1; + + if (p == NULL || *p == '\0' || *p == term) { + if (len != NULL) + *len = 0; + return 0; + } + + if (!is_name(*p)) + ok = 0; + p++; + for (;;) { + if (*p == '\0' || *p == term) + break; + if (!is_in_name(*p)) + ok = 0; + p++; + } + if (len != NULL) + *len = p - name; + + return ok; +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc * const savehandler = handler; + int volatile err = 0; + + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + return err; +} + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + * + * This always copies name and val when setting a variable, so + * the source strings can be from anywhere, and are no longer needed + * after this function returns. The VTEXTFIXED and VSTACK flags should + * not be used (but just in case they were, clear them.) + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + const char *q; + char *d; + int len; + int namelen; + char *nameeq; + + p = name; + + if (!validname(p, '=', &namelen)) + error("%.*s: bad variable name", namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + } else { + len += strlen(val); + } + d = nameeq = ckmalloc(len); + q = name; + while (--namelen >= 0) + *d++ = *q++; + *d++ = '='; + *d = '\0'; + if (val) + scopy(val, d); + setvareq(nameeq, flags & ~(VTEXTFIXED | VSTACK)); +} + + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. The flags (VTEXTFIXED or VSTACK) can be used to + * indicate the source of the string (if neither is set, the string will + * eventually be free()d when a replacement value is assigned.) + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + VTRACE(DBG_VARS, ("setvareq([%s],%#x) aflag=%d ", s, flags, aflag)); + if (aflag && !(flags & VNOEXPORT)) + flags |= VEXPORT; + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + VTRACE(DBG_VARS, ("was [%s] fl:%#x\n", vp->text, + vp->flags)); + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + if (flags & VNOERROR) + return; + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + + INTOFF; + + if (vp->func && !(vp->flags & VFUNCREF) && !(flags & VNOFUNC)) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + /* + * if we set a magic var, the magic dissipates, + * unless it is very special indeed. + */ + if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF) + vp->rfunc = NULL; + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + if (flags & VNOEXPORT) + vp->flags &= ~VEXPORT; + if (flags & VDOEXPORT) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT) + flags &= ~VEXPORT; + vp->flags |= flags & ~(VNOFUNC | VDOEXPORT); + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + */ + if (vp == &vmpath || (vp == &vmail && ! mpathset())) + chkmail(1); + + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + VTRACE(DBG_VARS, ("new noset\n")); + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags & ~(VNOFUNC|VFUNCREF|VDOEXPORT); + vp->text = s; + vp->name_len = nlen; + vp->func = NULL; + vp->next = *vpp; + *vpp = vp; + + VTRACE(DBG_VARS, ("new [%s] (%d) %#x\n", s, nlen, vp->flags)); +} + + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct strlist *list, int flags) +{ + struct strlist *lp; + + INTOFF; + for (lp = list ; lp ; lp = lp->next) { + setvareq(savestr(lp->text), flags); + } + INTON; +} + +void +listmklocal(struct strlist *list, int flags) +{ + struct strlist *lp; + + for (lp = list ; lp ; lp = lp->next) + mklocal(lp->text, flags); +} + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct strlist *sp; + struct var *v; + + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (strequal(sp->text, name)) + return strchr(sp->text, '=') + 1; + } + + v = find_var(name, NULL, NULL); + + if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT))) + return NULL; + if (v->rfunc && (v->flags & VFUNCREF) != 0) + return (*v->rfunc)(v) + v->name_len + 1; + return v->text + v->name_len + 1; +} + + + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env; + char **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) + nenv++; + } + CTRACE(DBG_VARS, ("environment: %d vars to export\n", nenv)); + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & (VEXPORT|VUNSET)) == VEXPORT) { + if (vp->rfunc && (vp->flags & VFUNCREF)) + *ep++ = (*vp->rfunc)(vp); + else + *ep++ = vp->text; + VTRACE(DBG_VARS, ("environment: %s\n", ep[-1])); + } + } + *ep = NULL; + return env; +} + + +/* + * Called when a shell procedure is invoked to clear out nonexported + * variables. It is also necessary to reallocate variables of with + * VSTACK set since these are currently allocated on the stack. + */ + +#ifdef mkinit +void shprocvar(void); + +SHELLPROC { + shprocvar(); +} +#endif + +void +shprocvar(void) +{ + struct var **vpp; + struct var *vp, **prev; + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (prev = vpp ; (vp = *prev) != NULL ; ) { + if ((vp->flags & VEXPORT) == 0) { + *prev = vp->next; + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + if ((vp->flags & VSTRFIXED) == 0) + ckfree(vp); + } else { + if (vp->flags & VSTACK) { + vp->text = savestr(vp->text); + vp->flags &=~ VSTACK; + } + prev = &vp->next; + } + } + } + initvar(); +} + + + +/* + * Command to list all variables which are set. Currently this command + * is invoked from the set command when the set command is called without + * any variables. + */ + +void +print_quoted(const char *p) +{ + const char *q; + + if (p[0] == '\0') { + out1fmt("''"); + return; + } + if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) { + out1fmt("%s", p); + return; + } + while (*p) { + if (*p == '\'') { + out1fmt("\\'"); + p++; + continue; + } + q = strchr(p, '\''); + if (!q) { + out1fmt("'%s'", p ); + return; + } + out1fmt("'%.*s'", (int)(q - p), p ); + p = q; + } +} + +static int +sort_var(const void *v_v1, const void *v_v2) +{ + const struct var * const *v1 = v_v1; + const struct var * const *v2 = v_v2; + char *t1 = (*v1)->text, *t2 = (*v2)->text; + + if (*t1 == *t2) { + char *p, *s; + + STARTSTACKSTR(p); + + /* + * note: if lengths are equal, strings must be different + * so we don't care which string we pick for the \0 in + * that case. + */ + if ((strchr(t1, '=') - t1) <= (strchr(t2, '=') - t2)) { + s = t1; + t1 = p; + } else { + s = t2; + t2 = p; + } + + while (*s && *s != '=') { + STPUTC(*s, p); + s++; + } + STPUTC('\0', p); + } + + return strcoll(t1, t2); +} + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ + +STATIC void +showvar(struct var *vp, const char *cmd, const char *xtra, int show_value) +{ + const char *p; + + if (cmd) + out1fmt("%s ", cmd); + if (xtra) + out1fmt("%s ", xtra); + p = vp->text; + if (vp->rfunc && (vp->flags & VFUNCREF) != 0) { + p = (*vp->rfunc)(vp); + if (p == NULL) + p = vp->text; + } + for ( ; *p != '=' ; p++) + out1c(*p); + if (!(vp->flags & VUNSET) && show_value) { + out1fmt("="); + print_quoted(++p); + } + out1c('\n'); +} + +int +showvars(const char *cmd, int flag, int show_value, const char *xtra) +{ + struct var **vpp; + struct var *vp; + + static struct var **list; /* static in case we are interrupted */ + static int list_len; + int count = 0; + + if (!list) { + list_len = 32; + list = ckmalloc(list_len * sizeof *list); + } + + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (flag && !(vp->flags & flag)) + continue; + if (vp->flags & VUNSET && !(show_value & 2)) + continue; + if (count >= list_len) { + list = ckrealloc(list, + (list_len << 1) * sizeof *list); + list_len <<= 1; + } + list[count++] = vp; + } + } + + qsort(list, count, sizeof *list, sort_var); + + for (vpp = list; count--; vpp++) + showvar(*vpp, cmd, xtra, show_value); + + /* no free(list), will be used again next time ... */ + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +static void __dead +export_usage(const char *cmd) +{ +#ifdef SMALL + if (*cmd == 'r') + error("Usage: %s [ -p | var[=val]... ]", cmd); + else + error("Usage: %s [ -p | [-n] var[=val]... ]", cmd); +#else + if (*cmd == 'r') + error("Usage: %s [-p [var...] | -q var... | var[=val]... ]", cmd); + else + error( + "Usage: %s [ -px [var...] | -q[x] var... | [-n|x] var[=val]... ]", + cmd); +#endif +} + +int +exportcmd(int argc, char **argv) +{ + struct var *vp; + char *name; + const char *p = argv[0]; + int flag = p[0] == 'r'? VREADONLY : VEXPORT; + int pflg = 0; + int nflg = 0; +#ifndef SMALL + int xflg = 0; + int qflg = 0; +#endif + int res; + int c; + int f; + +#ifdef SMALL +#define EXPORT_OPTS "np" +#else +#define EXPORT_OPTS "npqx" +#endif + + while ((c = nextopt(EXPORT_OPTS)) != '\0') { + +#undef EXPORT_OPTS + + switch (c) { + case 'n': + if (pflg || flag == VREADONLY +#ifndef SMALL + || qflg || xflg +#endif + ) + export_usage(p); + nflg = 1; + break; + case 'p': + if (nflg +#ifndef SMALL + || qflg +#endif + ) + export_usage(p); + pflg = 3; + break; +#ifndef SMALL + case 'q': + if (nflg || pflg) + export_usage(p); + qflg = 1; + break; + case 'x': + if (nflg || flag == VREADONLY) + export_usage(p); + flag = VNOEXPORT; + xflg = 1; + break; +#endif + } + } + + if ((nflg +#ifndef SMALL + || qflg +#endif + ) && *argptr == NULL) + export_usage(p); + +#ifndef SMALL + if (pflg && *argptr != NULL) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp && vp->flags & flag) + showvar(vp, p, xflg ? "-x" : NULL, 1); + } + return 0; + } +#endif + + if (pflg || *argptr == NULL) + return showvars( pflg ? p : 0, flag, pflg, +#ifndef SMALL + pflg && xflg ? "-x" : +#endif + NULL ); + + res = 0; +#ifndef SMALL + if (qflg) { + while ((name = *argptr++) != NULL) { + int len; + + vp = find_var(name, NULL, &len); + if (name[len] == '=') + export_usage(p); + if (!goodname(name)) + error("%s: bad variable name", name); + + if (vp == NULL || !(vp->flags & flag)) + res = 1; + } + return res; + } +#endif + + while ((name = *argptr++) != NULL) { + int len; + + f = flag; + + vp = find_var(name, NULL, &len); + p = name + len; + if (*p++ != '=') + p = NULL; + + if (vp != NULL) { + if (nflg) + vp->flags &= ~flag; + else if (flag&VEXPORT && vp->flags&VNOEXPORT) { + /* note we go ahead and do any assignment */ + sh_warnx("%.*s: not available for export", + len, name); + res = 1; + } else { + if (flag == VNOEXPORT) + vp->flags &= ~VEXPORT; + + /* if not NULL will be done in setvar below */ + if (p == NULL) + vp->flags |= flag; + } + if (p == NULL) + continue; + } else if (nflg && p == NULL && !goodname(name)) + error("%s: bad variable name", name); + + if (!nflg || p != NULL) + setvar(name, p, f); + } + return res; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc, char **argv) +{ + char *name; + int c; + int flags = 0; /*XXX perhaps VUNSET from a -o option value */ + + if (! in_function()) + error("Not in a function"); + + /* upper case options, as bash stole all the good ones ... */ + while ((c = nextopt("INx")) != '\0') + switch (c) { + case 'I': flags &= ~VUNSET; break; + case 'N': flags |= VUNSET; break; + case 'x': flags |= VEXPORT; break; + } + + while ((name = *argptr++) != NULL) { + mklocal(name, flags); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, its + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +void +mklocal(const char *name, int flags) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + char *p; + p = ckmalloc(sizeof_optlist); + lvp->text = memcpy(p, optlist, sizeof_optlist); + lvp->rfunc = NULL; + vp = NULL; + xtrace_clone(0); + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + flags &= ~VNOEXPORT; + if (strchr(name, '=')) + setvareq(savestr(name), + VSTRFIXED | (flags & ~VUNSET)); + else + setvar(name, NULL, VSTRFIXED|flags); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + lvp->rfunc = NULL; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + lvp->v_u = vp->v_u; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (flags & (VDOEXPORT | VUNSET)) + vp->flags &= ~VNOEXPORT; + if (vp->flags & VNOEXPORT && + (flags & (VEXPORT|VDOEXPORT|VUNSET)) == VEXPORT) + flags &= ~VEXPORT; + if (flags & (VNOEXPORT | VUNSET)) + vp->flags &= ~VEXPORT; + flags &= ~VNOEXPORT; + if (name[vp->name_len] == '=') + setvareq(savestr(name), flags & ~VUNSET); + else if (flags & VUNSET) + unsetvar(name, 0); + else + vp->flags |= flags & (VUNSET|VEXPORT); + + if (vp == &line_num) { + if (name[vp->name_len] == '=') + funclinebase = funclineabs -1; + else + funclinebase = 0; + } + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + VTRACE(DBG_VARS, ("poplocalvar %s\n", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof_optlist); + ckfree(lvp->text); + xtrace_pop(); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + (void)unsetvar(vp->text, 0); + } else { + if (lvp->func && (lvp->flags & (VNOFUNC|VFUNCREF)) == 0) + (*lvp->func)(lvp->text + vp->name_len + 1); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + vp->v_u = lvp->v_u; + } + ckfree(lvp); + } +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("List assignment not implemented"); + return 0; +} + + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ + +int +unsetcmd(int argc, char **argv) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int flg_x = 0; + int ret = 0; + + while ((i = nextopt("efvx")) != '\0') { + switch (i) { + case 'f': + flg_func = 1; + break; + case 'e': + case 'x': + flg_x = (2 >> (i == 'e')); + /* FALLTHROUGH */ + case 'v': + flg_var = 1; + break; + } + } + + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap, flg_x); + } + return ret; +} + + +/* + * Unset the specified variable. + */ + +int +unsetvar(const char *s, int unexport) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return 0; + + if (vp->flags & VREADONLY && !(unexport & 1)) + return 1; + + INTOFF; + if (unexport & 1) { + vp->flags &= ~VEXPORT; + } else { + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, nullstr, 0); + if (!(unexport & 2)) + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags&(VEXPORT|VSTRFIXED|VREADONLY|VNOEXPORT)) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + } + INTON; + return 0; +} + + +/* + * Returns true if the two strings specify the same varable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +STATIC int +strequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +STATIC struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + + len = p - name; + if (lenp) + *lenp = len; + + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} + +/* + * The following are the functions that create the values for + * shell variables that are dynamically produced when needed. + * + * The output strings cannot be malloc'd as there is nothing to + * free them - callers assume these are ordinary variables where + * the value returned is vp->text + * + * Each function needs its own storage space, as the results are + * used to create processes' environment, and (if exported) all + * the values will (might) be needed simultaneously. + * + * It is not a problem if a var is updated while nominally in use + * somewhere, all these are intended to be dynamic, the value they + * return is not guaranteed, an updated vaue is just as good. + * + * So, malloc a single buffer for the result of each function, + * grow, and even shrink, it as needed, but once we have one that + * is a suitable size for the actual usage, simply hold it forever. + * + * For a SMALL shell we implement only LINENO, none of the others, + * and give it just a fixed length static buffer for its result. + */ + +#ifndef SMALL + +struct space_reserved { /* record of space allocated for results */ + char *b; + int len; +}; + +/* rough (over-)estimate of the number of bytes needed to hold a number */ +static int +digits_in(intmax_t number) +{ + int res = 0; + + if (number & ~((1LL << 62) - 1)) + res = 64; /* enough for 2^200 and a bit more */ + else if (number & ~((1LL << 32) - 1)) + res = 20; /* enough for 2^64 */ + else if (number & ~((1 << 23) - 1)) + res = 10; /* enough for 2^32 */ + else + res = 8; /* enough for 2^23 or smaller */ + + return res; +} + +static int +make_space(struct space_reserved *m, int bytes) +{ + void *p; + + if (m->len >= bytes && m->len <= (bytes<<2)) + return 1; + + bytes = SHELL_ALIGN(bytes); + /* not ckrealloc() - we want failure, not error() here */ + p = realloc(m->b, bytes); + if (p == NULL) /* what we had should still be there */ + return 0; + + m->b = p; + m->len = bytes; + m->b[bytes - 1] = '\0'; + + return 1; +} +#endif + +char * +get_lineno(struct var *vp) +{ +#ifdef SMALL +#define length (8 + 10) /* 10 digits is enough for a 32 bit line num */ + static char result[length]; +#else + static struct space_reserved buf; +#define result buf.b +#define length buf.len +#endif + int ln = line_number; + + if (vp->flags & VUNSET) + return NULL; + + ln -= funclinebase; + +#ifndef SMALL + if (!make_space(&buf, vp->name_len + 2 + digits_in(ln))) + return vp->text; +#endif + + snprintf(result, length, "%.*s=%d", vp->name_len, vp->text, ln); + return result; +} +#undef result +#undef length + +#ifndef SMALL + +char * +get_hostname(struct var *vp) +{ + static struct space_reserved buf; + + if (vp->flags & VUNSET) + return NULL; + + if (!make_space(&buf, vp->name_len + 2 + 256)) + return vp->text; + + memcpy(buf.b, vp->text, vp->name_len + 1); /* include '=' */ + (void)gethostname(buf.b + vp->name_len + 1, + buf.len - vp->name_len - 3); + return buf.b; +} + +char * +get_tod(struct var *vp) +{ + static struct space_reserved buf; /* space for answers */ + static struct space_reserved tzs; /* remember TZ last used */ + static timezone_t last_zone; /* timezone data for tzs zone */ + const char *fmt; + char *tz; + time_t now; + struct tm tm_now, *tmp; + timezone_t zone = NULL; + static char t_err[] = "time error"; + int len; + + if (vp->flags & VUNSET) + return NULL; + + fmt = lookupvar("ToD_FORMAT"); + if (fmt == NULL) + fmt="%T"; + tz = lookupvar("TZ"); + (void)time(&now); + + if (tz != NULL) { + if (tzs.b == NULL || strcmp(tzs.b, tz) != 0) { + if (make_space(&tzs, strlen(tz) + 1)) { + INTOFF; + strcpy(tzs.b, tz); + if (last_zone) + tzfree(last_zone); + last_zone = zone = tzalloc(tz); + INTON; + } else + zone = tzalloc(tz); + } else + zone = last_zone; + + tmp = localtime_rz(zone, &now, &tm_now); + } else + tmp = localtime_r(&now, &tm_now); + + len = (strlen(fmt) * 4) + vp->name_len + 2; + while (make_space(&buf, len)) { + memcpy(buf.b, vp->text, vp->name_len+1); + if (tmp == NULL) { + if (buf.len >= vp->name_len+2+(int)(sizeof t_err - 1)) { + strcpy(buf.b + vp->name_len + 1, t_err); + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + len = vp->name_len + 4 + sizeof t_err - 1; + continue; + } + if (strftime_z(zone, buf.b + vp->name_len + 1, + buf.len - vp->name_len - 2, fmt, tmp)) { + if (zone && zone != last_zone) + tzfree(zone); + return buf.b; + } + if (len >= 4096) /* Let's be reasonable */ + break; + len <<= 1; + } + if (zone && zone != last_zone) + tzfree(zone); + return vp->text; +} + +char * +get_seconds(struct var *vp) +{ + static struct space_reserved buf; + intmax_t secs; + + if (vp->flags & VUNSET) + return NULL; + + secs = (intmax_t)time((time_t *)0) - sh_start_time; + if (!make_space(&buf, vp->name_len + 2 + digits_in(secs))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, secs); + return buf.b; +} + +char * +get_euser(struct var *vp) +{ + static struct space_reserved buf; + static uid_t lastuid = 0; + uid_t euid; + struct passwd *pw; + + if (vp->flags & VUNSET) + return NULL; + + euid = geteuid(); + if (buf.b != NULL && lastuid == euid) + return buf.b; + + pw = getpwuid(euid); + if (pw == NULL) + return vp->text; + + if (make_space(&buf, vp->name_len + 2 + strlen(pw->pw_name))) { + lastuid = euid; + snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text, + pw->pw_name); + return buf.b; + } + + return vp->text; +} + +char * +get_random(struct var *vp) +{ + static struct space_reserved buf; + static intmax_t random_val = 0; + +#ifdef USE_LRAND48 +#define random lrand48 +#define srandom srand48 +#endif + + if (vp->flags & VUNSET) + return NULL; + + if (vp->text != buf.b) { + /* + * Either initialisation, or a new seed has been set + */ + if (vp->text[vp->name_len + 1] == '\0') { + int fd; + + /* + * initialisation (without pre-seeding), + * or explictly requesting a truly random seed. + */ + fd = open("/dev/urandom", 0); + if (fd == -1) { + out2str("RANDOM initialisation failed\n"); + random_val = (getpid()<<3) ^ time((time_t *)0); + } else { + int n; + + do { + n = read(fd,&random_val,sizeof random_val); + } while (n != sizeof random_val); + close(fd); + } + } else + /* good enough for today */ + random_val = strtoimax(vp->text+vp->name_len+1,NULL,0); + + srandom((long)random_val); + } + +#if 0 + random_val = (random_val + 1) & 0x7FFF; /* 15 bit "random" numbers */ +#else + random_val = (random() >> 5) & 0x7FFF; +#endif + + if (!make_space(&buf, vp->name_len + 2 + digits_in(random_val))) + return vp->text; + + snprintf(buf.b, buf.len, "%.*s=%jd", vp->name_len, vp->text, + random_val); + + if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free(vp->text); + vp->flags |= VTEXTFIXED; + vp->text = buf.b; + + return vp->text; +#undef random +#undef srandom +} + +#endif /* SMALL */ |