summaryrefslogtreecommitdiff
path: root/appl/cmd/styxchat.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/styxchat.b')
-rw-r--r--appl/cmd/styxchat.b557
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";
+}