aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2019-12-12 22:02:35 -0600
committerRob Landley <rob@landley.net>2019-12-12 22:02:35 -0600
commit953b23aad3577bcf47b0962ebb286b655add58ba (patch)
treeeaa0fb91449b844cf1c59f22b4a4a38690efe6f5
parent03495651d78b8d802e1aa31e330aa93899a9386a (diff)
downloadtoybox-953b23aad3577bcf47b0962ebb286b655add58ba.tar.gz
Next round of toysh work, with associated lib plumbing.
-rw-r--r--lib/env.c48
-rw-r--r--lib/lib.h1
-rw-r--r--lib/xwrap.c9
-rw-r--r--toys/pending/sh.c515
4 files changed, 403 insertions, 170 deletions
diff --git a/lib/env.c b/lib/env.c
index 3e05f4e4..af0ec29d 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -22,6 +22,12 @@ long environ_bytes()
// Use this instead of envc so we keep track of what needs to be freed.
void xclearenv(void)
{
+ if (toys.envc) {
+ char **ss;
+
+ for (ss = environ; *ss; ss++) free(*ss);
+ free(environ);
+ }
toys.envc = 0;
*environ = 0;
}
@@ -29,18 +35,18 @@ void xclearenv(void)
// Frees entries we set earlier. Use with libc getenv but not setenv/putenv.
// if name has an equals and !val, act like putenv (name=val must be malloced!)
// if !val unset name. (Name with = and val is an error)
-void xsetenv(char *name, char *val)
+void xsetmyenv(int *envc, char ***env, char *name, char *val)
{
- unsigned i, len, envc;
+ unsigned i, len, ec;
char *new;
// If we haven't snapshot initial environment state yet, do so now.
- if (!toys.envc) {
+ if (!*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, toys.envc*sizeof(char *));
- environ = (void *)new;
+ while ((*env)[(*envc)++]);
+ memcpy(new = xmalloc(((*envc|0xff)+1)*sizeof(char *)), *env,
+ *envc*sizeof(char *));
+ *env = (void *)new;
}
new = strchr(name, '=');
@@ -53,16 +59,16 @@ void xsetenv(char *name, char *val)
if (val) new = xmprintf("%s=%s", name, val);
}
- envc = toys.envc-1; // compensate for size +1 above
- for (i = 0; environ[i]; i++) {
+ ec = (*envc)-1; // compensate for size +1 above
+ for (i = 0; (*env)[i]; i++) {
// Drop old entry, freeing as appropriate. Assumes no duplicates.
- if (!memcmp(name, environ[i], len) && environ[i][len]=='=') {
- if (i>=envc) free(environ[i]);
+ if (!memcmp(name, (*env)[i], len) && (*env)[i][len]=='=') {
+ if (i>=ec) free((*env)[i]);
else {
// move old entries down, add at end of old data
- toys.envc = envc--;
- for (; new ? i<envc : !!environ[i]; i++) environ[i] = environ[i+1];
- i = envc;
+ *envc = ec--;
+ for (; new ? i<ec : !!(*env)[i]; i++) (*env)[i] = (*env)[i+1];
+ i = ec;
}
break;
}
@@ -71,12 +77,18 @@ void xsetenv(char *name, char *val)
if (!new) return;
// resize and null terminate if expanding
- if (!environ[i]) {
+ if (!(*env)[i]) {
len = i+1;
- if (!(len&255)) environ = xrealloc(environ, len*sizeof(char *));
- environ[len] = 0;
+ if (!(len&255)) *env = xrealloc(*env, len*sizeof(char *));
+ (*env)[len] = 0;
}
- environ[i] = new;
+ (*env)[i] = new;
+}
+
+// xsetenv for normal environment (extern variables).
+void xsetenv(char *name, char *val)
+{
+ return xsetmyenv(&toys.envc, &environ, name, val);
}
void xunsetenv(char *name)
diff --git a/lib/lib.h b/lib/lib.h
index 8f949096..5ec648c8 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -134,6 +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_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 591c9513..cfd17219 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_both(char **argv, int *pipes)
+pid_t xpopen_setup(char **argv, int *pipes, void (*callback)(void))
{
int cestnepasun[4], pid;
@@ -269,6 +269,7 @@ pid_t xpopen_both(char **argv, int *pipes)
close(pipes[1]);
}
}
+ if (callback) callback();
if (argv) xexec(argv);
// In fork() case, force recursion because we know it's us.
@@ -309,6 +310,12 @@ pid_t xpopen_both(char **argv, int *pipes)
return pid;
}
+pid_t xpopen_both(char **argv, int *pipes)
+{
+ return xpopen_setup(argv, pipes, 0);
+}
+
+
// Wait for child process to exit, then return adjusted exit code.
int xwaitpid(pid_t pid)
{
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 147f5e7d..874f869a 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -101,6 +101,8 @@ GLOBALS(
long lineno;
+ char **locals;
+
struct double_list functions;
unsigned options;
@@ -117,11 +119,18 @@ GLOBALS(
// null terminated array of running processes in pipeline
struct sh_process {
- struct string_list *delete; // expanded strings
- int pid, exit; // status? Stopped? Exited?
+ 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;
struct sh_arg arg;
} *procs, *proc;
} *jobs, *job;
+ struct sh_process *callback_pp;
unsigned jobcnt;
)
@@ -142,7 +151,7 @@ void exit_main(void)
}
// like error_msg() but exit from shell scripts
-void syntax_err(char *msg, ...)
+static void syntax_err(char *msg, ...)
{
va_list va;
@@ -203,6 +212,9 @@ down:
// $((arithmetic)), split, path
#define NO_PATH (1<<0)
#define NO_SPLIT (1<<1)
+#define NO_BRACE (1<<2)
+#define NO_TILDE (1<<3)
+#define NO_QUOTE (1<<4)
// TODO: ${name:?error} causes an error/abort here (syntax_err longjmp?)
// TODO: $1 $@ $* need args marshalled down here: function+structure?
// arg = append to this
@@ -236,13 +248,30 @@ static void expand_arg(struct sh_arg *arg, char *new, unsigned flags,
*/
}
+// Expand exactly one arg, returning NULL if it split.
+// If return != new you need to free it.
+static char *expand_one_arg(char *new, unsigned flags)
+{
+ struct sh_arg arg;
+ char *s = 0;
+ int i;
+
+ memset(&arg, 0, sizeof(arg));
+ expand_arg(&arg, new, flags, 0);
+ if (arg.c == 1) s = *arg.v;
+ else for (i = 0; i < arg.c; i++) free(arg.v[i]);
+ free(arg.v);
+
+ return s;
+}
+
// Assign one variable
// s: key=val
// type: 0 = whatever it was before, local otherwise
#define TAKE_MEM 0x80000000
// declare -aAilnrux
// ft
-void setvar(char *s, unsigned type)
+static void setvar(char *s, unsigned type)
{
if (type&TAKE_MEM) type ^= TAKE_MEM;
else s = xstrdup(s);
@@ -251,22 +280,38 @@ void setvar(char *s, unsigned type)
xsetenv(s, 0);
}
-char *getvar(char *s)
+// get variable of length len starting at s.
+static char *getvar(char *s, int len)
{
- return getenv(s);
+ unsigned uu;
+ char **ss = TT.locals;
+
+ // loop through local, then global variables
+ for (uu = 0; ; uu++) {
+ if (!ss[uu]) {
+ if (ss != TT.locals) return 0;
+ ss = environ;
+ uu = 0;
+ }
+ // Use UHF rubik's cube protocol to find match.
+ if (!strncmp(ss[uu], s, len) && ss[uu][len] == '=') return ss[uu]+len+1;
+ }
}
// return length of match found at this point
static int anystart(char *s, char **try)
{
+ char *ss = s;
+
while (*try) {
- if (strstart(&s, *try)) return strlen(*try);
+ if (strstart(&s, *try)) return s-ss;
try++;
}
return 0;
}
+// is this one of the strings in try[] (null terminated array)
static int anystr(char *s, char **try)
{
while (*try) if (!strcmp(s, *try++)) return 1;
@@ -275,7 +320,7 @@ static int anystr(char *s, char **try)
}
// return length of valid prefix that could go before redirect
-int redir_prefix(char *word)
+static int redir_prefix(char *word)
{
char *s = word;
@@ -290,15 +335,12 @@ int redir_prefix(char *word)
// TODO |&
-// rd[0] = next, 1 = prev, 2 = len, 3-x = to/from redirection pairs.
-// Execute the commands in a pipeline segment
-struct sh_process *run_command(struct sh_arg *arg, int **rdlist)
+// Return number of entries at the start that are environment variable
+// assignments, and perform assignments if nothing else on the line
+static int assign_env(struct sh_arg *arg)
{
- struct sh_process *pp = xzalloc(sizeof(struct sh_process));
- struct toy_list *tl;
- char *s, *ss, *sss;
- unsigned envlen, j;
- int fd, here = 0, rdcount = 0, *rd = 0, *rr, hfd = 0;
+ int envlen, j;
+ char *s;
// Grab variable assignments
for (envlen = 0; envlen<arg->c; envlen++) {
@@ -308,165 +350,328 @@ struct sh_process *run_command(struct sh_arg *arg, int **rdlist)
}
// perform assignments locally if there's no command
- if (envlen == arg->c) {
- for (j = 0; j<envlen; j++) {
- struct sh_arg aa;
-
- aa.c = 0;
- expand_arg(&aa, arg->v[j], NO_PATH|NO_SPLIT, 0);
- setvar(*aa.v, TAKE_MEM);
- free(aa.v);
- }
- free(pp);
+ if (envlen != arg->c) return envlen;
- return 0;
+ for (j = 0; j<envlen; j++) {
+ s = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT);
+ setvar(s, TAKE_MEM*(s!=arg->v[j]));
}
+ return 0;
+}
+
+// cleanup one level of rdlist, can be llist_traverse() callback
+static void free_redirects(void *redir)
+{
+ struct sh_redirects *rd = redir;
+ int i, j;
+
+ for (i = 0; i<rd->count; i++) {
+ j = rd->rd[2*i+1];
+ if (j&3) close(j>>2); // close for parent process
+ }
+
+ free(rd);
+}
+
+// clean up struct sh_process
+static void cleanup_process(struct sh_process *pp)
+{
+ int i, *rr;
+
+ if (pp->rdlist) free_redirects(dlist_lpop((void *)pp->rdlist));
+ llist_traverse(pp->delete, free);
+
+ // 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]);
+ }
+}
+
+int next_hfd(int hfd)
+{
+ for (; hfd<99999; hfd++) if (-1 == fcntl(hfd, F_GETFL)) break;
+ return (hfd == 99999) ? -1 : hfd;
+}
+
+void add_redirect(struct sh_redirects **rdlist, int to, int from)
+{
+ 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;
+ }
+ rr = rd->rd+2*count;
+ rr[0] = to;
+ rr[1] = from;
+ rd->count++;
+}
+
+// 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)
+{
+ struct sh_process *pp;
+ char *s, *ss, *sss;
+ int j, to, from, here = 0, 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 them to temporary values >10. The rd[] array has pairs of
- // filehandles: child replaces fd1 with fd2 via dup2() and close() after
- // the vfork(). fd2 is <<1, if bottom bit set don't close it (dup instead).
+ // 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).
// Expand arguments and perform redirections
for (j = envlen; j<arg->c; j++) {
- // Is this a redirect?
- ss = (s = arg->v[j]) + redir_prefix(arg->v[j]);
- if (!anystr(ss, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>", ">&",
- ">|", ">", "&>>", "&>", 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});
+ if (ss == sss) {
// Nope: save/expand argument and loop
- expand_arg(&pp->arg, s, 0, 0);
+ expand_arg(&pp->arg, s, 0, &pp->delete);
continue;
+ } else if (j+1 >= arg->c) {
+ s = "\\n";
+ goto flush;
}
+ sss = arg->v[++j];
- // Yes. Expand rd[] and find first unused filehandle >10
- if (!(rdcount&31)) {
- if (rd) dlist_lpop((void *)rdlist);
- rd = xrealloc(rd, (2*rdcount+3+2*32)*sizeof(int *));
- dlist_add_nomalloc((void *)rdlist, (void *)rd);
- }
- rr = rd+3+rdcount;
- if (!hfd)
- for (hfd = 10; hfd<99999; hfd++) if (-1 == fcntl(hfd, F_GETFL)) break;
+ // It's a redirect: for [fd]<name s = start of [fd], ss = <, sss = name
- // error check: premature EOF, target fd too high, or redirect file splits
- if (++j == arg->c || (isdigit(*s) && ss-s>5)) goto flush;
- fd = pp->arg.c;
+ 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;
// expand arguments for everything but << and <<-
- if (strncmp(ss, "<<", 2) || ss[2] == '<') {
- expand_arg(&pp->arg, arg->v[j], NO_PATH|(NO_SPLIT*!strcmp(ss, "<<<")), 0);
- if (fd+1 != pp->arg.c) goto flush;
- sss = pp->arg.v[--pp->arg.c];
- } else dlist_add((void *)&pp->delete, sss = xstrdup(arg->v[j]));
-
- // rd[] entries come in pairs: first is which fd gets redirected after
- // vfork(), I.E. the [n] part of [n]<word
-
- if (isdigit(*ss)) fd = atoi(ss);
- else if (*ss == '{') {
- ss++;
+ 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 != arg->v[j]) dlist_add((void *)&pp->delete, sss);
+ }
+
+ // Parse the [fd] part of [fd]<name
+ to = *ss != '<';
+ if (isdigit(*s)) to = atoi(s);
+ else if (*s == '{') {
// when we close a filehandle, we _read_ from {var}, not write to it
- if ((!strcmp(s, "<&") || !strcmp(s, ">&")) && !strcmp(sss, "-")) {
- ss = xstrndup(ss, (s-ss)-1);
- sss = getvar(ss);
- free(ss);
- fd = -1;
- if (sss) fd = atoi(sss);
- if (fd<0) goto flush;
- if (fd>2) {
- rr[0] = fd;
- rr[1] = fd<<1; // close it
- rdcount++;
- }
+ 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);
+
continue;
- } else setvar(xmprintf("%.*s=%d", (int)(s-ss), ss, hfd), TAKE_MEM);
- } else fd = *ss != '<';
- *rr = fd;
+ // record high file descriptor in {to}<from environment variable
+ } else setvar(xmprintf("%.*s=%d", (int)(ss-s-1), s, to = hfd), TAKE_MEM);
+ }
- // at this point for [n]<word s = start of [n], ss = start of <, sss = word
+ // 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, "\"'")]);
+
+ // 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
+ else if (ss[2] == '<') {
+ if (!noforg) sss = expand_one_arg(sss, NO_PATH|NO_SPLIT);
+ len = strlen(sss);
+ if (len != writeall(from, sss, len)) bad++;
+ free(sss);
+ } else {
+ struct sh_arg *hh = arg+here++;
+
+ for (i = 0; i<hh->c; i++) {
+ 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);
+
+ while (zap && *ss == '\t') ss++;
+ l2 = writeall(from, ss, len = strlen(ss));
+ free(sss);
+ if (len != l2) break;
+ }
+ if (i != hh->c) bad++;
+ }
+ if (!bad && lseek(from, 0, SEEK_SET)) bad++;
+ }
- // second entry in this rd[] pair is new fd to dup2() after vfork(),
- // I.E. for [n]<word the fd if you open("word"). It's stored <<1 and the
- // low bit set means don't close(rr[1]) after dup2(rr[1]>>1, rr[0]);
+ // 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);
- // fd<0 means HERE document. Canned input stored earlier, becomes pipe later
- if (!strcmp(s, "<<<") || !strcmp(s, "<<-") || !strcmp(s, "<<")) {
- fd = --here<<2;
- if (s[2] == '-') fd += 1; // zap tabs
- if (s[strcspn(s, "\"'")]) fd += 2; // it was quoted so no expansion
- rr[1] = fd;
- rdcount++;
+ goto flush;
+ }
+ free(tmp);
+
+ if (from != hfd) close(from);
+ add_redirect(rdlist, to, (from<<2)+(2*(to!=from)));
continue;
}
- // Handle file descriptor duplication/close (&> &>> <& >& with number or -)
- if (strchr(ss, '&') && ss[2] != '>') {
- char *dig = sss;
+ // 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),
+ // 1 if we should close but dup for nofork recovery (ala <&2-)
- // These redirect existing fd so nothing to open()
- while (isdigit(dig)) dig++;
- if (dig-sss>5) {
+ // Handle file descriptor duplication/close (&> &>> <& >& with number or -)
+ // These redirect existing fd so nothing to open()
+ if (strchr(ss, '&') && ss[2] != '>' && *ss != '|') {
+ // is there an explicit fd?
+ ss = sss;
+ while (isdigit(ss)) ss++;
+ if (ss-sss>5 || (*ss && (*ss != '-' || ss[1]))) {
+ // bad fd
s = sss;
goto flush;
}
-// TODO can't check if fd is open here, must do it when actual redirects happen
- if (!*dig || (*dig=='-' && !dig[1])) {
- rr[1] = (((dig==sss) ? *rr : atoi(sss))<<1)+(*dig != '-');
- rdcount++;
+ // 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;
- }
+ continue;
}
// Permissions to open external file with: < > >> <& >& <> >| &>> &>
- if (!strcmp(ss, "<>")) fd = O_CREAT|O_RDWR;
- else if (strstr(ss, ">>")) fd = O_CREAT|O_APPEND;
+ if (!strcmp(ss, "<>")) from = O_CREAT|O_RDWR;
+ else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND;
else {
- fd = (*ss != '<') ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
+ 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)) fd |= O_EXCL;
+ if (stat(sss, &st) || !S_ISREG(st.st_mode)) from |= O_EXCL;
}
}
- // Open the file
// TODO: /dev/fd/# /dev/{stdin,stdout,stderr} /dev/{tcp,udp}/host/port
- if (-1 == (fd = xcreate(sss, fd|WARN_ONLY, 777)) || hfd != dup2(fd, hfd)) {
+
+// 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;
}
- if (fd != hfd) close(fd);
- rr[1] = hfd<<1;
- rdcount++;
-
- // queue up a 2>&1 ?
- if (strchr(ss, '&')) {
- if (!(31&++rdcount)) rd = xrealloc(rd, (2*rdcount+66)*sizeof(int *));
- rr = rd+3+rdcount;
- rr[0] = 2;
- rr[1] = 1+(1<<1);
- rdcount++;
+ if (from != hfd) close(from);
+
+ add_redirect(rdlist, to, (hfd<<2)+2);
+ }
+
+ s = 0;
+
+flush:
+ if (s) {
+ syntax_err("bad %s", s);
+ if (!pp->exit) pp->exit = 1;
+ }
+
+ 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;
}
- if (rd) rd[2] = rdcount;
-// TODO: ok, now _use_ in_rd[in_rdcount] and rd[rdcount]. :)
+ return rc;
+}
+
+// callback from xpopen_setup()
+static void redirect_callback(void)
+{
+ if (perform_redirects(TT.callback_pp, 0)) _exit(1);
+ TT.callback_pp = 0;
+}
+
+// Execute the commands in a pipeline segment
+static struct sh_process *run_command(struct sh_arg *arg,
+ struct sh_redirects **rdlist, int *pipes)
+{
+ struct sh_process *pp;
+ struct toy_list *tl;
-// TODO: handle ((math)) here
+ // 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;
-// TODO use envlen
-// TODO: check for functions
+// 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))
@@ -475,15 +680,18 @@ struct sh_process *run_command(struct sh_arg *arg, int **rdlist)
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));
-// TODO: redirect stdin/out
if (!sigsetjmp(rebound, 1)) {
toys.rebound = &rebound;
-// must be null terminated
- toy_init(tl, pp->arg.v);
+ toy_init(tl, pp->arg.v); // arg.v must be null terminated
tl->toy_main();
}
pp->exit = toys.exitval;
@@ -491,26 +699,15 @@ struct sh_process *run_command(struct sh_arg *arg, int **rdlist)
if (toys.old_umask) umask(toys.old_umask);
memcpy(&toys, &temp, sizeof(struct toy_context));
} else {
- int pipe[2];
-
- pipe[0] = 0;
- pipe[1] = 1;
-// TODO: redirect and pipe
-// TODO: redirecting stderr needs xpopen3() or rethink
- if (-1 == (pp->pid = xpopen_both(pp->arg.v, pipe)))
+ TT.callback_pp = pp;
+ if (-1 == (pp->pid = xpopen_setup(pp->arg.v, pipes, redirect_callback)))
perror_msg("%s: vfork", *pp->arg.v);
-// TODO: don't close stdin/stdout!
- else pp->exit = xpclose_both(pp->pid, 0);
}
+ cleanup_process(pp);
- s = 0;
-flush:
- if (s) {
- syntax_err("bad %s", s);
- if (!pp->exit) pp->exit = 1;
- }
- for (j = 0; j<rdcount; j++) if (rd[4+2*j]>6) close(rd[4+2*j]>>1);
- if (rdcount) free(dlist_lpop((void *)rdlist));
+ // unwind redirects
+
+// TODO: what if exception handler recovery?
return pp;
}
@@ -618,17 +815,20 @@ struct sh_pipeline {
};
// run a series of "command | command && command" with redirects.
-int run_pipeline(struct sh_pipeline **pl, int *rd)
+int run_pipeline(struct sh_pipeline **pl, struct sh_redirects **rdlist)
{
struct sh_process *pp;
- int rc = 0;
+ int rc = 0, pipes[2];
for (;;) {
// TODO job control
- if (!(pp = run_command((*pl)->arg, &rd))) rc = 0;
+// TODO pipes (ending, leading)
+ if (!(pp = run_command((*pl)->arg, rdlist, 0))) rc = 0;
else {
+// TODO backgrounding
+ if (pp->pid) pp->exit = xpclose_both(pp->pid, 0);
//wait4(pp);
- llist_traverse(pp->delete, free);
+// TODO -o pipefail
rc = pp->exit;
free(pp);
}
@@ -639,7 +839,6 @@ int run_pipeline(struct sh_pipeline **pl, int *rd)
}
-
// scratch space (state held between calls). Don't want to make it global yet
// because this could be reentrant.
struct sh_function {
@@ -716,9 +915,14 @@ static int parse_line(char *line, struct sh_function *sp)
} else if (pl->count != pl->here) {
arg += 1+pl->here;
- argxtend(arg);
- if (strcmp(line, arg->v[arg->c])) {
+ // Match unquoted EOF.
+ for (s = line, end = arg->v[arg->c]; *s && *end; s++, i++) {
+ s += strspn(s, "\\\"'");
+ if (*s != *end) break;
+ }
+ if (!*s && !*end) {
// Add this line
+ argxtend(arg);
arg->v[arg->c+1] = arg->v[arg->c];
arg->v[arg->c++] = xstrdup(line);
// EOF hit, end HERE document
@@ -744,6 +948,8 @@ static int parse_line(char *line, struct sh_function *sp)
// 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;
@@ -755,8 +961,10 @@ static int parse_line(char *line, struct sh_function *sp)
// queue up HERE EOF so input loop asks for more lines.
arg[pl->count].v = xzalloc(2*sizeof(void *));
- *arg[pl->count].v = arg->v[++i];
- arg[pl->count].c = -(s[2] == '<'); // note <<< as c = -1
+ 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;
}
@@ -974,6 +1182,7 @@ check:
free(delete);
// advance past <<< arguments (stored as here documents, but no new input)
+ if (!sp->pipeline) return 0;
pl = sp->pipeline->prev;
while (pl->count<pl->here && pl->arg[pl->count].c<0)
pl->arg[pl->count++].c = 0;
@@ -1032,7 +1241,8 @@ static void run_function(struct sh_function *sp)
struct blockstack {
struct blockstack *next;
struct sh_pipeline *start, *end;
- int run, loop, *redir;
+ struct sh_redirects *redir;
+ int run, loop;
struct sh_arg farg; // for/select arg stack
struct string_list *fdelete; // farg's cleanup list
@@ -1076,7 +1286,7 @@ 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);
+ else if (blk->run) toys.exitval = run_pipeline(&pl, &blk->redir);
else while (pl->next && !pl->next->type) pl = pl->next;
// Starting a new block?
@@ -1242,6 +1452,9 @@ void sh_main(void)
struct sh_function scratch;
int prompt = 0;
+ // Is this an interactive shell?
+// if (FLAG(i) || (!FLAG(c)&&(FLAG(S)||!toys.optc) && isatty(0) && isatty(1)))
+
// Set up signal handlers and grab control of this tty.
// Read environment for exports from parent shell