diff options
Diffstat (limited to 'appl/cmd/disk/kfs.b')
| -rw-r--r-- | appl/cmd/disk/kfs.b | 3842 |
1 files changed, 3842 insertions, 0 deletions
diff --git a/appl/cmd/disk/kfs.b b/appl/cmd/disk/kfs.b new file mode 100644 index 00000000..56440205 --- /dev/null +++ b/appl/cmd/disk/kfs.b @@ -0,0 +1,3842 @@ +implement Kfs; + +# +# Copyright © 1991-2003 Lucent Technologies Inc. +# Limbo version Copyright © 2004 Vita Nuova Holdings Limited +# + +# +# TO DO: +# - sync proc; Bmod; process structure +# - swiz? + +include "sys.m"; + sys: Sys; + Qid, Dir: import Sys; + DMEXCL, DMAPPEND, DMDIR: import Sys; + QTEXCL, QTAPPEND, QTDIR: import Sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + NOFID, OEXEC, ORCLOSE, OREAD, OWRITE, ORDWR, OTRUNC: import Styx; + IOHDRSZ: import Styx; + +include "daytime.m"; + daytime: Daytime; + now: import daytime; + +include "arg.m"; + +Kfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +MAXBUFSIZE: con 16*1024; + +# +# fundamental constants +# +NAMELEN: con 28; # size of names, including null byte +NDBLOCK: con 6; # number of direct blocks in Dentry +MAXFILESIZE: con big 16r7FFFFFFF; # Plan 9's limit (kfs's size is signed) + +SUPERADDR: con 1; +ROOTADDR: con 2; + +QPDIR: con int (1<<31); +QPNONE: con 0; +QPROOT: con 1; +QPSUPER: con 2; + +# +# don't change, these are the mode bits on disc +# +DALLOC: con 16r8000; +DDIR: con 16r4000; +DAPND: con 16r2000; +DLOCK: con 16r1000; +DREAD: con 4; +DWRITE: con 2; +DEXEC: con 1; + +# +# other constants +# + +MINUTE: con 60; +TLOCK: con 5*MINUTE; +NTLOCK: con 200; # number of active file locks + +Buffering: con 1; + +FID1, FID2, FID3: con 1+iota; + +None: con 0; # user ID for "none" +Noworld: con 9999; # conventional id for "noworld" group + +Lock: adt +{ + c: chan of int; + new: fn(): ref Lock; + lock: fn(c: self ref Lock); + canlock: fn(c: self ref Lock): int; + unlock: fn(c: self ref Lock); +}; + +Dentry: adt +{ + name: string; + uid: int; + gid: int; + muid: int; # not set by plan 9's kfs + mode: int; # mode bits on disc: DALLOC etc + qid: Qid; # 9p1 format on disc + size: big; # only 32-bits on disc, and Plan 9 limits it to signed + atime: int; + mtime: int; + + iob: ref Iobuf; # locked block containing directory entry, when in memory + buf: array of byte; # pointer into block to packed directory entry, when in memory + mod: int; # bits of buf that need updating + + unpack: fn(a: array of byte): ref Dentry; + get: fn(p: ref Iobuf, slot: int): ref Dentry; + geta: fn(d: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string); + getd: fn(f: ref File, mode: int): (ref Dentry, string); + put: fn(d: self ref Dentry); + access: fn(d: self ref Dentry, f: int, uid: int); + change: fn(d: self ref Dentry, f: int); + release: fn(d: self ref Dentry); + getblk: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + getblk1: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + rel2abs: fn(d: self ref Dentry, a: int, tag: int, putb: int): int; + trunc: fn(d: self ref Dentry, uid: int); + update: fn(d: self ref Dentry); + print: fn(d: self ref Dentry); +}; + +Uname, Uids, Umode, Uqid, Usize, Utime: con 1<<iota; # Dentry.mod + +# +# disc structure: +# Tag: pad[2] tag[2] path[4] +Tagsize: con 2+2+4; + +Tag: adt +{ + tag: int; + path: int; + + unpack: fn(a: array of byte): Tag; + pack: fn(t: self Tag, a: array of byte); +}; + +Superb: adt +{ + iob: ref Iobuf; + + fstart: int; + fsize: int; + tfree: int; + qidgen: int; # generator for unique ids + + fsok: int; + + fbuf: array of byte; # nfree[4] free[FEPERBLK*4]; aliased into containing block + + get: fn(dev: ref Device, flags: int): ref Superb; + touched: fn(s: self ref Superb); + put: fn(s: self ref Superb); + print: fn(s: self ref Superb); + + pack: fn(s: self ref Superb, a: array of byte); + unpack: fn(a: array of byte): ref Superb; +}; + +Device: adt +{ + fd: ref Sys->FD; + ronly: int; + # could put locks here if necessary + # partitioning by ds(3) +}; + +# +# one for each locked qid +# +Tlock: adt +{ + dev: ref Device; + time: int; + qpath: int; + file: cyclic ref File; # TO DO: probably not needed +}; + +File: adt +{ + qlock: chan of int; + qid: Qid; + wpath: ref Wpath; + tlock: cyclic ref Tlock; # if file is locked + fs: ref Device; + addr: int; + slot: int; + lastra: int; # read ahead address + fid: int; + uid: int; + open: int; + cons: int; # if opened by console + doffset: big; # directory reading + dvers: int; + dslot: int; + + new: fn(fid: int): ref File; + access: fn(f: self ref File, d: ref Dentry, mode: int): int; + lock: fn(f: self ref File); + unlock: fn(f: self ref File); +}; + +FREAD, FWRITE, FREMOV, FWSTAT: con 1<<iota; # File.open + +Chan: adt +{ + fd: ref Sys->FD; # fd request came in on +# rlock, wlock: QLock; # lock for reading/writing messages on cp + flags: int; + flist: list of ref File; # active files + fqlock: chan of int; +# reflock: RWLock; # lock for Tflush + msize: int; # version + + new: fn(fd: ref Sys->FD): ref Chan; + getfid: fn(c: self ref Chan, fid: int, flag: int): ref File; + putfid: fn(c: self ref Chan, f: ref File); + flock: fn(nil: self ref Chan); + funlock: fn(nil: self ref Chan); +}; + +Hiob: adt +{ + link: ref Iobuf; # TO DO: eliminate circular list + lk: ref Lock; + niob: int; + + newbuf: fn(h: self ref Hiob): ref Iobuf; +}; + +Iobuf: adt +{ + qlock: chan of int; + dev: ref Device; + fore: cyclic ref Iobuf; # lru hash chain + back: cyclic ref Iobuf; # for lru + iobuf: array of byte; # only active while locked + xiobuf: array of byte; # "real" buffer pointer + addr: int; + flags: int; + + get: fn(dev: ref Device, addr: int, flags: int):ref Iobuf; + put: fn(iob: self ref Iobuf); + lock: fn(iob: self ref Iobuf); + canlock: fn(iob: self ref Iobuf): int; + unlock: fn(iob: self ref Iobuf); + + checktag: fn(iob: self ref Iobuf, tag: int, qpath: int): int; + settag: fn(iob: self ref Iobuf, tag: int, qpath: int); +}; + +Wpath: adt +{ + up: cyclic ref Wpath; # pointer upwards in path + addr: int; # directory entry addr + slot: int; # directory entry slot +}; + +# +# error codes generated from the file server +# +Eaccess: con "access permission denied"; +Ealloc: con "phase error -- directory entry not allocated"; +Eauth: con "authentication failed"; +Eauthmsg: con "kfs: authentication not required"; +Ebadspc: con "attach -- bad specifier"; +Ebadu: con "attach -- privileged user"; +Ebroken: con "close/read/write -- lock is broken"; +Echar: con "bad character in directory name"; +Econvert: con "protocol botch"; +Ecount: con "read/write -- count too big"; +Edir1: con "walk -- in a non-directory"; +Edir2: con "create -- in a non-directory"; +Edot: con "create -- . and .. illegal names"; +Eempty: con "remove -- directory not empty"; +Eentry: con "directory entry not found"; +Eexist: con "create -- file exists"; +Efid: con "unknown fid"; +Efidinuse: con "fid already in use"; +Efull: con "file system full"; +Elocked: con "open/create -- file is locked"; +Emode: con "open/create -- unknown mode"; +Ename: con "create/wstat -- bad character in file name"; +Enotd: con "wstat -- attempt to change directory"; +Enotg: con "wstat -- not in group"; +Enotl: con "wstat -- attempt to change length"; +Enotm: con "wstat -- unknown type/mode"; +Enotu: con "wstat -- not owner"; +Eoffset: con "read/write -- offset negative"; +Eopen: con "read/write -- on non open fid"; +Ephase: con "phase error -- cannot happen"; +Eqid: con "phase error -- qid does not match"; +Eqidmode: con "wstat -- qid.qtype/dir.mode mismatch"; +Eronly: con "file system read only"; +Ersc: con "it's russ's fault. bug him."; +Esystem: con "kfs system error"; +Etoolong: con "name too long"; +Etoobig: con "write -- file size limit"; +Ewalk: con "walk -- too many (system wide)"; + +# +# tags on block +# +Tnone, +Tsuper, # the super block +Tdir, # directory contents +Tind1, # points to blocks +Tind2, # points to Tind1 +Tfile, # file contents +Tfree, # in free list +Tbuck, # cache fs bucket +Tvirgo, # fake worm virgin bits +Tcache, # cw cache things +MAXTAG: con iota; + +# +# flags to Iobuf.get +# + Bread, # read the block if miss + Bprobe, # return null if miss + Bmod, # set modified bit in buffer + Bimm, # set immediate bit in buffer + Bres: # never renamed + con 1<<iota; + +# +# check flags +# + Crdall, # read all files + Ctag, # rebuild tags + Cpfile, # print files + Cpdir, # print directories + Cfree, # rebuild free list + Cream, # clear all bad tags + Cbad, # clear all bad blocks + Ctouch, # touch old dir and indir + Cquiet: # report just nasty things + con 1<<iota; + +# +# buffer size variables, determined by RBUFSIZE +# +RBUFSIZE: int; +BUFSIZE: int; +DIRPERBUF: int; +INDPERBUF: int; +INDPERBUF2: int; +FEPERBUF: int; + +emptyblock: array of byte; + +wrenfd: ref Sys->FD; +thedevice: ref Device; +devnone: ref Device; +wstatallow := 0; +writeallow := 0; +writegroup := 0; + +ream := 0; +readonly := 0; +noatime := 0; +localfs: con 1; +conschan: ref Chan; +consuid := -1; +consgid := -1; +debug := 0; +kfsname: string; +consoleout: chan of string; +mainlock: ref Lock; +pids: list of int; + +noqid: Qid; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + daytime = load Daytime Daytime->PATH; + + styx->init(); + + + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + arg->setusage("disk/kfs [-r [-b bufsize]] [-cADPRW] [-n name] kfsfile"); + bufsize := 1024; + nocheck := 0; + while((o := arg->opt()) != 0) + case o { + 'c' => nocheck = 1; + 'r' => ream = 1; + 'b' => bufsize = int arg->earg(); + 'D' => debug = !debug; + 'P' => writeallow = 1; + 'W' => wstatallow = 1; + 'R' => readonly = 1; + 'A' => noatime = 1; # mainly useful for flash + 'n' => kfsname = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + devnone = ref Device(nil, 1); + mainlock = Lock.new(); + + conschan = Chan.new(nil); + conschan.msize = Styx->MAXRPC; + + mode := Sys->ORDWR; + if(readonly) + mode = Sys->OREAD; + wrenfd = sys->open(hd args, mode); + if(wrenfd == nil) + error(sys->sprint("can't open %s: %r", hd args)); + thedevice = ref Device(wrenfd, readonly); + if(ream){ + if(bufsize <= 0 || bufsize % 512 || bufsize > MAXBUFSIZE) + error(sys->sprint("invalid block size %d", bufsize)); + RBUFSIZE = bufsize; + wrenream(thedevice); + }else{ + if(!wreninit(thedevice)) + error("kfs magic in trouble"); + } + BUFSIZE = RBUFSIZE - Tagsize; + DIRPERBUF = BUFSIZE / Dentrysize; + INDPERBUF = BUFSIZE / 4; + INDPERBUF2 = INDPERBUF * INDPERBUF; + FEPERBUF = (BUFSIZE - Super1size - 4) / 4; + emptyblock = array[RBUFSIZE] of {* => byte 0}; + + iobufinit(30); + + if(ream){ + superream(thedevice, SUPERADDR); + rootream(thedevice, ROOTADDR); + wstatallow = writeallow = 1; + } + if(wrencheck(wrenfd)) + error("kfs super/root in trouble"); + + if(!ream && !superok(0)){ + sys->print("kfs needs check\n"); + if(!nocheck) + check(thedevice, Cquiet|Cfree); + } + + (d, e) := Dentry.geta(thedevice, ROOTADDR, 0, QPROOT, Bread); + if(d != nil && !(d.mode & DDIR)) + e = "not a directory"; + if(e != nil) + error("bad root: "+e); + if(debug) + d.print(); + d.put(); + + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + + sys->pctl(Sys->NEWFD, wrenfd.fd :: 0 :: 1 :: 2 :: nil); + wrenfd = sys->fildes(wrenfd.fd); + thedevice.fd = wrenfd; + + c := chan of int; + + if(Buffering){ + spawn syncproc(c); + pid := <-c; + if(pid) + pids = pid :: pids; + } + spawn consinit(c); + pid := <- c; + if(pid) + pids = pid :: pids; + + spawn kfs(sys->fildes(0)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "fail:error"; +} + +panic(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: panic: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "panic"; +} + +syncproc(c: chan of int) +{ + c <-= 0; +} + +shutdown() +{ + for(; pids != nil; pids = tl pids) + kill(hd pids); + # TO DO: when Bmod deferred, must sync + # sync super block + if(superok(1)){ + # ; + } + iobufclear(); +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +# +# limited file system support for console +# +kattach(fid: int): string +{ + return applycons(ref Tmsg.Attach(1, fid, NOFID, "adm", "")).t1; +} + +kopen(oldfid: int, newfid: int, names: array of string, mode: int): string +{ + (r1, e1) := applycons(ref Tmsg.Walk(1, oldfid, newfid, names)); + if(r1 != nil){ + pick m := r1 { + Walk => + if(len m.qids != len names){ + kclose(newfid); + cprint(Eexist); + return Eexist; + } + * => + return "unexpected reply"; + } + (r1, e1) = applycons(ref Tmsg.Open(1, newfid, mode)); + if(e1 != nil){ + kclose(newfid); + cprint(sys->sprint("open: %s", e1)); + } + } + return e1; +} + +kread(fid: int, offset: int, nbytes: int): (array of byte, string) +{ + (r, e) := applycons(ref Tmsg.Read(1, fid, big offset, nbytes)); + if(r != nil){ + pick m := r { + Read => + return (m.data, nil); + * => + return (nil, "unexpected reply"); + } + } + cprint(sys->sprint("read error: %s", e)); + return (nil, e); +} + +kclose(fid: int) +{ + applycons(ref Tmsg.Clunk(1, fid)); +} + +applycons(t: ref Tmsg): (ref Rmsg, string) +{ + r := apply(conschan, t); + pick m := r { + Error => + if(debug) + cprint(sys->sprint("%s: %s\n", t.text(), m.ename)); + return (nil, m.ename); + } + return (r, nil); +} + +# +# always reads /adm/users in userinit(), then +# optionally serves the command file, if used. +# +Req: adt { + nbytes: int; + rc: chan of (array of byte, string); +}; + +consinit(c: chan of int) +{ + kattach(FID1); + userinit(); + if(kfsname == nil){ + c <-= 0; + exit; + } + cfname := "kfs."+kfsname+".cmd"; + sys->bind("#s", "/chan", Sys->MBEFORE); + file := sys->file2chan("/chan", cfname); + if(file == nil) + error(sys->sprint("can't create /chan/%s: %r", cfname)); + c <-= sys->pctl(0, nil); + consc := chan of string; + checkend := chan of int; + cdata: array of byte; + pending: ref Req; + cfid := -1; + for(;;) alt{ + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(cfid == -1) + cfid = fid; + if(fid != cfid || pending != nil){ + rc <-= (nil, "kfs.cmd is busy"); + break; + } + if(cdata != nil){ + cdata = reply(rc, nbytes, cdata); + break; + } + if(nbytes <= 0 || consoleout == nil){ + rc <-= (nil, nil); + break; + } + pending = ref Req(nbytes, rc); + consc = consoleout; + (nil, data, fid, wc) := <-file.write => + if(cfid == -1) + cfid = fid; + if(wc == nil){ + if(fid == cfid){ + cfid = -1; + pending = nil; + cdata = nil; # discard unread data from last command + if((consc = consoleout) == nil) + consc = chan of string; + } + break; + } + if(fid != cfid){ + wc <-= (0, "kfs.cmd is busy"); + break; + } + (nf, fld) := sys->tokenize(string data, " \t\n\r"); + if(nf < 1){ + wc <-= (0, "illegal kfs request"); + break; + } + case hd fld { + "check" => + if(consoleout != nil){ + wc <-= (0, "check in progress"); + break; + } + f := 0; + if(nf > 1){ + f = checkflags(hd tl fld); + if(f < 0){ + wc <-= (0, "illegal check flag: "+hd tl fld); + break; + } + } + consoleout = chan of string; + spawn checkproc(checkend, f); + wc <-= (len data, nil); + consc = consoleout; + "users" or "user" => + cmd_users(); + wc <-= (len data, nil); + "sync" => + # nothing TO DO until writes are buffered + wc <-= (len data, nil); + "allow" => + wstatallow = writeallow = 1; + wc <-= (len data, nil); + "allowoff" or "disallow" => + wstatallow = writeallow = 0; + wc <-= (len data, nil); + * => + wc <-= (0, "unknown kfs request"); + continue; + } + <-checkend => + consoleout = nil; + consc = chan of string; + s := <-consc => + #sys->print("<-%s\n", s); + req := pending; + pending = nil; + if(req != nil) + cdata = reply(req.rc, req.nbytes, array of byte s); + else + cdata = array of byte s; + if(cdata != nil && cfid != -1) + consc = chan of string; + } +} + +reply(rc: chan of (array of byte, string), nbytes: int, a: array of byte): array of byte +{ + if(len a < nbytes) + nbytes = len a; + rc <-= (a[0:nbytes], nil); + if(nbytes == len a) + return nil; + return a[nbytes:]; +} + +checkproc(c: chan of int, flags: int) +{ + mainlock.lock(); + check(thedevice, flags); + mainlock.unlock(); + c <-= 1; +} + +# +# normal kfs service +# +kfs(rfd: ref Sys->FD) +{ + cp := Chan.new(rfd); + while((t := Tmsg.read(rfd, cp.msize)) != nil){ + if(debug) + sys->print("<- %s\n", t.text()); + r := apply(cp, t); + pick m := r { + Error => + r.tag = t.tag; + } + if(debug) + sys->print("-> %s\n", r.text()); + rbuf := r.pack(); + if(rbuf == nil) + panic("Rmsg.pack"); + if(sys->write(rfd, rbuf, len rbuf) != len rbuf) + panic("mount write"); + } + shutdown(); +} + +apply(cp: ref Chan, t: ref Tmsg): ref Rmsg +{ + mainlock.lock(); # TO DO: this is just to keep console and kfs from colliding + r: ref Rmsg; + pick m := t { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Version => + r = rversion(cp, m); + Auth => + r = rauth(cp, m); + Flush => + r = rflush(cp, m); + Attach => + r = rattach(cp, m); + Walk => + r = rwalk(cp, m); + Open => + r = ropen(cp, m); + Create => + r = rcreate(cp, m); + Read => + r = rread(cp, m); + Write => + r = rwrite(cp, m); + Clunk => + r = rclunk(cp, m); + Remove => + r = rremove(cp, m); + Stat => + r = rstat(cp, m); + Wstat => + r = rwstat(cp, m); + * => + panic("Styx mtype"); + return nil; + } + mainlock.unlock(); + return r; +} + +rversion(cp: ref Chan, t: ref Tmsg.Version): ref Rmsg +{ + cp.msize = RBUFSIZE+IOHDRSZ; + if(cp.msize < Styx->MAXRPC) + cp.msize = Styx->MAXRPC; + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + if(msize < 256) + return ref Rmsg.Error(t.tag, "message size too small"); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(nil: ref Chan, t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, Eauthmsg); +} + +rflush(nil: ref Chan, t: ref Tmsg.Flush): ref Rmsg +{ + # runlock(cp.reflock); + # wlock(cp.reflock); + # wunlock(cp.reflock); + # rlock(cp.reflock); + return ref Rmsg.Flush(t.tag); +} + +err(t: ref Tmsg, s: string): ref Rmsg.Error +{ + return ref Rmsg.Error(t.tag, s); +} + +ferr(t: ref Tmsg, s: string, file: ref File, p: ref Iobuf): ref Rmsg.Error +{ + if(p != nil) + p.put(); + if(file != nil) + file.unlock(); + return ref Rmsg.Error(t.tag, s); +} + +File.new(fid: int): ref File +{ + f := ref File; + f.qlock = chan[1] of int; + f.fid = fid; + f.cons = 0; + f.tlock = nil; + f.wpath = nil; + f.doffset = big 0; + f.dvers = 0; + f.dslot = 0; + f.uid = None; + f.cons = 0; +# f.cuid = None; + return f; +} + +# +# returns a locked file structure +# + +Chan.getfid(cp: self ref Chan, fid: int, flag: int): ref File +{ + if(fid == NOFID) + return nil; + cp.flock(); + for(l := cp.flist; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + cp.funlock(); + if(flag) + return nil; # fid in use + f.lock(); + if(f.fid == fid) + return f; + f.unlock(); + cp.flock(); + } + } + if(flag == 0){ + sys->print("kfs: cannot find %H.%ud", cp, fid); + cp.funlock(); + return nil; + } + f := File.new(fid); + f.lock(); + cp.flist = f :: cp.flist; + cp.funlock(); + return f; +} + +Chan.putfid(cp: self ref Chan, f: ref File) +{ + cp.flock(); + nl: list of ref File; + for(x := cp.flist; x != nil; x = tl x) + if(hd x != f) + nl = hd x :: nl; + cp.flist = nl; + cp.funlock(); + f.unlock(); +} + +File.lock(f: self ref File) +{ + f.qlock <-= 1; +} + +File.unlock(f: self ref File) +{ + <-f.qlock; +} + +Chan.new(fd: ref Sys->FD): ref Chan +{ + c := ref Chan; + c.fd = fd; + c.fqlock = chan[1] of int; +# rlock, wlock: QLock; # lock for reading/writing messages on cp + c.flags = 0; +# reflock: RWLock; # lock for Tflush + c.msize = 0; # set by rversion + return c; +} + +Chan.flock(c: self ref Chan) +{ + c.fqlock <-= 1; +} + +Chan.funlock(c: self ref Chan) +{ + <-c.fqlock; +} + +rattach(cp: ref Chan, t: ref Tmsg.Attach): ref Rmsg +{ + if(t.aname != "" && t.aname != "main") + return err(t, Ebadspc); + file := cp.getfid(t.fid, 1); + if(file == nil) + return err(t, Efidinuse); + p := Iobuf.get(thedevice, ROOTADDR, Bread); + if(p == nil){ + cp.putfid(file); + return err(t, "can't access root block"); + } + d := Dentry.get(p, 0); + if(d == nil || p.checktag(Tdir, QPROOT) || (d.mode & DALLOC) == 0 || (d.mode & DDIR) == 0){ + p.put(); + cp.putfid(file); + return err(t, Ealloc); + } + if(file.access(d, DEXEC)){ + p.put(); + cp.putfid(file); + return err(t, Eaccess); + } + d.access(FREAD, file.uid); + file.fs = thedevice; + file.qid = d.qid; + file.addr = p.addr; + file.slot = 0; + file.open = 0; + file.uid = strtouid(t.uname); + file.wpath = nil; + p.put(); + qid := file.qid; + file.unlock(); + return ref Rmsg.Attach(t.tag, qid); +} + +clone(nfile: ref File, file: ref File) +{ + nfile.qid = file.qid; + nfile.wpath = file.wpath; + nfile.fs = file.fs; + nfile.addr = file.addr; + nfile.slot = file.slot; + nfile.uid = file.uid; +# nfile.cuid = None; + nfile.open = file.open & ~FREMOV; +} + +walkname(file: ref File, wname: string): (string, Qid) +{ + # + # File must not have been opened for I/O by an open + # or create message and must represent a directory. + # + if(file.open != 0) + return (Emode, noqid); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + if(!(d.mode & DDIR)){ + d.put(); + return (Edir1, noqid); + } + + # + # For walked elements the implied user must + # have permission to search the directory. + # + if(file.access(d, DEXEC)){ + d.put(); + return (Eaccess, noqid); + } + d.access(FREAD, file.uid); + + if(wname == "." || wname == ".." && file.wpath == nil){ + d.put(); + return (nil, file.qid); + } + + d1: ref Dentry; # entry for wname, if found + slot: int; + + if(wname == ".."){ + d.put(); + addr := file.wpath.addr; + slot = file.wpath.slot; + (d1, e) = Dentry.geta(file.fs, addr, slot, QPNONE, Bread); + if(d1 == nil) + return (e, noqid); + file.wpath = file.wpath.up; + }else{ + + Search: + for(addr := 0; ; addr++){ + if(d.iob == nil){ + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + } + p1 := d.getblk1(addr, 0); + if(p1 == nil || p1.checktag(Tdir, int d.qid.path)){ + if(p1 != nil) + p1.put(); + return (Eentry, noqid); + } + for(slot = 0; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + if(wname != d1.name) + continue; + # + # update walk path + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + slot += DIRPERBUF*addr; + break Search; + } + p1.put(); + } + d.put(); + } + + file.addr = d1.iob.addr; + file.slot = slot; + file.qid = d1.qid; + d1.put(); + return (nil, file.qid); +} + +rwalk(cp: ref Chan, t: ref Tmsg.Walk): ref Rmsg +{ + nfile, tfile: ref File; + q: Qid; + + # The file identified by t.fid must be valid in the + # current session and must not have been opened for I/O + # by an open or create message. + + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + if(file.open != 0) + return ferr(t, Emode, file, nil); + + # If newfid is not the same as fid, allocate a new file; + # a side effect is checking newfid is not already in use (error); + # if there are no names to walk this will be equivalent to a + # simple 'clone' operation. + # Otherwise, fid and newfid are the same and if there are names + # to walk make a copy of 'file' to be used during the walk as + # 'file' must only be updated on success. + # Finally, it's a no-op if newfid is the same as fid and t.nwname + # is 0. + + nwqid := 0; + if(t.newfid != t.fid){ + if((nfile = cp.getfid(t.newfid, 1)) == nil) + return ferr(t, Efidinuse, file, nil); + } + else if(len t.names != 0) + nfile = tfile = File.new(NOFID); + else{ + file.unlock(); + return ref Rmsg.Walk(t.tag, nil); + } + clone(nfile, file); + + r := ref Rmsg.Walk(t.tag, array[len t.names] of Qid); + error: string; + for(nwname := 0; nwname < len t.names; nwname++){ + (error, q) = walkname(nfile, t.names[nwname]); + if(error != nil) + break; + r.qids[nwqid++] = q; + } + + if(len t.names == 0){ + + # Newfid must be different to fid (see above) + # so this is a simple 'clone' operation - there's + # nothing to do except unlock unless there's + # an error. + + nfile.unlock(); + if(error != nil) + cp.putfid(nfile); + }else if(nwqid < len t.names){ + # + # Didn't walk all elements, 'clunk' nfile + # and leave 'file' alone. + # Clear error if some of the elements were + # walked OK. + # + if(nfile != tfile) + cp.putfid(nfile); + if(nwqid != 0) + error = nil; + r.qids = r.qids[0:nwqid]; + }else{ + # + # Walked all elements. If newfid is the same + # as fid must update 'file' from the temporary + # copy used during the walk. + # Otherwise just unlock (when using tfile there's + # no need to unlock as it's a local). + # + if(nfile == tfile){ + file.qid = nfile.qid; + file.wpath = nfile.wpath; + file.addr = nfile.addr; + file.slot = nfile.slot; + }else + nfile.unlock(); + } + file.unlock(); + + if(error != nil) + return err(t, error); + return r; +} + +ropen(cp: ref Chan, f: ref Tmsg.Open): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # + # if remove on close, check access here + # + ro := isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)); + if(f.mode & ORCLOSE){ + if(ro) + return ferr(f, Eronly, file, nil); + # + # check on parent directory of file to be deleted + # + if(file.wpath == nil || file.wpath.addr == file.addr) + return ferr(f, Ephase, file, nil); + p := Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p == nil || p.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p); + if((d := Dentry.get(p, file.wpath.slot)) == nil || !(d.mode & DALLOC)) + return ferr(f, Ephase, file, p); + if(file.access(d, DWRITE)) + return ferr(f, Eaccess, file, p); + p.put(); + } + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + p := d.iob; + qid := d.qid; + fmod: int; + case f.mode & 7 { + + OREAD => + if(file.access(d, DREAD) && !wok) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + OWRITE => + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FWRITE; + + ORDWR => + if((d.mode & DDIR) + || (file.access(d, DREAD) && !wok) + || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FREAD+FWRITE; + + OEXEC => + if((d.mode & DDIR) || (file.access(d, DEXEC) && !wok)) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + * => + return ferr(f, Emode, file, p); + } + if(f.mode & OTRUNC){ + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + } + if(d.mode & DLOCK){ + if((t := tlocked(file, d)) == nil) + return ferr(f, Elocked, file, p); + file.tlock = t; + t.file = file; + } + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + if((f.mode & OTRUNC) && !(d.mode & DAPND)){ + d.trunc(file.uid); + qid.vers = d.qid.vers; + } + file.lastra = 1; + p.put(); + file.unlock(); + return ref Rmsg.Open(f.tag, qid, cp.msize-IOHDRSZ); +} + +rcreate(cp: ref Chan, f: ref Tmsg.Create): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(e != nil) + return ferr(f, e, file, nil); + p := d.iob; + if(!(d.mode & DDIR)) + return ferr(f, Edir2, file, p); + if(file.access(d, DWRITE) && !wok) + return ferr(f, Eaccess, file, p); + d.access(FREAD, file.uid); + + # + # Check the name is valid and will fit in an old + # directory entry. + # + if((l := checkname9p2(f.name)) == 0) + return ferr(f, Ename, file, p); + if(l+1 > NAMELEN) + return ferr(f, Etoolong, file, p); + if(f.name == "." || f.name == "..") + return ferr(f, Edot, file, p); + + addr1 := 0; # block with first empty slot, if any + slot1 := 0; + for(addr := 0; ; addr++){ + if((p1 := d.getblk(addr, 0)) == nil){ + if(addr1 != 0) + break; + p1 = d.getblk(addr, Tdir); + } + if(p1 == nil) + return ferr(f, Efull, file, p); + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + return ferr(f, Ephase, file, p); + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d1 := Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)){ + if(addr1 == 0){ + addr1 = p1.addr; + slot1 = slot + addr*DIRPERBUF; + } + continue; + } + if(f.name == d1.name){ + p1.put(); + return ferr(f, Eexist, file, p); + } + } + p1.put(); + } + + fmod: int; + + case f.mode & 7 { + OEXEC or + OREAD => # seems only useful to make directories + fmod = FREAD; + + OWRITE => + fmod = FWRITE; + + ORDWR => + fmod = FREAD+FWRITE; + + * => + return ferr(f, Emode, file, p); + } + if(f.perm & DMDIR) + if((f.mode & OTRUNC) || (f.perm & DMAPPEND) || (fmod & FWRITE)) + return ferr(f, Eaccess, file, p); + + # do it + + path := qidpathgen(file.fs); + if((p1 := Iobuf.get(file.fs, addr1, Bread|Bimm|Bmod)) == nil) + return ferr(f, Ephase, file, p); + d1 := Dentry.get(p1, slot1); + if(d1 == nil || p1.checktag(Tdir, int d.qid.path)){ + p.put(); + return ferr(f, Ephase, file, p1); + } + if(d1.mode & DALLOC){ + p.put(); + return ferr(f, Ephase, file, p1); + } + + d1.name = f.name; + if(cp == conschan){ + d1.uid = consuid; + d1.gid = consgid; + } + else{ + d1.uid = file.uid; + d1.gid = d.gid; + f.perm &= d.mode | ~8r666; + if(f.perm & DMDIR) + f.perm &= d.mode | ~8r777; + } + d1.qid.path = big path; + d1.qid.vers = 0; + d1.mode = DALLOC | (f.perm & 8r777); + if(f.perm & DMDIR) + d1.mode |= DDIR; + if(f.perm & DMAPPEND) + d1.mode |= DAPND; + t: ref Tlock; + if(f.perm & DMEXCL){ + d1.mode |= DLOCK; + t = tlocked(file, d1); + # if nil, out of tlock structures + } + d1.access(FWRITE, file.uid); + d1.change(~0); + d1.update(); + qid := mkqid(path, 0, d1.mode); + p1.put(); + d.change(~0); + d.access(FWRITE, file.uid); + d.update(); + p.put(); + + # + # do a walk to new directory entry + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + file.qid = qid; + file.tlock = t; + if(t != nil) + t.file = file; + file.lastra = 1; + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + file.addr = addr1; + file.slot = slot1; + file.unlock(); + return ref Rmsg.Create(f.tag, qid, cp.msize-IOHDRSZ); +} + +dirread(cp: ref Chan, f: ref Tmsg.Read, file: ref File, d: ref Dentry): ref Rmsg +{ + p1: ref Iobuf; + d1: ref Dentry; + + count := f.count; + data := array[count] of byte; + offset := f.offset; + iounit := cp.msize-IOHDRSZ; + + # Pick up where we left off last time if nothing has changed, + # otherwise must scan from the beginning. + + addr, slot: int; + start: big; + + if(offset == file.doffset){ # && file.qid.vers == file.dvers + addr = file.dslot/DIRPERBUF; + slot = file.dslot%DIRPERBUF; + start = offset; + } + else{ + addr = 0; + slot = 0; + start = big 0; + } + + nread := 0; +Dread: + for(;;){ + if(d.iob == nil){ + # + # This is just a check to ensure the entry hasn't + # gone away during the read of each directory block. + # + e: string; + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + p1 = d.getblk1(addr, 0); + if(p1 == nil) + break; + if(p1.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p1); + + for(; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + dir := dir9p2(d1); + n := styx->packdirsize(dir); + if(n > count-nread){ + p1.put(); + break Dread; + } + data[nread:] = styx->packdir(dir); + start += big n; + if(start < offset) + continue; + if(count < n){ + p1.put(); + break Dread; + } + count -= n; + nread += n; + offset += big n; + } + p1.put(); + slot = 0; + addr++; + } + + file.doffset = offset; + file.dvers = file.qid.vers; + file.dslot = slot+DIRPERBUF*addr; + + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rread(cp: ref Chan, f: ref Tmsg.Read): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FREAD)) + return ferr(f, Eopen, file, nil); + count := f.count; + iounit := cp.msize-IOHDRSZ; + if(count < 0 || count > iounit) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FREAD, file.uid); + if(d.mode & DDIR) + return dirread(cp, f, file, d); + + if(offset+big count > d.size) + count = int (d.size - offset); + if(count < 0) + count = 0; + data := array[count] of byte; + nread := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + if(addr == file.lastra+1) + ; # dbufread(p, d, addr+1); + file.lastra = addr; + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + p1 := d.getblk1(addr, 0); + if(p1 != nil){ + if(p1.checktag(Tfile, QPNONE)){ + p1.put(); + return ferr(f, Ephase, file, nil); + } + data[nread:] = p1.iobuf[o:o+n]; + p1.put(); + }else + data[nread:] = emptyblock[0:n]; + count -= n; + nread += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rwrite(cp: ref Chan, f: ref Tmsg.Write): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FWRITE)) + return ferr(f, Eopen, file, nil); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + count := len f.data; + if(count < 0 || count > cp.msize-IOHDRSZ) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FWRITE, file.uid); + if(d.mode & DAPND) + offset = d.size; + end := offset + big count; + if(end > d.size){ + if(end > MAXFILESIZE) + return ferr(f, Etoobig, file, nil); + d.size = end; + d.change(Usize); + } + d.update(); + + nwrite := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + qpath := int d.qid.path; + p1 := d.getblk1(addr, Tfile); + if(p1 == nil) + return ferr(f, Efull, file, nil); + if(p1.checktag(Tfile, qpath)){ + p1.put(); + return ferr(f, Ealloc, file, nil); + } + p1.iobuf[o:] = f.data[nwrite:nwrite+n]; + p1.flags |= Bmod; + p1.put(); + count -= n; + nwrite += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Write(f.tag, nwrite); +} + +doremove(f: ref File, iscon: int): string +{ + if(isro(f.fs) || f.cons == 0 && (writegroup && !ingroup(f.uid, writegroup))) + return Eronly; + # + # check permission on parent directory of file to be deleted + # + if(f.wpath == nil || f.wpath.addr == f.addr) + return Ephase; + (d1, e1) := Dentry.geta(f.fs, f.wpath.addr, f.wpath.slot, QPNONE, Bread); + if(e1 != nil) + return e1; + if(!iscon && f.access(d1, DWRITE)){ + d1.put(); + return Eaccess; + } + d1.access(FWRITE, f.uid); + d1.put(); + + # + # check on file to be deleted + # + (d, e) := Dentry.getd(f, Bread); + if(e != nil) + return e; + + # + # if deleting a directory, make sure it is empty + # + if(d.mode & DDIR) + for(addr:=0; (p1 := d.getblk(addr, 0)) != nil; addr++){ + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + d.put(); + return Ephase; + } + for(slot:=0; slot<DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + p1.put(); + d.put(); + return Eempty; + } + p1.put(); + } + + # + # do it + # + d.trunc(f.uid); + d.buf[0:] = emptyblock[0:Dentrysize]; + d.put(); + return nil; +} + +clunk(cp: ref Chan, file: ref File, remove: int, wok: int): string +{ + if((t := file.tlock) != nil){ + if(t.file == file) + t.time = 0; # free the lock + file.tlock = nil; + } + if(remove) + error := doremove(file, wok); + file.open = 0; + file.wpath = nil; + cp.putfid(file); + + return error; +} + +rclunk(cp: ref Chan, t: ref Tmsg.Clunk): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + clunk(cp, file, file.open & FREMOV, 0); + return ref Rmsg.Clunk(t.tag); +} + +rremove(cp: ref Chan, t: ref Tmsg.Remove): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + e := clunk(cp, file, 1, cp == conschan); + if(e != nil) + return err(t, e); + return ref Rmsg.Remove(t.tag); +} + +rstat(cp: ref Chan, f: ref Tmsg.Stat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + dir := dir9p2(d); + if(d.qid.path == big QPROOT) # stat of root gives time + dir.atime = now(); + d.put(); + if(styx->packdirsize(dir) > cp.msize-IOHDRSZ) + return ferr(f, Ersc, file, nil); + file.unlock(); + + return ref Rmsg.Stat(f.tag, dir); +} + +rwstat(cp: ref Chan, f: ref Tmsg.Wstat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # if user none, can't do anything unless in allow mode + + if(file.uid == None && !wstatallow) + return ferr(f, Eaccess, file, nil); + + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + # + # first get parent + # + p1: ref Iobuf; + d1: ref Dentry; + if(file.wpath != nil){ + p1 = Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p1 == nil) + return ferr(f, Ephase, file, p1); + d1 = Dentry.get(p1, file.wpath.slot); + if(d1 == nil || p1.checktag(Tdir, QPNONE) || !(d1.mode & DALLOC)) + return ferr(f, Ephase, file, p1); + } + + # + # now the file + # + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, p1); + + # + # Convert the message and fix up + # fields not to be changed. + # + dir := f.stat; + if(dir.uid == nil) + uid := d.uid; + else + uid = strtouid(dir.uid); + if(dir.gid == nil) + gid := d.gid; + else + gid = strtouid(dir.gid); + if(dir.name == nil) + dir.name = d.name; + else{ + if((l := checkname9p2(dir.name)) == 0){ + d.put(); + return ferr(f, Ename, file, p1); + } + if(l+1 > NAMELEN){ + d.put(); + return ferr(f, Etoolong, file, p1); + } + } + + # Before doing sanity checks, find out what the + # new 'mode' should be: + # if 'type' and 'mode' are both defaults, take the + # new mode from the old directory entry; + # else if 'type' is the default, use the new mode entry; + # else if 'mode' is the default, create the new mode from + # 'type' or'ed with the old directory mode; + # else neither are defaults, use the new mode but check + # it agrees with 'type'. + + if(dir.qid.qtype == 16rFF && dir.mode == ~0){ + dir.mode = d.mode & 8r777; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DDIR) + dir.mode |= DMDIR; + } + else if(dir.qid.qtype == 16rFF){ + # nothing to do + } + else if(dir.mode == ~0) + dir.mode = (dir.qid.qtype<<24)|(d.mode & 8r777); + else if(dir.qid.qtype != ((dir.mode>>24) & 16rFF)){ + d.put(); + return ferr(f, Eqidmode, file, p1); + } + + # Check for unknown type/mode bits + # and an attempt to change the directory bit. + + if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|8r777)){ + d.put(); + return ferr(f, Enotm, file, p1); + } + if(d.mode & DDIR) + mode := DMDIR; + else + mode = 0; + if((dir.mode^mode) & DMDIR){ + d.put(); + return ferr(f, Enotd, file, p1); + } + + if(dir.mtime == ~0) + dir.mtime = d.mtime; + if(dir.length == ~big 0) + dir.length = big d.size; + + + # Currently, can't change length. + + if(dir.length != big d.size){ + d.put(); + return ferr(f, Enotl, file, p1); + } + + + # if chown, + # must be god + # wstatallow set to allow chown during boot + + if(uid != d.uid && !wstatallow){ + d.put(); + return ferr(f, Enotu, file, p1); + } + + # if chgroup, + # must be either + # a) owner and in new group + # b) leader of both groups + # wstatallow and writeallow are set to allow chgrp during boot + + while(gid != d.gid){ + if(wstatallow || writeallow) + break; + if(d.uid == file.uid && ingroup(file.uid, gid)) + break; + if(leadgroup(file.uid, gid)) + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotg, file, p1); + } + + # if rename, + # must have write permission in parent + + while(d.name != dir.name){ + + # drop entry to prevent deadlock, then + # check that destination name is valid and unique + + d.put(); + if(checkname9p2(dir.name) == 0 || d1 == nil) + return ferr(f, Ename, file, p1); + if(dir.name == "." || dir.name == "..") + return ferr(f, Edot, file, p1); + + + for(addr := 0; ; addr++){ + if((p := d1.getblk(addr, 0)) == nil) + break; + if(p.checktag(Tdir, int d1.qid.path)){ + p.put(); + continue; + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d = Dentry.get(p, slot); + if(!(d.mode & DALLOC)) + continue; + if(dir.name == d.name){ + p.put(); + return ferr(f, Eexist, file, p1); + } + } + p.put(); + } + + # reacquire entry + + (d, nil) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, Ephase, file, p1); + + if(wstatallow || writeallow) # set to allow rename during boot + break; + if(d1 == nil || file.access(d1, DWRITE)){ + d.put(); + return ferr(f, Eaccess, file, p1); + } + break; + } + + # if mode/time, either + # a) owner + # b) leader of either group + + mode = dir.mode & 8r777; + if(dir.mode & DMAPPEND) + mode |= DAPND; + if(dir.mode & DMEXCL) + mode |= DLOCK; + while(d.mtime != dir.mtime || ((d.mode^mode) & (DAPND|DLOCK|8r777))){ + if(wstatallow) # set to allow chmod during boot + break; + if(d.uid == file.uid) + break; + if(leadgroup(file.uid, gid)) + break; + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotu, file, p1); + } + d.mtime = dir.mtime; + d.uid = uid; + d.gid = gid; + d.mode = (mode & (DAPND|DLOCK|8r777)) | (d.mode & (DALLOC|DDIR)); + + d.name = dir.name; + d.access(FWSTAT, file.uid); + d.change(~0); + d.put(); + + if(p1 != nil) + p1.put(); + file.unlock(); + + return ref Rmsg.Wstat(f.tag); +} + +superok(set: int): int +{ + sb := Superb.get(thedevice, Bread|Bmod|Bimm); + ok := sb.fsok; + sb.fsok = set; + if(debug) + sb.print(); + sb.touched(); + sb.put(); + return ok; +} + +# little-endian +get2(a: array of byte, o: int): int +{ + return (int a[o+1]<<8) | int a[o]; +} + +get2s(a: array of byte, o: int): int +{ + v := (int a[o+1]<<8) | int a[o]; + if(v & 16r8000) + v |= ~0 << 8; + return v; +} + +get4(a: array of byte, o: int): int +{ + return (int a[o+3]<<24) | (int a[o+2] << 16) | (int a[o+1]<<8) | int a[o]; +} + +put2(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); +} + +put4(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); + a[o+2] = byte (v>>16); + a[o+3] = byte (v>>24); +} + +Tag.unpack(a: array of byte): Tag +{ + return Tag(get2(a,2), get4(a,4)); +} + +Tag.pack(t: self Tag, a: array of byte) +{ + put2(a, 0, 0); + put2(a, 2, t.tag); + if(t.path != QPNONE) + put4(a, 4, t.path & ~QPDIR); +} + +Superb.get(dev: ref Device, flags: int): ref Superb +{ + p := Iobuf.get(dev, SUPERADDR, flags); + if(p == nil) + return nil; + if(p.checktag(Tsuper, QPSUPER)){ + p.put(); + return nil; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + return sb; +} + +Superb.touched(s: self ref Superb) +{ + s.iob.flags |= Bmod; +} + +Superb.put(sb: self ref Superb) +{ + if(sb.iob == nil) + return; + if(sb.iob.flags & Bmod) + sb.pack(sb.iob.iobuf); + sb.iob.put(); + sb.iob = nil; +} + +# this is the disk structure +# Superb: +# Super1; +# Fbuf fbuf; +# Fbuf: +# nfree[4] +# free[] # based on BUFSIZE +# Super1: +# long fstart; +# long fsize; +# long tfree; +# long qidgen; # generator for unique ids +# long fsok; # file system ok +# long roraddr; # dump root addr +# long last; # last super block addr +# long next; # next super block addr + +Ofstart: con 0; +Ofsize: con Ofstart+4; +Otfree: con Ofsize+4; +Oqidgen: con Otfree+4; +Ofsok: con Oqidgen+4; +Ororaddr: con Ofsok+4; +Olast: con Ororaddr+4; +Onext: con Olast+4; +Super1size: con Onext+4; + +Superb.unpack(a: array of byte): ref Superb +{ + s := ref Superb; + s.fstart = get4(a, Ofstart); + s.fsize = get4(a, Ofsize); + s.tfree = get4(a, Otfree); + s.qidgen = get4(a, Oqidgen); + s.fsok = get4(a, Ofsok); + s.fbuf = a[Super1size:]; + return s; +} + +Superb.pack(s: self ref Superb, a: array of byte) +{ + put4(a, Ofstart, s.fstart); + put4(a, Ofsize, s.fsize); + put4(a, Otfree, s.tfree); + put4(a, Oqidgen, s.qidgen); + put4(a, Ofsok, s.fsok); +} + +Superb.print(sb: self ref Superb) +{ + sys->print("fstart=%ud fsize=%ud tfree=%ud qidgen=%ud fsok=%d\n", + sb.fstart, sb.fsize, sb.tfree, sb.qidgen, sb.fsok); +} + +Dentry.get(p: ref Iobuf, slot: int): ref Dentry +{ + if(p == nil) + return nil; + buf := p.iobuf[(slot%DIRPERBUF)*Dentrysize:]; + d := Dentry.unpack(buf); + d.iob = p; + d.buf = buf; + return d; +} + +Dentry.geta(fs: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string) +{ + p := Iobuf.get(fs, addr, mode); + if(p == nil || p.checktag(Tdir, qpath)){ + if(p != nil) + p.put(); + return (nil, Ealloc); + } + d := Dentry.get(p, slot); + if(d == nil || !(d.mode & DALLOC)){ + p.put(); + return (nil, Ealloc); + } + return (d, nil); +} + +Dentry.getd(file: ref File, mode: int): (ref Dentry, string) +{ + (d, e) := Dentry.geta(file.fs, file.addr, file.slot, QPNONE, mode); # QPNONE should be file.wpath's path + if(e != nil) + return (nil, e); + if(file.qid.path != d.qid.path || (file.qid.qtype&QTDIR) != (d.qid.qtype&QTDIR)){ + d.put(); + return (nil, Eqid); + } + return (d, nil); +} + +# this is the disk structure: +# char name[NAMELEN]; +# short uid; +# short gid; [2*2] +# ushort mode; +# #define DALLOC 0x8000 +# #define DDIR 0x4000 +# #define DAPND 0x2000 +# #define DLOCK 0x1000 +# #define DREAD 0x4 +# #define DWRITE 0x2 +# #define DEXEC 0x1 +# [ushort muid] [2*2] +# Qid.path; [4] +# Qid.version; [4] +# long size; [4] +# long dblock[NDBLOCK]; +# long iblock; +# long diblock; +# long atime; +# long mtime; + +Oname: con 0; +Ouid: con Oname+NAMELEN; +Ogid: con Ouid+2; +Omode: con Ogid+2; +Omuid: con Omode+2; +Opath: con Omuid+2; +Overs: con Opath+4; +Osize: con Overs+4; +Odblock: con Osize+4; +Oiblock: con Odblock+NDBLOCK*4; +Odiblock: con Oiblock+4; +Oatime: con Odiblock+4; +Omtime: con Oatime+4; +Dentrysize: con Omtime+4; + +Dentry.unpack(a: array of byte): ref Dentry +{ + d := ref Dentry; + for(i:=0; i<NAMELEN; i++) + if(int a[i] == 0) + break; + d.name = string a[0:i]; + d.uid = get2s(a, Ouid); + d.gid = get2s(a, Ogid); + d.mode = get2(a, Omode); + d.muid = get2(a, Omuid); # note: not set by Plan 9's kfs + d.qid = mkqid(get4(a, Opath), get4(a, Overs), d.mode); + d.size = big get4(a, Osize) & big 16rFFFFFFFF; + d.atime = get4(a, Oatime); + d.mtime = get4(a, Omtime); + d.mod = 0; + return d; +} + +Dentry.change(d: self ref Dentry, f: int) +{ + d.mod |= f; +} + +Dentry.update(d: self ref Dentry) +{ + f := d.mod; + d.mod = 0; + if(d.iob == nil || (d.iob.flags & Bmod) == 0){ + if(f != 0) + panic("Dentry.update"); + return; + } + a := d.buf; + if(f & Uname){ + b := array of byte d.name; + for(i := 0; i < NAMELEN; i++) + if(i < len b) + a[i] = b[i]; + else + a[i] = byte 0; + } + if(f & Uids){ + put2(a, Ouid, d.uid); + put2(a, Ogid, d.gid); + } + if(f & Umode) + put2(a, Omode, d.mode); + if(f & Uqid){ + path := int d.qid.path; + if(d.mode & DDIR) + path |= QPDIR; + put4(a, Opath, path); + put4(a, Overs, d.qid.vers); + } + if(f & Usize) + put4(a, Osize, int d.size); + if(f & Utime){ + put4(a, Omtime, d.mtime); + put4(a, Oatime, d.atime); + } + d.iob.flags |= Bmod; +} + +Dentry.access(d: self ref Dentry, f: int, uid: int) +{ + if((p := d.iob) != nil && !readonly){ + if((f & (FWRITE|FWSTAT)) == 0 && noatime) + return; + if(f & (FREAD|FWRITE|FWSTAT)){ + d.atime = now(); + put4(d.buf, Oatime, d.atime); + p.flags |= Bmod; + } + if(f & FWRITE){ + d.mtime = now(); + put4(d.buf, Omtime, d.mtime); + d.muid = uid; + put2(d.buf, Omuid, uid); + d.qid.vers++; + put4(d.buf, Overs, d.qid.vers); + p.flags |= Bmod; + } + } +} + +# +# release the directory entry buffer and thus the +# lock on both buffer and entry, typically during i/o, +# to be reacquired later if needed +# +Dentry.release(d: self ref Dentry) +{ + if(d.iob != nil){ + d.update(); + d.iob.put(); + d.iob = nil; + d.buf = nil; + } +} + +Dentry.getblk(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 0); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +# +# same as Dentry.buf but calls d.release +# to reduce interference. +# +Dentry.getblk1(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 1); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +Dentry.rel2abs(d: self ref Dentry, a: int, tag: int, putb: int): int +{ + if(a < 0){ + sys->print("Dentry.rel2abs: neg\n"); + return 0; + } + p := d.iob; + if(p == nil || d.buf == nil) + panic("nil iob"); + data := d.buf; + qpath := int d.qid.path; + dev := p.dev; + if(a < NDBLOCK){ + addr := get4(data, Odblock+a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, qpath); + put4(data, Odblock+a*4, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return addr; + } + a -= NDBLOCK; + if(a < INDPERBUF){ + addr := get4(data, Oiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind1, qpath); + put4(data, Oiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return indfetch(dev, qpath, addr, a, Tind1, tag); + } + a -= INDPERBUF; + if(a < INDPERBUF2){ + addr := get4(data, Odiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind2, qpath); + put4(data, Odiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + addr = indfetch(dev, qpath, addr, a/INDPERBUF, Tind2, Tind1); + return indfetch(dev, qpath, addr, a%INDPERBUF, Tind1, tag); + } + if(putb) + d.release(); + sys->print("Dentry.buf: trip indirect\n"); + return 0; +} + +indfetch(dev: ref Device, path: int, addr: int, a: int, itag: int, tag: int): int +{ + if(addr == 0) + return 0; + bp := Iobuf.get(dev, addr, Bread); + if(bp == nil){ + sys->print("ind fetch bp = nil\n"); + return 0; + } + if(bp.checktag(itag, path)){ + sys->print("ind fetch tag\n"); + bp.put(); + return 0; + } + addr = get4(bp.iobuf, a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, path); + if(addr != 0){ + put4(bp.iobuf, a*4, addr); + bp.flags |= Bmod; + if(localfs || tag == Tdir) + bp.flags |= Bimm; + bp.settag(itag, path); + } + } + bp.put(); + return addr; +} + +balloc(dev: ref Device, tag: int, qpath: int): int +{ + # TO DO: cache superblock to reduce pack/unpack + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("balloc: super block"); + n := get4(sb.fbuf, 0); + n--; + sb.tfree--; + if(n < 0 || n >= FEPERBUF) + panic("balloc: bad freelist"); + a := get4(sb.fbuf, 4+n*4); + if(n == 0){ + if(a == 0){ + sb.tfree = 0; + sb.touched(); + sb.put(); + return 0; + } + bp := Iobuf.get(dev, a, Bread); + if(bp == nil || bp.checktag(Tfree, QPNONE)){ + if(bp != nil) + bp.put(); + sb.put(); + return 0; + } + sb.fbuf[0:] = bp.iobuf[0:(FEPERBUF+1)*4]; + sb.touched(); + bp.put(); + }else{ + put4(sb.fbuf, 0, n); + sb.touched(); + } + bp := Iobuf.get(dev, a, Bmod); + bp.iobuf[0:] = emptyblock; + bp.settag(tag, qpath); + if(tag == Tind1 || tag == Tind2 || tag == Tdir) + bp.flags |= Bimm; + bp.put(); + sb.put(); + return a; +} + +bfree(dev: ref Device, addr: int, d: int) +{ + if(addr == 0) + return; + if(d > 0){ + d--; + p := Iobuf.get(dev, addr, Bread); + if(p != nil){ + for(i:=INDPERBUF-1; i>=0; i--){ + a := get4(p.iobuf, i*4); + bfree(dev, a, d); + } + p.put(); + } + } + + # stop outstanding i/o + p := Iobuf.get(dev, addr, Bprobe); + if(p != nil){ + p.flags &= ~(Bmod|Bimm); + p.put(); + } + + s := Superb.get(dev, Bread|Bmod); + if(s == nil) + panic("bfree: super block"); + addfree(dev, addr, s); + s.put(); +} + +addfree(dev: ref Device, addr: int, sb: ref Superb) +{ + if(addr >= sb.fsize){ + sys->print("addfree: bad addr %ud\n", addr); + return; + } + n := get4(sb.fbuf, 0); + if(n < 0 || n > FEPERBUF) + panic("addfree: bad freelist"); + if(n >= FEPERBUF){ + p := Iobuf.get(dev, addr, Bmod); + if(p == nil) + panic("addfree: Iobuf.get"); + p.iobuf[0:] = sb.fbuf[0:(1+FEPERBUF)*4]; + sb.fbuf[0:] = emptyblock[0:(1+FEPERBUF)*4]; # clear it for debugging + p.settag(Tfree, QPNONE); + p.put(); + n = 0; + } + put4(sb.fbuf, 4+n*4, addr); + put4(sb.fbuf, 0, n+1); + sb.tfree++; + if(addr >= sb.fsize) + sb.fsize = addr+1; + sb.touched(); +} + +qidpathgen(dev: ref Device): int +{ + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("qidpathgen: super block"); + sb.qidgen++; + path := sb.qidgen; + sb.touched(); + sb.put(); + return path; +} + +Dentry.trunc(d: self ref Dentry, uid: int) +{ + p := d.iob; + data := d.buf; + bfree(p.dev, get4(data, Odiblock), 2); + put4(data, Odiblock, 0); + bfree(p.dev, get4(data, Oiblock), 1); + put4(data, Oiblock, 0); + for(i:=NDBLOCK-1; i>=0; i--){ + bfree(p.dev, get4(data, Odblock+i*4), 0); + put4(data, Odblock+i*4, 0); + } + d.size = big 0; + d.change(Usize); + p.flags |= Bmod|Bimm; + d.access(FWRITE, uid); + d.update(); +} + +Dentry.put(d: self ref Dentry) +{ + p := d.iob; + if(p == nil || d.buf == nil) + return; + d.update(); + p.put(); + d.iob = nil; + d.buf = nil; +} + +Dentry.print(d: self ref Dentry) +{ + sys->print("name=%#q uid=%d gid=%d mode=#%8.8ux qid.path=#%bux qid.vers=%ud size=%bud\n", + d.name, d.uid, d.gid, d.mode, d.qid.path, d.qid.vers, d.size); + p := d.iob; + if(p != nil && (data := p.iobuf) != nil){ + sys->print("\tdblock="); + for(i := 0; i < NDBLOCK; i++) + sys->print(" %d", get4(data, Odblock+i*4)); + sys->print(" iblock=%ud diblock=%ud\n", get4(data, Oiblock), get4(data, Odiblock)); + } +} + +HWidth: con 5; # buffers per line + +hiob: array of ref Hiob; + +iobufinit(niob: int) +{ + nhiob := niob/HWidth; + while(!prime(nhiob)) + nhiob++; + hiob = array[nhiob] of {* => ref Hiob(nil, Lock.new(), 0)}; + # allocate the buffers now + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(h.niob < HWidth) + h.newbuf(); + } +} + +iobufclear() +{ + # eliminate the cyclic references + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(--h.niob >= 0){ + p := hiob[i].link; + hiob[i].link = p.fore; + p.fore = p.back = nil; + p = nil; + } + } +} + +prime(n: int): int +{ + if((n%2) == 0) + return 0; + for(i:=3;; i+=2) { + if((n%i) == 0) + return 0; + if(i*i >= n) + return 1; + } +} + +Hiob.newbuf(hb: self ref Hiob): ref Iobuf +{ + # hb must be locked + p := ref Iobuf; + p.qlock = chan[1] of int; + q := hb.link; + if(q != nil){ + p.fore = q; + p.back = q.back; + q.back = p; + p.back.fore = p; + }else{ + hb.link = p; + p.fore = p; + p.back = p; + } + p.dev = devnone; + p.addr = -1; + p.flags = 0; + p.xiobuf = array[RBUFSIZE] of byte; + hb.niob++; + return p; +} + +Iobuf.get(dev: ref Device, addr: int, flags: int): ref Iobuf +{ + hb := hiob[addr%len hiob]; + p: ref Iobuf; +Search: + for(;;){ + hb.lk.lock(); + s := hb.link; + + # see if it's active + p = s; + do{ + if(p.addr == addr && p.dev == dev){ + if(p != s){ + p.back.fore = p.fore; + p.fore.back = p.back; + p.fore = s; + p.back = s.back; + s.back = p; + p.back.fore = p; + hb.link = p; + } + hb.lk.unlock(); + p.lock(); + if(p.addr != addr || p.dev != dev){ + # lost race + p.unlock(); + continue Search; + } + p.flags |= flags; + p.iobuf = p.xiobuf; + return p; + } + }while((p = p.fore) != s); + if(flags == Bprobe){ + hb.lk.unlock(); + return nil; + } + + # steal the oldest unlocked buffer + do{ + p = s.back; + if(p.canlock()){ + # TO DO: if Bmod, write it out and restart Hashed + # for now we needn't because Iobuf.put is synchronous + if(p.flags & Bmod) + sys->print("Bmod unexpected (%ud)\n", p.addr); + hb.link = p; + p.dev = dev; + p.addr = addr; + p.flags = flags; + break Search; + } + s = p; + }while(p != hb.link); + + # no unlocked blocks available; add a new one + p = hb.newbuf(); + p.lock(); # return it locked + break; + } + + p.dev = dev; + p.addr = addr; + p.flags = flags; + hb.lk.unlock(); + p.iobuf = p.xiobuf; + if(flags & Bread){ + if(wrenread(dev.fd, addr, p.iobuf)){ + eprint(sys->sprint("error reading block %ud: %r", addr)); + p.flags = 0; + p.dev = devnone; + p.addr = -1; + p.iobuf = nil; + p.unlock(); + return nil; + } + } + return p; +} + +Iobuf.put(p: self ref Iobuf) +{ + if(p.flags & Bmod) + p.flags |= Bimm; # temporary; see comment in Iobuf.get + if(p.flags & Bimm){ + if(!(p.flags & Bmod)) + eprint(sys->sprint("imm and no mod (%d)", p.addr)); + if(!wrenwrite(p.dev.fd, p.addr, p.iobuf)) + p.flags &= ~(Bmod|Bimm); + else + panic(sys->sprint("error writing block %ud: %r", p.addr)); + } + p.iobuf = nil; + p.unlock(); +} + +Iobuf.lock(p: self ref Iobuf) +{ + p.qlock <-= 1; +} + +Iobuf.canlock(p: self ref Iobuf): int +{ + alt{ + p.qlock <-= 1 => + return 1; + * => + return 0; + } +} + +Iobuf.unlock(p: self ref Iobuf) +{ + <-p.qlock; +} + +File.access(f: self ref File, d: ref Dentry, m: int): int +{ + if(wstatallow) + return 0; + + # none gets only other permissions + + if(f.uid != None){ + if(f.uid == d.uid) # owner + if((m<<6) & d.mode) + return 0; + if(ingroup(f.uid, d.gid)) # group membership + if((m<<3) & d.mode) + return 0; + } + + # + # other access for everyone except members of group "noworld" + # + if(m & d.mode){ + # + # walk directories regardless. + # otherwise it's impossible to get + # from the root to noworld's directories. + # + if((d.mode & DDIR) && (m == DEXEC)) + return 0; + if(!ingroup(f.uid, Noworld)) + return 0; + } + return 1; +} + +tagname(t: int): string +{ + case t { + Tnone => return "Tnone"; + Tsuper => return "Tsuper"; + Tdir => return "Tdir"; + Tind1 => return "Tind1"; + Tind2 => return "Tind2"; + Tfile => return "Tfile"; + Tfree => return "Tfree"; + Tbuck => return "Tbuck"; + Tvirgo => return "Tvirgo"; + Tcache => return "Tcache"; + * => return sys->sprint("%d", t); + } +} + +Iobuf.checktag(p: self ref Iobuf, tag: int, qpath: int): int +{ + t := Tag.unpack(p.iobuf[BUFSIZE:]); + if(t.tag != tag){ + if(1) + eprint(sys->sprint(" tag = %s; expected %s; addr = %ud\n", + tagname(t.tag), tagname(tag), p.addr)); + return 2; + } + if(qpath != QPNONE){ + qpath &= ~QPDIR; + if(qpath != t.path){ + if(qpath == (t.path&~QPDIR)) # old bug + return 0; + if(1) + eprint(sys->sprint(" tag/path = %ux; expected %s/%ux\n", + t.path, tagname(tag), qpath)); + return 1; + } + } + return 0; +} + +Iobuf.settag(p: self ref Iobuf, tag: int, qpath: int) +{ + Tag(tag, qpath).pack(p.iobuf[BUFSIZE:]); + p.flags |= Bmod; +} + +badmagic := 0; +wmagic := "kfs wren device\n"; + +wrenream(dev: ref Device) +{ + if(RBUFSIZE % 512) + panic(sys->sprint("kfs: bad buffersize(%d): restart a multiple of 512", RBUFSIZE)); + if(RBUFSIZE > MAXBUFSIZE) + panic(sys->sprint("kfs: bad buffersize(%d): must be at most %d", RBUFSIZE, MAXBUFSIZE)); + sys->print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE); + buf := array[RBUFSIZE] of {* => byte 0}; + buf[256:] = sys->aprint("%s%d\n", wmagic, RBUFSIZE); + if(sys->seek(dev.fd, big 0, 0) < big 0 || sys->write(dev.fd, buf, len buf) != len buf) + panic("can't ream disk"); +} + +wreninit(dev: ref Device): int +{ + (ok, nil) := sys->fstat(dev.fd); + if(ok < 0) + return 0; + buf := array[MAXBUFSIZE] of byte; + sys->seek(dev.fd, big 0, 0); + n := sys->read(dev.fd, buf, len buf); + if(n < len buf) + return 0; + badmagic = 0; + RBUFSIZE = 1024; + if(string buf[256:256+len wmagic] != wmagic){ + badmagic = 1; + return 0; + } + RBUFSIZE = int string buf[256+len wmagic:256+len wmagic+12]; + if(RBUFSIZE % 512) + error("bad block size"); + return 1; +} + +wrenread(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pread(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrenwrite(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pwrite(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrentag(buf: array of byte, tag: int, qpath: int): int +{ + t := Tag.unpack(buf[BUFSIZE:]); + return t.tag != tag || (qpath&~QPDIR) != t.path; +} + +wrencheck(fd: ref Sys->FD): int +{ + if(badmagic) + return 1; + buf := array[RBUFSIZE] of byte; + if(wrenread(fd, SUPERADDR, buf) || wrentag(buf, Tsuper, QPSUPER) || + wrenread(fd, ROOTADDR, buf) || wrentag(buf, Tdir, QPROOT)) + return 1; + d0 := Dentry.unpack(buf); + if(d0.mode & DALLOC) + return 0; + return 1; +} + +wrensize(dev: ref Device): int +{ + (ok, d) := sys->fstat(dev.fd); + if(ok < 0) + return -1; + return int (d.length / big RBUFSIZE); +} + +checkname9p2(s: string): int +{ + for(i := 0; i < len s; i++) + if(s[i] <= 8r40) + return 0; + return styx->utflen(s); +} + +isro(d: ref Device): int +{ + return d == nil || d.ronly; +} + +tlocks: list of ref Tlock; + +tlocked(f: ref File, d: ref Dentry): ref Tlock +{ + tim := now(); + path := int d.qid.path; + t1: ref Tlock; + for(l := tlocks; l != nil; l = tl l){ + t := hd l; + if(t.qpath == path && t.time >= tim && t.dev == f.fs) + return nil; # it's locked + if(t.file == nil || t1 == nil && t.time < tim) + t1 = t; + } + t := t1; + if(t == nil) + t = ref Tlock; + t.dev = f.fs; + t.qpath = path; + t.time = tim + TLOCK; + tlocks = t :: tlocks; + return t; +} + +mkqid(path: int, vers: int, mode: int): Qid +{ + qid: Qid; + + qid.path = big (path & ~QPDIR); + qid.vers = vers; + qid.qtype = 0; + if(mode & DDIR) + qid.qtype |= QTDIR; + if(mode & DAPND) + qid.qtype |= QTAPPEND; + if(mode & DLOCK) + qid.qtype |= QTEXCL; + return qid; +} + +dir9p2(d: ref Dentry): Sys->Dir +{ + dir: Sys->Dir; + + dir.name = d.name; + dir.uid = uidtostr(d.uid); + dir.gid = uidtostr(d.gid); + dir.muid = uidtostr(d.muid); + dir.qid = d.qid; + dir.mode = d.mode & 8r777; + if(d.mode & DDIR) + dir.mode |= DMDIR; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + dir.atime = d.atime; + dir.mtime = d.mtime; + dir.length = big d.size; + dir.dtype = 0; + dir.dev = 0; + return dir; +} + +rootream(dev: ref Device, addr: int) +{ + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tdir, QPROOT); + d := Dentry.get(p, 0); + d.name = "/"; + d.uid = -1; + d.gid = -1; + d.mode = DALLOC | DDIR | + ((DREAD|DWRITE|DEXEC) << 6) | + ((DREAD|DWRITE|DEXEC) << 3) | + ((DREAD|DWRITE|DEXEC) << 0); + d.qid.path = big QPROOT; + d.qid.vers = 0; + d.qid.qtype = QTDIR; + d.atime = now(); + d.mtime = d.atime; + d.change(~0); + d.access(FREAD|FWRITE, -1); + d.update(); + p.put(); +} + +superream(dev: ref Device, addr: int) +{ + fsize := wrensize(dev); + if(fsize <= 0) + panic("file system device size"); + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tsuper, QPSUPER); + sb := ref Superb; + sb.iob = p; + sb.fstart = 1; + sb.fsize = fsize; + sb.qidgen = 10; + sb.tfree = 0; + sb.fsok = 0; + sb.fbuf = p.iobuf[Super1size:]; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(i := fsize-1; i>=addr+2; i--) + addfree(dev, i, sb); + sb.put(); +} + +eprint(s: string) +{ + sys->print("kfs: %s\n", s); +} + +# +# /adm/users +# +# uid:user:leader:members[,...] + +User: adt { + uid: int; + name: string; + leader: int; + mem: list of int; +}; + +users: list of ref User; + +admusers := array[] of { + (-1, "adm", "adm"), + (None, "none", "adm"), + (Noworld, "noworld", nil), + (10000, "sys", nil), + (10001, "upas", "upas"), + (10002, "bootes", "bootes"), + (10006, "inferno", nil), +}; + +userinit() +{ + if(!cmd_users() && users == nil){ + cprint("initializing minimal user table"); + defaultusers(); + } + writegroup = strtouid("write"); +} + +cmd_users(): int +{ + if(kopen(FID1, FID2, array[] of {"adm", "users"}, OREAD) != nil) + return 0; + buf: array of byte; + for(off := 0;;){ + (a, e) := kread(FID2, off, Styx->MAXFDATA); + if(e != nil){ + cprint("/adm/users read error: "+e); + return 0; + } + if(len a == 0) + break; + off += len a; + if(buf != nil){ + c := array[len buf + len a] of byte; + if(buf != nil) + c[0:] = buf; + c[len buf:] = a; + buf = c; + }else + buf = a; + } + kclose(FID2); + + # (uid:name:lead:mem,...\n)+ + (nl, lines) := sys->tokenize(string buf, "\n"); + if(nl == 0){ + cprint("empty /adm/users"); + return 0; + } + oldusers := users; + users = nil; + + # first pass: enter id:name + for(l := lines; l != nil; l = tl l){ + uid, name, r: string; + s := hd l; + if(s == "" || s[0] == '#') + continue; + (uid, r) = field(s, ':'); + (name, r) = field(r, ':'); + if(uid == nil || name == nil || string int uid != uid){ + cprint("invalid /adm/users line: "+hd l); + users = oldusers; + return 0; + } + adduser(int uid, name, nil, nil); + } + + # second pass: groups and leaders + for(l = lines; l != nil; l = tl l){ + s := hd l; + if(s == "" || s[0] == '#') + continue; + name, lead, mem, r: string; + (nil, r) = field(s, ':'); # skip id + (name, r) = field(r, ':'); + (lead, mem) = field(r, ':'); + (nil, mems) := sys->tokenize(mem, ",\n"); + if(name == nil || lead == nil && mems == nil) + continue; + u := finduname(name); + if(lead != nil){ + lu := strtouid(lead); + if(lu != None) + u.leader = lu; + else if(lead != nil) + u.leader = u.uid; # mimic kfs not fs + } + mids: list of int = nil; + for(; mems != nil; mems = tl mems){ + lu := strtouid(hd mems); + if(lu != None) + mids = lu :: mids; + } + u.mem = mids; + } + + if(debug) + for(x := users; x != nil; x = tl x){ + u := hd x; + sys->print("%d : %q : %d :", u.uid, u.name, u.leader); + for(y := u.mem; y != nil; y = tl y) + sys->print(" %d", hd y); + sys->print("\n"); + } + return 1; +} + +field(s: string, c: int): (string, string) +{ + for(i := 0; i < len s; i++) + if(s[i] == c) + return (s[0:i], s[i+1:]); + return (s, nil); +} + +defaultusers() +{ + for(i := 0; i < len admusers; i++){ + (id, name, leader) := admusers[i]; + adduser(id, name, leader, nil); + } +} + +finduname(s: string): ref User +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.name == s) + return u; + } + return nil; +} + +uidtostr(id: int): string +{ + if(id == None) + return "none"; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id) + return u.name; + } + return sys->sprint("#%d", id); +} + +leadgroup(ui: int, gi: int): int +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gi){ + if(u.leader == ui) + return 1; + if(u.leader == 0) + return ingroup(ui, gi); + return 0; + } + } + return 0; +} + +strtouid(s: string): int +{ + if(s == "none") + return None; + u := finduname(s); + if(u != nil) + return u.uid; + return 0; +} + +ingroup(uid: int, gid: int): int +{ + if(uid == gid) + return 1; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gid){ + for(m := u.mem; m != nil; m = tl m) + if(hd m == uid) + return 1; + return 0; + } + } + return 0; +} + +baduname(s: string): int +{ + n := checkname9p2(s); + if(n == 0 || n+1 > NAMELEN || s == "." || s == ".."){ + sys->print("kfs: illegal user name %q\n", s); + return 1; + } + return 0; +} + +adduser(id: int, name: string, leader: string, mem: list of string) +{ + if(baduname(name)) + return; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id){ + sys->print("kfs: duplicate user ID %d (name %q)\n", id, u.name); + return; + }else if(u.name == name){ + sys->print("kfs: duplicate user name %q (id %d)\n", name, u.uid); + return; + } + } + if(name == leader) + lid := id; + else if(leader == nil) + lid = 0; + else if(!baduname(leader)) + lid = strtouid(leader); + else + return; + memid: list of int; + for(; mem != nil; mem = tl mem){ + if(baduname(hd mem)) + return; + x := strtouid(hd mem); + if(x != 0) + memid = x :: memid; + } + u := ref User(id, name, lid, memid); + users = u :: users; +} + +Lock.new(): ref Lock +{ + return ref Lock(chan[1] of int); +} + +Lock.lock(l: self ref Lock) +{ + l.c <-= 1; +} + +Lock.canlock(l: self ref Lock): int +{ + alt{ + l.c <-= 1 => + return 1; + * => + return 0; + } +} + +Lock.unlock(l: self ref Lock) +{ + <-l.c; +} + +# +# kfs check, could be a separate module if that seemed important +# + +MAXDEPTH: con 100; +MAXNAME: con 4000; + +Map: adt { + lo, hi: int; + bits: array of byte; + nbad: int; + ndup: int; + nmark: int; + + new: fn(lo, hi: int): ref Map; + isset: fn(b: self ref Map, a: int): int; + mark: fn(b: self ref Map, a: int): string; +}; + +Check: adt { + dev: ref Device; + + amap: ref Map; + qmap: ref Map; + + name: string; + nfiles: int; + maxq: int; + + mod: int; + flags: int; + oldblock: int; + + depth: int; + maxdepth: int; + + check: fn(c: self ref Check); + touch: fn(c: self ref Check, a: int): int; + checkdir: fn(c: self ref Check, a: int, qpath: int): int; + checkindir: fn(c: self ref Check, a: int, d: ref Dentry, qpath: int): int; + maked: fn(c: self ref Check, a: int, s: int, qpath: int): ref Dentry; + modd: fn(c: self ref Check, a: int, s: int, d: ref Dentry); + fsck: fn(c: self ref Check, d: ref Dentry): int; + xread: fn(c: self ref Check, a: int, qpath: int); + xtag: fn(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf; + ckfreelist: fn(c: self ref Check, sb: ref Superb); + mkfreelist: fn(c: self ref Check, sb: ref Superb); + amark: fn(c: self ref Check, a: int): int; + fmark: fn(c: self ref Check, a: int): int; + missing: fn(c: self ref Check, sb: ref Superb); + qmark: fn(c: self ref Check, q: int); +}; + +check(dev: ref Device, flag: int) +{ + #mainlock.wlock(); + #mainlock.wunlock(); + c := ref Check; + c.dev = dev; + c.nfiles = 0; + c.maxq = 0; + c.mod = 0; + c.flags = flag; + c.oldblock = 0; + c.depth = 0; + c.maxdepth = 0; + c.check(); +} + +checkflags(s: string): int +{ + f := 0; + for(i := 0; i < len s; i++) + case s[i] { + 'r' => f |= Crdall; + 't' => f |= Ctag; + 'P' => f |= Cpfile; + 'p' => f |= Cpdir; + 'f' => f |= Cfree; + 'c' => f |= Cream; + 'd' => f |= Cbad; + 'w' => f |= Ctouch; + 'q' => f |= Cquiet; + 'v' => ; # old verbose flag; ignored + * => return -1; + } + return f; +} + +Check.check(c: self ref Check) +{ + sbaddr := SUPERADDR; + p := c.xtag(sbaddr, Tsuper, QPSUPER); + if(p == nil){ + cprint(sys->sprint("bad superblock")); + return; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + + fstart := sb.fstart; + if(fstart != 1){ + cprint(sys->sprint("invalid superblock")); + return; + } + fsize := sb.fsize; + if(fsize < fstart || fsize > wrensize(c.dev)){ + cprint(sys->sprint("invalid size in superblock")); + return; + } + c.amap = Map.new(fstart, fsize); + + nqid := sb.qidgen+100; # not as much of a botch + if(nqid > 1024*1024*8) + nqid = 1024*1024*8; + if(nqid < 64*1024) + nqid = 64*1024; + c.qmap = Map.new(0, nqid); + + c.mod = 0; + c.depth = 0; + c.maxdepth = 0; + + if(c.amark(sbaddr)) + {} + + if(!(c.flags & Cquiet)) + cprint(sys->sprint("checking file system: %s", "main")); + c.nfiles = 0; + c.maxq = 0; + + d := c.maked(ROOTADDR, 0, QPROOT); + if(d != nil){ + if(c.amark(ROOTADDR)) + {} + if(c.fsck(d)) + c.modd(ROOTADDR, 0, d); + if(--c.depth != 0) + cprint("depth not zero on return"); + } + if(sb.qidgen < c.maxq) + cprint(sys->sprint("qid generator low path=%d maxq=%d", sb.qidgen, c.maxq)); + + nqbad := c.qmap.nbad + c.qmap.ndup; + c.qmap = nil; # could use to implement resequence + + ndup := c.amap.ndup; + nused := c.amap.nmark; + + c.amap.ndup = c.amap.nmark = 0; # reset for free list counts + if(c.flags & Cfree){ + c.name = "free list"; + c.mkfreelist(sb); + sb.qidgen = c.maxq; + p.settag(Tsuper, QPNONE); + }else + c.ckfreelist(sb); + + nbad := c.amap.nbad; + nfdup := c.amap.ndup; + nfree := c.amap.nmark; + # leave amap for missing, below + + if(c.mod){ + cprint("file system was modified"); + p.settag(Tsuper, QPNONE); + } + + if(!(c.flags & Cquiet)){ + cprint(sys->sprint("%8d files", c.nfiles)); + cprint(sys->sprint("%8d blocks in the file system", fsize-fstart)); + cprint(sys->sprint("%8d used blocks", nused)); + cprint(sys->sprint("%8d free blocks", sb.tfree)); + } + if(!(c.flags & Cfree)){ + if(nfree != sb.tfree) + cprint(sys->sprint("%8d free blocks found", nfree)); + if(nfdup) + cprint(sys->sprint("%8d blocks duplicated in the free list", nfdup)); + if(fsize-fstart-nused-nfree) + cprint(sys->sprint("%8d missing blocks", fsize-fstart-nused-nfree)); + } + if(ndup) + cprint(sys->sprint("%8d address duplications", ndup)); + if(nbad) + cprint(sys->sprint("%8d bad block addresses", nbad)); + if(nqbad) + cprint(sys->sprint("%8d bad qids", nqbad)); + if(!(c.flags & Cquiet)) + cprint(sys->sprint("%8d maximum qid path", c.maxq)); + c.missing(sb); + + sb.put(); +} + +Check.touch(c: self ref Check, a: int): int +{ + if((c.flags&Ctouch) && a){ + p := Iobuf.get(c.dev, a, Bread|Bmod); + if(p != nil) + p.put(); + return 1; + } + return 0; +} + +Check.checkdir(c: self ref Check, a: int, qpath: int): int +{ + ns := len c.name; + dmod := c.touch(a); + for(i:=0; i<DIRPERBUF; i++){ + nd := c.maked(a, i, qpath); + if(nd == nil) + break; + if(c.fsck(nd)){ + c.modd(a, i, nd); + dmod++; + } + c.depth--; + c.name = c.name[0:ns]; + } + c.name = c.name[0:ns]; + return dmod; +} + +Check.checkindir(c: self ref Check, a: int, d: ref Dentry, qpath: int): int +{ + dmod := c.touch(a); + p := c.xtag(a, Tind1, qpath); + if(p == nil) + return dmod; + for(i:=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + p.put(); + return dmod; +} + +Check.fsck(c: self ref Check, d: ref Dentry): int +{ + p: ref Iobuf; + i: int; + a, qpath: int; + + if(++c.depth >= c.maxdepth){ + c.maxdepth = c.depth; + if(c.maxdepth >= MAXDEPTH){ + cprint(sys->sprint("max depth exceeded: %s", c.name)); + return 0; + } + } + dmod := 0; + if(!(d.mode & DALLOC)) + return 0; + c.nfiles++; + + ns := len c.name; + i = styx->utflen(d.name); + if(i >= NAMELEN){ + d.name[NAMELEN-1] = 0; # TO DO: not quite right + cprint(sys->sprint("%q.name (%q) not terminated", c.name, d.name)); + return 0; + } + ns += i; + if(ns >= MAXNAME){ + cprint(sys->sprint("%q.name (%q) name too large", c.name, d.name)); + return 0; + } + c.name += d.name; + + if(d.mode & DDIR){ + if(ns > 1) + c.name += "/"; + if(c.flags & Cpdir) + cprint(sys->sprint("%s", c.name)); + } else if(c.flags & Cpfile) + cprint(sys->sprint("%s", c.name)); + + qpath = int d.qid.path & ~QPDIR; + c.qmark(qpath); + if(qpath > c.maxq) + c.maxq = qpath; + for(i=0; i<NDBLOCK; i++){ + a = get4(d.buf, Odblock+i*4); + if(a == 0) + continue; + if(c.amark(a)){ + put4(d.buf, Odblock+i*4, 0); + dmod++; + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + a = get4(d.buf, Oiblock); + if(a){ + if(c.amark(a)){ + put4(d.buf, Oiblock, 0); + dmod++; + } + else + dmod += c.checkindir(a, d, qpath); + } + + a = get4(d.buf, Odiblock); + if(a && c.amark(a)){ + put4(d.buf, Odiblock, 0); + return dmod + 1; + } + dmod += c.touch(a); + p = c.xtag(a, Tind2, qpath); + if(p != nil){ + for(i=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + dmod += c.checkindir(a, d, qpath); + } + p.put(); + } + return dmod; +} + +Check.ckfreelist(c: self ref Check, sb: ref Superb) +{ + c.name = "free list"; + cprint(sys->sprint("check %s", c.name)); + fb := sb.fbuf; + a := SUPERADDR; + p: ref Iobuf; + lo := 0; + hi := 0; + for(;;){ + n := get4(fb, 0); # nfree + if(n < 0 || n > FEPERBUF){ + cprint(sys->sprint("check: nfree bad %d", a)); + break; + } + for(i:=1; i<n; i++){ + a = get4(fb, 4+i*4); # free[i] + if(a && !c.fmark(a)){ + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + } + } + a = get4(fb, 4); # free[0] + if(a == 0) + break; + if(c.fmark(a)) + break; + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + if(p != nil) + p.put(); + p = c.xtag(a, Tfree, QPNONE); + if(p == nil) + break; + fb = p.iobuf; + } + if(p != nil) + p.put(); + cprint(sys->sprint("lo = %d; hi = %d", lo, hi)); +} + +# +# make freelist from scratch +# +Check.mkfreelist(c: self ref Check, sb: ref Superb) +{ + sb.fbuf[0:] = emptyblock[0:(FEPERBUF+1)*4]; + sb.tfree = 0; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(a:=sb.fsize-sb.fstart-1; a >= 0; a--){ + i := a>>3; + if(i < 0 || i >= len c.amap.bits) + continue; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) != byte 0) + continue; + addfree(c.dev, sb.fstart+a, sb); + c.amap.bits[i] |= b; + } + sb.iob.flags |= Bmod; +} + +# +# makes a copy of a Dentry's representation on disc so that +# the rest of the much larger iobuf can be freed. +# +Check.maked(c: self ref Check, a: int, s: int, qpath: int): ref Dentry +{ + p := c.xtag(a, Tdir, qpath); + if(p == nil) + return nil; + d := Dentry.get(p, s); + if(d == nil) + return nil; + copy := array[len d.buf] of byte; + copy[0:] = d.buf; + d.put(); + d.buf = copy; + return d; +} + +Check.modd(c: self ref Check, a: int, s: int, d1: ref Dentry) +{ + if(!(c.flags & Cbad)) + return; + p := Iobuf.get(c.dev, a, Bread); + d := Dentry.get(p, s); + if(d == nil){ + if(p != nil) + p.put(); + return; + } + d.buf[0:] = d1.buf; + p.flags |= Bmod; + p.put(); +} + +Check.xread(c: self ref Check, a: int, qpath: int) +{ + p := c.xtag(a, Tfile, qpath); + if(p != nil) + p.put(); +} + +Check.xtag(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf +{ + if(a == 0) + return nil; + p := Iobuf.get(c.dev, a, Bread); + if(p == nil){ + cprint(sys->sprint("check: \"%s\": xtag: p null", c.name)); + if(c.flags & (Cream|Ctag)){ + p = Iobuf.get(c.dev, a, Bmod); + if(p != nil){ + p.iobuf[0:] = emptyblock; + p.settag(tag, qpath); + c.mod++; + return p; + } + } + return nil; + } + if(p.checktag(tag, qpath)){ + cprint(sys->sprint("check: \"%s\": xtag: checktag", c.name)); + if(c.flags & Cream) + p.iobuf[0:] = emptyblock; + if(c.flags & (Cream|Ctag)){ + p.settag(tag, qpath); + c.mod++; + } + return p; + } + return p; +} + +Check.amark(c: self ref Check, a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return e != "dup"; # don't clear dup blocks because rm might repair + } + return 0; +} + +Check.fmark(c: self ref Check,a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return 1; + } + return 0; +} + +Check.missing(c: self ref Check, sb: ref Superb) +{ + n := 0; + for(a:=sb.fsize-sb.fstart-1; a>=0; a--){ + i := a>>3; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) == byte 0){ + cprint(sys->sprint("missing: %d", sb.fstart+a)); + n++; + } + if(n > 10){ + cprint(sys->sprint(" ...")); + break; + } + } +} + +Check.qmark(c: self ref Check, qpath: int) +{ + e := c.qmap.mark(qpath); + if(e != nil){ + if(c.qmap.nbad+c.qmap.ndup < 20) + cprint(sys->sprint("check: \"%s\": qid %s 0x%ux", c.name, e, qpath)); + } +} + +Map.new(lo, hi: int): ref Map +{ + m := ref Map; + n := (hi-lo+7)>>3; + m.bits = array[n] of {* => byte 0}; + m.lo = lo; + m.hi = hi; + m.nbad = 0; + m.ndup = 0; + m.nmark = 0; + return m; +} + +Map.isset(m: self ref Map, i: int): int +{ + if(i < m.lo || i >= m.hi) + return -1; # hard to say + i -= m.lo; + return (m.bits[i>>3] & byte (1<<(i&7))) != byte 0; +} + +Map.mark(m: self ref Map, i: int): string +{ + if(i < m.lo || i >= m.hi){ + m.nbad++; + return "out of range"; + } + i -= m.lo; + b := byte (1 << (i&7)); + i >>= 3; + if((m.bits[i] & b) != byte 0){ + m.ndup++; + return "dup"; + } + m.bits[i] |= b; + m.nmark++; + return nil; +} + +cprint(s: string) +{ + if(consoleout != nil) + consoleout <-= s+"\n"; + else + eprint(s); +} |
