summaryrefslogtreecommitdiff
path: root/appl/cmd/sh/file2chan.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/sh/file2chan.b')
-rw-r--r--appl/cmd/sh/file2chan.b459
1 files changed, 459 insertions, 0 deletions
diff --git a/appl/cmd/sh/file2chan.b b/appl/cmd/sh/file2chan.b
new file mode 100644
index 00000000..a54c9965
--- /dev/null
+++ b/appl/cmd/sh/file2chan.b
@@ -0,0 +1,459 @@
+implement Shellbuiltin;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "lock.m";
+ lock: Lock;
+ Semaphore: import lock;
+include "sh.m";
+ sh: Sh;
+ Listnode, Context: import sh;
+ myself: Shellbuiltin;
+
+Tag: adt {
+ tagid, blocked: int;
+ offset, fid: int;
+ pick {
+ Read =>
+ count: int;
+ rc: chan of (array of byte, string);
+ Write =>
+ data: array of byte;
+ wc: chan of (int, string);
+ }
+};
+
+taglock: ref Lock->Semaphore;
+maxtagid := 1;
+tags := array[16] of list of ref Tag;
+
+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("file2chan: cannot load self: %r"));
+
+ lock = load Lock Lock->PATH;
+ if (lock == nil) ctxt.fail("bad module", sys->sprint("file2chan: cannot load %s: %r", Lock->PATH));
+ lock->init();
+
+ taglock = Semaphore.new();
+ if (taglock == nil)
+ ctxt.fail("no lock", "file2chan: cannot make lock");
+
+
+ ctxt.addbuiltin("file2chan", myself);
+ ctxt.addbuiltin("rblock", myself);
+ ctxt.addbuiltin("rread", myself);
+ ctxt.addbuiltin("rreadone", myself);
+ ctxt.addbuiltin("rwrite", myself);
+ ctxt.addbuiltin("rerror", myself);
+ ctxt.addbuiltin("fetchwdata", myself);
+ ctxt.addbuiltin("putrdata", myself);
+ ctxt.addsbuiltin("rget", myself);
+
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+getself(): Shellbuiltin
+{
+ return myself;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh,
+ cmd: list of ref Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "file2chan" => return builtin_file2chan(ctxt, cmd);
+ "rblock" => return builtin_rblock(ctxt, cmd);
+ "rread" => return builtin_rread(ctxt, cmd, 0);
+ "rreadone" => return builtin_rread(ctxt, cmd, 1);
+ "rwrite" => return builtin_rwrite(ctxt, cmd);
+ "rerror" => return builtin_rerror(ctxt, cmd);
+ "fetchwdata" => return builtin_fetchwdata(ctxt, cmd);
+ "putrdata" => return builtin_putrdata(ctxt, cmd);
+ }
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh,
+ argv: list of ref Listnode): list of ref Listnode
+{
+ # could add ${rtags} to retrieve list of currently outstanding tags
+ case (hd argv).word {
+ "rget" => return sbuiltin_rget(ctxt, argv);
+ }
+ return nil;
+}
+
+builtin_file2chan(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ rcmd, wcmd, ccmd: ref Listnode;
+ path: string;
+
+ n := len argv;
+ if (n < 4 || n > 5)
+ ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
+
+ (path, argv) = ((hd tl argv).word, tl tl argv);
+ (rcmd, argv) = (hd argv, tl argv);
+ (wcmd, argv) = (hd argv, tl argv);
+ if (argv != nil)
+ ccmd = hd argv;
+ if (path == nil || !iscmd(rcmd) || !iscmd(wcmd) || (ccmd != nil && !iscmd(ccmd)))
+ ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
+
+ (dir, f) := pathsplit(path);
+ if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) {
+ reporterror(ctxt, sys->sprint("file2chan: cannot bind #s: %r"));
+ return "no #s";
+ }
+ fio := sys->file2chan(dir, f);
+ if (fio == nil) {
+ reporterror(ctxt, sys->sprint("file2chan: cannot make %s: %r", path));
+ return "cannot make chan";
+ }
+ sync := chan of int;
+ spawn srv(sync, ctxt, fio, rcmd, wcmd, ccmd);
+ apid := <-sync;
+ ctxt.set("apid", ref Listnode(nil, string apid) :: nil);
+ if (ctxt.options() & ctxt.INTERACTIVE)
+ sys->fprint(sys->fildes(2), "%d\n", apid);
+ return nil;
+}
+
+srv(sync: chan of int, ctxt: ref Context,
+ fio: ref Sys->FileIO, rcmd, wcmd, ccmd: ref Listnode)
+{
+ ctxt = ctxt.copy(1);
+ sync <-= sys->pctl(0, nil);
+ for (;;) {
+ fid, offset, count: int;
+ rc: Sys->Rread;
+ wc: Sys->Rwrite;
+ d: array of byte;
+ t: ref Tag = nil;
+ cmd: ref Listnode = nil;
+ alt {
+ (offset, count, fid, rc) = <-fio.read =>
+ if (rc != nil) {
+ t = ref Tag.Read(0, 0, offset, fid, count, rc);
+ cmd = rcmd;
+ } else
+ continue; # we get a close on both read and write...
+ (offset, d, fid, wc) = <-fio.write =>
+ if (wc != nil) {
+ t = ref Tag.Write(0, 0, offset, fid, d, wc);
+ cmd = wcmd;
+ }
+ }
+ if (t != nil) {
+ addtag(t);
+ ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
+ ctxt.run(cmd :: nil, 0);
+ taglock.obtain();
+ # make a default reply if it hasn't been deliberately blocked.
+ del := 0;
+ if (t.tagid >= 0 && !t.blocked) {
+ pick mt := t {
+ Read =>
+ rreply(mt.rc, nil, "invalid read");
+ Write =>
+ wreply(mt.wc, len mt.data, nil);
+ }
+ del = 1;
+ }
+ taglock.release();
+ if (del)
+ deltag(t.tagid);
+ ctxt.setlocal("tag", nil);
+ } else if (ccmd != nil) {
+ t = ref Tag.Read(0, 0, -1, fid, -1, nil);
+ addtag(t);
+ ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
+ ctxt.run(ccmd :: nil, 0);
+ deltag(t.tagid);
+ ctxt.setlocal("tag", nil);
+ }
+ }
+}
+
+builtin_rread(ctxt: ref Context, argv: list of ref Listnode, one: int): string
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: "+(hd argv).word+" [tag] data");
+ argv = tl argv;
+
+ t := envgettag(ctxt, argv, n == 3);
+ if (t == nil)
+ ctxt.fail("bad tag", "rread: cannot find tag");
+ if (n == 3)
+ argv = tl argv;
+ mt := etr(ctxt, "rread", t);
+ arg := word(hd argv);
+ d := array of byte arg;
+ if (one) {
+ if (mt.offset >= len d)
+ d = nil;
+ else
+ d = d[mt.offset:];
+ }
+ if (len d > mt.count)
+ d = d[0:mt.count];
+ rreply(mt.rc, d, nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rwrite(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ n := len argv;
+ if (n > 3)
+ ctxt.fail("usage", "usage: rwrite [tag [count]]");
+ t := envgettag(ctxt, tl argv, n > 1);
+ if (t == nil)
+ ctxt.fail("bad tag", "rwrite: cannot find tag");
+
+ mt := etw(ctxt, "rwrite", t);
+ count := len mt.data;
+ if (n == 3) {
+ arg := word(hd tl argv);
+ if (!isnum(arg))
+ ctxt.fail("usage", "usage: freply [tag [count]]");
+ count = int arg;
+ }
+ wreply(mt.wc, count, nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rblock(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: rblock [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "rblock: cannot find tag");
+ t.blocked = 1;
+ return nil;
+}
+
+sbuiltin_rget(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
+ argv = tl argv;
+ t := envgettag(ctxt, tl argv, tl argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "rget: cannot find tag");
+ s := "";
+ case (hd argv).word {
+ "data" =>
+ s = string etw(ctxt, "rget", t).data;
+ "count" =>
+ s = string etr(ctxt, "rget", t).count;
+ "offset" =>
+ s = string t.offset;
+ "fid" =>
+ s = string t.fid;
+ * =>
+ ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
+ }
+
+ return ref Listnode(nil, s) :: nil;
+}
+
+builtin_fetchwdata(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: fetchwdata [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "fetchwdata: cannot find tag");
+ d := etw(ctxt, "fetchwdata", t).data;
+ sys->write(sys->fildes(1), d, len d);
+ return nil;
+}
+
+builtin_putrdata(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ argv = tl argv;
+ if (len argv > 1)
+ ctxt.fail("usage", "usage: putrdata [tag]");
+ t := envgettag(ctxt, argv, argv != nil);
+ if (t == nil)
+ ctxt.fail("bad tag", "putrdata: cannot find tag");
+ mt := etr(ctxt, "putrdata", t);
+ buf := array[mt.count] of byte;
+ n := 0;
+ fd := sys->fildes(0);
+ while (n < mt.count) {
+ nr := sys->read(fd, buf[n:mt.count], mt.count - n);
+ if (nr <= 0)
+ break;
+ n += nr;
+ }
+
+ rreply(mt.rc, buf[0:n], nil);
+ deltag(t.tagid);
+ return nil;
+}
+
+builtin_rerror(ctxt: ref Context, argv: list of ref Listnode): string
+{
+ # usage: ferror [tag] error
+ n := len argv;
+ if (n < 2 || n > 3)
+ ctxt.fail("usage", "usage: ferror [tag] error");
+ t := envgettag(ctxt, tl argv, n == 3);
+ if (t == nil)
+ ctxt.fail("bad tag", "rerror: cannot find tag");
+ if (n == 3)
+ argv = tl argv;
+ err := word(hd tl argv);
+ pick mt := t {
+ Read =>
+ rreply(mt.rc, nil, err);
+ Write =>
+ wreply(mt.wc, 0, err);
+ }
+ deltag(t.tagid);
+ return nil;
+}
+
+envgettag(ctxt: ref Context, args: list of ref Listnode, useargs: int): ref Tag
+{
+ tagid: int;
+ if (useargs)
+ tagid = int (hd args).word;
+ else {
+ args = ctxt.get("tag");
+ if (args == nil || tl args != nil)
+ return nil;
+ tagid = int (hd args).word;
+ }
+ return gettag(tagid);
+}
+
+etw(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Write
+{
+ pick mt := t {
+ Write => return mt;
+ }
+ ctxt.fail("bad tag", cmd + ": inappropriate tag id");
+ return nil;
+}
+
+etr(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Read
+{
+ pick mt := t {
+ Read => return mt;
+ }
+ ctxt.fail("bad tag", cmd + ": inappropriate tag id");
+ return nil;
+}
+
+wreply(wc: chan of (int, string), count: int, err: string)
+{
+ alt {
+ wc <-= (count, err) => ;
+ * => ;
+ }
+}
+
+rreply(rc: chan of (array of byte, string), d: array of byte, err: string)
+{
+ alt {
+ rc <-= (d, err) => ;
+ * => ;
+ }
+}
+
+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;
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] > '9' || s[i] < '0')
+ return 0;
+ return 1;
+}
+
+iscmd(n: ref Listnode): int
+{
+ return n.cmd != nil || (n.word != nil && n.word[0] == '}');
+}
+
+addtag(t: ref Tag)
+{
+ taglock.obtain();
+ t.tagid = maxtagid++;
+ slot := t.tagid % len tags;
+ tags[slot] = t :: tags[slot];
+ taglock.release();
+}
+
+deltag(tagid: int)
+{
+ taglock.obtain();
+ slot := tagid % len tags;
+ nwl: list of ref Tag;
+ for (wl := tags[slot]; wl != nil; wl = tl wl)
+ if ((hd wl).tagid != tagid)
+ nwl = hd wl :: nwl;
+ else
+ (hd wl).tagid = -1;
+ tags[slot] = nwl;
+ taglock.release();
+}
+
+gettag(tagid: int): ref Tag
+{
+ slot := tagid % len tags;
+ for (wl := tags[slot]; wl != nil; wl = tl wl)
+ if ((hd wl).tagid == tagid)
+ return hd wl;
+ return nil;
+}
+
+pathsplit(p: string): (string, string)
+{
+ for (i := len p - 1; i >= 0; i--)
+ if (p[i] != '/')
+ break;
+ if (i < 0)
+ return (p, nil);
+ p = p[0:i+1];
+ for (i = len p - 1; i >=0; i--)
+ if (p[i] == '/')
+ break;
+ if (i < 0)
+ return (".", p);
+ return (p[0:i+1], p[i+1:]);
+}
+
+reporterror(ctxt: ref Context, err: string)
+{
+ if (ctxt.options() & ctxt.VERBOSE)
+ sys->fprint(sys->fildes(2), "%s\n", err);
+}