aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2019-12-23 23:21:36 -0600
committerRob Landley <rob@landley.net>2019-12-23 23:21:36 -0600
commit908584d5e7f59f273dbfb46d96abddc0b02432b7 (patch)
treea94dd4f75b7bb1c752abf773692f6decc9003ab9
parentf2e9d72c709374ab31ff58fb0b1f8f9e7f0e37d0 (diff)
downloadtoybox-908584d5e7f59f273dbfb46d96abddc0b02432b7.tar.gz
Toysh passes two tests now! Woo! (Otherwise, does not remotely work right now.)
-rw-r--r--scripts/runtest.sh65
-rwxr-xr-xtests/sh.test16
-rw-r--r--toys/pending/sh.c483
3 files changed, 283 insertions, 281 deletions
diff --git a/scripts/runtest.sh b/scripts/runtest.sh
index 9c19811c..2cc8ff7a 100644
--- a/scripts/runtest.sh
+++ b/scripts/runtest.sh
@@ -95,20 +95,6 @@ wrong_args()
fi
}
-# Announce failure and handle fallout
-do_fail()
-{
- FAILCOUNT=$(($FAILCOUNT+1))
- printf "%s\n" "$SHOWFAIL: $NAME"
- if [ -n "$VERBOSE" ]
- then
- [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
- printf "%s\n" "echo -ne '$5' |$EVAL $2"
- printf "%s\n" "$DIFF"
- [ "$VERBOSE" == fail ] && exit 1
- fi
-}
-
# Announce success
do_pass()
{
@@ -144,7 +130,15 @@ testing()
DIFF="$(diff -au${NOSPACE:+w} expected actual)"
if [ ! -z "$DIFF" ]
then
- do_fail
+ FAILCOUNT=$(($FAILCOUNT+1))
+ printf "%s\n" "$SHOWFAIL: $NAME"
+ if [ -n "$VERBOSE" ]
+ then
+ [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
+ printf "%s\n" "echo -ne '$5' |$EVAL $2"
+ printf "%s\n" "$DIFF"
+ [ "$VERBOSE" == fail ] && exit 1
+ fi
else
[ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME"
fi
@@ -164,7 +158,20 @@ testcmd()
testing "$X" "\"$C\" $2" "$3" "$4" "$5"
}
-# txpect NAME COMMAND I/O/E/Xstring
+# Announce failure and handle fallout for txpect
+do_fail()
+{
+ FAILCOUNT=$(($FAILCOUNT+1))
+ printf "%s\n" "$SHOWFAIL: $NAME"
+ if [ ! -z "$CASE" ]
+ then
+ echo "Expected '$CASE'"
+ echo "Got '$A'"
+ fi
+ [ "$VERBOSE" == fail ] && exit 1
+}
+
+# txpect NAME COMMAND [I/O/E/Xstring]...
# Run COMMAND and interact with it: send I strings to input, read O or E
# strings from stdout or stderr (empty string is "any nonzero string here"),
# X means close stdin/stdout/stderr and match return code (blank means nonzero)
@@ -172,13 +179,14 @@ txpect()
{
# Run command with redirection through fifos
NAME="$1"
+ CASE=
if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
then
do_fail
return
fi
- $2 <in-$$ >out-$$ 2>err-$$ &
+ eval "$2" <in-$$ >out-$$ 2>err-$$ &
shift 2
: {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$
@@ -188,10 +196,12 @@ txpect()
while [ $# -gt 0 ]
do
LEN=$((${#1}-1))
+ CASE="$1"
+ A=
case ${1::1} in
# send input to child
- I) echo -en "${1:1}" >&$IN || { do_fail;return;} ;;
+ I) echo -en "${1:1}" >&$IN || { do_fail;break;} ;;
# check output from child
[OE])
@@ -201,9 +211,16 @@ txpect()
read -t2 $LARG A <&$O
if [ $LEN -eq 0 ]
then
- [ -z "$A" ] && { do_fail;return;}
+ [ -z "$A" ] && { do_fail;break;}
else
- [ "$A" != "${1:1}" ] && { do_fail;return;}
+ if [ "$A" != "${1:1}" ]
+ then
+ # Append the rest of the output if there is any.
+ read -t.1 B <&$O
+ A="$A$B"
+ read -t.1 -rN 9999 B<&$ERR
+ do_fail;break;
+ fi
fi
;;
@@ -211,12 +228,12 @@ txpect()
X)
exec {IN}<&- {OUT}<&- {ERR}<&-
wait
- X=$?
+ A=$?
if [ -z "$LEN" ]
then
- [ $X -eq 0 ] && { do_fail;return;} # any error
+ [ $A -eq 0 ] && { do_fail;break;} # any error
else
- [ $X != "${1:1}" ] && { do_fail;return;} # specific value
+ [ $A != "${1:1}" ] && { do_fail;break;} # specific value
fi
;;
esac
@@ -225,7 +242,7 @@ txpect()
# In case we already closed it
exec {IN}<&- {OUT}<&- {ERR}<&-
- do_pass
+ [ $# -eq 0 ] && do_pass
}
# Recursively grab an executable and all the libraries needed to run it.
diff --git a/tests/sh.test b/tests/sh.test
index ac7b6e45..ecfa2791 100755
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -9,17 +9,17 @@
[ -z "$SH" ] && { [ -z "$TEST_HOST" ] && SH="sh" || export SH="bash" ; }
export EVAL="$SH -c"
-testing "leading assignments don't affect current line" \
- 'VAR=12345 echo ${VAR}a' "a\n" "" ""
-testing "can't have space before first : but yes around arguments" \
- 'BLAH=abcdefghi; echo ${BLAH: 1 : 3 }' "bcd\n" "" ""
+#testing "leading assignments don't affect current line" \
+# 'VAR=12345 echo ${VAR}a' "a\n" "" ""
+#testing "can't have space before first : but yes around arguments" \
+# 'BLAH=abcdefghi; echo ${BLAH: 1 : 3 }' "bcd\n" "" ""
+
+# texpect "name" "command" E/O/I"string"
# Prompt changes for root/normal user
-[ $(id -u) -eq 0 ] && P='#' || P='$'
+[ $(id -u) -eq 0 ] && P='# ' || P='$ '
# run sufficiently isolated shell child process to get predictable results
-SH="env -i PATH=${PATH@Q} PS1=\\$ $SH --noediting --noprofile --norc -is"
-
-# texpect "name" "command" E/O/I"string"
+SH="env -i PATH=${PATH@Q} PS1='\\$ ' $SH --noediting --noprofile --norc -is"
txpect "prompt and exit" "$SH" "E$P" "Iexit\n" X0
txpect "prompt and echo" "$SH" "E$P" "Iecho hello\n" "Ohello"$'\n' "E$P" X0
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 874f869a..fc0dfbd9 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -49,7 +49,7 @@
USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
-USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
+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
@@ -120,20 +120,26 @@ GLOBALS(
// null terminated array of running processes in pipeline
struct sh_process {
struct string_list *delete; // expanded strings
- struct sh_redirects {
- struct sh_redirects *next, *prev;
- int count, rd[];
- // rdlist = NULL if process didn't redirect, urd undoes <&- for builtins
- // rdlist is ** because this is our view into inherited context
- } **rdlist, *urd;
- int pid, exit;
+ int *urd, pid, exit; // undo redirects, child PID, exit status
struct sh_arg arg;
} *procs, *proc;
} *jobs, *job;
struct sh_process *callback_pp;
unsigned jobcnt;
+ int hfd; // next high filehandle (>= 10)
)
+
+// ordered for greedy matching, so >&; becomes >& ; not > &;
+// these would be const so the array is rodata, but then the compiler
+// throws endless warnings. The strings are already rodata and you don't
+// need to say const for that, but insecure C++ loons keep working to screw
+// up C because its continued existence threatens them somehow.
+static char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
+ ">&", ">|", ">", "&>>", "&>", 0};
+static char *flowcontrol[] = {";;&", ";;", ";&", ";", "||", "|&", "|",
+ "&&", "&", "(", ")", 0};
+
#define SH_NOCLOBBER 1 // set -C
void cd_main(void)
@@ -162,50 +168,52 @@ static void syntax_err(char *msg, ...)
if (*toys.optargs) xexit();
}
-// Print prompt, parsing escapes
+// 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, c, cc;
+ char *s, c, cc, *pp = toybuf;
+ int len;
if (!prompt) prompt = "\\$ ";
- while (*prompt) {
+ while ((len = sizeof(toybuf)-(pp-toybuf))>0 && *prompt) {
c = *(prompt++);
if (c=='!') {
if (*prompt=='!') prompt++;
else {
- printf("%ld", TT.lineno);
+ pp += snprintf(pp, len, "%ld", TT.lineno);
continue;
}
} else if (c=='\\') {
- int i = 0;
-
cc = *(prompt++);
- if (!cc) goto down;
+ 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=='$') putchar(getuid() ? '$' : '#');
+ else if (cc=='$') *pp++ = getuid() ? '$' : '#';
else if (cc=='h' || cc=='H') {
- *toybuf = 0;
- gethostname(toybuf, sizeof(toybuf)-1);
- if (cc=='h' && (s = strchr(toybuf, '.'))) *s = 0;
- fputs(toybuf, stdout);
- } else if (cc=='s') fputs(getbasename(*toys.argv), stdout);
- else {
- if (!(c = unescape(cc))) {
- c = '\\';
- prompt--;
- }
- i++;
- }
- if (!i) continue;
- }
-down:
- putchar(c);
+ *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 (!(c = unescape(cc))) {
+ *pp++ = '\\';
+ if (--len) *pp++ = c;
+ } else *pp++ = c;
+ } else *pp++ = c;
}
- fflush(stdout);
+ len = pp-toybuf;
+ if (len>=sizeof(toybuf)) len = sizeof(toybuf);
+ writeall(2, toybuf, len);
}
// quote removal, brace, tilde, parameter/variable, $(command),
@@ -360,113 +368,117 @@ static int assign_env(struct sh_arg *arg)
return 0;
}
-// cleanup one level of rdlist, can be llist_traverse() callback
-static void free_redirects(void *redir)
+// restore displaced filehandles, closing high filehandles they were copied to
+static void unredirect(int *urd)
{
- struct sh_redirects *rd = redir;
- int i, j;
+ int *rr = urd+1, i;
- for (i = 0; i<rd->count; i++) {
- j = rd->rd[2*i+1];
- if (j&3) close(j>>2); // close for parent process
- }
+ if (!urd) return;
- free(rd);
+ for (i = 0; i<*urd; i++) {
+ // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
+ dup2(rr[0], rr[1]);
+ close(rr[0]);
+ rr += 2;
+ }
+ free(urd);
}
-// clean up struct sh_process
-static void cleanup_process(struct sh_process *pp)
+// Return next available high (>=10) file descriptor
+int next_hfd()
{
- int i, *rr;
-
- if (pp->rdlist) free_redirects(dlist_lpop((void *)pp->rdlist));
- llist_traverse(pp->delete, free);
+ int hfd;
- // restore stdin/out/err for interactive builtins
- if (pp->urd) for (i = 0; pp->urd->count; i++) {
- rr = pp->urd->rd+2*i;
- dup2(rr[0], rr[1]); // TODO fd exhaustion? (And do what about it?)
- close(rr[0]);
+ 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;
}
-}
-int next_hfd(int hfd)
-{
- for (; hfd<99999; hfd++) if (-1 == fcntl(hfd, F_GETFL)) break;
- return (hfd == 99999) ? -1 : hfd;
+ return hfd;
}
-void add_redirect(struct sh_redirects **rdlist, int to, int from)
+/*
+4 cases:
+redirect now: from -> to, saving displaced to (+, +) = hfd to
+explicitly close (now), saving displaced to (-1, +) = hfd to
+redirect saving to, close saving from: (+, +) = hfd to then (-1, +) = hfd to
+{var} leak - nothing to save? Except the from open is deferred (+, +) but to == 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 == -1 just save to, else dup from->to after saving to.
+int save_redirect(int **rd, int from, int to)
{
- struct sh_redirects *rd = *rdlist;
- int *rr, count;
-
- // if to and from both -1, add a redirect level instead of redirect entry
- if (to == -1 && from == -1) {
- rd = 0;
- count = 0;
- } else count = (rd = (*rdlist)->prev)->count;
-
- if (!rd || (count && !(count&31))) {
- if (rd) dlist_lpop((void *)rdlist);
- // add extra entry in case of |&
- dlist_add_nomalloc((void *)rdlist,
- xrealloc(rd, sizeof(*rd)+(count+32)*2*sizeof(int *)));
- if (!rd) return;
- rd = (*rdlist)->prev;
+ int cnt, hfd, *rr;
+
+ // 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 ((hfd = next_hfd())==-1 || hfd != dup2(to, hfd)) return 1;
+
+ // dup "to"
+ if (from != -1 && to != dup2(from, to)) {
+ close(hfd);
+
+ return 1;
}
- rr = rd->rd+2*count;
- rr[0] = to;
- rr[1] = from;
- rd->count++;
+
+ // 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;
}
-// Expand arguments and collect redirects. This can be called from command
-// or block context.
-static struct sh_process *expand_redir(struct sh_arg *arg, int envlen,
- struct sh_redirects **rdlist)
+// 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)
{
struct sh_process *pp;
- char *s, *ss, *sss;
- int j, to, from, here = 0, hfd = 10;
+ char *s, *ss, *sss, *cv = 0;
+ int j, to, from, here = 0;
+
+ TT.hfd = 10;
if (envlen<0 || envlen>=arg->c) return 0;
pp = xzalloc(sizeof(struct sh_process));
- // We vfork() instead of fork to support nommu systems, and do
- // redirection setup in the parent process. Open new filehandles
- // and move to temporary values >10. Child calls dup2()/close after vfork().
- // If fd2 < 0 it's a here document (parent process writes to a pipe later).
+ // 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;
// Is this a redirect? s = prefix, ss = operator
sss = ss = (s = arg->v[j]) + redir_prefix(arg->v[j]);
- sss += anystart(ss, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
- ">&", ">|", ">", "&>>", "&>", 0});
+ sss += anystart(ss, redirectors);
if (ss == sss) {
// Nope: save/expand argument and loop
expand_arg(&pp->arg, s, 0, &pp->delete);
continue;
} else if (j+1 >= arg->c) {
+ // redirect needs one argument
s = "\\n";
- goto flush;
+ break;
}
sss = arg->v[++j];
- // It's a redirect: for [fd]<name s = start of [fd], ss = <, sss = name
+ // It's a redirect: for [to]<from s = start of [to], ss = <, sss = from
- if (!pp->rdlist) add_redirect(pp->rdlist = rdlist, -1, -1);
- hfd = next_hfd(hfd);
- // error check: premature EOF, no free high fd, target fd too big
- if (hfd == -1 || ++j == arg->c || (isdigit(*s) && ss-s>5)) goto flush;
+ 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);
- if (!sss) goto flush; // arg splitting here is an error
+ if (!sss) {
+ s = sss;
+ break; // arg splitting here is an error
+ }
if (sss != arg->v[j]) dlist_add((void *)&pp->delete, sss);
}
@@ -476,33 +488,35 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen,
else if (*s == '{') {
// when we close a filehandle, we _read_ from {var}, not write to it
if ((!strcmp(ss, "<&") || !strcmp(ss, ">&")) && !strcmp(sss, "-")) {
- to = -1;
- if ((ss = getvar(s+1, ss-s-2))) to = atoi(ss); // TODO trailing garbage?
- if (to<0) goto flush;
- add_redirect(rdlist, to, (to<<2)+1);
+ if (!(ss = getvar(s+1, ss-s-2))) break;
+ to = atoi(ss); // TODO trailing garbage?
+ if (save_redirect(&pp->urd, -1, to)) break;
continue;
// record high file descriptor in {to}<from environment variable
- } else setvar(xmprintf("%.*s=%d", (int)(ss-s-1), s, to = hfd), TAKE_MEM);
+ } 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-1), s+1, to);
+ }
}
// HERE documents?
if (!strcmp(ss, "<<<") || !strcmp(ss, "<<-") || !strcmp(ss, "<<")) {
char *tmp = getvar("TMPDIR", 6);
- int i, bad, len, l2, zap = (ss[2] == '-'),
- noforg =(ss[strcspn(ss, "\"'")]);
+ int i, len, bad = 0, 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 here document contents to file and lseek back to start
+ // write contents to file (if <<< else <<) then lseek back to start
else if (ss[2] == '<') {
- if (!noforg) sss = expand_one_arg(sss, NO_PATH|NO_SPLIT);
+ if (x) sss = expand_one_arg(sss, NO_PATH|NO_SPLIT);
len = strlen(sss);
if (len != writeall(from, sss, len)) bad++;
- free(sss);
+ if (x) free(sss);
} else {
struct sh_arg *hh = arg+here++;
@@ -510,38 +524,21 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen,
ss = hh->v[i];
sss = 0;
// expand_parameter, commands, and arithmetic
- if (!noforg)
- ss = sss = expand_one_arg(ss,
- NO_PATH|NO_SPLIT|NO_BRACE|NO_TILDE|NO_QUOTE);
+ if (x) ss = sss = expand_one_arg(ss,
+ NO_PATH|NO_SPLIT|NO_BRACE|NO_TILDE|NO_QUOTE);
while (zap && *ss == '\t') ss++;
- l2 = writeall(from, ss, len = strlen(ss));
+ x = writeall(from, ss, len = strlen(ss));
free(sss);
- if (len != l2) break;
+ if (len != x) break;
}
if (i != hh->c) bad++;
}
if (!bad && lseek(from, 0, SEEK_SET)) bad++;
- }
-
- // error report/handling
- if (bad || from == -1 || hfd != dup2(from, hfd)) {
- if (bad || from == -1) perror_msg("bad %s: '%s'", ss, tmp);
- else perror_msg("dup2");
- if (from != -1) close(from);
- pp->exit = 1;
- s = 0;
- free(tmp);
-
- goto flush;
- }
+ if (bad) close(from);
+ } else bad++;
free(tmp);
-
- if (from != hfd) close(from);
- add_redirect(rdlist, to, (from<<2)+(2*(to!=from)));
-
- continue;
- }
+ if (bad) break;
// from>=0 means it's fd<<2 (new fd to dup2() after vfork()) plus
// 2 if we should close(from>>2) after dup2(from>>2, to),
@@ -549,142 +546,92 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen,
// Handle file descriptor duplication/close (&> &>> <& >& with number or -)
// These redirect existing fd so nothing to open()
- if (strchr(ss, '&') && ss[2] != '>' && *ss != '|') {
+ } else if (strchr(ss, '&')) {
+
// is there an explicit fd?
- ss = sss;
- while (isdigit(ss)) ss++;
+ for (ss = sss; isdigit(*ss); ss++);
if (ss-sss>5 || (*ss && (*ss != '-' || ss[1]))) {
// bad fd
s = sss;
- goto flush;
+ break;
}
- // TODO can't reasonably check if fd is open here, should
- // do it when actual redirects happen
- add_redirect(rdlist, to, (((ss==sss)?to:atoi(sss))<<2)+(*ss != '-'));
-
- continue;
- }
+ from = (ss=sss) ? to : atoi(sss);
+ saveclose++;
+ } else {
- // Permissions to open external file with: < > >> <& >& <> >| &>> &>
- if (!strcmp(ss, "<>")) from = O_CREAT|O_RDWR;
- else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND;
- else {
- from = (*ss != '<') ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
- if (!strcmp(ss, ">") && (TT.options&SH_NOCLOBBER)) {
- struct stat st;
+ // Permissions to open external file with: < > >> <& >& <> >| &>> &>
+ if (!strcmp(ss, "<>")) from = O_CREAT|O_RDWR;
+ else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND;
+ else {
+ from = (*ss != '<') ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
+ if (!strcmp(ss, ">") && (TT.options&SH_NOCLOBBER)) {
+ struct stat st;
- // Not _just_ O_EXCL: > /dev/null allowed
- if (stat(sss, &st) || !S_ISREG(st.st_mode)) from |= O_EXCL;
+ // Not _just_ O_EXCL: > /dev/null allowed
+ if (stat(sss, &st) || !S_ISREG(st.st_mode)) from |= O_EXCL;
+ }
}
- }
// TODO: /dev/fd/# /dev/{stdin,stdout,stderr} /dev/{tcp,udp}/host/port
// TODO: is umask respected here?
- // Open the file
- from = xcreate(sss, from|WARN_ONLY, 777);
- if (-1 == from || hfd != dup2(from, hfd)) {
- pp->exit = 1;
- s = 0;
- if (from != -1) perror_msg("dup2");
-
- goto flush;
+ // Open the file
+ if (-1 == (from = xcreate(sss, from|WARN_ONLY, 777))) break;
}
- if (from != hfd) close(from);
- add_redirect(rdlist, to, (hfd<<2)+2);
+ // perform redirect, saving displaced "to".
+ save_redirect(&pp->urd, from, to);
+ // Do we save displaced "to" in env variable instead of undo list?
+ if (cv) {
+ --*pp->urd;
+ setvar(cv, TAKE_MEM);
+ cv = 0;
+ }
+ if (saveclose) save_redirect(&pp->urd, from, to);
+ else close(from);
}
- s = 0;
-
-flush:
- if (s) {
+ if (j != arg->c) {
syntax_err("bad %s", s);
if (!pp->exit) pp->exit = 1;
+ free(cv);
}
return pp;
}
-// perform the redirects in an rdlist, saving undo information as necessary
-// rd->rd[] is destination/source filehandle pairs, length is 2*rd->count
-// first (dest): filehandle to replace (via dup2)
-// second (src): fd<<2 + 2=close fd after dup, 1=close but save for nofork
-static int perform_redirects(struct sh_process *pp, int nofork)
-{
- struct sh_redirects *rd = 0;
- int rc = 0, hfd = 20;
-
- if (pp->rdlist) rd = *pp->rdlist;
- if (rd) for (;;) {
- int i, j, *rr;
-
- for (i = 0; i<rd->count; i++) {
- rr = rd->rd+2*i;
-
- // preserve redirected stdin/out/err for nofork, to restore later
- if (nofork && (rr[1]&1)) {
- if (!pp->urd) add_redirect(&pp->urd, -1, -1);
- hfd = next_hfd(hfd);
- if (hfd == -1 || hfd != dup2(rr[0], hfd)) {
- perror_msg("%d", rr[0]);
- rc = 1;
- continue; // don't perform a redirect we can't undo
- } else add_redirect(&pp->urd, hfd, rr[0]);
- }
-
- // move the filehandle
- j = rr[1]>>2;
- if (rr[0] != j && j != dup2(rr[0], j)) {
- perror_msg("%d", j);
- rc = 1;
- } else if ((rr[1]&1) || ((rr[1]&2) && !nofork)) {
- close(j);
- rr[1] &= ~2;
- }
- }
-
- if (rd->next == *pp->rdlist) break;
- rd = rd->next;
- }
-
- return rc;
-}
-
-// callback from xpopen_setup()
+// callback from xpopen_setup() to close all the high filehandles
+// we cached things in so child doesn't inherit unnecessary open fds.
static void redirect_callback(void)
{
- if (perform_redirects(TT.callback_pp, 0)) _exit(1);
- TT.callback_pp = 0;
+ int i, *rr = TT.callback_pp->urd;
+
+ for (i = 0; i<*rr; i++) close(rr[1+2*i]);
}
-// Execute the commands in a pipeline segment
-static struct sh_process *run_command(struct sh_arg *arg,
- struct sh_redirects **rdlist, int *pipes)
+// Execute a single command
+static struct sh_process *run_command(struct sh_arg *arg, int *pipes)
{
struct sh_process *pp;
struct toy_list *tl;
- // grab environment var assignments, expand arguments and queue up redirects
- if (!(pp = expand_redir(arg, assign_env(arg), rdlist))) return 0;
- if (pp->exit) return pp;
+ // grab environment var assignments, expand arguments and perform redirects
+ if (!(pp = expand_redir(arg, assign_env(arg)))) return 0;
+ // Do nothing if nothing to do
+ if (pp->exit || !pp->arg.v);
+ else if (!strcmp(*pp->arg.v, "((")) {
+ printf("Math!\n");
// TODO: handle ((math))
// TODO: check for functions()
-
// Is this command a builtin that should run in this process?
- if ((tl = toy_find(*pp->arg.v))
+ } else if ((tl = toy_find(*pp->arg.v))
&& (tl->flags & (TOYFLAG_NOFORK|TOYFLAG_MAYFORK)))
{
struct toy_context temp;
sigjmp_buf rebound;
- // NOFORK can't background and blocks until done or interrupted, so
- // do redirects here then unwind after the command.
-
- perform_redirects(pp, 1);
-
// This fakes lots of what toybox_main() does.
memcpy(&temp, &toys, sizeof(struct toy_context));
memset(&toys, 0, sizeof(struct toy_context));
@@ -703,11 +650,10 @@ static struct sh_process *run_command(struct sh_arg *arg,
if (-1 == (pp->pid = xpopen_setup(pp->arg.v, pipes, redirect_callback)))
perror_msg("%s: vfork", *pp->arg.v);
}
- cleanup_process(pp);
- // unwind redirects
-
-// TODO: what if exception handler recovery?
+ // cleanup process
+ llist_traverse(pp->delete, free);
+ unredirect(pp->urd);
return pp;
}
@@ -764,15 +710,12 @@ static char *parse_word(char *start)
// Things we should only return at the _start_ of a word
// Redirections. 123<<file- parses as 2 args: "123<<" "file-".
- // Greedy matching: >&; becomes >& ; not > &;
s = end + redir_prefix(end);
- j = anystart(s, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
- ">&", ">|", ">", 0});
+ j = anystart(s, redirectors);
if (j) s += j;
// Control characters
- else s = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
- "|&", "|", "&&", "&>>", "&>", "&", "(", ")", 0});
+ else s = end + anystart(end, flowcontrol);
if (s != end) return (end == start) ? s : end;
i++;
}
@@ -814,16 +757,48 @@ struct sh_pipeline {
struct sh_arg arg[1];
};
-// run a series of "command | command && command" with redirects.
-int run_pipeline(struct sh_pipeline **pl, struct sh_redirects **rdlist)
+// run a series of "segment | segment && segment" with redirects.
+int run_pipeline(struct sh_pipeline **pl)
{
struct sh_process *pp;
- int rc = 0, pipes[2];
+ int rc = 0, pin = 0, pipes[2], *psave;
+ // loop through pipeline segments in a block
for (;;) {
-// TODO job control
-// TODO pipes (ending, leading)
- if (!(pp = run_command((*pl)->arg, rdlist, 0))) rc = 0;
+ struct sh_arg *arg = (*pl)->arg;
+
+ psave = 0;
+/*
+ char *ctl = arg->v[arg->c];
+
+ // Did the previous pipe segment pipe input into us?
+ if (pin-->0) save_redir(&psave, pipes[1], 0);
+
+// TODO job control: & backgrounding
+// TODO && ||
+// TODO | |&
+// TODO add redir segment, redir pipes[1] to pipes[0] with close, pop again later
+// TODO pipe segments are subshells ala ( )
+// TODO: don't () around single process pipeline, shell otherwise
+// this turns into exec for sh -c "echo"
+// single pipe segments that _aren't_ builtins run directly,
+// compound pipe segments and builtins run via subshell.
+// I.E. subshell implicit exec
+// TODO we're not running a command, we're running a block stack?
+// echo one two three | while read i; do echo hello; done
+// TODO free/cleanup partial pipeline on NULL return?
+
+// TODO run_pipeline has to merge into run_function because pipes don't
+// connect commands, they connect arbitrary function chunks.
+ if (ctl) {
+
+ // pipe?
+ if (*ctl == '|' && ctl[1] != '|') {
+ }
+*/
+
+// TODO: force background? builtin in pipeline implicit ()
+ if (!(pp = run_command(arg, 0))) rc = 0;
else {
// TODO backgrounding
if (pp->pid) pp->exit = xpclose_both(pp->pid, 0);
@@ -832,6 +807,7 @@ int run_pipeline(struct sh_pipeline **pl, struct sh_redirects **rdlist)
rc = pp->exit;
free(pp);
}
+ unredirect(psave);
if ((*pl)->next && !(*pl)->next->type) *pl = (*pl)->next;
else return rc;
@@ -1043,6 +1019,8 @@ static int parse_line(char *line, struct sh_function *sp)
free(s);
s = 0;
}
+ // ;; and friends only allowed in case statements
+ if (*s == ';' && (!ex || strcmp(ex, "esac"))) goto flush;
last = s;
pl->count = -1;
@@ -1181,8 +1159,11 @@ check:
}
free(delete);
- // advance past <<< arguments (stored as here documents, but no new input)
+ // 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;
@@ -1190,10 +1171,11 @@ check:
// 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;
- dlist_terminate(sp->pipeline);
+ if (pl->arg->v[pl->arg->c]) return 1;
// Don't need more input, can start executing.
+ dlist_terminate(sp->pipeline);
return 0;
flush:
@@ -1241,8 +1223,7 @@ static void run_function(struct sh_function *sp)
struct blockstack {
struct blockstack *next;
struct sh_pipeline *start, *end;
- struct sh_redirects *redir;
- int run, loop;
+ int run, loop, *redir;
struct sh_arg farg; // for/select arg stack
struct string_list *fdelete; // farg's cleanup list
@@ -1278,6 +1259,10 @@ static void run_function(struct sh_function *sp)
llist_traverse(blk->fdelete, free);
free(llist_pop(&blk));
}
+ if (i) {
+ syntax_err("break outside loop");
+ break;
+ }
pl = pl->next;
continue;
@@ -1285,8 +1270,8 @@ static void run_function(struct sh_function *sp)
// inherit redirects?
// returns last statement of pipeline
- if (!blk) toys.exitval = run_pipeline(&pl, 0);
- else if (blk->run) toys.exitval = run_pipeline(&pl, &blk->redir);
+ if (!blk) toys.exitval = run_pipeline(&pl);
+ else if (blk->run) toys.exitval = run_pipeline(&pl);
else while (pl->next && !pl->next->type) pl = pl->next;
// Starting a new block?
@@ -1477,8 +1462,8 @@ void sh_main(void)
if (!s) s = prompt ? "> " : (getpid() ? "\\$ " : "# ");
do_prompt(s);
} else TT.lineno++;
+// TODO line editing/history
if (!(new = xgetline(f ? f : stdin, 0))) break;
-
// TODO if (!isspace(*new)) add_to_history(line);
// returns 0 if line consumed, command if it needs more data