xref: /inferno-os/appl/cmd/sh/file2chan.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Shellbuiltin;
2
3include "sys.m";
4	sys: Sys;
5include "draw.m";
6include "lock.m";
7	lock: Lock;
8	Semaphore: import lock;
9include "sh.m";
10	sh: Sh;
11	Listnode, Context: import sh;
12	myself: Shellbuiltin;
13
14Tag: adt {
15	tagid, blocked: int;
16	offset, fid: int;
17	pick {
18	Read =>
19		count: int;
20		rc: chan of (array of byte, string);
21	Write =>
22		data: array of byte;
23		wc: chan of (int, string);
24	}
25};
26
27taglock: ref Lock->Semaphore;
28maxtagid := 1;
29tags := array[16] of list of ref Tag;
30
31initbuiltin(ctxt: ref Context, shmod: Sh): string
32{
33	sys = load Sys Sys->PATH;
34	sh = shmod;
35
36	myself = load Shellbuiltin "$self";
37	if (myself == nil)
38		ctxt.fail("bad module", sys->sprint("file2chan: cannot load self: %r"));
39
40	lock = load Lock Lock->PATH;
41	if (lock == nil) ctxt.fail("bad module", sys->sprint("file2chan: cannot load %s: %r", Lock->PATH));
42	lock->init();
43
44	taglock = Semaphore.new();
45	if (taglock == nil)
46		ctxt.fail("no lock", "file2chan: cannot make lock");
47
48
49	ctxt.addbuiltin("file2chan", myself);
50	ctxt.addbuiltin("rblock", myself);
51	ctxt.addbuiltin("rread", myself);
52	ctxt.addbuiltin("rreadone", myself);
53	ctxt.addbuiltin("rwrite", myself);
54	ctxt.addbuiltin("rerror", myself);
55	ctxt.addbuiltin("fetchwdata", myself);
56	ctxt.addbuiltin("putrdata", myself);
57	ctxt.addsbuiltin("rget", myself);
58
59	return nil;
60}
61
62whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
63{
64	return nil;
65}
66
67getself(): Shellbuiltin
68{
69	return myself;
70}
71
72runbuiltin(ctxt: ref Context, nil: Sh,
73			cmd: list of ref Listnode, nil: int): string
74{
75	case (hd cmd).word {
76	"file2chan" =>		return builtin_file2chan(ctxt, cmd);
77	"rblock" =>		return builtin_rblock(ctxt, cmd);
78	"rread" =>			return builtin_rread(ctxt, cmd, 0);
79	"rreadone" =>		return builtin_rread(ctxt, cmd, 1);
80	"rwrite" =>		return builtin_rwrite(ctxt, cmd);
81	"rerror" =>		return builtin_rerror(ctxt, cmd);
82	"fetchwdata" =>	return builtin_fetchwdata(ctxt, cmd);
83	"putrdata" =>		return builtin_putrdata(ctxt, cmd);
84	}
85	return nil;
86}
87
88runsbuiltin(ctxt: ref Context, nil: Sh,
89			argv: list of ref Listnode): list of ref Listnode
90{
91	# could add ${rtags} to retrieve list of currently outstanding tags
92	case (hd argv).word {
93	"rget" =>			return sbuiltin_rget(ctxt, argv);
94	}
95	return nil;
96}
97
98builtin_file2chan(ctxt: ref Context, argv: list of ref Listnode): string
99{
100	rcmd, wcmd, ccmd: ref Listnode;
101	path: string;
102
103	n := len argv;
104	if (n < 4 || n > 5)
105		ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
106
107	(path, argv) = ((hd tl argv).word, tl tl argv);
108	(rcmd, argv) = (hd argv, tl argv);
109	(wcmd, argv) = (hd argv, tl argv);
110	if (argv != nil)
111		ccmd = hd argv;
112	if (path == nil || !iscmd(rcmd) || !iscmd(wcmd) || (ccmd != nil && !iscmd(ccmd)))
113		ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]");
114
115	(dir, f) := pathsplit(path);
116	if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) {
117		reporterror(ctxt, sys->sprint("file2chan: cannot bind #s: %r"));
118		return "no #s";
119	}
120	fio := sys->file2chan(dir, f);
121	if (fio == nil) {
122		reporterror(ctxt, sys->sprint("file2chan: cannot make %s: %r", path));
123		return "cannot make chan";
124	}
125	sync := chan of int;
126	spawn srv(sync, ctxt, fio, rcmd, wcmd, ccmd);
127	apid := <-sync;
128	ctxt.set("apid", ref Listnode(nil, string apid) :: nil);
129	if (ctxt.options() & ctxt.INTERACTIVE)
130		sys->fprint(sys->fildes(2), "%d\n", apid);
131	return nil;
132}
133
134srv(sync: chan of int, ctxt: ref Context,
135		fio: ref Sys->FileIO, rcmd, wcmd, ccmd: ref Listnode)
136{
137	ctxt = ctxt.copy(1);
138	sync <-= sys->pctl(0, nil);
139	for (;;) {
140		fid, offset, count: int;
141		rc: Sys->Rread;
142		wc: Sys->Rwrite;
143		d: array of byte;
144		t: ref Tag = nil;
145		cmd: ref Listnode = nil;
146		alt {
147		(offset, count, fid, rc) = <-fio.read =>
148			if (rc != nil) {
149				t = ref Tag.Read(0, 0, offset, fid, count, rc);
150				cmd = rcmd;
151			} else
152				continue;		# we get a close on both read and write...
153		(offset, d, fid, wc) = <-fio.write =>
154			if (wc != nil) {
155				t = ref Tag.Write(0, 0, offset, fid, d, wc);
156				cmd = wcmd;
157			}
158		}
159		if (t != nil) {
160			addtag(t);
161			ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
162			ctxt.run(cmd :: nil, 0);
163			taglock.obtain();
164			# make a default reply if it hasn't been deliberately blocked.
165			del := 0;
166			if (t.tagid >= 0 && !t.blocked) {
167				pick mt := t {
168				Read =>
169					rreply(mt.rc, nil, "invalid read");
170				Write =>
171					wreply(mt.wc, len mt.data, nil);
172				}
173				del = 1;
174			}
175			taglock.release();
176			if (del)
177				deltag(t.tagid);
178			ctxt.setlocal("tag", nil);
179		} else if (ccmd != nil) {
180			t = ref Tag.Read(0, 0, -1, fid, -1, nil);
181			addtag(t);
182			ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil);
183			ctxt.run(ccmd :: nil, 0);
184			deltag(t.tagid);
185			ctxt.setlocal("tag", nil);
186		}
187	}
188}
189
190builtin_rread(ctxt: ref Context, argv: list of ref Listnode, one: int): string
191{
192	n := len argv;
193	if (n < 2 || n > 3)
194		ctxt.fail("usage", "usage: "+(hd argv).word+" [tag] data");
195	argv = tl argv;
196
197	t := envgettag(ctxt, argv, n == 3);
198	if (t == nil)
199		ctxt.fail("bad tag", "rread: cannot find tag");
200	if (n == 3)
201		argv = tl argv;
202	mt := etr(ctxt, "rread", t);
203	arg := word(hd argv);
204	d := array of byte arg;
205	if (one) {
206		if (mt.offset >= len d)
207			d = nil;
208		else
209			d = d[mt.offset:];
210	}
211	if (len d > mt.count)
212		d = d[0:mt.count];
213	rreply(mt.rc, d, nil);
214	deltag(t.tagid);
215	return nil;
216}
217
218builtin_rwrite(ctxt: ref Context, argv: list of ref Listnode): string
219{
220	n := len argv;
221	if (n > 3)
222		ctxt.fail("usage", "usage: rwrite [tag [count]]");
223	t := envgettag(ctxt, tl argv, n > 1);
224	if (t == nil)
225		ctxt.fail("bad tag", "rwrite: cannot find tag");
226
227	mt := etw(ctxt, "rwrite", t);
228	count := len mt.data;
229	if (n == 3) {
230		arg := word(hd tl argv);
231		if (!isnum(arg))
232			ctxt.fail("usage", "usage: freply [tag [count]]");
233		count = int arg;
234	}
235	wreply(mt.wc, count, nil);
236	deltag(t.tagid);
237	return nil;
238}
239
240builtin_rblock(ctxt: ref Context, argv: list of ref Listnode): string
241{
242	argv = tl argv;
243	if (len argv > 1)
244		ctxt.fail("usage", "usage: rblock [tag]");
245	t := envgettag(ctxt, argv, argv != nil);
246	if (t == nil)
247		ctxt.fail("bad tag", "rblock: cannot find tag");
248	t.blocked = 1;
249	return nil;
250}
251
252sbuiltin_rget(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
253{
254	n := len argv;
255	if (n < 2 || n > 3)
256		ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
257	argv = tl argv;
258	t := envgettag(ctxt, tl argv, tl argv != nil);
259	if (t == nil)
260		ctxt.fail("bad tag", "rget: cannot find tag");
261	s := "";
262	case (hd argv).word {
263	"data" =>
264		s = string etw(ctxt, "rget", t).data;
265	"count" =>
266		s = string etr(ctxt, "rget", t).count;
267	"offset" =>
268		s = string t.offset;
269	"fid" =>
270		s = string t.fid;
271	* =>
272		ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]");
273	}
274
275	return ref Listnode(nil, s) :: nil;
276}
277
278builtin_fetchwdata(ctxt: ref Context, argv: list of ref Listnode): string
279{
280	argv = tl argv;
281	if (len argv > 1)
282		ctxt.fail("usage", "usage: fetchwdata [tag]");
283	t := envgettag(ctxt, argv, argv != nil);
284	if (t == nil)
285		ctxt.fail("bad tag", "fetchwdata: cannot find tag");
286	d := etw(ctxt, "fetchwdata", t).data;
287	sys->write(sys->fildes(1), d, len d);
288	return nil;
289}
290
291builtin_putrdata(ctxt: ref Context, argv: list of ref Listnode): string
292{
293	argv = tl argv;
294	if (len argv > 1)
295		ctxt.fail("usage", "usage: putrdata [tag]");
296	t := envgettag(ctxt, argv, argv != nil);
297	if (t == nil)
298		ctxt.fail("bad tag", "putrdata: cannot find tag");
299	mt := etr(ctxt, "putrdata", t);
300	buf := array[mt.count] of byte;
301	n := 0;
302	fd := sys->fildes(0);
303	while (n < mt.count) {
304		nr := sys->read(fd, buf[n:mt.count], mt.count - n);
305		if (nr <= 0)
306			break;
307		n += nr;
308	}
309
310	rreply(mt.rc, buf[0:n], nil);
311	deltag(t.tagid);
312	return nil;
313}
314
315builtin_rerror(ctxt: ref Context, argv: list of ref Listnode): string
316{
317	# usage: ferror [tag] error
318	n := len argv;
319	if (n < 2 || n > 3)
320		ctxt.fail("usage", "usage: ferror [tag] error");
321	t := envgettag(ctxt, tl argv, n == 3);
322	if (t == nil)
323		ctxt.fail("bad tag", "rerror: cannot find tag");
324	if (n == 3)
325		argv = tl argv;
326	err := word(hd tl argv);
327	pick mt := t {
328	Read =>
329		rreply(mt.rc, nil, err);
330	Write =>
331		wreply(mt.wc, 0, err);
332	}
333	deltag(t.tagid);
334	return nil;
335}
336
337envgettag(ctxt: ref Context, args: list of ref Listnode, useargs: int): ref Tag
338{
339	tagid: int;
340	if (useargs)
341		tagid = int (hd args).word;
342	else {
343		args = ctxt.get("tag");
344		if (args == nil || tl args != nil)
345			return nil;
346		tagid = int (hd args).word;
347	}
348	return gettag(tagid);
349}
350
351etw(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Write
352{
353	pick mt := t {
354	Write =>	return mt;
355	}
356	ctxt.fail("bad tag", cmd + ": inappropriate tag id");
357	return nil;
358}
359
360etr(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Read
361{
362	pick mt := t {
363	Read =>	return mt;
364	}
365	ctxt.fail("bad tag", cmd + ": inappropriate tag id");
366	return nil;
367}
368
369wreply(wc: chan of (int, string), count: int, err: string)
370{
371	alt {
372	wc <-= (count, err) => ;
373	* => ;
374	}
375}
376
377rreply(rc: chan of (array of byte, string), d: array of byte, err: string)
378{
379	alt {
380	rc <-= (d, err) => ;
381	* => ;
382	}
383}
384
385word(n: ref Listnode): string
386{
387	if (n.word != nil)
388		return n.word;
389	if (n.cmd != nil)
390		n.word = sh->cmd2string(n.cmd);
391	return n.word;
392}
393
394isnum(s: string): int
395{
396	for (i := 0; i < len s; i++)
397		if (s[i] > '9' || s[i] < '0')
398			return 0;
399	return 1;
400}
401
402iscmd(n: ref Listnode): int
403{
404	return n.cmd != nil || (n.word != nil && n.word[0] == '}');
405}
406
407addtag(t: ref Tag)
408{
409	taglock.obtain();
410	t.tagid = maxtagid++;
411	slot := t.tagid % len tags;
412	tags[slot] = t :: tags[slot];
413	taglock.release();
414}
415
416deltag(tagid: int)
417{
418	taglock.obtain();
419	slot := tagid % len tags;
420	nwl: list of ref Tag;
421	for (wl := tags[slot]; wl != nil; wl = tl wl)
422		if ((hd wl).tagid != tagid)
423			nwl = hd wl :: nwl;
424		else
425			(hd wl).tagid = -1;
426	tags[slot] = nwl;
427	taglock.release();
428}
429
430gettag(tagid: int): ref Tag
431{
432	slot := tagid % len tags;
433	for (wl := tags[slot]; wl != nil; wl = tl wl)
434		if ((hd wl).tagid == tagid)
435			return hd wl;
436	return nil;
437}
438
439pathsplit(p: string): (string, string)
440{
441	for (i := len p - 1; i >= 0; i--)
442		if (p[i] != '/')
443			break;
444	if (i < 0)
445		return (p, nil);
446	p = p[0:i+1];
447	for (i = len p - 1; i >=0; i--)
448		if (p[i] == '/')
449			break;
450	if (i < 0)
451		return (".", p);
452	return (p[0:i+1], p[i+1:]);
453}
454
455reporterror(ctxt: ref Context, err: string)
456{
457	if (ctxt.options() & ctxt.VERBOSE)
458		sys->fprint(sys->fildes(2), "%s\n", err);
459}
460