ref: 866d74c0c4bb50e85e9e8bb95140c10d409e53be
dir: /appl/alphabet/fs/unbundle.b/
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;
}