xref: /inferno-os/appl/cmd/fs/proto.b (revision 2b69dba5038ffd0b59cf30a4c44bce549e5097f8)
1implement Fsmodule;
2include "sys.m";
3	sys: Sys;
4include "readdir.m";
5	readdir: Readdir;
6include "bufio.m";
7	bufio: Bufio;
8	Iobuf: import bufio;
9include "string.m";
10	str: String;
11include "draw.m";
12include "sh.m";
13include "fslib.m";
14	fslib: Fslib;
15	Report, Value, type2s, report, quit: import fslib;
16	Fschan, Fsdata, Entrychan, Entry,
17	Gatechan, Gatequery, Nilentry, Option,
18	Next, Down, Skip, Quit: import Fslib;
19
20File: adt {
21	name: string;
22	mode: int;
23	owner: string;
24	group: string;
25	old: string;
26	flags: int;
27	sub: cyclic array of ref File;
28};
29
30Proto: adt {
31	indent: int;
32	lastline: string;
33	iob: ref Iobuf;
34};
35
36Star, Plus, Empty: con 1<<iota;
37
38types(): string
39{
40	return "xs-rs";
41}
42
43badmod(p: string)
44{
45	sys->fprint(sys->fildes(2), "fs: proto: cannot load %s: %r\n", p);
46	raise "fail:bad module";
47}
48
49init()
50{
51	sys = load Sys Sys->PATH;
52	fslib = load Fslib Fslib->PATH;
53	if(fslib == nil)
54		badmod(Fslib->PATH);
55	readdir = load Readdir Readdir->PATH;
56	if(readdir == nil)
57		badmod(Readdir->PATH);
58	bufio = load Bufio Bufio->PATH;
59	if(bufio == nil)
60		badmod(Bufio->PATH);
61	str = load String String->PATH;
62	if(str == nil)
63		badmod(String->PATH);
64}
65
66run(nil: ref Draw->Context, report: ref Report,
67			opts: list of Option, args: list of ref Value): ref Value
68{
69	protofile := (hd args).s().i;
70	rootpath: string;
71	if(opts != nil)
72		rootpath = (hd (hd opts).args).s().i;
73	if(rootpath == nil)
74		rootpath = "/";
75
76	proto := ref Proto(0, nil, nil);
77	if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){
78		sys->fprint(sys->fildes(2), "fs: proto: cannot open %q: %r\n", protofile);
79		return nil;
80	}
81	root := ref File(rootpath, ~0, nil, nil, nil, 0, nil);
82	(root.flags, root.sub) = readproto(proto, -1);
83	c := chan of (Fsdata, chan of int);
84	spawn protowalk(c, root, report.start("proto"));
85	return ref Value.X(c);
86}
87
88protowalk(c: Fschan, root: ref File, errorc: chan of string)
89{
90	protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, errorc);
91	quit(errorc);
92}
93
94protowalk1(c: Fschan, flags: int, path: string, d: ref Sys->Dir,
95		sub: array of ref File, errorc: chan of string): int
96{
97	reply := chan of int;
98	c <-= ((d, nil), reply);
99	case r := <-reply {
100	Quit =>
101		quit(errorc);
102	Next or
103	Skip =>
104		return r;
105	}
106	a: array of ref Sys->Dir;
107	n := 0;
108	if((flags&Empty)==0)
109		(a, n) = readdir->init(path, Readdir->NAME|Readdir->COMPACT);
110	i := j := 0;
111	prevsub: string;
112	while(i < n || j < len sub){
113		for(; j < len sub; j++){
114			s := sub[j].name;
115			if(s == prevsub){
116				report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s)));
117				continue;			# eliminate duplicates in proto
118			}
119			# if we're copying from an old file, and there's a matching
120			# entry in the directory, then skip it.
121			if(sub[j].old != nil && i < n && s == a[i].name)
122				i++;
123			if(sub[j].old != nil || i < n && s >= a[i].name)
124				break;
125			report(errorc, sys->sprint("%s not found", pathconcat(path, s)));
126		}
127		foundsub := j < len sub && (sub[j].old != nil || sub[j].name == a[i].name);
128
129		if(foundsub || flags&(Plus|Star)){
130			f: ref File;
131			if(foundsub){
132				f = sub[j++];
133				prevsub = f.name;
134			}
135			p: string;
136			d: ref Sys->Dir;
137			if(foundsub && f.old != nil){
138				p = f.old;
139				(ok, xd) := sys->stat(p);
140				if(ok == -1){
141					report(errorc, sys->sprint("cannot stat %q: %r", p));
142					continue;
143				}
144				d = ref xd;
145			}else{
146				p = pathconcat(path, a[i].name);
147				d = a[i++];
148			}
149
150			d = file2dir(f, d);
151			r: int;
152			if((d.mode & Sys->DMDIR) == 0)
153				r = walkfile(c, p, d, errorc);
154			else if(flags & Plus)
155				r = protowalk1(c, Plus, p, d, nil, errorc);
156			else if((flags&Star) && !foundsub)
157				r = protowalk1(c, Empty, p, d, nil, errorc);
158			else
159				r = protowalk1(c, f.flags, p, d, f.sub, errorc);
160			if(r == Skip)
161				return Next;
162		}else
163			i++;
164	}
165
166	c <-= ((nil, nil), reply);
167	if(<-reply == Quit)
168		quit(errorc);
169	return Next;
170}
171
172pathconcat(p, name: string): string
173{
174	if(p != nil && p[len p - 1] != '/')
175		p[len p] = '/';
176	p += name;
177	return p;
178}
179
180# from(ish) walk.b
181walkfile(c: Fschan, path: string, d: ref Sys->Dir, errorc: chan of string): int
182{
183	reply := chan of int;
184	fd := sys->open(path, Sys->OREAD);
185	if(fd == nil){
186		report(errorc, sys->sprint("cannot open %q: %r", path));
187		return Next;
188	}
189	c <-= ((d, nil), reply);
190	case r := <-reply {
191	Quit =>
192		quit(errorc);
193	Next or
194	Skip =>
195		return r;
196	}
197	length := d.length;
198	for(n := big 0; n < length; ){
199		nr := Sys->ATOMICIO;
200		if(n + big Sys->ATOMICIO > length)
201			nr = int (length - n);
202		buf := array[nr] of byte;
203		nr = sys->read(fd, buf, nr);
204		if(nr <= 0){
205			if(nr < 0)
206				report(errorc, sys->sprint("error reading %q: %r", path));
207			else
208				report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)",
209						path, n, length));
210			break;
211		}else if(nr < len buf)
212			buf = buf[0:nr];
213		c <-= ((nil, buf), reply);
214		case <-reply {
215		Quit =>
216			quit(errorc);
217		Skip =>
218			return Next;
219		}
220		n += big nr;
221	}
222	c <-= ((nil, nil), reply);
223	if(<-reply == Quit)
224		quit(errorc);
225	return Next;
226}
227
228readproto(proto: ref Proto, indent: int): (int, array of ref File)
229{
230	a := array[10] of ref File;
231	n := 0;
232	flags := 0;
233	while((f := readline(proto, indent)) != nil){
234		if(f.name == "*")
235			flags |= Star;
236		else if(f.name == "+")
237			flags |= Plus;
238		else{
239			(f.flags, f.sub) = readproto(proto, proto.indent);
240			if(n == len a)
241				a = (array[n * 2] of ref File)[0:] = a;
242			a[n++] = f;
243		}
244	}
245	if(n < len a)
246		a = (array[n] of ref File)[0:] = a[0:n];
247	mergesort(a, array[n] of ref File);
248	return (flags, a);
249}
250
251readline(proto: ref Proto, indent: int): ref File
252{
253	s: string;
254	if(proto.lastline != nil){
255		s = proto.lastline;
256		proto.lastline = nil;
257	}else if(proto.indent == -1)
258		return nil;
259	else if((s = proto.iob.gets('\n')) == nil){
260		proto.indent = -1;
261		return nil;
262	}
263	spc := 0;
264	for(i := 0; i < len s; i++){
265		c := s[i];
266		if(c == ' ')
267			spc++;
268		else if(c == '\t')
269			spc += 8;
270		else
271			break;
272	}
273	if(i == len s || s[i] == '#' || s[i] == '\n')
274		return readline(proto, indent);	# XXX sort out tail recursion!
275	if(spc <= indent){
276		proto.lastline = s;
277		return nil;
278	}
279	proto.indent = spc;
280	(nil, toks) := sys->tokenize(s, " \t\n");
281	f := ref File(nil, ~0, nil, nil, nil, 0, nil);
282	(f.name, toks) = (getname(hd toks, 0), tl toks);
283	if(toks == nil)
284		return f;
285	(f.mode, toks) = (getmode(hd toks), tl toks);
286	if(toks == nil)
287		return f;
288	(f.owner, toks) = (getname(hd toks, 1), tl toks);
289	if(toks == nil)
290		return f;
291	(f.group, toks) = (getname(hd toks, 1), tl toks);
292	if(toks == nil)
293		return f;
294	(f.old, toks) = (hd toks, tl toks);
295	return f;
296}
297
298mergesort(a, b: array of ref File)
299{
300	r := len a;
301	if (r > 1) {
302		m := (r-1)/2 + 1;
303		mergesort(a[0:m], b[0:m]);
304		mergesort(a[m:], b[m:]);
305		b[0:] = a;
306		for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
307			if(b[i].name > b[j].name)
308				a[k] = b[j++];
309			else
310				a[k] = b[i++];
311		}
312		if (i < m)
313			a[k:] = b[i:m];
314		else if (j < r)
315			a[k:] = b[j:r];
316	}
317}
318
319getname(s: string, allowminus: int): string
320{
321	if(s == nil)
322		return nil;
323	if(allowminus && s == "-")
324		return nil;
325	if(s[0] == '$')
326		return getenv(s[1:]);
327	return s;
328}
329
330getenv(s: string): string
331{
332	# XXX implement env variables
333	return nil;
334}
335
336getmode(s: string): int
337{
338	s = getname(s, 1);
339	if(s == nil)
340		return ~0;
341	m := 0;
342	i := 0;
343	if(s[i] == 'd'){
344		m |= Sys->DMDIR;
345		i++;
346	}
347	if(i < len s && s[i] == 'a'){
348		m |= Sys->DMAPPEND;
349		i++;
350	}
351	if(i < len s && s[i] == 'l'){
352		m |= Sys->DMEXCL;
353		i++;
354	}
355	(xmode, t) := str->toint(s, 8);
356	if(t != nil){
357		# report(aux.errorc, "bad mode specification %q", s);
358		return ~0;
359	}
360	return xmode | m;
361}
362
363file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir
364{
365	d := ref Sys->nulldir;
366	if(old != nil){
367		if(old.dtype != 'M'){
368			d.uid = "sys";
369			d.gid = "sys";
370			xmode := (old.mode >> 6) & 7;
371			d.mode = old.mode | xmode | (xmode << 3);
372		}else{
373			d.uid = old.uid;
374			d.gid = old.gid;
375			d.mode = old.mode;
376		}
377		d.length = old.length;
378		d.mtime = old.mtime;
379		d.atime = old.atime;
380		d.muid = old.muid;
381		d.name = old.name;
382	}
383	if(f != nil){
384		d.name = f.name;
385		if(f.owner != nil)
386			d.uid = f.owner;
387		if(f.group != nil)
388			d.gid = f.group;
389		if(f.mode != ~0)
390			d.mode = f.mode;
391	}
392	return d;
393}
394