/* $NetBSD: var.c,v 1.80 2021/08/09 11:29:30 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 #ifndef lint #if 0 static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; #else __RCSID("$NetBSD: var.c,v 1.80 2021/08/09 11:29:30 kre Exp $"); #endif #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include /* * 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; STATIC int makespecial(const char *); /* * Initialize the variable symbol tables and import the environment */ #ifdef mkinit INCLUDE INCLUDE INCLUDE 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; char *p; v = find_var(name, NULL, NULL); if (v == NULL || v->flags & VUNSET) return NULL; if (v->rfunc && (v->flags & VFUNCREF) != 0) { p = (*v->rfunc)(v); if (p == NULL) return NULL; } else p = v->text; return p + 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; char *p; 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) { p = (*v->rfunc)(v); if (p == NULL) return NULL; } else p = v->text; return p + 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); if (*ep != NULL) ep++; } 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; p = vp->text; if (vp->rfunc && (vp->flags & VFUNCREF) != 0) { p = (*vp->rfunc)(vp); if (p == NULL) { if (!(show_value & 2)) return; p = vp->text; show_value = 0; } } if (cmd) out1fmt("%s ", cmd); if (xtra) out1fmt("%s ", xtra); 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 variable. 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); INTOFF; /* not ckrealloc() - we want failure, not error() here */ p = realloc(m->b, bytes); if (p != NULL) { m->b = p; m->len = bytes; m->b[bytes - 1] = '\0'; } INTON; return p != NULL; } #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) { INTOFF; if (make_space(&tzs, strlen(tz) + 1)) { 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); INTON; } 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); INTON; } return buf.b; } if (len >= 4096) /* Let's be reasonable */ break; len <<= 1; } if (zone && zone != last_zone) { tzfree(zone); INTON; } 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))) { INTOFF; lastuid = euid; snprintf(buf.b, buf.len, "%.*s=%s", vp->name_len, vp->text, pw->pw_name); INTON; 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 explicitly requesting a truly random seed. */ INTOFF; 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); } INTON; } 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); INTOFF; if (buf.b != vp->text && (vp->flags & (VTEXTFIXED|VSTACK)) == 0) free(vp->text); vp->flags |= VTEXTFIXED; vp->text = buf.b; INTON; return vp->text; #undef random #undef srandom } STATIC int makespecial(const char *name) { const struct varinit *ip; struct var *vp; CTRACE(DBG_VARS, ("makespecial('%s') -> ", name)); for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { if (strequal(ip->text, name)) { if (!(ip->flags & VFUNCREF)) { CTRACE(DBG_VARS, ("+1\n")); return 1; } INTOFF; vp->flags &= ~VUNSET; vp->v_u = ip->v_u; INTON; CTRACE(DBG_VARS, ("0\n")); return 0; } } CTRACE(DBG_VARS, ("1\n")); return 1; } int specialvarcmd(int argc, char **argv) { int res = 0; char **ap; (void) nextopt(""); if (!*argptr) error("Usage: specialvar var..."); for (ap = argptr; *ap ; ap++) res |= makespecial(*ap); return res; } #endif /* SMALL */