#include "shell_common.h"
#if ENABLE_FEATURE_SH_MATH
# include "math.h"
+#else
+typedef long arith_t;
+# define ARITH_FMT "%ld"
#endif
#if ENABLE_ASH_RANDOM_SUPPORT
# include "random.h"
va_list ap;
int ret;
- va_start(ap, fmt);
INT_OFF;
+ va_start(ap, fmt);
ret = vsnprintf(outbuf, length, fmt, ap);
va_end(ap);
INT_ON;
static struct parsefile *g_parsefile = &basepf; /* current input file */
static int startlinno; /* line # where last token started */
static char *commandname; /* currently executing command */
-static struct strlist *cmdenviron; /* environment for builtin command */
/* ============ Message printing */
/*
* Search the environment of a builtin command.
*/
-static const char *
+static ALWAYS_INLINE const char *
bltinlookup(const char *name)
{
- struct strlist *sp;
-
- for (sp = cmdenviron; sp; sp = sp->next) {
- if (varcmp(sp->text, name) == 0)
- return var_end(sp->text);
- }
return lookupvar(name);
}
* will go away.
* Called with interrupts off.
*/
-static void
+static struct var *
setvareq(char *s, int flags)
{
struct var *vp, **vpp;
vpp = hashvar(s);
flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
- vp = *findvar(vpp, s);
+ vpp = findvar(vpp, s);
+ vp = *vpp;
if (vp) {
if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
const char *n;
}
if (flags & VNOSET)
- return;
+ goto out;
if (vp->var_func && !(flags & VNOFUNC))
vp->var_func(var_end(s));
out_free:
if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
free(s);
- return;
+ goto out;
}
flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
} else {
/* variable s is not found */
if (flags & VNOSET)
- return;
+ goto out;
if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
goto out_free;
vp = ckzalloc(sizeof(*vp));
s = ckstrdup(s);
vp->var_text = s;
vp->flags = flags;
+
+ out:
+ return vp;
}
/*
* 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.
*/
-static void
+static struct var *
setvar(const char *name, const char *val, int flags)
{
const char *q;
char *nameeq;
size_t namelen;
size_t vallen;
+ struct var *vp;
q = endofname(name);
p = strchrnul(q, '=');
p = mempcpy(p, val, vallen);
}
*p = '\0';
- setvareq(nameeq, flags | VNOSAVE);
+ vp = setvareq(nameeq, flags | VNOSAVE);
INT_ON;
+
+ return vp;
}
static void FAST_FUNC
static void
unsetvar(const char *s)
{
- setvar0(s, NULL);
+ setvar(s, NULL, 0);
}
/*
/*
* Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
*/
-#if !ENABLE_FEATURE_SH_MATH
-/* cvtnum() is used even if math support is off (to prepare $? values and such) */
-typedef long arith_t;
-# define ARITH_FMT "%ld"
-#endif
static int
cvtnum(arith_t num)
{
int len;
- expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest);
- len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num);
+ /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+ len = sizeof(arith_t) * 3;
+ /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+ if (sizeof(arith_t) < 4) len += 2;
+
+ expdest = makestrspace(len, expdest);
+ len = fmtstr(expdest, len, ARITH_FMT, num);
STADJUST(len, expdest);
return len;
}
#endif
/* argstr needs it */
-static char *evalvar(char *p, int flags, struct strlist *var_str_list);
+static char *evalvar(char *p, int flags);
/*
* Perform variable and command substitution. If EXP_FULL is set, output CTLESC
* characters to allow for further processing. Otherwise treat
* $@ like $* since no splitting will be performed.
- *
- * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
- * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it
- * for correct expansion of "B=$A" word.
*/
static void
-argstr(char *p, int flags, struct strlist *var_str_list)
+argstr(char *p, int flags)
{
static const char spclchars[] ALIGN1 = {
'=',
inquotes ^= EXP_QUOTED;
/* "$@" syntax adherence hack */
if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
- p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1;
+ p = evalvar(p + 1, flags | inquotes) + 1;
goto start;
}
addquote:
goto addquote;
case CTLVAR:
TRACE(("argstr: evalvar('%s')\n", p));
- p = evalvar(p, flags | inquotes, var_str_list);
+ p = evalvar(p, flags | inquotes);
TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
goto start;
case CTLBACKQ:
static const char *
subevalvar(char *p, char *varname, int strloc, int subtype,
- int startloc, int varflags, int flag, struct strlist *var_str_list)
+ int startloc, int varflags, int flag)
{
struct nodelist *saveargbackq = argbackq;
int quotes = flag & QUOTES_ESC;
// p, varname, strloc, subtype, startloc, varflags, quotes);
argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
- (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
- var_str_list);
+ (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0)
+ );
STPUTC('\0', expdest);
argbackq = saveargbackq;
startp = (char *)stackblock() + startloc;
* ash -c 'echo ${#1#}' name:'1=#'
*/
static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp)
+varvalue(char *name, int varflags, int flags, int *quotedp)
{
const char *p;
int num;
goto value;
default:
/* NB: name has form "VAR=..." */
-
- /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
- * which should be considered before we check variables. */
- if (var_str_list) {
- unsigned name_len = (strchrnul(name, '=') - name) + 1;
- p = NULL;
- do {
- char *str, *eq;
- str = var_str_list->text;
- eq = strchr(str, '=');
- if (!eq) /* stop at first non-assignment */
- break;
- eq++;
- if (name_len == (unsigned)(eq - str)
- && strncmp(str, name, name_len) == 0
- ) {
- p = eq;
- /* goto value; - WRONG! */
- /* think "A=1 A=2 B=$A" */
- }
- var_str_list = var_str_list->next;
- } while (var_str_list);
- if (p)
- goto value;
- }
p = lookupvar(name);
value:
if (!p)
* input string.
*/
static char *
-evalvar(char *p, int flag, struct strlist *var_str_list)
+evalvar(char *p, int flag)
{
char varflags;
char subtype;
p = strchr(p, '=') + 1; //TODO: use var_end(p)?
again:
- varlen = varvalue(var, varflags, flag, var_str_list, "ed);
+ varlen = varvalue(var, varflags, flag, "ed);
if (varflags & VSNUL)
varlen--;
if (varlen < 0) {
argstr(
p,
- flag | EXP_TILDE | EXP_WORD,
- var_str_list
+ flag | EXP_TILDE | EXP_WORD
);
goto end;
}
goto record;
subevalvar(p, var, 0, subtype, startloc, varflags,
- flag & ~QUOTES_ESC, var_str_list);
+ flag & ~QUOTES_ESC);
varflags &= ~VSNUL;
/*
* Remove any recorded regions beyond
STPUTC('\0', expdest);
patloc = expdest - (char *)stackblock();
if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
- startloc, varflags, flag, var_str_list)) {
+ startloc, varflags, flag)) {
int amount = expdest - (
(char *)stackblock() + patloc - 1
);
argbackq = arg->narg.backquote;
STARTSTACKSTR(expdest);
TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
- argstr(arg->narg.text, flag,
- /* var_str_list: */ arglist ? arglist->list : NULL);
+ argstr(arg->narg.text, flag);
p = _STPUTC('\0', expdest);
expdest = p - 1;
if (arglist == NULL) {
setstackmark(&smark);
argbackq = pattern->narg.backquote;
STARTSTACKSTR(expdest);
- argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
- /* var_str_list: */ NULL);
+ argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
STACKSTRNUL(expdest);
ifsfree();
result = patmatch(stackblock(), val);
clearenv();
while (*envp)
putenv(*envp++);
+ popredir(/*drop:*/ 1, /*restore:*/ 0);
run_applet_no_and_exit(applet_no, cmd, argv);
}
/* re-exec ourselves with the new arguments */
#endif
}
-static struct localvar *localvars;
+struct localvar_list {
+ struct localvar_list *next;
+ struct localvar *lv;
+};
+
+static struct localvar_list *localvar_stack;
/*
* Called after a function returns.
* Interrupts must be off.
*/
static void
-poplocalvars(void)
+poplocalvars(int keep)
{
- struct localvar *lvp;
+ struct localvar_list *ll;
+ struct localvar *lvp, *next;
struct var *vp;
- while ((lvp = localvars) != NULL) {
- localvars = lvp->next;
+ INT_OFF;
+ ll = localvar_stack;
+ localvar_stack = ll->next;
+
+ next = ll->lv;
+ free(ll);
+
+ while ((lvp = next) != NULL) {
+ next = lvp->next;
vp = lvp->vp;
TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
- if (vp == NULL) { /* $- saved */
+ if (keep) {
+ int bits = VSTRFIXED;
+
+ if (lvp->flags != VUNSET) {
+ if (vp->var_text == lvp->text)
+ bits |= VTEXTFIXED;
+ else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
+ free((char*)lvp->text);
+ }
+
+ vp->flags &= ~bits;
+ vp->flags |= (lvp->flags & bits);
+
+ if ((vp->flags &
+ (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+ unsetvar(vp->var_text);
+ } else if (vp == NULL) { /* $- saved */
memcpy(optlist, lvp->text, sizeof(optlist));
free((char*)lvp->text);
optschanged();
- } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ } else if (lvp->flags == VUNSET) {
+ vp->flags &= ~(VSTRFIXED|VREADONLY);
unsetvar(vp->var_text);
} else {
if (vp->var_func)
}
free(lvp);
}
+ INT_ON;
+}
+
+/*
+ * Create a new localvar environment.
+ */
+static struct localvar_list *
+pushlocalvars(void)
+{
+ struct localvar_list *ll;
+
+ INT_OFF;
+ ll = ckzalloc(sizeof(*ll));
+ /*ll->lv = NULL; - zalloc did it */
+ ll->next = localvar_stack;
+ localvar_stack = ll;
+ INT_ON;
+
+ return ll->next;
+}
+
+static void
+unwindlocalvars(struct localvar_list *stop)
+{
+ while (localvar_stack != stop)
+ poplocalvars(0);
}
static int
evalfun(struct funcnode *func, int argc, char **argv, int flags)
{
volatile struct shparam saveparam;
- struct localvar *volatile savelocalvars;
struct jmploc *volatile savehandler;
struct jmploc jmploc;
int e;
saveparam = shellparam;
- savelocalvars = localvars;
savehandler = exception_handler;
e = setjmp(jmploc.loc);
if (e) {
}
INT_OFF;
exception_handler = &jmploc;
- localvars = NULL;
shellparam.malloced = 0;
func->count++;
funcnest++;
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
+ pushlocalvars();
evaltree(func->n.narg.next, flags & EV_TESTED);
+ poplocalvars(0);
funcdone:
INT_OFF;
funcnest--;
freefunc(func);
- poplocalvars();
- localvars = savelocalvars;
freeparam(&shellparam);
shellparam = saveparam;
exception_handler = savehandler;
* x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
* x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
*/
- lvp = localvars;
+ lvp = localvar_stack->lv;
while (lvp) {
if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) {
if (eq)
if (vp == NULL) {
/* variable did not exist yet */
if (eq)
- setvareq(name, VSTRFIXED);
+ vp = setvareq(name, VSTRFIXED);
else
- setvar(name, NULL, VSTRFIXED);
- vp = *vpp; /* the new variable */
+ vp = setvar(name, NULL, VSTRFIXED);
lvp->flags = VUNSET;
} else {
lvp->text = vp->var_text;
}
}
lvp->vp = vp;
- lvp->next = localvars;
- localvars = lvp;
+ lvp->next = localvar_stack->lv;
+ localvar_stack->lv = lvp;
ret:
INT_ON;
}
{
char *name;
- if (!funcnest)
+ if (!localvar_stack)
ash_msg_and_raise_error("not in a function");
argv = argptr;
#if ENABLE_FEATURE_SH_MATH
{ BUILTIN_NOSPEC "let" , letcmd },
#endif
- { BUILTIN_ASSIGN "local" , localcmd },
+ { BUILTIN_SPEC_REG_ASSG "local" , localcmd },
#if ENABLE_ASH_PRINTF
{ BUILTIN_REGULAR "printf" , printfcmd },
#endif
static const struct builtincmd null_bltin = {
"\0\0", bltincmd /* why three NULs? */
};
+ struct localvar_list *localvar_stop;
struct stackmark smark;
union node *argp;
struct arglist arglist;
/* First expand the arguments. */
TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
setstackmark(&smark);
+ localvar_stop = pushlocalvars();
back_exitstatus = 0;
cmdentry.cmdtype = CMDBUILTIN;
spp = varlist.lastp;
expandarg(argp, &varlist, EXP_VARTILDE);
+ mklocal((*spp)->text);
+
/*
* Modify the command lookup path, if a PATH= assignment
* is present
/* NOTREACHED */
} /* default */
case CMDBUILTIN:
- cmdenviron = varlist.list;
- if (cmdenviron) {
- struct strlist *list = cmdenviron;
- int i = VNOSET;
- if (spclbltin > 0 || argc == 0) {
- i = 0;
- if (cmd_is_exec && argc > 1)
- i = VEXPORT;
- }
- listsetvar(list, i);
+ if (spclbltin > 0 || argc == 0) {
+ poplocalvars(1);
+ if (cmd_is_exec && argc > 1)
+ listsetvar(varlist.list, VEXPORT);
}
+
/* Tight loop with builtins only:
* "while kill -0 $child; do true; done"
* will never exit even if $child died, unless we do this
goto readstatus;
case CMDFUNCTION:
- listsetvar(varlist.list, 0);
+ poplocalvars(1);
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
out:
if (cmd->ncmd.redirect)
popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+ unwindlocalvars(localvar_stop);
if (lastarg) {
/* dsl: I think this is intended to be used to support
* '_' in 'vi' command mode during line editing...
char *fullname;
char **argv;
char *args_need_save;
- struct strlist *sp;
volatile struct shparam saveparam;
- for (sp = cmdenviron; sp; sp = sp->next)
- setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+//???
+// struct strlist *sp;
+// for (sp = cmdenviron; sp; sp = sp->next)
+// setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
nextopt(nullstr); /* handle possible "--" */
argv = argptr;
/* from redir.c: */
while (redirlist)
popredir(/*drop:*/ 0, /*restore:*/ 0);
+
+ /* from var.c: */
+ unwindlocalvars(NULL);
}
#if PROFILE