aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2021-03-17 01:31:47 -0500
committerRob Landley <rob@landley.net>2021-03-17 01:31:47 -0500
commit935217e6d470b0819f2970a4408cedca52fd9525 (patch)
treeb4d43778f9d0009fa1412871a3113330bb99893a
parent8c7af93bde1798da7d59182201b3599158bf926d (diff)
downloadtoybox-935217e6d470b0819f2970a4408cedca52fd9525.tar.gz
Add local variables and basic function() support plumbing.
Not fully wired up yet, probably a bunch of regressions.
-rw-r--r--tests/sh.test1
-rw-r--r--toys/pending/sh.c1141
2 files changed, 646 insertions, 496 deletions
diff --git a/tests/sh.test b/tests/sh.test
index abbb1c6e..efd906d6 100644
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -99,6 +99,7 @@ testing 'prefix is local for builtins' 'abc=123; abc=def unset abc; echo $abc' \
testing 'prefix localizes magic vars' \
'SECONDS=123; SECONDS=345 true; echo $SECONDS' '123\n' '' ''
shxpect 'body evaluated before variable exports' I$'a=x${} y${}\n' RE'y${}'
+testing '$NOTHING clears $_' 'true; $NOTHING; echo $_' '\n' '' ''
testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 51b3cc70..c83f0243 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -47,7 +47,7 @@ USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
-USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK))
@@ -172,6 +172,17 @@ config JOBS
-r Show running processes
-s Show stopped processes
+config LOCAL
+ bool
+ default n
+ depends on SH
+ help
+ usage: local [NAME[=VALUE]...]
+
+ Create a local variable that lasts until return from this function.
+ With no arguments lists local variables in current function context.
+ TODO: implement "declare" options.
+
config SHIFT
bool
default n
@@ -208,36 +219,42 @@ GLOBALS(
// keep ifs here: used to work around compiler limitation in run_command()
char *ifs, *isexec, *wcpat;
unsigned options, jobcnt, LINENO;
- int hfd, pid, bangpid, varslen, cdcount;
+ int hfd, pid, bangpid, varslen, cdcount, srclvl, fncount;
long long SECONDS;
- // global and local variables
- struct sh_vars {
- long flags;
- char *str;
- } *vars;
+// FUNCTION transplant pipelines from place to place?
+// function keyword can have pointer to function struct? Still refcnt?
+// is function body like HERE document? Lifetime rules
- // Parsed functions
+ // Callable functions
struct sh_function {
char *name;
struct sh_pipeline { // pipeline segments: linked list of arg w/metadata
struct sh_pipeline *next, *prev, *end;
- unsigned lineno;
- int count, here, type; // TODO abuse type to replace count during parsing
+ int count, here, type, lineno;
struct sh_arg {
char **v;
int c;
} arg[1];
} *pipeline;
- struct double_list *expect; // should be zero at end of parsing
} *functions;
// runtime function call stack
struct sh_fcall {
- struct sh_fcall *next;
- struct sh_function *func;
+ struct sh_fcall *next, *prev;
+
+ // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+ struct sh_vars {
+ long flags;
+ char *str;
+ } *vars;
+
+// struct sh_function *func;
struct sh_pipeline *pl;
- int *urd, pout;
+ int *urd, pout, varslen;
+ struct sh_arg arg;
+ struct arg_list *delete;
+ long shift;
// Runtime stack of nested if/else/fi and for/do/done contexts.
struct sh_blockstack {
@@ -261,22 +278,12 @@ GLOBALS(
struct sh_arg *raw, arg;
} *pp; // currently running process
- struct sh_callstack {
- struct sh_callstack *next;
- struct sh_function scratch;
- struct sh_arg arg;
- struct arg_list *delete;
- long shift;
- unsigned lineno;
- } *cc;
-
// job list, command line for $*, scratch space for do_wildcard_files()
struct sh_arg jobs, *wcdeck;
)
-// Can't yet avoid this prototype. Fundamental problem is $($($(blah))) nests,
-// leading to function loop with run->parse->run
-static int sh_run(char *new);
+// Prototype because $($($(blah))) nests, leading to run->parse->run loop
+int do_source(char *name, FILE *ff);
// ordered for greedy matching, so >&; becomes >& ; not > &;
// making these const means I need to typecast the const away later to
@@ -326,6 +333,20 @@ static void arg_add_del(struct sh_arg *arg, char *data,struct arg_list **delete)
arg_add(arg, push_arg(delete, data));
}
+// Assign one variable from malloced key=val string, returns var struct
+// TODO implement remaining types
+#define VAR_NOFREE (1<<10)
+#define VAR_WHITEOUT (1<<9)
+#define VAR_DICT (1<<8)
+#define VAR_ARRAY (1<<7)
+#define VAR_INT (1<<6)
+#define VAR_TOLOWER (1<<5)
+#define VAR_TOUPPER (1<<4)
+#define VAR_NAMEREF (1<<3)
+#define VAR_GLOBAL (1<<2)
+#define VAR_READONLY (1<<1)
+#define VAR_MAGIC (1<<0)
+
// return length of valid variable name
static char *varend(char *s)
{
@@ -336,26 +357,34 @@ static char *varend(char *s)
}
// Return index of variable within this list
-static struct sh_vars *findvar(char *name)
+static struct sh_vars *findvar(char *name, struct sh_fcall **pff)
{
int len = varend(name)-name;
- struct sh_vars *var = TT.vars+TT.varslen;
+ struct sh_fcall *ff = TT.ff;
+
+ // advance through locals to global context, ignoring whiteouts
+ if (len) do {
+ struct sh_vars *var = ff->vars+ff->varslen;
- if (len) while (var-- != TT.vars)
- if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
+ if (!var) continue;
+ if (pff) *pff = ff;
+ while (var-- != ff->vars)
+ if (pff || !(var->flags&VAR_WHITEOUT))
+ if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
+ } while ((ff = ff->next)!=TT.ff);
return 0;
}
-// Append variable to TT.vars, returning *struct. Does not check duplicates.
-static struct sh_vars *addvar(char *s)
+// Append variable to ff->vars, returning *struct. Does not check duplicates.
+static struct sh_vars *addvar(char *s, struct sh_fcall *ff)
{
- if (!(TT.varslen&31))
- TT.vars = xrealloc(TT.vars, (TT.varslen+32)*sizeof(*TT.vars));
- TT.vars[TT.varslen].flags = 0;
- TT.vars[TT.varslen].str = s;
+ if (!(ff->varslen&31))
+ ff->vars = xrealloc(ff->vars, (ff->varslen+32)*sizeof(*ff->vars));
+ ff->vars[ff->varslen].flags = 0;
+ ff->vars[ff->varslen].str = s;
- return TT.vars+TT.varslen++;
+ return ff->vars+ff->varslen++;
}
// TODO function to resolve a string into a number for $((1+2)) etc
@@ -370,18 +399,6 @@ long long do_math(char **s)
return ll;
}
-// Assign one variable from malloced key=val string, returns var struct
-// TODO implement remaining types
-#define VAR_DICT 256
-#define VAR_ARRAY 128
-#define VAR_INT 64
-#define VAR_TOLOWER 32
-#define VAR_TOUPPER 16
-#define VAR_NAMEREF 8
-#define VAR_GLOBAL 4
-#define VAR_READONLY 2
-#define VAR_MAGIC 1
-
// declare -aAilnrux
// ft
static struct sh_vars *setvar(char *s)
@@ -397,8 +414,8 @@ static struct sh_vars *setvar(char *s)
return 0;
}
if (!strncmp(s, "IFS=", 4)) TT.ifs = s+4;
- if (!(var = findvar(s))) return addvar(s);
- flags = var->flags;
+ if (!(var = findvar(s, 0))) return addvar(s, TT.ff->prev);
+ flags = (var->flags &= ~VAR_WHITEOUT);
if (flags&VAR_READONLY) {
error_msg("%.*s: read only", len, s);
@@ -419,9 +436,9 @@ static struct sh_vars *setvar(char *s)
// TODO: trailing garbage after do_math()?
if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(&ss);
else if (*s == 'R') srandom(do_math(&ss));
- } else if (flags&VAR_GLOBAL) xsetenv(var->str = s, 0);
- else {
- free(var->str);
+ } else {
+ if (!(flags&VAR_NOFREE)) free(var->str);
+ else var->flags ^= VAR_NOFREE;
var->str = s;
}
@@ -430,15 +447,24 @@ static struct sh_vars *setvar(char *s)
static void unsetvar(char *name)
{
- struct sh_vars *var = findvar(name);
- int ii = var-TT.vars;
+ struct sh_fcall *ff;
+ struct sh_vars *var = findvar(name, &ff);
+ int ii = var-ff->vars, len = varend(name)-name;
- if (!var) return;
- if (var->flags&VAR_GLOBAL) xunsetenv(name);
- else free(var->str);
+ // Is this freeable?
+ if (name[len]) return error_msg("bad %s", name);
+ if (!var || (var->flags&VAR_WHITEOUT)) return;
+ if (var->flags&VAR_READONLY) return error_msg("readonly %s", name);
- memmove(TT.vars+ii, TT.vars+ii+1, TT.varslen-ii);
- TT.varslen--;
+ // turn local into whiteout or free from global context
+ if (ff != TT.ff->prev) {
+ var->flags = VAR_WHITEOUT;
+ if (!(var->flags&VAR_NOFREE))
+ (var->str = xrealloc(var->str, len+2))[len+1] = 0;
+ } else {
+ if (!(var->flags&VAR_NOFREE)) free(var->str);
+ memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii);
+ }
}
static struct sh_vars *setvarval(char *name, char *val)
@@ -449,7 +475,7 @@ static struct sh_vars *setvarval(char *name, char *val)
// get value of variable starting at s.
static char *getvar(char *s)
{
- struct sh_vars *var = findvar(s);
+ struct sh_vars *var = findvar(s, 0);
if (!var) return 0;
@@ -458,7 +484,7 @@ static char *getvar(char *s)
if (c == 'S') sprintf(toybuf, "%lld", (millitime()-TT.SECONDS)/1000);
else if (c == 'R') sprintf(toybuf, "%ld", random()&((1<<16)-1));
- else if (c == 'L') sprintf(toybuf, "%u", TT.LINENO);
+ else if (c == 'L') sprintf(toybuf, "%u", TT.ff->pl->lineno);
else if (c == 'G') sprintf(toybuf, "TODO: GROUPS");
return toybuf;
@@ -467,6 +493,38 @@ static char *getvar(char *s)
return varend(var->str)+1;
}
+// TODO: keep variable arrays sorted for binary search
+
+// create array of variables visible in current function.
+static struct sh_vars **visible_vars(void)
+{
+ struct sh_arg arg;
+ struct sh_fcall *ff;
+ struct sh_vars *vv;
+ unsigned ii, jj, len;
+
+ arg.c = 0;
+ arg.v = 0;
+
+ // Find non-duplicate entries: TODO, sort and binary search
+ for (ff = TT.ff; ; ff = ff->next) {
+ if (!TT.ff->vars) continue;
+ for (ii = TT.ff->varslen; ii--;) {
+ vv = TT.ff->vars+ii;
+ len = 1+(varend(vv->str)-vv->str);
+ for (jj = 0; ;jj++) {
+ if (jj == arg.c) arg_add(&arg, (void *)vv);
+ else if (strncmp(arg.v[jj], vv->str, len)) continue;
+
+ break;
+ }
+ }
+ if (ff->next == TT.ff) break;
+ }
+
+ return (void *)arg.v;
+}
+
// malloc declare -x "escaped string"
static char *declarep(struct sh_vars *var)
{
@@ -556,7 +614,7 @@ static char *parse_word(char *start, int early, int quote)
// barf if we're near overloading quote stack (nesting ridiculously deep)
if (quote>4000) {
- syntax_err("tilt");
+ syntax_err("bad quote depth");
return (void *)1;
}
@@ -677,14 +735,118 @@ static int save_redirect(int **rd, int from, int to)
// TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending
static void subshell_callback(char **argv)
{
- char *s;
-
- xsetenv(s = xmprintf("@%d,%d=", getpid(), getppid()), 0);
- s[strlen(s)-1] = 0;
- xsetenv(xmprintf("$=%d", TT.pid), 0);
+ // This depends on environ having been replaced by caller
+ environ[1] = xmprintf("@%d,%d", getpid(), getppid());
+ environ[2] = xmprintf("$=%d", TT.pid);
// TODO: test $$ in (nommu)
}
+// turn a parsed pipeline back into a string.
+static char *pl2str(struct sh_pipeline *pl, int one)
+{
+ struct sh_pipeline *end = 0, *pp;
+ int len = len, i;
+ char *s, *ss;
+
+ // Find end of block (or one argument)
+ if (one) end = pl->next;
+ else for (end = pl, len = 0; end; end = end->next)
+ if (end->type == 1) len++;
+ else if (end->type == 3 && --len<0) break;
+
+ // measure, then allocate
+ for (ss = 0;; ss = xmalloc(len+1)) {
+ for (pp = pl; pp != end; pp = pp->next) {
+ for (i = len = 0; i<pp->arg->c; i++)
+ len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]);
+ if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end);
+ len += snprintf(ss+len, ss ? INT_MAX : 0, s);
+ }
+
+ if (ss) return ss;
+ }
+
+// TODO test output with case and function
+// TODO add HERE documents back in
+}
+
+// restore displaced filehandles, closing high filehandles they were copied to
+static void unredirect(int *urd)
+{
+ int *rr = urd+1, i;
+
+ if (!urd) return;
+
+ for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) {
+ // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
+ dup2(rr[0], rr[1]);
+ close(rr[0]);
+ }
+ free(urd);
+}
+
+// when ending a block, free, cleanup redirects and pop stack.
+static struct sh_pipeline *pop_block(struct sh_blockstack **cached)
+{
+ struct sh_blockstack *blk = TT.ff->blk;
+ struct sh_pipeline *pl = blk->start->end;
+
+ // when ending a block, free, cleanup redirects and pop stack.
+ if (TT.ff->pout != -1) close(TT.ff->pout);
+ TT.ff->pout = blk->pout;
+ unredirect(blk->urd);
+ llist_traverse(blk->fdelete, llist_free_arg);
+ free(blk->farg.v);
+ free(llist_pop(&TT.ff->blk));
+ if (cached) *cached = TT.ff->blk;
+
+ return pl;
+}
+
+// Add entry to runtime function call stack
+static void call_function(void)
+{
+ // push to dlist like single list (reverse order, new entry becomes TT.ff)
+ if (TT.ff) TT.ff = TT.ff->next;
+ dlist_add_nomalloc((void *)&TT.ff, xzalloc(sizeof(struct sh_fcall)));
+ TT.ff = TT.ff->prev;
+ TT.ff->pout = -1;
+
+// TODO caller needs to set pl, vars, func
+ // default $* is to copy previous
+ TT.ff->arg.v = TT.ff->next->arg.v;
+ TT.ff->arg.c = TT.ff->next->arg.c;
+}
+
+// returns 0 if source popped, nonzero if function popped
+static int end_function(int funconly)
+{
+ struct sh_fcall *ff = TT.ff;
+ int func = ff->next!=ff && ff->vars;
+
+ if (!func && funconly) return 0;
+
+ if (ff->pout != -1) close(ff->pout);
+ ff->pout = -1;
+ unredirect(ff->urd);
+ ff->urd = 0;
+
+ llist_traverse(ff->delete, llist_free_arg);
+ ff->delete = 0;
+ while (ff->blk) pop_block(0);
+
+ // for a function, free variables and pop context
+ if (!func) return 0;
+
+ while (ff->varslen)
+ if (!(ff->vars[--ff->varslen].flags&VAR_NOFREE))
+ free(ff->vars[ff->varslen].str);
+ free(ff->vars);
+ free(dlist_pop(&TT.ff));
+
+ return 1;
+}
+
// TODO check every caller of run_subshell for error, or syntax_error() here
// from pipe() failure
@@ -695,67 +857,56 @@ static int run_subshell(char *str, int len)
// The with-mmu path is significantly faster.
if (CFG_TOYBOX_FORK) {
- char *s;
-
if ((pid = fork())<0) perror_msg("fork");
else if (!pid) {
- s = xstrndup(str, len);
- sh_run(s);
- free(s);
- _exit(toys.exitval);
+ call_function();
+ if (str) {
+ do_source(0, fmemopen(str, len, "r"));
+ _exit(toys.exitval);
+ } else TT.ff->pl = TT.ff->next->pl->next;
}
// On nommu vfork, exec /proc/self/exe, and pipe state data to ourselves.
} else {
- int pipes[2], i, c;
+ int pipes[2];
+ unsigned len, i;
+ char **oldenv = environ, *s, *ss = str ? : pl2str(TT.ff->pl->next, 0);
+ struct sh_vars **vv;
// open pipe to child
if (pipe(pipes) || 254 != dup2(pipes[0], 254)) return 1;
close(pipes[0]);
fcntl(pipes[1], F_SETFD, FD_CLOEXEC);
- // vfork child
+ // vfork child with clean environment
+ environ = xzalloc(4*sizeof(char *));
+ *environ = getvar("PATH") ? : "PATH=";
pid = xpopen_setup(0, 0, subshell_callback);
// free entries added to end of environment by callback (shared heap)
- for (i = 0; environ[i]; i++) {
- c = environ[i][0];
- if (c == '_' || !ispunct(c)) continue;
- free(environ[i]);
- environ[i] = 0;
- }
+ free(environ[1]);
+ free(environ[2]);
+ free(environ);
+ environ = oldenv;
- // marshall data to child
+ // marshall context to child
close(254);
- for (i = 0; i<TT.varslen; i++) {
- char *s;
-
- if (TT.vars[i].flags&VAR_GLOBAL) continue;
- dprintf(pipes[1], "%s\n", s = declarep(TT.vars+i));
+ for (i = 0, vv = visible_vars(); vv[i]; i++) {
+ if (vv[i]->flags&VAR_WHITEOUT) continue;
+ dprintf(pipes[1], "%s\n", s = declarep(vv[i]));
free(s);
}
- dprintf(pipes[1], "%.*s\n", len, str);
+ free(vv);
+
+ // send command
+ dprintf(pipes[1], "%.*s\n", len, ss);
+ if (!str) free(ss);
close(pipes[1]);
}
return pid;
}
-// restore displaced filehandles, closing high filehandles they were copied to
-static void unredirect(int *urd)
-{
- int *rr = urd+1, i;
-
- if (!urd) return;
-
- for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) {
- // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
- dup2(rr[0], rr[1]);
- close(rr[0]);
- }
- free(urd);
-}
-
// Call subshell with either stdin/stdout redirected, return other end of pipe
static int pipe_subshell(char *s, int len, int out)
{
@@ -817,15 +968,15 @@ char *getvar_special(char *str, int len, int *used, struct arg_list **delete)
*ss = 0;
} else if (cc == '?') s = xmprintf("%d", toys.exitval);
else if (cc == '$') s = xmprintf("%d", TT.pid);
- else if (cc == '#') s = xmprintf("%d", TT.cc->arg.c?TT.cc->arg.c-1:0);
+ else if (cc == '#') s = xmprintf("%d", TT.ff->arg.c ? TT.ff->arg.c-1 : 0);
else if (cc == '!') s = xmprintf("%d"+2*!TT.bangpid, TT.bangpid);
else {
delete = 0;
for (*used = uu = 0; *used<len && isdigit(str[*used]); ++*used)
uu = (10*uu)+str[*used]-'0';
if (*used) {
- if (uu) uu += TT.cc->shift;
- if (uu<TT.cc->arg.c) s = TT.cc->arg.v[uu];
+ if (uu) uu += TT.ff->shift;
+ if (uu<TT.ff->arg.c) s = TT.ff->arg.v[uu];
} else if ((*used = varend(str)-str)) return getvar(str);
}
if (s) push_arg(delete, s);
@@ -1321,10 +1472,15 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s);
// special case: normal varname followed by @} or *} = prefix list
if (ss[jj] == '*' || (ss[jj] == '@' && !isalpha(ss[jj+1]))) {
- for (slice++, kk = 0; kk<TT.varslen; kk++)
- if (!strncmp(s = TT.vars[kk].str, ss, jj))
+ struct sh_vars **vv = visible_vars();
+
+ for (slice++, kk = 0; vv[kk]; kk++) {
+ if (vv[kk]->flags&VAR_WHITEOUT) continue;
+ if (!strncmp(s = vv[kk]->str, ss, jj))
arg_add(&aa, push_arg(delete, s = xstrndup(s, stridx(s, '='))));
+ }
if (aa.c) push_arg(delete, aa.v);
+ free(vv);
// else dereference to get new varname, discarding if none, check err
} else {
@@ -1342,8 +1498,8 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s);
if (!jj) ifs = (void *)1;
else if (ifs && *(ss = ifs)) {
if (strchr("@*", cc)) {
- aa.c = TT.cc->arg.c-1;
- aa.v = TT.cc->arg.v+1;
+ aa.c = TT.ff->arg.c-1;
+ aa.v = TT.ff->arg.v+1;
jj = 1;
} else ifs = getvar_special(ifs, strlen(ifs), &jj, delete);
if (ss && ss[jj]) {
@@ -1366,8 +1522,8 @@ barf:
// Resolve unprefixed variables
if (strchr("{$", ss[-1])) {
if (strchr("@*", cc)) {
- aa.c = TT.cc->arg.c-1;
- aa.v = TT.cc->arg.v+1;
+ aa.c = TT.ff->arg.c-1;
+ aa.v = TT.ff->arg.v+1;
} else {
ifs = getvar_special(ss, jj, &jj, delete);
if (!jj) {
@@ -1400,7 +1556,6 @@ barf:
// when aa proceed through entries until NULL, else process ifs once
mm = yy = 0;
do {
-
// get next argument
if (aa.c) ifs = aa.v[mm++] ? : "";
@@ -1412,7 +1567,7 @@ barf:
push_arg(delete, ifs = slashcopy(slice+xx+1, "}", 0));
if (dd == '?' || (dd == '=' &&
!(setvar(s = xmprintf("%.*s=%s", (int)(slice-ss), ss, ifs)))))
- goto barf;
+ goto barf; // TODO ? exits past "source" boundary
}
} else if (dd == '-'); // NOP when ifs not empty
// use alternate value
@@ -1831,35 +1986,6 @@ static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
// TODO |&
-// turn a parsed pipeline back into a string.
-static char *pl2str(struct sh_pipeline *pl, int one)
-{
- struct sh_pipeline *end = 0, *pp;
- int len = len, i;
- char *s, *ss;
-
- // Find end of block (or one argument)
- if (one) end = pl->next;
- else for (end = pl, len = 0; end; end = end->next)
- if (end->type == 1) len++;
- else if (end->type == 3 && --len<0) break;
-
- // measure, then allocate
- for (ss = 0;; ss = xmalloc(len+1)) {
- for (pp = pl; pp != end; pp = pp->next) {
- for (i = len = 0; i<pp->arg->c; i++)
- len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]);
- if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end);
- len += snprintf(ss+len, ss ? INT_MAX : 0, s);
- }
-
- if (ss) return ss;
- }
-
-// TODO test output with case and function
-// TODO add HERE documents back in
-}
-
// Expand arguments and perform redirections. Return new process object with
// expanded args. This can be called from command or block context.
static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd)
@@ -1869,7 +1995,6 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd)
int j, to, from, here = 0;
TT.hfd = 10;
-
pp = xzalloc(sizeof(struct sh_process));
pp->urd = urd;
pp->raw = arg;
@@ -2076,36 +2201,68 @@ notfd:
return pp;
}
-static void shexec(char *cmd, char **argv)
-{
- xsetenv(xmprintf("_=%s", cmd), 0);
- execve(cmd, argv, environ);
-// TODO: why?
- if (errno == ENOEXEC) run_subshell("source \"$_\"", 11);
-}
-
// Call binary, or run via child shell
static void sh_exec(char **argv)
{
- char *pp = getvar("PATH" ? : _PATH_DEFPATH), *cc = TT.isexec ? : *argv;
+ char *pp = getvar("PATH" ? : _PATH_DEFPATH), *cc = TT.isexec ? : *argv, *ss,
+ **sss = 0, **oldenv = environ, **argv2;
struct string_list *sl;
if (getpid() != TT.pid) signal(SIGINT, SIG_DFL); // TODO: restore all?
- if (strchr(cc, '/')) shexec(cc, argv);
- else for (sl = find_in_path(pp, cc); sl; free(llist_pop(&sl)))
- shexec(sl->str, argv);
+ errno = ENOENT;
+ if (strchr(ss = cc, '/')) {
+ if (access(ss, X_OK)) ss = 0;
+ } else for (sl = find_in_path(pp, cc); sl || (ss = 0); free(llist_pop(&sl)))
+ if (!access(ss = sl->str, X_OK)) break;
+
+ if (ss) {
+ struct sh_vars **vv = visible_vars();
+ struct sh_arg aa;
+ unsigned uu, argc;
+
+ // convert vars in-place and use original sh_arg alloc to add one more
+ aa.v = environ = (void *)vv;
+ for (aa.c = uu = 0; vv[uu]; uu++) {
+ if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+ if (*(pp = vv[uu]->str)=='_' && pp[1] == '=') sss = aa.v+aa.c;
+ aa.v[aa.c++] = pp;
+ }
+ }
+ aa.v[aa.c] = 0;
+ if (!sss) {
+ arg_add(&aa, 0);
+ sss = aa.v+aa.c-1;
+ }
+ *sss = xmprintf("_=%s", ss);
+
+ // exec or source
+ execve(ss, argv, environ);
+ if (errno == ENOEXEC) {
+ for (argc = 0; argv[argc++];);
+ argv2 = xmalloc((argc+2)*sizeof(char *));
+ memcpy(argv2+2, argv, argc*sizeof(char *));
+ argv2[0] = "sh";
+ argv2[1] = "--";
+ xexec(argv2);
+ free(argv2);
+ }
+ environ = oldenv;
+ free(*sss);
+ free(aa.v);
+ }
perror_msg("%s", *argv);
if (!TT.isexec) _exit(127);
+ llist_traverse(sl, free);
}
-// Execute a single command
-static struct sh_process *run_command(struct sh_arg *arg)
+// Execute a single command at TT.ff->pl
+static struct sh_process *run_command(void)
{
- char *s, *ss = 0, *sss, **old = environ;
- struct sh_arg env = {0};
+ char *s, *sss;
+ struct sh_arg *arg = TT.ff->pl->arg;
int envlen, jj = 0, ll;
- struct sh_process *pp;
+ struct sh_process *pp = 0;
struct arg_list *delete = 0;
struct toy_list *tl;
@@ -2115,51 +2272,39 @@ static struct sh_process *run_command(struct sh_arg *arg)
if (s == arg->v[envlen] || *s != '=') break;
}
- // perform assignments locally if there's no command
- if (envlen == arg->c) {
- while (jj<envlen) {
- if (!(s = expand_one_arg(arg->v[jj], SEMI_IFS, 0))) break;
- setvar((s == arg->v[jj++]) ? xstrdup(s) : s);
- }
- if (jj == envlen) setvarval("_", "");
-
- // assign leading environment variables (if any) in temp environ copy
- } else if (envlen) {
- while (environ[env.c]) env.c++;
- memcpy(env.v = xmalloc(sizeof(char *)*(env.c+33)), environ,
- sizeof(char *)*(env.c+1));
- for (; jj<envlen; jj++) {
- if (!(sss = expand_one_arg(arg->v[jj], SEMI_IFS, &delete))) break;
- for (ll = 0; ll<env.c; ll++) {
- for (s = sss, ss = env.v[ll]; *s == *ss && *s != '='; s++, ss++);
- if (*s != '=') continue;
- env.v[ll] = sss;
- break;
+ // expand arguments and perform redirects
+ pp = expand_redir(arg, envlen, 0);
+
+ // assign variables. If command, create temp function context to hold vars
+ if (!pp->exit && envlen) {
+ struct sh_fcall *ff;
+ struct sh_vars *vv;
+
+ if (!(ll = envlen == arg->c)) call_function();
+ for (; jj<envlen && !pp->exit; jj++) {
+ s = arg->v[jj];
+ if (!ll && (!(vv = findvar(s, &ff)) || ff != TT.ff)) {
+ if (vv && (vv->flags&VAR_READONLY)) {
+ error_msg("%.*s: readonly variable", (int)(varend(s)-s), s);
+ continue;
+ }
+ addvar(s, TT.ff)->flags = VAR_NOFREE|VAR_GLOBAL;
}
- if (ll == env.c) arg_add(&env, sss);
+ if (!(sss = expand_one_arg(s, SEMI_IFS, ll ? &delete : 0))) pp->exit = 1;
+ else setvar((!ll || sss != s) ? s : xstrdup(s));
}
- environ = env.v;
}
- // return early if error or assignment only
- if (envlen == arg->c || jj != envlen) {
- pp = xzalloc(sizeof(struct sh_process));
- pp->exit = jj != envlen;
-
- goto out;
- }
-
- // expand arguments and perform redirects
- pp = expand_redir(arg, envlen, 0);
-
- // Do nothing if nothing to do
- if (pp->exit || !pp->arg.v);
-// else if (!strcmp(*pp->arg.v, "(("))
-// TODO: handle ((math)) currently totally broken
-// TODO: call functions()
+ // Run command
+ s = pp->arg.v ? pp->arg.v[pp->arg.c-1] : "";
+ if (pp->exit || envlen == arg->c) s = 0; // leave $_ alone
+ else if (!pp->arg.v); // nothing to do but blank ""
+// TODO handle ((math)): else if (!strcmp(*pp->arg.v, "(("))
+// TODO: call functions() FUNCTION
+// TODO what about "echo | x=1 | export fruit", must subshell? Test this.
+// TODO: figure out when can exec instead of forking, ala sh -c blah
// Is this command a builtin that should run in this process?
- else if ((tl = toy_find(*pp->arg.v))
- && ((tl->flags&TOYFLAG_NOFORK)
+ else if ((tl = toy_find(*pp->arg.v)) && ((tl->flags&TOYFLAG_NOFORK)
|| (TT.ff->pout == -1 && (tl->flags&TOYFLAG_MAYFORK))))
{
sigjmp_buf rebound;
@@ -2190,14 +2335,10 @@ static struct sh_process *run_command(struct sh_arg *arg)
} else if (-1==(pp->pid = xpopen_setup(pp->arg.v, 0, sh_exec)))
perror_msg("%s: vfork", *pp->arg.v);
- // Restore environment variables
- environ = old;
- free(env.v);
-
- if (pp->arg.c) setvarval("_", pp->arg.v[pp->arg.c-1]);
// cleanup process
unredirect(pp->urd);
-out:
+ if (envlen && envlen!=arg->c) end_function(0);
+ if (s) setvarval("_", s);
llist_traverse(delete, llist_free_arg);
return pp;
@@ -2227,13 +2368,6 @@ static void free_pipeline(void *pipeline)
free(pl);
}
-static void free_function(struct sh_function *sp)
-{
- llist_traverse(sp->pipeline, free_pipeline);
- llist_traverse(sp->expect, free);
- memset(sp, 0, sizeof(struct sh_function));
-}
-
// TODO this has to add to a namespace context. Functions within functions...
static struct sh_pipeline *add_function(char *name, struct sh_pipeline *pl)
{
@@ -2243,24 +2377,25 @@ dprintf(2, "stub add_function");
}
// Append a new pipeline to function, returning pipeline and pipeline's arg
-static struct sh_pipeline *add_pl(struct sh_function *sp, struct sh_arg **arg)
+static struct sh_pipeline *add_pl(struct sh_pipeline **ppl, struct sh_arg **arg)
{
struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline));
*arg = pl->arg;
- if (TT.cc) pl->lineno = TT.cc->lineno;
- dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
+ pl->lineno = TT.LINENO;
+ dlist_add_nomalloc((void *)ppl, (void *)pl);
return pl->end = pl;
}
// Add a line of shell script to a shell function. Returns 0 if finished,
// 1 to request another line of input (> prompt), -1 for syntax err
-static int parse_line(char *line, struct sh_function *sp)
+static int parse_line(char *line, struct sh_pipeline **ppl,
+ struct double_list **expect)
{
char *start = line, *delete = 0, *end, *s, *ex, done = 0,
*tails[] = {"fi", "done", "esac", "}", "]]", ")", 0};
- struct sh_pipeline *pl = sp->pipeline ? sp->pipeline->prev : 0, *pl2, *pl3;
+ struct sh_pipeline *pl = *ppl ? (*ppl)->prev : 0, *pl2, *pl3;
struct sh_arg *arg = 0;
long i;
@@ -2300,7 +2435,7 @@ static int parse_line(char *line, struct sh_function *sp)
// Parse words, assemble argv[] pipelines, check flow control and HERE docs
if (start) for (;;) {
- ex = sp->expect ? sp->expect->prev->data : 0;
+ ex = *expect ? (*expect)->prev->data : 0;
// Look for << HERE redirections in completed pipeline segment
if (pl && pl->count == -1) {
@@ -2317,9 +2452,9 @@ static int parse_line(char *line, struct sh_function *sp)
// Add another arg[] to the pipeline segment (removing/readding to list
// because realloc can move pointer)
- dlist_lpop(&sp->pipeline);
+ dlist_lpop(ppl);
pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg));
- dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
+ dlist_add_nomalloc((void *)ppl, (void *)pl);
// queue up HERE EOF so input loop asks for more lines.
arg[pl->count].v = xzalloc(2*sizeof(void *));
@@ -2341,7 +2476,7 @@ static int parse_line(char *line, struct sh_function *sp)
if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush;
// Is this a new pipeline segment?
- if (!pl) pl = add_pl(sp, &arg);
+ if (!pl) pl = add_pl(ppl, &arg);
// Do we need to request another line to finish word (find ending quote)?
if (!end) {
@@ -2376,7 +2511,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (pl->prev->type == 2) {
// Add a call to "true" between empty ) ;;
arg_add(arg, xstrdup(":"));
- pl = add_pl(sp, &arg);
+ pl = add_pl(ppl, &arg);
}
pl->type = 129;
} else {
@@ -2401,7 +2536,7 @@ static int parse_line(char *line, struct sh_function *sp)
}
// don't save blank pipeline segments
- if (!arg->c) free_pipeline(dlist_lpop(&sp->pipeline));
+ if (!arg->c) free_pipeline(dlist_lpop(ppl));
// stop at EOL, else continue with new pipeline segment for )
if (end == start) done++;
@@ -2422,7 +2557,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (arg->c==3) {
if (strcmp(s, "in")) goto flush;
pl->type = 1;
- (pl = add_pl(sp, &arg))->type = 129;
+ (pl = add_pl(ppl, &arg))->type = 129;
}
continue;
@@ -2439,7 +2574,7 @@ static int parse_line(char *line, struct sh_function *sp)
// esac right after "in" or ";;" ends block, fall through
if (arg->c>1) {
arg->v[1] = 0;
- pl = add_pl(sp, &arg);
+ pl = add_pl(ppl, &arg);
arg_add(arg, s);
} else pl->type = 0;
} else {
@@ -2447,7 +2582,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (i>0 && ((i&1)==!!strchr("|)", *s) || strchr(";(", *s)))
goto flush;
if (*s=='&' || !strcmp(s, "||")) goto flush;
- if (*s==')') pl = add_pl(sp, &arg);
+ if (*s==')') pl = add_pl(ppl, &arg);
continue;
}
@@ -2485,9 +2620,9 @@ static int parse_line(char *line, struct sh_function *sp)
// end function segment, expect function body
pl->count = -1;
- dlist_add(&sp->expect, "}");
- dlist_add(&sp->expect, 0);
- dlist_add(&sp->expect, "{");
+ dlist_add(expect, "}");
+ dlist_add(expect, 0);
+ dlist_add(expect, "{");
continue;
}
@@ -2498,7 +2633,7 @@ static int parse_line(char *line, struct sh_function *sp)
// Sanity check and break the segment
if (strncmp(s, "((", 2) && *varend(s)) goto flush;
pl->count = -1;
- sp->expect->prev->data = "do\0C";
+ (*expect)->prev->data = "do\0C";
continue;
@@ -2511,7 +2646,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (!strcmp(ex, "{")) {
if (strcmp(s, "{")) goto flush;
free(arg->v[--arg->c]); // don't save the {, function starts the block
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
continue;
@@ -2536,7 +2671,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (!strcmp(s, "for") || !strcmp(s, "select") || !strcmp(s, "case")) {
// TODO why !pl->type here
if (!pl->type) pl->type = (*s == 'c') ? 128 : 1;
- dlist_add(&sp->expect, (*s == 'c') ? "esac" : "do\0A");
+ dlist_add(expect, (*s == 'c') ? "esac" : "do\0A");
continue;
}
@@ -2549,14 +2684,14 @@ static int parse_line(char *line, struct sh_function *sp)
else if (!strcmp(s, "(")) end = ")";
// Expecting NULL means a statement: I.E. any otherwise unrecognized word
- if (!ex && sp->expect) free(dlist_lpop(&sp->expect));
+ if (!ex && *expect) free(dlist_lpop(expect));
// Did we start a new statement
if (end) {
pl->type = 1;
// Only innermost statement needed in { { { echo ;} ;} ;} and such
- if (sp->expect && !sp->expect->prev->data) free(dlist_lpop(&sp->expect));
+ if (*expect && !(*expect)->prev->data) free(dlist_lpop(expect));
// if can't end a statement here skip next few tests
} else if (!ex);
@@ -2569,7 +2704,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (aa->v[aa->c] && strcmp(aa->v[aa->c], "&")) goto flush;
// consume word, record block end location in earlier !0 type blocks
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
if (3 == (pl->type = anystr(s, tails) ? 3 : 2)) {
for (i = 0, pl2 = pl3 = pl; (pl2 = pl2->prev);) {
if (pl2->type == 3) i++;
@@ -2592,7 +2727,7 @@ static int parse_line(char *line, struct sh_function *sp)
// fi could have elif, which queues a then.
} else if (!strcmp(ex, "fi")) {
if (!strcmp(s, "elif")) {
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
end = "then";
// catch duplicate else while we're here
} else if (!strcmp(s, "else")) {
@@ -2600,7 +2735,7 @@ static int parse_line(char *line, struct sh_function *sp)
s = "2 else";
goto flush;
}
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
end = "fi\0B";
}
}
@@ -2609,8 +2744,8 @@ static int parse_line(char *line, struct sh_function *sp)
if (end) {
if (!pl->type) pl->type = 2;
- dlist_add(&sp->expect, end);
- if (!anystr(end, tails)) dlist_add(&sp->expect, 0);
+ dlist_add(expect, end);
+ if (!anystr(end, tails)) dlist_add(expect, 0);
pl->count = -1;
}
@@ -2621,27 +2756,28 @@ static int parse_line(char *line, struct sh_function *sp)
free(delete);
// ignore blank and comment lines
- if (!sp->pipeline) return 0;
+ if (!*ppl) return 0;
// TODO <<< has no parsing impact, why play with it here at all?
// advance past <<< arguments (stored as here documents, but no new input)
- pl = sp->pipeline->prev;
+ pl = (*ppl)->prev;
while (pl->count<pl->here && pl->arg[pl->count].c<0)
pl->arg[pl->count++].c = 0;
// return if HERE document pending or more flow control needed to complete
- if (sp->expect) return 1;
- if (sp->pipeline && pl->count != pl->here) return 1;
+ if (*expect) return 1;
+ if (*ppl && pl->count != pl->here) return 1;
if (pl->arg->v[pl->arg->c] && strcmp(pl->arg->v[pl->arg->c], "&")) return 1;
// Don't need more input, can start executing.
- dlist_terminate(sp->pipeline);
+ dlist_terminate(*ppl);
return 0;
flush:
if (s) syntax_err(s);
- free_function(sp);
+ llist_traverse(*ppl, free_pipeline);
+ llist_traverse(*expect, free);
return 0-!!s;
}
@@ -2664,24 +2800,6 @@ static int wait_pipeline(struct sh_process *pp)
return rc;
}
-// when ending a block, free, cleanup redirects and pop stack.
-static struct sh_pipeline *pop_block(struct sh_blockstack **cached)
-{
- struct sh_blockstack *blk = TT.ff->blk;
- struct sh_pipeline *pl = blk->start->end;
-
- // when ending a block, free, cleanup redirects and pop stack.
- if (TT.ff->pout != -1) close(TT.ff->pout);
- TT.ff->pout = blk->pout;
- unredirect(blk->urd);
- llist_traverse(blk->fdelete, llist_free_arg);
- free(blk->farg.v);
- free(llist_pop(&TT.ff->blk));
- if (cached) *cached = TT.ff->blk;
-
- return pl;
-}
-
// Print prompt to stderr, parsing escapes
// Truncated to 4k at the moment, waiting for somebody to complain.
static void do_prompt(char *prompt)
@@ -2696,7 +2814,7 @@ static void do_prompt(char *prompt)
if (c=='!') {
if (*prompt=='!') prompt++;
else {
- pp += snprintf(pp, len, "%u", TT.cc->lineno);
+ pp += snprintf(pp, len, "%u", TT.LINENO);
continue;
}
} else if (c=='\\') {
@@ -2742,6 +2860,46 @@ static void do_prompt(char *prompt)
writeall(2, toybuf, len);
}
+// returns NULL for EOF, 1 for invalid, else null terminated string.
+static char *get_next_line(FILE *ff, int prompt)
+{
+ char *new;
+ int len, cc;
+
+ if (!ff) {
+ char ps[16];
+
+ sprintf(ps, "PS%d", prompt);
+ do_prompt(getvar(ps));
+ }
+
+// TODO what should ctrl-C do? (also in "select")
+// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
+// TODO: after first EINTR returns closed?
+// TODO: ctrl-z during script read having already read partial line,
+// SIGSTOP and SIGTSTP need SA_RESTART, but child proc should stop
+// TODO if (!isspace(*new)) add_to_history(line);
+
+ for (new = 0, len = 0;;) {
+ errno = 0;
+ if (!(cc = getc(ff ? : stdin))) {
+ if (TT.LINENO) continue;
+ free(new);
+ return (char *)1;
+ }
+ if (cc<0) {
+ if (errno == EINTR) continue;
+ break;
+ }
+ if (!(len&63)) new = xrealloc(new, len+65);
+ if (cc == '\n') break;
+ new[len++] = cc;
+ }
+ if (new) new[len] = 0;
+
+ return new;
+}
+
/*
TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
probably have to inline run_command here to do that? Implicit ()
@@ -2757,35 +2915,6 @@ static void do_prompt(char *prompt)
when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)'
*/
-// Add entry to runtime function call stack
-static void call_function(struct sh_function *func)
-{
- struct sh_fcall *ff = xzalloc(sizeof(struct sh_fcall));
-
- ff->next = TT.ff;
- ff->func = func;
- ff->pl = func->pipeline;
- ff->pout = -1;
- TT.ff = ff;
-}
-
-static struct sh_fcall *pop_function(struct sh_blockstack **blk)
-{
- struct sh_fcall *ff = TT.ff;
-
- if (!ff) return 0;
-
- if (ff->pout != -1) close(ff->pout);
- unredirect(ff->urd);
-
- while (ff->blk) pop_block(0);
- TT.ff = ff->next;
- free(ff);
- if (blk) *blk = TT.ff ? TT.ff->blk : 0;
-
- return TT.ff;
-}
-
// run a parsed shell function. Handle flow control blocks and characters,
// setup pipes and block redirection, break/continue, call builtins, functions,
// vfork/exec external commands. Return when out of input.
@@ -2798,11 +2927,9 @@ static void run_lines(void)
// iterate through pipeline segments
for (;;) {
-
- // return from function
if (!TT.ff->pl) {
- if (!TT.ff->next) break;
- pop_function(&blk);
+ if (!end_function(1)) break;
+ blk = TT.ff->blk;
continue;
}
@@ -2810,8 +2937,6 @@ static void run_lines(void)
ctl = TT.ff->pl->end->arg->v[TT.ff->pl->end->arg->c];
s = *TT.ff->pl->arg->v;
ss = TT.ff->pl->arg->v[1];
-
- TT.LINENO = TT.ff->pl->lineno;
if (!pplist) TT.hfd = 10;
// Skip disabled blocks, handle pipes and backgrounding
@@ -2823,17 +2948,20 @@ static void run_lines(void)
}
if (TT.options&OPT_x) {
- struct sh_callstack *sc;
+ unsigned lineno;
char *ss, *ps4 = getvar("PS4");
// duplicate first char of ps4 call depth times
if (ps4 && *ps4) {
- for (i = 0, sc = TT.cc; sc; sc = sc->next) i++;
j = getutf8(ps4, k = strlen(ps4), 0);
- ss = xmalloc(i*j+k);
- for (k = 0; k<i; k++) memcpy(ss+k*j, ps4, j);
+ ss = xmalloc(TT.srclvl*j+k+1);
+ for (k = 0; k<TT.srclvl; k++) memcpy(ss+k*j, ps4, j);
strcpy(ss+k*j, ps4+j);
+ // show saved line number from function, not next to read
+ lineno = TT.LINENO;
+ TT.LINENO = TT.ff->pl->lineno;
do_prompt(ss);
+ TT.LINENO = lineno;
free(ss);
// TODO resolve variables
@@ -2878,7 +3006,6 @@ static void run_lines(void)
// Is this an executable segment?
if (!TT.ff->pl->type) {
-
// Is it a flow control jump? These aren't handled as normal builtins
// because they move *pl to other pipeline segments which is local here.
if (!strcmp(s, "break") || !strcmp(s, "continue")) {
@@ -2902,8 +3029,7 @@ static void run_lines(void)
}
// Parse and run next command, saving resulting process
- } else dlist_add_nomalloc((void *)&pplist,
- (void *)run_command(TT.ff->pl->arg));
+ } else dlist_add_nomalloc((void *)&pplist, (void *)run_command());
// Start of flow control block?
} else if (TT.ff->pl->type == 1) {
@@ -2933,18 +3059,8 @@ static void run_lines(void)
// If we spawn a subshell, pass data off to child process
i = ctl && !strcmp(ctl, "&");
if (!rc && (blk->pout!=-1 || !strcmp(s, "(") || i)) {
- // Create new process
- if (!CFG_TOYBOX_FORK) {
- ss = pl2str(TT.ff->pl->next, 0);
- pp->pid = run_subshell(ss, strlen(ss));
- free(ss);
- } else if (!(pp->pid = fork())) {
- // Clear the board in child process: run pl->next in root sh_fcall
- // just leak rather than freeing, it's copy-on-write with parent
- TT.ff->pl = TT.ff->pl->next;
- TT.ff->next = 0;
- blk = TT.ff->blk = 0;
- pplist = 0;
+ if (!(pp->pid = run_subshell(0, -1))) {
+ blk = 0, pplist = 0;
continue;
}
@@ -3042,18 +3158,17 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
else if (!strcmp(ss, "select")) {
- do_prompt(getvar("PS3"));
-// TODO: ctrl-c not breaking out of this?
- if (!(ss = xgetline(stdin))) {
+ if (!(ss = get_next_line(0, 3)) || ss==(void *)1) {
TT.ff->pl = pop_block(&blk);
printf("\n");
- } else if (!*ss) {
- TT.ff->pl = blk->start;
- continue;
} else {
match = atoi(ss);
- setvarval(blk->fvar, (match<1 || match>blk->farg.c)
- ? "" : blk->farg.v[match-1]);
+ free(ss);
+ if (!*ss) {
+ TT.ff->pl = blk->start;
+ continue;
+ } else setvarval(blk->fvar, (match<1 || match>blk->farg.c)
+ ? "" : blk->farg.v[match-1]);
}
} else if (blk->loop >= blk->farg.c) TT.ff->pl = pop_block(&blk);
else if (!strncmp(blk->fvar, "((", 2)) {
@@ -3064,22 +3179,19 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n");
// end of block
} else if (TT.ff->pl->type == 3) {
- // If we end a block we're not in, pop function context.
- if (!blk) {
+ // If we end a block we're not in, exit subshell
+ if (!blk) xexit();
- // Exit subshell if no function context left
- if (!pop_function(&blk)) xexit();
- } else {
+ // repeating block?
+ if (blk->run && !strcmp(s, "done")) {
+ TT.ff->pl = blk->middle;
+ continue;
+ }
- // repeating block?
- if (blk->run && !strcmp(s, "done")) {
- TT.ff->pl = blk->middle;
- continue;
- }
+ // cleans up after trailing redirections/pipe
+ pop_block(&blk);
- // cleans up after trailing redirections/pipe
- pop_block(&blk);
- }
+// FUNCTION this!
} else if (TT.ff->pl->type == 'f') TT.ff->pl = add_function(s, TT.ff->pl);
// Three cases: background & pipeline | last process in pipeline ;
@@ -3110,40 +3222,20 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n");
toys.exitval = wait_pipeline(pplist);
llist_traverse(pplist, free_process);
}
- while (pop_function(0));
-}
-
-// Parse and run a self-contained command line with no prompt/continuation
-static int sh_run(char *new)
-{
- struct sh_function scratch;
- void *ff = TT.ff;
-
-// TODO switch the fmemopen for -c to use this? Error checking? $(blah)
-// TODO Merge this with do_source()
-
- memset(&scratch, 0, sizeof(struct sh_function));
- if (!parse_line(new, &scratch)) {
- TT.ff = 0;
- call_function(&scratch);
- run_lines();
- TT.ff = ff;
- }
-// TODO else error?
- free_function(&scratch);
- return toys.exitval;
+ // exit source context (and function calls on syntax err)
+ while (end_function(0));
}
// set variable
-static struct sh_vars *initlocal(char *name, char *val)
+static struct sh_vars *initvar(char *name, char *val)
{
- return addvar(xmprintf("%s=%s", name, val ? val : ""));
+ return addvar(xmprintf("%s=%s", name, val ? val : ""), TT.ff);
}
-static struct sh_vars *initlocaldef(char *name, char *val, char *def)
+static struct sh_vars *initvardef(char *name, char *val, char *def)
{
- return initlocal(name, (!val || !*val) ? def : val);
+ return initvar(name, (!val || !*val) ? def : val);
}
// export existing "name" or assign/export name=value string (making new copy)
@@ -3154,28 +3246,26 @@ static void export(char *str)
// Make sure variable exists and is updated
if (strchr(str, '=')) shv = setvar(xstrdup(str));
- else if (!(shv = findvar(str))) shv = addvar(str = xmprintf("%s=", str));
+ else if (!(shv = findvar(str, 0))) {
+ shv = addvar(str = xmprintf("%s=", str), TT.ff->prev);
+ shv->flags = VAR_WHITEOUT;
+ } else if (shv->flags&VAR_WHITEOUT) shv->flags |= VAR_GLOBAL;
if (!shv || (shv->flags&VAR_GLOBAL)) return;
- // Resolve local magic for export
+ // Resolve magic for export (bash bug compatibility, really should be dynamic)
if (shv->flags&VAR_MAGIC) {
s = shv->str;
shv->str = xmprintf("%.*s=%s", (int)(varend(str)-str), str, getvar(str));
free(s);
}
-
- xsetenv(shv->str, 0);
shv->flags |= VAR_GLOBAL;
}
static void unexport(char *str)
{
- struct sh_vars *shv = findvar(str);
+ struct sh_vars *shv = findvar(str, 0);
- if (shv) {
- if (shv->flags&VAR_GLOBAL) shv->str = xpop_env(str);
- shv->flags &=~VAR_GLOBAL;
- }
+ if (shv) shv->flags &=~VAR_GLOBAL;
if (strchr(str, '=')) setvar(str);
}
@@ -3194,71 +3284,62 @@ FILE *fpathopen(char *name)
return f;
}
-// get line with command history
-char *prompt_getline(FILE *ff, int prompt)
-{
- char *new, ps[16];
-
-// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
- errno = 0;
- if (!ff && prompt) {
- sprintf(ps, "PS%d", prompt);
- do_prompt(getvar(ps));
- }
- do if ((new = xgetline(ff ? : stdin))) return new;
- while (errno == EINTR);
-// TODO: after first EINTR returns closed?
-// TODO: ctrl-z during script read having already read partial line,
-// SIGSTOP and SIGTSTP need need SA_RESTART, but child proc should stop
-// TODO if (!isspace(*new)) add_to_history(line);
-
- return 0;
-}
-
// Read script input and execute lines, with or without prompts
int do_source(char *name, FILE *ff)
{
- struct sh_callstack *cc = xzalloc(sizeof(struct sh_callstack));
- int more = 0;
+ struct sh_pipeline *pl = 0;
+ struct double_list *expect = 0;
+ unsigned lineno = TT.LINENO, more = 0;
+ int cc, ii;
char *new;
- cc->next = TT.cc;
- cc->arg.v = toys.optargs;
- cc->arg.c = toys.optc;
- TT.cc = cc;
+// TODO fix/catch NONBLOCK on input?
+
+// TODO when DO we reset lineno? (!LINENO means \0 returns 1)
+// when do we NOT reset lineno? Inherit but preserve perhaps? newline in $()?
+ if (!name) TT.LINENO = 0;
do {
- new = prompt_getline(ff, more+1);
- if (!(TT.LINENO = TT.cc->lineno++) && new && !memcmp(new, "\177ELF", 4)) {
- error_msg("'%s' is ELF", name);
- free(new);
+ if ((void *)1 == (new = get_next_line(ff, more+1))) goto is_binary;
- break;
+ // did we exec an ELF file or something?
+ if (!TT.LINENO++ && name && new) {
+ wchar_t wc;
+
+ // A shell script's first line has no high bytes that aren't valid utf-8.
+ for (ii = 0; new[ii] && 0<(cc = utf8towc(&wc, new+ii, 4)); ii += cc);
+ if (new[ii]) {
+is_binary:
+ if (name) error_msg("'%s' is binary", name); // TODO syntax_err() exit?
+ free(new);
+ new = 0;
+ }
}
// TODO: source <(echo 'echo hello\') vs source <(echo -n 'echo hello\')
// prints "hello" vs "hello\"
// returns 0 if line consumed, command if it needs more data
- more = parse_line(new ? : " ", &cc->scratch);
- if (more==1) {
- if (!new && !ff) syntax_err("unexpected end of file");
- } else {
- if (!more) {
- call_function(&cc->scratch);
- run_lines();
- }
- free_function(&cc->scratch);
- more = 0;
- }
+ more = parse_line(new ? : " ", &pl, &expect);
free(new);
- } while(new);
+ if (more==1) {
+ if (!new) {
+ if (!ff) syntax_err("unexpected end of file");
+ } else continue;
+ } else if (!more && pl) {
+ TT.ff->pl = pl;
+ run_lines();
+ } else more = 0;
+
+ llist_traverse(pl, free_pipeline);
+ pl = 0;
+ llist_traverse(expect, free);
+ expect = 0;
+ } while (new);
if (ff) fclose(ff);
- TT.cc = TT.cc->next;
- free_function(&cc->scratch);
- llist_traverse(cc->delete, llist_free_arg);
- free(cc);
+
+ if (!name) TT.LINENO = lineno;
return more;
}
@@ -3266,49 +3347,55 @@ int do_source(char *name, FILE *ff)
// init locals, sanitize environment, handle nommu subshell handoff
static void subshell_setup(void)
{
- int ii, to, from, pid, ppid, zpid, myppid = getppid(), len, uid = getuid();
+ int ii, from, pid, ppid, zpid, myppid = getppid(), len, uid = getuid();
struct passwd *pw = getpwuid(uid);
- char *s, *ss, *magic[] = {"SECONDS","RANDOM","LINENO","GROUPS"},
+ char *s, *ss, *magic[] = {"SECONDS", "RANDOM", "LINENO", "GROUPS"},
*readonly[] = {xmprintf("EUID=%d", geteuid()), xmprintf("UID=%d", uid),
xmprintf("PPID=%d", myppid)};
struct stat st;
+ struct sh_vars *shv;
struct utsname uu;
+ // Create initial function context
+ call_function();
+ TT.ff->arg.v = toys.optargs;
+ TT.ff->arg.c = toys.optc;
+
// Initialize magic and read only local variables
srandom(TT.SECONDS = millitime());
for (ii = 0; ii<ARRAY_LEN(magic); ii++)
- initlocal(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii]));
+ initvar(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii]));
for (ii = 0; ii<ARRAY_LEN(readonly); ii++)
- addvar(readonly[ii])->flags = VAR_READONLY|VAR_INT;
+ addvar(readonly[ii], TT.ff)->flags = VAR_READONLY|VAR_INT;
// Add local variables that can be overwritten
- initlocal("PATH", _PATH_DEFPATH);
+ initvar("PATH", _PATH_DEFPATH);
if (!pw) pw = (void *)toybuf; // first use, so still zeroed
sprintf(toybuf+1024, "%u", uid);
- initlocaldef("HOME", pw->pw_dir, "/");
- initlocaldef("SHELL", pw->pw_shell, "/bin/sh");
- initlocaldef("USER", pw->pw_name, toybuf+1024);
- initlocaldef("LOGNAME", pw->pw_name, toybuf+1024);
+ initvardef("HOME", pw->pw_dir, "/");
+ initvardef("SHELL", pw->pw_shell, "/bin/sh");
+ initvardef("USER", pw->pw_name, toybuf+1024);
+ initvardef("LOGNAME", pw->pw_name, toybuf+1024);
gethostname(toybuf, sizeof(toybuf)-1);
- initlocal("HOSTNAME", toybuf);
+ initvar("HOSTNAME", toybuf);
uname(&uu);
- initlocal("HOSTTYPE", uu.machine);
+ initvar("HOSTTYPE", uu.machine);
sprintf(toybuf, "%s-unknown-linux", uu.machine);
- initlocal("MACHTYPE", toybuf);
- initlocal("OSTYPE", uu.sysname);
+ initvar("MACHTYPE", toybuf);
+ initvar("OSTYPE", uu.sysname);
// sprintf(toybuf, "%s-toybox", TOYBOX_VERSION);
- // initlocal("BASH_VERSION", toybuf);
- initlocal("OPTERR", "1"); // TODO: test if already exported?
+ // initvar("BASH_VERSION", toybuf); TODO
+ initvar("OPTERR", "1"); // TODO: test if already exported?
if (readlink0("/proc/self/exe", s = toybuf, sizeof(toybuf))||(s=getenv("_")))
- initlocal("BASH", s);
- initlocal("PS2", "> ");
- initlocal("PS3", "#? ");
- initlocal("PS4", "+ ");
+ initvar("BASH", s);
+ initvar("PS2", "> ");
+ initvar("PS3", "#? ");
+ initvar("PS4", "+ ");
// Ensure environ copied and toys.envc set, and clean out illegal entries
TT.ifs = " \t\n";
- xsetenv("", 0);
- for (to = from = pid = ppid = zpid = 0; (s = environ[from]); from++) {
+
+ for (from = pid = ppid = zpid = 0; (s = environ[from]); from++) {
// If nommu subshell gets handoff
if (!CFG_TOYBOX_FORK && !toys.stacktop) {
@@ -3320,27 +3407,23 @@ static void subshell_setup(void)
}
// Filter out non-shell variable names from inherited environ.
- // (haven't xsetenv() yet so no need to free() or adjust toys.envc)
- ss = varend(s);
- if (*ss == '=') {
- struct sh_vars *shv = findvar(s);
+ if (*varend(s) != '=') continue;
- if (!shv) addvar(environ[from])->flags = VAR_GLOBAL;
- else if (shv->flags&VAR_READONLY) continue;
- else {
- shv->flags |= VAR_GLOBAL;
+ if (!(shv = findvar(s, 0))) addvar(s, TT.ff)->flags = VAR_GLOBAL|VAR_NOFREE;
+ else if (shv->flags&VAR_READONLY) continue;
+ else {
+ if (!(shv->flags&VAR_NOFREE)) {
free(shv->str);
- shv->str = s;
+ shv->flags ^= VAR_NOFREE;
}
- environ[to++] = s;
+ shv->flags |= VAR_GLOBAL;
+ shv->str = s;
}
if (!memcmp(s, "IFS=", 4)) TT.ifs = s+4;
}
- environ[to++] = 0;
- toys.envc = to;
// set/update PWD
- sh_run("cd .");
+ do_source(0, fmemopen("cd .", 4, "r"));
// set _ to path to this shell
s = toys.argv[0];
@@ -3350,31 +3433,30 @@ static void subshell_setup(void)
s = xmprintf("%s/%s", ss, s);
free(ss);
ss = s;
- } else if (*toybuf) s = toybuf;
+ } else if (*toybuf) s = toybuf; // from /proc/self/exe
}
- s = xsetenv("_", s);
- if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL;
+ setvarval("_", s)->flags |= VAR_GLOBAL;
free(ss);
if (!(ss = getvar("SHLVL"))) export("SHLVL=1");
else {
char buf[16];
sprintf(buf, "%u", atoi(ss+6)+1);
- xsetenv("SHLVL", buf);
- export("SHLVL");
+ setvarval("SHLVL", buf)->flags |= VAR_GLOBAL;
}
//TODO indexed array,associative array,integer,local,nameref,readonly,uppercase
// if (s+1<ss && strchr("aAilnru", *s)) {
- // sanity check: magic env variable, pipe status
- if (CFG_TOYBOX_FORK || toys.stacktop || pid!=getpid() || ppid!=myppid) return;
- if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
- TT.pid = zpid;
- fcntl(254, F_SETFD, FD_CLOEXEC);
- do_source("", fdopen(254, "r"));
+ // Are we a nofork subshell? (check magic env variable and pipe status)
+ if (!CFG_TOYBOX_FORK && !toys.stacktop && pid==getpid() && ppid==myppid) {
+ if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
+ TT.pid = zpid;
+ fcntl(254, F_SETFD, FD_CLOEXEC);
+ do_source(0, fdopen(254, "r"));
- xexit();
+ xexit();
+ }
}
void sh_main(void)
@@ -3421,14 +3503,13 @@ void sh_main(void)
xsignal(SIGINT, SIG_IGN);
}
-// TODO unify fmemopen() here with sh_run
if (cc) ff = fmemopen(cc, strlen(cc), "r");
else if (TT.options&FLAG_s) ff = (TT.options&FLAG_i) ? 0 : stdin;
else if (!(ff = fpathopen(*toys.optargs))) perror_exit_raw(*toys.optargs);
// Read and execute lines from file
if (do_source(cc ? : *toys.optargs, ff))
- error_exit("%u:unfinished line"+3*!TT.cc->lineno, TT.cc->lineno);
+ error_exit("%u:unfinished line"+3*!TT.LINENO, TT.LINENO);
}
// TODO: ./blah.sh one two three: put one two three in scratch.arg
@@ -3515,9 +3596,14 @@ void set_main(void)
char *cc, *ostr[] = {"braceexpand", "noclobber", "xtrace"};
int ii, jj, kk, oo = 0, dd = 0;
+ // display visible variables
if (!*toys.optargs) {
+ struct sh_vars **vv = visible_vars();
+
// TODO escape properly
- for (ii = 0; ii<TT.varslen; ii++) printf("%s\n", TT.vars[ii].str);
+ for (ii = 0; vv[ii]; ii++)
+ if (!(vv[ii]->flags&VAR_WHITEOUT)) printf("%s\n", vv[ii]->str);
+ free(vv);
return;
}
@@ -3550,9 +3636,10 @@ void set_main(void)
// handle positional parameters
if (cc) {
struct arg_list *al, **head;
- struct sh_arg *arg = &TT.cc->arg;
+ struct sh_arg *arg = &TT.ff->arg;
- for (al = *(head = &TT.cc->delete); al; al = *(head = &al->next))
+ // don't free memory that's already scheduled for deletion
+ for (al = *(head = &TT.ff->delete); al; al = *(head = &al->next))
if (al->arg == (void *)arg->v) break;
// free last set's memory (if any) so it doesn't accumulate in loop
@@ -3563,8 +3650,8 @@ void set_main(void)
}
while (toys.optargs[ii])
- arg_add(arg, push_arg(&TT.cc->delete, strdup(toys.optargs[ii++])));
- push_arg(&TT.cc->delete, arg->v);
+ arg_add(arg, push_arg(&TT.ff->delete, strdup(toys.optargs[ii++])));
+ push_arg(&TT.ff->delete, arg->v);
}
}
@@ -3595,7 +3682,16 @@ void export_main(void)
// list existing variables?
if (!toys.optc) {
- for (arg = environ; *arg; arg++) xprintf("declare -x %s\n", *arg);
+ struct sh_vars **vv = visible_vars();
+ unsigned uu;
+
+ for (uu = 0; vv[uu]; uu++) {
+ if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+ xputs(eq = declarep(vv[uu]));
+ free(eq);
+ }
+ }
+
return;
}
@@ -3614,19 +3710,17 @@ void export_main(void)
void eval_main(void)
{
- struct sh_arg old = TT.cc->arg, *volatile arg = &TT.cc->arg;
char *s;
- // borrow the $* expand infrastructure (avoiding $* from trap handler race).
- arg->c = 0;
- arg->v = toys.argv;
- arg->c = toys.optc+1;
+ // borrow the $* expand infrastructure
+ call_function();
+ TT.ff->arg.v = toys.argv;
+ TT.ff->arg.c = toys.optc+1;
s = expand_one_arg("\"$*\"", SEMI_IFS, 0);
- arg->c = 0;
- arg->v = old.v;
- arg->c = old.c;
-
- sh_run(s);
+ TT.ff->arg.v = TT.ff->next->arg.v;
+ TT.ff->arg.c = TT.ff->next->arg.c;
+ do_source(0, fmemopen(s, strlen(s), "r"));
+ free(dlist_pop(&TT.ff));
free(s);
}
@@ -3746,19 +3840,67 @@ void jobs_main(void)
}
}
+#define CLEANUP_exec
+#define FOR_local
+#include "generated/flags.h"
+
+void local_main(void)
+{
+ struct sh_fcall *ff, *ff2;
+ struct sh_vars *var;
+ char **arg, *eq;
+
+ // find local variable context
+ for (ff = TT.ff;; ff = ff->next) {
+ if (ff == TT.ff->prev) return error_msg("not in function");
+ if (ff->vars) break;
+ }
+
+ // list existing vars (todo:
+ if (!toys.optc) {
+ for (var = ff->vars; var; var++) xputs(var->str); // TODO escape
+ return;
+ }
+
+ // set/move variables
+ for (arg = toys.optargs; *arg; arg++) {
+ if ((eq = varend(*arg)) == *arg || (*eq && *eq != '=')) {
+ error_msg("bad %s", *arg);
+ continue;
+ }
+
+ if ((var = findvar(*arg, &ff2)) && ff == ff2 && !*eq) continue;
+ if (var && (var->flags&VAR_READONLY)) {
+ error_msg("%.*s: readonly variable", (int)(varend(*arg)-*arg), *arg);
+ continue;
+ }
+
+ // Add local inheriting global status and setting whiteout if blank.
+ if (!var || ff!=ff2) {
+ int flags = var ? var->flags&VAR_GLOBAL : 0;
+
+ var = addvar(xmprintf("%s%s", *arg, *eq ? "" : "="), ff);
+ var->flags = flags|(VAR_WHITEOUT*!*eq);
+ }
+
+ // TODO accept declare options to set more flags
+ // TODO, integer, uppercase take effect. Setvar?
+ }
+}
+
void shift_main(void)
{
long long by = 1;
if (toys.optc) by = atolx(*toys.optargs);
- by += TT.cc->shift;
- if (by<0 || by>=TT.cc->arg.c) toys.exitval++;
- else TT.cc->shift = by;
+ by += TT.ff->shift;
+ if (by<0 || by>=TT.ff->arg.c) toys.exitval++;
+ else TT.ff->shift = by;
}
void source_main(void)
{
- char *name = toys.optargs[1];
+ char *name = *toys.optargs;
FILE *ff = fpathopen(name);
if (!ff) return perror_msg_raw(name);
@@ -3766,6 +3908,13 @@ void source_main(void)
// $0 is shell name, not source file name while running this
// TODO add tests: sh -c "source input four five" one two three
*toys.optargs = *toys.argv;
-
- do_source(name, ff);
+ if (++TT.srclvl>99) error_msg("bad source depth %d", TT.srclvl);
+ else {
+ call_function();
+ TT.ff->arg.v = toys.optargs;
+ TT.ff->arg.c = toys.optc;
+ do_source(name, ff);
+ free(dlist_pop(&TT.ff));
+ }
+ --TT.srclvl;
}