xref: /inferno-os/appl/cmd/styxchat.b (revision 9274481003af38a88988b4e9a3a2c3e0df206bee)
1implement Styxchat;
2
3#
4# Copyright © 2002,2003 Vita Nuova Holdings Limited.  All rights reserved.
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "styx.m";
13	styx: Styx;
14	Tmsg, Rmsg: import styx;
15
16include "string.m";
17	str: String;
18
19include "bufio.m";
20	bufio: Bufio;
21	Iobuf: import bufio;
22
23include "dial.m";
24	dial: Dial;
25
26include "arg.m";
27
28Styxchat: module
29{
30	init:	fn(nil: ref Draw->Context, nil: list of string);
31};
32
33msgsize := 64*1024;
34nexttag := 1;
35verbose := 0;
36
37stdin: ref Sys->FD;
38
39init(nil: ref Draw->Context, args: list of string)
40{
41	sys = load Sys Sys->PATH;
42	styx = load Styx Styx->PATH;
43	str = load String String->PATH;
44	bufio = load Bufio Bufio->PATH;
45	dial = load Dial Dial->PATH;
46	styx->init();
47
48	client := 1;
49	addr := 0;
50	arg := load Arg Arg->PATH;
51	arg->init(args);
52	arg->setusage("styxchat [-nsv] [-m messagesize] [dest]");
53	while((o := arg->opt()) != 0)
54		case o {
55		'm' =>
56			msgsize = atoi(arg->earg());
57		's' =>
58			client = 0;
59		'n' =>
60			addr = 1;
61		'v' =>
62			verbose++;
63		* =>
64			arg->usage();
65		}
66	args = arg->argv();
67	arg = nil;
68	fd: ref Sys->FD;
69	if(args == nil){
70		fd = sys->fildes(0);
71		stdin = sys->open("/dev/cons", Sys->ORDWR);
72		if (stdin == nil)
73			err(sys->sprint("can't open /dev/cons: %r"));
74		sys->dup(stdin.fd, 1);
75	}else{
76		if(tl args != nil)
77			arg->usage();
78		stdin = sys->fildes(0);
79		dest := hd args;
80		if(addr){
81			dest = dial->netmkaddr(dest, "net", "styx");
82			if (client){
83				c := dial->dial(dest, nil);
84				if(c == nil)
85					err(sys->sprint("can't dial %s: %r", dest));
86				fd = c.dfd;
87			}else{
88				lc := dial->announce(dest);
89				if(lc == nil)
90					err(sys->sprint("can't announce %s: %r", dest));
91				c := dial->listen(lc);
92				if(c == nil)
93					err(sys->sprint("can't listen on %s: %r", dest));
94				fd = dial->accept(c);
95				if(fd == nil)
96					err(sys->sprint("can't open %s/data: %r", c.dir));
97			}
98		}else{
99			fd = sys->open(dest, Sys->ORDWR);
100			if(fd == nil)
101				err(sys->sprint("can't open %s: %r", dest));
102		}
103	}
104	sys->pctl(Sys->NEWPGRP, nil);
105	if(client){
106		spawn Rreader(fd);
107		Twriter(fd);
108	}else{
109		spawn Treader(fd);
110		Rwriter(fd);
111	}
112}
113
114quit(e: int)
115{
116	fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
117	if(fd != nil)
118		sys->fprint(fd, "killgrp");
119	if(e)
120		raise "fail:error";
121	exit;
122}
123
124Rreader(fd: ref Sys->FD)
125{
126	while((m := Rmsg.read(fd, msgsize)) != nil){
127		sys->print("<- %s\n%s", m.text(), Rdump(m));
128		if(tagof m == tagof Rmsg.Readerror)
129			quit(1);
130	}
131	sys->print("styxchat: server hungup\n");
132}
133
134Twriter(fd: ref Sys->FD)
135{
136	in := bufio->fopen(stdin, Sys->OREAD);
137	while((l := in.gets('\n')) != nil){
138		if(l != nil && l[0] == '#')
139			continue;
140		(t, err) := Tparse(l);
141		if(t == nil){
142			if(err != nil)
143				sys->print("?%s\n", err);
144		}else{
145			if(t.tag == 0)
146				t.tag = nexttag;
147			a := t.pack();
148			if(a != nil){
149				sys->print("-> %s\n%s", t.text(), Tdump(t));
150				n := len a;
151				if(n <= msgsize){
152					if(sys->write(fd, a, len a) != len a)
153						sys->print("?write error to server: %r\n");
154					if(t.tag != Styx->NOTAG && t.tag != ~0)
155						nexttag++;
156				}else
157					sys->print("?message bigger than agreed: %d bytes\n", n);
158			}else
159				sys->fprint(sys->fildes(2), "styxchat: T-message conversion failed\n");
160		}
161	}
162}
163
164Rdump(m: ref Rmsg): string
165{
166	if(!verbose)
167		return "";
168	pick r :=m {
169	Read =>
170		return dump(r.data, len r.data, verbose>1);
171	* =>
172		return "";
173	}
174}
175
176Tdump(m: ref Tmsg): string
177{
178	if(!verbose)
179		return "";
180	pick t := m {
181	Write =>
182		return dump(t.data, len t.data, verbose>1);
183	* =>
184		return "";
185	}
186}
187
188isprint(c: int): int
189{
190	return c >= 16r20 && c < 16r7F || c == '\n' || c == '\t' || c == '\r';
191}
192
193textdump(a: array of byte, lim: int): string
194{
195	s := "\ttext(\"";
196	for(i := 0; i < lim; i++)
197		case c := int a[i] {
198		'\t' =>
199			s += "\\t";
200		'\n' =>
201			s += "\\n";
202		'\r' =>
203			s += "\\r";
204		'"' =>
205			s += "\\\"";
206		* =>
207			if(isprint(c))
208				s[len s] = c;
209			else
210				s += sys->sprint("\\u%4.4ux", c);
211		}
212	s += "\")\n";
213	return s;
214}
215
216dump(a: array of byte, lim: int, text: int): string
217{
218	if(a == nil)
219		return "";
220	if(len a < lim)
221		lim = len a;
222	printable := 1;
223	for(i := 0; i < lim; i++)
224		if(!isprint(int a[i])){
225			printable = 0;
226			break;
227		}
228	if(printable)
229		return textdump(a, lim);
230	s := "\tdump(";
231	for(i = 0; i < lim; i++)
232		s += sys->sprint("%2.2ux", int a[i]);
233	s += ")\n";
234	if(text)
235		s += textdump(a, lim);
236	return s;
237}
238
239val(s: string): int
240{
241	if(s == "~0")
242		return ~0;
243	return atoi(s);
244}
245
246bigval(s: string): big
247{
248	if(s == "~0")
249		return ~ big 0;
250	return atob(s);
251}
252
253fid(s: string): int
254{
255	if(s == "nofid" || s == "NOFID")
256		return Styx->NOFID;
257	return val(s);
258}
259
260tag(s: string): int
261{
262	if(s == "~0" || s == "notag" || s == "NOTAG")
263		return Styx->NOTAG;
264	return atoi(s);
265}
266
267dir(name: string, uid: string, gid: string, mode: int, mtime: int, length: big): Sys->Dir
268{
269	d := sys->zerodir;
270	d.name = name;
271	d.uid = uid;
272	d.gid = gid;
273	d.mode = mode;
274	d.mtime = mtime;
275	d.length = length;
276	return d;
277}
278
279Tparse(s: string): (ref Tmsg, string)
280{
281	args := str->unquoted(s);
282	if(args == nil)
283		return (nil, nil);
284	argc := len args;
285	av := array[argc] of string;
286	for(i:=0; args != nil; args = tl args)
287		av[i++] = hd args;
288	case av[0] {
289	"Tversion" =>
290		if(argc != 3)
291			return (nil, "usage: Tversion messagesize version");
292		return (ref Tmsg.Version(Styx->NOTAG, atoi(av[1]), av[2]), nil);
293	"Tauth" =>
294		if(argc != 4)
295			return (nil, "usage: Tauth afid uname aname");
296		return (ref Tmsg.Auth(0, fid(av[1]), av[2], av[3]), nil);
297	"Tflush" =>
298		if(argc != 2)
299			return (nil, "usage: Tflush oldtag");
300		return (ref Tmsg.Flush(0, tag(av[1])), nil);
301	"Tattach" =>
302		if(argc != 5)
303			return (nil, "usage: Tattach fid afid uname aname");
304		return (ref Tmsg.Attach(0, fid(av[1]), fid(av[2]), av[3], av[4]), nil);
305	"Twalk" =>
306		if(argc < 3)
307			return (nil, "usage: Twalk fid newfid [name...]");
308		names: array of string;
309		if(argc > 3)
310			names = av[3:];
311		return (ref Tmsg.Walk(0, fid(av[1]), fid(av[2]), names), nil);
312	"Topen" =>
313		if(argc != 3)
314			return (nil, "usage: Topen fid mode");
315		return (ref Tmsg.Open(0, fid(av[1]), atoi(av[2])), nil);
316	"Tcreate" =>
317		if(argc != 5)
318			return (nil, "usage: Tcreate fid name perm mode");
319		return (ref Tmsg.Create(0, fid(av[1]), av[2], atoi(av[3]), atoi(av[4])), nil);
320	"Tread" =>
321		if(argc != 4)
322			return (nil, "usage: Tread fid offset count");
323		return (ref Tmsg.Read(0, fid(av[1]), atob(av[2]), atoi(av[3])), nil);
324	"Twrite" =>
325		if(argc != 4)
326			return (nil, "usage: Twrite fid offset data");
327		return (ref Tmsg.Write(0, fid(av[1]), atob(av[2]), array of byte av[3]), nil);
328	"Tclunk" =>
329		if(argc != 2)
330			return (nil, "usage: Tclunk fid");
331		return (ref Tmsg.Clunk(0, fid(av[1])), nil);
332	"Tremove" =>
333		if(argc != 2)
334			return (nil, "usage: Tremove fid");
335		return (ref Tmsg.Remove(0, fid(av[1])), nil);
336	"Tstat" =>
337		if(argc != 2)
338			return (nil, "usage: Tstat fid");
339		return (ref Tmsg.Stat(0, fid(av[1])), nil);
340	"Twstat" =>
341		if(argc != 8)
342			return (nil, "usage: Twstat fid name uid gid mode mtime length");
343		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);
344	"nexttag" =>
345		if(argc < 2)
346			return (nil, sys->sprint("next tag is %d", nexttag));
347		nexttag = tag(av[1]);
348		return (nil, nil);
349	"dump" =>
350		verbose++;
351		return (nil, nil);
352	* =>
353		return (nil, "unknown message type");
354	}
355}
356
357#
358# server side
359#
360
361Treader(fd: ref Sys->FD)
362{
363	while((m := Tmsg.read(fd, msgsize)) != nil){
364		sys->print("<- %s\n", m.text());
365		if(tagof m == tagof Tmsg.Readerror)
366			quit(1);
367	}
368	sys->print("styxchat: clients hungup\n");
369}
370
371Rwriter(fd: ref Sys->FD)
372{
373	in := bufio->fopen(stdin, Sys->OREAD);
374	while((l := in.gets('\n')) != nil){
375		if(l != nil && l[0] == '#')
376			continue;
377		(r, err) := Rparse(l);
378		if(r == nil){
379			if(err != nil)
380				sys->print("?%s\n", err);
381		}else{
382			a := r.pack();
383			if(a != nil){
384				sys->print("-> %s\n", r.text());
385				n := len a;
386				if(n <= msgsize){
387					if(sys->write(fd, a, len a) != len a)
388						sys->print("?write error to clients: %r\n");
389				}else
390					sys->print("?message bigger than agreed: %d bytes\n", n);
391			}else
392				sys->fprint(sys->fildes(2), "styxchat: R-message conversion failed\n");
393		}
394	}
395}
396
397qid(s: string): Sys->Qid
398{
399	(nf, flds) := sys->tokenize(s, ".");
400	q := Sys->Qid(big 0, 0, 0);
401	if(nf < 1)
402		return q;
403	q.path = atob(hd flds);
404	if(nf < 2)
405		return q;
406	q.vers = atoi(hd tl flds);
407	if(nf < 3)
408		return q;
409	q.qtype = mode(hd tl tl flds);
410	return q;
411}
412
413mode(s: string): int
414{
415	if(len s > 0 && s[0] >= '0' && s[0] <= '9')
416		return atoi(s);
417	mode := 0;
418	for(i := 0; i < len s; i++){
419		case s[i] {
420		'd' =>
421			mode |= Sys->QTDIR;
422		'a' =>
423			mode |= Sys->QTAPPEND;
424		'u' =>
425			mode |= Sys->QTAUTH;
426		'l' =>
427			mode |= Sys->QTEXCL;
428		'f' =>
429			;
430		* =>
431			sys->fprint(sys->fildes(2), "styxchat: unknown mode character %c, ignoring\n", s[i]);
432		}
433	}
434	return mode;
435}
436
437rdir(a: array of string): Sys->Dir
438{
439	d := sys->zerodir;
440	d.qid = qid(a[0]);
441	d.mode = atoi(a[1]) | (d.qid.qtype<<24);
442	d.atime = atoi(a[2]);
443	d.mtime = atoi(a[3]);
444	d.length = atob(a[4]);
445	d.name = a[5];
446	d.uid = a[6];
447	d.gid = a[7];
448	d.muid = a[8];
449	return d;
450}
451
452Rparse(s: string): (ref Rmsg, string)
453{
454	args := str->unquoted(s);
455	if(args == nil)
456		return (nil, nil);
457	argc := len args;
458	av := array[argc] of string;
459	for(i:=0; args != nil; args = tl args)
460		av[i++] = hd args;
461	case av[0] {
462	"Rversion" =>
463		if(argc != 4)
464			return (nil, "usage: Rversion tag messagesize version");
465		return (ref Rmsg.Version(tag(av[1]), atoi(av[2]), av[3]), nil);
466	"Rauth" =>
467		if(argc != 3)
468			return (nil, "usage: Rauth tag aqid");
469		return (ref Rmsg.Auth(tag(av[1]), qid(av[2])), nil);
470	"Rflush" =>
471		if(argc != 2)
472			return (nil, "usage: Rflush tag");
473		return (ref Rmsg.Flush(tag(av[1])), nil);
474	"Rattach" =>
475		if(argc != 3)
476			return (nil, "usage: Rattach tag qid");
477		return (ref Rmsg.Attach(tag(av[1]), qid(av[2])), nil);
478	"Rwalk" =>
479		if(argc < 2)
480			return (nil, "usage: Rwalk tag [qid ...]");
481		qids := array[argc-2] of Sys->Qid;
482		for(i = 0; i < len qids; i++)
483			qids[i] = qid(av[i+2]);
484		return (ref Rmsg.Walk(tag(av[1]), qids), nil);
485	"Ropen" =>
486		if(argc != 4)
487			return (nil, "usage: Ropen tag qid iounit");
488		return (ref Rmsg.Open(tag(av[1]), qid(av[2]), atoi(av[3])), nil);
489	"Rcreate" =>
490		if(argc != 4)
491			return (nil, "usage: Rcreate tag qid iounit");
492		return (ref Rmsg.Create(tag(av[1]), qid(av[2]), atoi(av[3])), nil);
493	"Rread" =>
494		if(argc != 3)
495			return (nil, "usage: Rread tag data");
496		return (ref Rmsg.Read(tag(av[1]), array of byte av[2]), nil);
497	"Rwrite" =>
498		if(argc != 3)
499			return (nil, "usage: Rwrite tag count");
500		return (ref Rmsg.Write(tag(av[1]), atoi(av[2])), nil);
501	"Rclunk" =>
502		if(argc != 2)
503			return (nil, "usage: Rclunk tag");
504		return (ref Rmsg.Clunk(tag(av[1])), nil);
505	"Rremove" =>
506		if(argc != 2)
507			return (nil, "usage: Rremove tag");
508		return (ref Rmsg.Remove(tag(av[1])), nil);
509	"Rstat" =>
510		if(argc != 11)
511			return (nil, "usage: Rstat tag qid mode atime mtime length name uid gid muid");
512		return (ref Rmsg.Stat(tag(av[1]), rdir(av[2:])), nil);
513	"Rwstat" =>
514		if(argc != 8)
515			return (nil, "usage: Rwstat tag");
516		return (ref Rmsg.Wstat(tag(av[1])), nil);
517	"Rerror" =>
518		if(argc != 3)
519			return (nil, "usage: Rerror tag ename");
520		return (ref Rmsg.Error(tag(av[1]), av[2]), nil);
521	"dump" =>
522		verbose++;
523		return (nil, nil);
524	* =>
525		return (nil, "unknown message type");
526	}
527}
528
529atoi(s: string): int
530{
531	(i, nil) := str->toint(s, 0);
532	return i;
533}
534
535# atoi with traditional unix semantics for octal and hex.
536atob(s: string): big
537{
538	(b, nil) := str->tobig(s, 0);
539	return b;
540}
541
542err(s: string)
543{
544	sys->fprint(sys->fildes(2), "styxchat: %s\n", s);
545	raise "fail:error";
546}
547