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); <- 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; }