diff options
Diffstat (limited to 'appl/cmd/archfs.b')
| -rw-r--r-- | appl/cmd/archfs.b | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/appl/cmd/archfs.b b/appl/cmd/archfs.b new file mode 100644 index 00000000..11567731 --- /dev/null +++ b/appl/cmd/archfs.b @@ -0,0 +1,630 @@ +implement Archfs; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + +include "string.m"; + str: String; + +include "daytime.m"; + daytime: Daytime; + +include "styx.m"; + styx: Styx; + NOFID: import Styx; + +include "arg.m"; + +Archfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Ahdr: adt { + name: string; + modestr: string; + d: ref Sys->Dir; +}; + +Archive: adt { + b: ref Bufio->Iobuf; + nexthdr: big; + canseek: int; + hdr: ref Ahdr; + err: string; +}; + +Iobuf: import bufio; +Tmsg, Rmsg: import styx; + +Einuse : con "fid already in use"; +Ebadfid : con "bad fid"; +Eopen : con "fid already opened"; +Enotfound : con "file does not exist"; +Enotdir : con "not a directory"; +Eperm : con "permission denied"; + +UID: con "inferno"; +GID: con "inferno"; + +debug := 0; + +Dir: adt { + dir: Sys->Dir; + offset: big; + parent: cyclic ref Dir; + child: cyclic ref Dir; + sibling: cyclic ref Dir; +}; + +Fid: adt { + fid: int; + open: int; + dir: ref Dir; +}; + +HTSZ: con 32; +fidtab := array[HTSZ] of list of ref Fid; + +root: ref Dir; +qid: int; +mtpt := "/mnt/arch"; +bio: ref Iobuf; +buf: array of byte; +skip := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + styx = load Styx Styx->PATH; + if(bufio == nil || styx == nil || daytime == nil || str == nil) + fatal("failed to load modules"); + styx->init(); + + flags := Sys->MREPL; + arg := load Arg Arg->PATH; + if(arg == nil) + fatal("failed to load "+Arg->PATH); + arg->init(args); + arg->setusage("archfs [-ab] [-m mntpt] archive [prefix ...]"); + while((c := arg->opt()) != 0){ + case c { + 'D' => + debug = 1; + 'a' => + flags = Sys->MAFTER; + 'b' => + flags = Sys->MBEFORE; + 'm' => + mtpt = arg->earg(); + 's' => + skip = 1; + * => + arg->usage(); + } + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + buf = array[Sys->ATOMICIO] of byte; + # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now()); + root = newdir(basename(mtpt), UID, GID, 8r555|Sys->DMDIR, daytime->now()); + root.parent = root; + readarch(hd args, tl args); + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + fatal("can't create pipe"); + pidch := chan of int; + spawn serve(p[1], pidch); + pid := <- pidch; + if(sys->mount(p[0], nil, mtpt, flags, nil) < 0) + fatal(sys->sprint("cannot mount archive on %s: %r", mtpt)); +} + +reply(fd: ref Sys->FD, m: ref Rmsg): int +{ + if(debug) + sys->fprint(sys->fildes(2), "-> %s\n", m.text()); + s := m.pack(); + if(s == nil) + return -1; + return sys->write(fd, s, len s); +} + +error(fd: ref Sys->FD, m: ref Tmsg, e: string) +{ + reply(fd, ref Rmsg.Error(m.tag, e)); +} + +serve(fd: ref Sys->FD, pidch: chan of int) +{ + e: string; + f: ref Fid; + + pidch <-= sys->pctl(Sys->NEWNS|Sys->NEWFD, 1 :: 2 :: fd.fd :: bio.fd.fd :: nil); + bio.fd = sys->fildes(bio.fd.fd); + fd = sys->fildes(fd.fd); +Work: + while((m0 := Tmsg.read(fd, Styx->MAXRPC)) != nil){ + if(debug) + sys->fprint(sys->fildes(2), "<- %s\n", m0.text()); + pick m := m0 { + Readerror => + fatal("read error on styx server"); + Version => + (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION); + reply(fd, ref Rmsg.Version(m.tag, s, v)); + Auth => + error(fd, m, "authentication not required"); + Flush => + reply(fd, ref Rmsg.Flush(m.tag)); + Walk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(f.open){ + error(fd, m, Eopen); + continue; + } + dir := f.dir; + nq := 0; + nn := len m.names; + qids := array[nn] of Sys->Qid; + if(nn > 0){ + for(k := 0; k < nn; k++){ + if((dir.dir.mode & Sys->DMDIR) == 0){ + if(k == 0){ + error(fd, m, Enotdir); + continue Work; + } + break; + } + dir = lookup(dir, m.names[k]); + if(dir == nil){ + if(k == 0){ + error(fd, m, Enotfound); + continue Work; + } + break; + } + qids[nq++] = dir.dir.qid; + } + } + if(nq < nn) + qids = qids[0: nq]; + if(nq == nn){ + if(m.newfid != m.fid){ + f = newfid(m.newfid); + if(f == nil){ + error(fd, m, Einuse); + continue Work; + } + } + f.dir = dir; + } + reply(fd, ref Rmsg.Walk(m.tag, qids)); + Open => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(m.mode != Sys->OREAD){ + error(fd, m, Eperm); + continue; + } + f.open = 1; + reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA)); + Create => + error(fd, m, Eperm); + Read => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + data := read(f.dir, m.offset, m.count); + reply(fd, ref Rmsg.Read(m.tag, data)); + Write => + error(fd, m, Eperm); + Clunk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + freefid(f); + reply(fd, ref Rmsg.Clunk(m.tag)); + Stat => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir)); + Remove => + error(fd, m, Eperm); + Wstat => + error(fd, m, Eperm); + Attach => + f = newfid(m.fid); + if(f == nil){ + error(fd, m, Einuse); + continue; + } + f.dir = root; + reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid)); + * => + fatal("unknown styx message"); + } + } +} + +newfid(fid: int): ref Fid +{ + if(fid == NOFID) + return nil; + hv := hashval(fid); + ff: ref Fid; + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid) + return nil; + if(ff == nil && f.fid == NOFID) + ff = f; + } + if((f := ff) == nil){ + f = ref Fid; + fidtab[hv] = f :: fidtab[hv]; + } + f.fid = fid; + f.open = 0; + return f; +} + +freefid(f: ref Fid) +{ + hv := hashval(f.fid); + for(l := fidtab[hv]; l != nil; l = tl l) + if(hd l == f){ + f.fid = NOFID; + f.dir = nil; + f.open = 0; + return; + } + fatal("cannot find fid"); +} + +mapfid(fid: int): (ref Fid, string) +{ + if(fid == NOFID) + return (nil, Ebadfid); + hv := hashval(fid); + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + if(f.dir == nil) + return (nil, Enotfound); + return (f, nil); + } + } + return (nil, Ebadfid); +} + +hashval(n: int): int +{ + n %= HTSZ; + if(n < 0) + n += HTSZ; + return n; +} + +readarch(f: string, args: list of string) +{ + ar := openarch(f); + if(ar == nil || ar.b == nil) + fatal(sys->sprint("cannot open %s: %r", f)); + bio = ar.b; + while((a := gethdr(ar)) != nil){ + if(args != nil){ + if(!selected(a.name, args)){ + if(skip) + return; + #drain(ar, int a.d.length); + continue; + } + mkdirs("/", a.name); + } + d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0); + if((a.d.mode & Sys->DMDIR) == 0){ + d.dir.length = a.d.length; + d.offset = bio.offset(); + } + #drain(ar, int a.d.length); + } + if(ar.err != nil) + fatal(ar.err); +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +basename(f: string): string +{ + for(i := len f; i > 0; ) + if(f[--i] == '/') + return f[i+1:]; + return f; +} + +split(p: string): (string, string) +{ + if(p == nil) + fatal("nil string in split"); + if(p[0] != '/') + fatal("p0 not / in split"); + while(p[0] == '/') + p = p[1:]; + i := 0; + while(i < len p && p[i] != '/') + i++; + if(i == len p) + return (p, nil); + else + return (p[0:i], p[i:]); +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil){ + # sys->print("mkdir %s\n", basedir); + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +read(d: ref Dir, offset: big, n: int): array of byte +{ + if(d.dir.mode & Sys->DMDIR) + return readdir(d, int offset, n); + return readfile(d, offset, n); +} + +readdir(d: ref Dir, o: int, n: int): array of byte +{ + k := 0; + m := 0; + b := array[n] of byte; + for(s := d.child; s != nil; s = s.sibling){ + l := styx->packdirsize(s.dir); + if(k < o){ + k += l; + continue; + } + if(m+l > n) + break; + b[m: ] = styx->packdir(s.dir); + m += l; + } + return b[0: m]; +} + +readfile(d: ref Dir, offset: big, n: int): array of byte +{ + if(offset+big n > d.dir.length) + n = int(d.dir.length-offset); + if(n <= 0 || offset < big 0) + return nil; + bio.seek(d.offset+offset, Bufio->SEEKSTART); + a := array[n] of byte; + p := 0; + m := 0; + for( ; n != 0; n -= m){ + l := len buf; + if(n < l) + l = n; + m = bio.read(buf, l); + if(m <= 0 || m != l) + fatal("premature eof"); + a[p:] = buf[0:m]; + p += m; + } + return a; +} + +mkdir(f: string, mode: int, mtime: int, uid: string, gid: string, existsok: int): ref Dir +{ + if(f == "/") + return nil; + d := newdir(basename(f), uid, gid, mode, mtime); + addfile(d, f, existsok); + return d; +} + +addfile(d: ref Dir, path: string, existsok: int) +{ + elem: string; + + opath := path; + p := prev := root; + basedir := ""; +# sys->print("addfile %s: %s\n", d.dir.name, path); + while(path != nil){ + (elem, path) = split(path); + basedir += "/" + elem; + op := p; + p = lookup(p, elem); + if(path == nil){ + if(p != nil){ + if(!existsok && (p.dir.mode&Sys->DMDIR) == 0) + sys->fprint(sys->fildes(2), "addfile: %s already there", opath); + # fatal(sys->sprint("addfile: %s already there", opath)); + return; + } + if(prev.child == nil) + prev.child = d; + else { + for(s := prev.child; s.sibling != nil; s = s.sibling) + ; + s.sibling = d; + } + d.parent = prev; + } + else { + if(p == nil){ + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + p = lookup(op, elem); + if(p == nil) + fatal("bad file system"); + } + } + prev = p; + } +} + +lookup(p: ref Dir, f: string): ref Dir +{ + if((p.dir.mode&Sys->DMDIR) == 0) + fatal("not a directory in lookup"); + if(f == ".") + return p; + if(f == "..") + return p.parent; + for(d := p.child; d != nil; d = d.sibling) + if(d.dir.name == f) + return d; + return nil; +} + +newdir(name, uid, gid: string, mode, mtime: int): ref Dir +{ + dir := sys->zerodir; + dir.name = name; + dir.uid = uid; + dir.gid = gid; + dir.mode = mode; + dir.qid.path = big (qid++); + dir.qid.qtype = mode>>24; + dir.qid.vers = 0; + dir.atime = dir.mtime = mtime; + dir.length = big 0; + + d := ref Dir; + d.dir = dir; + d.offset = big 0; + return d; +} + +prd(d: ref Dir) +{ + dir := d.dir; + sys->print("%q %q %q %bx %x %x %d %d %bd %d %d %bd\n", + dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset); +} + +fatal(e: string) +{ + sys->fprint(sys->fildes(2), "archfs: %s\n", e); + raise "fail:error"; +} + +openarch(file: string): ref Archive +{ + b := bufio->open(file, Bufio->OREAD); + if(b == nil) + return nil; + ar := ref Archive; + ar.b = b; + ar.nexthdr = big 0; + ar.canseek = 1; + ar.hdr = ref Ahdr; + ar.hdr.d = ref Sys->Dir; + return ar; +} + +NFLDS: con 6; + +gethdr(ar: ref Archive): ref Ahdr +{ + a := ar.hdr; + b := ar.b; + m := b.offset(); + n := ar.nexthdr; + if(m != n){ + if(ar.canseek) + b.seek(n, Bufio->SEEKSTART); + else { + if(m > n) + fatal(sys->sprint("bad offset in gethdr: m=%bd n=%bd", m, n)); + if(drain(ar, int(n-m)) < 0) + return nil; + } + } + if((s := b.gets('\n')) == nil){ + ar.err = "premature end of archive"; + return nil; + } + if(s == "end of archive\n") + return nil; + (nf, fs) := sys->tokenize(s, " \t\n"); + if(nf != NFLDS){ + ar.err = "too few fields in file header"; + return nil; + } + a.name = hd fs; fs = tl fs; + (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs; + a.d.uid = hd fs; fs = tl fs; + a.d.gid = hd fs; fs = tl fs; + (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs; + (tmp, nil) := str->toint(hd fs, 10); fs = tl fs; + a.d.length = big tmp; + ar.nexthdr = b.offset()+a.d.length; + return a; +} + +drain(ar: ref Archive, n: int): int +{ + while(n > 0){ + m := n; + if(m > len buf) + m = len buf; + p := ar.b.read(buf, m); + if(p != m){ + ar.err = "unexpectedly short read"; + return -1; + } + n -= m; + } + return 0; +} |
