#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.91 2026/05/28 10:07:58 kre Exp $");
#endif
#endif
#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>
#ifndef SMALL
#include <locale.h>
#endif
#include "shell.h"
#include "output.h"
#include "expand.h"
#include "nodes.h"
#include "eval.h"
#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 *);
STATIC void set_locale_var(const char *, int, struct var *);
void init_locale_vars(void);
#endif
struct localvar *localvars;
#ifndef SMALL
struct var vhistsize;
struct var vhistfile;
struct var vhistappend;
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;
struct var lc_all;
struct var lc_collate;
struct var lc_ctype;
struct var lc_messages;
struct var lc_monetary;
struct var lc_numeric;
struct var lc_time;
struct var lc_lang;
#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 } },
{ &vhistfile, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE=",
{ .set_func= sethistfile } },
{ &vhistappend, VSTRFIXED|VTEXTFIXED|VUNSET, "HISTAPPEND=",
{ .set_func= sethistappend } },
#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 } },
{ &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 } },
{ &lc_all, VSTRFIXED|VTEXTFIXED|VUNSET|VFUNCPOST, "LC_ALL=",
{ .set_func= set_locale_var } },
{ &lc_collate, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_COLLATE=",
{ .set_func= set_locale_var } },
{ &lc_ctype, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE=",
{ .set_func= set_locale_var } },
{ &lc_messages, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_MESSAGES=",
{ .set_func= set_locale_var } },
{ &lc_monetary, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_MONETARY=",
{ .set_func= set_locale_var } },
{ &lc_numeric, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_NUMERIC=",
{ .set_func= set_locale_var } },
{ &lc_time, VSTRFIXED|VTEXTFIXED|VUNSET, "LC_TIME=",
{ .set_func= set_locale_var } },
{ &lc_lang, VSTRFIXED|VTEXTFIXED|VUNSET|VFUNCPOST, "LANG=",
{ .set_func= set_locale_var } },
#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 *);
#ifdef mkinit
INCLUDE <stdio.h>
INCLUDE <unistd.h>
INCLUDE <time.h>
INCLUDE "var.h"
INCLUDE "version.h"
MKINIT char **environ;
MKINIT void setvareqsafe(char *, int);
MKINIT void init_locale_vars(void);
INIT {
char **envp;
char buf[64];
#ifndef SMALL
sh_start_time = (intmax_t)time((time_t *)0);
#endif
initvar();
for (envp = environ ; *envp ; envp++) {
if (strchr(*envp, '=')) {
setvareqsafe(*envp, VEXPORT|VTEXTFIXED|VUNSAFE);
}
}
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);
init_locale_vars();
#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
#ifdef REJECT_NULS
" REJECT_NULS"
#endif
#ifdef RESCUEDIR
" RESCUE"
#endif
, VTEXTFIXED|VREADONLY|VNOEXPORT);
setvar("LINENO", "1", VTEXTFIXED);
}
#endif
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;
}
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);
setvarsafe("PSc", (u == 0 ? "#" : "$"), 0);
}
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;
}
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;
}
void
setvareqsafe(char *s, int flags)
{
struct jmploc jmploc;
struct jmploc * const savehandler = handler;
volatile int e_s = errors_suppressed;
if (!setjmp(jmploc.loc)) {
handler = &jmploc;
errors_suppressed = 1;
setvareq(s, flags);
}
handler = savehandler;
errors_suppressed = e_s;
}
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;
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));
}
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|VFUNCPOST)) &&
!(flags & VNOFUNC))
(*vp->func)(s + vp->name_len + 1, flags, vp);
if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
ckfree(vp->text);
if (vp->rfunc && (vp->flags & (VFUNCREF|VSPECIAL)) == VFUNCREF)
vp->rfunc = NULL;
vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET|VUNSAFE);
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;
if (vp->func &&
(vp->flags & (VFUNCREF|VFUNCPOST)) == VFUNCPOST &&
!(flags & VNOFUNC))
(*vp->func)(s + vp->name_len + 1, flags, vp);
if (vp == &vmpath || (vp == &vmail && ! mpathset()))
chkmail(1);
INTON;
return;
}
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));
}
void
setvar_invocation(int argc, char **argv)
{
char value[32];
char *v;
v = value;
*v++ = '!';
if (argc > 0 && argv[0] != NULL && argv[0][0] == '-')
*v++ = '-';
if (shellparam.nparam == 0)
*v++ = '0';
if (minusc)
*v++ = 'c';
if (commandname)
*v++ = 'f';
if (iflag)
*v++ = 'i';
if (loginsh)
*v++ = 'l';
if (privileged)
*v++ = 'p';
if (sflag)
*v++ = 's';
*v++ = '\0';
setvar("NBSH_INVOCATION", value, VNOEXPORT);
}
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);
}
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;
}
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;
}
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;
}
#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();
}
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 (__predict_false(*v1 == *v2))
return 0;
if (__predict_false(*t1 == *t2)) {
char *p, *s;
STARTSTACKSTR(p);
if ((*v1)->name_len <= (*v2)->name_len) {
CHECKSTRSPACE((*v1)->name_len + 1, p);
s = t1;
t1 = p;
} else {
CHECKSTRSPACE((*v2)->name_len + 1, p);
s = t2;
t2 = p;
}
while (*s && *s != '=') {
USTPUTC(*s, p);
s++;
}
USTPUTC('\0', p);
}
return strcoll(t1, t2);
}
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 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);
return 0;
}
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) {
sh_warnx("%.*s: not available for export",
len, name);
res = 1;
} else {
if (flag == VNOEXPORT)
vp->flags &= ~VEXPORT;
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;
}
int
localcmd(int argc, char **argv)
{
char *name;
int c;
int flags = 0;
if (! in_function())
error("Not in a function");
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;
}
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;
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;
}
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) {
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,
lvp->flags, lvp->vp);
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;
}
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'));
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;
}
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' || !(vp->flags & VUNSET))
setvar(s, nullstr, VUNSET);
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;
}
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;
}
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;
if (len == 0)
return NULL;
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;
}
#ifndef SMALL
struct space_reserved {
char *b;
int len;
};
static int
digits_in(intmax_t number)
{
int res = 0;
if (number & ~((1LL << 62) - 1))
res = 64;
else if (number & ~((1LL << 32) - 1))
res = 20;
else if (number & ~((1 << 23) - 1))
res = 10;
else
res = 8;
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;
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)
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);
(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;
static struct space_reserved tzs;
static timezone_t last_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 (zone != NULL) {
if (strftime_z(zone, buf.b + vp->name_len + 1,
buf.len - vp->name_len - 2, fmt, tmp)) {
if (zone != last_zone) {
tzfree(zone);
INTON;
}
return buf.b;
}
} else if (strftime(buf.b + vp->name_len + 1,
buf.len - vp->name_len - 2, fmt, tmp))
return buf.b;
if (len >= 4096)
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) {
if (vp->text[vp->name_len + 1] == '\0') {
int fd;
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
random_val = strtoimax(vp->text+vp->name_len+1,NULL,0);
srandom((long)random_val);
}
#if 0
random_val = (random_val + 1) & 0x7FFF;
#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;
}
struct lc_vars {
const char *name;
int category;
struct var *vp;
};
const struct lc_vars lc_vars[] = {
{ .name= "LC_ALL", .category= LC_ALL, .vp= &lc_all },
{ .name= "LC_COLLATE", .category= LC_COLLATE, .vp= &lc_collate },
{ .name= "LC_CTYPE", .category= LC_CTYPE, .vp= &lc_ctype },
{ .name= "LC_MESSAGES", .category= LC_MESSAGES, .vp= &lc_messages },
{ .name= "LC_MONETARY", .category= LC_MONETARY, .vp= &lc_monetary },
{ .name= "LC_NUMERIC", .category= LC_NUMERIC, .vp= &lc_numeric },
{ .name= "LC_TIME", .category= LC_TIME, .vp= &lc_time },
{ .name= "LANG", .category= LC_ALL, .vp= &lc_lang },
{ .name= NULL, .category= 0, .vp= NULL }
};
STATIC void
set_locale_var(const char *val, int flags, struct var *vp)
{
const struct lc_vars *lv;
if (flags & VUNSAFE)
return;
for (lv = lc_vars; lv->name != NULL; lv++) {
if (lv->vp != vp)
continue;
if (flags & VUNSET) {
if (lv->category == LC_ALL) {
if (lv->vp == &lc_lang)
return;
init_locale_vars();
return;
}
if (lv->vp->flags & VUNSET)
return;
vp = NULL;
if (!(lc_all.flags & VUNSET))
vp = &lc_all;
else if (!(lc_lang.flags & VUNSET))
vp = &lc_lang;
if (vp != NULL)
val = vp->text + vp->name_len + 1;
else
val = "C";
set_locale_var(val, 0, lv->vp);
} else {
char *old;
if (lv->category == LC_ALL) {
init_locale_vars();
return;
}
old = setlocale(lv->category, NULL);
if (old != NULL && strcmp(old, val) != 0) {
if (setlocale(lv->category, val) == NULL) {
if (lv->category != LC_COLLATE)
outfmt(out2,
"Setting %s to '%s' failed"
" setlocale(3)\n", lv->name, val);
} else if (lv->category == LC_CTYPE) {
if (iflag && (Eflag || Vflag)) {
int oE = Eflag, oV = Vflag;
Eflag = Vflag = 0;
histedit();
Eflag = oE, Vflag=oV;
histedit();
}
}
}
}
return;
}
}
void
init_locale_vars(void)
{
const struct lc_vars *lv;
const char *defval;
if (!(lc_all.flags & VUNSET)) {
for (lv = lc_vars; lv->name != NULL; lv++) {
if (lv->category == LC_ALL)
continue;
set_locale_var(lc_all.text + 7, 0, lv->vp);
}
return;
}
if (lc_lang.flags & VUNSET)
defval = "C";
else
defval = lc_lang.text + 5;
for (lv = lc_vars; lv->name != NULL; lv++) {
if (lv->category == LC_ALL)
continue;
set_locale_var( (lv->vp->flags & VUNSET ? defval :
lv->vp->text + lv->vp->name_len + 1),
0, lv->vp);
}
}
#endif