blob: 07ba9b7254956b3065491223c46ab5df86b6e385 [file] [log] [blame]
/* sh.c - toybox shell
*
* Copyright 2006 Rob Landley <rob@landley.net>
*
* This shell aims for bash compatibility. The bash man page is at:
* http://man7.org/linux/man-pages/man1/bash.1.html
*
* The POSIX-2008/SUSv4 shell spec is at:
* http://opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
* and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html
*
* The chap02 link describes the following shell builtins:
*
* break : continue exit
* . eval exec export readonly return set shift times trap unset
*
* The second link (the utilities directory) also contains specs for the
* following shell builtins:
*
* cd ulimit umask
* alias bg command fc fg getopts hash jobs kill read type unalias wait
*
* deviations from posix: don't care about $LANG or $LC_ALL
* TODO: test that $PS1 color changes work without stupid \[ \] hack
* TODO: Handle embedded NUL bytes in the command line? (When/how?)
* builtins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
* disown umask suspend source pushd popd dirs logout times trap
* unset local export readonly set : . let history declare
* "special" builtins: break continue eval exec return shift
* builtins with extra shell behavior: kill pwd time test
* | & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
* * ? [ # ~ = %
* ! { } case do done elif else esac fi for if in then until while
* [[ ]] function select
* label:
* TODO: test exit from "trap EXIT" doesn't recurse
* TODO: ! history expansion
* TODO: getuid() vs geteuid()
*
* bash man page:
* control operators || & && ; ;; ;& ;;& ( ) | |& <newline>
* reserved words
* ! case coproc do done elif else esac fi for function if in select
* then until while { } time [[ ]]
USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK))
USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK))
USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
// Login lies in argv[0], so add some aliases to catch that
USE_SH(OLDTOY(-sh, sh, 0))
USE_SH(OLDTOY(-toysh, sh, 0))
USE_SH(OLDTOY(-bash, sh, 0))
config SH
bool "sh (toysh)"
default n
help
usage: sh [-c command] [script]
Command shell. Runs a shell script, or reads input interactively
and responds to it.
-c command line to execute
-i interactive mode (default when STDIN is a tty)
# These are here for the help text, they're not selectable and control nothing
config CD
bool
default n
depends on SH
help
usage: cd [-PL] [path]
Change current directory. With no arguments, go $HOME.
-P Physical path: resolve symlinks in path
-L Local path: .. trims directories off $PWD (default)
config EXIT
bool
default n
depends on SH
help
usage: exit [status]
Exit shell. If no return value supplied on command line, use value
of most recent command, or 0 if none.
config UNSET
bool
default n
depends on SH
help
usage: unset [-fvn] NAME...
-f NAME is a function
-v NAME is a variable
-n dereference NAME and unset that
config EVAL
bool
default n
depends on SH
help
usage: eval COMMAND...
Execute (combined) arguments as a shell command.
config EXEC
bool
default n
depends on SH
help
usage: exec [-cl] [-a NAME] COMMAND...
-a set argv[0] to NAME
-c clear environment
-l prepend - to argv[0]
config EXPORT
bool
default n
depends on SH
help
usage: export [-n] [NAME[=VALUE]...]
Make variables available to child processes. NAME exports existing local
variable(s), NAME=VALUE sets and exports.
-n Unexport. Turn listed variable(s) into local variables.
With no arguments list exported variables/attributes as "declare" statements.
config SHIFT
bool
default n
depends on SH
help
usage: shift [N]
Skip N (default 1) positional parameters, moving $1 and friends along the list.
Does not affect $0.
*/
#define FOR_sh
#include "toys.h"
GLOBALS(
union {
struct {
char *c;
} sh;
struct {
char *a;
} exec;
};
// keep lineno here, we use it to work around a compiler bug
long lineno;
char *ifs, *isexec;
struct double_list functions;
unsigned options, jobcnt;
int hfd, pid, varslen, shift, cdcount;
unsigned long long SECONDS;
struct sh_vars {
long flags;
char *str;
} *vars;
// Running jobs for job control.
struct sh_job {
struct sh_job *next, *prev;
unsigned jobno;
// Every pipeline has at least one set of arguments or it's Not A Thing
struct sh_arg {
char **v;
int c;
} pipeline;
// null terminated array of running processes in pipeline
struct sh_process {
struct sh_process *next, *prev;
struct arg_list *delete; // expanded strings
// undo redirects, a=b at start, child PID, exit status, has !
int *urd, envlen, pid, exit, not;
struct sh_arg arg;
} *procs, *proc;
} *jobs, *job;
struct sh_process *pp;
struct sh_arg *arg;
)
// 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);
// Pipeline segments
struct sh_pipeline {
struct sh_pipeline *next, *prev;
int count, here, type;
struct sh_arg arg[1];
};
// scratch space (state held between calls). Don't want to make it global yet
// because this could be reentrant.
struct sh_function {
char *name;
struct sh_pipeline *pipeline;
struct double_list *expect;
// TODO: lifetime rules for arg? remember "shift" command.
struct sh_arg *arg; // arguments to function call
char *end;
};
#define BUGBUG 0
// call with NULL to just dump FDs
static void dump_state(struct sh_function *sp)
{
struct sh_pipeline *pl;
long i;
int q = 0, fd = open("/proc/self/fd", O_RDONLY);
DIR *dir = fdopendir(fd);
char buf[256];
if (sp && sp->expect) {
struct double_list *dl;
for (dl = sp->expect; dl; dl = (dl->next == sp->expect) ? 0 : dl->next)
dprintf(255, "expecting %s\n", dl->data);
if (sp->pipeline)
dprintf(255, "pipeline count=%d here=%d\n", sp->pipeline->prev->count,
sp->pipeline->prev->here);
}
if (sp) for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) {
for (i = 0; i<pl->arg->c; i++)
dprintf(255, "arg[%d][%ld]=%s\n", q, i, pl->arg->v[i]);
if (pl->arg->c<0) dprintf(255, "argc=%d\n", pl->arg->c);
else dprintf(255, "type=%d term[%d]=%s\n", pl->type, q++, pl->arg->v[pl->arg->c]);
}
if (dir) {
struct dirent *dd;
while ((dd = readdir(dir))) {
if (atoi(dd->d_name)!=fd && 0<readlinkat(fd, dd->d_name, buf,sizeof(buf)))
dprintf(255, "OPEN %d: %s = %s\n", getpid(), dd->d_name, buf);
}
closedir(dir);
}
close(fd);
}
// ordered for greedy matching, so >&; becomes >& ; not > &;
// making these const means I need to typecast the const away later to
// avoid endless warnings.
static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
">&", ">|", ">", "&>>", "&>", 0};
#define OPT_I 1
#define OPT_BRACE 2 // set -B
#define OPT_NOCLOBBER 4 // set -C
#define OPT_S 8
#define OPT_C 16
static void syntax_err(char *s)
{
error_msg("syntax error: %s", s);
toys.exitval = 2;
}
// append to array with null terminator and realloc as necessary
static void array_add(char ***list, unsigned count, char *data)
{
if (!(count&31)) *list = xrealloc(*list, sizeof(char *)*(count+33));
(*list)[count] = data;
(*list)[count+1] = 0;
}
// add argument to an arg_list
static void add_arg(struct arg_list **list, char *arg)
{
struct arg_list *al;
if (!list) return;
al = xmalloc(sizeof(struct arg_list));
al->next = *list;
al->arg = arg;
*list = al;
}
static void array_add_del(char ***list, unsigned count, char *data,
struct arg_list **delete)
{
add_arg(delete, data);
array_add(list, count, data);
}
// return length of valid variable name
static char *varend(char *s)
{
if (isdigit(*s)) return s;
while (*s>' ' && (*s=='_' || !ispunct(*s))) s++;
return s;
}
// Return index of variable within this list
static struct sh_vars *findvar(char *name)
{
int len = varend(name)-name;
struct sh_vars *var = TT.vars+TT.varslen;
if (len) while (var-- != TT.vars)
if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
return 0;
}
// Append variable to TT.vars, returning *struct. Does not check duplicates.
static struct sh_vars *addvar(char *s)
{
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;
return TT.vars+TT.varslen++;
}
// TODO function to resolve a string into a number for $((1+2)) etc
long long do_math(char *s)
{
return atoll(s);
}
// 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)
{
int len = varend(s)-s;
long flags;
struct sh_vars *var;
if (s[len] != '=') {
error_msg("bad setvar %s\n", s);
free(s);
return 0;
}
if (len == 3 && !memcmp(s, "IFS", 3)) TT.ifs = s+4;
if (!(var = findvar(s))) return addvar(s);
flags = var->flags;
if (flags&VAR_READONLY) {
error_msg("%.*s: read only", len, s);
free(s);
return var;
} else if (flags&VAR_MAGIC) {
if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(s+len-1);
else if (*s == 'R') srandom(do_math(s+len-1));
} else if (flags&VAR_GLOBAL) xsetenv(var->str = s, 0);
else {
free(var->str);
var->str = s;
}
// TODO if (flags&(VAR_TOUPPER|VAR_TOLOWER))
// unicode _is stupid enough for upper/lower case to be different utf8 byte
// lengths. example: lowercase of U+0130 (C4 B0) is U+0069 (69)
// TODO VAR_INT
// TODO VAR_ARRAY VAR_DICT
return var;
}
static void unsetvar(char *name)
{
struct sh_vars *var = findvar(name);
int ii = var-TT.vars;
if (!var) return;
if (var->flags&VAR_GLOBAL) xunsetenv(name);
else free(var->str);
memmove(TT.vars+ii, TT.vars+ii+1, TT.varslen-ii);
TT.varslen--;
}
static struct sh_vars *setvarval(char *name, char *val)
{
return setvar(xmprintf("%s=%s", name, val));
}
// get value of variable starting at s.
static char *getvar(char *s)
{
struct sh_vars *var = findvar(s);
if (!var) return 0;
if (var->flags & VAR_MAGIC) {
char c = *var->str;
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, "%ld", TT.lineno);
else if (c == 'G') sprintf(toybuf, "TODO: GROUPS");
return toybuf;
}
return varend(var->str)+1;
}
// malloc declare -x "escaped string"
static char *declarep(struct sh_vars *var)
{
char *types = "-rgnuliaA", *in = types, flags[16], *out = flags, *ss;
int len;
while (*++in) if (var->flags&(1<<(in-types))) *out++ = *in;
if (in == types) *out++ = *types;
*out = 0;
len = out-flags;
for (in = types = varend(var->str); *in; in++) len += !!strchr("$\"\\`", *in);
len += in-types;
ss = xmalloc(len+13);
out = ss + sprintf(ss, "declare -%s \"", out);
while (types) {
if (strchr("$\"\\`", *in)) *out++ = '\\';
*out++ = *types++;
}
*out++ = '"';
*out = 0;
return ss;
}
// return length of match found at this point (try is null terminated array)
static int anystart(char *s, char **try)
{
char *ss = s;
while (*try) if (strstart(&s, *try++)) return s-ss;
return 0;
}
// does this entire string match one of the strings in try[]
static int anystr(char *s, char **try)
{
while (*try) if (!strcmp(s, *try++)) return 1;
return 0;
}
// return length of valid prefix that could go before redirect
static int redir_prefix(char *word)
{
char *s = word;
if (*s == '{') {
if (*(s = varend(s+1)) == '}' && s != word+1) s++;
else s = word;
} else while (isdigit(*s)) s++;
return s-word;
}
// parse next word from command line. Returns end, or 0 if need continuation
// caller eats leading spaces. early = skip one quote block (or return start)
static char *parse_word(char *start, int early)
{
int i, quote = 0, q, qc = 0;
char *end = start, *s;
// Things we should only return at the _start_ of a word
if (strstart(&end, "<(") || strstart(&end, ">(")) toybuf[quote++]=')';
// Redirections. 123<<file- parses as 2 args: "123<<" "file-".
s = end + redir_prefix(end);
if ((i = anystart(s, (void *)redirectors))) return s+i;
// (( is a special quote at the start of a word
if (strstart(&end, "((")) toybuf[quote++] = 254;
// find end of this word
while (*end) {
i = 0;
// barf if we're near overloading quote stack (nesting ridiculously deep)
if (quote>4000) {
syntax_err("tilt");
return (void *)1;
}
// Handle quote contexts
if ((q = quote ? toybuf[quote-1] : 0)) {
// when waiting for parentheses, they nest
if ((q == ')' || q >= 254) && (*end == '(' || *end == ')')) {
if (*end == '(') qc++;
else if (qc) qc--;
else if (q >= 254) {
// (( can end with )) or retroactively become two (( if we hit one )
if (strstart(&end, "))")) quote--;
else if (q == 254) return start+1;
else if (q == 255) toybuf[quote-1] = ')';
} else if (*end == ')') quote--;
end++;
// end quote?
} else if (*end == q) quote--, end++;
// single quote claims everything
else if (q == '\'') end++;
else i++;
// loop if we already handled a symbol and aren't stopping early
if (early && !quote) return end;
if (!i) continue;
} else {
// Things that only matter when unquoted
if (isspace(*end)) break;
if (*end == ')') return end+(start==end);
// Flow control characters that end pipeline segments
s = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
"|&", "|", "&&", "&", "(", ")", 0});
if (s != end) return (end == start) ? s : end;
}
// Things the same unquoted or in most non-single-quote contexts
// start new quote context?
if (strchr("\"'`", *end)) toybuf[quote++] = *end;
// backslash escapes
else if (*end == '\\') {
if (!end[1] || (end[1]=='\n' && !end[2])) return 0;
end += 2;
} else if (*end == '$' && -1 != (i = stridx("({[", end[1]))) {
end++;
if (strstart(&end, "((")) toybuf[quote++] = 255;
else {
toybuf[quote++] = ")}]"[i];
end++;
}
}
if (early && !quote) return end;
end++;
}
return quote ? 0 : end;
}
// Return next available high (>=10) file descriptor
static int next_hfd()
{
int hfd;
for (; TT.hfd<=99999; TT.hfd++) if (-1 == fcntl(TT.hfd, F_GETFL)) break;
hfd = TT.hfd;
if (TT.hfd > 99999) {
hfd = -1;
if (!errno) errno = EMFILE;
}
return hfd;
}
// Perform a redirect, saving displaced filehandle to a high (>10) fd
// rd is an int array: [0] = count, followed by from/to pairs to restore later.
// If from >= 0 dup from->to after saving to. If from == -1 just save to.
// if from == -2 schedule "to" to be closed by unredirect.
static int save_redirect(int **rd, int from, int to)
{
int cnt, hfd, *rr;
if (from == to) return 0;
// save displaced to, copying to high (>=10) file descriptor to undo later
// except if we're saving to environment variable instead (don't undo that)
if (from>-2) {
if ((hfd = next_hfd())==-1) return 1;
if (hfd != dup2(to, hfd)) hfd = -1;
else fcntl(hfd, F_SETFD, FD_CLOEXEC);
if (BUGBUG) dprintf(255, "%d redir from=%d to=%d hfd=%d\n", getpid(), from, to, hfd);
// dup "to"
if (from >= 0 && to != dup2(from, to)) {
if (hfd >= 0) close(hfd);
return 1;
}
} else {
if (BUGBUG) dprintf(255, "%d schedule close %d\n", getpid(), to);
hfd = to;
to = -1;
}
// Append undo information to redirect list so we can restore saved hfd later.
if (!((cnt = *rd ? **rd : 0)&31)) *rd = xrealloc(*rd, (cnt+33)*2*sizeof(int));
*(rr = *rd) = ++cnt;
rr[2*cnt-1] = hfd;
rr[2*cnt] = to;
return 0;
}
// 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);
// TODO: test $$ in (nommu)
}
// TODO check every caller of run_subshell for error, or syntax_error() here
// from pipe() failure
// Pass environment and command string to child shell, return PID of child
static int run_subshell(char *str, int len)
{
pid_t pid;
if (BUGBUG) dprintf(255, "run_subshell %.*s\n", len, str);
// 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);
}
// On nommu vfork, exec /proc/self/exe, and pipe state data to ourselves.
} else {
int pipes[2], i, c;
// 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
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;
}
// marshall data 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));
free(s);
}
dprintf(pipes[1], "%.*s\n", len, str);
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 (BUGBUG) dprintf(255, "%d urd %d %d\n", getpid(), rr[0], rr[1]);
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)
{
int pipes[2], *uu = 0, in = !out;
// Grab subshell data
if (pipe(pipes)) {
perror_msg("%.*s", len, s);
return -1;
}
// Perform input or output redirect and launch process (ignoring errors)
save_redirect(&uu, pipes[in], in);
close(pipes[in]);
run_subshell(s, len);
unredirect(uu);
return pipes[out];
}
// utf8 strchr: return wide char matched at wc from chrs, or 0 if not matched
// if len, save length of wc
static int utf8chr(char *wc, char *chrs, int *len)
{
wchar_t wc1, wc2;
int ll;
if (len) *len = 1;
if (!*wc) return 0;
if (0<(ll = utf8towc(&wc1, wc, 99))) {
if (len) *len = ll;
while (*chrs) {
if(1>(ll = utf8towc(&wc2, chrs, 99))) chrs++;
else {
if (wc1 == wc2) return wc1;
chrs += ll;
}
}
}
return 0;
}
#define NO_PATH (1<<0) // path expansion (wildcards)
#define NO_SPLIT (1<<1) // word splitting
#define NO_BRACE (1<<2) // {brace,expansion}
#define NO_TILDE (1<<3) // ~username/path
#define NO_QUOTE (1<<4) // quote removal
#define SEMI_IFS (1<<5) // Use ' ' instead of IFS to combine $*
// TODO: parameter/variable $(command) $((math)) split pathglob
// TODO: ${name:?error} causes an error/abort here (syntax_err longjmp?)
// TODO: $1 $@ $* need args marshalled down here: function+structure?
// arg = append to this
// str = string to expand
// flags = type of expansions (not) to do
// delete = append new allocations to this so they can be freed later
// TODO: at_args: $1 $2 $3 $* $@
static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags,
struct arg_list **delete)
{
char cc, qq = 0, *old = str, *new = str, *s, *ss, *ifs, **aa;
int ii = 0, dd, jj, kk, ll, oo = 0, nodel;
if (BUGBUG) dprintf(255, "expand %s\n", str);
// Tilde expansion
if (!(flags&NO_TILDE) && *str == '~') {
struct passwd *pw = 0;
ss = 0;
while (str[ii] && str[ii]!=':' && str[ii]!='/') ii++;
if (ii==1) {
if (!(ss = getvar("HOME")) || !*ss) pw = bufgetpwuid(getuid());
} else {
// TODO bufgetpwnam
pw = getpwnam(s = xstrndup(str+1, ii-1));
free(s);
}
if (pw) {
ss = pw->pw_dir;
if (!ss || !*ss) ss = "/";
}
if (ss) {
oo = strlen(ss);
s = xmprintf("%s%s", ss, str+ii);
if (old != new) free(new);
new = s;
}
}
// parameter/variable expansion, and dequoting
for (; (cc = str[ii++]); old!=new && (new[oo] = 0)) {
// skip literal chars
if (!strchr("$'`\\\"", cc)) {
if (old != new) new[oo++] = cc;
continue;
}
// allocate snapshot if we just started modifying
if (old == new) {
new = xstrdup(new);
new[oo = ii-1] = 0;
}
ifs = 0;
aa = 0;
nodel = 0;
// handle different types of escapes
if (cc == '\\') new[oo++] = str[ii] ? str[ii++] : cc;
else if (cc == '"') qq++;
else if (cc == '\'') {
if (qq&1) new[oo++] = cc;
else {
qq += 2;
while ((cc = str[ii++]) != '\'') new[oo++] = cc;
}
// both types of subshell work the same, so do $( here not in '$' below
// TODO $((echo hello) | cat) ala $(( becomes $( ( retroactively
} else if (cc == '`' || (cc == '$' && strchr("([", str[ii]))) {
off_t pp = 0;
s = str+ii-1;
kk = parse_word(s, 1)-s;
if (str[ii] == '[' || *toybuf == 255) {
s += 2+(str[ii]!='[');
kk -= 3+2*(str[ii]!='[');
dprintf(2, "TODO: do math for %.*s\n", kk, s);
} else {
// Run subshell and trim trailing newlines
s += (jj = 1+(cc == '$'));
ii += --kk;
kk -= jj;
// Special case echo $(<input)
for (ss = s; isspace(*ss); ss++);
if (*ss != '<') ss = 0;
else {
while (isspace(*++ss));
if (!(ll = parse_word(ss, 0)-ss)) ss = 0;
else {
jj = ll+(ss-s);
while (isspace(s[jj])) jj++;
if (jj != kk) ss = 0;
else {
jj = xcreate_stdio(ss = xstrndup(ss, ll), O_RDONLY|WARN_ONLY, 0);
free(ss);
}
}
}
// TODO what does \ in `` mean? What is echo `printf %s \$x` supposed to do?
if (!ss) jj = pipe_subshell(s, kk, 0);
if ((ifs = readfd(jj, 0, &pp)))
for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0);
close(jj);
}
} else if (cc == '$') {
// parse $ $'' ${} or $VAR
cc = str[ii++];
if (cc=='\'') {
for (s = str+ii; *s != '\''; oo += wcrtomb(new+oo, unescape2(&s, 0),0));
ii = s-str+1;
continue;
} else if (cc == '{') {
cc = *(ss = str+ii);
if (!(jj = strchr(ss, '}')-ss)) ifs = (void *)1;
ii += jj+1;
if (jj>1) {
// handle ${!x} and ${#x}
if (*ss == '!') {
if (!(ss = getvar(ss+1)) || !*ss) continue;
jj = varend(ss)-ss;
if (ss[jj]) ifs = (void *)1;
} else if (*ss == '#') {
if (jj == 2 && (*ss == '@' || *ss == '*')) jj--;
else ifs = xmprintf("%ld", (long)strlen(getvar(ss) ? : ""));
}
}
} else {
ss = str+--ii;
if (!(jj = varend(ss)-ss)) jj++;
ii += jj;
}
// ${#nom} ${} ${x}
// ${x:-y} use default
// ${x:=y} assign default (error if positional)
// ${x:?y} err if null
// ${x:+y} alt value
// ${x:off} ${x:off:len} off<0 from end (must ": -"), len<0 also from end must
// 0-based indexing
// ${@:off:len} positional parameters, off -1 = len, -len is error
// 1-based indexing
// ${!x} deref (does bad substitution if name has : in it)
// ${!x*} ${!x@} names matching prefix
// note: expands something other than arg->c
// ${x#y} remove shortest prefix ${x##y} remove longest prefix
// x can be @ or *
// ${x%y} ${x%%y} suffix
// ${x/pat/sub} substitute ${x//pat/sub} global ${x/#pat/sub} begin
// ${x/%pat/sub} end ${x/pat} delete pat
// x can be @ or *
// ${x^pat} ${x^^pat} uppercase/g ${x,} ${x,,} lowercase/g (no pat = ?)
// ${x@QEPAa} Q=$'blah' E=blah without the $'' wrap, P=expand as $PS1
// A=declare that recreates var a=attribute flags
// x can be @*
// TODO: $_ is last arg of last command, and exported as path to exe run
// TODO: $! is PID of most recent background job
if (ifs);
else if (cc == '-') {
s = ifs = xmalloc(8);
if (TT.options&OPT_I) *s++ = 'i';
if (TT.options&OPT_BRACE) *s++ = 'B';
if (TT.options&OPT_S) *s++ = 's';
if (TT.options&OPT_C) *s++ = 'c';
*s = 0;
} else if (cc == '?') ifs = xmprintf("%d", toys.exitval);
else if (cc == '$') ifs = xmprintf("%d", TT.pid);
else if (cc == '#') ifs = xmprintf("%d", TT.arg->c?TT.arg->c-1:0);
else if (cc == '*' || cc == '@') aa = TT.arg->v+1;
else if (isdigit(cc)) {
for (kk = ll = 0; kk<jj && isdigit(ss[kk]); kk++)
ll = (10*ll)+ss[kk]-'0';
if (ll) ll += TT.shift;
if (ll<TT.arg->c) ifs = TT.arg->v[ll];
nodel = 1;
// $VARIABLE
} else {
if (ss == varend(ss)) {
ii--;
if (ss[-1] == '$') new[oo++] = '$';
else ifs = (void *)1;
} else ifs = getvar(ss);
nodel = 1;
}
}
// TODO: $((a=42)) can change var, affect lifetime
// must replace ifs AND any previous output arg[] within pointer strlen()
// TODO ${blah} here
if (ifs == (void *)1) {
error_msg("%.*s: bad substitution", (int)(s-(str+ii)+3), str+ii-2);
free(new);
return 1;
}
// combine before/ifs/after sections, splitting words on $IFS in ifs
if (ifs || aa) {
char sep[8];
// If not gluing together, nothing to substitute, not quoted: do nothing
if (!aa && !*ifs && !qq) continue;
// Fetch separator
*sep = 0;
if ((qq&1) && cc=='*') {
wchar_t wc;
if (flags&SEMI_IFS) strcpy(sep, " ");
else if (0<(dd = utf8towc(&wc, TT.ifs, 4)))
sprintf(sep, "%.*s", dd, TT.ifs);
}
// when aa proceed through entries until NULL, else process ifs once
do {
// get next argument, is this last entry, find end of word
if (aa) {
ifs = *aa ? : "";
if (*aa) aa++;
nodel = 1;
}
if (qq&1) ss = ifs+strlen(ifs);
else for (ss = ifs; *ss; ss += kk)
if ((ll = utf8chr(ss, TT.ifs, &kk))) break;
kk = !aa || !*aa;
// loop within current ifs checking region to split words
do {
// fast path: use existing memory when no prefix, not splitting it,
// and either not last entry or no suffix
if (!oo && !*ss && (!kk || !str[ii]) && !((qq&1) && cc=='*')) {
if (!qq && ss==ifs) break;
array_add_del(&arg->v, arg->c++, ifs, nodel ? 0 : delete);
nodel = 1;
continue;
}
// resize allocation and copy next chunk of IFS-free data
new = xrealloc(new, oo + (ss-ifs) + strlen(sep) +
((jj = kk && !*ss) ? strlen(str+ii) : 0) + 1);
oo += sprintf(new + oo, "%.*s", (int)(ss-ifs), ifs);
if (!nodel) free(ifs);
nodel = 1;
if (jj) break;
// for single quoted "$*" append IFS
if ((qq&1) && cc == '*') oo += sprintf(new+oo, "%s", sep);
// add argument if quoted, non-blank, or non-whitespace separator
else {
if (qq || *new || *ss) {
array_add_del(&arg->v, arg->c++, new, nodel ? 0 : delete);
nodel = 1;
}
qq &= 1;
new = xstrdup(str+ii);
oo = 0;
}
// Skip trailing seperator (combining whitespace)
while ((jj = utf8chr(ss, TT.ifs, &ll)) && iswspace(jj)) ss += ll;
} while (*(ifs = ss));
} while (!kk);
}
}
// TODO globbing * ? [
// Word splitting completely eliminating argument when no non-$IFS data left
// wordexp keeps pattern when no matches
// TODO NO_SPLIT cares about IFS, see also trailing \n
// quote removal
// Record result.
if (*new || qq)
array_add_del(&arg->v, arg->c++, new, (old != new) ? delete : 0);
else if(old != new) free(new);
return 0;
}
// expand braces (ala {a,b,c}) and call expand_arg_nobrace() each permutation
static int expand_arg(struct sh_arg *arg, char *old, unsigned flags,
struct arg_list **delete)
{
struct brace {
struct brace *next, *prev, *stack;
int active, cnt, idx, dots[2], commas[];
} *bb = 0, *blist = 0, *bstk, *bnext;
int i, j;
char *s, *ss;
// collect brace spans
if ((TT.options&OPT_BRACE) && !(flags&NO_BRACE)) for (i = 0; ; i++) {
while ((s = parse_word(old+i, 1)) != old+i) i += s-(old+i);
if (!bb && !old[i]) break;
if (bb && (!old[i] || old[i] == '}')) {
bb->active = bb->commas[bb->cnt+1] = i;
for (bnext = bb; bb && bb->active; bb = (bb==blist)?0:bb->prev);
if (!old[i] || !bnext->cnt) // discard commaless brace from start/middle
free(dlist_pop((blist == bnext) ? &blist : &bnext));
} else if (old[i] == '{') {
dlist_add_nomalloc((void *)&blist,
(void *)(bb = xzalloc(sizeof(struct brace)+34*4)));
bb->commas[0] = i;
} else if (!bb) continue;
else if (bb && old[i] == ',') {
if (bb->cnt && !(bb->cnt&31)) {
dlist_lpop(&blist);
dlist_add_nomalloc((void *)&blist,
(void *)(bb = xrealloc(bb, sizeof(struct brace)+(bb->cnt+34)*4)));
}
bb->commas[++bb->cnt] = i;
}
}
// TODO NOSPLIT with braces? (Collate with spaces?)
// If none, pass on verbatim
if (!blist) return expand_arg_nobrace(arg, old, flags, delete);
// enclose entire range in top level brace.
(bstk = xzalloc(sizeof(struct brace)+8))->commas[1] = strlen(old)+1;
bstk->commas[0] = -1;
// loop through each combination
for (;;) {
// Brace expansion can't be longer than original string. Keep start to {
s = ss = xmalloc(bstk->commas[1]);
// Append output from active braces (in "saved" list)
for (bb = blist; bb;) {
// keep prefix and push self onto stack
if (bstk == bb) bstk = bstk->stack; // pop self
i = bstk->commas[bstk->idx]+1;
if (bstk->commas[bstk->cnt+1]>bb->commas[0])
s = stpncpy(s, old+i, bb->commas[0]-i);
// pop sibling
if (bstk->commas[bstk->cnt+1]<bb->commas[0]) bstk = bstk->stack;
bb->stack = bstk; // push
bb->active = 1;
bstk = bnext = bb;
// skip inactive spans from earlier or later commas
while ((bnext = (bnext->next==blist) ? 0 : bnext->next)) {
i = bnext->commas[0];
// past end of this brace
if (i>bb->commas[bb->cnt+1]) break;
// in this brace but not this selection
if (i<bb->commas[bb->idx] || i>bb->commas[bb->idx+1]) {
bnext->active = 0;
bnext->stack = 0;
// in this selection
} else break;
}
// is next span past this range?
if (!bnext || bnext->commas[0]>bb->commas[bb->idx+1]) {
// output uninterrupted span
i = bb->commas[bstk->idx]+1;
s = stpncpy(s, old+i, bb->commas[bb->idx+1]-i);
// While not sibling, output tail and pop
while (!bnext || bnext->commas[0] > bstk->commas[bstk->cnt+1]) {
if (!(bb = bstk->stack)) break;
i = bstk->commas[bstk->cnt+1]+1; // start of span
j = bb->commas[bb->idx+1]; // enclosing comma span
while (bnext) {
if (bnext->commas[0]<j) {
j = bnext->commas[0];// sibling
break;
} else if (bb->commas[bb->cnt+1]>bnext->commas[0])
bnext = (bnext->next == blist) ? 0 : bnext->next;
else break;
}
s = stpncpy(s, old+i, j-i);
// if next is sibling but parent _not_ a sibling, don't pop
if (bnext && bnext->commas[0]<bstk->stack->commas[bstk->stack->cnt+1])
break;
bstk = bstk->stack;
}
}
bb = (bnext == blist) ? 0 : bnext;
}
// Save result, aborting on expand error
add_arg(delete, ss);
if (expand_arg_nobrace(arg, ss, flags, delete)) {
llist_traverse(blist, free);
return 1;
}
// increment
for (bb = blist->prev; bb; bb = (bb == blist) ? 0 : bb->prev) {
if (!bb->stack) continue;
else if (++bb->idx > bb->cnt) bb->idx = 0;
else break;
}
// if increment went off left edge, done expanding
if (!bb) break;
}
llist_traverse(blist, free);
return 0;
}
// Expand exactly one arg, returning NULL on error.
static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
{
struct sh_arg arg;
char *s = 0;
int i;
memset(&arg, 0, sizeof(arg));
if (!expand_arg(&arg, new, flags, del) && arg.c == 1) s = *arg.v;
if (!del && !s) for (i = 0; i < arg.c; i++) free(arg.v[i]);
free(arg.v);
return s;
}
// TODO |&
// turn a parsed pipeline back into a string.
static char *pl2str(struct sh_pipeline *pl)
{
struct sh_pipeline *end = 0;
int level = 0, len = 0, i, j;
char *s, *ss, *sss;
// measure, then allocate
for (j = 0; ; j++) for (end = pl; end; end = end->next) {
if (end->type == 1) level++;
else if (end->type == 3 && --level<0) break;
for (i = 0; i<pl->arg->c; i++)
if (j) ss += sprintf(ss, "%s ", pl->arg->v[i]);
else len += strlen(pl->arg->v[i])+1;
sss = pl->arg->v[pl->arg->c];
if (!sss) sss = ";";
if (j) ss = stpcpy(ss, sss);
else len += strlen(sss);
// TODO add HERE documents back in
if (j) return s;
s = ss = xmalloc(len+1);
}
}
// 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 envlen, int *urd)
{
struct sh_process *pp;
char *s = s, *ss, *sss, *cv = 0;
int j, to, from, here = 0;
TT.hfd = 10;
pp = xzalloc(sizeof(struct sh_process));
pp->urd = urd;
// When we redirect, we copy each displaced filehandle to restore it later.
// Expand arguments and perform redirections
for (j = envlen; j<arg->c; j++) {
int saveclose = 0, bad = 0;
s = arg->v[j];
if (!strcmp(s, "!")) {
pp->not ^= 1;
continue;
}
// Handle <() >() redirectionss
if ((*s == '<' || *s == '>') && s[1] == '(') {
int new = pipe_subshell(s+2, strlen(s+2)-1, *s == '>');
// Grab subshell data
if (new == -1) {
pp->exit = 1;
return pp;
}
save_redirect(&pp->urd, -2, new);
// bash uses /dev/fd/%d which requires /dev/fd to be a symlink to
// /proc/self/fd so we just produce that directly.
array_add_del(&pp->arg.v, pp->arg.c++,
ss = xmprintf("/proc/self/fd/%d", new), &pp->delete);
continue;
}
// Is this a redirect? s = prefix, ss = operator
ss = s + redir_prefix(arg->v[j]);
sss = ss + anystart(ss, (void *)redirectors);
if (ss == sss) {
// Nope: save/expand argument and loop
if (expand_arg(&pp->arg, s, 0, &pp->delete)) {
pp->exit = 1;
return pp;
}
continue;
} else if (j+1 >= arg->c) {
// redirect needs one argument
s = "\\n";
break;
}
sss = arg->v[++j];
// It's a redirect: for [to]<from s = start of [to], ss = <, sss = from
if (isdigit(*s) && ss-s>5) break;
// expand arguments for everything but << and <<-
if (strncmp(ss, "<<", 2) && ss[2] != '<' &&
!(sss = expand_one_arg(sss, NO_PATH, &pp->delete)))
{
s = 0;
break; // arg splitting here is an error
}
// Parse the [fd] part of [fd]<name
to = *ss != '<';
if (isdigit(*s)) to = atoi(s);
else if (*s == '{') {
if (*varend(s+1) != '}') break;
// when we close a filehandle, we _read_ from {var}, not write to it
if ((!strcmp(ss, "<&") || !strcmp(ss, ">&")) && !strcmp(sss, "-")) {
if (!(ss = getvar(s+1))) break;
to = atoi(ss); // TODO trailing garbage?
if (save_redirect(&pp->urd, -1, to)) break;
close(to);
continue;
// record high file descriptor in {to}<from environment variable
} else {
// we don't save this, it goes in the env var and user can close it.
if (-1 == (to = next_hfd())) break;
cv = xmprintf("%.*s=%d", (int)(ss-s-2), s+1, to);
}
}
// HERE documents?
if (!strcmp(ss, "<<<") || !strcmp(ss, "<<-") || !strcmp(ss, "<<")) {
char *tmp = getvar("TMPDIR");
int i, len, zap = (ss[2] == '-'), x = !ss[strcspn(ss, "\"'")];
// store contents in open-but-deleted /tmp file.
tmp = xmprintf("%s/sh-XXXXXX", tmp ? tmp : "/tmp");
if ((from = mkstemp(tmp))>=0) {
if (unlink(tmp)) bad++;
// write contents to file (if <<< else <<) then lseek back to start
else if (ss[2] == '<') {
if (x && !(sss = expand_one_arg(sss, NO_PATH|NO_SPLIT, 0))) {
s = 0;
break;
}
len = strlen(sss);
if (len != writeall(from, sss, len)) bad++;
if (x) free(sss);
} else {
struct sh_arg *hh = arg+here++;
for (i = 0; i<hh->c; i++) {
ss = hh->v[i];
sss = 0;
// TODO audit this ala man page
// expand_parameter, commands, and arithmetic
if (x && !(ss = sss = expand_one_arg(ss,
NO_PATH|NO_SPLIT|NO_BRACE|NO_TILDE|NO_QUOTE, 0)))
{
s = 0;
break;
}
while (zap && *ss == '\t') ss++;
x = writeall(from, ss, len = strlen(ss));
free(sss);
if (len != x) break;
}
if (i != hh->c) bad++;
}
if (!bad && lseek(from, 0, SEEK_SET)) bad++;
if (bad) close(from);
} else bad++;
free(tmp);
if (bad) break;
// from is fd<<2 (new fd to dup2() after vfork()) plus
// 2 if we should close(from>>2) after dup2(from>>2, to),
// 1 if we should close but dup for nofork recovery (ala <&2-)
// Handle file descriptor duplication/close (&> &>> <& >& with number or -)
// These redirect existing fd so nothing to open()
} else if (*ss == '&' || ss[1] == '&') {
// is there an explicit fd?
for (ss = sss; isdigit(*ss); ss++);
if (ss-sss>5 || (*ss && (*ss != '-' || ss[1]))) {
if (*ss=='&') ss++;
saveclose = 4;
goto notfd;
}
from = (ss==sss) ? to : atoi(sss);
saveclose = 2-(*ss == '-');
} else {
notfd:
// Permissions to open external file with: < > >> <& >& <> >| &>> &>
if (!strcmp(ss, "<>")) from = O_CREAT|O_RDWR;
else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND|O_WRONLY;
else {
from = (*ss == '<') ? O_RDONLY : O_CREAT|O_WRONLY|O_TRUNC;
if (!strcmp(ss, ">") && (TT.options&OPT_NOCLOBBER)) {
struct stat st;
// Not _just_ O_EXCL: > /dev/null allowed
if (stat(sss, &st) || !S_ISREG(st.st_mode)) from |= O_EXCL;
}
}
// we expect /dev/fd/# and /dev/{stdin,stdout,stderr} to be in /dev
// TODO: /dev/{tcp,udp}/host/port
// Open the file
if (-1 == (from = xcreate_stdio(sss, from|WARN_ONLY, 0666))) {
s = 0;
break;
}
}
// perform redirect, saving displaced "to".
if (save_redirect(&pp->urd, from, to)) bad++;
// Do we save displaced "to" in env variable instead of undo list?
if (cv) {
--*pp->urd;
setvar(cv);
cv = 0;
}
if ((saveclose&1) && save_redirect(&pp->urd, -1, from)) bad++;
if ((saveclose&4) && save_redirect(&pp->urd, from, 2)) bad++;
if (!(saveclose&2)) close(from);
if (bad) break;
}
// didn't parse everything?
if (j != arg->c) {
if (s) syntax_err(s);
if (!pp->exit) pp->exit = 1;
free(cv);
}
return pp;
}
static void shexec(char *cmd, char **argv)
{
xsetenv(xmprintf("_=%s", cmd), 0);
execve(cmd, argv, environ);
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;
struct string_list *sl;
if (getpid() != TT.pid) signal(SIGINT, SIG_DFL);
if (strchr(cc, '/')) shexec(cc, argv);
else for (sl = find_in_path(pp, cc); sl; free(llist_pop(&sl)))
shexec(sl->str, argv);
perror_msg("%s", *argv);
if (!TT.isexec) _exit(127);
}
// Execute a single command
static struct sh_process *run_command(struct sh_arg *arg)
{
char *s, *ss = 0, *sss, **env = 0, **old = environ;
int envlen, jj, kk, ll;
struct sh_process *pp;
struct toy_list *tl;
if (BUGBUG) dprintf(255, "run_command %s\n", arg->v[0]);
// Grab leading variable assignments
for (envlen = 0; envlen<arg->c; envlen++) {
s = varend(arg->v[envlen]);
if (s == arg->v[envlen] || *s != '=') break;
}
// expand arguments and perform redirects
pp = expand_redir(arg, envlen, 0);
if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); for (i=0; i<pp->arg.c; i++) dprintf(255, "'%s' ", pp->arg.v[i]); dprintf(255, "\n"); }
// perform assignments locally if there's no command
if (envlen == arg->c) {
for (jj = 0; jj<envlen; jj++) {
if (!(s = expand_one_arg(arg->v[jj], NO_PATH|NO_SPLIT, 0))) break;
if (s == arg->v[jj]) s = xstrdup(s);
setvar(s);
}
goto out;
}
if (envlen) {
for (kk = 0; environ[kk]; kk++);
env = xmalloc(sizeof(char *)*(kk+33));
memcpy(env, environ, sizeof(char *)*(kk+1));
environ = env;
// assign leading environment variables
for (jj = 0; jj<envlen; jj++) {
if (!(sss = expand_one_arg(arg->v[jj], NO_PATH|NO_SPLIT, &pp->delete)))
break;
for (ll = 0; ll<kk; ll++) {
for (s = sss, ss = environ[ll]; *s == *ss && *s != '='; s++, ss++);
if (*s != '=') continue;
environ[ll] = sss;
break;
}
if (ll == kk) array_add(&environ, kk++, sss);
}
} else jj = 0;
// Do nothing if nothing to do
if (jj != envlen || pp->exit || !pp->arg.v);
// else if (!strcmp(*pp->arg.v, "(("))
// TODO: handle ((math)) currently totally broken
// TODO: call functions()
// Is this command a builtin that should run in this process?
else if ((tl = toy_find(*pp->arg.v))
&& (tl->flags & (TOYFLAG_NOFORK|TOYFLAG_MAYFORK)))
{
sigjmp_buf rebound;
char temp[jj = offsetof(struct toy_context, rebound)];
// This fakes lots of what toybox_main() does.
memcpy(&temp, &toys, jj);
memset(&toys, 0, jj);
// If we give the union in TT a name, the compiler complains
// "declaration does not declare anything", but if we DON'T give it a name
// it accepts it. So we can't use the union's type name here, and have
// to offsetof() the first thing _after_ the union to get the size.
memset(&TT, 0, offsetof(struct sh_data, lineno));
TT.pp = pp;
if (!sigsetjmp(rebound, 1)) {
toys.rebound = &rebound;
toy_singleinit(tl, pp->arg.v); // arg.v must be null terminated
tl->toy_main();
xflush(0);
}
TT.pp = 0;
toys.rebound = 0;
pp->exit = toys.exitval;
if (toys.optargs != toys.argv+1) free(toys.optargs);
if (toys.old_umask) umask(toys.old_umask);
memcpy(&toys, &temp, jj);
} else if (-1==(pp->pid = xpopen_setup(pp->arg.v+envlen, 0, sh_exec)))
perror_msg("%s: vfork", *pp->arg.v);
// Restore environment variables
environ = old;
free(env);
out:
setvarval("_", (envlen == arg->c) ? "" : s);
// cleanup process
unredirect(pp->urd);
return pp;
}
static void free_process(void *ppp)
{
struct sh_process *pp = ppp;
llist_traverse(pp->delete, llist_free_arg);
free(pp);
}
// if then fi for while until select done done case esac break continue return
// Free one pipeline segment.
static void free_pipeline(void *pipeline)
{
struct sh_pipeline *pl = pipeline;
int i, j;
// free arguments and HERE doc contents
if (pl) for (j=0; j<=pl->count; j++) {
for (i = 0; i<=pl->arg[j].c; i++) free(pl->arg[j].v[i]);
free(pl->arg[j].v);
}
free(pl);
}
// Return end of current block, or NULL if we weren't in block and fell off end.
static struct sh_pipeline *block_end(struct sh_pipeline *pl)
{
int i = 0;
// TODO: should this be inlined into type 1 processing to set blk->end and
// then everything else use that?
while (pl) {
if (pl->type == 1 || pl->type == 'f') i++;
else if (pl->type == 3) if (--i<1) break;
pl = pl->next;
}
return 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)
{
dprintf(2, "stub add_function");
return block_end(pl->next);
}
// 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)
{
char *start = line, *delete = 0, *end, *last = 0, *s, *ex, done = 0,
*tails[] = {"fi", "done", "esac", "}", "]]", ")", 0};
struct sh_pipeline *pl = sp->pipeline ? sp->pipeline->prev : 0;
struct sh_arg *arg = 0;
long i;
// Resume appending to last statement?
if (pl) {
arg = pl->arg;
// Extend/resume quoted block
if (arg->c<0) {
delete = start = xmprintf("%s%s", arg->v[arg->c = (-arg->c)-1], start);
free(arg->v[arg->c]);
arg->v[arg->c] = 0;
// is a HERE document in progress?
} else if (pl->count != pl->here) {
arg += 1+pl->here;
// Match unquoted EOF.
for (s = line, end = arg->v[arg->c]; *s && *end; s++) {
s += strspn(s, "\\\"'");
if (*s != *end) break;
}
if (!*s && !*end) {
// Add this line
array_add(&arg->v, arg->c++, xstrdup(line));
array_add(&arg->v, arg->c, arg->v[arg->c]);
arg->c++;
// EOF hit, end HERE document
} else {
arg->v[arg->c] = 0;
pl->here++;
}
start = 0;
// Nope, new segment
} else pl = 0;
}
// Parse words, assemble argv[] pipelines, check flow control and HERE docs
if (start) for (;;) {
ex = sp->expect ? sp->expect->prev->data : 0;
// Look for << HERE redirections in completed pipeline segment
if (pl && pl->count == -1) {
pl->count = 0;
arg = pl->arg;
if (BUGBUG>1) dprintf(255, "{%d:%s}\n", pl->type, ex ? ex : (sp->expect ? "*" : ""));
// find arguments of the form [{n}]<<[-] with another one after it
for (i = 0; i<arg->c; i++) {
s = arg->v[i] + redir_prefix(arg->v[i]);
// TODO <<< is funky
// argc[] entries removed from main list? Can have more than one?
if (strcmp(s, "<<") && strcmp(s, "<<-") && strcmp(s, "<<<")) continue;
if (i+1 == arg->c) goto flush;
// Add another arg[] to the pipeline segment (removing/readding to list
// because realloc can move pointer)
dlist_lpop(&sp->pipeline);
pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg));
dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
// queue up HERE EOF so input loop asks for more lines.
arg[pl->count].v = xzalloc(2*sizeof(void *));
arg[pl->count].v[0] = arg->v[++i];
arg[pl->count].v[1] = 0;
arg[pl->count].c = 0;
if (s[2] == '<') pl->here++; // <<< doesn't load more data
}
pl = 0;
}
if (done) break;
s = 0;
// skip leading whitespace/comment here to know where next word starts
while (isspace(*start)) ++start;
if (*start=='#') while (*start && *start != '\n') ++start;
// Parse next word and detect overflow (too many nested quotes).
if ((end = parse_word(start, 0)) == (void *)1) goto flush;
if (BUGBUG>1) dprintf(255, "[%.*s] ", end ? (int)(end-start) : 0, start);
// Is this a new pipeline segment?
if (!pl) {
pl = xzalloc(sizeof(struct sh_pipeline));
arg = pl->arg;
dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
}
// Do we need to request another line to finish word (find ending quote)?
if (!end) {
// Save unparsed bit of this line, we'll need to re-parse it.
array_add(&arg->v, arg->c++, xstrndup(start, strlen(start)));
arg->c = -arg->c;
free(delete);
return 1;
}
// Ok, we have a word. What does it _mean_?
// Did we hit end of line or ) outside a function declaration?
// ) is only saved at start of a statement, ends current statement
if (end == start || (arg->c && *start == ')' && pl->type!='f')) {
if (!arg->v) array_add(&arg->v, arg->c, 0);
if (pl->type == 'f' && arg->c<3) {
s = "function()";
goto flush;
}
// "for" on its own line is an error.
if (arg->c == 1 && ex && !memcmp(ex, "do\0A", 4)) {
s = "newline";
goto flush;
}
// don't save blank pipeline segments
if (!arg->c) free_pipeline(dlist_lpop(&sp->pipeline));
// stop at EOL, else continue with new pipeline segment for )
if (end == start) done++;
pl->count = -1;
last = 0;
continue;
}
// Save argument (strdup) and check for flow control
array_add(&arg->v, arg->c, s = xstrndup(start, end-start));
start = end;
if (strchr(";|&", *s) && strncmp(s, "&>", 2)) {
// treat ; as newline so we don't have to check both elsewhere.
if (!strcmp(s, ";")) {
arg->v[arg->c] = 0;
free(s);
s = 0;
// TODO enforce only one ; allowed between "for i" and in or do.
if (!arg->c && ex && !memcmp(ex, "do\0C", 4)) continue;
// ;; and friends only allowed in case statements
} else if (*s == ';' && (!ex || strcmp(ex, "esac"))) goto flush;
last = s;
// flow control without a statement is an error
if (!arg->c) goto flush;
pl->count = -1;
continue;
} else arg->v[++arg->c] = 0;
// is a function() in progress?
if (arg->c>1 && !strcmp(s, "(")) pl->type = 'f';
if (pl->type=='f') {
if (arg->c == 2 && strcmp(s, "(")) goto flush;
if (arg->c == 3) {
if (strcmp(s, ")")) goto flush;
// end function segment, expect function body
pl->count = -1;
last = 0;
dlist_add(&sp->expect, "}");
dlist_add(&sp->expect, 0);
dlist_add(&sp->expect, "{");
continue;
}
// a for/select must have at least one additional argument on same line
} else if (ex && !memcmp(ex, "do\0A", 4)) {
// Sanity check and break the segment
if (strncmp(s, "((", 2) && strchr(s, '=')) goto flush;
pl->count = -1;
sp->expect->prev->data = "do\0C";
continue;
// flow control is the first word of a pipeline segment
} else if (arg->c>1) continue;
// Do we expect something that _must_ come next? (no multiple statements)
if (ex) {
// When waiting for { it must be next symbol, but can be on a new line.
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));
continue;
// The "test" part of for/select loops can have (at most) one "in" line,
// for {((;;))|name [in...]} do
} else if (!memcmp(ex, "do\0C", 4)) {
if (strcmp(s, "do")) {
// can only have one "in" line between for/do, but not with for(())
if (pl->prev->type == 's') goto flush;
if (!strncmp(pl->prev->arg->v[1], "((", 2)) goto flush;
else if (strcmp(s, "in")) goto flush;
pl->type = 's';
continue;
}
}
}
// start of a new block?
// for/select requires variable name on same line, can't break segment yet
if (!strcmp(s, "for") || !strcmp(s, "select")) {
if (!pl->type) pl->type = 1;
dlist_add(&sp->expect, "do\0A");
continue;
}
end = 0;
if (!strcmp(s, "if")) end = "then";
else if (!strcmp(s, "while") || !strcmp(s, "until")) end = "do\0B";
else if (!strcmp(s, "case")) end = "esac";
else if (!strcmp(s, "{")) end = "}";
else if (!strcmp(s, "[[")) end = "]]";
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));
// 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 can't end a statement here skip next few tests
} else if (!ex);
// If we got here we expect a specific word to end this block: is this it?
else if (!strcmp(s, ex)) {
// can't "if | then" or "while && do", only ; & or newline works
if (last && (strcmp(ex, "then") || strcmp(last, "&"))) {
s = end;
goto flush;
}
free(dlist_lpop(&sp->expect));
pl->type = anystr(s, tails) ? 3 : 2;
// if it's a multipart block, what comes next?
if (!strcmp(s, "do")) end = "done";
else if (!strcmp(s, "then")) end = "fi\0A";
// fi could have elif, which queues a then.
} else if (!strcmp(ex, "fi")) {
if (!strcmp(s, "elif")) {
free(dlist_lpop(&sp->expect));
end = "then";
// catch duplicate else while we're here
} else if (!strcmp(s, "else")) {
if (ex[3] != 'A') {
s = "2 else";
goto flush;
}
free(dlist_lpop(&sp->expect));
end = "fi\0B";
}
}
// Queue up the next thing to expect, all preceded by a statement
if (end) {
if (!pl->type) pl->type = 2;
dlist_add(&sp->expect, end);
if (!anystr(end, tails)) dlist_add(&sp->expect, 0);
pl->count = -1;
}
// syntax error check: these can't be the first word in an unexpected place
if (!pl->type && anystr(s, (char *[]){"then", "do", "esac", "}", "]]", ")",
"done", "fi", "elif", "else", 0})) goto flush;
}
free(delete);
// ignore blank and comment lines
if (!sp->pipeline) 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;
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 (pl->arg->v[pl->arg->c]) return 1;
// Don't need more input, can start executing.
dlist_terminate(sp->pipeline);
return 0;
flush:
if (s) syntax_err(s);
free_function(sp);
return 0-!!s;
}
/* Flow control statements:
if/then/elif/else/fi, for select while until/do/done, case/esac,
{/}, [[/]], (/), function assignment
*/
// wait for every process in a pipeline to end
static int wait_pipeline(struct sh_process *pp)
{
int rc = 0;
for (dlist_terminate(pp); pp; pp = pp->next) {
if (pp->pid) {
// TODO job control: not xwait, handle EINTR ourselves and check signals
pp->exit = xwaitpid(pp->pid);
pp->pid = 0;
}
// TODO handle set -o pipefail here
rc = pp->not ? !pp->exit : pp->exit;
}
return rc;
}
// pipe data into and out of this segment, I.E. handle leading and trailing |
static int pipe_segments(char *ctl, int *pipes, int **urd)
{
unredirect(*urd);
*urd = 0;
// Did the previous pipe segment pipe input into us?
if (*pipes != -1) {
if (save_redirect(urd, *pipes, 0)) return 1;
close(*pipes);
*pipes = -1;
}
// are we piping output to the next segment?
if (ctl && *ctl == '|' && ctl[1] != '|') {
if (pipe(pipes)) {
perror_msg("pipe");
// TODO record pipeline rc
// TODO check did not reach end of pipeline after loop
return 1;
}
if (save_redirect(urd, pipes[1], 1)) {
close(pipes[0]);
close(pipes[1]);
return 1;
}
if (pipes[1] != 1) close(pipes[1]);
fcntl(*pipes, F_SETFD, FD_CLOEXEC);
if (ctl[1] == '&') save_redirect(urd, 1, 2);
}
return 0;
}
// Handle && and || traversal in pipeline segments
static struct sh_pipeline *skip_andor(int rc, struct sh_pipeline *pl)
{
char *ctl = pl->arg->v[pl->arg->c];
// For && and || skip pipeline segment(s) based on return code
while (ctl && ((!strcmp(ctl, "&&") && rc) || (!strcmp(ctl, "||") && !rc))) {
if (!pl->next || pl->next->type == 2 || pl->next->type == 3) break;
pl = pl->type ? block_end(pl) : pl->next;
ctl = pl ? pl->arg->v[pl->arg->c] : 0;
}
return pl;
}
struct blockstack {
struct blockstack *next;
struct sh_pipeline *start, *end;
struct sh_process *pin; // processes piping into this block
int run, loop, *urd, pout;
struct sh_arg farg; // for/select arg stack
struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
};
// when ending a block, free, cleanup redirects and pop stack.
static struct sh_pipeline *pop_block(struct blockstack **blist, int *pout)
{
struct blockstack *blk = *blist;
struct sh_pipeline *pl = blk->end;
// when ending a block, free, cleanup redirects and pop stack.
if (*pout != -1) close(*pout);
*pout = blk->pout;
unredirect(blk->urd);
llist_traverse(blk->fdelete, free);
free(llist_pop(blist));
return pl;
}
// run a parsed shell function. Handle flow control blocks and characters,
// setup pipes and block redirection, break/continue, call builtins,
// vfork/exec external commands.
static void run_function(struct sh_pipeline *pl)
{
struct sh_pipeline *end;
struct blockstack *blk = 0, *new;
struct sh_process *pplist = 0; // processes piping into current level
int *urd = 0, pipes[2] = {-1, -1};
long i;
// TODO can't free sh_process delete until ready to dispose else no debug output
TT.hfd = 10;
// iterate through pipeline segments
while (pl) {
struct sh_arg *arg = pl->arg;
char *s = *arg->v, *ss = arg->v[1], *ctl = arg->v[arg->c];
if (BUGBUG) dprintf(255, "%d runtype=%d %s %s\n", getpid(), pl->type, s, ctl);
// Is this an executable segment?
if (!pl->type) {
// Skip disabled block
if (blk && !blk->run) {
pl = pl->next;
continue;
}
if (pipe_segments(ctl, pipes, &urd)) break;
// If we just started a new pipeline, implicit parentheses (subshell)
// TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
// probably have to inline run_command here to do that? Implicit ()
// also "X=42 | true; echo $X" doesn't get X.
// TODO: bash supports "break &" and "break > file". No idea why.
// 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")) {
// How many layers to peel off?
i = ss ? atol(ss) : 0;
if (i<1) i = 1;
if (!blk || arg->c>2 || ss[strspn(ss, "0123456789")]) {
syntax_err(s);
break;
}
while (i && blk)
if (!--i && *s == 'c') pl = blk->start;
else pl = pop_block(&blk, pipes);
if (i) {
syntax_err("break outside loop");
break;
}
pl = pl->next;
continue;
// Parse and run next command
} else {
// TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
// probably have to inline run_command here to do that? Implicit ()
// also "X=42 | true; echo $X" doesn't get X.
// I.E. run_subshell() here sometimes? (But when?)
dlist_add_nomalloc((void *)&pplist, (void *)run_command(arg));
}
if (*pipes == -1) {
toys.exitval = wait_pipeline(pplist);
llist_traverse(pplist, free_process);
pplist = 0;
pl = skip_andor(toys.exitval, pl);
}
// Start of flow control block?
} else if (pl->type == 1) {
struct sh_process *pp = 0;
int rc;
// are we entering this block (rather than looping back to it)?
if (!blk || blk->start != pl) {
// If it's a nested block we're not running, skip ahead.
end = block_end(pl->next);
if (blk && !blk->run) {
pl = end;
if (pl) pl = pl->next;
continue;
}
// If previous piped into this block, save context until block end
if (pipe_segments(end->arg->v[end->arg->c], pipes, &urd)) break;
// It's a new block we're running, save context and add it to the stack.
new = xzalloc(sizeof(*blk));
new->next = blk;
blk = new;
blk->start = pl;
blk->end = end;
blk->run = 1;
// save context until block end
blk->pout = *pipes;
blk->urd = urd;
urd = 0;
*pipes = -1;
// Perform redirects listed at end of block
pp = expand_redir(end->arg, 1, blk->urd);
blk->urd = pp->urd;
rc = pp->exit;
if (pp->arg.c) {
syntax_err(*pp->arg.v);
rc = 1;
}
llist_traverse(pp->delete, free);
free(pp);
if (rc) {
toys.exitval = rc;
break;
}
}
// What flow control statement is this?
// {/} if/then/elif/else/fi, while until/do/done - no special handling
// for select/do/done
if (!strcmp(s, "for") || !strcmp(s, "select")) {
if (blk->loop);
else if (!strncmp(blk->fvar = ss, "((", 2)) {
blk->loop = 1;
dprintf(2, "TODO skipped init for((;;)), need math parser\n");
} else {
// populate blk->farg with expanded arguments
if (pl->next->type == 's') {
for (i = 1; i<pl->next->arg->c; i++)
if (expand_arg(&blk->farg, pl->next->arg->v[i], 0, &blk->fdelete))
break;
if (i != pl->next->arg->c) {
pl = pop_block(&blk, pipes)->next;
continue;
}
// this can't fail
} else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete);
}
// TODO case/esac [[/]] ((/)) function/}
/*
TODO: a | b | c needs subshell for builtins?
- anything that can produce output
- echo declare dirs
(a; b; c) like { } but subshell
when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)'
*/
// subshell
} else if (!strcmp(s, "(")) {
if (!CFG_TOYBOX_FORK) {
ss = pl2str(pl->next);
pp->pid = run_subshell(ss, strlen(ss));
free(ss);
} else {
if (!(pp->pid = fork())) {
run_function(pl->next);
_exit(toys.exitval);
}
}
dlist_add_nomalloc((void *)&pplist, (void *)pp);
pl = blk->end->prev;
}
// gearshift from block start to block body (end of flow control test)
} else if (pl->type == 2) {
// Handle if statement
if (!strcmp(s, "then")) blk->run = blk->run && !toys.exitval;
else if (!strcmp(s, "else") || !strcmp(s, "elif")) blk->run = !blk->run;
else if (!strcmp(s, "do")) {
ss = *blk->start->arg->v;
if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
else if (blk->loop >= blk->farg.c) {
blk->run = 0;
pl = block_end(pl);
continue;
} else if (!strncmp(blk->fvar, "((", 2)) {
dprintf(2, "TODO skipped running for((;;)), need math parser\n");
} else setvarval(blk->fvar, blk->farg.v[blk->loop++]);
}
// end of block, may have trailing redirections and/or pipe
} else if (pl->type == 3) {
// if we end a block we're not in, we started in a block.
if (!blk) break;
// repeating block?
if (blk->run && !strcmp(s, "done")) {
pl = blk->start;
continue;
}
pop_block(&blk, pipes);
} else if (pl->type == 'f') pl = add_function(s, pl);
pl = pl->next;
}
// did we exit with unfinished stuff?
// TODO: current context isn't packaged into a block, so can't just pop it
if (*pipes != -1) close(*pipes);
if (pplist) {
toys.exitval = wait_pipeline(pplist);
llist_traverse(pplist, free_process);
}
unredirect(urd);
// Cleanup from syntax_err();
while (blk) pop_block(&blk, pipes);
}
// Parse and run a self-contained command line with no prompt/continuation
static int sh_run(char *new)
{
struct sh_function scratch;
// TODO switch the fmemopen for -c to use this? Error checking? $(blah)
memset(&scratch, 0, sizeof(struct sh_function));
if (!parse_line(new, &scratch)) run_function(scratch.pipeline);
free_function(&scratch);
return toys.exitval;
}
// Print prompt to stderr, parsing escapes
// Truncated to 4k at the moment, waiting for somebody to complain.
static void do_prompt(char *prompt)
{
char *s, *ss, c, cc, *pp = toybuf;
int len, ll;
if (!prompt) return;
while ((len = sizeof(toybuf)-(pp-toybuf))>0 && *prompt) {
c = *(prompt++);
if (c=='!') {
if (*prompt=='!') prompt++;
else {
pp += snprintf(pp, len, "%ld", TT.lineno);
continue;
}
} else if (c=='\\') {
cc = *(prompt++);
if (!cc) {
*pp++ = c;
break;
}
// \nnn \dD{}hHjlstT@AuvVwW!#$
// Ignore bash's "nonprintable" hack; query our cursor position instead.
if (cc=='[' || cc==']') continue;
else if (cc=='$') *pp++ = getuid() ? '$' : '#';
else if (cc=='h' || cc=='H') {
*pp = 0;
gethostname(pp, len);
pp[len-1] = 0;
if (cc=='h' && (s = strchr(pp, '.'))) *s = 0;
pp += strlen(pp);
} else if (cc=='s') {
s = getbasename(*toys.argv);
while (*s && len--) *pp++ = *s++;
} else if (cc=='w') {
if ((s = getvar("PWD"))) {
if ((ss = getvar("HOME")) && strstart(&s, ss)) {
*pp++ = '~';
if (--len && *s!='/') *pp++ = '/';
len--;
}
if (len>0) {
ll = strlen(s);
pp = stpncpy(pp, s, ll>len ? len : ll);
}
}
} else if (!(c = unescape(cc))) {
*pp++ = '\\';
if (--len) *pp++ = c;
} else *pp++ = c;
} else *pp++ = c;
}
len = pp-toybuf;
if (len>=sizeof(toybuf)) len = sizeof(toybuf);
writeall(2, toybuf, len);
}
// only set local variable when global not present, does not extend array
static struct sh_vars *initlocal(char *name, char *val)
{
return addvar(xmprintf("%s=%s", name, val ? val : ""));
}
static struct sh_vars *initlocaldef(char *name, char *val, char *def)
{
return initlocal(name, (!val || !*val) ? def : val);
}
// export existing "name" or assign/export name=value string (making new copy)
static void export(char *str)
{
struct sh_vars *shv = 0;
char *s;
// 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));
if (!shv || (shv->flags&VAR_GLOBAL)) return;
// Resolve local magic for export
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);
if (shv) {
if (shv->flags&VAR_GLOBAL) shv->str = xpop_env(str);
shv->flags &=~VAR_GLOBAL;
}
if (strchr(str, '=')) setvar(str);
}
// 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();
struct passwd *pw = getpwuid(uid);
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 utsname uu;
FILE *fp;
// 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]));
for (ii = 0; ii<ARRAY_LEN(readonly); ii++)
addvar(readonly[ii])->flags = VAR_READONLY|VAR_INT;
// Add local variables that can be overwritten
initlocal("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);
gethostname(toybuf, sizeof(toybuf)-1);
initlocal("HOSTNAME", toybuf);
uname(&uu);
initlocal("HOSTTYPE", uu.machine);
sprintf(toybuf, "%s-unknown-linux", uu.machine);
initlocal("MACHTYPE", toybuf);
initlocal("OSTYPE", uu.sysname);
// sprintf(toybuf, "%s-toybox", TOYBOX_VERSION);
// initlocal("BASH_VERSION", toybuf);
initlocal("OPTERR", "1"); // TODO: test if already exported?
if (readlink0("/proc/self/exe", s = toybuf, sizeof(toybuf))||(s=getenv("_")))
initlocal("BASH", s);
initlocal("PS2", "> ");
// 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++) {
// If nommu subshell gets handoff
if (!CFG_TOYBOX_FORK && !toys.stacktop) {
len = 0;
sscanf(s, "@%d,%d%n", &pid, &ppid, &len);
if (s[len]) pid = ppid = 0;
if (*s == '$' && s[1] == '=') zpid = atoi(s+2);
// TODO marshall $- to subshell like $$
}
// 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 (!shv) addvar(environ[from])->flags = VAR_GLOBAL;
else if (shv->flags&VAR_READONLY) continue;
else {
shv->flags |= VAR_GLOBAL;
free(shv->str);
shv->str = s;
}
environ[to++] = s;
}
if (!memcmp(s, "IFS=", 4)) TT.ifs = s+4;
}
environ[toys.optc = to] = 0;
// set/update PWD
sh_run("cd .");
// set _ to path to this shell
s = toys.argv[0];
ss = 0;
if (!strchr(s, '/')) {
if ((ss = getcwd(0, 0))) {
s = xmprintf("%s/%s", ss, s);
free(ss);
ss = s;
} else if (*toybuf) s = toybuf;
}
s = xsetenv("_", s);
if (!findvar(s)) addvar(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");
}
//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);
fp = fdopen(254, "r");
// This is not efficient, could array_add the local vars.
// TODO implicit exec when possible
while ((s = xgetline(fp, 0))) toys.exitval = sh_run(s);
fclose(fp);
xexit();
}
void sh_main(void)
{
char *new, *cc = TT.sh.c;
struct sh_function scratch;
int prompt = 0;
struct string_list *sl = 0;
struct sh_arg arg;
FILE *f;
signal(SIGPIPE, SIG_IGN);
TT.options = OPT_BRACE;
TT.pid = getpid();
TT.SECONDS = time(0);
TT.arg = &arg;
if (!(arg.c = toys.optc)) {
arg.v = xmalloc(2*sizeof(char *));
arg.v[arg.c++] = *toys.argv;
arg.v[arg.c] = 0;
} else memcpy(arg.v = xmalloc((arg.c+1)*sizeof(char *)), toys.optargs,
(arg.c+1)*sizeof(char *));
// TODO euid stuff?
// TODO login shell?
// TODO read profile, read rc
// if (!FLAG(noprofile)) { }
if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); if (fd == -1) fd = open("/dev/console", O_RDWR); if (fd == -1) dup2(2, 255); else dup2(fd, 255); close(fd); }
// Is this an interactive shell?
if (FLAG(s) || (!FLAG(c) && !toys.optc)) TT.options |= OPT_S;
if (FLAG(i) || (!FLAG(c) && (TT.options&OPT_S) && isatty(0)))
TT.options |= OPT_I;
if (FLAG(c)) TT.options |= OPT_C;
// Read environment for exports from parent shell. Note, calls run_sh()
// which blanks argument sections of TT and this, so parse everything
// we need from shell command line before that.
subshell_setup();
if (TT.options&OPT_I) {
if (!getvar("PS1")) setvarval("PS1", getpid() ? "\\$ " : "# ");
// TODO Set up signal handlers and grab control of this tty.
// ^C SIGINT ^\ SIGQUIT ^Z SIGTSTP SIGTTIN SIGTTOU SIGCHLD
// setsid(), setpgid(), tcsetpgrp()...
xsignal(SIGINT, SIG_IGN);
}
memset(&scratch, 0, sizeof(scratch));
// TODO unify fmemopen() here with sh_run
if (cc) f = fmemopen(cc, strlen(cc), "r");
else if (TT.options&OPT_S) f = stdin;
// TODO: syntax_err should exit from shell scripts
else if (!(f = fopen(*toys.optargs, "r"))) {
char *pp = getvar("PATH") ? : _PATH_DEFPATH;
for (sl = find_in_path(pp, *toys.optargs); sl; free(llist_pop(&sl)))
if ((f = fopen(sl->str, "r"))) break;
if (sl) llist_traverse(sl->next, free);
else perror_exit_raw(*toys.optargs);
}
// Loop prompting and reading lines
for (;;) {
TT.lineno++;
if ((TT.options&(OPT_I|OPT_S|OPT_C)) == (OPT_I|OPT_S))
do_prompt(getvar(prompt ? "PS2" : "PS1"));
// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
if (!(new = xgetline(f, 0))) {
// TODO: after first EINTR getline returns always closed?
if (errno != EINTR) break;
free_function(&scratch);
prompt = 0;
if (f != stdin) break;
continue;
// TODO: ctrl-z during script read having already read partial line,
// SIGSTOP and SIGTSTP need need SA_RESTART, but child proc should stop
}
if (BUGBUG) dprintf(255, "line=%s\n", new);
if (sl) {
if (*new == 0x7f) error_exit("'%s' is ELF", sl->str);
free(sl);
sl = 0;
}
// TODO if (!isspace(*new)) add_to_history(line);
// returns 0 if line consumed, command if it needs more data
prompt = parse_line(new, &scratch);
if (BUGBUG) dump_state(&scratch);
if (prompt != 1) {
// TODO: ./blah.sh one two three: put one two three in scratch.arg
if (!prompt) run_function(scratch.pipeline);
free_function(&scratch);
prompt = 0;
}
free(new);
}
if (prompt) error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
}
/********************* shell builtin functions *************************/
#define CLEANUP_sh
#define FOR_cd
#include "generated/flags.h"
void cd_main(void)
{
char *home = getvar("HOME") ? : "/", *pwd = getvar("PWD"), *from, *to = 0,
*dd = xstrdup(*toys.optargs ? *toys.optargs : home);
int bad = 0;
// TODO: CDPATH? Really?
// prepend cwd or $PWD to relative path
if (*dd != '/') {
from = pwd ? : (to = getcwd(0, 0));
if (!from) setvarval("PWD", "(nowhere)");
else {
from = xmprintf("%s/%s", from, dd);
free(dd);
free(to);
dd = from;
}
}
if (FLAG(P)) {
struct stat st;
char *pp;
// Does this directory exist?
if ((pp = xabspath(dd, 1)) && stat(pp, &st) && !S_ISDIR(st.st_mode))
bad++, errno = ENOTDIR;
else {
free(dd);
dd = pp;
}
} else {
// cancel out . and .. in the string
for (from = to = dd; *from;) {
if (*from=='/' && from[1]=='/') from++;
else if (*from!='/' || from[1]!='.') *to++ = *from++;
else if (!from[2] || from[2]=='/') from += 2;
else if (from[2]=='.' && (!from[3] || from[3]=='/')) {
from += 3;
while (to>dd && *--to != '/');
} else *to++ = *from++;
}
if (to == dd) to++;
if (to-dd>1 && to[-1]=='/') to--;
*to = 0;
}
if (bad || chdir(dd)) perror_msg("chdir '%s'", dd);
else {
if (pwd) {
setvarval("OLDPWD", pwd);
if (TT.cdcount == 1) {
export("OLDPWD");
TT.cdcount++;
}
}
setvarval("PWD", dd);
if (!TT.cdcount) {
export("PWD");
TT.cdcount++;
}
}
free(dd);
}
void exit_main(void)
{
exit(*toys.optargs ? atoi(*toys.optargs) : 0);
}
void unset_main(void)
{
char **arg, *s;
for (arg = toys.optargs; *arg; arg++) {
s = varend(*arg);
if (s == *arg || *s) {
error_msg("bad '%s'", *arg);
continue;
}
// unset magic variable?
if (!strcmp(*arg, "IFS")) TT.ifs = " \t\n";
unsetvar(*arg);
}
}
#define CLEANUP_cd
#define FOR_export
#include "generated/flags.h"
void export_main(void)
{
char **arg, *eq;
// list existing variables?
if (!toys.optc) {
for (arg = environ; *arg; arg++) xprintf("declare -x %s\n", *arg);
return;
}
// set/move variables
for (arg = toys.optargs; *arg; arg++) {
eq = varend(*arg);
if (eq == *arg || (*eq && *eq != '=')) {
error_msg("bad %s", *arg);
continue;
}
if (FLAG(n)) unexport(*arg);
else export(*arg);
}
}
void eval_main(void)
{
struct sh_arg *old = TT.arg, new = {toys.argv, toys.optc+1};
char *s;
TT.arg = &new;
s = expand_one_arg("\"$*\"", SEMI_IFS, 0);
TT.arg = old;
sh_run(s);
free(s);
}
#define CLEANUP_export
#define FOR_exec
#include "generated/flags.h"
void exec_main(void)
{
char *ee[1] = {0}, **old = environ;
// discard redirects and return if nothing to exec
free(TT.pp->urd);
TT.pp->urd = 0;
if (!toys.optc) return;
// exec, handling -acl
TT.isexec = *toys.optargs;
if (FLAG(c)) environ = ee;
if (TT.exec.a || FLAG(l))
*toys.optargs = xmprintf("%s%s", FLAG(l) ? "-" : "", TT.exec.a?:TT.isexec);
sh_exec(toys.optargs);
// report error (usually ENOENT) and return
perror_msg("%s", TT.isexec);
TT.isexec = 0;
toys.exitval = 127;
environ = old;
}
void shift_main(void)
{
long long by = 1;
if (toys.optc) by = atolx(*toys.optargs);
by += TT.shift;
if (by<0 || by>= TT.arg->c) toys.exitval++;
else TT.shift = by;
}