summaryrefslogtreecommitdiff
path: root/bin/sh/var.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/sh/var.c')
-rw-r--r--bin/sh/var.c1587
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 */