xref: /inferno-os/appl/cmd/ndb/cs.b (revision a5e7f7282341dff49881e8b7ce247b44fd8bcc7e)
1implement Cs;
2
3#
4# Connection server translates net!machine!service into
5# /net/tcp/clone 135.104.9.53!564
6#
7
8include "sys.m";
9	sys:	Sys;
10
11include "draw.m";
12
13include "srv.m";
14	srv: Srv;
15
16include "bufio.m";
17include "attrdb.m";
18	attrdb: Attrdb;
19	Attr, Db, Dbentry, Tuples: import attrdb;
20
21include "ip.m";
22	ip: IP;
23include "ipattr.m";
24	ipattr: IPattr;
25
26include "arg.m";
27
28Cs: module
29{
30	init:	fn(nil: ref Draw->Context, nil: list of string);
31};
32
33# signature of dial-on-demand module
34CSdial: module
35{
36	init:	fn(nil: ref Draw->Context): string;
37	connect:	fn(): string;
38};
39
40Reply: adt
41{
42	fid:	int;
43	pid:	int;
44	addrs:	list of string;
45	err:	string;
46};
47
48Cached: adt
49{
50	expire:	int;
51	query:	string;
52	addrs:	list of string;
53};
54
55Ncache: con 16;
56cache:= array[Ncache] of ref Cached;
57nextcache := 0;
58
59rlist: list of ref Reply;
60
61ndbfile := "/lib/ndb/local";
62ndb: ref Db;
63mntpt := "/net";
64myname: string;
65
66stderr: ref Sys->FD;
67
68verbose := 0;
69dialmod: CSdial;
70
71init(ctxt: ref Draw->Context, args: list of string)
72{
73	sys = load Sys Sys->PATH;
74	stderr = sys->fildes(2);
75	attrdb = load Attrdb Attrdb->PATH;
76	if(attrdb == nil)
77		cantload(Attrdb->PATH);
78	attrdb->init();
79	ip = load IP IP->PATH;
80	if(ip == nil)
81		cantload(IP->PATH);
82	ip->init();
83	ipattr = load IPattr IPattr->PATH;
84	if(ipattr == nil)
85		cantload(IPattr->PATH);
86	ipattr->init(attrdb, ip);
87
88	svcname := "#scs";
89	arg := load Arg Arg->PATH;
90	if (arg == nil)
91		cantload(Arg->PATH);
92	arg->init(args);
93	arg->setusage("cs [-v] [-x mntpt] [-f database] [-d dialmod]");
94	while((c := arg->opt()) != 0)
95		case c {
96		'v' or 'D' =>
97			verbose++;
98		'd' =>	# undocumented hack to replace svc/cs/cs
99			f := arg->arg();
100			if(f != nil){
101				dialmod = load CSdial f;
102				if(dialmod == nil)
103					cantload(f);
104			}
105		'f' =>
106			ndbfile = arg->earg();
107		'x' =>
108			mntpt = arg->earg();
109			svcname = "#scs"+svcpt(mntpt);
110		* =>
111			arg->usage();
112		}
113
114	if(arg->argv() != nil)
115		arg->usage();
116	arg = nil;
117
118	srv = load Srv Srv->PATH;	# hosted Inferno only
119	if(srv != nil)
120		srv->init();
121
122	sys->remove(svcname+"/cs");
123	sys->unmount(svcname, mntpt);
124	publish(svcname);
125	if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0)
126		error(sys->sprint("can't bind #s on %s: %r", mntpt));
127	file := sys->file2chan(mntpt, "cs");
128	if(file == nil)
129		error(sys->sprint("can't make %s/cs: %r", mntpt));
130	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
131	refresh();
132	if(dialmod != nil){
133		e := dialmod->init(ctxt);
134		if(e != nil)
135			error(sys->sprint("can't initialise dial-on-demand: %s", e));
136	}
137	spawn cs(file);
138}
139
140svcpt(s: string): string
141{
142	for(i:=0; i<len s; i++)
143		if(s[i] == '/')
144			s[i] = '_';
145	return s;
146}
147
148publish(dir: string)
149{
150	d := Sys->nulldir;
151	d.mode = 8r777;
152	if(sys->wstat(dir, d) < 0)
153		sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir);
154}
155
156cantload(m: string)
157{
158	error(sys->sprint("cannot load %s: %r", m));
159}
160
161error(s: string)
162{
163	sys->fprint(sys->fildes(2), "cs: %s\n", s);
164	raise "fail:error";
165}
166
167refresh()
168{
169	myname = sysname();
170	if(ndb == nil){
171		ndb2 := Db.open(ndbfile);
172		if(ndb2 == nil){
173			err := sys->sprint("%r");
174			ndb2 = Db.open("/lib/ndb/inferno");	# try to get service map at least
175			if(ndb2 == nil)
176				sys->fprint(sys->fildes(2), "cs: warning: can't open %s: %s\n", ndbfile, err);	# continue without it
177		}
178		ndb = Db.open(mntpt+"/ndb");
179		if(ndb != nil)
180			ndb = ndb.append(ndb2);
181		else
182			ndb = ndb2;
183	}else
184		ndb.reopen();
185}
186
187sysname(): string
188{
189	t := rf("/dev/sysname");
190	if(t != nil)
191		return t;
192	t = rf("#e/sysname");
193	if(t == nil){
194		s := rf(mntpt+"/ndb");
195		if(s != nil){
196			db := Db.sopen(s);
197			if(db != nil){
198				(e, nil) := db.find(nil, "sys");
199				if(e != nil)
200					t = e.findfirst("sys");
201			}
202		}
203	}
204	if(t != nil){
205		fd := sys->open("/dev/sysname", Sys->OWRITE);
206		if(fd != nil)
207			sys->fprint(fd, "%s", t);
208	}
209	return t;
210}
211
212rf(name: string): string
213{
214	fd := sys->open(name, Sys->OREAD);
215	buf := array[512] of byte;
216	n := sys->read(fd, buf, len buf);
217	if(n <= 0)
218		return nil;
219	return string buf[0:n];
220}
221
222cs(file: ref Sys->FileIO)
223{
224	pidc := chan of int;
225	donec := chan of ref Reply;
226	for (;;) {
227		alt {
228		(nil, buf, fid, wc) := <-file.write =>
229			cleanfid(fid);	# each write cancels previous requests
230			if(dialmod != nil){
231				e := dialmod->connect();
232				if(e != nil){
233					if(len e > 5 && e[0:5]=="fail:")
234						e = e[5:];
235					if(e == "")
236						e = "unknown error";
237					wc <-= (0, "cs: dial on demand: "+e);
238					break;
239				}
240			}
241			if(wc != nil){
242				nbytes := len buf;
243				query := string buf;
244				if(query == "refresh"){
245					refresh();
246					wc <-= (nbytes, nil);
247					break;
248				}
249				now := time();
250				r := ref Reply;
251				r.fid = fid;
252				spawn request(r, query, nbytes, now, wc, pidc, donec);
253				r.pid = <-pidc;
254				rlist = r :: rlist;
255			}
256
257		(off, nbytes, fid, rc) := <-file.read =>
258			if(rc != nil){
259				r := findfid(fid);
260				if(r != nil)
261					reply(r, off, nbytes, rc);
262				else
263					rc <-= (nil, "unknown request");
264			} else
265				;	# cleanfid(fid);		# compensate for csendq in file2chan
266
267		r := <-donec =>
268			r.pid = 0;
269		}
270	}
271}
272
273findfid(fid: int): ref Reply
274{
275	for(rl := rlist; rl != nil; rl = tl rl){
276		r := hd rl;
277		if(r.fid == fid)
278			return r;
279	}
280	return nil;
281}
282
283cleanfid(fid: int)
284{
285	rl := rlist;
286	rlist = nil;
287	for(; rl != nil; rl = tl rl){
288		r := hd rl;
289		if(r.fid != fid)
290			rlist = r :: rlist;
291		else
292			killgrp(r.pid);
293	}
294}
295
296killgrp(pid: int)
297{
298	if(pid != 0){
299		fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
300		if(fd == nil || sys->fprint(fd, "killgrp") < 0)
301			sys->fprint(stderr, "cs: can't killgrp %d: %r\n", pid);
302	}
303}
304
305request(r: ref Reply, query: string, nbytes: int, now: int, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply)
306{
307	pidc <-= sys->pctl(Sys->NEWPGRP, nil);
308	if(query != nil && query[0] == '!'){
309		# general query
310		(r.addrs, r.err) = genquery(query[1:]);
311	}else{
312		(r.addrs, r.err) = xlate(query, now);
313		if(r.addrs == nil && r.err == nil)
314			r.err = "cs: can't translate address";
315	}
316	if(r.err != nil){
317		if(verbose)
318			sys->fprint(stderr, "cs: %s: %s\n", query, r.err);
319		wc <-= (0, r.err);
320	} else
321		wc <-= (nbytes, nil);
322	donec <-= r;
323}
324
325reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string))
326{
327	if(r.err != nil){
328		rc <-= (nil, r.err);
329		return;
330	}
331	addr: string = nil;
332	if(r.addrs != nil){
333		addr = hd r.addrs;
334		r.addrs = tl r.addrs;
335	}
336	off = 0;	# this version ignores offset
337	rc <-= reads(addr, off, nbytes);
338}
339
340#
341# return the file2chan reply for a read of the given string
342#
343reads(str: string, off, nbytes: int): (array of byte, string)
344{
345	bstr := array of byte str;
346	slen := len bstr;
347	if(off < 0 || off >= slen)
348		return (nil, nil);
349	if(off + nbytes > slen)
350		nbytes = slen - off;
351	if(nbytes <= 0)
352		return (nil, nil);
353	return (bstr[off:off+nbytes], nil);
354}
355
356lookcache(query: string, now: int): ref Cached
357{
358	for(i:=0; i<len cache; i++){
359		c := cache[i];
360		if(c != nil && c.query == query && now < c.expire){
361			if(verbose)
362				sys->print("cache: %s -> %s\n", query, hd c.addrs);
363			return c;
364		}
365	}
366	return nil;
367}
368
369putcache(query: string, addrs: list of string, now: int)
370{
371	ce := ref Cached;
372	ce.expire = now+120;
373	ce.query = query;
374	ce.addrs = addrs;
375	cache[nextcache] = ce;
376	nextcache = (nextcache+1)%Ncache;
377}
378
379xlate(address: string, now: int): (list of string, string)
380{
381	n: int;
382	l, rl, results: list of string;
383	repl, netw, mach, service: string;
384
385	ce := lookcache(address, now);
386	if(ce != nil && ce.addrs != nil)
387		return (ce.addrs, nil);
388
389	(n, l) = sys->tokenize(address, "!\n");
390	if(n < 2)
391		return (nil, "bad format request");
392
393	netw = hd l;
394	if(netw == "net")
395		netw = "tcp";	# TO DO: better (needs lib/ndb)
396	if(!isnetwork(netw))
397		return (nil, "network unavailable "+netw);
398	l = tl l;
399
400	if(!isipnet(netw)) {
401		repl = mntpt + "/" + netw + "/clone ";
402		for(;;){
403			repl += hd l;
404			if((l = tl l) == nil)
405				break;
406			repl += "!";
407		}
408		return (repl :: nil, nil);	# no need to cache
409	}
410
411	if(n != 3)
412		return (nil, "bad format request");
413	mach = hd l;
414	service = hd tl l;
415
416	if(!isnumeric(service)) {
417		s := xlatesvc(netw, service);
418		if(s == nil){
419			if(srv != nil)
420				s = srv->ipn2p(netw, service);
421			if(s == nil)
422				return (nil, "cs: can't translate service");
423		}
424		service = s;
425	}
426
427	attr := ipattr->dbattr(mach);
428	if(mach == "*")
429		l = "" :: nil;
430	else if(attr != "ip") {
431		# Symbolic server == "$SVC"
432		if(mach[0] == '$' && len mach > 1 && ndb != nil){
433			(s, nil) := ipattr->findnetattr(ndb, "sys", myname, mach[1:]);
434			if(s == nil){
435				names := dblook("infernosite", "", mach[1:]);
436				if(names == nil)
437					return (nil, "cs: can't translate "+mach);
438				s = hd names;
439			}
440			mach = s;
441			attr = ipattr->dbattr(mach);
442		}
443		if(attr == "sys"){
444			results = dblook("sys", mach, "ip");
445			if(results != nil)
446				attr = "ip";
447		}
448		if(attr != "ip"){
449			err: string;
450			(results, err) = querydns(mach, "ip");
451			if(err != nil)
452				return (nil, err);
453		}else if(results == nil)
454			results = mach :: nil;
455		l = results;
456		if(l == nil){
457			if(srv != nil)
458				l = srv->iph2a(mach);
459			if(l == nil)
460				return (nil, "cs: unknown host");
461		}
462	} else
463		l = mach :: nil;
464
465	while(l != nil) {
466		s := hd l;
467		l = tl l;
468		dnetw := netw;
469		if(s != nil){
470			(divert, err) := ipattr->findnetattr(ndb, "ip", s, "divert-"+netw);
471			if(err == nil && divert != nil){
472				dnetw = divert;
473				if(!isnetwork(dnetw))
474					return (nil, "network unavailable "+dnetw);	# XXX should only give up if all addresses fail?
475			}
476		}
477
478		if(s != "")
479			s[len s] = '!';
480		s += service;
481
482		repl = mntpt+"/"+dnetw+"/clone "+s;
483		if(verbose)
484			sys->fprint(stderr, "cs: %s!%s!%s -> %s\n", netw, mach, service, repl);
485
486		rl = repl :: rl;
487	}
488	rl = reverse(rl);
489	putcache(address, rl, now);
490	return (rl, nil);
491}
492
493querydns(name: string, rtype: string): (list of string, string)
494{
495	fd := sys->open(mntpt+"/dns", Sys->ORDWR);
496	if(fd == nil)
497		return (nil, nil);
498	if(sys->fprint(fd, "%s %s", name, rtype) < 0)
499		return (nil, sys->sprint("%r"));
500	rl: list of string;
501	buf := array[256] of byte;
502	sys->seek(fd, big 0, 0);
503	while((n := sys->read(fd, buf, len buf)) > 0){
504		# name rtype value
505		(nf, fld) := sys->tokenize(string buf[0:n], " \t");
506		if(nf != 3){
507			sys->fprint(stderr, "cs: odd result from dns: %s\n", string buf[0:n]);
508			continue;
509		}
510		rl = hd tl tl fld :: rl;
511	}
512	return (reverse(rl), nil);
513}
514
515dblook(attr: string, val: string, rattr: string): list of string
516{
517	rl: list of string;
518	ptr: ref Attrdb->Dbptr;
519	for(;;){
520		e: ref Dbentry;
521		(e, ptr) = ndb.findbyattr(ptr, attr, val, rattr);
522		if(e == nil)
523			break;
524		for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){
525			(nil, al) := hd l;
526			for(; al != nil; al = tl al)
527				if(!inlist((hd al).val, rl))
528					rl = (hd al).val :: rl;
529		}
530	}
531	return reverse(rl);
532}
533
534inlist(s: string, l: list of string): int
535{
536	for(; l != nil; l = tl l)
537		if(hd l == s)
538			return 1;
539	return 0;
540}
541
542reverse(l: list of string): list of string
543{
544	t: list of string;
545	for(; l != nil; l = tl l)
546		t = hd l :: t;
547	return t;
548}
549
550isnumeric(a: string): int
551{
552	i, c: int;
553
554	for(i = 0; i < len a; i++) {
555		c = a[i];
556		if(c < '0' || c > '9')
557			return 0;
558	}
559	return 1;
560}
561
562nets: list of string;
563
564isnetwork(s: string) : int
565{
566	if(find(s, nets))
567		return 1;
568	(ok, nil) := sys->stat(mntpt+"/"+s+"/clone");
569	if(ok >= 0) {
570		nets = s :: nets;
571		return 1;
572	}
573	return 0;
574}
575
576find(e: string, l: list of string) : int
577{
578	for(; l != nil; l = tl l)
579		if (e == hd l)
580			return 1;
581	return 0;
582}
583
584isipnet(s: string) : int
585{
586	return s == "net" || s == "tcp" || s == "udp" || s == "il";
587}
588
589xlatesvc(proto: string, s: string): string
590{
591	if(ndb == nil || s == nil || isnumeric(s))
592		return s;
593	(e, nil) := ndb.findbyattr(nil, proto, s, "port");
594	if(e == nil)
595		return nil;
596	matches := e.findbyattr(proto, s, "port");
597	if(matches == nil)
598		return nil;
599	(ts, al) := hd matches;
600	restricted := "";
601	if(ts.hasattr("restricted"))
602		restricted = "!r";
603	if(verbose > 1)
604		sys->print("%s=%q port=%s%s\n", proto, s, (hd al).val, restricted);
605	return (hd al).val+restricted;
606}
607
608time(): int
609{
610	timefd := sys->open("/dev/time", Sys->OREAD);
611	if(timefd == nil)
612		return 0;
613	buf := array[128] of byte;
614	sys->seek(timefd, big 0, 0);
615	n := sys->read(timefd, buf, len buf);
616	if(n < 0)
617		return 0;
618	return int ((big string buf[0:n]) / big 1000000);
619}
620
621#
622# general query: attr1=val1 attr2=val2 ... finds matching tuple(s)
623#	where attr1 is the key and val1 can't be *
624#
625genquery(query: string): (list of string, string)
626{
627	(tups, err) := attrdb->parseline(query, 0);
628	if(err != nil)
629		return (nil, "bad query: "+err);
630	if(tups == nil)
631		return (nil, "bad query");
632	pairs := tups.pairs;
633	a0 := (hd pairs).attr;
634	if(a0 == "ipinfo")
635		return (nil, "ipinfo not yet supported");
636	v0 := (hd pairs).val;
637
638	# if((a0 == "dom" || a0 == "ip") && v0 != nil){
639	# 	query dns ...
640	# }
641
642	ptr: ref Attrdb->Dbptr;
643	e: ref Dbentry;
644	for(;;){
645		(e, ptr) = ndb.findpair(ptr, a0, v0);
646		if(e == nil)
647			break;
648		for(l := e.lines; l != nil; l = tl l)
649			if(qmatch(hd l, tl pairs)){
650				ls: list of string;
651				for(l = e.lines; l != nil; l = tl l)
652					ls = tuptext(hd l) :: ls;
653				return (reverse(ls), nil);
654			}
655	}
656	return  (nil, "no match");
657}
658
659#
660# see if set of tuples t contains every non-* attr/val pair
661#
662qmatch(t: ref Tuples, av: list of ref Attr): int
663{
664Match:
665	for(; av != nil; av = tl av){
666		a := hd av;
667		for(pl := t.pairs; pl != nil; pl = tl pl)
668			if((hd pl).attr == a.attr &&
669			    (a.val == "*" || a.val == (hd pl).val))
670				continue Match;
671		return 0;
672	}
673	return 1;
674}
675
676tuptext(t: ref Tuples): string
677{
678	s: string;
679	for(pl := t.pairs; pl != nil; pl = tl pl){
680		p := hd pl;
681		if(s != nil)
682			s[len s] = ' ';
683		s += sys->sprint("%s=%q", p.attr, p.val);
684	}
685	return s;
686}
687