aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/rc/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/rc/exec.c')
-rw-r--r--src/cmd/rc/exec.c1267
1 files changed, 1267 insertions, 0 deletions
diff --git a/src/cmd/rc/exec.c b/src/cmd/rc/exec.c
new file mode 100644
index 0000000..5baaf1a
--- /dev/null
+++ b/src/cmd/rc/exec.c
@@ -0,0 +1,1267 @@
+#include "rc.h"
+#include "exec.h"
+
+#include <sys/wait.h>
+
+int yyparse(void);
+
+struct Builtin{
+ char *name;
+ void (*func)(void);
+};
+
+struct State {
+ int async;
+};
+
+static struct State state;
+
+// -----------------------------------------------------------------------
+// globals
+
+static Word nullpath = { .str="", .link=nil };
+
+struct Builtin builtin[]={
+ {"cd", xcd},
+ {".", xdot},
+ {"echo", xecho},
+ {"exit", xexit},
+ {"fg", xfg},
+ {"jobs", xjob},
+ 0,
+};
+
+// -----------------------------------------------------------------------
+// internal
+
+/* words and lists */
+
+static
+void
+pushword(char *str)
+{
+ if(!runner->args)
+ fatal("attempt to push on empty argument stack\n");
+
+ runner->args->word = makeword(str, runner->args->word);
+}
+
+static
+void
+popword(void)
+{
+ Word *w;
+ if(!runner->args)
+ fatal("tried to pop word on empty argument stack\n");
+
+ w = runner->args->word;
+ if(!w)
+ fatal("tried to pop word but nothing there\n");
+
+ runner->args->word = w->link;
+ efree(w->str);
+ efree(w);
+}
+
+static
+Word*
+copywords(Word *a, Word *tail)
+{
+ Word *v = nil, **end;
+
+ for(end=&v; a; a = a->link,end=&(*end)->link)
+ *end = makeword(a->str, nil);
+ *end = tail;
+
+ return v;
+}
+
+static
+void
+freewords(Word *w)
+{
+ Word *n;
+ while(w){
+ efree(w->str);
+ n = w->link;
+ efree(w);
+ w = n;
+ }
+}
+
+static
+void
+freelist(Word *w)
+{
+ Word *n;
+ while(w){
+ n = w->link;
+ efree(w->str);
+ efree(w);
+ w = n;
+ }
+
+}
+
+static
+void
+pushlist(void)
+{
+ List *stack = emalloc(sizeof(*stack));
+
+ stack->word = nil;
+ stack->link = runner->args;
+
+ runner->args = stack;
+}
+
+static
+void
+poplist(void)
+{
+ List *stack = runner->args;
+ if(!stack)
+ fatal("attempted to pop an empty argument stack\n");
+
+ freelist(stack->word);
+ runner->args = stack->link;
+ efree(stack);
+}
+
+/* system interop */
+static
+Word*
+path(char *w)
+{
+ Word *path;
+
+ if(strncmp(w, "/", 1)==0
+ || strncmp(w, "./", 2)==0
+ || strncmp(w, "../", 3)==0
+ || (path = var("path")->val)==0)
+ path=&nullpath;
+
+ return path;
+}
+
+static inline
+void
+undoredirs(void)
+{
+ while(runner->redir.end != runner->redir.start)
+ Xpopredir();
+}
+
+static inline
+int
+exitsnext(void)
+{
+ Code *c = &runner->code.exe[runner->code.i];
+ while(c->f == Xpopredir)
+ c++;
+
+ return c->f == Xexit;
+}
+
+static inline
+void
+defaultsignal(void)
+{
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGTTIN, SIG_DFL);
+ signal(SIGTTOU, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+}
+
+static inline
+void
+setpid(Thread *job, int pid)
+{
+ job->pid = pid;
+ if(job->pgid <= 0){
+ job->pgid = pid;
+ addjob(job);
+ }
+
+ setpgid(pid, job->pgid);
+}
+
+/* fork/execute helpers */
+
+static inline
+void
+initchild(Thread *job, int fg)
+{
+ int pid = getpid();
+ setpid(job, pid);
+
+ if(job->flag.user){
+ if(fg)
+ tcsetpgrp(0, job->pgid);
+ else
+ job->flag.user = 0;
+ defaultsignal();
+ }
+
+ clearwait(job);
+}
+
+static inline
+void
+initparent(Thread *job, int pid, int fg)
+{
+ setpid(job, pid);
+
+ if(job->flag.user){
+ if(!fg){
+ tcsetpgrp(0, job->pgid);
+ job->flag.user = 0;
+ }
+ }
+ addwait(job, pid);
+}
+
+static
+void
+xx(void)
+{
+ popword(); // "exec"
+ if(!runner->args->word){
+ Xerror("empty argument list");
+ return;
+ }
+
+ redirect(runner->redir.end);
+ execute(runner->args->word, path(runner->args->word->str));
+ poplist();
+}
+
+static
+int
+xforkx(void)
+{
+ int n, pid;
+
+ switch(pid=fork()){
+ case -1:
+ Xerror("try again\n");
+ return -1;
+ case 0: // child
+ initchild(runner, 1);
+
+ pushword("exec");
+ xx();
+
+ exit(2); // NOTE: unreachable: xx does not return
+ default: // parent
+ initparent(runner, pid, 0);
+
+ return pid;
+ }
+}
+
+/* redirections */
+void
+pushredir(int type, int from, int to)
+{
+ Redir *r = emalloc(sizeof(*r));
+
+ r->type = type;
+ r->from = from;
+ r->to = to;
+
+ r->link = runner->redir.end, runner->redir.end = r;
+}
+
+/* byte code */
+static
+void
+run(Code *c, int pc, Var *local, int inherit)
+{
+ Thread *new = emalloc(sizeof(*new));
+
+ new->code.i = pc;
+ new->code.exe = copycode(c);
+
+ new->cmd.path = nil;
+ new->cmd.io = nil;
+
+ new->args = nil;
+ new->local = local;
+
+ new->flag.eof = 0;
+ if(runner){
+ new->pid = runner->pid;
+ new->flag.user = runner->flag.user;
+ new->redir.end = new->redir.start = runner->redir.end;
+ }else{
+ new->pid = shell.pid;
+ new->flag.user = shell.interactive;
+ new->redir.end = new->redir.start = nil;
+ }
+
+ new->wait.status = 0;
+ new->wait.len = 0;
+ new->wait.cap = 0;
+ new->wait.on = nil;
+
+ new->status = 0;
+ if(inherit)
+ new->pgid = runner->pgid;
+ else
+ new->pgid = -1;
+
+ new->line = 0;
+ new->caller = runner;
+ new->link = nil;
+
+ runner = new;
+}
+
+// -----------------------------------------------------------------------
+// exported builtins
+
+// XXX: find a better place for these
+Word*
+makeword(char *str, Word *link)
+{
+ Word *w = emalloc(sizeof(*w));
+
+ w->str = strdup(str);
+ w->link = link;
+
+ return w;
+}
+
+void
+freeword(Word *word)
+{
+ Word *n;
+
+ while(word){
+ efree(word->str);
+ n = word->link;
+ efree(word);
+ word = n;
+ }
+}
+
+int
+count(Word *w)
+{
+ int n;
+ for(n = 0; w; n++)
+ w = w->link;
+ return n;
+}
+
+// -----------------------------------------------------------------------
+// builtins
+
+void
+xecho(void)
+{
+ int fd;
+ Word *arg;
+ char *b, *s, buf[128];
+
+ fd = mapfd(1);
+ b = buf;
+
+ popword(); // echo
+
+ // TODO: controllable flags here
+ arg = runner->args->word;
+printword:
+ s = arg->str;
+ while(*s){
+ *b++ = *s++;
+ if(b == arrend(buf)-2) // always have 2 bytes available
+ write(fd, buf, arrlen(buf)-2), b = buf;
+ }
+
+ arg = arg->link;
+ if(arg){
+ *b++ = ' ';
+ goto printword;
+ }else{
+ *b++ = '\n';
+ *b++ = 0;
+ /* fallthrough */
+ }
+ write(fd, buf, b-buf);
+
+ poplist();
+}
+
+void
+xexit(void)
+{
+ Word *arg;
+
+ popword(); // exit
+ arg = runner->args->word;
+ switch(count(arg)){
+ default:
+ print(shell.err, "invalid number of arguments to exit, exiting anyways\n");
+ case 0:
+ Xexit();
+ }
+ /* unreachable */
+}
+
+void
+xcd(void)
+{
+ Word *arg;
+ Word *cdpath;
+ char dir[512];
+
+ popword(); // cd
+
+ arg = runner->args->word;
+ switch(count(arg)){
+ default:
+ print(shell.err, "usage: cd [directory]\n");
+ break;
+ case 0:
+ arg = var("home")->val;
+ if(count(arg) >= 1){
+ if(chdir(arg->str) < 0)
+ print(shell.err, "failed cd: %s\n", strerror(errno));
+ }else{
+ print(shell.err, "ambiguous cd: $home empty\n");
+ }
+ break;
+
+ case 1:
+ // TODO: add cdpath
+ cdpath = &nullpath;
+ for(; cdpath; cdpath = cdpath->link){
+ strcpy(dir, cdpath->str);
+ if(dir[0])
+ strcat(dir,"/");
+ strcat(dir, arg->str);
+ if(chdir(dir) < 0){
+ print(shell.err, "failed cd %s: %s\n", dir, strerror(errno));
+ }
+ break;
+ }
+ break;
+ }
+
+ poplist();
+}
+
+static Code dotcmd[14] =
+{
+ [0] = {.i = 0},
+ [1] = {.f = Xmark},
+ [2] = {.f = Xword},
+ [3] = {.s = "0"},
+ [4] = {.f = Xlocal},
+ [5] = {.f = Xmark},
+ [6] = {.f = Xword},
+ [7] = {.s = "*"},
+ [8] = {.f = Xlocal},
+ [9] = {.f = Xreadcmd},
+ [10] = {.f = Xunlocal},
+ [11] = {.f = Xunlocal},
+ [12] = {.f = Xreturn},
+};
+
+void
+xdot(void)
+{
+ Word *p;
+ List *argv;
+ char *base;
+ int fd, iflag = 0;
+ Thread *old;
+ char file[512];
+
+ popword(); // "."
+#if 0
+ if(proc->args->word && strcmp(proc->args->word->str, "-i")==0){
+ iflag = 1;
+ popword();
+ }
+#endif
+ /* get input file */
+ if(!runner->args->word){
+ Xerror("usage: . [-i] file [arg ...]\n");
+ return;
+ }
+
+ base = strdup(runner->args->word->str);
+ popword();
+ for(fd=-1, p=path(base); p; p = p->link){
+ strcpy(file, p->str);
+
+ if(file[0])
+ strcat(file, "/");
+ strcat(file, base);
+
+ if((fd = open(file, 0))>=0)
+ break;
+ }
+
+ if(fd<0){
+ print(shell.err, "failed open: %s: ", base);
+ return;
+ }
+ /* set up for a new command loop */
+ old = runner; // store pointer to old code
+ run(dotcmd, 1, nil, 0);
+
+ /* operations on new command stack */
+ pushredir(Rclose, fd, 0);
+ runner->cmd.path = base;
+ runner->cmd.io = openfd(fd);
+
+ /* push $* value */
+ pushlist();
+ runner->args->word = old->args->word;
+
+ /* free caller's copy of $* */
+ argv = old->args;
+ old->args = argv->link;
+ efree(argv);
+
+ /* push $0 value */
+ pushlist();
+ pushword(base);
+ //ndot++;
+}
+
+void
+xjob(void)
+{
+ int i;
+ Thread *job;
+
+ for(i=0, job = shell.jobs; job; job = job->link, i++)
+ report(job,i);
+
+ poplist();
+}
+
+void
+xfg(void)
+{
+ int i;
+ Thread *job, *old;
+
+ popword(); // fg
+
+ /* get input job id */
+ if(!runner->args->word){
+ print(shell.err, "usage: fg [pid|\%num]\n");
+ poplist();
+ return;
+ }
+
+ i = atoi(runner->args->word->str);
+ popword(); // [pid|num]
+
+ for(job=shell.jobs; i > 0; job=job->link, --i)
+ ;
+
+ poplist(); // this goes here?
+
+ wakeup(job);
+ job->caller = runner, runner = job; // XXX: can this leave zombies?
+ foreground(job, 1);
+}
+
+void
+xboot(int argc, char *argv[])
+{
+ int i;
+ Code bootstrap[32];
+ char num[12];
+
+ i = 0;
+ bootstrap[i++].i = 1;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f = Xassign;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xmark;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s="*";
+ bootstrap[i++].f = Xdollar;
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s = "/dev/stdin";
+ bootstrap[i++].f = Xword;
+ bootstrap[i++].s=".";
+ bootstrap[i++].f = Xbasic;
+ bootstrap[i++].f = Xexit;
+ bootstrap[i].i = 0;
+
+ run(bootstrap, 1, nil, 0);
+ runner->pid = runner->pgid = shell.pid;
+ pushlist(); // prime bootstrap argv
+
+ argv0 = strdup(argv[0]);
+ for(i = argc-1; i > 0; --i)
+ pushword(argv[i]);
+
+ /* main interpreter loop */
+ for(;;){
+ runner->code.i++;
+ (*runner->code.exe[runner->code.i-1].f)();
+ }
+}
+
+// -----------------------------------------------------------------------
+// exported interpreter bytecode
+
+void
+Xmark(void)
+{
+ pushlist();
+}
+
+void
+Xword(void)
+{
+ pushword(runner->code.exe[runner->code.i++].s);
+}
+
+void
+Xtrue(void)
+{
+ if(!runner->status){
+ assert(runner->wait.status == Pdone);
+ runner->code.i++;
+ deljob(runner);
+ runner->pgid = -1;
+ }else
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xfalse(void)
+{
+ if(runner->status){
+ assert(runner->wait.status == Pdone);
+ runner->code.i++;
+ deljob(runner);
+ runner->pgid = -1;
+ } else
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xgoto(void)
+{
+ runner->code.i = runner->code.exe[runner->code.i].i;
+}
+
+void
+Xfor(void)
+{
+ if(!runner->args->word){
+ poplist();
+ runner->code.i = runner->code.exe[runner->code.i].i;
+ }else{
+ freelist(runner->local->val);
+
+ runner->local->val = runner->args->word;
+ runner->local->new = 1;
+ runner->args->word = runner->args->word->link;
+
+ runner->local->val->link = nil;
+ runner->code.i++;
+ }
+
+}
+
+static
+Word*
+catlist(Word *l, Word *r, Word *tail)
+{
+ Word *w;
+ char *buf;
+
+ if(l->link || r->link)
+ tail = catlist( (!l->link)?l:l->link, (!r->link)?r:r->link, tail);
+
+ buf = emalloc(strlen(l->str)+strlen(r->str)+1);
+ strcpy(buf, l->str);
+ strcat(buf, r->str);
+
+ w = makeword(buf, tail);
+ efree(buf);
+
+ return w;
+}
+
+void
+Xconcatenate(void)
+{
+ int rn, ln;
+ Word *l = runner->args->word;
+ Word *r = runner->args->link->word;
+ Word *w = runner->args->link->link->word;
+
+ ln = count(l), rn = count(r);
+ if(ln != 0 || rn != 0) {
+ if(ln == 0 || rn == 0){
+ Xerror("null list in concatenation\n");
+ return;
+ }
+ if(ln != 1 && rn != 1 && ln != rn) {
+ Xerror("mismatched list lengths in concatenation\n");
+ return;
+ }
+ w = catlist(l, r, w);
+ }
+
+ poplist();
+ poplist();
+ runner->args->word = w;
+}
+
+void
+Xdollar(void)
+{
+ int n;
+ char *s, *t;
+ Word *a, *star;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name not singleton!\n");
+ return;
+ }
+ s = runner->args->word->str;
+ // deglob(s);
+ n = 0;
+
+ for(t = s;'0'<=*t && *t<='9';t++)
+ n = n*10+*t-'0';
+
+ a = runner->args->link->word;
+
+ if(n==0 || *t)
+ a = copywords(var(s)->val, a);
+ else{
+ star = var("*")->val;
+ if(star && 1<=n && n<=count(star)){
+ while(--n)
+ star = star->link;
+
+ a = makeword(star->str, a);
+ }
+ }
+
+ poplist();
+ runner->args->word = a;
+}
+
+static
+Word*
+cpwords(Word *array, Word *tail, int n)
+{
+ Word *cp, **end;
+
+ cp = nil, end = &cp;
+ while(n-- > 0){
+ *end = makeword(array->str, nil);
+ end = &(*end)->link;
+ array = array->link;
+ }
+ *end = tail;
+
+ return cp;
+}
+
+
+static
+Word*
+getindex(Word *array, int len, Word *index, Word *tail)
+{
+ char *s;
+ int n, m;
+ if(!index)
+ return tail;
+
+ tail = getindex(array, len, index->link, tail);
+
+ s = index->str;
+ //deglob(s)
+
+ m = 0, n = 0;
+ while('0' <= *s && *s <= '9')
+ n = 10*n + (*s++ - '0');
+ if(*s == '-'){
+ if(*++s == 0)
+ m = len - n;
+ else{
+ while('0' <= *s && *s <= '9')
+ m = 10*m + (*s++ - '0');
+ m -= n;
+ }
+ }
+
+ if(n<1 || n > len || m < 0)
+ return tail;
+ if(n+m > len)
+ m = len-n;
+ while(--n > 0)
+ array = array->link;
+ return cpwords(array, tail, m+1);
+}
+
+void
+Xindex(void)
+{
+ char *s;
+ Word *val, *ret;
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name not a singleton");
+ return;
+ }
+ s = runner->args->word->str;
+ //deglob(s)
+ val = var(s)->val;
+ poplist();
+
+ ret = runner->args->link->word; // pointer to next stack frame
+ ret = getindex(val, count(val), runner->args->word, ret);
+ poplist();
+
+ // push result back on stack
+ runner->args->word = ret;
+}
+
+void
+Xjoin(void)
+{
+ int n;
+ char *s;
+ Word *arg, *elt;
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name is not singleton\n");
+ return;
+ }
+
+ s = runner->args->word->str;
+ // deglob(s)
+
+ arg = var(s)->val;
+ poplist();
+
+ n = count(arg);
+ if(n==0){
+ pushword("");
+ return;
+ }
+
+ for(elt = arg; elt; elt=elt->link)
+ n += strlen(elt->str);
+
+ s = emalloc(n);
+ if(arg){
+ strcpy(s, arg->str);
+ for(elt = arg->link; elt; elt = elt->link){
+ strcat(s, " ");
+ strcat(s, elt->str);
+ }
+ }else
+ s[0] = 0;
+
+ pushword(s);
+ efree(s);
+}
+
+void
+Xassign(void)
+{
+ Var *v;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name not singleton!\n");
+ return;
+ }
+ //deglob(runq->argv->words->word);
+ v = var(runner->args->word->str);
+ poplist();
+
+ //globlist();
+ freewords(v->val);
+ v->val = runner->args->word;
+ v->new = 1;
+ if(v->update)
+ v->update(v);
+
+ runner->args->word = nil;
+ poplist();
+}
+
+void
+Xreadcmd(void)
+{
+ Thread *root;
+ Word *prompt;
+
+ flush(shell.err);
+ root = runner;
+
+ resetprompt();
+
+ if(yyparse()){
+ // resource cleanup?
+ if(runner->flag.eof)
+ Xreturn();
+ else
+ --root->code.i;
+ }else{
+ --root->code.i; /* re-execute Xreadcmd after codebuf runs */
+ run(compiled, 1, root->local, 0);
+ }
+
+ killzombies();
+ freeparsetree();
+}
+
+void
+Xlocal(void)
+{
+ if(count(runner->args->word)!=1){
+ Xerror("variable name must be singleton\n");
+ return;
+ }
+ //deglob(shell->args->word->str);
+
+ runner->local = makevar(strdup(runner->args->word->str), runner->local);
+ runner->local->val = copywords(runner->args->link->word, nil);
+ runner->local->new = 1;
+
+ poplist();
+ poplist();
+}
+
+void
+Xunlocal(void)
+{
+ Var *v = runner->local, *hide;
+ if(!v)
+ fatal("Xunlocal: no locals!\n", 0);
+
+ runner->local = v->link;
+ hide = var(v->name);
+ hide->new = 1;
+
+ efree(v->name);
+ freewords(v->val);
+ efree(v);
+}
+
+void
+Xasync(void)
+{
+ int pid;
+ /*
+ int null = open("/dev/null", 0);
+ if(!null){
+ Xerror("can not open /dev/null\n");
+ return;
+ }
+ */
+
+ switch(pid=fork()){
+ case -1:
+ // close(null);
+ Xerror("fork failed: try again");
+ break;
+
+ case 0: // child in background
+ initchild(runner,0);
+ /* pushredir(Ropen, null, 0); */
+
+ run(runner->code.exe, runner->code.i+1, runner->local, 0);
+ runner->caller = nil;
+ runner->flag.user = 0;
+ break;
+
+ default: // parent in foreground
+ initparent(runner,pid,1);
+ // close(null);
+
+ runner->code.i = runner->code.exe[runner->code.i].i; /* jump to end of async command */
+ /* don't wait: continue running */
+ }
+}
+
+void
+Xsubshell(void)
+{
+ int pid, user;
+
+ user = runner->flag.user;
+ switch(pid=fork()){
+ case -1:
+ Xerror("fork failed: try again");
+ break;
+
+ case 0: // child
+ initchild(runner, 1);
+ run(runner->code.exe, runner->code.i+1, runner->local, 1);
+ runner->caller = nil;
+ break;
+
+ default: // parent
+ initparent(runner, pid, 0); // relinquish control
+ waitfor(runner, pid); // wait until child finishes
+ if(user){
+ tcsetpgrp(0, shell.pid);
+ runner->flag.user = 1; // take control
+ }
+
+ runner->code.i = runner->code.exe[runner->code.i].i; // jump to end of subshell command and continue execution
+ }
+}
+
+void
+Xpipewait(void)
+{
+ foreground(runner, 0);
+}
+
+void
+Xpipe(void)
+{
+ Thread *orig;
+ int pc, pid, lfd, rfd, pfd[2];
+
+ orig = runner;
+ pc = orig->code.i;
+ lfd = orig->code.exe[pc++].i;
+ rfd = orig->code.exe[pc++].i;
+
+ if(pipe(pfd)<0){
+ Xerror("can't get pipe\n");
+ return;
+ }
+
+ switch(pid=fork()){
+ case -1:
+ Xerror("try again");
+ break;
+ case 0: // child
+ initchild(runner,1);
+
+ /* child 0 (writer) forked process */
+ run(runner->code.exe, pc+2, runner->local, 1);
+ runner->caller = nil;
+
+ close(pfd[0]);
+ pushredir(Ropen, pfd[1], lfd);
+ break;
+
+ default: // parent
+ initparent(runner,pid,0);
+
+ /* child 1 (reader) subprocess*/
+ run(runner->code.exe, runner->code.exe[pc].i, runner->local, 1);
+
+ close(pfd[1]);
+ pushredir(Ropen, pfd[0], rfd);
+
+ orig->code.i = orig->code.exe[pc+1].i;
+ break;
+ }
+}
+
+void
+Xbasic(void)
+{
+ Var *v;
+ Word *arg;
+ int pid, status;
+ struct Builtin *b;
+
+ arg = runner->args->word;
+ if(!arg){
+ Xerror("empty argument list\n");
+ return;
+ }
+
+ v = var(arg->str);
+ if(v->func){
+ return;
+ }
+
+ // see if it matches a builtin
+ for(b = builtin; b->name; b++){
+ if(strcmp(b->name, arg->str)==0){
+ b->func();
+ return;
+ }
+ }
+
+ /* if we are here then it's an external command */
+ if(exitsnext()){ // if we exit immediately, no need to fork
+ pushword("exec");
+ xx();
+ Xexit();
+ }
+
+ // run the external command
+ if((pid = xforkx()) < 0) {
+ Xerror("try again");
+ return;
+ }
+
+ poplist();
+ foreground(runner, 0); // waits for child
+}
+
+void
+Xcount(void)
+{
+ Word *arg;
+ char *str, num[12];
+
+ if(count(runner->args->word) != 1){
+ Xerror("variable name not a singleton\n");
+ return;
+ }
+
+ str = runner->args->word->str;
+ arg = var(str)->val;
+ poplist();
+
+ itoa(num, count(arg));
+ pushword(num);
+}
+
+void
+Xflat(void)
+{
+ int len;
+ char *str;
+ Word *arg, *a;
+
+ if(count(runner->args->word)!=1){
+ Xerror("variable name is not a singleton\n");
+ return;
+ }
+
+ str = runner->args->word->str;
+ arg = var(str)->val;
+ poplist();
+
+ len = count(arg);
+ if(!len){
+ pushword("");
+ return;
+ }
+
+ for(a=arg; a; a=a->link)
+ len += strlen(a->str);
+
+ str = emalloc(len);
+ if(arg){
+ strcpy(str, arg->str);
+ for(a = arg->link; a; a = a->link){
+ strcat(str," ");
+ strcat(str,a->str);
+ }
+ }else
+ str[0] = 0;
+
+ pushword(str);
+ efree(str);
+}
+
+void
+Xbang(void)
+{
+ if(runner->status)
+ runner->status = 0;
+ else
+ runner->status = 1;
+}
+
+void
+Xpopredir(void)
+{
+ Redir *r = runner->redir.end;
+ if(!r)
+ fatal("attempted to pop a nil redir\n");
+
+ runner->redir.end = runner->redir.end->link;
+ if(r->type==Ropen)
+ close(r->from);
+
+ efree(r);
+}
+
+void
+Xreturn(void)
+{
+ Thread *curr = runner;
+
+ switch(curr->wait.status){
+ /*
+ * If our job is still running or suspended we must:
+ * 1. move program one step back to rerun Xreturn upon recall
+ * 2. return to our calling thread
+ * 3. don't free!
+ */
+ case Prun:
+ report(curr, 0);
+ curr->flag.user = 0;
+ case Pstop:
+ curr->code.i--;
+ runner = curr->caller;
+ curr->caller = nil; // detach job
+ return;
+ /*
+ * If our job has finished:
+ * 1. remove from our list
+ * 2. continue to clean up its memory
+ */
+ case Pdone:
+ deljob(curr);
+ /* fallthrough */
+ default:
+ ;
+ }
+
+ undoredirs();
+
+ while(curr->args)
+ poplist();
+ freecode(curr->code.exe);
+ efree(curr->wait.on);
+
+ runner = curr->caller;
+ efree(curr);
+ if(!runner)
+ exit(0);
+}
+
+void
+Xexit(void)
+{
+ exit(runner->status);
+}
+
+void
+Xerror(char *msg)
+{
+ print(shell.err, "rc: %s", msg);
+ flush(shell.err);
+ while(!runner->flag.user)
+ Xreturn();
+}
+