summaryrefslogtreecommitdiff
path: root/appl/cmd/sh/std.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/sh/std.b')
-rw-r--r--appl/cmd/sh/std.b812
1 files changed, 812 insertions, 0 deletions
diff --git a/appl/cmd/sh/std.b b/appl/cmd/sh/std.b
new file mode 100644
index 00000000..6a944614
--- /dev/null
+++ b/appl/cmd/sh/std.b
@@ -0,0 +1,812 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+include "filepat.m";
+ filepat: Filepat;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+builtinnames := array[] of {
+ "if", "while", "~", "!", "apply", "for",
+ "status", "pctl", "fn", "subfn", "and", "or",
+ "raise", "rescue", "flag", "getlines", "no",
+};
+
+sbuiltinnames := array[] of {
+ "hd", "tl", "index", "split", "join", "pid", "parse", "env", "pipe",
+};
+
+initbuiltin(ctxt: ref Context, shmod: Sh): string
+{
+ sys = load Sys Sys->PATH;
+ sh = shmod;
+ myself = load Shellbuiltin "$self";
+ if (myself == nil)
+ ctxt.fail("bad module", sys->sprint("std: cannot load self: %r"));
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil)
+ ctxt.fail("bad module",
+ sys->sprint("std: cannot load: %s: %r", Filepat->PATH));
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ ctxt.fail("bad module",
+ sys->sprint("std: cannot load: %s: %r", Bufio->PATH));
+ names := builtinnames;
+ for (i := 0; i < len names; i++)
+ ctxt.addbuiltin(names[i], myself);
+ names = sbuiltinnames;
+ for (i = 0; i < len names; i++)
+ ctxt.addsbuiltin(names[i], myself);
+ env := ctxt.envlist();
+ for (; env != nil; env = tl env) {
+ (name, val) := hd env;
+ if (len name > 3 && name[0:3] == "fn-")
+ fndef(ctxt, name[3:], val, 0);
+ if (len name > 4 && name[0:4] == "sfn-")
+ fndef(ctxt, name[4:], val, 1);
+ }
+ return nil;
+}
+
+whatis(c: ref Sh->Context, sh: Sh, name: string, wtype: int): string
+{
+ ename, fname: string;
+ case wtype {
+ BUILTIN =>
+ (ename, fname) = ("fn-", "fn ");
+ SBUILTIN =>
+ (ename, fname) = ("sfn-", "subfn ");
+ OTHER =>
+ return nil;
+ }
+
+ val := c.get(ename + name);
+ if (val != nil)
+ return fname + name + " " + sh->quoted(hd val :: nil, 0);
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode, last: int): string
+{
+ status: string;
+ name := (hd cmd).word;
+ val := c.get("fn-" + name);
+ if (val != nil)
+ return c.run(hd val :: tl cmd, last);
+ case name {
+ "if" => status = builtin_if(c, cmd, last);
+ "while" => status = builtin_while(c, cmd, last);
+ "and" => status = builtin_and(c, cmd, last);
+ "apply" => status = builtin_apply(c, cmd, last);
+ "for" => status = builtin_for(c, cmd, last);
+ "or" => status = builtin_or(c, cmd, last);
+ "!" => status = builtin_not(c, cmd, last);
+ "fn" => status = builtin_fn(c, cmd, last, 0);
+ "subfn" => status = builtin_fn(c, cmd, last, 1);
+ "~" => status = builtin_twiddle(c, cmd, last);
+ "status" => status = builtin_status(c, cmd, last);
+ "pctl" => status = builtin_pctl(c, cmd, last);
+ "raise" => status = builtin_raise(c, cmd, last);
+ "rescue" => status = builtin_rescue(c, cmd, last);
+ "flag" => status = builtin_flag(c, cmd, last);
+ "getlines" => status = builtin_getlines(c, cmd, last);
+ "no" => status = builtin_no(c, cmd, last);
+ }
+ return status;
+}
+
+runsbuiltin(c: ref Sh->Context, nil: Sh,
+ cmd: list of ref Sh->Listnode): list of ref Listnode
+{
+ name := (hd cmd).word;
+ val := c.get("sfn-" + name);
+ if (val != nil)
+ return runsubfn(c, val, tl cmd);
+ case name {
+ "pid" =>
+ return ref Listnode(nil, string sys->pctl(0, nil)) :: nil;
+ "hd" =>
+ if (tl cmd == nil)
+ return nil;
+ return hd tl cmd :: nil;
+ "tl" =>
+ if (tl cmd == nil)
+ return nil;
+ return tl tl cmd;
+ "index" =>
+ return sbuiltin_index(c, cmd);
+ "split" =>
+ return sbuiltin_split(c, cmd);
+ "join" =>
+ return sbuiltin_join(c, cmd);
+ "parse" =>
+ return sbuiltin_parse(c, cmd);
+ "env" =>
+ return sbuiltin_env(c, cmd);
+ "pipe" =>
+ return sbuiltin_pipe(c, cmd);
+ }
+ return nil;
+}
+
+runsubfn(ctxt: ref Context, body, args: list of ref Listnode): list of ref Listnode
+{
+ if (body == nil)
+ return nil;
+ ctxt.push();
+ {
+ ctxt.setlocal("result", nil);
+ ctxt.run(hd body :: args, 0);
+ result := ctxt.get("result");
+ ctxt.pop();
+ return result;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ raise e;
+ }
+}
+
+sbuiltin_index(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode
+{
+ if (len val < 2 || (hd tl val).word == nil)
+ builtinusage(ctxt, "index num list");
+ k := int (hd tl val).word - 1;
+ val = tl tl val;
+ for (; k > 0 && val != nil; k--)
+ val = tl val;
+ if (val != nil)
+ val = hd val :: nil;
+ return val;
+}
+
+# return a parsed version of a string, raising a "parse error" exception if
+# it fails. the string must be a braced command block.
+sbuiltin_parse(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ if (len args != 2)
+ builtinusage(ctxt, "parse arg");
+ args = tl args;
+ if ((hd args).cmd != nil)
+ return ref Listnode((hd args).cmd, nil) :: nil;
+ w := (hd args).word;
+ if (w == nil || w[0] != '{') #}
+ ctxt.fail("parse error", "parse: argument must be a braced block");
+ (n, err) := sh->parse(w);
+ if (err != nil)
+ ctxt.fail("parse error", "parse: " + err);
+ return ref Listnode(n, nil) :: nil;
+}
+
+sbuiltin_env(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
+{
+ vl: list of string;
+ for (e := ctxt.envlist(); e != nil; e = tl e) {
+ (n, v) := hd e;
+ if (v != nil) # XXX this is debatable... someone might want to see null local vars.
+ vl = n :: vl;
+ }
+ return sh->stringlist2list(vl);
+}
+
+word(n: ref Listnode): string
+{
+ if (n.word != nil)
+ return n.word;
+ if (n.cmd != nil)
+ n.word = sh->cmd2string(n.cmd);
+ return n.word;
+}
+
+# usage: split [separators] value
+sbuiltin_split(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ n := len args;
+ if (n < 2 || n > 3)
+ builtinusage(ctxt, "split [separators] value");
+ seps: string;
+ if (n == 2) {
+ ifs := ctxt.get("ifs");
+ if (ifs == nil)
+ ctxt.fail("usage", "split: $ifs not set");
+ seps = word(hd ifs);
+ } else {
+ args = tl args;
+ seps = word(hd args);
+ }
+ (nil, toks) := sys->tokenize(word(hd tl args), seps);
+ return sh->stringlist2list(toks);
+}
+
+sbuiltin_join(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, "join separator [arg...]");
+ seps := word(hd args);
+ if (tl args == nil)
+ return ref Listnode(nil, nil) :: nil;
+ s := word(hd tl args);
+ for (args = tl tl args; args != nil; args = tl args)
+ s += seps + word(hd args);
+ return ref Listnode(nil, s) :: nil;
+}
+
+builtin_fn(ctxt: ref Context, args: list of ref Listnode, nil: int, issub: int): string
+{
+ n := len args;
+ title := (hd args).word;
+ if (n < 2)
+ builtinusage(ctxt, title + " [name...] [{body}]");
+ for (al := tl args; tl al != nil; al = tl al)
+ if ((hd al).cmd != nil)
+ builtinusage(ctxt, title + " [name...] [{body}]");
+ if ((hd al).cmd != nil) {
+ cmd := hd al :: nil;
+ for (al = tl args; tl al != nil; al = tl al)
+ fndef(ctxt, (hd al).word, cmd, issub);
+ } else {
+ for (al = tl args; al != nil; al = tl al)
+ fnundef(ctxt, (hd al).word, issub);
+ }
+ return nil;
+}
+
+fndef(ctxt: ref Context, name: string, cmd: list of ref Listnode, issub: int)
+{
+ if (cmd == nil)
+ return;
+ if (issub) {
+ ctxt.set("sfn-" + name, cmd);
+ ctxt.addsbuiltin(name, myself);
+ } else {
+ ctxt.set("fn-" + name, cmd);
+ ctxt.addbuiltin(name, myself);
+ }
+}
+
+fnundef(ctxt: ref Context, name: string, issub: int)
+{
+ if (issub) {
+ ctxt.set("sfn-" + name, nil);
+ ctxt.removesbuiltin(name, myself);
+ } else {
+ ctxt.set("fn-" + name, nil);
+ ctxt.removebuiltin(name, myself);
+ }
+}
+
+builtin_flag(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ n := len args;
+ if (n < 2 || n > 3 || len (hd tl args).word != 1)
+ builtinusage(ctxt, "flag [vxei] [+-]");
+ flag := (hd tl args).word[0];
+ p := "";
+ if (n == 3)
+ p = (hd tl tl args).word;
+ mask := 0;
+ case flag {
+ 'v' => mask = Context.VERBOSE;
+ 'x' => mask = Context.EXECPRINT;
+ 'e' => mask = Context.ERROREXIT;
+ 'i' => mask = Context.INTERACTIVE;
+ * => builtinusage(ctxt, "flag [vxei] [+-]");
+ }
+ case p {
+ "" => if (ctxt.options() & mask)
+ return nil;
+ return "not set";
+ "-" => ctxt.setoptions(mask, 0);
+ "+" => ctxt.setoptions(mask, 1);
+ * => builtinusage(ctxt, "flag [vxei] [+-]");
+ }
+ return nil;
+}
+
+builtin_no(nil: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args != nil)
+ return "yes";
+ return nil;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '{');
+}
+
+builtin_if(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ args = tl args;
+ nargs := len args;
+ if (nargs < 2)
+ builtinusage(ctxt, "if {cond} {action} [{cond} {action}]... [{elseaction}]");
+
+ status: string;
+ dolstar := ctxt.get("*");
+ while (args != nil) {
+ cmd: ref Listnode = nil;
+ if (tl args == nil) {
+ cmd = hd args;
+ args = tl args;
+ } else {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]");
+
+ status = ctxt.run(hd args :: dolstar, 0);
+ if (status == nil) {
+ cmd = hd tl args;
+ args = nil;
+ } else
+ args = tl tl args;
+ setstatus(ctxt, status);
+ }
+ if (cmd != nil) {
+ if (!iscmd(cmd))
+ builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]");
+
+ status = ctxt.run(cmd :: dolstar, 0);
+ }
+ }
+ return status;
+}
+
+builtin_or(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ s: string;
+ dolstar := ctxt.get("*");
+ for (args = tl args; args != nil; args = tl args) {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "or [{cmd} ...]");
+ if ((s = ctxt.run(hd args :: dolstar, 0)) == nil)
+ return nil;
+ else
+ setstatus(ctxt, s);
+ }
+ return s;
+}
+
+builtin_and(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ dolstar := ctxt.get("*");
+ for (args = tl args; args != nil; args = tl args) {
+ if (!iscmd(hd args))
+ builtinusage(ctxt, "and [{cmd} ...]");
+ if ((s := ctxt.run(hd args :: dolstar, 0)) != nil)
+ return s;
+ else
+ setstatus(ctxt, nil);
+ }
+ return nil;
+}
+
+builtin_while(ctxt: ref Context, args: list of ref Listnode, nil: int) : string
+{
+ args = tl args;
+ if (len args != 2 || !iscmd(hd args) || !iscmd(hd tl args))
+ builtinusage(ctxt, "while {condition} {cmd}");
+
+ dolstar := ctxt.get("*");
+ cond := hd args :: dolstar;
+ action := hd tl args :: dolstar;
+ status := "";
+
+ for(;;){
+ {
+ while (ctxt.run(cond, 0) == nil)
+ status = setstatus(ctxt, ctxt.run(action, 0));
+ return status;
+ } exception e{
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ }
+ }
+}
+
+builtin_getlines(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ argv = tl argv;
+ seps := "\n";
+ if (n == 3) {
+ seps = word(hd argv);
+ argv = tl argv;
+ }
+ if (len seps == 0)
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ if (!iscmd(hd argv))
+ builtinusage(ctxt, "getlines [separators] {cmd}");
+ cmd := hd argv :: ctxt.get("*");
+ stdin := bufio->fopen(sys->fildes(0), Sys->OREAD);
+ if (stdin == nil)
+ ctxt.fail("bad input", sys->sprint("getlines: cannot open stdin: %r"));
+ status := "";
+ ctxt.push();
+ for(;;){
+ {
+ for (;;) {
+ s: string;
+ if (len seps == 1)
+ s = stdin.gets(seps[0]);
+ else
+ s = stdin.gett(seps);
+ if (s == nil)
+ break;
+ # make sure we don't lose the last unterminated line
+ lastc := s[len s - 1];
+ if (lastc == seps[0])
+ s = s[0:len s - 1];
+ else for (i := 1; i < len seps; i++) {
+ if (lastc == seps[i]) {
+ s = s[0:len s - 1];
+ break;
+ }
+ }
+ ctxt.setlocal("line", ref Listnode(nil, s) :: nil);
+ status = setstatus(ctxt, ctxt.run(cmd, 0));
+ }
+ ctxt.pop();
+ return status;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ if (loopexcept(e) == BREAK)
+ return status;
+ ctxt.push();
+ }
+ }
+}
+
+# usage: raise [name]
+builtin_raise(ctxt: ref Context, args: list of ref Listnode, nil: int) : string
+{
+ ename: ref Listnode;
+ if (tl args == nil) {
+ e := ctxt.get("exception");
+ if (e == nil)
+ ctxt.fail("bad raise context", "raise: no exception found");
+ ename = (hd e);
+ } else
+ ename = hd tl args;
+ if (ename.word == nil && ename.cmd != nil)
+ ctxt.fail("bad raise context", "raise: bad exception name");
+ xraise("fail:" + ename.word);
+ return nil;
+}
+
+# usage: rescue pattern rescuecmd cmd
+builtin_rescue(ctxt: ref Context, args: list of ref Listnode, last: int) : string
+{
+ args = tl args;
+ if (len args != 3 || !iscmd(hd tl args) || !iscmd(hd tl tl args))
+ builtinusage(ctxt, "rescue pattern {rescuecmd} {cmd}");
+ if ((hd args).word == nil && (hd args).cmd != nil)
+ ctxt.fail("usage", "rescue: bad pattern");
+ dolstar := ctxt.get("*");
+ handler := hd tl args :: dolstar;
+ code := hd tl tl args :: dolstar;
+ {
+ return ctxt.run(code, 0);
+ } exception e {
+ "fail:*" =>
+ ctxt.push();
+ ctxt.set("exception", ref Listnode(nil, e[5:]) :: nil);
+ {
+ status := ctxt.run(handler, last);
+ ctxt.pop();
+ return status;
+ } exception e2{
+ "fail:*" =>
+ ctxt.pop();
+ raise e;
+ }
+ }
+}
+
+builtin_not(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ # syntax: ! cmd [args...]
+ args = tl args;
+ if (args == nil || ctxt.run(args, last) == nil)
+ return "false";
+ return "";
+}
+
+builtin_for(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ Usage: con "for var in [item...] {cmd}";
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, Usage);
+ var := (hd args).word;
+ if (var == nil)
+ ctxt.fail("bad assign", "for: bad variable name");
+ args = tl args;
+ if (args == nil || (hd args).word != "in")
+ builtinusage(ctxt, Usage);
+ args = tl args;
+ if (args == nil)
+ builtinusage(ctxt, Usage);
+ for (eargs := args; tl eargs != nil; eargs = tl eargs)
+ ;
+ cmd := hd eargs;
+ if (!iscmd(cmd))
+ builtinusage(ctxt, Usage);
+
+ status := "";
+ dolstar := ctxt.get("*");
+ for(;;){
+ {
+ for (; tl args != nil; args = tl args) {
+ ctxt.setlocal(var, hd args :: nil);
+ status = setstatus(ctxt, ctxt.run(cmd :: dolstar, 0));
+ }
+ return status;
+ } exception e {
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ args = tl args;
+ }
+ }
+}
+
+CONTINUE, BREAK: con iota;
+loopexcept(ename: string): int
+{
+ case ename[5:] {
+ "break" =>
+ return BREAK;
+ "continue" =>
+ return CONTINUE;
+ * =>
+ raise ename;
+ }
+ return 0;
+}
+
+builtin_apply(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ args = tl args;
+ if (args == nil || !iscmd(hd args))
+ builtinusage(ctxt, "apply {cmd} [val...]");
+
+ status := "";
+ cmd := hd args;
+ for(;;){
+ {
+ for (args = tl args; args != nil; args = tl args)
+ status = setstatus(ctxt, ctxt.run(cmd :: hd args :: nil, 0));
+
+ return status;
+ } exception e{
+ "fail:*" =>
+ if (loopexcept(e) == BREAK)
+ return status;
+ }
+ }
+}
+
+builtin_status(nil: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args != nil)
+ return (hd tl args).word;
+ return "";
+}
+
+pctlnames := array[] of {
+ ("newfd", Sys->NEWFD),
+ ("forkfd", Sys->FORKFD),
+ ("newns", Sys->NEWNS),
+ ("forkns", Sys->FORKNS),
+ ("newpgrp", Sys->NEWPGRP),
+ ("nodevs", Sys->NODEVS)
+};
+
+builtin_pctl(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ if (len argv < 2)
+ builtinusage(ctxt, "pctl option... [fdnum...]");
+
+ finalmask := 0;
+ fdlist: list of int;
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ w := (hd argv).word;
+ if (isnum(w))
+ fdlist = int w :: fdlist;
+ else {
+ for (i := 0; i < len pctlnames; i++) {
+ (name, mask) := pctlnames[i];
+ if (name == w) {
+ finalmask |= mask;
+ break;
+ }
+ }
+ if (i == len pctlnames)
+ ctxt.fail("usage", "pctl: unknown flag " + w);
+ }
+ }
+ sys->pctl(finalmask, fdlist);
+ return nil;
+}
+
+# usage: ~ value pattern...
+builtin_twiddle(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+{
+ argv = tl argv;
+ if (argv == nil)
+ builtinusage(ctxt, "~ word [pattern...]");
+ if (tl argv == nil)
+ return "no match";
+ w := word(hd argv);
+
+ for (argv = tl argv; argv != nil; argv = tl argv)
+ if (filepat->match(word(hd argv), w))
+ return "";
+
+ return "no match";
+}
+
+#builtin_echo(ctxt: ref Context, argv: list of ref Listnode, nil: int): string
+#{
+# argv = tl argv;
+# nflag := 0;
+# if (argv != nil && word(hd argv) == "-n") {
+# nflag = 1;
+# argv = tl argv;
+# }
+# s: string;
+# if (argv != nil) {
+# s = word(hd argv);
+# for (argv = tl argv; argv != nil; argv = tl argv)
+# s += " " + word(hd argv);
+# }
+# e: int;
+# if (nflag)
+# e = sys->print("%s", s);
+# else
+# e = sys->print("%s\n", s);
+# if (e == -1) {
+# err := sys->sprint("%r");
+# if (ctxt.options() & ctxt.VERBOSE)
+# sys->fprint(sys->fildes(2), "echo: write error: %s\n", err);
+# return err;
+# }
+# return nil;
+#}
+
+ENOEXIST: con "file does not exist";
+TMPDIR: con "/tmp/pipes";
+sbuiltin_pipe(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ n: int;
+ if (len argv != 3 || !iscmd(hd tl tl argv))
+ builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}");
+ s := (hd tl argv).word;
+ case s {
+ "from" =>
+ n = 1;
+ "to" =>
+ n = 0;
+ * =>
+ if (!isnum(s))
+ builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}");
+ n = int s;
+ }
+ pipeid := ctxt.get("pipeid");
+ seq: int;
+ if (pipeid == nil)
+ seq = 0;
+ else
+ seq = int (hd pipeid).word;
+ id := "pipe." + string sys->pctl(0, nil) + "." + string seq;
+ ctxt.set("pipeid", ref Listnode(nil, string ++seq) :: nil);
+ mkdir(TMPDIR);
+ d := "/tmp/" + id + "d";
+ if (mkdir(d) == -1)
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot make %s: %r", d));
+ if (sys->bind("#|", d, Sys->MREPL) == -1) {
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe onto %s: %r", d));
+ }
+ if (rename(d + "/data", id + "x") == -1 || rename(d + "/data1", id + "y")) {
+ sys->unmount(nil, d);
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot rename pipe: %r"));
+ }
+ if (sys->bind(d, TMPDIR, Sys->MBEFORE) == -1) {
+ sys->unmount(nil, d);
+ sys->remove(d);
+ ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe dir: %r"));
+ }
+ sys->unmount(nil, d);
+ sys->remove(d);
+ sync := chan of string;
+ spawn runpipe(sync, ctxt, n, TMPDIR + "/" + id + "x", hd tl tl argv);
+ if ((e := <-sync) != nil)
+ ctxt.fail("bad pipe", e);
+ return ref Listnode(nil, TMPDIR + "/" + id + "y") :: nil;
+}
+
+mkdir(f: string): int
+{
+ if (sys->create(f, Sys->OREAD, Sys->DMDIR | 8r777) == nil)
+ return -1;
+ return 0;
+}
+
+runpipe(sync: chan of string, ctxt: ref Context, fdno: int, p: string, cmd: ref Listnode)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ ctxt = ctxt.copy(1);
+ if ((fd := sys->open(p, Sys->ORDWR)) == nil) {
+ sync <-= sys->sprint("cannot open %s: %r", p);
+ exit;
+ }
+ sys->dup(fd.fd, fdno);
+ fd = nil;
+ sync <-= nil;
+ ctxt.run(cmd :: ctxt.get("*"), 1);
+}
+
+rename(x, y: string): int
+{
+ (ok, nil) := sys->stat(x);
+ if (ok == -1)
+ return -1;
+ inf := sys->nulldir;
+ inf.name = y;
+ if (sys->wstat(x, inf) == -1)
+ return -1;
+ return 0;
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "usage: " + s);
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+# same as sys->raise(), but check that length of error string is
+# acceptable, and truncate as appropriate.
+xraise(s: string)
+{
+ d := array of byte s;
+ if (len d > Sys->WAITLEN)
+ raise string d[0:Sys->WAITLEN];
+ else {
+ d = nil;
+ raise s;
+ }
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+