#include "rc.h" #include "exec.h" #include 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(); }