implement Ofstree; include "sys.m"; sys: Sys; fprint, OREAD, open, pwrite, pread, remove, sprint, pctl, millisec, DMDIR, write, nulldir, tokenize, fildes, QTDIR, FD, read, create, OWRITE, ORDWR, Dir, Qid: import sys; include "names.m"; names: Names; cleanname: import names; include "string.m"; str: String; splitstrl: import str; include "styx.m"; styx: Styx; unpackdir: import styx; include "error.m"; err: Error; stderr, panic: import err; include "readdir.m"; readdir: Readdir; include "ofstree.m"; # Cachedir can be used to keep a map of names/serverqids # to store within the directory ("dir/.ofs") when using a disk cache. # this is untested; not yet used. # BUG: we create the files as they are seen by stop, but for # many of them, we don't know at that time if the files are # files or directories. So, this must be changed to # create the files really when we need them to keep data # on them. Directories as necessary to keep the files there. Cachefile: adt { name: string; qid: Qid; }; Cachedir: adt { fname: string; map: list of Cachefile; get: fn(path: string): ref Cachedir; find: fn(c: self ref Cachedir, name: string): ref Qid; add: fn(c: self ref Cachedir, name: string, q: Qid); del: fn(c: self ref Cachedir, name: string); put: fn(c: self ref Cachedir); }; Cachedir.find(c: self ref Cachedir, name: string): ref Qid { for (l := c.map; l != nil; l = tl l) if ((hd l).name == name) return ref (hd l).qid; return nil; } Cachedir.add(c: self ref Cachedir, name: string, q: Qid) { nmap : list of Cachefile; found := 0; for (l := c.map; l != nil; l = tl l) if ((hd l).name == name){ found = 1; nmap = Cachefile(name, q) :: nmap; } else nmap = hd l :: nmap; if (!found) c.map = Cachefile(name, q) :: c.map; } Cachedir.del(c: self ref Cachedir, name: string) { nmap : list of Cachefile; for (l := c.map; l != nil; l = tl l) if ((hd l).name != name) nmap = hd l :: nmap; c.map = nmap; } Cachedir.get(fname: string): ref Cachedir { # BUG: should read only if qid changed fname += "/.ofs"; fd := open(fname, OREAD); if (fd == nil) return ref Cachedir(fname, nil); buf := array[16*1024] of byte; tot := 0; for(;;){ nr := read(fd, buf[tot:], len buf - tot); if (nr < 0) return ref Cachedir(fname, nil); if (nr == 0){ buf = buf[0:tot]; break; } if (tot > 64 * 1024 * 1024){ fprint(stderr, "ofs: cachedir: file too large. fix me.\n"); return nil; } if (tot == len buf){ nbuf := array[2 * len buf] of byte; nbuf[0:] = buf; buf = nbuf; } } text := string buf; buf = nil; (n, items) := tokenize(text, " \n"); nents := n/4; map : list of Cachefile; for (i := 0; i < nents; i++){ name := hd items; items = tl items; path := big hd items; items = tl items; vers := int hd items; items = tl items; qt := int hd items; items = tl items; map = Cachefile(name, Qid(path, vers, qt)) :: map; } return ref Cachedir(fname, map); } Cachedir.put(c: self ref Cachedir) { fd := create(c.fname, OWRITE, 8r664); if (fd == nil) return; for (l := c.map; l != nil; l = tl l){ e := hd l; fprint(fd, "%s %bd %d %d\n", e.name, e.qid.path, e.qid.vers, e.qid.qtype); } } ncachedfiles := 0; tab: array of ref Cfile; fsdir: string; init(msys: Sys, mstr: String, mstyx: Styx, merr: Error, n: Names, dir: string): string { sys = msys; str = mstr; styx = mstyx; err = merr; names = n; readdir = load Readdir Readdir->PATH; if (readdir == nil) fsdir = nil; tab = array[211] of ref Cfile; # use a prime number if (dir != nil) fsdir = names->cleanname(dir); return nil; } hashfn(q: big, n: int): int { h := int (q % big n); if (h < 0) h += n; return h; } Cfile.create(parent: ref Cfile, d: ref Sys->Dir): ref Cfile { if (Cfile.find(d.qid.path) != nil){ fprint(stderr, "fscreate: qid already exists: %bx\n", d.qid.path); return nil; } fh: ref Cfile; if (parent == nil) fh = ref Cfile(~0, ~0, nil, d, nil, 0, 0, 0, 0, nil, d.qid.path, Qid(big 0, 0, d.qid.qtype), 0, nil, nil, nil); else { if (parent.walk(d.name) != nil) return nil; fh = ref Cfile(~0, ~0, nil, d, nil, 0, 0, 0, 0, nil, parent.d.qid.path, Qid(big 0, 0, d.qid.qtype), 0, nil, nil, nil); fh.sibling = parent.child; parent.child = fh; } slot := hashfn(d.qid.path, len tab); fh.hash = tab[slot]; tab[slot] = fh; ncachedfiles++; if (fsdir != nil && parent != nil){ path := names->cleanname(fsdir + "/" + fh.getpath() ); mode := 8r664; if (d.mode&DMDIR) mode = DMDIR|8r775; fd := sys->open(path, OREAD); if (fd == nil) fd = sys->create(path, OREAD, mode); if (debug) fprint(stderr, "cache: create %s\n", path); if (fd == nil) fprint(stderr, "cache: create %s: %r\n", path); } return fh; } Cfile.find(q: big): ref Cfile { for (fh := tab[hashfn(q, len tab)]; fh != nil; fh = fh.hash) if (fh.d.qid.path == q) return fh; return nil; } Cfile.updatedirdata(f: self ref Cfile, data : array of byte) { sons, l : list of ref Dir; gonesons : list of big; tot := n:= 0; d : ref Dir; xd : Dir; fh: ref Cfile; if (len data == 0) return; f.dirreaded = 1; sons = nil; gonesons = nil; l = nil; d = nil; fh = nil; # 1. unpack. do { (n, xd) = unpackdir(data[tot:]); tot += n; if (n >= 0) sons = ref xd :: sons; } while (n > 0 && tot < len data); # 2. update changed ones; record gone ones. gonesons = nil; for(fh = f.child; fh != nil; fh = fh.sibling){ # search by name; qids might be faked for (l = sons; l != nil && (hd l).name != fh.d.name ; l = tl l) ; if (l != nil){ q := fh.d.qid.path; # we invent our own qids fh.d = ref *(hd l); fh.serverqid = fh.d.qid; fh.d.qid.path = q; } else # must keep files just created (they may be not yet reported by the server) if (!fh.created) gonesons = fh.d.qid.path :: gonesons; } # 3. remove gone ones while(gonesons != nil){ if (debug) fprint(stderr, "cache: readdir: invalidate %bx \n", hd gonesons); if (fsdir != nil){ sf := Cfile.find(hd gonesons); path := names->cleanname(fsdir + "/" + sf.getpath()); if (path != fsdir) removefsdir(fsdir + "/" + sf.getpath()); } removeqid(hd gonesons); gonesons = tl gonesons; } # 4. add new ones for(l = sons; l != nil; l = tl l){ d = hd l; sq := d.qid.path; for (fh = f.child; fh != nil && fh.serverqid.path != sq; fh = fh.sibling) ; if (fh == nil){ d.qid.path = ++qseq; fh = Cfile.create(f, d); if (fh == nil){ # this happens for files bound twice or more in the same dir # the file is already cached, ignore. --qseq; } else { fh.serverqid = Qid(sq, d.qid.vers, d.qid.qtype); if (debug) fprint(stderr, "cache: new: %s\n", fh.text()); } } } } Cfile.getpath(fh: self ref Cfile): string { if (fh == nil) panic("fsgetpath: nil fh"); if (fh.d == nil) panic("fsgetpath: nil d"); s : string; if (fh.parentqid == fh.d.qid.path) return "/"; for(;;) { if (fh.d == nil) panic("fsgetpath:null dir"); if (s == nil){ if (fh.oldname != nil) s = fh.oldname; else s = fh.d.name; } else if (fh.parentqid == fh.d.qid.path) return "/" + s; else { if (fh.oldname != nil) s = fh.oldname + "/" + s; else s = fh.d.name + "/" + s; } fh = Cfile.find(fh.parentqid); if (fh == nil) panic("fsgetpath:parent not in table"); } return nil; } Cfile.walk(fh: self ref Cfile, name: string): ref Cfile { if (name == "..") return Cfile.find(fh.parentqid); for (fh = fh.child; fh != nil; fh = fh.sibling) if (fh.d.name == name) return fh; return nil; } qseq := big 16r6b60000000000000; Cfile.walkorcreate(fh: self ref Cfile, name: string, d: ref Dir): (ref Cfile, int) { newf := 0; f := fh.walk(name); if (f == nil){ newf = 1; if (d == nil){ d = ref *fh.d; d.qid.path = ++qseq; d.qid.qtype = QTDIR; d.qid.vers = 0; d.name = name; } else { d.qid.path = ++qseq; # we use our own qids. d.name = name; # even when d.qid is ok. if (d.mode&DMDIR) d.qid.qtype |= QTDIR; } f = Cfile.create(fh, d); if (f == nil) panic(sprint("walkorcreate: create: %s at %s q %bx\n", name, fh.d.name, d.qid.path)); else if (debug) fprint(stderr, "cache: added: %s\n", f.text()); } if (f == nil) panic(sprint("walkorcreate: nil f for name %s", name)); return (f, newf); } Cfile.children(f: self ref Cfile, cnt, off: int) : list of Sys->Dir { fh := f.child; while(off > 0 && fh != nil){ off--; fh = fh.sibling; } l : list of Sys->Dir; l = nil; while(cnt > 0 && fh != nil){ cnt--; l = *fh.d :: l; fh = fh.sibling; } return l; } removeqid(q: big): string { prev: ref Cfile; # remove from hash table slot := hashfn(q, len tab); for (fh := tab[slot]; fh != nil; fh = fh.hash) { if (fh.d.qid.path == q) break; prev = fh; } if (fh == nil) return "file not found"; if (prev == nil) tab[slot] = fh.hash; else prev.hash = fh.hash; fh.hash = nil; # remove from parent's children parent := Cfile.find(fh.parentqid); if (parent != nil) { prev = nil; for (sfh := parent.child; sfh != nil; sfh = sfh.sibling) { if (sfh == fh) break; prev = sfh; } if (sfh == nil) panic("child not found in parent"); if (prev == nil) parent.child = fh.sibling; else prev.sibling = fh.sibling; } fh.sibling = nil; # now remove any descendents sibling: ref Cfile; for (sfh := fh.child; sfh != nil; sfh = sibling) { sibling = sfh.sibling; sfh.parentqid = sfh.d.qid.path; # make sure it doesn't disrupt things. removeqid(sfh.d.qid.path); } ncachedfiles--; return nil; } removefsdir(path: string) { if (debug) fprint(stderr, "cache: remove %s\n", path); if (path[0:4] != "/tmp" && path[0:6] != "/cache") return; # SAFETY FIRST (dirs, e) := readdir->init(path, Readdir->NONE); for (i := 0; i < e; i++) removefsdir(path + "/" + dirs[i].name); sys->remove(path); } Cfile.remove(f: self ref Cfile): string { if (fsdir != nil){ path := names->cleanname(fsdir + "/" + f.getpath()); if (path != fsdir) removefsdir(fsdir + "/" + f.getpath()); } return removeqid(f.d.qid.path); } applydir(d: ref Sys->Dir, onto: ref Sys->Dir): ref Sys->Dir { if (d.name != nil) onto.name = d.name; if (d.uid != nil) onto.uid = d.uid; if (d.gid != nil) onto.gid = d.gid; if (d.muid != nil) onto.muid = d.muid; if (d.qid.vers != ~0) onto.qid.vers = d.qid.vers; if (d.qid.qtype != ~0) onto.qid.qtype = d.qid.qtype; if (d.qid.vers != ~0) onto.qid.vers = d.qid.vers; if (d.mode != ~0) onto.mode = d.mode; if (d.atime != ~0) onto.atime = d.atime; if (d.mtime != ~0) onto.mtime = d.mtime; if (d.length != ~big 0) onto.length = d.length; if (d.dtype != ~0) onto.dtype = d.dtype; if (d.dev != ~0) onto.dev = d.dev; return onto; } Cfile.wstat(fh : self ref Cfile, d: ref Sys->Dir): string { q := fh.d.qid.path; # if renaming a file, check for duplicates if (d.name != nil && d.name != fh.d.name) { parent := Cfile.find(fh.parentqid); if (parent != nil && parent != fh && parent.walk(d.name) != nil) return "File already exists"; fh.oldname = fh.d.name; parent.time = 0; # invalidate fh.time = 0; # invalidate if (fsdir != nil && d.uid != nil){ nd := sys->nulldir; nd.name = d.name; sys->wstat(fsdir + "/" + fh.getpath(), nd); } } d = applydir(d, fh.d); if (fh.data != nil && d.length < big len fh.data) if ((d.qid.qtype&QTDIR) == 0) fh.data = fh.data[0:int d.length]; if (fsdir != nil && d.uid != nil){ # we update cache attributes only to truncate files that are # shorter in the fs. No other wstat is propatagated to the cache. # It caches just data. cfd := sys->nulldir; (e, xd) := sys->stat(fsdir + "/" + fh.getpath()); if (e >= 0 && xd.length > d.length){ cfd.length = d.length; cfd.mode = d.mode; if (cfd.mode & DMDIR) cfd.mode |= 8r775; else cfd.mode |= 8r660; if (debug) fprint(stderr, "cache: wstat %s mode %x\n", fsdir + "/" + fh.getpath(), cfd.mode); sys->wstat(fsdir + "/" + fh.getpath(), cfd); } } fh.d = d; fh.d.qid.path = q; # ensure the qid can't be changed return nil; } Cfile.dump(f: self ref Cfile, t: int, pref: string) { tabs := "\t\t\t\t\t\t\t\t\t\t"; s := ""; if (pref != nil){ s = pref + sprint("(%d files)\n", ncachedfiles); pref = nil; } ts := tabs[0:t]; s += ts + f.text(); a:= array of byte s; write(stderr, a, len a); for (fh := f.child; fh != nil; fh = fh.sibling) fh.dump(t+1, nil); } Cfile.pwrite(fh: self ref Cfile, data: array of byte, off: big): int { if (fsdir != nil){ path := fsdir + "/" + fh.getpath(); if (fh.fsfd == nil){ if (debug) fprint(stderr, "cache: open %s\n", path); fh.fsfd = open(path, ORDWR); if (fh.fsfd == nil){ # perhaps we created a dir. # replace it with a file, now that we know. remove(path); fh.fsfd = create(path, ORDWR, 8664); } } if (fh.fsfd != nil) return pwrite(fh.fsfd, data, len data, off); else fprint(stderr, "cache: %s: %r\n", path); } return -1; } Cfile.pread(fh : self ref Cfile, cnt: int, off: big): array of byte { if (fsdir == nil) return nil; if (fh.fsfd == nil){ path := fsdir + "/" + fh.getpath(); fh.fsfd = open(path, ORDWR); if (debug) fprint(stderr, "cache: open %s\n", path); } if (fh.fsfd == nil) return nil; data := array[cnt] of byte; nr := pread(fh.fsfd, data, len data, off); if (nr <= 0) return nil; return data[0:nr]; } Cfile.text(fh: self ref Cfile): string { if (fh == nil) return "nil file"; return sprint(" \"%s\" %s\tc%d s%d sq=%s %d bytes\n", fh.getpath(), dir2text(fh.d), fh.created, fh.dirtyd, qid2text(fh.serverqid), len fh.data); } dir2text(d: ref Sys->Dir): string { return sys->sprint("[\"%s\" %s 8r%uo %bd]", d.uid, qid2text(d.qid), d.mode, d.length); } qid2text(q: Sys->Qid): string { return sys->sprint("%.2ubx:%.2ux:%.2ux", q.path, q.vers, q.qtype); }