ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/fs/unbundle.b/
implement Unbundle;
include "sys.m";
sys: Sys;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "string.m";
str: String;
include "bundle.m";
bundle: Bundle;
include "draw.m";
include "sh.m";
include "fslib.m";
fslib: Fslib;
Report, Value,quit, report: import fslib;
Fschan, Fsdata, Entrychan, Entry,
Quit, Next, Skip, Down,
Option: import Fslib;
include "unbundle.m";
types(): string
{
return "xs";
}
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;
fslib = load Fslib Fslib->PATH;
if(fslib == nil)
badmod(Fslib->PATH);
bufio = load Bufio Bufio->PATH;
if(bufio == nil)
badmod(Bufio->PATH);
str = load String String->PATH;
if(str == nil)
badmod(String->PATH);
bundle = load Bundle Bundle->PATH;
if(bundle == nil)
badmod(Bundle->PATH);
bundle->init();
}
run(nil: ref Draw->Context, report: ref Report,
nil: list of Option, args: list of ref Value): ref Value
{
p := (hd args).s().i;
iob: ref Bufio->Iobuf;
if(p == "-")
iob = bufio->fopen(sys->fildes(0), Sys->OREAD);
else
iob = bufio->open(p, Sys->OREAD);
if(iob == nil){
sys->fprint(sys->fildes(2), "fs: unbundle: cannot open %q: %r\n", p);
return nil;
}
seekable := p != "-";
if(seekable)
seekable = isseekable(iob.fd);
return ref Value.X(unbundle(report, iob, seekable, Sys->ATOMICIO));
}
# 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;
}
unbundle(r: ref Report, iob: ref Iobuf, seekable, blocksize: int): Fschan
{
c := chan of (Fsdata, chan of int);
spawn unbundleproc(iob, c, seekable, blocksize, r.start("bundle"));
return c;
}
EOF: con "end of archive\n";
unbundleproc(iob: ref Iobuf, c: Fschan, seekable, blocksize: int, errorc: chan of string)
{
reply := chan of int;
p := iob.gets('\n');
# XXX overall header?
if(p == nil || p == EOF){
fslib->sendnulldir(c);
quit(errorc);
}
d := header2dir(p);
if(d == nil){
fslib->sendnulldir(c);
report(errorc, "invalid first header");
quit(errorc);
}
if((d.mode & Sys->DMDIR) == 0){
fslib->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;
}