xref: /inferno-os/appl/cmd/archfs.b (revision 3f1f06c5d12b24c4061e5123acabf72348ff45a2)
1implement Archfs;
2
3include "sys.m";
4	sys: Sys;
5include "draw.m";
6
7include "bufio.m";
8	bufio: Bufio;
9
10include "string.m";
11	str: String;
12
13include "daytime.m";
14	daytime: Daytime;
15
16include "styx.m";
17	styx: Styx;
18	NOFID: import Styx;
19
20include "arg.m";
21
22Archfs: module
23{
24	init:	fn(nil: ref Draw->Context, nil: list of string);
25};
26
27Ahdr: adt {
28	name: string;
29	modestr: string;
30	d: ref Sys->Dir;
31};
32
33Archive: adt {
34	b: ref Bufio->Iobuf;
35	nexthdr: big;
36	canseek: int;
37	hdr: ref Ahdr;
38	err: string;
39};
40
41Iobuf: import bufio;
42Tmsg, Rmsg: import styx;
43
44Einuse		: con "fid already in use";
45Ebadfid		: con "bad fid";
46Eopen		: con "fid already opened";
47Enotfound	: con "file does not exist";
48Enotdir		: con "not a directory";
49Eperm		: con "permission denied";
50
51UID: con "inferno";
52GID: con "inferno";
53
54debug := 0;
55
56Dir: adt {
57	dir: Sys->Dir;
58	offset: big;
59	parent: cyclic ref Dir;
60	child: cyclic ref Dir;
61	sibling: cyclic ref Dir;
62};
63
64Fid: adt {
65	fid:	int;
66	open:	int;
67	dir:	ref Dir;
68};
69
70HTSZ: con 32;
71fidtab := array[HTSZ] of list of ref Fid;
72
73root: ref Dir;
74qid: int;
75mtpt := "/mnt/arch";
76bio: ref Iobuf;
77buf: array of byte;
78skip := 0;
79
80init(nil: ref Draw->Context, args: list of string)
81{
82	sys = load Sys Sys->PATH;
83	bufio = load Bufio Bufio->PATH;
84	str = load String String->PATH;
85	daytime = load Daytime Daytime->PATH;
86	styx = load Styx Styx->PATH;
87	if(bufio == nil || styx == nil || daytime == nil || str == nil)
88		fatal("failed to load modules");
89	styx->init();
90
91	flags := Sys->MREPL;
92	arg := load Arg Arg->PATH;
93	if(arg == nil)
94		fatal("failed to load "+Arg->PATH);
95	arg->init(args);
96	arg->setusage("archfs [-ab] [-m mntpt] archive [prefix ...]");
97	while((c := arg->opt()) != 0){
98		case c {
99		'D' =>
100			debug = 1;
101		'a' =>
102			flags = Sys->MAFTER;
103		'b' =>
104			flags = Sys->MBEFORE;
105		'm' =>
106			mtpt = arg->earg();
107		's' =>
108			skip = 1;
109		* =>
110			arg->usage();
111		}
112	}
113	args = arg->argv();
114	if(args == nil)
115		arg->usage();
116	arg = nil;
117
118	buf = array[Sys->ATOMICIO] of byte;
119	# root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now());
120	root = newdir(basename(mtpt), UID, GID, 8r555|Sys->DMDIR, daytime->now());
121	root.parent = root;
122	readarch(hd args, tl args);
123	p := array[2] of ref Sys->FD;
124	if(sys->pipe(p) < 0)
125		fatal("can't create pipe");
126	pidch := chan of int;
127	spawn serve(p[1], pidch);
128	<- pidch;
129	if(sys->mount(p[0], nil, mtpt, flags, nil) < 0)
130		fatal(sys->sprint("cannot mount archive on %s: %r", mtpt));
131}
132
133reply(fd: ref Sys->FD, m: ref Rmsg): int
134{
135	if(debug)
136		sys->fprint(sys->fildes(2), "-> %s\n", m.text());
137	s := m.pack();
138	if(s == nil)
139		return -1;
140	return sys->write(fd, s, len s);
141}
142
143error(fd: ref Sys->FD, m: ref Tmsg, e: string)
144{
145	reply(fd, ref Rmsg.Error(m.tag, e));
146}
147
148serve(fd: ref Sys->FD, pidch: chan of int)
149{
150	e: string;
151	f: ref Fid;
152
153	pidch <-= sys->pctl(Sys->NEWNS|Sys->NEWFD, 1 :: 2 :: fd.fd :: bio.fd.fd :: nil);
154	bio.fd = sys->fildes(bio.fd.fd);
155	fd = sys->fildes(fd.fd);
156Work:
157	while((m0 := Tmsg.read(fd, Styx->MAXRPC)) != nil){
158		if(debug)
159			sys->fprint(sys->fildes(2), "<- %s\n", m0.text());
160		pick m := m0 {
161		Readerror =>
162			fatal("read error on styx server");
163		Version =>
164			(s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION);
165			reply(fd, ref Rmsg.Version(m.tag, s, v));
166		Auth =>
167			error(fd, m, "authentication not required");
168		Flush =>
169			reply(fd, ref Rmsg.Flush(m.tag));
170		Walk =>
171			(f, e) = mapfid(m.fid);
172			if(e != nil){
173				error(fd, m, e);
174				continue;
175			}
176			if(f.open){
177				error(fd, m, Eopen);
178				continue;
179			}
180			dir := f.dir;
181			nq := 0;
182			nn := len m.names;
183			qids := array[nn] of Sys->Qid;
184			if(nn > 0){
185				for(k := 0; k < nn; k++){
186					if((dir.dir.mode & Sys->DMDIR) == 0){
187						if(k == 0){
188							error(fd, m, Enotdir);
189							continue Work;
190						}
191						break;
192					}
193					dir  = lookup(dir, m.names[k]);
194					if(dir == nil){
195						if(k == 0){
196							error(fd, m, Enotfound);
197							continue Work;
198						}
199						break;
200					}
201					qids[nq++] = dir.dir.qid;
202				}
203			}
204			if(nq < nn)
205				qids = qids[0: nq];
206			if(nq == nn){
207				if(m.newfid != m.fid){
208					f = newfid(m.newfid);
209					if(f == nil){
210						error(fd, m, Einuse);
211						continue Work;
212					}
213				}
214				f.dir = dir;
215			}
216			reply(fd, ref Rmsg.Walk(m.tag, qids));
217		Open =>
218			(f, e) = mapfid(m.fid);
219			if(e != nil){
220				error(fd, m, e);
221				continue;
222			}
223			if(m.mode != Sys->OREAD){
224				error(fd, m, Eperm);
225				continue;
226			}
227			f.open = 1;
228			reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA));
229		Create =>
230			error(fd, m, Eperm);
231		Read =>
232			(f, e) = mapfid(m.fid);
233			if(e != nil){
234				error(fd, m, e);
235				continue;
236			}
237			data := read(f.dir, m.offset, m.count);
238			reply(fd, ref Rmsg.Read(m.tag, data));
239		Write =>
240			error(fd, m, Eperm);
241		Clunk =>
242			(f, e) = mapfid(m.fid);
243			if(e != nil){
244				error(fd, m, e);
245				continue;
246			}
247			freefid(f);
248			reply(fd, ref Rmsg.Clunk(m.tag));
249		Stat =>
250			(f, e) = mapfid(m.fid);
251			if(e != nil){
252				error(fd, m, e);
253				continue;
254			}
255			reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir));
256		Remove =>
257			error(fd, m, Eperm);
258		Wstat =>
259			error(fd, m, Eperm);
260		Attach =>
261			f = newfid(m.fid);
262			if(f == nil){
263				error(fd, m, Einuse);
264				continue;
265			}
266			f.dir = root;
267			reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid));
268		* =>
269			fatal("unknown styx message");
270		}
271	}
272}
273
274newfid(fid: int): ref Fid
275{
276	if(fid == NOFID)
277		return nil;
278	hv := hashval(fid);
279	ff: ref Fid;
280	for(l := fidtab[hv]; l != nil; l = tl l){
281		f := hd l;
282		if(f.fid == fid)
283			return nil;
284		if(ff == nil && f.fid == NOFID)
285			ff = f;
286	}
287	if((f := ff) == nil){
288		f = ref Fid;
289		fidtab[hv] = f :: fidtab[hv];
290	}
291	f.fid = fid;
292	f.open = 0;
293	return f;
294}
295
296freefid(f: ref Fid)
297{
298	hv := hashval(f.fid);
299	for(l := fidtab[hv]; l != nil; l = tl l)
300		if(hd l == f){
301			f.fid = NOFID;
302			f.dir = nil;
303			f.open = 0;
304			return;
305		}
306	fatal("cannot find fid");
307}
308
309mapfid(fid: int): (ref Fid, string)
310{
311	if(fid == NOFID)
312		return (nil, Ebadfid);
313	hv := hashval(fid);
314	for(l := fidtab[hv]; l != nil; l = tl l){
315		f := hd l;
316		if(f.fid == fid){
317			if(f.dir == nil)
318				return (nil, Enotfound);
319			return (f, nil);
320		}
321	}
322	return (nil, Ebadfid);
323}
324
325hashval(n: int): int
326{
327	n %= HTSZ;
328	if(n < 0)
329		n += HTSZ;
330	return n;
331}
332
333readarch(f: string, args: list of string)
334{
335	ar := openarch(f);
336	if(ar == nil || ar.b == nil)
337		fatal(sys->sprint("cannot open %s: %r", f));
338	bio = ar.b;
339	while((a := gethdr(ar)) != nil){
340		if(args != nil){
341			if(!selected(a.name, args)){
342				if(skip)
343					return;
344				#drain(ar, int a.d.length);
345				continue;
346			}
347			mkdirs("/", a.name);
348		}
349		d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0);
350		if((a.d.mode & Sys->DMDIR) == 0){
351			d.dir.length = a.d.length;
352			d.offset = bio.offset();
353		}
354		#drain(ar, int a.d.length);
355	}
356	if(ar.err != nil)
357		fatal(ar.err);
358}
359
360selected(s: string, args: list of string): int
361{
362	for(; args != nil; args = tl args)
363		if(fileprefix(hd args, s))
364			return 1;
365	return 0;
366}
367
368fileprefix(prefix, s: string): int
369{
370	n := len prefix;
371	m := len s;
372	if(n > m || !str->prefix(prefix, s))
373		return 0;
374	if(m > n && s[n] != '/')
375		return 0;
376	return 1;
377}
378
379basename(f: string): string
380{
381	for(i := len f; i > 0; )
382		if(f[--i] == '/')
383			return f[i+1:];
384	return f;
385}
386
387split(p: string): (string, string)
388{
389	if(p == nil)
390		fatal("nil string in split");
391	if(p[0] != '/')
392		fatal("p0 not / in split");
393	while(p[0] == '/')
394		p = p[1:];
395	i := 0;
396	while(i < len p && p[i] != '/')
397		i++;
398	if(i == len p)
399		return (p, nil);
400	else
401		return (p[0:i], p[i:]);
402}
403
404mkdirs(basedir, name: string)
405{
406	(nil, names) := sys->tokenize(name, "/");
407	while(names != nil){
408		# sys->print("mkdir %s\n", basedir);
409		mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
410		if(tl names == nil)
411			break;
412		basedir = basedir + "/" + hd names;
413		names = tl names;
414	}
415}
416
417read(d: ref Dir, offset: big, n: int): array of byte
418{
419	if(d.dir.mode & Sys->DMDIR)
420		return readdir(d, int offset, n);
421	return readfile(d, offset, n);
422}
423
424readdir(d: ref Dir, o: int, n: int): array of byte
425{
426	k := 0;
427	m := 0;
428	b := array[n] of byte;
429	for(s := d.child; s != nil; s = s.sibling){
430		l := styx->packdirsize(s.dir);
431		if(k < o){
432			k += l;
433			continue;
434		}
435		if(m+l > n)
436			break;
437		b[m: ] = styx->packdir(s.dir);
438		m += l;
439	}
440	return b[0: m];
441}
442
443readfile(d: ref Dir, offset: big, n: int): array of byte
444{
445	if(offset+big n > d.dir.length)
446		n = int(d.dir.length-offset);
447	if(n <= 0 || offset < big 0)
448		return nil;
449	bio.seek(d.offset+offset, Bufio->SEEKSTART);
450	a := array[n] of byte;
451	p := 0;
452	m := 0;
453	for( ; n != 0; n -= m){
454		l := len buf;
455		if(n < l)
456			l = n;
457		m = bio.read(buf, l);
458		if(m <= 0 || m != l)
459			fatal("premature eof");
460		a[p:] = buf[0:m];
461		p += m;
462	}
463	return a;
464}
465
466mkdir(f: string, mode: int, mtime: int, uid: string, gid: string, existsok: int): ref Dir
467{
468	if(f == "/")
469		return nil;
470	d := newdir(basename(f), uid, gid, mode, mtime);
471	addfile(d, f, existsok);
472	return d;
473}
474
475addfile(d: ref Dir, path: string, existsok: int)
476{
477	elem: string;
478
479	opath := path;
480	p := prev := root;
481	basedir := "";
482# sys->print("addfile %s: %s\n", d.dir.name, path);
483	while(path != nil){
484		(elem, path) = split(path);
485		basedir += "/" + elem;
486		op := p;
487		p = lookup(p, elem);
488		if(path == nil){
489			if(p != nil){
490				if(!existsok && (p.dir.mode&Sys->DMDIR) == 0)
491					sys->fprint(sys->fildes(2), "addfile: %s already there", opath);
492					# fatal(sys->sprint("addfile: %s already there", opath));
493				return;
494			}
495			if(prev.child == nil)
496				prev.child = d;
497			else {
498				for(s := prev.child; s.sibling != nil; s = s.sibling)
499					;
500				s.sibling = d;
501			}
502			d.parent = prev;
503		}
504		else {
505			if(p == nil){
506				mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1);
507				p = lookup(op, elem);
508				if(p == nil)
509					fatal("bad file system");
510			}
511		}
512		prev = p;
513	}
514}
515
516lookup(p: ref Dir, f: string): ref Dir
517{
518	if((p.dir.mode&Sys->DMDIR) == 0)
519		fatal("not a directory in lookup");
520	if(f == ".")
521		return p;
522	if(f == "..")
523		return p.parent;
524	for(d := p.child; d != nil; d = d.sibling)
525		if(d.dir.name == f)
526			return d;
527	return nil;
528}
529
530newdir(name, uid, gid: string, mode, mtime: int): ref Dir
531{
532	dir := sys->zerodir;
533	dir.name = name;
534	dir.uid = uid;
535	dir.gid = gid;
536	dir.mode = mode;
537	dir.qid.path = big (qid++);
538	dir.qid.qtype = mode>>24;
539	dir.qid.vers = 0;
540	dir.atime = dir.mtime = mtime;
541	dir.length = big 0;
542
543	d := ref Dir;
544	d.dir = dir;
545	d.offset = big 0;
546	return d;
547}
548
549prd(d: ref Dir)
550{
551	dir := d.dir;
552	sys->print("%q %q %q %bx %x %x %d %d %bd %d %d %bd\n",
553		dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset);
554}
555
556fatal(e: string)
557{
558	sys->fprint(sys->fildes(2), "archfs: %s\n", e);
559	raise "fail:error";
560}
561
562openarch(file: string): ref Archive
563{
564	b := bufio->open(file, Bufio->OREAD);
565	if(b == nil)
566		return nil;
567	ar := ref Archive;
568	ar.b = b;
569	ar.nexthdr = big 0;
570	ar.canseek = 1;
571	ar.hdr = ref Ahdr;
572	ar.hdr.d = ref Sys->Dir;
573	return ar;
574}
575
576NFLDS: con 6;
577
578gethdr(ar: ref Archive): ref Ahdr
579{
580	a := ar.hdr;
581	b := ar.b;
582	m := b.offset();
583	n := ar.nexthdr;
584	if(m != n){
585		if(ar.canseek)
586			b.seek(n, Bufio->SEEKSTART);
587		else {
588			if(m > n)
589				fatal(sys->sprint("bad offset in gethdr: m=%bd n=%bd", m, n));
590			if(drain(ar, int(n-m)) < 0)
591				return nil;
592		}
593	}
594	if((s := b.gets('\n')) == nil){
595		ar.err = "premature end of archive";
596		return nil;
597	}
598	if(s == "end of archive\n")
599		return nil;
600	(nf, fs) := sys->tokenize(s, " \t\n");
601	if(nf != NFLDS){
602		ar.err = "too few fields in file header";
603		return nil;
604	}
605	a.name = hd fs;						fs = tl fs;
606	(a.d.mode, nil) = str->toint(hd fs, 8);		fs = tl fs;
607	a.d.uid = hd fs;						fs = tl fs;
608	a.d.gid = hd fs;						fs = tl fs;
609	(a.d.mtime, nil) = str->toint(hd fs, 10);	fs = tl fs;
610	(tmp, nil) := str->toint(hd fs, 10);		fs = tl fs;
611	a.d.length = big tmp;
612	ar.nexthdr = b.offset()+a.d.length;
613	return a;
614}
615
616drain(ar: ref Archive, n: int): int
617{
618	while(n > 0){
619		m := n;
620		if(m > len buf)
621			m = len buf;
622		p := ar.b.read(buf, m);
623		if(p != m){
624			ar.err = "unexpectedly short read";
625			return -1;
626		}
627		n -= m;
628	}
629	return 0;
630}
631