aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2020-05-03 23:15:35 -0500
committerRob Landley <rob@landley.net>2020-05-03 23:15:35 -0500
commite9396994d328606ff436b87bea3a0321bba98809 (patch)
tree497cc2fbda98a124d9dc9dd836d80d5b92028702
parentb0ed57b1aad9d2a0cfb64999cc1e0bc15471a48c (diff)
downloadtoybox-e9396994d328606ff436b87bea3a0321bba98809.tar.gz
Next round of shell work.
-rw-r--r--lib/env.c33
-rw-r--r--lib/lib.h2
-rw-r--r--lib/xwrap.c5
-rw-r--r--tests/sh.test7
-rw-r--r--toys/pending/sh.c177
5 files changed, 120 insertions, 104 deletions
diff --git a/lib/env.c b/lib/env.c
index 3017c40a..70fb0def 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -37,49 +37,46 @@ void xclearenv(void)
// returns pointer to new name=value environment string, NULL if none
char *xsetenv(char *name, char *val)
{
- unsigned i, len, ec;
+ unsigned i, j = 0, len;
char *new;
// If we haven't snapshot initial environment state yet, do so now.
if (!toys.envc) {
+
// envc is size +1 so even if env empty it's nonzero after initialization
while (environ[toys.envc++]);
- memcpy(new = xmalloc(((toys.envc|0xff)+1)*sizeof(char *)), environ,
+ memcpy(new = xmalloc(((toys.envc|31)+1)*sizeof(char *)), environ,
toys.envc*sizeof(char *));
environ = (void *)new;
}
- new = strchr(name, '=');
- if (new) {
+ if (!(new = strchr(name, '='))) {
+ len = strlen(name);
+ if (val) new = xmprintf("%s=%s", name, val);
+ } else {
len = new-name;
if (val) error_exit("xsetenv %s to %s", name, val);
new = name;
- } else {
- len = strlen(name);
- if (val) new = xmprintf("%s=%s", name, val);
}
- ec = toys.envc-1; // compensate for size +1 above
for (i = 0; environ[i]; i++) {
// Drop old entry, freeing as appropriate. Assumes no duplicates.
if (!memcmp(name, environ[i], len) && environ[i][len]=='=') {
- if (i>=ec) free(environ[i]);
- else {
- // move old entries down, add at end of old data
- toys.envc = ec--;
- for (; new ? i<ec : !!environ[i]; i++) environ[i] = environ[i+1];
- i = ec;
- }
- break;
+ if (i<toys.envc-1) toys.envc--;
+ else free(environ[i]);
+ j++;
}
+
+ // move data down to fill hole, including null terminator
+ if (j && !(environ[i] = environ[i+1])) break;
}
if (!new) return 0;
// resize and null terminate if expanding
- if (!environ[i]) {
+ if (!j && !environ[i]) {
len = i+1;
- if (!(len&255)) environ = xrealloc(environ, (len+256)*sizeof(char *));
+ if (!(len&31)) environ = xrealloc(environ, (len+32)*sizeof(char *));
environ[len] = 0;
}
diff --git a/lib/lib.h b/lib/lib.h
index 60c800ca..72b4fd65 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -134,7 +134,7 @@ void xputs(char *s);
void xputc(char c);
void xflush(int flush);
void xexec(char **argv);
-pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void));
+pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(char **argv));
pid_t xpopen_both(char **argv, int *pipes);
int xwaitpid(pid_t pid);
int xpclose_both(pid_t pid, int *pipes);
diff --git a/lib/xwrap.c b/lib/xwrap.c
index 5f18f295..b4a52c9e 100644
--- a/lib/xwrap.c
+++ b/lib/xwrap.c
@@ -224,7 +224,7 @@ void xexec(char **argv)
// If -1, replace with pipe handle connected to stdin/stdout.
// NULL treated as {0, 1}, I.E. leave stdin/stdout as is
// return: pid of child process
-pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void))
+pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(char **argv))
{
int cestnepasun[4], pid;
@@ -269,7 +269,7 @@ pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void))
close(pipes[1]);
}
}
- if (callback) callback();
+ if (callback) callback(argv);
if (argv) xexec(argv);
// In fork() case, force recursion because we know it's us.
@@ -287,6 +287,7 @@ pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void))
// setting high bit of argv[0][0] to let new process know
**toys.argv |= 0x80;
execv(s, toys.argv);
+ if ((s = getenv("_"))) execv(s, toys.argv);
perror_msg_raw(s);
_exit(127);
diff --git a/tests/sh.test b/tests/sh.test
index 28b453f0..0759398d 100644
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -42,7 +42,10 @@ testing "exec3" '$C -c "{ exec readlink /proc/self/fd/0;} < /proc/self/exe"' \
testing 'arg shift' "$SH -c '"'for i in "" 2 1 1 1; do echo $? $1; shift $i; done'"' one two three four five" \
"0 two\n0 three\n0 five\n0\n1\n" "" ""
-
+# The bash man page is lying when it says $_ starts with an absolute path.
+ln -s $(which $SH) bash
+testing 'non-absolute $_' "./bash -c 'echo \$_'" './bash\n' '' ''
+rm bash
testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
@@ -61,7 +64,7 @@ testing 'IFS $*' "$SH -c 'IFS=xy; echo \"\$*\"' one two tyree" "twoxtyree\n" \
"" ""
testing 'default exports' \
- "env -i \"$(which $SH)\" --noprofile -norc -c env | sort" \
+ "env -i \"$(which $SH)\" --noprofile --norc -c env | sort" \
"PWD=$(pwd)\nSHLVL=1\n_=$(which env)\n" "" ""
# Change EVAL to call sh -c for us, using "bash" explicitly for the host.
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index e5d7227f..632f75b6 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -168,7 +168,7 @@ GLOBALS(
// keep lineno here, we use it to work around a compiler bug
long lineno;
- char *ifs;
+ char *ifs, *isexec;
struct double_list functions;
unsigned options, jobcnt;
int hfd, pid, varslen, shift, cdcount;
@@ -401,6 +401,19 @@ static struct sh_vars *setvar(char *s)
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));
@@ -427,21 +440,6 @@ static char *getvar(char *s)
return varend(var->str)+1;
}
-static void unsetvar(char *name)
-{
- struct sh_vars *var = findvar(name);
- int ii;
-
- if (!var) return;
- if (var->flags&VAR_GLOBAL) {
- *varend(var->str) = 0;
- xunsetenv(var->str);
- } else free(var->str);
-
- ii = var-TT.vars;
- memmove(TT.vars+ii, TT.vars+ii+1, TT.varslen-ii);
-}
-
// malloc declare -x "escaped string"
static char *declarep(struct sh_vars *var)
{
@@ -641,7 +639,7 @@ if (BUGBUG) dprintf(255, "%d schedule close %d\n", getpid(), to);
}
// TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending
-static void subshell_callback(void)
+static void subshell_callback(char **argv)
{
char *s;
@@ -674,7 +672,7 @@ if (BUGBUG) dprintf(255, "run_subshell %.*s\n", len, str);
// On nommu vfork, exec /proc/self/exe, and pipe state data to ourselves.
} else {
- int pipes[2], i;
+ int pipes[2], i, c;
// open pipe to child
if (pipe(pipes) || 254 != dup2(pipes[0], 254)) return 1;
@@ -686,7 +684,8 @@ if (BUGBUG) dprintf(255, "run_subshell %.*s\n", len, str);
// free entries added to end of environment by callback (shared heap)
for (i = 0; environ[i]; i++) {
- if (!ispunct(environ[i][0])) continue;
+ c = environ[i][0];
+ if (c == '_' || !ispunct(c)) continue;
free(environ[i]);
environ[i] = 0;
}
@@ -791,8 +790,6 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags,
if (BUGBUG) dprintf(255, "expand %s\n", str);
-// TODO ls -l /proc/$$/fd
-
// Tilde expansion
if (!(flags&NO_TILDE) && *str == '~') {
struct passwd *pw = 0;
@@ -1467,13 +1464,33 @@ notfd:
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 (strchr(cc, '/')) shexec(cc, argv);
+ else for (sl = find_in_path(pp, cc); sl; free(llist_pop(&sl)))
+ shexec(sl->str, 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;
- int envlen, j;
- char *s;
if (BUGBUG) dprintf(255, "run_command %s\n", arg->v[0]);
@@ -1489,14 +1506,36 @@ if (BUGBUG) dprintf(255, "run_command %s\n", arg->v[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 (j = 0; j<envlen; j++) {
- if (!(s = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, 0))) break;
- if (s == arg->v[j]) s = xstrdup(s);
+ 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
- } else if (pp->exit || !pp->arg.v);
+ if (jj != envlen || pp->exit || !pp->arg.v);
// else if (!strcmp(*pp->arg.v, "(("))
// TODO: handle ((math)) currently totally broken
// TODO: call functions()
@@ -1505,11 +1544,11 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f
&& (tl->flags & (TOYFLAG_NOFORK|TOYFLAG_MAYFORK)))
{
sigjmp_buf rebound;
- char temp[j = offsetof(struct toy_context, rebound)];
+ char temp[jj = offsetof(struct toy_context, rebound)];
// This fakes lots of what toybox_main() does.
- memcpy(&temp, &toys, j);
- memset(&toys, 0, j);
+ 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
@@ -1529,45 +1568,16 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f
pp->exit = toys.exitval;
if (toys.optargs != toys.argv+1) free(toys.optargs);
if (toys.old_umask) umask(toys.old_umask);
- memcpy(&toys, &temp, j);
- } else {
- char **env = 0, **old = environ, *ss = 0, *sss;
- int kk = 0, ll, mm = -1;
+ 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);
- // We don't allocate/free any array members, just the array
- if (environ) while (environ[kk]) {
- if (strncmp(environ[kk], "SHLVL=", 6)) mm = kk;
- kk++;
- }
- if (kk) {
- env = xmalloc(sizeof(char *)*(kk+33));
- memcpy(env, environ, sizeof(char *)*(kk+1));
- if (mm != -1) env[mm] = xmprintf("SHLVL=%d", atoi(env[mm]+6)+1);
- environ = env;
- }
-
- // assign leading environment variables
- for (j = 0; j<envlen; j++) {
- if (!(sss = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, &pp->delete)))
- break;
- for (ll = 0; ll<kk; ll++) {
- for (s = sss, ss = env[ll]; *s == *ss && *s != '='; s++, ss++);
- if (*s != '=') continue;
- env[ll] = sss;
- break;
- }
- if (ll == kk) array_add(&environ, kk++, sss);
- }
-
- if (j == envlen && -1 == (pp->pid = xpopen_both(pp->arg.v, 0)))
- perror_msg("%s: vfork", *pp->arg.v);
-
- // Restore environment variables
- environ = old;
- if (mm != -1) free(env[mm]);
- free(env);
- }
+ // Restore environment variables
+ environ = old;
+ free(env);
+out:
+ setvarval("_", (envlen == arg->c) ? "" : s);
// cleanup process
unredirect(pp->urd);
@@ -2201,7 +2211,7 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
} else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete);
}
-// TODO case/esac [[/]] (/) ((/)) function/}
+// TODO case/esac [[/]] ((/)) function/}
/*
TODO: a | b | c needs subshell for builtins?
@@ -2358,7 +2368,7 @@ static struct sh_vars *initlocal(char *name, char *val)
return addvar(xmprintf("%s=%s", name, val ? val : ""));
}
-// export malloced name=value string
+// export existing "name" or assign/export name=value string (making new copy)
static void export(char *str)
{
struct sh_vars *shv = 0;
@@ -2366,7 +2376,7 @@ static void export(char *str)
// Make sure variable exists and is updated
if (strchr(str, '=')) shv = setvar(xstrdup(str));
- else if (!findvar(str)) shv = addvar(str = xmprintf("%s=", str));
+ else if (!(shv = findvar(str))) shv = addvar(str = xmprintf("%s=", str));
if (!shv || (shv->flags&VAR_GLOBAL)) return;
// Resolve local magic for export
@@ -2433,6 +2443,7 @@ static void subshell_setup(void)
// 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
@@ -2479,9 +2490,13 @@ static void subshell_setup(void)
s = xsetenv("_", s);
if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL;
free(ss);
- if (!getvar("SHLVL")) {
- s = xsetenv("SHLVL", "1");
- if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL;
+ 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
@@ -2732,8 +2747,7 @@ void eval_main(void)
void exec_main(void)
{
- char *ee[1] = {0}, **env = FLAG(c) ? ee : environ, *cc, *pp = getvar("PATH");
- struct string_list *sl;
+ char *ee[1] = {0}, **old = environ;
// discard redirects and return if nothing to exec
free(TT.pp->urd);
@@ -2741,16 +2755,17 @@ void exec_main(void)
if (!toys.optc) return;
// exec, handling -acl
- cc = *toys.optargs;
+ 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 ? : cc);
- if (strchr(cc, '/')) execve(cc, toys.optargs, env);
- else for (sl = find_in_path(pp?:_PATH_DEFPATH, cc); sl; free(llist_pop(&sl)))
- execve(sl->str, toys.optargs, env);
+ *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", cc);
+ perror_msg("%s", TT.isexec);
toys.exitval = 127;
+ environ = old;
+ TT.isexec = 0;
}
void shift_main(void)