code: 9ferno

ref: da7d6df6faf18e289fe0f3f61524dcc7fddeef18
dir: /appl/alphabet/fs/unbundle.b/

View raw version
implement Unbundle, Fsmodule;
include "sys.m";
	sys: Sys;
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;
include "string.m";
	str: String;
include "draw.m";
include "sh.m";
include "alphabet/reports.m";
	reports: Reports;
	Report, quit, report: import reports;
include "alphabet/fs.m";
	fs: Fs;
	Value: import fs;
	Fschan, Fsdata, Entrychan, Entry,
	Quit, Next, Skip, Down,
	Option: import Fs;

Unbundle: module {};
types(): string
{
	return "xf";
}

badmod(p: string)
{
	sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p);
	raise "fail:bad module";
}

init()
{
	sys = load Sys Sys->PATH;
	fs = load Fs Fs->PATH;
	if(fs == nil)
		badmod(Fs->PATH);
	fs->init();
	reports = load Reports Reports->PATH;
	if(reports == nil)
		badmod(Reports->PATH);
	bufio = load Bufio Bufio->PATH;
	if(bufio == nil)
		badmod(Bufio->PATH);
	str = load String String->PATH;
	if(str == nil)
		badmod(String->PATH);
}

run(nil: ref Draw->Context, report: ref Report,
			nil: list of Option, args: list of ref Value): ref Value
{
	f := (hd args).f().i;
	c := ref Value.Vx(chan of (Fsdata, chan of int));
	spawn unbundleproc((hd args).f().i, nil, c.i, -1, Sys->ATOMICIO, report.start("unbundle"));
	return c;
}

# dodgy heuristic... avoid, or using the stat-length of pipes and net connections
isseekable(fd: ref Sys->FD): int
{
	(ok, stat) := sys->fstat(fd);
	if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I')
		return 0;
	return 1;
}

EOF: con "end of archive\n";

unbundleproc(f: chan of ref Sys->FD, iob: ref Iobuf, c: Fschan,
		seekable, blocksize: int, errorc: chan of string)
{
	if(f != nil){
		fd := <-f;
		if(fd == nil){
			sys->pipe(p := array[2] of ref Sys->FD);
			f <-= p[1];
			p[1] = nil;
			fd = p[0];
		}else
			f <-= nil;
		if(seekable == -1)
			seekable = isseekable(fd);
		iob = bufio->fopen(fd, Sys->OREAD);
		f = nil;
	}

	reply := chan of int;
	p := iob.gets('\n');
	# XXX overall header?
	if(p == nil || p == EOF){
		fs->sendnulldir(c);
		quit(errorc);
	}
	d := header2dir(p);
	if(d == nil){
		fs->sendnulldir(c);
		report(errorc, sys->sprint("invalid first header %q", p[0:len p - 1]));
		quit(errorc);
	}
	if((d.mode & Sys->DMDIR) == 0){
		fs->sendnulldir(c);
		report(errorc, "first entry is not a directory");
		quit(errorc);
	}
	c <-= ((d, nil), reply);
	case r := <-reply {
	Down =>
		unbundledir(iob, c, 0, seekable, blocksize, errorc);
		c <-= ((nil, nil), reply);
		<-reply;
	Skip or
	Next =>
		unbundledir(iob, c, 1, seekable, blocksize, errorc);
	Quit =>
		break;
	}
	quit(errorc);
}

unbundledir(iob: ref Iobuf, c: Fschan,
			skipping, seekable, blocksize: int, errorc: chan of string): int
{
	reply := chan of int;
	while((p := iob.gets('\n')) != nil){
		if(p == EOF)
			break;
		if(p[0] == '\n')
			break;
		d := header2dir(p);
		if(d == nil){
			report(errorc, sys->sprint("invalid bundle header %q", p[0:len p - 1]));
			return -1;
		}
		if(d.mode & Sys->DMDIR){
			if(skipping)
				continue;
			c <-= ((d, nil), reply);
			case <-reply {
			Quit =>
				quit(errorc);
			Down =>
				r := unbundledir(iob, c, 0, seekable, blocksize, errorc);
				c <-= ((nil, nil), reply);
				if(<-reply == Quit)
					quit(errorc);
				if(r == -1)
					return -1;
			Skip =>
				if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1)
					return -1;
				skipping = 1;
			Next =>
				if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1)
					return -1;
			}
		}else{
			if(skipping){
				if(skipdata(iob, d.length, seekable) == -1)
					return -1;
			}else{
				case unbundlefile(iob, d, c, errorc, seekable, blocksize) {
				-1 =>
					return -1;
				Skip =>
					skipping = 1;
				}
			}
		}
	}
	if(p == nil)
		report(errorc, "unexpected eof");
	return 0;
}

skipdata(iob: ref Iobuf, length: big, seekable: int): int
{
	if(seekable){
		iob.seek(big length, Sys->SEEKRELA);
		return 0;
	}
	buf := array[Sys->ATOMICIO] of byte;
	for(n := big 0; n < length; ){
		nb := Sys->ATOMICIO;
		if(length - n < big Sys->ATOMICIO)
			nb = int (length - n);
		nb = iob.read(buf, nb);
		if(nb <= 0)
			return -1;
		n += big nb;
	}
	return 0;
}

unbundlefile(iob: ref Iobuf, d: ref Sys->Dir,
	c: Fschan, errorc: chan of string, seekable, blocksize: int): int
{
	reply := chan of int;
	c <-= ((d, nil), reply);
	case <-reply {
	Quit =>
		quit(errorc);
	Skip =>
		if(skipdata(iob, d.length, seekable) == -1)
			return -1;
		return Skip;
	Next =>
		if(skipdata(iob, d.length, seekable) == -1)
			return -1;
		return Next;
	}
	length := d.length;
	for(n := big 0; n < length; ){
		nr := blocksize;
		if(n + big blocksize > length)
			nr = int (length - n);
		buf := array[nr] of byte;
		nr = iob.read(buf, nr);
		if(nr <= 0){
			if(nr < 0)
				report(errorc, sys->sprint("read error: %r"));
			else
				report(errorc, sys->sprint("premature eof"));
			return -1;
		}else if(nr < len buf)
			buf = buf[0:nr];
		c <-= ((nil, buf), reply);
		n += big nr;
		case <-reply {
		Quit =>
			quit(errorc);
		Skip =>
			if(skipdata(iob, length - n, seekable) == -1)
				return -1;
			return Next;
		}
	}
	c <-= ((nil, nil), reply);
	if(<-reply == Quit)
		quit(errorc);
	return Next;
}

header2dir(s: string): ref Sys->Dir
{
	toks := str->unquoted(s);
	nf := len toks;
	if(nf != 6)
		return nil;
	d := ref Sys->nulldir;
	(d.name, toks) = (hd toks, tl toks);
	(d.mode, toks) = (str->toint(hd toks, 8).t0, tl toks);
	(d.uid, toks) = (hd toks, tl toks);
	(d.gid, toks) = (hd toks, tl toks);
	(d.mtime, toks) = (int hd toks, tl toks);
	(d.length, toks) = (big hd toks, tl toks);
	return d;
}