ref: 60ecd07e6d3f5786c8723dc9172c35d580fdadc8
dir: /appl/cmd/disk/kfs.b/
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 && !readonly && !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(!readonly && 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; if(count > iounit) count = iounit; # 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); }