diff options
Diffstat (limited to 'appl/cmd/mash/exec.b')
| -rw-r--r-- | appl/cmd/mash/exec.b | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/appl/cmd/mash/exec.b b/appl/cmd/mash/exec.b new file mode 100644 index 00000000..faa003b3 --- /dev/null +++ b/appl/cmd/mash/exec.b @@ -0,0 +1,401 @@ +# +# Manage the execution of a command. +# + +srv: string; # srv file proto +nsrv: int = 0; # srv file unique id + +# +# Return error string. +# +errstr(): string +{ + return sys->sprint("%r"); +} + +# +# Server thread for servefd. +# +server(c: ref Sys->FileIO, fd: ref Sys->FD, write: int) +{ + a: array of byte; + if (!write) + a = array[Sys->ATOMICIO] of byte; + for (;;) { + alt { + (nil, b, nil, wc) := <- c.write => + if (wc == nil) + return; + if (!write) { + wc <- = (0, EPIPE); + return; + } + r := sys->write(fd, b, len b); + if (r < 0) { + wc <- = (0, errstr()); + return; + } + wc <- = (r, nil); + (nil, n, nil, rc) := <- c.read => + if (rc == nil) + return; + if (write) { + rc <- = (array[0] of byte, nil); + return; + } + if (n > Sys->ATOMICIO) + n = Sys->ATOMICIO; + r := sys->read(fd, a, n); + if (r < 0) { + rc <- = (nil, errstr()); + return; + } + rc <- = (a[0:r], nil); + } + } +} + +# +# Serve FD as a #s file. Used to implement generators. +# +Env.servefd(e: self ref Env, fd: ref Sys->FD, write: int): string +{ + (s, c) := e.servefile(nil); + spawn server(c, fd, write); + return s; +} + +# +# Generate name and FileIO adt for a served filed. +# +Env.servefile(e: self ref Env, n: string): (string, ref Sys->FileIO) +{ + c: ref Sys->FileIO; + s: string; + if (srv == nil) { + (ok, d) := sys->stat(CHAN); + if (ok < 0) + e.couldnot("stat", CHAN); + if (d.dtype != 's') { + if (sys->bind("#s", CHAN, Sys->MBEFORE) < 0) + e.couldnot("bind", CHAN); + } + srv = "mash." + string sys->pctl(0, nil); + } + retry := 0; + for (;;) { + if (retry || n == nil) + s = srv + "." + string nsrv++; + else + s = n; + c = sys->file2chan(CHAN, s); + s = CHAN + "/" + s; + if (c == nil) { + if (retry || n == nil || errstr() != EEXISTS) + e.couldnot("file2chan", s); + retry = 1; + continue; + } + break; + } + if (n != nil) + n = CHAN + "/" + n; + else + n = s; + if (retry && sys->bind(s, n, Sys->MREPL) < 0) + e.couldnot("bind", n); + return (n, c); +} + +# +# Shorthand for string output. +# +Env.output(e: self ref Env, s: string) +{ + if (s == nil) + return; + out := e.outfile(); + if (out == nil) + return; + out.puts(s); + out.close(); +} + +# +# Return Iobuf for stdout. +# +Env.outfile(e: self ref Env): ref Bufio->Iobuf +{ + fd := e.out; + if (fd == nil) + fd = sys->fildes(1); + out := bufio->fopen(fd, Bufio->OWRITE); + if (out == nil) + e.report(sys->sprint("fopen failed: %r")); + return out; +} + +# +# Return FD for /dev/null. +# +Env.devnull(e: self ref Env): ref Sys->FD +{ + fd := sys->open(DEVNULL, Sys->OREAD); + if (fd == nil) + e.couldnot("open", DEVNULL); + return fd; +} + +# +# Make a pipe. +# +Env.pipe(e: self ref Env): array of ref Sys->FD +{ + fds := array[2] of ref Sys->FD; + if (sys->pipe(fds) < 0) { + e.report(sys->sprint("pipe failed: %r")); + return nil; + } + return fds; +} + +# +# Open wait file for an env. +# +waitfd(e: ref Env) +{ + w := "#p/" + string sys->pctl(0, nil) + "/wait"; + fd := sys->open(w, sys->OREAD); + if (fd == nil) + e.couldnot("open", w); + e.wait = fd; +} + +# +# Wait for a thread. Perhaps propagate exception or exit. +# +waitfor(e: ref Env, pid: int, wc: chan of int, ec, xc: chan of string) +{ + if (ec != nil || xc != nil) { + spawn waiter(e, pid, wc); + if (ec == nil) + ec = chan of string; + if (xc == nil) + xc = chan of string; + alt { + <-wc => + return; + x := <-ec => + <-wc; + exitmash(); + x := <-xc => + <-wc; + s := x; + if (len s < FAILLEN || s[0:FAILLEN] != FAIL) + s = FAIL + s; + raise s; + } + } else + waiter(e, pid, nil); +} + +# +# Wait for a specific pid. +# +waiter(e: ref Env, pid: int, wc: chan of int) +{ + buf := array[sys->WAITLEN] of byte; + for(;;) { + n := sys->read(e.wait, buf, len buf); + if (n < 0) { + e.report(sys->sprint("read wait: %r\n")); + break; + } + status := string buf[0:n]; + if (status[len status - 1] != ':') + sys->fprint(e.stderr, "%s\n", status); + who := int status; + if (who != 0 && who == pid) + break; + } + if (wc != nil) + wc <-= 0; +} + +# +# Preparse IO for a new thread. +# Make a new FD group and redirect stdin/stdout. +# +prepareio(in, out: ref sys->FD): (int, ref Sys->FD) +{ + fds := list of { 0, 1, 2}; + if (in != nil) + fds = in.fd :: fds; + if (out != nil) + fds = out.fd :: fds; + pid := sys->pctl(sys->NEWFD, fds); + console := sys->fildes(2); + if (in != nil) { + sys->dup(in.fd, 0); + in = nil; + } + if (out != nil) { + sys->dup(out.fd, 1); + out = nil; + } + return (pid, console); +} + +# +# Add ".dis" to a command if missing. +# +dis(s: string): string +{ + if (len s < 4 || s[len s - 4:] != ".dis") + return s + ".dis"; + return s; +} + +# +# Load a builtin. +# +Env.doload(e: self ref Env, s: string) +{ + file := dis(s); + l := load Mashbuiltin file; + if (l == nil) { + err := errstr(); + if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") { + l = load Mashbuiltin LIB + file; + if (l == nil) + err = errstr(); + } + if (l == nil) { + e.report(s + ": " + err); + return; + } + } + l->mashinit("load" :: s :: nil, lib, l, e); +} + +# +# Execute a spawned thread (dis module or builtin). +# +mkprog(args: list of string, e: ref Env, in, out: ref Sys->FD, wc: chan of int, ec, xc: chan of string) +{ + (pid, console) := prepareio(in, out); + wc <-= pid; + if (pid < 0) + return; + cmd := hd args; + { + b := e.builtin(cmd); + if (b != nil) { + e = e.copy(); + e.in = in; + e.out = out; + e.stderr = console; + e.wait = nil; + b->mashcmd(e, args); + } else { + file := dis(cmd); + c := load Command file; + if (c == nil) { + err := errstr(); + if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") { + c = load Command "/dis/" + file; + if (c == nil) + err = errstr(); + } + if (c == nil) { + sys->fprint(console, "%s: %s\n", file, err); + return; + } + } + c->init(gctxt, args); + } + }exception x{ + FAILPAT => + if (xc != nil) + xc <-= x; + # the command failure should be propagated silently to + # a higher level, where $status can be set.. - wrtp. + #else + # sys->fprint(console, "%s: %s\n", cmd, x.name); + exit; + EPIPE => + if (xc != nil) + xc <-= x; + #else + # sys->fprint(console, "%s: %s\n", cmd, x.name); + exit; + EXIT => + if (ec != nil) + ec <-= x; + exit; + } +} + +# +# Open/create files for redirection. +# +redirect(e: ref Env, f: array of string, in, out: ref Sys->FD): (int, ref Sys->FD, ref Sys->FD) +{ + s: string; + err := 0; + if (f[Rinout] != nil) { + s = f[Rinout]; + in = sys->open(s, Sys->ORDWR); + if (in == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + out = in; + } else if (f[Rin] != nil) { + s = f[Rin]; + in = sys->open(s, Sys->OREAD); + if (in == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + } + if (f[Rout] != nil || f[Rappend] != nil) { + if (f[Rappend] != nil) { + s = f[Rappend]; + out = sys->open(s, Sys->OWRITE); + if (out != nil) + sys->seek(out, big 0, Sys->SEEKEND); + } else { + s = f[Rout]; + out = nil; + } + if (out == nil) { + out = sys->create(s, Sys->OWRITE, 8r666); + if (out == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + } + } + if (err) + return (0, nil, nil); + return (1, in, out); +} + +# +# Spawn a command and maybe wait for it. +# +exec(a: list of string, e: ref Env, infd, outfd: ref Sys->FD, wait: int) +{ + if (wait && e.wait == nil) + waitfd(e); + wc := chan of int; + if (wait && (e.flags & ERaise)) + xc := chan of string; + if (wait && (e.flags & ETop)) + ec := chan of string; + spawn mkprog(a, e, infd, outfd, wc, ec, xc); + pid := <-wc; + if (wait) + waitfor(e, pid, wc, ec, xc); +} |
