diff options
Diffstat (limited to 'appl/cmd/styxchat.b')
| -rw-r--r-- | appl/cmd/styxchat.b | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/appl/cmd/styxchat.b b/appl/cmd/styxchat.b new file mode 100644 index 00000000..f0b1f2c5 --- /dev/null +++ b/appl/cmd/styxchat.b @@ -0,0 +1,557 @@ +implement Styxchat; + +# +# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "string.m"; + str: String; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Styxchat: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +msgsize := 64*1024; +nexttag := 1; +verbose := 0; + +stdin: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + styx->init(); + + client := 1; + addr := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("styxchat [-nsv] [-m messagesize] [dest]"); + while((o := arg->opt()) != 0) + case o { + 'm' => + msgsize = atoi(arg->earg()); + 's' => + client = 0; + 'n' => + addr = 1; + 'v' => + verbose++; + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + fd: ref Sys->FD; + if(args == nil){ + fd = sys->fildes(0); + stdin = sys->open("/dev/cons", Sys->ORDWR); + if (stdin == nil) + err(sys->sprint("can't open /dev/cons: %r")); + sys->dup(stdin.fd, 1); + }else{ + if(tl args != nil) + arg->usage(); + stdin = sys->fildes(0); + dest := hd args; + if(addr){ + dest = netmkaddr(dest, "net", "styx"); + if (client){ + (rc, c) := sys->dial(dest, nil); + if(rc < 0) + err(sys->sprint("can't dial %s: %r", dest)); + fd = c.dfd; + }else{ + (rlc, lc) := sys->announce(dest); + if (rlc < 0) + err(sys->sprint("can't announce %s: %r", dest)); + (rc, c) := sys->listen(lc); + if (rc < 0) + err(sys->sprint("can't listen on %s: %r", dest)); + fd = sys->open(c.dir + "/data", Sys->ORDWR); + if (fd == nil) + err(sys->sprint("can't open %s/data: %r", c.dir)); + } + }else{ + fd = sys->open(dest, Sys->ORDWR); + if(fd == nil) + err(sys->sprint("can't open %s: %r", dest)); + } + } + sys->pctl(Sys->NEWPGRP, nil); + if(client){ + spawn Rreader(fd); + Twriter(fd); + }else{ + spawn Treader(fd); + Rwriter(fd); + } +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +quit(e: int) +{ + fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(e) + raise "fail:error"; + exit; +} + +Rreader(fd: ref Sys->FD) +{ + while((m := Rmsg.read(fd, msgsize)) != nil){ + sys->print("<- %s\n%s", m.text(), Rdump(m)); + if(tagof m == tagof Rmsg.Readerror) + quit(1); + } + sys->print("styxchat: server hungup\n"); +} + +Twriter(fd: ref Sys->FD) +{ + in := bufio->fopen(stdin, Sys->OREAD); + while((l := in.gets('\n')) != nil){ + if(l != nil && l[0] == '#') + continue; + (t, err) := Tparse(l); + if(t == nil){ + if(err != nil) + sys->print("?%s\n", err); + }else{ + if(t.tag == 0) + t.tag = nexttag; + a := t.pack(); + if(a != nil){ + sys->print("-> %s\n%s", t.text(), Tdump(t)); + n := len a; + if(n <= msgsize){ + if(sys->write(fd, a, len a) != len a) + sys->print("?write error to server: %r\n"); + if(t.tag != Styx->NOTAG && t.tag != ~0) + nexttag++; + }else + sys->print("?message bigger than agreed: %d bytes\n", n); + }else + sys->fprint(sys->fildes(2), "styxchat: T-message conversion failed\n"); + } + } +} + +Rdump(m: ref Rmsg): string +{ + if(!verbose) + return ""; + pick r :=m { + Read => + return dump(r.data, len r.data, verbose>1); + * => + return ""; + } +} + +Tdump(m: ref Tmsg): string +{ + if(!verbose) + return ""; + pick t := m { + Write => + return dump(t.data, len t.data, verbose>1); + * => + return ""; + } +} + +isprint(c: int): int +{ + return c >= 16r20 && c < 16r7F || c == '\n' || c == '\t' || c == '\r'; +} + +textdump(a: array of byte, lim: int): string +{ + s := "\ttext(\""; + for(i := 0; i < lim; i++) + case c := int a[i] { + '\t' => + s += "\\t"; + '\n' => + s += "\\n"; + '\r' => + s += "\\r"; + '"' => + s += "\\\""; + * => + if(isprint(c)) + s[len s] = c; + else + s += sys->sprint("\\u%4.4ux", c); + } + s += "\")\n"; + return s; +} + +dump(a: array of byte, lim: int, text: int): string +{ + if(a == nil) + return ""; + if(len a < lim) + lim = len a; + printable := 1; + for(i := 0; i < lim; i++) + if(!isprint(int a[i])){ + printable = 0; + break; + } + if(printable) + return textdump(a, lim); + s := "\tdump("; + for(i = 0; i < lim; i++) + s += sys->sprint("%2.2ux", int a[i]); + s += ")\n"; + if(text) + s += textdump(a, lim); + return s; +} + +val(s: string): int +{ + if(s == "~0") + return ~0; + return atoi(s); +} + +bigval(s: string): big +{ + if(s == "~0") + return ~ big 0; + return atob(s); +} + +fid(s: string): int +{ + if(s == "nofid" || s == "NOFID") + return Styx->NOFID; + return val(s); +} + +tag(s: string): int +{ + if(s == "~0" || s == "notag" || s == "NOTAG") + return Styx->NOTAG; + return atoi(s); +} + +dir(name: string, uid: string, gid: string, mode: int, mtime: int, length: big): Sys->Dir +{ + d := sys->zerodir; + d.name = name; + d.uid = uid; + d.gid = gid; + d.mode = mode; + d.mtime = mtime; + d.length = length; + return d; +} + +Tparse(s: string): (ref Tmsg, string) +{ + args := str->unquoted(s); + if(args == nil) + return (nil, nil); + argc := len args; + av := array[argc] of string; + for(i:=0; args != nil; args = tl args) + av[i++] = hd args; + case av[0] { + "Tversion" => + if(argc != 3) + return (nil, "usage: Tversion messagesize version"); + return (ref Tmsg.Version(Styx->NOTAG, atoi(av[1]), av[2]), nil); + "Tauth" => + if(argc != 4) + return (nil, "usage: Tauth afid uname aname"); + return (ref Tmsg.Auth(0, fid(av[1]), av[2], av[3]), nil); + "Tflush" => + if(argc != 2) + return (nil, "usage: Tflush oldtag"); + return (ref Tmsg.Flush(0, tag(av[1])), nil); + "Tattach" => + if(argc != 5) + return (nil, "usage: Tattach fid afid uname aname"); + return (ref Tmsg.Attach(0, fid(av[1]), fid(av[2]), av[3], av[4]), nil); + "Twalk" => + if(argc < 3) + return (nil, "usage: Twalk fid newfid [name...]"); + names: array of string; + if(argc > 3) + names = av[3:]; + return (ref Tmsg.Walk(0, fid(av[1]), fid(av[2]), names), nil); + "Topen" => + if(argc != 3) + return (nil, "usage: Topen fid mode"); + return (ref Tmsg.Open(0, fid(av[1]), atoi(av[2])), nil); + "Tcreate" => + if(argc != 5) + return (nil, "usage: Tcreate fid name perm mode"); + return (ref Tmsg.Create(0, fid(av[1]), av[2], atoi(av[3]), atoi(av[4])), nil); + "Tread" => + if(argc != 4) + return (nil, "usage: Tread fid offset count"); + return (ref Tmsg.Read(0, fid(av[1]), atob(av[2]), atoi(av[3])), nil); + "Twrite" => + if(argc != 4) + return (nil, "usage: Twrite fid offset data"); + return (ref Tmsg.Write(0, fid(av[1]), atob(av[2]), array of byte av[3]), nil); + "Tclunk" => + if(argc != 2) + return (nil, "usage: Tclunk fid"); + return (ref Tmsg.Clunk(0, fid(av[1])), nil); + "Tremove" => + if(argc != 2) + return (nil, "usage: Tremove fid"); + return (ref Tmsg.Remove(0, fid(av[1])), nil); + "Tstat" => + if(argc != 2) + return (nil, "usage: Tstat fid"); + return (ref Tmsg.Stat(0, fid(av[1])), nil); + "Twstat" => + if(argc != 8) + return (nil, "usage: Twstat fid name uid gid mode mtime length"); + return (ref Tmsg.Wstat(0, fid(av[1]), dir(av[2], av[3], av[4], val(av[5]), val(av[6]), bigval(av[7]))), nil); + "nexttag" => + if(argc < 2) + return (nil, sys->sprint("next tag is %d", nexttag)); + nexttag = tag(av[1]); + return (nil, nil); + "dump" => + verbose++; + return (nil, nil); + * => + return (nil, "unknown message type"); + } +} + +# +# server side +# + +Treader(fd: ref Sys->FD) +{ + while((m := Tmsg.read(fd, msgsize)) != nil){ + sys->print("<- %s\n", m.text()); + if(tagof m == tagof Tmsg.Readerror) + quit(1); + } + sys->print("styxchat: clients hungup\n"); +} + +Rwriter(fd: ref Sys->FD) +{ + in := bufio->fopen(stdin, Sys->OREAD); + while((l := in.gets('\n')) != nil){ + if(l != nil && l[0] == '#') + continue; + (r, err) := Rparse(l); + if(r == nil){ + if(err != nil) + sys->print("?%s\n", err); + }else{ + a := r.pack(); + if(a != nil){ + sys->print("-> %s\n", r.text()); + n := len a; + if(n <= msgsize){ + if(sys->write(fd, a, len a) != len a) + sys->print("?write error to clients: %r\n"); + }else + sys->print("?message bigger than agreed: %d bytes\n", n); + }else + sys->fprint(sys->fildes(2), "styxchat: R-message conversion failed\n"); + } + } +} + +qid(s: string): Sys->Qid +{ + (nf, flds) := sys->tokenize(s, "."); + q := Sys->Qid(big 0, 0, 0); + if(nf < 1) + return q; + q.path = atob(hd flds); + if(nf < 2) + return q; + q.vers = atoi(hd tl flds); + if(nf < 3) + return q; + q.qtype = mode(hd tl tl flds); + return q; +} + +mode(s: string): int +{ + if(len s > 0 && s[0] >= '0' && s[0] <= '9') + return atoi(s); + mode := 0; + for(i := 0; i < len s; i++){ + case s[i] { + 'd' => + mode |= Sys->QTDIR; + 'a' => + mode |= Sys->QTAPPEND; + 'u' => + mode |= Sys->QTAUTH; + 'l' => + mode |= Sys->QTEXCL; + 'f' => + ; + * => + sys->fprint(sys->fildes(2), "styxchat: unknown mode character %c, ignoring\n", s[i]); + } + } + return mode; +} + +rdir(a: array of string): Sys->Dir +{ + d := sys->zerodir; + d.qid = qid(a[0]); + d.mode = atoi(a[1]) | (d.qid.qtype<<24); + d.atime = atoi(a[2]); + d.mtime = atoi(a[3]); + d.length = atob(a[4]); + d.name = a[5]; + d.uid = a[6]; + d.gid = a[7]; + d.muid = a[8]; + return d; +} + +Rparse(s: string): (ref Rmsg, string) +{ + args := str->unquoted(s); + if(args == nil) + return (nil, nil); + argc := len args; + av := array[argc] of string; + for(i:=0; args != nil; args = tl args) + av[i++] = hd args; + case av[0] { + "Rversion" => + if(argc != 4) + return (nil, "usage: Rversion tag messagesize version"); + return (ref Rmsg.Version(tag(av[1]), atoi(av[2]), av[3]), nil); + "Rauth" => + if(argc != 3) + return (nil, "usage: Rauth tag aqid"); + return (ref Rmsg.Auth(tag(av[1]), qid(av[2])), nil); + "Rflush" => + if(argc != 2) + return (nil, "usage: Rflush tag"); + return (ref Rmsg.Flush(tag(av[1])), nil); + "Rattach" => + if(argc != 3) + return (nil, "usage: Rattach tag qid"); + return (ref Rmsg.Attach(tag(av[1]), qid(av[2])), nil); + "Rwalk" => + if(argc < 2) + return (nil, "usage: Rwalk tag [qid ...]"); + qids := array[argc-2] of Sys->Qid; + for(i = 0; i < len qids; i++) + qids[i] = qid(av[i+2]); + return (ref Rmsg.Walk(tag(av[1]), qids), nil); + "Ropen" => + if(argc != 4) + return (nil, "usage: Ropen tag qid iounit"); + return (ref Rmsg.Open(tag(av[1]), qid(av[2]), atoi(av[3])), nil); + "Rcreate" => + if(argc != 4) + return (nil, "usage: Rcreate tag qid iounit"); + return (ref Rmsg.Create(tag(av[1]), qid(av[2]), atoi(av[3])), nil); + "Rread" => + if(argc != 3) + return (nil, "usage: Rread tag data"); + return (ref Rmsg.Read(tag(av[1]), array of byte av[2]), nil); + "Rwrite" => + if(argc != 3) + return (nil, "usage: Rwrite tag count"); + return (ref Rmsg.Write(tag(av[1]), atoi(av[2])), nil); + "Rclunk" => + if(argc != 2) + return (nil, "usage: Rclunk tag"); + return (ref Rmsg.Clunk(tag(av[1])), nil); + "Rremove" => + if(argc != 2) + return (nil, "usage: Rremove tag"); + return (ref Rmsg.Remove(tag(av[1])), nil); + "Rstat" => + if(argc != 11) + return (nil, "usage: Rstat tag qid mode atime mtime length name uid gid muid"); + return (ref Rmsg.Stat(tag(av[1]), rdir(av[2:])), nil); + "Rwstat" => + if(argc != 8) + return (nil, "usage: Rwstat tag"); + return (ref Rmsg.Wstat(tag(av[1])), nil); + "Rerror" => + if(argc != 3) + return (nil, "usage: Rerror tag ename"); + return (ref Rmsg.Error(tag(av[1]), av[2]), nil); + "dump" => + verbose++; + return (nil, nil); + * => + return (nil, "unknown message type"); + } +} + +atoi(s: string): int +{ + (i, nil) := str->toint(s, 0); + return i; +} + +# atoi with traditional unix semantics for octal and hex. +atob(s: string): big +{ + (b, nil) := str->tobig(s, 0); + return b; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "styxchat: %s\n", s); + raise "fail:error"; +} |
