forsyth20121205-174063a80cc13 years agoHistory
438 lines · 9.0 KB · Brainfuck
RawDownload
implement Tarfs; ## Copyright © 2003 Vita Nuova Holdings Limited.  All rights reserved.# include "sys.m";	sys: Sys; include "draw.m"; include "daytime.m";	daytime: Daytime; include "arg.m"; include "styx.m";	styx: Styx;	Tmsg, Rmsg: import styx; include "styxservers.m";	styxservers: Styxservers;	Fid, Styxserver, Navigator, Navop: import styxservers;	Enotfound: import styxservers; Tarfs: module{	init: fn(nil: ref Draw->Context, nil: list of string);}; File: adt {	x:	int;	name:	string;	mode:	int;	uid:	string;	gid:	string;	mtime:	int;	length:	big;	offset:	big;	parent:	cyclic ref File;	children:	cyclic list of ref File; 	find:		fn(f: self ref File, name: string): ref File;	enter:	fn(d: self ref File, f: ref File);	stat:		fn(d: self ref File): ref Sys->Dir;}; tarfd: ref Sys->FD;pflag: int;root: ref File;files: array of ref File;pathgen: int; error(s: string){	sys->fprint(sys->fildes(2), "tarfs: %s\n", s);	raise "fail:error";} checkload[T](m: T, path: string){	if(m == nil)		error(sys->sprint("can't load %s: %r", path));} init(nil: ref Draw->Context, args: list of string){	sys = load Sys Sys->PATH;	sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);	styx = load Styx Styx->PATH;	checkload(styx, Styx->PATH);	styx->init();	styxservers = load Styxservers Styxservers->PATH;	checkload(styxservers, Styxservers->PATH);	styxservers->init(styx);	daytime = load Daytime Daytime->PATH;	checkload(daytime, Daytime->PATH); 	arg := load Arg Arg->PATH;	checkload(arg, Arg->PATH);	arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint");	arg->init(args);	flags := Sys->MREPL;	while((o := arg->opt()) != 0)		case o {		'a' =>	flags = Sys->MAFTER;		'b' =>	flags = Sys->MBEFORE;		'D' =>	styxservers->traceset(1);		'p' =>	pflag++;		* =>		arg->usage();		}	args = arg->argv();	if(len args != 2)		arg->usage();	arg = nil; 	file := hd args;	args = tl args;	mountpt := hd args; 	sys->pctl(Sys->FORKFD, nil); 	files = array[100] of ref File;	root = files[0] = ref File;	root.x = 0;	root.name = "/";	root.mode = Sys->DMDIR | 8r555;	root.uid = "0";	root.gid = "0";	root.length = big 0;	root.offset = big 0;	root.mtime = 0;	pathgen = 1; 	tarfd = sys->open(file, Sys->OREAD);	if(tarfd == nil)		error(sys->sprint("can't open %s: %r", file));	if(readtar(tarfd) < 0)		error(sys->sprint("error reading %s: %r", file)); 	fds := array[2] of ref Sys->FD;	if(sys->pipe(fds) < 0)		error(sys->sprint("can't create pipe: %r")); 	navops := chan of ref Navop;	spawn navigator(navops); 	(tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0);	fds[0] = nil; 	pidc := chan of int;	spawn server(tchan, srv, pidc, navops);	<-pidc; 	if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0)		error(sys->sprint("can't mount tarfs: %r"));} server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop){	pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil);Serve:	while((gm := <-tchan) != nil){		root.mtime = daytime->now();		pick m := gm {		Readerror =>			sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error);			break Serve;		Read =>			(c, err) := srv.canread(m);			if(c == nil){				srv.reply(ref Rmsg.Error(m.tag, err));				break;			}			if(c.qtype & Sys->QTDIR){				srv.default(m);	# does readdir				break;			}			f := files[int c.path];			n := m.count;			if(m.offset + big n > f.length)				n = int (f.length - m.offset);			if(n <= 0){				srv.reply(ref Rmsg.Read(m.tag, nil));				break;			}			a := array[n] of byte;			sys->seek(tarfd, f.offset+m.offset, 0);			n = sys->read(tarfd, a, len a);			if(n < 0)				srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r")));			else				srv.reply(ref Rmsg.Read(m.tag, a[0:n]));		* =>			srv.default(gm);		}	}	navops <-= nil;		# shut down navigator} File.enter(dir: self ref File, f: ref File){	if(pathgen >= len files){		t := array[pathgen+50] of ref File;		t[0:] = files;		files = t;	}	if(0)		sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length);	f.x = pathgen;	f.parent = dir;	dir.children = f :: dir.children;	files[pathgen++] = f;} File.find(f: self ref File, name: string): ref File{	for(g := f.children; g != nil; g = tl g)		if((hd g).name == name)			return hd g;	return nil;} File.stat(f: self ref File): ref Sys->Dir{	d := ref sys->zerodir;	d.mode = f.mode;	if(pflag) {		d.mode &= 16rff<<24;		d.mode |= 8r444;		if(f.mode & Sys->DMDIR)			d.mode |= 8r111;	}	d.qid.path = big f.x;	d.qid.qtype = f.mode>>24;	d.name = f.name;	d.uid = f.uid;	d.gid = f.gid;	d.muid = d.uid;	d.length = f.length;	d.mtime = f.mtime;	d.atime = root.mtime;	return d;} split(s: string): (string, string){	for(i := 0; i < len s; i++)		if(s[i] == '/'){			for(j := i+1; j < len s && s[j] == '/';)				j++;			return (s[0:i], s[j:]);		}	return (nil, s);} putfile(f: ref File){	orign := n := f.name;	df := root;	for(;;){		(d, rest) := split(n);		if(d == ".") {			n = rest;			continue;		}		if(d == "..") {			warn(sys->sprint("ignoring %q", orign));			return;		}		if(d == nil || rest == nil){			f.name = n;			break;		}		g := df.find(d);		if(g == nil){			g = ref *f;			g.name = d;			g.mode |= Sys->DMDIR;			df.enter(g);		}		n = rest;		df = g;	}	if(f.name != "." && f.name != "..")		df.enter(f);} navigator(navops: chan of ref Navop){	while((m := <-navops) != nil){		pick n := m {		Stat =>			n.reply <-= (files[int n.path].stat(), nil);		Walk =>			f := files[int n.path];			if((f.mode & Sys->DMDIR) == 0){				n.reply <-= (nil, "not a directory");				break;			}			case n.name {			".." =>				if(f.parent != nil)					f = f.parent;				n.reply <-= (f.stat(), nil);			* =>				f = f.find(n.name);				if(f != nil)					n.reply <-= (f.stat(), nil);				else					n.reply <-= (nil, Enotfound);			}		Readdir =>			f := files[int n.path];			if((f.mode & Sys->DMDIR) == 0){				n.reply <-= (nil, "not a directory");				break;			}			g := f.children;			for(i := n.offset; i > 0 && g != nil; i--)				g = tl g;			for(; --n.count >= 0 && g != nil; g = tl g)				n.reply <-= ((hd g).stat(), nil);			n.reply <-= (nil, nil);		}	}} Blocksize: con 512;Namelen: con 100;Userlen: con 32; Oname: con 0;Omode: con Namelen;Ouid: con Omode+8;Ogid: con Ouid+8;Osize: con Ogid+8;Omtime: con Osize+12;Ochksum: con Omtime+12;Olinkflag: con Ochksum+8;Olinkname: con Olinkflag+1;# POSIX extensions followOmagic: con Olinkname+Namelen;	# ustarOuname: con Omagic+8;Ogname: con Ouname+Userlen;Omajor: con Ogname+Userlen;Ominor: con Omajor+8;Oend: con Ominor+8; readtar(fd: ref Sys->FD): int{	buf := array[Blocksize] of byte;	offset := big 0;	for(;;){		sys->seek(fd, offset, 0);		n := sys->read(fd, buf, len buf);		if(n == 0)			break;		if(n < 0)			return -1;		if(n < len buf){			sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n));			return -1;		}		if(buf[0] == byte 0)			break;		offset += big Blocksize;		mode := int octal(buf[Omode:Ouid]);		linkflag := int buf[Olinkflag];		# don't use linkname		if((mode & 8r170000) == 8r40000)			linkflag = '5';		mode &= 8r777;		case linkflag {		'1' or '2' or 's' =>		# ignore links and symbolic links			continue;		'3' or '4' or '6' =>	# special file or fifo (leave them, but empty)			;		'5' =>			mode |= Sys->DMDIR;		}		f := ref File;		f.name = ascii(buf[Oname:Omode]);		while(len f.name > 0 && f.name[0] == '/')			f.name = f.name[1:];		while(len f.name > 0 && f.name[len f.name-1] == '/'){			mode |= Sys->DMDIR;			f.name = f.name[:len f.name-1];		}		f.mode = mode;		f.uid = string octal(buf[Ouid:Ogid]);		f.gid = string octal(buf[Ogid:Osize]);		f.length = octal(buf[Osize:Omtime]);		if(f.length < big 0)			error(sys->sprint("tar file size is negative: %s", f.name));		if(mode & Sys->DMDIR)			f.length = big 0;		f.mtime = int octal(buf[Omtime:Ochksum]);		sum := int octal(buf[Ochksum:Olinkflag]);		if(sum != checksum(buf))			error(sys->sprint("checksum error on %s", f.name));		f.offset = offset;		offset += f.length;		v := int (f.length % big Blocksize);		if(v != 0)			offset += big (Blocksize-v); 		if(ascii(buf[Omagic:Ouname]) == "ustar" && string buf[Omagic+6:Omagic+8] == "00") {			f.uid = ascii(buf[Ouname:Ogname]);			f.gid = ascii(buf[Ogname:Omajor]);		}					putfile(f);	}	return 0;} ascii(b: array of byte): string{	top := 0;	for(i := 0; i < len b && b[i] != byte 0; i++)		if(int b[i] >= 16r80)			top = 1;	if(top)		;	# TO DO: do it by hand if not utf-8	return string b[0:i];} octal(b: array of byte): big{	v := big 0;	for(i := 0; i < len b && b[i] == byte ' '; i++)		;	for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){		c := int b[i];		if(!(c >= '0' && c <= '7'))			error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c));		v = (v<<3) | big (c-'0');	}	return v;} checksum(b: array of byte): int{	c := 0;	for(i := 0; i < Ochksum; i++)		c += int b[i];	for(; i < Olinkflag; i++)		c += ' ';	for(; i < len b; i++)		c += int b[i];	return c;} warn(s: string){	sys->fprint(sys->fildes(2), "%s\n", s);}