diff options
Diffstat (limited to 'appl/cmd/fs/unbundle.b')
| -rw-r--r-- | appl/cmd/fs/unbundle.b | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/appl/cmd/fs/unbundle.b b/appl/cmd/fs/unbundle.b new file mode 100644 index 00000000..ad500e8e --- /dev/null +++ b/appl/cmd/fs/unbundle.b @@ -0,0 +1,259 @@ +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; +} |
