xref: /inferno-os/appl/cmd/tarfs.b (revision d78912f045c51c0ac413a65d982c926f397160e2)
1implement Tarfs;
2
3#
4# Copyright © 2003 Vita Nuova Holdings Limited.  All rights reserved.
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "daytime.m";
13	daytime: Daytime;
14
15include "arg.m";
16
17include "styx.m";
18	styx: Styx;
19	Tmsg, Rmsg: import styx;
20
21include "styxservers.m";
22	styxservers: Styxservers;
23	Fid, Styxserver, Navigator, Navop: import styxservers;
24	Enotfound: import styxservers;
25
26Tarfs: module
27{
28	init: fn(nil: ref Draw->Context, nil: list of string);
29};
30
31File: adt {
32	x:	int;
33	name:	string;
34	mode:	int;
35	uid:	string;
36	gid:	string;
37	mtime:	int;
38	length:	big;
39	offset:	big;
40	parent:	cyclic ref File;
41	children:	cyclic list of ref File;
42
43	find:		fn(f: self ref File, name: string): ref File;
44	enter:	fn(d: self ref File, f: ref File);
45	stat:		fn(d: self ref File): ref Sys->Dir;
46};
47
48tarfd: ref Sys->FD;
49pflag: int;
50root: ref File;
51files: array of ref File;
52pathgen: int;
53
54error(s: string)
55{
56	sys->fprint(sys->fildes(2), "tarfs: %s\n", s);
57	raise "fail:error";
58}
59
60checkload[T](m: T, path: string)
61{
62	if(m == nil)
63		error(sys->sprint("can't load %s: %r", path));
64}
65
66init(nil: ref Draw->Context, args: list of string)
67{
68	sys = load Sys Sys->PATH;
69	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
70	styx = load Styx Styx->PATH;
71	checkload(styx, Styx->PATH);
72	styx->init();
73	styxservers = load Styxservers Styxservers->PATH;
74	checkload(styxservers, Styxservers->PATH);
75	styxservers->init(styx);
76	daytime = load Daytime Daytime->PATH;
77	checkload(daytime, Daytime->PATH);
78
79	arg := load Arg Arg->PATH;
80	checkload(arg, Arg->PATH);
81	arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint");
82	arg->init(args);
83	flags := Sys->MREPL;
84	while((o := arg->opt()) != 0)
85		case o {
86		'a' =>	flags = Sys->MAFTER;
87		'b' =>	flags = Sys->MBEFORE;
88		'D' =>	styxservers->traceset(1);
89		'p' =>	pflag++;
90		* =>		arg->usage();
91		}
92	args = arg->argv();
93	if(len args != 2)
94		arg->usage();
95	arg = nil;
96
97	file := hd args;
98	args = tl args;
99	mountpt := hd args;
100
101	sys->pctl(Sys->FORKFD, nil);
102
103	files = array[100] of ref File;
104	root = files[0] = ref File;
105	root.x = 0;
106	root.name = "/";
107	root.mode = Sys->DMDIR | 8r555;
108	root.uid = "0";
109	root.gid = "0";
110	root.length = big 0;
111	root.offset = big 0;
112	root.mtime = 0;
113	pathgen = 1;
114
115	tarfd = sys->open(file, Sys->OREAD);
116	if(tarfd == nil)
117		error(sys->sprint("can't open %s: %r", file));
118	if(readtar(tarfd) < 0)
119		error(sys->sprint("error reading %s: %r", file));
120
121	fds := array[2] of ref Sys->FD;
122	if(sys->pipe(fds) < 0)
123		error(sys->sprint("can't create pipe: %r"));
124
125	navops := chan of ref Navop;
126	spawn navigator(navops);
127
128	(tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0);
129	fds[0] = nil;
130
131	pidc := chan of int;
132	spawn server(tchan, srv, pidc, navops);
133	<-pidc;
134
135	if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0)
136		error(sys->sprint("can't mount tarfs: %r"));
137}
138
139server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop)
140{
141	pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil);
142Serve:
143	while((gm := <-tchan) != nil){
144		root.mtime = daytime->now();
145		pick m := gm {
146		Readerror =>
147			sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error);
148			break Serve;
149		Read =>
150			(c, err) := srv.canread(m);
151			if(c == nil){
152				srv.reply(ref Rmsg.Error(m.tag, err));
153				break;
154			}
155			if(c.qtype & Sys->QTDIR){
156				srv.default(m);	# does readdir
157				break;
158			}
159			f := files[int c.path];
160			n := m.count;
161			if(m.offset + big n > f.length)
162				n = int (f.length - m.offset);
163			if(n <= 0){
164				srv.reply(ref Rmsg.Read(m.tag, nil));
165				break;
166			}
167			a := array[n] of byte;
168			sys->seek(tarfd, f.offset+m.offset, 0);
169			n = sys->read(tarfd, a, len a);
170			if(n < 0)
171				srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));
172			else
173				srv.reply(ref Rmsg.Read(m.tag, a[0:n]));
174		* =>
175			srv.default(gm);
176		}
177	}
178	navops <-= nil;		# shut down navigator
179}
180
181File.enter(dir: self ref File, f: ref File)
182{
183	if(pathgen >= len files){
184		t := array[pathgen+50] of ref File;
185		t[0:] = files;
186		files = t;
187	}
188	if(0)
189		sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length);
190	f.x = pathgen;
191	f.parent = dir;
192	dir.children = f :: dir.children;
193	files[pathgen++] = f;
194}
195
196File.find(f: self ref File, name: string): ref File
197{
198	for(g := f.children; g != nil; g = tl g)
199		if((hd g).name == name)
200			return hd g;
201	return nil;
202}
203
204File.stat(f: self ref File): ref Sys->Dir
205{
206	d := ref sys->zerodir;
207	d.mode = f.mode;
208	if(pflag) {
209		d.mode &= 16rff<<24;
210		d.mode |= 8r444;
211		if(f.mode & Sys->DMDIR)
212			d.mode |= 8r111;
213	}
214	d.qid.path = big f.x;
215	d.qid.qtype = f.mode>>24;
216	d.name = f.name;
217	d.uid = f.uid;
218	d.gid = f.gid;
219	d.muid = d.uid;
220	d.length = f.length;
221	d.mtime = f.mtime;
222	d.atime = root.mtime;
223	return d;
224}
225
226split(s: string): (string, string)
227{
228	for(i := 0; i < len s; i++)
229		if(s[i] == '/'){
230			for(j := i+1; j < len s && s[j] == '/';)
231				j++;
232			return (s[0:i], s[j:]);
233		}
234	return (nil, s);
235}
236
237putfile(f: ref File)
238{
239	orign := n := f.name;
240	df := root;
241	for(;;){
242		(d, rest) := split(n);
243		if(d == ".") {
244			n = rest;
245			continue;
246		}
247		if(d == "..") {
248			warn(sys->sprint("ignoring %q", orign));
249			return;
250		}
251		if(d == nil || rest == nil){
252			f.name = n;
253			break;
254		}
255		g := df.find(d);
256		if(g == nil){
257			g = ref *f;
258			g.name = d;
259			g.mode |= Sys->DMDIR;
260			df.enter(g);
261		}
262		n = rest;
263		df = g;
264	}
265	if(f.name != "." && f.name != "..")
266		df.enter(f);
267}
268
269navigator(navops: chan of ref Navop)
270{
271	while((m := <-navops) != nil){
272		pick n := m {
273		Stat =>
274			n.reply <-= (files[int n.path].stat(), nil);
275		Walk =>
276			f := files[int n.path];
277			if((f.mode & Sys->DMDIR) == 0){
278				n.reply <-= (nil, "not a directory");
279				break;
280			}
281			case n.name {
282			".." =>
283				if(f.parent != nil)
284					f = f.parent;
285				n.reply <-= (f.stat(), nil);
286			* =>
287				f = f.find(n.name);
288				if(f != nil)
289					n.reply <-= (f.stat(), nil);
290				else
291					n.reply <-= (nil, Enotfound);
292			}
293		Readdir =>
294			f := files[int n.path];
295			if((f.mode & Sys->DMDIR) == 0){
296				n.reply <-= (nil, "not a directory");
297				break;
298			}
299			g := f.children;
300			for(i := n.offset; i > 0 && g != nil; i--)
301				g = tl g;
302			for(; --n.count >= 0 && g != nil; g = tl g)
303				n.reply <-= ((hd g).stat(), nil);
304			n.reply <-= (nil, nil);
305		}
306	}
307}
308
309Blocksize: con 512;
310Namelen: con 100;
311Userlen: con 32;
312
313Oname: con 0;
314Omode: con Namelen;
315Ouid: con Omode+8;
316Ogid: con Ouid+8;
317Osize: con Ogid+8;
318Omtime: con Osize+12;
319Ochksum: con Omtime+12;
320Olinkflag: con Ochksum+8;
321Olinkname: con Olinkflag+1;
322# POSIX extensions follow
323Omagic: con Olinkname+Namelen;	# ustar
324Ouname: con Omagic+8;
325Ogname: con Ouname+Userlen;
326Omajor: con Ogname+Userlen;
327Ominor: con Omajor+8;
328Oend: con Ominor+8;
329
330readtar(fd: ref Sys->FD): int
331{
332	buf := array[Blocksize] of byte;
333	offset := big 0;
334	for(;;){
335		sys->seek(fd, offset, 0);
336		n := sys->read(fd, buf, len buf);
337		if(n == 0)
338			break;
339		if(n < 0)
340			return -1;
341		if(n < len buf){
342			sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n));
343			return -1;
344		}
345		if(buf[0] == byte 0)
346			break;
347		offset += big Blocksize;
348		mode := int octal(buf[Omode:Ouid]);
349		linkflag := int buf[Olinkflag];
350		# don't use linkname
351		if((mode & 8r170000) == 8r40000)
352			linkflag = '5';
353		mode &= 8r777;
354		case linkflag {
355		'1' or '2' or 's' =>		# ignore links and symbolic links
356			continue;
357		'3' or '4' or '6' =>	# special file or fifo (leave them, but empty)
358			;
359		'5' =>
360			mode |= Sys->DMDIR;
361		}
362		f := ref File;
363		f.name = ascii(buf[Oname:Omode]);
364		while(len f.name > 0 && f.name[0] == '/')
365			f.name = f.name[1:];
366		while(len f.name > 0 && f.name[len f.name-1] == '/'){
367			mode |= Sys->DMDIR;
368			f.name = f.name[:len f.name-1];
369		}
370		f.mode = mode;
371		f.uid = string octal(buf[Ouid:Ogid]);
372		f.gid = string octal(buf[Ogid:Osize]);
373		f.length = octal(buf[Osize:Omtime]);
374		if(f.length < big 0)
375			error(sys->sprint("tar file size is negative: %s", f.name));
376		if(mode & Sys->DMDIR)
377			f.length = big 0;
378		f.mtime = int octal(buf[Omtime:Ochksum]);
379		sum := int octal(buf[Ochksum:Olinkflag]);
380		if(sum != checksum(buf))
381			error(sys->sprint("checksum error on %s", f.name));
382		f.offset = offset;
383		offset += f.length;
384		v := int (f.length % big Blocksize);
385		if(v != 0)
386			offset += big (Blocksize-v);
387
388		if(ascii(buf[Omagic:Ouname]) == "ustar" && string buf[Omagic+6:Omagic+8] == "00") {
389			f.uid = ascii(buf[Ouname:Ogname]);
390			f.gid = ascii(buf[Ogname:Omajor]);
391		}
392
393		putfile(f);
394	}
395	return 0;
396}
397
398ascii(b: array of byte): string
399{
400	top := 0;
401	for(i := 0; i < len b && b[i] != byte 0; i++)
402		if(int b[i] >= 16r80)
403			top = 1;
404	if(top)
405		;	# TO DO: do it by hand if not utf-8
406	return string b[0:i];
407}
408
409octal(b: array of byte): big
410{
411	v := big 0;
412	for(i := 0; i < len b && b[i] == byte ' '; i++)
413		;
414	for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){
415		c := int b[i];
416		if(!(c >= '0' && c <= '7'))
417			error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c));
418		v = (v<<3) | big (c-'0');
419	}
420	return v;
421}
422
423checksum(b: array of byte): int
424{
425	c := 0;
426	for(i := 0; i < Ochksum; i++)
427		c += int b[i];
428	for(; i < Olinkflag; i++)
429		c += ' ';
430	for(; i < len b; i++)
431		c += int b[i];
432	return c;
433}
434
435warn(s: string)
436{
437	sys->fprint(sys->fildes(2), "%s\n", s);
438}
439