From ce05175372a9ddca1a225db0765ace1127a39293 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 12 Nov 2021 09:22:01 -0800 Subject: chore: simplified organizational structure --- src/cmd/rc/exec.c | 1267 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1267 insertions(+) create mode 100644 src/cmd/rc/exec.c (limited to 'src/cmd/rc/exec.c') 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 + +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(); +} + -- cgit v1.2.1