# # 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 ref Sys->Exception) { if (ec != nil || xc != nil) { spawn waiter(e, pid, wc); if (ec == nil) ec = chan of ref Sys->Exception; if (xc == nil) xc = chan of ref Sys->Exception; alt { <-wc => return; x := <-ec => <-wc; exitmash(); x := <-xc => <-wc; s := x.name; if (len s < FAILLEN || s[0:FAILLEN] != FAIL) s = FAIL + s; sys->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 (err == ENOENT && 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 ref Sys->Exception) { (pid, console) := prepareio(in, out); wc <-= pid; if (pid < 0) return; x := ref Sys->Exception; cmd := hd args; if (sys->rescue(FAILPAT, x) == Sys->EXCEPTION) { 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; } if (sys->rescue(EPIPE, x) == Sys->EXCEPTION) { if (xc != nil) xc <-= x; #else # sys->fprint(console, "%s: %s\n", cmd, x.name); exit; } if (sys->rescue(EXIT, x) == Sys->EXCEPTION) { if (ec != nil) ec <-= x; exit; } 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 (err == ENOENT && 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); } } # # 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, 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 ref Sys->Exception; if (wait && (e.flags & ETop)) ec := chan of ref Sys->Exception; spawn mkprog(a, e, infd, outfd, wc, ec, xc); pid := <-wc; if (wait) waitfor(e, pid, wc, ec, xc); }