aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/fossil
diff options
context:
space:
mode:
authorDavid du Colombier <0intro@gmail.com>2013-09-23 23:00:39 +0200
committerDavid du Colombier <0intro@gmail.com>2013-09-23 23:00:39 +0200
commit6f4d00ee45693290fae042b27536b54f77b96acd (patch)
tree60ad31bf16ed2000661c02345dd2a63851588a5d /src/cmd/fossil
parentfea86f063930ea187f1c77e93207ac8d39125520 (diff)
downloadplan9port-6f4d00ee45693290fae042b27536b54f77b96acd.tar.gz
plan9port-6f4d00ee45693290fae042b27536b54f77b96acd.tar.bz2
plan9port-6f4d00ee45693290fae042b27536b54f77b96acd.zip
fossil: import from plan 9
R=rsc https://codereview.appspot.com/7988047
Diffstat (limited to 'src/cmd/fossil')
-rw-r--r--src/cmd/fossil/9.h258
-rw-r--r--src/cmd/fossil/9auth.c175
-rw-r--r--src/cmd/fossil/9dir.c132
-rw-r--r--src/cmd/fossil/9excl.c126
-rw-r--r--src/cmd/fossil/9fid.c304
-rw-r--r--src/cmd/fossil/9fsys.c1896
-rw-r--r--src/cmd/fossil/9lstn.c184
-rw-r--r--src/cmd/fossil/9p.c1181
-rw-r--r--src/cmd/fossil/9ping.c109
-rw-r--r--src/cmd/fossil/9proc.c825
-rw-r--r--src/cmd/fossil/9srv.c242
-rw-r--r--src/cmd/fossil/9user.c948
-rw-r--r--src/cmd/fossil/Ccli.c112
-rw-r--r--src/cmd/fossil/Ccmd.c459
-rw-r--r--src/cmd/fossil/Ccons.c398
-rw-r--r--src/cmd/fossil/Clog.c40
-rw-r--r--src/cmd/fossil/archive.c466
-rw-r--r--src/cmd/fossil/bwatch.c421
-rw-r--r--src/cmd/fossil/cache.c2114
-rw-r--r--src/cmd/fossil/check.c799
-rw-r--r--src/cmd/fossil/dat.h332
-rw-r--r--src/cmd/fossil/disk.c406
-rw-r--r--src/cmd/fossil/dump.c86
-rw-r--r--src/cmd/fossil/epoch.c51
-rw-r--r--src/cmd/fossil/error.c38
-rw-r--r--src/cmd/fossil/error.h33
-rw-r--r--src/cmd/fossil/file.c1860
-rw-r--r--src/cmd/fossil/flchk.c118
-rw-r--r--src/cmd/fossil/flfmt.c571
-rw-r--r--src/cmd/fossil/flfmt9660.c565
-rw-r--r--src/cmd/fossil/flfmt9660.h3
-rw-r--r--src/cmd/fossil/fns.h106
-rw-r--r--src/cmd/fossil/fossil.c143
-rw-r--r--src/cmd/fossil/fs.c1099
-rw-r--r--src/cmd/fossil/fs.h67
-rw-r--r--src/cmd/fossil/last.c40
-rw-r--r--src/cmd/fossil/mkfile136
-rw-r--r--src/cmd/fossil/nobwatch.c39
-rw-r--r--src/cmd/fossil/pack.c225
-rw-r--r--src/cmd/fossil/periodic.c84
-rw-r--r--src/cmd/fossil/source.c1068
-rw-r--r--src/cmd/fossil/srcload.c270
-rw-r--r--src/cmd/fossil/stdinc.h11
-rw-r--r--src/cmd/fossil/trunc.c19
-rw-r--r--src/cmd/fossil/vac.c746
-rw-r--r--src/cmd/fossil/vac.h107
-rw-r--r--src/cmd/fossil/view.c1127
-rw-r--r--src/cmd/fossil/walk.c65
48 files changed, 20604 insertions, 0 deletions
diff --git a/src/cmd/fossil/9.h b/src/cmd/fossil/9.h
new file mode 100644
index 00000000..b7bdec26
--- /dev/null
+++ b/src/cmd/fossil/9.h
@@ -0,0 +1,258 @@
+#include <auth.h>
+#include <fcall.h>
+
+enum {
+ NFidHash = 503,
+};
+
+typedef struct Con Con;
+typedef struct DirBuf DirBuf;
+typedef struct Excl Excl;
+typedef struct Fid Fid;
+typedef struct Fsys Fsys;
+typedef struct Msg Msg;
+
+#pragma incomplete DirBuf
+#pragma incomplete Excl
+#pragma incomplete Fsys
+
+struct Msg {
+ uchar* data;
+ u32int msize; /* actual size of data */
+ Fcall t;
+ Fcall r;
+ Con* con;
+
+ Msg* anext; /* allocation free list */
+
+ Msg* mnext; /* all active messsages on this Con */
+ Msg* mprev;
+
+ int state; /* */
+
+ Msg* flush; /* flushes waiting for this Msg */
+
+ Msg* rwnext; /* read/write queue */
+ int nowq; /* do not place on write queue */
+};
+
+enum {
+ MsgN = 0,
+ MsgR = 1,
+ Msg9 = 2,
+ MsgW = 3,
+ MsgF = 4,
+};
+
+enum {
+ ConNoneAllow = 1<<0,
+ ConNoAuthCheck = 1<<1,
+ ConNoPermCheck = 1<<2,
+ ConWstatAllow = 1<<3,
+ ConIPCheck = 1<<4,
+};
+struct Con {
+ char* name;
+ uchar* data; /* max, not negotiated */
+ int isconsole; /* immutable */
+ int flags; /* immutable */
+ char remote[128]; /* immutable */
+ VtLock* lock;
+ int state;
+ int fd;
+ Msg* version;
+ u32int msize; /* negotiated with Tversion */
+ VtRendez* rendez;
+
+ Con* anext; /* alloc */
+ Con* cnext; /* in use */
+ Con* cprev;
+
+ VtLock* alock;
+ int aok; /* authentication done */
+
+ VtLock* mlock;
+ Msg* mhead; /* all Msgs on this connection */
+ Msg* mtail;
+ VtRendez* mrendez;
+
+ VtLock* wlock;
+ Msg* whead; /* write queue */
+ Msg* wtail;
+ VtRendez* wrendez;
+
+ VtLock* fidlock; /* */
+ Fid* fidhash[NFidHash];
+ Fid* fhead;
+ Fid* ftail;
+ int nfid;
+};
+
+enum {
+ ConDead = 0,
+ ConNew = 1,
+ ConDown = 2,
+ ConInit = 3,
+ ConUp = 4,
+ ConMoribund = 5,
+};
+
+struct Fid {
+ VtLock* lock;
+ Con* con;
+ u32int fidno;
+ int ref; /* inc/dec under Con.fidlock */
+ int flags;
+
+ int open;
+ Fsys* fsys;
+ File* file;
+ Qid qid;
+ char* uid;
+ char* uname;
+ DirBuf* db;
+ Excl* excl;
+
+ VtLock* alock; /* Tauth/Tattach */
+ AuthRpc* rpc;
+ char* cuname;
+
+ Fid* sort; /* sorted by uname in cmdWho */
+ Fid* hash; /* lookup by fidno */
+ Fid* next; /* clunk session with Tversion */
+ Fid* prev;
+};
+
+enum { /* Fid.flags and fidGet(..., flags) */
+ FidFCreate = 0x01,
+ FidFWlock = 0x02,
+};
+
+enum { /* Fid.open */
+ FidOCreate = 0x01,
+ FidORead = 0x02,
+ FidOWrite = 0x04,
+ FidORclose = 0x08,
+};
+
+/*
+ * 9p.c
+ */
+extern int (*rFcall[Tmax])(Msg*);
+extern int validFileName(char*);
+
+/*
+ * 9auth.c
+ */
+extern int authCheck(Fcall*, Fid*, Fsys*);
+extern int authRead(Fid*, void*, int);
+extern int authWrite(Fid*, void*, int);
+
+/*
+ * 9dir.c
+ */
+extern void dirBufFree(DirBuf*);
+extern int dirDe2M(DirEntry*, uchar*, int);
+extern int dirRead(Fid*, uchar*, int, vlong);
+
+/*
+ * 9excl.c
+ */
+extern int exclAlloc(Fid*);
+extern void exclFree(Fid*);
+extern void exclInit(void);
+extern int exclUpdate(Fid*);
+
+/*
+ * 9fid.c
+ */
+extern void fidClunk(Fid*);
+extern void fidClunkAll(Con*);
+extern Fid* fidGet(Con*, u32int, int);
+extern void fidInit(void);
+extern void fidPut(Fid*);
+
+/*
+ * 9fsys.c
+ */
+extern void fsysFsRlock(Fsys*);
+extern void fsysFsRUnlock(Fsys*);
+extern Fs* fsysGetFs(Fsys*);
+extern Fsys* fsysGet(char*);
+extern char* fsysGetName(Fsys*);
+extern File* fsysGetRoot(Fsys*, char*);
+extern Fsys* fsysIncRef(Fsys*);
+extern int fsysInit(void);
+extern int fsysNoAuthCheck(Fsys*);
+extern int fsysNoPermCheck(Fsys*);
+extern void fsysPut(Fsys*);
+extern int fsysWstatAllow(Fsys*);
+
+/*
+ * 9lstn.c
+ */
+extern int lstnInit(void);
+
+/*
+ * 9proc.c
+ */
+extern Con* conAlloc(int, char*, int);
+extern void conInit(void);
+extern void msgFlush(Msg*);
+extern void msgInit(void);
+
+/*
+ * 9srv.c
+ */
+extern int srvInit(void);
+
+/*
+ * 9user.c
+ */
+extern int groupLeader(char*, char*);
+extern int groupMember(char*, char*);
+extern int groupWriteMember(char*);
+extern char* unameByUid(char*);
+extern char* uidByUname(char*);
+extern int usersInit(void);
+extern int usersFileRead(char*);
+extern int validUserName(char*);
+
+extern char* uidadm;
+extern char* unamenone;
+extern char* uidnoworld;
+
+/*
+ * Ccli.c
+ */
+extern int cliAddCmd(char*, int (*)(int, char*[]));
+extern int cliError(char*, ...);
+extern int cliInit(void);
+extern int cliExec(char*);
+#pragma varargck argpos cliError 1
+
+/*
+ * Ccmd.c
+ */
+extern int cmdInit(void);
+
+/*
+ * Ccons.c
+ */
+extern int consPrompt(char*);
+extern int consInit(void);
+extern int consOpen(int, int, int);
+extern int consTTY(void);
+extern int consWrite(char*, int);
+
+/*
+ * Clog.c
+ */
+extern int consPrint(char*, ...);
+extern int consVPrint(char*, va_list);
+#pragma varargck argpos consPrint 1
+
+/*
+ * fossil.c
+ */
+extern int Dflag;
diff --git a/src/cmd/fossil/9auth.c b/src/cmd/fossil/9auth.c
new file mode 100644
index 00000000..72b1f016
--- /dev/null
+++ b/src/cmd/fossil/9auth.c
@@ -0,0 +1,175 @@
+#include "stdinc.h"
+#include "9.h"
+
+int
+authRead(Fid* afid, void* data, int count)
+{
+ AuthInfo *ai;
+ AuthRpc *rpc;
+
+ if((rpc = afid->rpc) == nil){
+ vtSetError("not an auth fid");
+ return -1;
+ }
+
+ switch(auth_rpc(rpc, "read", nil, 0)){
+ default:
+ vtSetError("fossil authRead: auth protocol not finished");
+ return -1;
+ case ARdone:
+ if((ai = auth_getinfo(rpc)) == nil){
+ vtSetError("%r");
+ break;
+ }
+ if(ai->cuid == nil || *ai->cuid == '\0'){
+ vtSetError("auth with no cuid");
+ auth_freeAI(ai);
+ break;
+ }
+ assert(afid->cuname == nil);
+ afid->cuname = vtStrDup(ai->cuid);
+ auth_freeAI(ai);
+ if(Dflag)
+ fprint(2, "authRead cuname %s\n", afid->cuname);
+ assert(afid->uid == nil);
+ if((afid->uid = uidByUname(afid->cuname)) == nil){
+ vtSetError("unknown user %#q", afid->cuname);
+ break;
+ }
+ return 0;
+ case ARok:
+ if(count < rpc->narg){
+ vtSetError("not enough data in auth read");
+ break;
+ }
+ memmove(data, rpc->arg, rpc->narg);
+ return rpc->narg;
+ case ARphase:
+ vtSetError("%r");
+ break;
+ }
+ return -1;
+}
+
+int
+authWrite(Fid* afid, void* data, int count)
+{
+ assert(afid->rpc != nil);
+ if(auth_rpc(afid->rpc, "write", data, count) != ARok)
+ return -1;
+ return count;
+}
+
+int
+authCheck(Fcall* t, Fid* fid, Fsys* fsys)
+{
+ Con *con;
+ Fid *afid;
+ uchar buf[1];
+
+ /*
+ * Can't lookup with FidWlock here as there may be
+ * protocol to do. Use a separate lock to protect altering
+ * the auth information inside afid.
+ */
+ con = fid->con;
+ if(t->afid == NOFID){
+ /*
+ * If no authentication is asked for, allow
+ * "none" provided the connection has already
+ * been authenticatated.
+ *
+ * The console is allowed to attach without
+ * authentication.
+ */
+ vtRLock(con->alock);
+ if(con->isconsole){
+ /* anything goes */
+ }else if((con->flags&ConNoneAllow) || con->aok){
+ static int noneprint;
+
+ if(noneprint++ < 10)
+ consPrint("attach %s as %s: allowing as none\n",
+ fsysGetName(fsys), fid->uname);
+ vtMemFree(fid->uname);
+ fid->uname = vtStrDup(unamenone);
+ }else{
+ vtRUnlock(con->alock);
+ consPrint("attach %s as %s: connection not authenticated, not console\n",
+ fsysGetName(fsys), fid->uname);
+ vtSetError("cannot attach as none before authentication");
+ return 0;
+ }
+ vtRUnlock(con->alock);
+
+ if((fid->uid = uidByUname(fid->uname)) == nil){
+ consPrint("attach %s as %s: unknown uname\n",
+ fsysGetName(fsys), fid->uname);
+ vtSetError("unknown user");
+ return 0;
+ }
+ return 1;
+ }
+
+ if((afid = fidGet(con, t->afid, 0)) == nil){
+ consPrint("attach %s as %s: bad afid\n",
+ fsysGetName(fsys), fid->uname);
+ vtSetError("bad authentication fid");
+ return 0;
+ }
+
+ /*
+ * Check valid afid;
+ * check uname and aname match.
+ */
+ if(!(afid->qid.type & QTAUTH)){
+ consPrint("attach %s as %s: afid not an auth file\n",
+ fsysGetName(fsys), fid->uname);
+ fidPut(afid);
+ vtSetError("bad authentication fid");
+ return 0;
+ }
+ if(strcmp(afid->uname, fid->uname) != 0 || afid->fsys != fsys){
+ consPrint("attach %s as %s: afid is for %s as %s\n",
+ fsysGetName(fsys), fid->uname,
+ fsysGetName(afid->fsys), afid->uname);
+ fidPut(afid);
+ vtSetError("attach/auth mismatch");
+ return 0;
+ }
+
+ vtLock(afid->alock);
+ if(afid->cuname == nil){
+ if(authRead(afid, buf, 0) != 0 || afid->cuname == nil){
+ vtUnlock(afid->alock);
+ consPrint("attach %s as %s: %R\n",
+ fsysGetName(fsys), fid->uname);
+ fidPut(afid);
+ vtSetError("fossil authCheck: auth protocol not finished");
+ return 0;
+ }
+ }
+ vtUnlock(afid->alock);
+
+ assert(fid->uid == nil);
+ if((fid->uid = uidByUname(afid->cuname)) == nil){
+ consPrint("attach %s as %s: unknown cuname %s\n",
+ fsysGetName(fsys), fid->uname, afid->cuname);
+ fidPut(afid);
+ vtSetError("unknown user");
+ return 0;
+ }
+
+ vtMemFree(fid->uname);
+ fid->uname = vtStrDup(afid->cuname);
+ fidPut(afid);
+
+ /*
+ * Allow "none" once the connection has been authenticated.
+ */
+ vtLock(con->alock);
+ con->aok = 1;
+ vtUnlock(con->alock);
+
+ return 1;
+}
diff --git a/src/cmd/fossil/9dir.c b/src/cmd/fossil/9dir.c
new file mode 100644
index 00000000..09b483bd
--- /dev/null
+++ b/src/cmd/fossil/9dir.c
@@ -0,0 +1,132 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+/* one entry buffer for reading directories */
+struct DirBuf {
+ DirEntryEnum* dee;
+ int valid;
+ DirEntry de;
+};
+
+static DirBuf*
+dirBufAlloc(File* file)
+{
+ DirBuf *db;
+
+ db = vtMemAllocZ(sizeof(DirBuf));
+ db->dee = deeOpen(file);
+ if(db->dee == nil){
+ /* can happen if dir is removed from under us */
+ vtMemFree(db);
+ return nil;
+ }
+ return db;
+}
+
+void
+dirBufFree(DirBuf* db)
+{
+ if(db == nil)
+ return;
+
+ if(db->valid)
+ deCleanup(&db->de);
+ deeClose(db->dee);
+ vtMemFree(db);
+}
+
+int
+dirDe2M(DirEntry* de, uchar* p, int np)
+{
+ int n;
+ Dir dir;
+
+ memset(&dir, 0, sizeof(Dir));
+
+ dir.qid.path = de->qid;
+ dir.qid.vers = de->mcount;
+ dir.mode = de->mode & 0777;
+ if(de->mode & ModeAppend){
+ dir.qid.type |= QTAPPEND;
+ dir.mode |= DMAPPEND;
+ }
+ if(de->mode & ModeExclusive){
+ dir.qid.type |= QTEXCL;
+ dir.mode |= DMEXCL;
+ }
+ if(de->mode & ModeDir){
+ dir.qid.type |= QTDIR;
+ dir.mode |= DMDIR;
+ }
+ if(de->mode & ModeSnapshot){
+ dir.qid.type |= QTMOUNT; /* just for debugging */
+ dir.mode |= DMMOUNT;
+ }
+ if(de->mode & ModeTemporary){
+ dir.qid.type |= QTTMP;
+ dir.mode |= DMTMP;
+ }
+
+ dir.atime = de->atime;
+ dir.mtime = de->mtime;
+ dir.length = de->size;
+
+ dir.name = de->elem;
+ if((dir.uid = unameByUid(de->uid)) == nil)
+ dir.uid = smprint("(%s)", de->uid);
+ if((dir.gid = unameByUid(de->gid)) == nil)
+ dir.gid = smprint("(%s)", de->gid);
+ if((dir.muid = unameByUid(de->mid)) == nil)
+ dir.muid = smprint("(%s)", de->mid);
+
+ n = convD2M(&dir, p, np);
+
+ vtMemFree(dir.muid);
+ vtMemFree(dir.gid);
+ vtMemFree(dir.uid);
+
+ return n;
+}
+
+int
+dirRead(Fid* fid, uchar* p, int count, vlong offset)
+{
+ int n, nb;
+ DirBuf *db;
+
+ /*
+ * special case of rewinding a directory
+ * otherwise ignore the offset
+ */
+ if(offset == 0 && fid->db){
+ dirBufFree(fid->db);
+ fid->db = nil;
+ }
+
+ if(fid->db == nil){
+ fid->db = dirBufAlloc(fid->file);
+ if(fid->db == nil)
+ return -1;
+ }
+
+ db = fid->db;
+
+ for(nb = 0; nb < count; nb += n){
+ if(!db->valid){
+ n = deeRead(db->dee, &db->de);
+ if(n < 0)
+ return -1;
+ if(n == 0)
+ break;
+ db->valid = 1;
+ }
+ n = dirDe2M(&db->de, p+nb, count-nb);
+ if(n <= BIT16SZ)
+ break;
+ db->valid = 0;
+ deCleanup(&db->de);
+ }
+
+ return nb;
+}
diff --git a/src/cmd/fossil/9excl.c b/src/cmd/fossil/9excl.c
new file mode 100644
index 00000000..6fce280a
--- /dev/null
+++ b/src/cmd/fossil/9excl.c
@@ -0,0 +1,126 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+static struct {
+ VtLock* lock;
+
+ Excl* head;
+ Excl* tail;
+} ebox;
+
+struct Excl {
+ Fsys* fsys;
+ uvlong path;
+ ulong time;
+
+ Excl* next;
+ Excl* prev;
+};
+
+enum {
+ LifeTime = (5*60),
+};
+
+int
+exclAlloc(Fid* fid)
+{
+ ulong t;
+ Excl *excl;
+
+ assert(fid->excl == nil);
+
+ t = time(0L);
+ vtLock(ebox.lock);
+ for(excl = ebox.head; excl != nil; excl = excl->next){
+ if(excl->fsys != fid->fsys || excl->path != fid->qid.path)
+ continue;
+ /*
+ * Found it.
+ * Now, check if it's timed out.
+ * If not, return error, it's locked.
+ * If it has timed out, zap the old
+ * one and continue on to allocate a
+ * a new one.
+ */
+ if(excl->time >= t){
+ vtUnlock(ebox.lock);
+ vtSetError("exclusive lock");
+ return 0;
+ }
+ excl->fsys = nil;
+ }
+
+ /*
+ * Not found or timed-out.
+ * Alloc a new one and initialise.
+ */
+ excl = vtMemAllocZ(sizeof(Excl));
+ excl->fsys = fid->fsys;
+ excl->path = fid->qid.path;
+ excl->time = t+LifeTime;
+ if(ebox.tail != nil){
+ excl->prev = ebox.tail;
+ ebox.tail->next = excl;
+ }
+ else{
+ ebox.head = excl;
+ excl->prev = nil;
+ }
+ ebox.tail = excl;
+ excl->next = nil;
+ vtUnlock(ebox.lock);
+
+ fid->excl = excl;
+ return 1;
+}
+
+int
+exclUpdate(Fid* fid)
+{
+ ulong t;
+ Excl *excl;
+
+ excl = fid->excl;
+
+ t = time(0L);
+ vtLock(ebox.lock);
+ if(excl->time < t || excl->fsys != fid->fsys){
+ vtUnlock(ebox.lock);
+ vtSetError("exclusive lock broken");
+ return 0;
+ }
+ excl->time = t+LifeTime;
+ vtUnlock(ebox.lock);
+
+ return 1;
+}
+
+void
+exclFree(Fid* fid)
+{
+ Excl *excl;
+
+ if((excl = fid->excl) == nil)
+ return;
+ fid->excl = nil;
+
+ vtLock(ebox.lock);
+ if(excl->prev != nil)
+ excl->prev->next = excl->next;
+ else
+ ebox.head = excl->next;
+ if(excl->next != nil)
+ excl->next->prev = excl->prev;
+ else
+ ebox.tail = excl->prev;
+ vtUnlock(ebox.lock);
+
+ vtMemFree(excl);
+}
+
+void
+exclInit(void)
+{
+ ebox.lock = vtLockAlloc();
+}
diff --git a/src/cmd/fossil/9fid.c b/src/cmd/fossil/9fid.c
new file mode 100644
index 00000000..ea7de5ab
--- /dev/null
+++ b/src/cmd/fossil/9fid.c
@@ -0,0 +1,304 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+static struct {
+ VtLock* lock;
+
+ Fid* free;
+ int nfree;
+ int inuse;
+} fbox;
+
+static void
+fidLock(Fid* fid, int flags)
+{
+ if(flags & FidFWlock){
+ vtLock(fid->lock);
+ fid->flags = flags;
+ }
+ else
+ vtRLock(fid->lock);
+
+ /*
+ * Callers of file* routines are expected to lock fsys->fs->elk
+ * before making any calls in order to make sure the epoch doesn't
+ * change underfoot. With the exception of Tversion and Tattach,
+ * that implies all 9P functions need to lock on entry and unlock
+ * on exit. Fortunately, the general case is the 9P functions do
+ * fidGet on entry and fidPut on exit, so this is a convenient place
+ * to do the locking.
+ * No fsys->fs->elk lock is required if the fid is being created
+ * (Tauth, Tattach and Twalk). FidFCreate is always accompanied by
+ * FidFWlock so the setting and testing of FidFCreate here and in
+ * fidUnlock below is always done under fid->lock.
+ * A side effect is that fidFree is called with the fid locked, and
+ * must call fidUnlock only after it has disposed of any File
+ * resources still held.
+ */
+ if(!(flags & FidFCreate))
+ fsysFsRlock(fid->fsys);
+}
+
+static void
+fidUnlock(Fid* fid)
+{
+ if(!(fid->flags & FidFCreate))
+ fsysFsRUnlock(fid->fsys);
+ if(fid->flags & FidFWlock){
+ fid->flags = 0;
+ vtUnlock(fid->lock);
+ return;
+ }
+ vtRUnlock(fid->lock);
+}
+
+static Fid*
+fidAlloc(void)
+{
+ Fid *fid;
+
+ vtLock(fbox.lock);
+ if(fbox.nfree > 0){
+ fid = fbox.free;
+ fbox.free = fid->hash;
+ fbox.nfree--;
+ }
+ else{
+ fid = vtMemAllocZ(sizeof(Fid));
+ fid->lock = vtLockAlloc();
+ fid->alock = vtLockAlloc();
+ }
+ fbox.inuse++;
+ vtUnlock(fbox.lock);
+
+ fid->con = nil;
+ fid->fidno = NOFID;
+ fid->ref = 0;
+ fid->flags = 0;
+ fid->open = FidOCreate;
+ assert(fid->fsys == nil);
+ assert(fid->file == nil);
+ fid->qid = (Qid){0, 0, 0};
+ assert(fid->uid == nil);
+ assert(fid->uname == nil);
+ assert(fid->db == nil);
+ assert(fid->excl == nil);
+ assert(fid->rpc == nil);
+ assert(fid->cuname == nil);
+ fid->hash = fid->next = fid->prev = nil;
+
+ return fid;
+}
+
+static void
+fidFree(Fid* fid)
+{
+ if(fid->file != nil){
+ fileDecRef(fid->file);
+ fid->file = nil;
+ }
+ if(fid->db != nil){
+ dirBufFree(fid->db);
+ fid->db = nil;
+ }
+ fidUnlock(fid);
+
+ if(fid->uid != nil){
+ vtMemFree(fid->uid);
+ fid->uid = nil;
+ }
+ if(fid->uname != nil){
+ vtMemFree(fid->uname);
+ fid->uname = nil;
+ }
+ if(fid->excl != nil)
+ exclFree(fid);
+ if(fid->rpc != nil){
+ close(fid->rpc->afd);
+ auth_freerpc(fid->rpc);
+ fid->rpc = nil;
+ }
+ if(fid->fsys != nil){
+ fsysPut(fid->fsys);
+ fid->fsys = nil;
+ }
+ if(fid->cuname != nil){
+ vtMemFree(fid->cuname);
+ fid->cuname = nil;
+ }
+
+ vtLock(fbox.lock);
+ fbox.inuse--;
+ if(fbox.nfree < 10){
+ fid->hash = fbox.free;
+ fbox.free = fid;
+ fbox.nfree++;
+ }
+ else{
+ vtLockFree(fid->alock);
+ vtLockFree(fid->lock);
+ vtMemFree(fid);
+ }
+ vtUnlock(fbox.lock);
+}
+
+static void
+fidUnHash(Fid* fid)
+{
+ Fid *fp, **hash;
+
+ assert(fid->ref == 0);
+
+ hash = &fid->con->fidhash[fid->fidno % NFidHash];
+ for(fp = *hash; fp != nil; fp = fp->hash){
+ if(fp == fid){
+ *hash = fp->hash;
+ break;
+ }
+ hash = &fp->hash;
+ }
+ assert(fp == fid);
+
+ if(fid->prev != nil)
+ fid->prev->next = fid->next;
+ else
+ fid->con->fhead = fid->next;
+ if(fid->next != nil)
+ fid->next->prev = fid->prev;
+ else
+ fid->con->ftail = fid->prev;
+ fid->prev = fid->next = nil;
+
+ fid->con->nfid--;
+}
+
+Fid*
+fidGet(Con* con, u32int fidno, int flags)
+{
+ Fid *fid, **hash;
+
+ if(fidno == NOFID)
+ return nil;
+
+ hash = &con->fidhash[fidno % NFidHash];
+ vtLock(con->fidlock);
+ for(fid = *hash; fid != nil; fid = fid->hash){
+ if(fid->fidno != fidno)
+ continue;
+
+ /*
+ * Already in use is an error
+ * when called from attach, clone or walk.
+ */
+ if(flags & FidFCreate){
+ vtUnlock(con->fidlock);
+ vtSetError("%s: fid 0x%ud in use", argv0, fidno);
+ return nil;
+ }
+ fid->ref++;
+ vtUnlock(con->fidlock);
+
+ fidLock(fid, flags);
+ if((fid->open & FidOCreate) || fid->fidno == NOFID){
+ fidPut(fid);
+ vtSetError("%s: fid invalid", argv0);
+ return nil;
+ }
+ return fid;
+ }
+
+ if((flags & FidFCreate) && (fid = fidAlloc()) != nil){
+ assert(flags & FidFWlock);
+ fid->con = con;
+ fid->fidno = fidno;
+ fid->ref = 1;
+
+ fid->hash = *hash;
+ *hash = fid;
+ if(con->ftail != nil){
+ fid->prev = con->ftail;
+ con->ftail->next = fid;
+ }
+ else{
+ con->fhead = fid;
+ fid->prev = nil;
+ }
+ con->ftail = fid;
+ fid->next = nil;
+
+ con->nfid++;
+ vtUnlock(con->fidlock);
+
+ /*
+ * The FidOCreate flag is used to prevent any
+ * accidental access to the Fid between unlocking the
+ * hash and acquiring the Fid lock for return.
+ */
+ fidLock(fid, flags);
+ fid->open &= ~FidOCreate;
+ return fid;
+ }
+ vtUnlock(con->fidlock);
+
+ vtSetError("%s: fid not found", argv0);
+ return nil;
+}
+
+void
+fidPut(Fid* fid)
+{
+ vtLock(fid->con->fidlock);
+ assert(fid->ref > 0);
+ fid->ref--;
+ vtUnlock(fid->con->fidlock);
+
+ if(fid->ref == 0 && fid->fidno == NOFID){
+ fidFree(fid);
+ return;
+ }
+ fidUnlock(fid);
+}
+
+void
+fidClunk(Fid* fid)
+{
+ assert(fid->flags & FidFWlock);
+
+ vtLock(fid->con->fidlock);
+ assert(fid->ref > 0);
+ fid->ref--;
+ fidUnHash(fid);
+ fid->fidno = NOFID;
+ vtUnlock(fid->con->fidlock);
+
+ if(fid->ref > 0){
+ /* not reached - fidUnHash requires ref == 0 */
+ fidUnlock(fid);
+ return;
+ }
+ fidFree(fid);
+}
+
+void
+fidClunkAll(Con* con)
+{
+ Fid *fid;
+ u32int fidno;
+
+ vtLock(con->fidlock);
+ while(con->fhead != nil){
+ fidno = con->fhead->fidno;
+ vtUnlock(con->fidlock);
+ if((fid = fidGet(con, fidno, FidFWlock)) != nil)
+ fidClunk(fid);
+ vtLock(con->fidlock);
+ }
+ vtUnlock(con->fidlock);
+}
+
+void
+fidInit(void)
+{
+ fbox.lock = vtLockAlloc();
+}
diff --git a/src/cmd/fossil/9fsys.c b/src/cmd/fossil/9fsys.c
new file mode 100644
index 00000000..4e139f70
--- /dev/null
+++ b/src/cmd/fossil/9fsys.c
@@ -0,0 +1,1896 @@
+#include "stdinc.h"
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+#include "9.h"
+
+struct Fsys {
+ VtLock* lock;
+
+ char* name; /* copy here & Fs to ease error reporting */
+ char* dev;
+ char* venti;
+
+ Fs* fs;
+ VtSession* session;
+ int ref;
+
+ int noauth;
+ int noperm;
+ int wstatallow;
+
+ Fsys* next;
+};
+
+int mempcnt; /* from fossil.c */
+
+int fsGetBlockSize(Fs *fs);
+
+static struct {
+ VtLock* lock;
+ Fsys* head;
+ Fsys* tail;
+
+ char* curfsys;
+} sbox;
+
+static char *_argv0;
+#define argv0 _argv0
+
+static char FsysAll[] = "all";
+
+static char EFsysBusy[] = "fsys: '%s' busy";
+static char EFsysExists[] = "fsys: '%s' already exists";
+static char EFsysNoCurrent[] = "fsys: no current fsys";
+static char EFsysNotFound[] = "fsys: '%s' not found";
+static char EFsysNotOpen[] = "fsys: '%s' not open";
+
+static char *
+ventihost(char *host)
+{
+ if(host != nil)
+ return vtStrDup(host);
+ host = getenv("venti");
+ if(host == nil)
+ host = vtStrDup("$venti");
+ return host;
+}
+
+static void
+prventihost(char *host)
+{
+ char *vh;
+
+ vh = ventihost(host);
+ fprint(2, "%s: dialing venti at %s\n",
+ argv0, netmkaddr(vh, 0, "venti"));
+ free(vh);
+}
+
+static VtSession *
+myDial(char *host, int canfail)
+{
+ prventihost(host);
+ return vtDial(host, canfail);
+}
+
+static int
+myRedial(VtSession *z, char *host)
+{
+ prventihost(host);
+ return vtRedial(z, host);
+}
+
+static Fsys*
+_fsysGet(char* name)
+{
+ Fsys *fsys;
+
+ if(name == nil || name[0] == '\0')
+ name = "main";
+
+ vtRLock(sbox.lock);
+ for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
+ if(strcmp(name, fsys->name) == 0){
+ fsys->ref++;
+ break;
+ }
+ }
+ vtRUnlock(sbox.lock);
+ if(fsys == nil)
+ vtSetError(EFsysNotFound, name);
+ return fsys;
+}
+
+static int
+cmdPrintConfig(int argc, char* argv[])
+{
+ Fsys *fsys;
+ char *usage = "usage: printconfig";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc)
+ return cliError(usage);
+
+ vtRLock(sbox.lock);
+ for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
+ consPrint("\tfsys %s config %s\n", fsys->name, fsys->dev);
+ if(fsys->venti && fsys->venti[0])
+ consPrint("\tfsys %s venti %q\n", fsys->name,
+ fsys->venti);
+ }
+ vtRUnlock(sbox.lock);
+ return 1;
+}
+
+Fsys*
+fsysGet(char* name)
+{
+ Fsys *fsys;
+
+ if((fsys = _fsysGet(name)) == nil)
+ return nil;
+
+ vtLock(fsys->lock);
+ if(fsys->fs == nil){
+ vtSetError(EFsysNotOpen, fsys->name);
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+ return nil;
+ }
+ vtUnlock(fsys->lock);
+
+ return fsys;
+}
+
+char*
+fsysGetName(Fsys* fsys)
+{
+ return fsys->name;
+}
+
+Fsys*
+fsysIncRef(Fsys* fsys)
+{
+ vtLock(sbox.lock);
+ fsys->ref++;
+ vtUnlock(sbox.lock);
+
+ return fsys;
+}
+
+void
+fsysPut(Fsys* fsys)
+{
+ vtLock(sbox.lock);
+ assert(fsys->ref > 0);
+ fsys->ref--;
+ vtUnlock(sbox.lock);
+}
+
+Fs*
+fsysGetFs(Fsys* fsys)
+{
+ assert(fsys != nil && fsys->fs != nil);
+
+ return fsys->fs;
+}
+
+void
+fsysFsRlock(Fsys* fsys)
+{
+ vtRLock(fsys->fs->elk);
+}
+
+void
+fsysFsRUnlock(Fsys* fsys)
+{
+ vtRUnlock(fsys->fs->elk);
+}
+
+int
+fsysNoAuthCheck(Fsys* fsys)
+{
+ return fsys->noauth;
+}
+
+int
+fsysNoPermCheck(Fsys* fsys)
+{
+ return fsys->noperm;
+}
+
+int
+fsysWstatAllow(Fsys* fsys)
+{
+ return fsys->wstatallow;
+}
+
+static char modechars[] = "YUGalLdHSATs";
+static ulong modebits[] = {
+ ModeSticky,
+ ModeSetUid,
+ ModeSetGid,
+ ModeAppend,
+ ModeExclusive,
+ ModeLink,
+ ModeDir,
+ ModeHidden,
+ ModeSystem,
+ ModeArchive,
+ ModeTemporary,
+ ModeSnapshot,
+ 0
+};
+
+char*
+fsysModeString(ulong mode, char *buf)
+{
+ int i;
+ char *p;
+
+ p = buf;
+ for(i=0; modebits[i]; i++)
+ if(mode & modebits[i])
+ *p++ = modechars[i];
+ sprint(p, "%luo", mode&0777);
+ return buf;
+}
+
+int
+fsysParseMode(char* s, ulong* mode)
+{
+ ulong x, y;
+ char *p;
+
+ x = 0;
+ for(; *s < '0' || *s > '9'; s++){
+ if(*s == 0)
+ return 0;
+ p = strchr(modechars, *s);
+ if(p == nil)
+ return 0;
+ x |= modebits[p-modechars];
+ }
+ y = strtoul(s, &p, 8);
+ if(*p != '\0' || y > 0777)
+ return 0;
+ *mode = x|y;
+ return 1;
+}
+
+File*
+fsysGetRoot(Fsys* fsys, char* name)
+{
+ File *root, *sub;
+
+ assert(fsys != nil && fsys->fs != nil);
+
+ root = fsGetRoot(fsys->fs);
+ if(name == nil || strcmp(name, "") == 0)
+ return root;
+
+ sub = fileWalk(root, name);
+ fileDecRef(root);
+
+ return sub;
+}
+
+static Fsys*
+fsysAlloc(char* name, char* dev)
+{
+ Fsys *fsys;
+
+ vtLock(sbox.lock);
+ for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
+ if(strcmp(fsys->name, name) != 0)
+ continue;
+ vtSetError(EFsysExists, name);
+ vtUnlock(sbox.lock);
+ return nil;
+ }
+
+ fsys = vtMemAllocZ(sizeof(Fsys));
+ fsys->lock = vtLockAlloc();
+ fsys->name = vtStrDup(name);
+ fsys->dev = vtStrDup(dev);
+
+ fsys->ref = 1;
+
+ if(sbox.tail != nil)
+ sbox.tail->next = fsys;
+ else
+ sbox.head = fsys;
+ sbox.tail = fsys;
+ vtUnlock(sbox.lock);
+
+ return fsys;
+}
+
+static int
+fsysClose(Fsys* fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] close";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ return cliError("close isn't working yet; halt %s and then kill fossil",
+ fsys->name);
+
+ /*
+ * Oooh. This could be hard. What if fsys->ref != 1?
+ * Also, fsClose() either does the job or panics, can we
+ * gracefully detect it's still busy?
+ *
+ * More thought and care needed here.
+ fsClose(fsys->fs);
+ fsys->fs = nil;
+ vtClose(fsys->session);
+ fsys->session = nil;
+
+ if(sbox.curfsys != nil && strcmp(fsys->name, sbox.curfsys) == 0){
+ sbox.curfsys = nil;
+ consPrompt(nil);
+ }
+
+ return 1;
+ */
+}
+
+static int
+fsysVac(Fsys* fsys, int argc, char* argv[])
+{
+ uchar score[VtScoreSize];
+ char *usage = "usage: [fsys name] vac path";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc != 1)
+ return cliError(usage);
+
+ if(!fsVac(fsys->fs, argv[0], score))
+ return 0;
+
+ consPrint("vac:%V\n", score);
+ return 1;
+}
+
+static int
+fsysSnap(Fsys* fsys, int argc, char* argv[])
+{
+ int doarchive;
+ char *usage = "usage: [fsys name] snap [-a] [-s /active] [-d /archive/yyyy/mmmm]";
+ char *src, *dst;
+
+ src = nil;
+ dst = nil;
+ doarchive = 0;
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'a':
+ doarchive = 1;
+ break;
+ case 'd':
+ if((dst = ARGF()) == nil)
+ return cliError(usage);
+ break;
+ case 's':
+ if((src = ARGF()) == nil)
+ return cliError(usage);
+ break;
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ if(!fsSnapshot(fsys->fs, src, dst, doarchive))
+ return 0;
+
+ return 1;
+}
+
+static int
+fsysSnapClean(Fsys *fsys, int argc, char* argv[])
+{
+ u32int arch, snap, life;
+ char *usage = "usage: [fsys name] snapclean [maxminutes]\n";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc > 1)
+ return cliError(usage);
+ if(argc == 1)
+ life = atoi(argv[0]);
+ else
+ snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
+
+ fsSnapshotCleanup(fsys->fs, life);
+ return 1;
+}
+
+static int
+fsysSnapTime(Fsys* fsys, int argc, char* argv[])
+{
+ char buf[128], *x;
+ int hh, mm, changed;
+ u32int arch, snap, life;
+ char *usage = "usage: [fsys name] snaptime [-a hhmm] [-s snapminutes] [-t maxminutes]";
+
+ changed = 0;
+ snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
+ ARGBEGIN{
+ case 'a':
+ changed = 1;
+ x = ARGF();
+ if(x == nil)
+ return cliError(usage);
+ if(strcmp(x, "none") == 0){
+ arch = ~(u32int)0;
+ break;
+ }
+ if(strlen(x) != 4 || strspn(x, "0123456789") != 4)
+ return cliError(usage);
+ hh = (x[0]-'0')*10 + x[1]-'0';
+ mm = (x[2]-'0')*10 + x[3]-'0';
+ if(hh >= 24 || mm >= 60)
+ return cliError(usage);
+ arch = hh*60+mm;
+ break;
+ case 's':
+ changed = 1;
+ x = ARGF();
+ if(x == nil)
+ return cliError(usage);
+ if(strcmp(x, "none") == 0){
+ snap = ~(u32int)0;
+ break;
+ }
+ snap = atoi(x);
+ break;
+ case 't':
+ changed = 1;
+ x = ARGF();
+ if(x == nil)
+ return cliError(usage);
+ if(strcmp(x, "none") == 0){
+ life = ~(u32int)0;
+ break;
+ }
+ life = atoi(x);
+ break;
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 0)
+ return cliError(usage);
+
+ if(changed){
+ snapSetTimes(fsys->fs->snap, arch, snap, life);
+ return 1;
+ }
+ snapGetTimes(fsys->fs->snap, &arch, &snap, &life);
+ if(arch != ~(u32int)0)
+ sprint(buf, "-a %02d%02d", arch/60, arch%60);
+ else
+ sprint(buf, "-a none");
+ if(snap != ~(u32int)0)
+ sprint(buf+strlen(buf), " -s %d", snap);
+ else
+ sprint(buf+strlen(buf), " -s none");
+ if(life != ~(u32int)0)
+ sprint(buf+strlen(buf), " -t %ud", life);
+ else
+ sprint(buf+strlen(buf), " -t none");
+ consPrint("\tsnaptime %s\n", buf);
+ return 1;
+}
+
+static int
+fsysSync(Fsys* fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] sync";
+ int n;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 0)
+ return cliError(usage);
+
+ n = cacheDirty(fsys->fs->cache);
+ fsSync(fsys->fs);
+ consPrint("\t%s sync: wrote %d blocks\n", fsys->name, n);
+ return 1;
+}
+
+static int
+fsysHalt(Fsys *fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] halt";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 0)
+ return cliError(usage);
+
+ fsHalt(fsys->fs);
+ return 1;
+}
+
+static int
+fsysUnhalt(Fsys *fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] unhalt";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 0)
+ return cliError(usage);
+
+ if(!fsys->fs->halted)
+ return cliError("file system %s not halted", fsys->name);
+
+ fsUnhalt(fsys->fs);
+ return 1;
+}
+
+static int
+fsysRemove(Fsys* fsys, int argc, char* argv[])
+{
+ File *file;
+ char *usage = "usage: [fsys name] remove path ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc == 0)
+ return cliError(usage);
+
+ vtRLock(fsys->fs->elk);
+ while(argc > 0){
+ if((file = fileOpen(fsys->fs, argv[0])) == nil)
+ consPrint("%s: %R\n", argv[0]);
+ else{
+ if(!fileRemove(file, uidadm))
+ consPrint("%s: %R\n", argv[0]);
+ fileDecRef(file);
+ }
+ argc--;
+ argv++;
+ }
+ vtRUnlock(fsys->fs->elk);
+
+ return 1;
+}
+
+static int
+fsysClri(Fsys* fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] clri path ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc == 0)
+ return cliError(usage);
+
+ vtRLock(fsys->fs->elk);
+ while(argc > 0){
+ if(!fileClriPath(fsys->fs, argv[0], uidadm))
+ consPrint("clri %s: %R\n", argv[0]);
+ argc--;
+ argv++;
+ }
+ vtRUnlock(fsys->fs->elk);
+
+ return 1;
+}
+
+/*
+ * Inspect and edit the labels for blocks on disk.
+ */
+static int
+fsysLabel(Fsys* fsys, int argc, char* argv[])
+{
+ Fs *fs;
+ Label l;
+ int n, r;
+ u32int addr;
+ Block *b, *bb;
+ char *usage = "usage: [fsys name] label addr [type state epoch epochClose tag]";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc != 1 && argc != 6)
+ return cliError(usage);
+
+ r = 0;
+ vtRLock(fsys->fs->elk);
+
+ fs = fsys->fs;
+ addr = strtoul(argv[0], 0, 0);
+ b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
+ if(b == nil)
+ goto Out0;
+
+ l = b->l;
+ consPrint("%slabel %#ux %ud %ud %ud %ud %#x\n",
+ argc==6 ? "old: " : "", addr, l.type, l.state,
+ l.epoch, l.epochClose, l.tag);
+
+ if(argc == 6){
+ if(strcmp(argv[1], "-") != 0)
+ l.type = atoi(argv[1]);
+ if(strcmp(argv[2], "-") != 0)
+ l.state = atoi(argv[2]);
+ if(strcmp(argv[3], "-") != 0)
+ l.epoch = strtoul(argv[3], 0, 0);
+ if(strcmp(argv[4], "-") != 0)
+ l.epochClose = strtoul(argv[4], 0, 0);
+ if(strcmp(argv[5], "-") != 0)
+ l.tag = strtoul(argv[5], 0, 0);
+
+ consPrint("new: label %#ux %ud %ud %ud %ud %#x\n",
+ addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
+ bb = _blockSetLabel(b, &l);
+ if(bb == nil)
+ goto Out1;
+ n = 0;
+ for(;;){
+ if(blockWrite(bb, Waitlock)){
+ while(bb->iostate != BioClean){
+ assert(bb->iostate == BioWriting);
+ vtSleep(bb->ioready);
+ }
+ break;
+ }
+ consPrint("blockWrite: %R\n");
+ if(n++ >= 5){
+ consPrint("giving up\n");
+ break;
+ }
+ sleep(5*1000);
+ }
+ blockPut(bb);
+ }
+ r = 1;
+Out1:
+ blockPut(b);
+Out0:
+ vtRUnlock(fs->elk);
+
+ return r;
+}
+
+/*
+ * Inspect and edit the blocks on disk.
+ */
+static int
+fsysBlock(Fsys* fsys, int argc, char* argv[])
+{
+ Fs *fs;
+ char *s;
+ Block *b;
+ uchar *buf;
+ u32int addr;
+ int c, count, i, offset;
+ char *usage = "usage: [fsys name] block addr offset [count [data]]";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc < 2 || argc > 4)
+ return cliError(usage);
+
+ fs = fsys->fs;
+ addr = strtoul(argv[0], 0, 0);
+ offset = strtoul(argv[1], 0, 0);
+ if(offset < 0 || offset >= fs->blockSize){
+ vtSetError("bad offset");
+ return 0;
+ }
+ if(argc > 2)
+ count = strtoul(argv[2], 0, 0);
+ else
+ count = 100000000;
+ if(offset+count > fs->blockSize)
+ count = fs->blockSize - count;
+
+ vtRLock(fs->elk);
+
+ b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
+ if(b == nil){
+ vtSetError("cacheLocal %#ux: %R", addr);
+ vtRUnlock(fs->elk);
+ return 0;
+ }
+
+ consPrint("\t%sblock %#ux %ud %ud %.*H\n",
+ argc==4 ? "old: " : "", addr, offset, count, count, b->data+offset);
+
+ if(argc == 4){
+ s = argv[3];
+ if(strlen(s) != 2*count){
+ vtSetError("bad data count");
+ goto Out;
+ }
+ buf = vtMemAllocZ(count);
+ for(i = 0; i < count*2; i++){
+ if(s[i] >= '0' && s[i] <= '9')
+ c = s[i] - '0';
+ else if(s[i] >= 'a' && s[i] <= 'f')
+ c = s[i] - 'a' + 10;
+ else if(s[i] >= 'A' && s[i] <= 'F')
+ c = s[i] - 'A' + 10;
+ else{
+ vtSetError("bad hex");
+ vtMemFree(buf);
+ goto Out;
+ }
+ if((i & 1) == 0)
+ c <<= 4;
+ buf[i>>1] |= c;
+ }
+ memmove(b->data+offset, buf, count);
+ consPrint("\tnew: block %#ux %ud %ud %.*H\n",
+ addr, offset, count, count, b->data+offset);
+ blockDirty(b);
+ }
+
+Out:
+ blockPut(b);
+ vtRUnlock(fs->elk);
+
+ return 1;
+}
+
+/*
+ * Free a disk block.
+ */
+static int
+fsysBfree(Fsys* fsys, int argc, char* argv[])
+{
+ Fs *fs;
+ Label l;
+ char *p;
+ Block *b;
+ u32int addr;
+ char *usage = "usage: [fsys name] bfree addr ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc == 0)
+ return cliError(usage);
+
+ fs = fsys->fs;
+ vtRLock(fs->elk);
+ while(argc > 0){
+ addr = strtoul(argv[0], &p, 0);
+ if(*p != '\0'){
+ consPrint("bad address - '%ud'\n", addr);
+ /* syntax error; let's stop */
+ vtRUnlock(fs->elk);
+ return 0;
+ }
+ b = cacheLocal(fs->cache, PartData, addr, OReadOnly);
+ if(b == nil){
+ consPrint("loading %#ux: %R\n", addr);
+ continue;
+ }
+ l = b->l;
+ if(l.state == BsFree)
+ consPrint("%#ux is already free\n", addr);
+ else{
+ consPrint("label %#ux %ud %ud %ud %ud %#x\n",
+ addr, l.type, l.state, l.epoch, l.epochClose, l.tag);
+ l.state = BsFree;
+ l.type = BtMax;
+ l.tag = 0;
+ l.epoch = 0;
+ l.epochClose = 0;
+ if(!blockSetLabel(b, &l, 0))
+ consPrint("freeing %#ux: %R\n", addr);
+ }
+ blockPut(b);
+ argc--;
+ argv++;
+ }
+ vtRUnlock(fs->elk);
+
+ return 1;
+}
+
+static int
+fsysDf(Fsys *fsys, int argc, char* argv[])
+{
+ char *usage = "usage: [fsys name] df";
+ u32int used, tot, bsize;
+ Fs *fs;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc != 0)
+ return cliError(usage);
+
+ fs = fsys->fs;
+ cacheCountUsed(fs->cache, fs->elo, &used, &tot, &bsize);
+ consPrint("\t%s: %,llud used + %,llud free = %,llud (%.1f%% used)\n",
+ fsys->name, used*(vlong)bsize, (tot-used)*(vlong)bsize,
+ tot*(vlong)bsize, used*100.0/tot);
+ return 1;
+}
+
+/*
+ * Zero an entry or a pointer.
+ */
+static int
+fsysClrep(Fsys* fsys, int argc, char* argv[], int ch)
+{
+ Fs *fs;
+ Entry e;
+ Block *b;
+ u32int addr;
+ int i, max, offset, sz;
+ uchar zero[VtEntrySize];
+ char *usage = "usage: [fsys name] clr%c addr offset ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage, ch);
+ }ARGEND
+ if(argc < 2)
+ return cliError(usage, ch);
+
+ fs = fsys->fs;
+ vtRLock(fsys->fs->elk);
+
+ addr = strtoul(argv[0], 0, 0);
+ b = cacheLocal(fs->cache, PartData, addr, argc==4 ? OReadWrite : OReadOnly);
+ if(b == nil){
+ vtSetError("cacheLocal %#ux: %R", addr);
+ Err:
+ vtRUnlock(fsys->fs->elk);
+ return 0;
+ }
+
+ switch(ch){
+ default:
+ vtSetError("clrep");
+ goto Err;
+ case 'e':
+ if(b->l.type != BtDir){
+ vtSetError("wrong block type");
+ goto Err;
+ }
+ sz = VtEntrySize;
+ memset(&e, 0, sizeof e);
+ entryPack(&e, zero, 0);
+ break;
+ case 'p':
+ if(b->l.type == BtDir || b->l.type == BtData){
+ vtSetError("wrong block type");
+ goto Err;
+ }
+ sz = VtScoreSize;
+ memmove(zero, vtZeroScore, VtScoreSize);
+ break;
+ }
+ max = fs->blockSize/sz;
+
+ for(i = 1; i < argc; i++){
+ offset = atoi(argv[i]);
+ if(offset >= max){
+ consPrint("\toffset %d too large (>= %d)\n", i, max);
+ continue;
+ }
+ consPrint("\tblock %#ux %d %d %.*H\n", addr, offset*sz, sz, sz, b->data+offset*sz);
+ memmove(b->data+offset*sz, zero, sz);
+ }
+ blockDirty(b);
+ blockPut(b);
+ vtRUnlock(fsys->fs->elk);
+
+ return 1;
+}
+
+static int
+fsysClre(Fsys* fsys, int argc, char* argv[])
+{
+ return fsysClrep(fsys, argc, argv, 'e');
+}
+
+static int
+fsysClrp(Fsys* fsys, int argc, char* argv[])
+{
+ return fsysClrep(fsys, argc, argv, 'p');
+}
+
+static int
+fsysEsearch1(File* f, char* s, u32int elo)
+{
+ int n, r;
+ DirEntry de;
+ DirEntryEnum *dee;
+ File *ff;
+ Entry e, ee;
+ char *t;
+
+ dee = deeOpen(f);
+ if(dee == nil)
+ return 0;
+
+ n = 0;
+ for(;;){
+ r = deeRead(dee, &de);
+ if(r < 0){
+ consPrint("\tdeeRead %s/%s: %R\n", s, de.elem);
+ break;
+ }
+ if(r == 0)
+ break;
+ if(de.mode & ModeSnapshot){
+ if((ff = fileWalk(f, de.elem)) == nil)
+ consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
+ else{
+ if(!fileGetSources(ff, &e, &ee))
+ consPrint("\tcannot get sources for %s/%s: %R\n", s, de.elem);
+ else if(e.snap != 0 && e.snap < elo){
+ consPrint("\t%ud\tclri %s/%s\n", e.snap, s, de.elem);
+ n++;
+ }
+ fileDecRef(ff);
+ }
+ }
+ else if(de.mode & ModeDir){
+ if((ff = fileWalk(f, de.elem)) == nil)
+ consPrint("\tcannot walk %s/%s: %R\n", s, de.elem);
+ else{
+ t = smprint("%s/%s", s, de.elem);
+ n += fsysEsearch1(ff, t, elo);
+ vtMemFree(t);
+ fileDecRef(ff);
+ }
+ }
+ deCleanup(&de);
+ if(r < 0)
+ break;
+ }
+ deeClose(dee);
+
+ return n;
+}
+
+static int
+fsysEsearch(Fs* fs, char* path, u32int elo)
+{
+ int n;
+ File *f;
+ DirEntry de;
+
+ f = fileOpen(fs, path);
+ if(f == nil)
+ return 0;
+ if(!fileGetDir(f, &de)){
+ consPrint("\tfileGetDir %s failed: %R\n", path);
+ fileDecRef(f);
+ return 0;
+ }
+ if((de.mode & ModeDir) == 0){
+ fileDecRef(f);
+ deCleanup(&de);
+ return 0;
+ }
+ deCleanup(&de);
+ n = fsysEsearch1(f, path, elo);
+ fileDecRef(f);
+ return n;
+}
+
+static int
+fsysEpoch(Fsys* fsys, int argc, char* argv[])
+{
+ Fs *fs;
+ int force, n, remove;
+ u32int low, old;
+ char *usage = "usage: [fsys name] epoch [[-ry] low]";
+
+ force = 0;
+ remove = 0;
+ ARGBEGIN{
+ case 'y':
+ force = 1;
+ break;
+ case 'r':
+ remove = 1;
+ break;
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 1)
+ return cliError(usage);
+ if(argc > 0)
+ low = strtoul(argv[0], 0, 0);
+ else
+ low = ~(u32int)0;
+
+ if(low == 0)
+ return cliError("low epoch cannot be zero");
+
+ fs = fsys->fs;
+
+ vtRLock(fs->elk);
+ consPrint("\tlow %ud hi %ud\n", fs->elo, fs->ehi);
+ if(low == ~(u32int)0){
+ vtRUnlock(fs->elk);
+ return 1;
+ }
+ n = fsysEsearch(fsys->fs, "/archive", low);
+ n += fsysEsearch(fsys->fs, "/snapshot", low);
+ consPrint("\t%d snapshot%s found with epoch < %ud\n", n, n==1 ? "" : "s", low);
+ vtRUnlock(fs->elk);
+
+ /*
+ * There's a small race here -- a new snapshot with epoch < low might
+ * get introduced now that we unlocked fs->elk. Low has to
+ * be <= fs->ehi. Of course, in order for this to happen low has
+ * to be equal to the current fs->ehi _and_ a snapshot has to
+ * run right now. This is a small enough window that I don't care.
+ */
+ if(n != 0 && !force){
+ consPrint("\tnot setting low epoch\n");
+ return 1;
+ }
+ old = fs->elo;
+ if(!fsEpochLow(fs, low))
+ consPrint("\tfsEpochLow: %R\n");
+ else{
+ consPrint("\told: epoch%s %ud\n", force ? " -y" : "", old);
+ consPrint("\tnew: epoch%s %ud\n", force ? " -y" : "", fs->elo);
+ if(fs->elo < low)
+ consPrint("\twarning: new low epoch < old low epoch\n");
+ if(force && remove)
+ fsSnapshotRemove(fs);
+ }
+
+ return 1;
+}
+
+static int
+fsysCreate(Fsys* fsys, int argc, char* argv[])
+{
+ int r;
+ ulong mode;
+ char *elem, *p, *path;
+ char *usage = "usage: [fsys name] create path uid gid perm";
+ DirEntry de;
+ File *file, *parent;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc != 4)
+ return cliError(usage);
+
+ if(!fsysParseMode(argv[3], &mode))
+ return cliError(usage);
+ if(mode&ModeSnapshot)
+ return cliError("create - cannot create with snapshot bit set");
+
+ if(strcmp(argv[1], uidnoworld) == 0)
+ return cliError("permission denied");
+
+ vtRLock(fsys->fs->elk);
+ path = vtStrDup(argv[0]);
+ if((p = strrchr(path, '/')) != nil){
+ *p++ = '\0';
+ elem = p;
+ p = path;
+ if(*p == '\0')
+ p = "/";
+ }
+ else{
+ p = "/";
+ elem = path;
+ }
+
+ r = 0;
+ if((parent = fileOpen(fsys->fs, p)) == nil)
+ goto out;
+
+ file = fileCreate(parent, elem, mode, argv[1]);
+ fileDecRef(parent);
+ if(file == nil){
+ vtSetError("create %s/%s: %R", p, elem);
+ goto out;
+ }
+
+ if(!fileGetDir(file, &de)){
+ vtSetError("stat failed after create: %R");
+ goto out1;
+ }
+
+ if(strcmp(de.gid, argv[2]) != 0){
+ vtMemFree(de.gid);
+ de.gid = vtStrDup(argv[2]);
+ if(!fileSetDir(file, &de, argv[1])){
+ vtSetError("wstat failed after create: %R");
+ goto out2;
+ }
+ }
+ r = 1;
+
+out2:
+ deCleanup(&de);
+out1:
+ fileDecRef(file);
+out:
+ vtMemFree(path);
+ vtRUnlock(fsys->fs->elk);
+
+ return r;
+}
+
+static void
+fsysPrintStat(char *prefix, char *file, DirEntry *de)
+{
+ char buf[64];
+
+ if(prefix == nil)
+ prefix = "";
+ consPrint("%sstat %q %q %q %q %s %llud\n", prefix,
+ file, de->elem, de->uid, de->gid, fsysModeString(de->mode, buf), de->size);
+}
+
+static int
+fsysStat(Fsys* fsys, int argc, char* argv[])
+{
+ int i;
+ File *f;
+ DirEntry de;
+ char *usage = "usage: [fsys name] stat files...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc == 0)
+ return cliError(usage);
+
+ vtRLock(fsys->fs->elk);
+ for(i=0; i<argc; i++){
+ if((f = fileOpen(fsys->fs, argv[i])) == nil){
+ consPrint("%s: %R\n", argv[i]);
+ continue;
+ }
+ if(!fileGetDir(f, &de)){
+ consPrint("%s: %R\n", argv[i]);
+ fileDecRef(f);
+ continue;
+ }
+ fsysPrintStat("\t", argv[i], &de);
+ deCleanup(&de);
+ fileDecRef(f);
+ }
+ vtRUnlock(fsys->fs->elk);
+ return 1;
+}
+
+static int
+fsysWstat(Fsys *fsys, int argc, char* argv[])
+{
+ File *f;
+ char *p;
+ DirEntry de;
+ char *usage = "usage: [fsys name] wstat file elem uid gid mode length\n"
+ "\tuse - for any field to mean don't change";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc != 6)
+ return cliError(usage);
+
+ vtRLock(fsys->fs->elk);
+ if((f = fileOpen(fsys->fs, argv[0])) == nil){
+ vtSetError("console wstat - walk - %R");
+ vtRUnlock(fsys->fs->elk);
+ return 0;
+ }
+ if(!fileGetDir(f, &de)){
+ vtSetError("console wstat - stat - %R");
+ fileDecRef(f);
+ vtRUnlock(fsys->fs->elk);
+ return 0;
+ }
+ fsysPrintStat("\told: w", argv[0], &de);
+
+ if(strcmp(argv[1], "-") != 0){
+ if(!validFileName(argv[1])){
+ vtSetError("console wstat - bad elem");
+ goto error;
+ }
+ vtMemFree(de.elem);
+ de.elem = vtStrDup(argv[1]);
+ }
+ if(strcmp(argv[2], "-") != 0){
+ if(!validUserName(argv[2])){
+ vtSetError("console wstat - bad uid");
+ goto error;
+ }
+ vtMemFree(de.uid);
+ de.uid = vtStrDup(argv[2]);
+ }
+ if(strcmp(argv[3], "-") != 0){
+ if(!validUserName(argv[3])){
+ vtSetError("console wstat - bad gid");
+ goto error;
+ }
+ vtMemFree(de.gid);
+ de.gid = vtStrDup(argv[3]);
+ }
+ if(strcmp(argv[4], "-") != 0){
+ if(!fsysParseMode(argv[4], &de.mode)){
+ vtSetError("console wstat - bad mode");
+ goto error;
+ }
+ }
+ if(strcmp(argv[5], "-") != 0){
+ de.size = strtoull(argv[5], &p, 0);
+ if(argv[5][0] == '\0' || *p != '\0' || (vlong)de.size < 0){
+ vtSetError("console wstat - bad length");
+ goto error;
+ }
+ }
+
+ if(!fileSetDir(f, &de, uidadm)){
+ vtSetError("console wstat - %R");
+ goto error;
+ }
+ deCleanup(&de);
+
+ if(!fileGetDir(f, &de)){
+ vtSetError("console wstat - stat2 - %R");
+ goto error;
+ }
+ fsysPrintStat("\tnew: w", argv[0], &de);
+ deCleanup(&de);
+ fileDecRef(f);
+ vtRUnlock(fsys->fs->elk);
+
+ return 1;
+
+error:
+ deCleanup(&de); /* okay to do this twice */
+ fileDecRef(f);
+ vtRUnlock(fsys->fs->elk);
+ return 0;
+}
+
+static void
+fsckClri(Fsck *fsck, char *name, MetaBlock *mb, int i, Block *b)
+{
+ USED(name);
+
+ if((fsck->flags&DoClri) == 0)
+ return;
+
+ mbDelete(mb, i);
+ mbPack(mb);
+ blockDirty(b);
+}
+
+static void
+fsckClose(Fsck *fsck, Block *b, u32int epoch)
+{
+ Label l;
+
+ if((fsck->flags&DoClose) == 0)
+ return;
+ l = b->l;
+ if(l.state == BsFree || (l.state&BsClosed)){
+ consPrint("%#ux is already closed\n", b->addr);
+ return;
+ }
+ if(epoch){
+ l.state |= BsClosed;
+ l.epochClose = epoch;
+ }else
+ l.state = BsFree;
+
+ if(!blockSetLabel(b, &l, 0))
+ consPrint("%#ux setlabel: %R\n", b->addr);
+}
+
+static void
+fsckClre(Fsck *fsck, Block *b, int offset)
+{
+ Entry e;
+
+ if((fsck->flags&DoClre) == 0)
+ return;
+ if(offset<0 || offset*VtEntrySize >= fsck->bsize){
+ consPrint("bad clre\n");
+ return;
+ }
+ memset(&e, 0, sizeof e);
+ entryPack(&e, b->data, offset);
+ blockDirty(b);
+}
+
+static void
+fsckClrp(Fsck *fsck, Block *b, int offset)
+{
+ if((fsck->flags&DoClrp) == 0)
+ return;
+ if(offset<0 || offset*VtScoreSize >= fsck->bsize){
+ consPrint("bad clre\n");
+ return;
+ }
+ memmove(b->data+offset*VtScoreSize, vtZeroScore, VtScoreSize);
+ blockDirty(b);
+}
+
+static int
+fsysCheck(Fsys *fsys, int argc, char *argv[])
+{
+ int i, halting;
+ char *usage = "usage: [fsys name] check [-v] [options]";
+ Fsck fsck;
+ Block *b;
+ Super super;
+
+ memset(&fsck, 0, sizeof fsck);
+ fsck.fs = fsys->fs;
+ fsck.clri = fsckClri;
+ fsck.clre = fsckClre;
+ fsck.clrp = fsckClrp;
+ fsck.close = fsckClose;
+ fsck.print = consPrint;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ for(i=0; i<argc; i++){
+ if(strcmp(argv[i], "pblock") == 0)
+ fsck.printblocks = 1;
+ else if(strcmp(argv[i], "pdir") == 0)
+ fsck.printdirs = 1;
+ else if(strcmp(argv[i], "pfile") == 0)
+ fsck.printfiles = 1;
+ else if(strcmp(argv[i], "bclose") == 0)
+ fsck.flags |= DoClose;
+ else if(strcmp(argv[i], "clri") == 0)
+ fsck.flags |= DoClri;
+ else if(strcmp(argv[i], "clre") == 0)
+ fsck.flags |= DoClre;
+ else if(strcmp(argv[i], "clrp") == 0)
+ fsck.flags |= DoClrp;
+ else if(strcmp(argv[i], "fix") == 0)
+ fsck.flags |= DoClose|DoClri|DoClre|DoClrp;
+ else if(strcmp(argv[i], "venti") == 0)
+ fsck.useventi = 1;
+ else if(strcmp(argv[i], "snapshot") == 0)
+ fsck.walksnapshots = 1;
+ else{
+ consPrint("unknown option '%s'\n", argv[i]);
+ return cliError(usage);
+ }
+ }
+
+ halting = fsys->fs->halted==0;
+ if(halting)
+ fsHalt(fsys->fs);
+ if(fsys->fs->arch){
+ b = superGet(fsys->fs->cache, &super);
+ if(b == nil){
+ consPrint("could not load super block\n");
+ goto Out;
+ }
+ blockPut(b);
+ if(super.current != NilBlock){
+ consPrint("cannot check fs while archiver is running; "
+ "wait for it to finish\n");
+ goto Out;
+ }
+ }
+ fsCheck(&fsck);
+ consPrint("fsck: %d clri, %d clre, %d clrp, %d bclose\n",
+ fsck.nclri, fsck.nclre, fsck.nclrp, fsck.nclose);
+Out:
+ if(halting)
+ fsUnhalt(fsys->fs);
+ return 1;
+}
+
+static int
+fsysVenti(char* name, int argc, char* argv[])
+{
+ int r;
+ char *host;
+ char *usage = "usage: [fsys name] venti [address]";
+ Fsys *fsys;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc == 0)
+ host = nil;
+ else if(argc == 1)
+ host = argv[0];
+ else
+ return cliError(usage);
+
+ if((fsys = _fsysGet(name)) == nil)
+ return 0;
+
+ vtLock(fsys->lock);
+ if(host == nil)
+ host = fsys->venti;
+ else{
+ vtMemFree(fsys->venti);
+ if(host[0])
+ fsys->venti = vtStrDup(host);
+ else{
+ host = nil;
+ fsys->venti = nil;
+ }
+ }
+
+ /* already open: do a redial */
+ if(fsys->fs != nil){
+ if(fsys->session == nil){
+ vtSetError("file system was opened with -V");
+ r = 0;
+ goto out;
+ }
+ r = 1;
+ if(!myRedial(fsys->session, host)
+ || !vtConnect(fsys->session, 0))
+ r = 0;
+ goto out;
+ }
+
+ /* not yet open: try to dial */
+ if(fsys->session)
+ vtClose(fsys->session);
+ r = 1;
+ if((fsys->session = myDial(host, 0)) == nil
+ || !vtConnect(fsys->session, 0))
+ r = 0;
+out:
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+ return r;
+}
+
+static ulong
+freemem(void)
+{
+ int nf, pgsize = 0;
+ uvlong size, userpgs = 0, userused = 0;
+ char *ln, *sl;
+ char *fields[2];
+ Biobuf *bp;
+
+ size = 64*1024*1024;
+ bp = Bopen("#c/swap", OREAD);
+ if (bp != nil) {
+ while ((ln = Brdline(bp, '\n')) != nil) {
+ ln[Blinelen(bp)-1] = '\0';
+ nf = tokenize(ln, fields, nelem(fields));
+ if (nf != 2)
+ continue;
+ if (strcmp(fields[1], "pagesize") == 0)
+ pgsize = atoi(fields[0]);
+ else if (strcmp(fields[1], "user") == 0) {
+ sl = strchr(fields[0], '/');
+ if (sl == nil)
+ continue;
+ userpgs = atoll(sl+1);
+ userused = atoll(fields[0]);
+ }
+ }
+ Bterm(bp);
+ if (pgsize > 0 && userpgs > 0)
+ size = (userpgs - userused) * pgsize;
+ }
+ /* cap it to keep the size within 32 bits */
+ if (size >= 3840UL * 1024 * 1024)
+ size = 3840UL * 1024 * 1024;
+ return size;
+}
+
+static int
+fsysOpen(char* name, int argc, char* argv[])
+{
+ char *p, *host;
+ Fsys *fsys;
+ int noauth, noventi, noperm, rflag, wstatallow, noatimeupd;
+ long ncache;
+ char *usage = "usage: fsys name open [-APVWr] [-c ncache]";
+
+ ncache = 1000;
+ noauth = noperm = wstatallow = noventi = noatimeupd = 0;
+ rflag = OReadWrite;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'A':
+ noauth = 1;
+ break;
+ case 'P':
+ noperm = 1;
+ break;
+ case 'V':
+ noventi = 1;
+ break;
+ case 'W':
+ wstatallow = 1;
+ break;
+ case 'a':
+ noatimeupd = 1;
+ break;
+ case 'c':
+ p = ARGF();
+ if(p == nil)
+ return cliError(usage);
+ ncache = strtol(argv[0], &p, 0);
+ if(ncache <= 0 || p == argv[0] || *p != '\0')
+ return cliError(usage);
+ break;
+ case 'r':
+ rflag = OReadOnly;
+ break;
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ if((fsys = _fsysGet(name)) == nil)
+ return 0;
+
+ /* automatic memory sizing? */
+ if(mempcnt > 0) {
+ /* TODO: 8K is a hack; use the actual block size */
+ ncache = (((vlong)freemem() * mempcnt) / 100) / (8*1024);
+ if (ncache < 100)
+ ncache = 100;
+ }
+
+ vtLock(fsys->lock);
+ if(fsys->fs != nil){
+ vtSetError(EFsysBusy, fsys->name);
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+ return 0;
+ }
+
+ if(noventi){
+ if(fsys->session){
+ vtClose(fsys->session);
+ fsys->session = nil;
+ }
+ }
+ else if(fsys->session == nil){
+ if(fsys->venti && fsys->venti[0])
+ host = fsys->venti;
+ else
+ host = nil;
+ fsys->session = myDial(host, 1);
+ if(!vtConnect(fsys->session, nil) && !noventi)
+ fprint(2, "warning: connecting to venti: %R\n");
+ }
+ if((fsys->fs = fsOpen(fsys->dev, fsys->session, ncache, rflag)) == nil){
+ vtSetError("fsOpen: %R");
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+ return 0;
+ }
+ fsys->fs->name = fsys->name; /* for better error messages */
+ fsys->noauth = noauth;
+ fsys->noperm = noperm;
+ fsys->wstatallow = wstatallow;
+ fsys->fs->noatimeupd = noatimeupd;
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+
+ if(strcmp(name, "main") == 0)
+ usersFileRead(nil);
+
+ return 1;
+}
+
+static int
+fsysUnconfig(char* name, int argc, char* argv[])
+{
+ Fsys *fsys, **fp;
+ char *usage = "usage: fsys name unconfig";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ vtLock(sbox.lock);
+ fp = &sbox.head;
+ for(fsys = *fp; fsys != nil; fsys = fsys->next){
+ if(strcmp(fsys->name, name) == 0)
+ break;
+ fp = &fsys->next;
+ }
+ if(fsys == nil){
+ vtSetError(EFsysNotFound, name);
+ vtUnlock(sbox.lock);
+ return 0;
+ }
+ if(fsys->ref != 0 || fsys->fs != nil){
+ vtSetError(EFsysBusy, fsys->name);
+ vtUnlock(sbox.lock);
+ return 0;
+ }
+ *fp = fsys->next;
+ vtUnlock(sbox.lock);
+
+ if(fsys->session != nil){
+ vtClose(fsys->session);
+ vtFree(fsys->session);
+ }
+ if(fsys->venti != nil)
+ vtMemFree(fsys->venti);
+ if(fsys->dev != nil)
+ vtMemFree(fsys->dev);
+ if(fsys->name != nil)
+ vtMemFree(fsys->name);
+ vtMemFree(fsys);
+
+ return 1;
+}
+
+static int
+fsysConfig(char* name, int argc, char* argv[])
+{
+ Fsys *fsys;
+ char *part;
+ char *usage = "usage: fsys name config [dev]";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc > 1)
+ return cliError(usage);
+
+ if(argc == 0)
+ part = foptname;
+ else
+ part = argv[0];
+
+ if((fsys = _fsysGet(part)) != nil){
+ vtLock(fsys->lock);
+ if(fsys->fs != nil){
+ vtSetError(EFsysBusy, fsys->name);
+ vtUnlock(fsys->lock);
+ fsysPut(fsys);
+ return 0;
+ }
+ vtMemFree(fsys->dev);
+ fsys->dev = vtStrDup(part);
+ vtUnlock(fsys->lock);
+ }
+ else if((fsys = fsysAlloc(name, part)) == nil)
+ return 0;
+
+ fsysPut(fsys);
+ return 1;
+}
+
+static struct {
+ char* cmd;
+ int (*f)(Fsys*, int, char**);
+ int (*f1)(char*, int, char**);
+} fsyscmd[] = {
+ { "close", fsysClose, },
+ { "config", nil, fsysConfig, },
+ { "open", nil, fsysOpen, },
+ { "unconfig", nil, fsysUnconfig, },
+ { "venti", nil, fsysVenti, },
+
+ { "bfree", fsysBfree, },
+ { "block", fsysBlock, },
+ { "check", fsysCheck, },
+ { "clre", fsysClre, },
+ { "clri", fsysClri, },
+ { "clrp", fsysClrp, },
+ { "create", fsysCreate, },
+ { "df", fsysDf, },
+ { "epoch", fsysEpoch, },
+ { "halt", fsysHalt, },
+ { "label", fsysLabel, },
+ { "remove", fsysRemove, },
+ { "snap", fsysSnap, },
+ { "snaptime", fsysSnapTime, },
+ { "snapclean", fsysSnapClean, },
+ { "stat", fsysStat, },
+ { "sync", fsysSync, },
+ { "unhalt", fsysUnhalt, },
+ { "wstat", fsysWstat, },
+ { "vac", fsysVac, },
+
+ { nil, nil, },
+};
+
+static int
+fsysXXX1(Fsys *fsys, int i, int argc, char* argv[])
+{
+ int r;
+
+ vtLock(fsys->lock);
+ if(fsys->fs == nil){
+ vtUnlock(fsys->lock);
+ vtSetError(EFsysNotOpen, fsys->name);
+ return 0;
+ }
+
+ if(fsys->fs->halted
+ && fsyscmd[i].f != fsysUnhalt && fsyscmd[i].f != fsysCheck){
+ vtSetError("file system %s is halted", fsys->name);
+ vtUnlock(fsys->lock);
+ return 0;
+ }
+
+ r = (*fsyscmd[i].f)(fsys, argc, argv);
+ vtUnlock(fsys->lock);
+ return r;
+}
+
+static int
+fsysXXX(char* name, int argc, char* argv[])
+{
+ int i, r;
+ Fsys *fsys;
+
+ for(i = 0; fsyscmd[i].cmd != nil; i++){
+ if(strcmp(fsyscmd[i].cmd, argv[0]) == 0)
+ break;
+ }
+
+ if(fsyscmd[i].cmd == nil){
+ vtSetError("unknown command - '%s'", argv[0]);
+ return 0;
+ }
+
+ /* some commands want the name... */
+ if(fsyscmd[i].f1 != nil){
+ if(strcmp(name, FsysAll) == 0){
+ vtSetError("cannot use fsys %#q with %#q command", FsysAll, argv[0]);
+ return 0;
+ }
+ return (*fsyscmd[i].f1)(name, argc, argv);
+ }
+
+ /* ... but most commands want the Fsys */
+ if(strcmp(name, FsysAll) == 0){
+ r = 1;
+ vtRLock(sbox.lock);
+ for(fsys = sbox.head; fsys != nil; fsys = fsys->next){
+ fsys->ref++;
+ r = fsysXXX1(fsys, i, argc, argv) && r;
+ fsys->ref--;
+ }
+ vtRUnlock(sbox.lock);
+ }else{
+ if((fsys = _fsysGet(name)) == nil)
+ return 0;
+ r = fsysXXX1(fsys, i, argc, argv);
+ fsysPut(fsys);
+ }
+ return r;
+}
+
+static int
+cmdFsysXXX(int argc, char* argv[])
+{
+ char *name;
+
+ if((name = sbox.curfsys) == nil){
+ vtSetError(EFsysNoCurrent, argv[0]);
+ return 0;
+ }
+
+ return fsysXXX(name, argc, argv);
+}
+
+static int
+cmdFsys(int argc, char* argv[])
+{
+ Fsys *fsys;
+ char *usage = "usage: fsys [name ...]";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc == 0){
+ vtRLock(sbox.lock);
+ currfsysname = sbox.head->name;
+ for(fsys = sbox.head; fsys != nil; fsys = fsys->next)
+ consPrint("\t%s\n", fsys->name);
+ vtRUnlock(sbox.lock);
+ return 1;
+ }
+ if(argc == 1){
+ fsys = nil;
+ if(strcmp(argv[0], FsysAll) != 0 && (fsys = fsysGet(argv[0])) == nil)
+ return 0;
+ sbox.curfsys = vtStrDup(argv[0]);
+ consPrompt(sbox.curfsys);
+ if(fsys)
+ fsysPut(fsys);
+ return 1;
+ }
+
+ return fsysXXX(argv[0], argc-1, argv+1);
+}
+
+int
+fsysInit(void)
+{
+ int i;
+
+ fmtinstall('H', encodefmt);
+ fmtinstall('V', scoreFmt);
+ fmtinstall('R', vtErrFmt);
+ fmtinstall('L', labelFmt);
+
+ sbox.lock = vtLockAlloc();
+
+ cliAddCmd("fsys", cmdFsys);
+ for(i = 0; fsyscmd[i].cmd != nil; i++){
+ if(fsyscmd[i].f != nil)
+ cliAddCmd(fsyscmd[i].cmd, cmdFsysXXX);
+ }
+ /* the venti cmd is special: the fs can be either open or closed */
+ cliAddCmd("venti", cmdFsysXXX);
+ cliAddCmd("printconfig", cmdPrintConfig);
+
+ return 1;
+}
diff --git a/src/cmd/fossil/9lstn.c b/src/cmd/fossil/9lstn.c
new file mode 100644
index 00000000..9ba508f4
--- /dev/null
+++ b/src/cmd/fossil/9lstn.c
@@ -0,0 +1,184 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+typedef struct Lstn Lstn;
+struct Lstn {
+ int afd;
+ int flags;
+ char* address;
+ char dir[NETPATHLEN];
+
+ Lstn* next;
+ Lstn* prev;
+};
+
+static struct {
+ VtLock* lock;
+
+ Lstn* head;
+ Lstn* tail;
+} lbox;
+
+static void
+lstnFree(Lstn* lstn)
+{
+ vtLock(lbox.lock);
+ if(lstn->prev != nil)
+ lstn->prev->next = lstn->next;
+ else
+ lbox.head = lstn->next;
+ if(lstn->next != nil)
+ lstn->next->prev = lstn->prev;
+ else
+ lbox.tail = lstn->prev;
+ vtUnlock(lbox.lock);
+
+ if(lstn->afd != -1)
+ close(lstn->afd);
+ vtMemFree(lstn->address);
+ vtMemFree(lstn);
+}
+
+static void
+lstnListen(void* a)
+{
+ Lstn *lstn;
+ int dfd, lfd;
+ char newdir[NETPATHLEN];
+
+ vtThreadSetName("listen");
+
+ lstn = a;
+ for(;;){
+ if((lfd = listen(lstn->dir, newdir)) < 0){
+ fprint(2, "listen: listen '%s': %r", lstn->dir);
+ break;
+ }
+ if((dfd = accept(lfd, newdir)) >= 0)
+ conAlloc(dfd, newdir, lstn->flags);
+ else
+ fprint(2, "listen: accept %s: %r\n", newdir);
+ close(lfd);
+ }
+ lstnFree(lstn);
+}
+
+static Lstn*
+lstnAlloc(char* address, int flags)
+{
+ int afd;
+ Lstn *lstn;
+ char dir[NETPATHLEN];
+
+ vtLock(lbox.lock);
+ for(lstn = lbox.head; lstn != nil; lstn = lstn->next){
+ if(strcmp(lstn->address, address) != 0)
+ continue;
+ vtSetError("listen: already serving '%s'", address);
+ vtUnlock(lbox.lock);
+ return nil;
+ }
+
+ if((afd = announce(address, dir)) < 0){
+ vtSetError("listen: announce '%s': %r", address);
+ vtUnlock(lbox.lock);
+ return nil;
+ }
+
+ lstn = vtMemAllocZ(sizeof(Lstn));
+ lstn->afd = afd;
+ lstn->address = vtStrDup(address);
+ lstn->flags = flags;
+ memmove(lstn->dir, dir, NETPATHLEN);
+
+ if(lbox.tail != nil){
+ lstn->prev = lbox.tail;
+ lbox.tail->next = lstn;
+ }
+ else{
+ lbox.head = lstn;
+ lstn->prev = nil;
+ }
+ lbox.tail = lstn;
+ vtUnlock(lbox.lock);
+
+ if(vtThread(lstnListen, lstn) < 0){
+ vtSetError("listen: thread '%s': %r", lstn->address);
+ lstnFree(lstn);
+ return nil;
+ }
+
+ return lstn;
+}
+
+static int
+cmdLstn(int argc, char* argv[])
+{
+ int dflag, flags;
+ Lstn *lstn;
+ char *usage = "usage: listen [-dIN] [address]";
+
+ dflag = 0;
+ flags = 0;
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'd':
+ dflag = 1;
+ break;
+ case 'I':
+ flags |= ConIPCheck;
+ break;
+ case 'N':
+ flags |= ConNoneAllow;
+ break;
+ }ARGEND
+
+ switch(argc){
+ default:
+ return cliError(usage);
+ case 0:
+ vtRLock(lbox.lock);
+ for(lstn = lbox.head; lstn != nil; lstn = lstn->next)
+ consPrint("\t%s\t%s\n", lstn->address, lstn->dir);
+ vtRUnlock(lbox.lock);
+ break;
+ case 1:
+ if(!dflag){
+ if(lstnAlloc(argv[0], flags) == nil)
+ return 0;
+ break;
+ }
+
+ vtLock(lbox.lock);
+ for(lstn = lbox.head; lstn != nil; lstn = lstn->next){
+ if(strcmp(lstn->address, argv[0]) != 0)
+ continue;
+ if(lstn->afd != -1){
+ close(lstn->afd);
+ lstn->afd = -1;
+ }
+ break;
+ }
+ vtUnlock(lbox.lock);
+
+ if(lstn == nil){
+ vtSetError("listen: '%s' not found", argv[0]);
+ return 0;
+ }
+ break;
+ }
+
+ return 1;
+}
+
+int
+lstnInit(void)
+{
+ lbox.lock = vtLockAlloc();
+
+ cliAddCmd("listen", cmdLstn);
+
+ return 1;
+}
diff --git a/src/cmd/fossil/9p.c b/src/cmd/fossil/9p.c
new file mode 100644
index 00000000..c3fae5ad
--- /dev/null
+++ b/src/cmd/fossil/9p.c
@@ -0,0 +1,1181 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+enum {
+ OMODE = 0x7, /* Topen/Tcreate mode */
+};
+
+enum {
+ PermX = 1,
+ PermW = 2,
+ PermR = 4,
+};
+
+static char EPermission[] = "permission denied";
+
+static int
+permFile(File* file, Fid* fid, int perm)
+{
+ char *u;
+ DirEntry de;
+
+ if(!fileGetDir(file, &de))
+ return -1;
+
+ /*
+ * User none only gets other permissions.
+ */
+ if(strcmp(fid->uname, unamenone) != 0){
+ /*
+ * There is only one uid<->uname mapping
+ * and it's already cached in the Fid, but
+ * it might have changed during the lifetime
+ * if this Fid.
+ */
+ if((u = unameByUid(de.uid)) != nil){
+ if(strcmp(fid->uname, u) == 0 && ((perm<<6) & de.mode)){
+ vtMemFree(u);
+ deCleanup(&de);
+ return 1;
+ }
+ vtMemFree(u);
+ }
+ if(groupMember(de.gid, fid->uname) && ((perm<<3) & de.mode)){
+ deCleanup(&de);
+ return 1;
+ }
+ }
+ if(perm & de.mode){
+ if(perm == PermX && (de.mode & ModeDir)){
+ deCleanup(&de);
+ return 1;
+ }
+ if(!groupMember(uidnoworld, fid->uname)){
+ deCleanup(&de);
+ return 1;
+ }
+ }
+ if(fsysNoPermCheck(fid->fsys) || (fid->con->flags&ConNoPermCheck)){
+ deCleanup(&de);
+ return 1;
+ }
+ vtSetError(EPermission);
+
+ deCleanup(&de);
+ return 0;
+}
+
+static int
+permFid(Fid* fid, int p)
+{
+ return permFile(fid->file, fid, p);
+}
+
+static int
+permParent(Fid* fid, int p)
+{
+ int r;
+ File *parent;
+
+ parent = fileGetParent(fid->file);
+ r = permFile(parent, fid, p);
+ fileDecRef(parent);
+
+ return r;
+}
+
+int
+validFileName(char* name)
+{
+ char *p;
+
+ if(name == nil || name[0] == '\0'){
+ vtSetError("no file name");
+ return 0;
+ }
+ if(name[0] == '.'){
+ if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){
+ vtSetError(". and .. illegal as file name");
+ return 0;
+ }
+ }
+
+ for(p = name; *p != '\0'; p++){
+ if((*p & 0xFF) < 040){
+ vtSetError("bad character in file name");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+rTwstat(Msg* m)
+{
+ Dir dir;
+ Fid *fid;
+ ulong mode, oldmode;
+ DirEntry de;
+ char *gid, *strs, *uid;
+ int gl, op, retval, tsync, wstatallow;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
+ return 0;
+
+ gid = uid = nil;
+ retval = 0;
+
+ if(strcmp(fid->uname, unamenone) == 0 || (fid->qid.type & QTAUTH)){
+ vtSetError(EPermission);
+ goto error0;
+ }
+ if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
+ vtSetError("read-only filesystem");
+ goto error0;
+ }
+
+ if(!fileGetDir(fid->file, &de))
+ goto error0;
+
+ strs = vtMemAlloc(m->t.nstat);
+ if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){
+ vtSetError("wstat -- protocol botch");
+ goto error;
+ }
+
+ /*
+ * Run through each of the (sub-)fields in the provided Dir
+ * checking for validity and whether it's a default:
+ * .type, .dev and .atime are completely ignored and not checked;
+ * .qid.path, .qid.vers and .muid are checked for validity but
+ * any attempt to change them is an error.
+ * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
+ * possibly be changed.
+ *
+ * 'Op' flags there are changed fields, i.e. it's not a no-op.
+ * 'Tsync' flags all fields are defaulted.
+ */
+ tsync = 1;
+ if(dir.qid.path != ~0){
+ if(dir.qid.path != de.qid){
+ vtSetError("wstat -- attempt to change qid.path");
+ goto error;
+ }
+ tsync = 0;
+ }
+ if(dir.qid.vers != ~0){
+ if(dir.qid.vers != de.mcount){
+ vtSetError("wstat -- attempt to change qid.vers");
+ goto error;
+ }
+ tsync = 0;
+ }
+ if(dir.muid != nil && *dir.muid != '\0'){
+ if((uid = uidByUname(dir.muid)) == nil){
+ vtSetError("wstat -- unknown muid");
+ goto error;
+ }
+ if(strcmp(uid, de.mid) != 0){
+ vtSetError("wstat -- attempt to change muid");
+ goto error;
+ }
+ vtMemFree(uid);
+ uid = nil;
+ tsync = 0;
+ }
+
+ /*
+ * Check .qid.type and .mode agree if neither is defaulted.
+ */
+ if(dir.qid.type != (uchar)~0 && dir.mode != ~0){
+ if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
+ vtSetError("wstat -- qid.type/mode mismatch");
+ goto error;
+ }
+ }
+
+ op = 0;
+
+ oldmode = de.mode;
+ if(dir.qid.type != (uchar)~0 || dir.mode != ~0){
+ /*
+ * .qid.type or .mode isn't defaulted, check for unknown bits.
+ */
+ if(dir.mode == ~0)
+ dir.mode = (dir.qid.type<<24)|(de.mode & 0777);
+ if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|DMTMP|0777)){
+ vtSetError("wstat -- unknown bits in qid.type/mode");
+ goto error;
+ }
+
+ /*
+ * Synthesise a mode to check against the current settings.
+ */
+ mode = dir.mode & 0777;
+ if(dir.mode & DMEXCL)
+ mode |= ModeExclusive;
+ if(dir.mode & DMAPPEND)
+ mode |= ModeAppend;
+ if(dir.mode & DMDIR)
+ mode |= ModeDir;
+ if(dir.mode & DMTMP)
+ mode |= ModeTemporary;
+
+ if((de.mode^mode) & ModeDir){
+ vtSetError("wstat -- attempt to change directory bit");
+ goto error;
+ }
+
+ if((de.mode & (ModeAppend|ModeExclusive|ModeTemporary|0777)) != mode){
+ de.mode &= ~(ModeAppend|ModeExclusive|ModeTemporary|0777);
+ de.mode |= mode;
+ op = 1;
+ }
+ tsync = 0;
+ }
+
+ if(dir.mtime != ~0){
+ if(dir.mtime != de.mtime){
+ de.mtime = dir.mtime;
+ op = 1;
+ }
+ tsync = 0;
+ }
+
+ if(dir.length != ~0){
+ if(dir.length != de.size){
+ /*
+ * Cannot change length on append-only files.
+ * If we're changing the append bit, it's okay.
+ */
+ if(de.mode & oldmode & ModeAppend){
+ vtSetError("wstat -- attempt to change length of append-only file");
+ goto error;
+ }
+ if(de.mode & ModeDir){
+ vtSetError("wstat -- attempt to change length of directory");
+ goto error;
+ }
+ de.size = dir.length;
+ op = 1;
+ }
+ tsync = 0;
+ }
+
+ /*
+ * Check for permission to change .mode, .mtime or .length,
+ * must be owner or leader of either group, for which test gid
+ * is needed; permission checks on gid will be done later.
+ */
+ if(dir.gid != nil && *dir.gid != '\0'){
+ if((gid = uidByUname(dir.gid)) == nil){
+ vtSetError("wstat -- unknown gid");
+ goto error;
+ }
+ tsync = 0;
+ }
+ else
+ gid = vtStrDup(de.gid);
+
+ wstatallow = (fsysWstatAllow(fid->fsys) || (m->con->flags&ConWstatAllow));
+
+ /*
+ * 'Gl' counts whether neither, one or both groups are led.
+ */
+ gl = groupLeader(gid, fid->uname) != 0;
+ gl += groupLeader(de.gid, fid->uname) != 0;
+
+ if(op && !wstatallow){
+ if(strcmp(fid->uid, de.uid) != 0 && !gl){
+ vtSetError("wstat -- not owner or group leader");
+ goto error;
+ }
+ }
+
+ /*
+ * Check for permission to change group, must be
+ * either owner and in new group or leader of both groups.
+ * If gid is nil here then
+ */
+ if(strcmp(gid, de.gid) != 0){
+ if(!wstatallow
+ && !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname))
+ && !(gl == 2)){
+ vtSetError("wstat -- not owner and not group leaders");
+ goto error;
+ }
+ vtMemFree(de.gid);
+ de.gid = gid;
+ gid = nil;
+ op = 1;
+ tsync = 0;
+ }
+
+ /*
+ * Rename.
+ * Check .name is valid and different to the current.
+ * If so, check write permission in parent.
+ */
+ if(dir.name != nil && *dir.name != '\0'){
+ if(!validFileName(dir.name))
+ goto error;
+ if(strcmp(dir.name, de.elem) != 0){
+ if(permParent(fid, PermW) <= 0)
+ goto error;
+ vtMemFree(de.elem);
+ de.elem = vtStrDup(dir.name);
+ op = 1;
+ }
+ tsync = 0;
+ }
+
+ /*
+ * Check for permission to change owner - must be god.
+ */
+ if(dir.uid != nil && *dir.uid != '\0'){
+ if((uid = uidByUname(dir.uid)) == nil){
+ vtSetError("wstat -- unknown uid");
+ goto error;
+ }
+ if(strcmp(uid, de.uid) != 0){
+ if(!wstatallow){
+ vtSetError("wstat -- not owner");
+ goto error;
+ }
+ if(strcmp(uid, uidnoworld) == 0){
+ vtSetError(EPermission);
+ goto error;
+ }
+ vtMemFree(de.uid);
+ de.uid = uid;
+ uid = nil;
+ op = 1;
+ }
+ tsync = 0;
+ }
+
+ if(op)
+ retval = fileSetDir(fid->file, &de, fid->uid);
+ else
+ retval = 1;
+
+ if(tsync){
+ /*
+ * All values were defaulted,
+ * make the state of the file exactly what it
+ * claims to be before returning...
+ */
+ USED(tsync);
+ }
+
+error:
+ deCleanup(&de);
+ vtMemFree(strs);
+ if(gid != nil)
+ vtMemFree(gid);
+ if(uid != nil)
+ vtMemFree(uid);
+error0:
+ fidPut(fid);
+ return retval;
+};
+
+static int
+rTstat(Msg* m)
+{
+ Dir dir;
+ Fid *fid;
+ DirEntry de;
+
+ if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
+ return 0;
+ if(fid->qid.type & QTAUTH){
+ memset(&dir, 0, sizeof(Dir));
+ dir.qid = fid->qid;
+ dir.mode = DMAUTH;
+ dir.atime = time(0L);
+ dir.mtime = dir.atime;
+ dir.length = 0;
+ dir.name = "#¿";
+ dir.uid = fid->uname;
+ dir.gid = fid->uname;
+ dir.muid = fid->uname;
+
+ if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
+ vtSetError("stat QTAUTH botch");
+ fidPut(fid);
+ return 0;
+ }
+ m->r.stat = m->data;
+
+ fidPut(fid);
+ return 1;
+ }
+ if(!fileGetDir(fid->file, &de)){
+ fidPut(fid);
+ return 0;
+ }
+ fidPut(fid);
+
+ /*
+ * TODO: optimise this copy (in convS2M) away somehow.
+ * This pettifoggery with m->data will do for the moment.
+ */
+ m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
+ m->r.stat = m->data;
+ deCleanup(&de);
+
+ return 1;
+}
+
+static int
+_rTclunk(Fid* fid, int remove)
+{
+ int rok;
+
+ if(fid->excl)
+ exclFree(fid);
+
+ rok = 1;
+ if(remove && !(fid->qid.type & QTAUTH)){
+ if((rok = permParent(fid, PermW)) > 0)
+ rok = fileRemove(fid->file, fid->uid);
+ }
+ fidClunk(fid);
+
+ return rok;
+}
+
+static int
+rTremove(Msg* m)
+{
+ Fid *fid;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
+ return 0;
+ return _rTclunk(fid, 1);
+}
+
+static int
+rTclunk(Msg* m)
+{
+ Fid *fid;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
+ return 0;
+ _rTclunk(fid, (fid->open & FidORclose));
+
+ return 1;
+}
+
+static int
+rTwrite(Msg* m)
+{
+ Fid *fid;
+ int count, n;
+
+ if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
+ return 0;
+ if(!(fid->open & FidOWrite)){
+ vtSetError("fid not open for write");
+ goto error;
+ }
+
+ count = m->t.count;
+ if(count < 0 || count > m->con->msize-IOHDRSZ){
+ vtSetError("write count too big");
+ goto error;
+ }
+ if(m->t.offset < 0){
+ vtSetError("write offset negative");
+ goto error;
+ }
+ if(fid->excl != nil && !exclUpdate(fid))
+ goto error;
+
+ if(fid->qid.type & QTDIR){
+ vtSetError("is a directory");
+ goto error;
+ }
+ else if(fid->qid.type & QTAUTH)
+ n = authWrite(fid, m->t.data, count);
+ else
+ n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
+ if(n < 0)
+ goto error;
+
+
+ m->r.count = n;
+
+ fidPut(fid);
+ return 1;
+
+error:
+ fidPut(fid);
+ return 0;
+}
+
+static int
+rTread(Msg* m)
+{
+ Fid *fid;
+ uchar *data;
+ int count, n;
+
+ if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
+ return 0;
+ if(!(fid->open & FidORead)){
+ vtSetError("fid not open for read");
+ goto error;
+ }
+
+ count = m->t.count;
+ if(count < 0 || count > m->con->msize-IOHDRSZ){
+ vtSetError("read count too big");
+ goto error;
+ }
+ if(m->t.offset < 0){
+ vtSetError("read offset negative");
+ goto error;
+ }
+ if(fid->excl != nil && !exclUpdate(fid))
+ goto error;
+
+ /*
+ * TODO: optimise this copy (in convS2M) away somehow.
+ * This pettifoggery with m->data will do for the moment.
+ */
+ data = m->data+IOHDRSZ;
+ if(fid->qid.type & QTDIR)
+ n = dirRead(fid, data, count, m->t.offset);
+ else if(fid->qid.type & QTAUTH)
+ n = authRead(fid, data, count);
+ else
+ n = fileRead(fid->file, data, count, m->t.offset);
+ if(n < 0)
+ goto error;
+
+ m->r.count = n;
+ m->r.data = (char*)data;
+
+ fidPut(fid);
+ return 1;
+
+error:
+ fidPut(fid);
+ return 0;
+}
+
+static int
+rTcreate(Msg* m)
+{
+ Fid *fid;
+ File *file;
+ ulong mode;
+ int omode, open, perm;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
+ return 0;
+ if(fid->open){
+ vtSetError("fid open for I/O");
+ goto error;
+ }
+ if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
+ vtSetError("read-only filesystem");
+ goto error;
+ }
+ if(!fileIsDir(fid->file)){
+ vtSetError("not a directory");
+ goto error;
+ }
+ if(permFid(fid, PermW) <= 0)
+ goto error;
+ if(!validFileName(m->t.name))
+ goto error;
+ if(strcmp(fid->uid, uidnoworld) == 0){
+ vtSetError(EPermission);
+ goto error;
+ }
+
+ omode = m->t.mode & OMODE;
+ open = 0;
+
+ if(omode == OREAD || omode == ORDWR || omode == OEXEC)
+ open |= FidORead;
+ if(omode == OWRITE || omode == ORDWR)
+ open |= FidOWrite;
+ if((open & (FidOWrite|FidORead)) == 0){
+ vtSetError("unknown mode");
+ goto error;
+ }
+ if(m->t.perm & DMDIR){
+ if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
+ vtSetError("illegal mode");
+ goto error;
+ }
+ if(m->t.perm & DMAPPEND){
+ vtSetError("illegal perm");
+ goto error;
+ }
+ }
+
+ mode = fileGetMode(fid->file);
+ perm = m->t.perm;
+ if(m->t.perm & DMDIR)
+ perm &= ~0777|(mode & 0777);
+ else
+ perm &= ~0666|(mode & 0666);
+ mode = perm & 0777;
+ if(m->t.perm & DMDIR)
+ mode |= ModeDir;
+ if(m->t.perm & DMAPPEND)
+ mode |= ModeAppend;
+ if(m->t.perm & DMEXCL)
+ mode |= ModeExclusive;
+ if(m->t.perm & DMTMP)
+ mode |= ModeTemporary;
+
+ if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
+ fidPut(fid);
+ return 0;
+ }
+ fileDecRef(fid->file);
+
+ fid->qid.vers = fileGetMcount(file);
+ fid->qid.path = fileGetId(file);
+ fid->file = file;
+ mode = fileGetMode(fid->file);
+ if(mode & ModeDir)
+ fid->qid.type = QTDIR;
+ else
+ fid->qid.type = QTFILE;
+ if(mode & ModeAppend)
+ fid->qid.type |= QTAPPEND;
+ if(mode & ModeExclusive){
+ fid->qid.type |= QTEXCL;
+ assert(exclAlloc(fid) != 0);
+ }
+ if(m->t.mode & ORCLOSE)
+ open |= FidORclose;
+ fid->open = open;
+
+ m->r.qid = fid->qid;
+ m->r.iounit = m->con->msize-IOHDRSZ;
+
+ fidPut(fid);
+ return 1;
+
+error:
+ fidPut(fid);
+ return 0;
+}
+
+static int
+rTopen(Msg* m)
+{
+ Fid *fid;
+ int isdir, mode, omode, open, rofs;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
+ return 0;
+ if(fid->open){
+ vtSetError("fid open for I/O");
+ goto error;
+ }
+
+ isdir = fileIsDir(fid->file);
+ open = 0;
+ rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
+
+ if(m->t.mode & ORCLOSE){
+ if(isdir){
+ vtSetError("is a directory");
+ goto error;
+ }
+ if(rofs){
+ vtSetError("read-only filesystem");
+ goto error;
+ }
+ if(permParent(fid, PermW) <= 0)
+ goto error;
+
+ open |= FidORclose;
+ }
+
+ omode = m->t.mode & OMODE;
+ if(omode == OREAD || omode == ORDWR){
+ if(permFid(fid, PermR) <= 0)
+ goto error;
+ open |= FidORead;
+ }
+ if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
+ if(isdir){
+ vtSetError("is a directory");
+ goto error;
+ }
+ if(rofs){
+ vtSetError("read-only filesystem");
+ goto error;
+ }
+ if(permFid(fid, PermW) <= 0)
+ goto error;
+ open |= FidOWrite;
+ }
+ if(omode == OEXEC){
+ if(isdir){
+ vtSetError("is a directory");
+ goto error;
+ }
+ if(permFid(fid, PermX) <= 0)
+ goto error;
+ open |= FidORead;
+ }
+ if((open & (FidOWrite|FidORead)) == 0){
+ vtSetError("unknown mode");
+ goto error;
+ }
+
+ mode = fileGetMode(fid->file);
+ if((mode & ModeExclusive) && exclAlloc(fid) == 0)
+ goto error;
+
+ /*
+ * Everything checks out, try to commit any changes.
+ */
+ if((m->t.mode & OTRUNC) && !(mode & ModeAppend))
+ if(!fileTruncate(fid->file, fid->uid))
+ goto error;
+
+ if(isdir && fid->db != nil){
+ dirBufFree(fid->db);
+ fid->db = nil;
+ }
+
+ fid->qid.vers = fileGetMcount(fid->file);
+ m->r.qid = fid->qid;
+ m->r.iounit = m->con->msize-IOHDRSZ;
+
+ fid->open = open;
+
+ fidPut(fid);
+ return 1;
+
+error:
+ if(fid->excl != nil)
+ exclFree(fid);
+ fidPut(fid);
+ return 0;
+}
+
+static int
+rTwalk(Msg* m)
+{
+ Qid qid;
+ Fcall *r, *t;
+ int nwname, wlock;
+ File *file, *nfile;
+ Fid *fid, *ofid, *nfid;
+
+ t = &m->t;
+ if(t->fid == t->newfid)
+ wlock = FidFWlock;
+ else
+ wlock = 0;
+
+ /*
+ * 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((ofid = fidGet(m->con, t->fid, wlock)) == nil)
+ return 0;
+ if(ofid->open){
+ vtSetError("file open for I/O");
+ fidPut(ofid);
+ return 0;
+ }
+
+ /*
+ * 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.
+ * It's a no-op if newfid is the same as fid and t->nwname is 0.
+ */
+ nfid = nil;
+ if(t->fid != t->newfid){
+ nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
+ if(nfid == nil){
+ vtSetError("%s: walk: newfid 0x%ud in use",
+ argv0, t->newfid);
+ fidPut(ofid);
+ return 0;
+ }
+ nfid->open = ofid->open & ~FidORclose;
+ nfid->file = fileIncRef(ofid->file);
+ nfid->qid = ofid->qid;
+ nfid->uid = vtStrDup(ofid->uid);
+ nfid->uname = vtStrDup(ofid->uname);
+ nfid->fsys = fsysIncRef(ofid->fsys);
+ fid = nfid;
+ }
+ else
+ fid = ofid;
+
+ r = &m->r;
+ r->nwqid = 0;
+
+ if(t->nwname == 0){
+ if(nfid != nil)
+ fidPut(nfid);
+ fidPut(ofid);
+
+ return 1;
+ }
+
+ file = fid->file;
+ fileIncRef(file);
+ qid = fid->qid;
+
+ for(nwname = 0; nwname < t->nwname; nwname++){
+ /*
+ * Walked elements must represent a directory and
+ * the implied user must have permission to search
+ * the directory. Walking .. is always allowed, so that
+ * you can't walk into a directory and then not be able
+ * to walk out of it.
+ */
+ if(!(qid.type & QTDIR)){
+ vtSetError("not a directory");
+ break;
+ }
+ switch(permFile(file, fid, PermX)){
+ case 1:
+ break;
+ case 0:
+ if(strcmp(t->wname[nwname], "..") == 0)
+ break;
+ case -1:
+ goto Out;
+ }
+ if((nfile = fileWalk(file, t->wname[nwname])) == nil)
+ break;
+ fileDecRef(file);
+ file = nfile;
+ qid.type = QTFILE;
+ if(fileIsDir(file))
+ qid.type = QTDIR;
+ if(fileIsAppend(file))
+ qid.type |= QTAPPEND;
+ if(fileIsTemporary(file))
+ qid.type |= QTTMP;
+ if(fileIsExclusive(file))
+ qid.type |= QTEXCL;
+ qid.vers = fileGetMcount(file);
+ qid.path = fileGetId(file);
+ r->wqid[r->nwqid++] = qid;
+ }
+
+ if(nwname == t->nwname){
+ /*
+ * Walked all elements. Update the target fid
+ * from the temporary qid used during the walk,
+ * and tidy up.
+ */
+ fid->qid = r->wqid[r->nwqid-1];
+ fileDecRef(fid->file);
+ fid->file = file;
+
+ if(nfid != nil)
+ fidPut(nfid);
+
+ fidPut(ofid);
+ return 1;
+ }
+
+Out:
+ /*
+ * Didn't walk all elements, 'clunk' nfid if it exists
+ * and leave fid untouched.
+ * It's not an error if some of the elements were walked OK.
+ */
+ fileDecRef(file);
+ if(nfid != nil)
+ fidClunk(nfid);
+
+ fidPut(ofid);
+ if(nwname == 0)
+ return 0;
+ return 1;
+}
+
+static int
+rTflush(Msg* m)
+{
+ if(m->t.oldtag != NOTAG)
+ msgFlush(m);
+ return 1;
+}
+
+static void
+parseAname(char *aname, char **fsname, char **path)
+{
+ char *s;
+
+ if(aname && aname[0])
+ s = vtStrDup(aname);
+ else
+ s = vtStrDup("main/active");
+ *fsname = s;
+ if((*path = strchr(s, '/')) != nil)
+ *(*path)++ = '\0';
+ else
+ *path = "";
+}
+
+/*
+ * Check remote IP address against /mnt/ipok.
+ * Sources.cs.bell-labs.com uses this to disallow
+ * network connections from Sudan, Libya, etc.,
+ * following U.S. cryptography export regulations.
+ */
+static int
+conIPCheck(Con* con)
+{
+ char ok[256], *p;
+ int fd;
+
+ if(con->flags&ConIPCheck){
+ if(con->remote[0] == 0){
+ vtSetError("cannot verify unknown remote address");
+ return 0;
+ }
+ if(access("/mnt/ipok/ok", AEXIST) < 0){
+ /* mount closes the fd on success */
+ if((fd = open("/srv/ipok", ORDWR)) >= 0
+ && mount(fd, -1, "/mnt/ipok", MREPL, "") < 0)
+ close(fd);
+ if(access("/mnt/ipok/ok", AEXIST) < 0){
+ vtSetError("cannot verify remote address");
+ return 0;
+ }
+ }
+ snprint(ok, sizeof ok, "/mnt/ipok/ok/%s", con->remote);
+ if((p = strchr(ok, '!')) != nil)
+ *p = 0;
+ if(access(ok, AEXIST) < 0){
+ vtSetError("restricted remote address");
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+rTattach(Msg* m)
+{
+ Fid *fid;
+ Fsys *fsys;
+ char *fsname, *path;
+
+ if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
+ return 0;
+
+ parseAname(m->t.aname, &fsname, &path);
+ if((fsys = fsysGet(fsname)) == nil){
+ fidClunk(fid);
+ vtMemFree(fsname);
+ return 0;
+ }
+ fid->fsys = fsys;
+
+ if(m->t.uname[0] != '\0')
+ fid->uname = vtStrDup(m->t.uname);
+ else
+ fid->uname = vtStrDup(unamenone);
+
+ if((fid->con->flags&ConIPCheck) && !conIPCheck(fid->con)){
+ consPrint("reject %s from %s: %R\n", fid->uname, fid->con->remote);
+ fidClunk(fid);
+ vtMemFree(fsname);
+ return 0;
+ }
+ if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
+ if((fid->uid = uidByUname(fid->uname)) == nil)
+ fid->uid = vtStrDup(unamenone);
+ }
+ else if(!authCheck(&m->t, fid, fsys)){
+ fidClunk(fid);
+ vtMemFree(fsname);
+ return 0;
+ }
+
+ fsysFsRlock(fsys);
+ if((fid->file = fsysGetRoot(fsys, path)) == nil){
+ fsysFsRUnlock(fsys);
+ fidClunk(fid);
+ vtMemFree(fsname);
+ return 0;
+ }
+ fsysFsRUnlock(fsys);
+ vtMemFree(fsname);
+
+ fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR};
+ m->r.qid = fid->qid;
+
+ fidPut(fid);
+ return 1;
+}
+
+static int
+rTauth(Msg* m)
+{
+ int afd;
+ Con *con;
+ Fid *afid;
+ Fsys *fsys;
+ char *fsname, *path;
+
+ parseAname(m->t.aname, &fsname, &path);
+ if((fsys = fsysGet(fsname)) == nil){
+ vtMemFree(fsname);
+ return 0;
+ }
+ vtMemFree(fsname);
+
+ if(fsysNoAuthCheck(fsys) || (m->con->flags&ConNoAuthCheck)){
+ m->con->aok = 1;
+ vtSetError("authentication disabled");
+ fsysPut(fsys);
+ return 0;
+ }
+ if(strcmp(m->t.uname, unamenone) == 0){
+ vtSetError("user 'none' requires no authentication");
+ fsysPut(fsys);
+ return 0;
+ }
+
+ con = m->con;
+ if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
+ fsysPut(fsys);
+ return 0;
+ }
+ afid->fsys = fsys;
+
+ if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
+ vtSetError("can't open \"/mnt/factotum/rpc\"");
+ fidClunk(afid);
+ return 0;
+ }
+ if((afid->rpc = auth_allocrpc(afd)) == nil){
+ close(afd);
+ vtSetError("can't auth_allocrpc");
+ fidClunk(afid);
+ return 0;
+ }
+ if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
+ vtSetError("can't auth_rpc");
+ fidClunk(afid);
+ return 0;
+ }
+
+ afid->open = FidOWrite|FidORead;
+ afid->qid.type = QTAUTH;
+ afid->qid.path = m->t.afid;
+ afid->uname = vtStrDup(m->t.uname);
+
+ m->r.qid = afid->qid;
+
+ fidPut(afid);
+ return 1;
+}
+
+static int
+rTversion(Msg* m)
+{
+ int v;
+ Con *con;
+ Fcall *r, *t;
+
+ t = &m->t;
+ r = &m->r;
+ con = m->con;
+
+ vtLock(con->lock);
+ if(con->state != ConInit){
+ vtUnlock(con->lock);
+ vtSetError("Tversion: down");
+ return 0;
+ }
+ con->state = ConNew;
+
+ /*
+ * Release the karma of past lives and suffering.
+ * Should this be done before or after checking the
+ * validity of the Tversion?
+ */
+ fidClunkAll(con);
+
+ if(t->tag != NOTAG){
+ vtUnlock(con->lock);
+ vtSetError("Tversion: invalid tag");
+ return 0;
+ }
+
+ if(t->msize < 256){
+ vtUnlock(con->lock);
+ vtSetError("Tversion: message size too small");
+ return 0;
+ }
+ if(t->msize < con->msize)
+ r->msize = t->msize;
+ else
+ r->msize = con->msize;
+
+ r->version = "unknown";
+ if(t->version[0] == '9' && t->version[1] == 'P'){
+ /*
+ * Currently, the only defined version
+ * is "9P2000"; ignore any later versions.
+ */
+ v = strtol(&t->version[2], 0, 10);
+ if(v >= 2000){
+ r->version = VERSION9P;
+ con->msize = r->msize;
+ con->state = ConUp;
+ }
+ else if(strcmp(t->version, "9PEoF") == 0){
+ r->version = "9PEoF";
+ con->msize = r->msize;
+ con->state = ConMoribund;
+
+ /*
+ * Don't want to attempt to write this
+ * message as the connection may be already
+ * closed.
+ */
+ m->state = MsgF;
+ }
+ }
+ vtUnlock(con->lock);
+
+ return 1;
+}
+
+int (*rFcall[Tmax])(Msg*) = {
+ [Tversion] = rTversion,
+ [Tauth] = rTauth,
+ [Tattach] = rTattach,
+ [Tflush] = rTflush,
+ [Twalk] = rTwalk,
+ [Topen] = rTopen,
+ [Tcreate] = rTcreate,
+ [Tread] = rTread,
+ [Twrite] = rTwrite,
+ [Tclunk] = rTclunk,
+ [Tremove] = rTremove,
+ [Tstat] = rTstat,
+ [Twstat] = rTwstat,
+};
diff --git a/src/cmd/fossil/9ping.c b/src/cmd/fossil/9ping.c
new file mode 100644
index 00000000..a21eea9b
--- /dev/null
+++ b/src/cmd/fossil/9ping.c
@@ -0,0 +1,109 @@
+#include <u.h>
+#include <libc.h>
+
+typedef uvlong u64int;
+
+#define TWID64 ((u64int)~(u64int)0)
+
+
+u64int
+unittoull(char *s)
+{
+ char *es;
+ u64int n;
+
+ if(s == nil)
+ return TWID64;
+ n = strtoul(s, &es, 0);
+ if(*es == 'k' || *es == 'K'){
+ n *= 1024;
+ es++;
+ }else if(*es == 'm' || *es == 'M'){
+ n *= 1024*1024;
+ es++;
+ }else if(*es == 'g' || *es == 'G'){
+ n *= 1024*1024*1024;
+ es++;
+ }
+ if(*es != '\0')
+ return TWID64;
+ return n;
+}
+
+void
+main(int argc, char *argv[])
+{
+ int fd, i;
+ int n = 1000, m;
+ int s = 1;
+ double *t, t0, t1;
+ uchar *buf;
+ double a, d, max, min;
+
+ m = OREAD;
+ ARGBEGIN{
+ case 'n':
+ n = atoi(ARGF());
+ break;
+ case 's':
+ s = unittoull(ARGF());
+ if(s < 1 || s > 1024*1024)
+ sysfatal("bad size");
+ break;
+ case 'r':
+ m = OREAD;
+ break;
+ case 'w':
+ m = OWRITE;
+ break;
+ }ARGEND
+
+ fd = 0;
+ if(argc == 1){
+ fd = open(argv[0], m);
+ if(fd < 0)
+ sysfatal("could not open file: %s: %r", argv[0]);
+ }
+
+ buf = malloc(s);
+ t = malloc(n*sizeof(double));
+
+ t0 = nsec();
+ for(i=0; i<n; i++){
+ if(m == OREAD){
+ if(pread(fd, buf, s, 0) < s)
+ sysfatal("bad read: %r");
+ }else{
+ if(pwrite(fd, buf, s, 0) < s)
+ sysfatal("bad write: %r");
+ }
+ t1 = nsec();
+ t[i] = (t1 - t0)*1e-3;
+ t0 = t1;
+ }
+
+ a = 0.;
+ d = 0.;
+ max = 0.;
+ min = 1e12;
+
+ for(i=0; i<n; i++){
+ a += t[i];
+ if(max < t[i])
+ max = t[i];
+ if(min > t[i])
+ min = t[i];
+ }
+
+ a /= n;
+
+ for(i=0; i<n; i++)
+ d += (a - t[i]) * (a - t[i]);
+ d /= n;
+ d = sqrt(d);
+
+ print("avg = %.0fµs min = %.0fµs max = %.0fµs dev = %.0fµs\n", a, min, max, d);
+
+ exits(0);
+}
+
diff --git a/src/cmd/fossil/9proc.c b/src/cmd/fossil/9proc.c
new file mode 100644
index 00000000..bddd3baa
--- /dev/null
+++ b/src/cmd/fossil/9proc.c
@@ -0,0 +1,825 @@
+#include "stdinc.h"
+
+#include "9.h"
+#include "dat.h"
+#include "fns.h"
+
+enum {
+ NConInit = 128,
+ NMsgInit = 384,
+ NMsgProcInit = 64,
+ NMsizeInit = 8192+IOHDRSZ,
+};
+
+static struct {
+ VtLock* alock; /* alloc */
+ Msg* ahead;
+ VtRendez* arendez;
+
+ int maxmsg;
+ int nmsg;
+ int nmsgstarve;
+
+ VtLock* rlock; /* read */
+ Msg* rhead;
+ Msg* rtail;
+ VtRendez* rrendez;
+
+ int maxproc;
+ int nproc;
+ int nprocstarve;
+
+ u32int msize; /* immutable */
+} mbox;
+
+static struct {
+ VtLock* alock; /* alloc */
+ Con* ahead;
+ VtRendez* arendez;
+
+ VtLock* clock;
+ Con* chead;
+ Con* ctail;
+
+ int maxcon;
+ int ncon;
+ int nconstarve;
+
+ u32int msize;
+} cbox;
+
+static void
+conFree(Con* con)
+{
+ assert(con->version == nil);
+ assert(con->mhead == nil);
+ assert(con->whead == nil);
+ assert(con->nfid == 0);
+ assert(con->state == ConMoribund);
+
+ if(con->fd >= 0){
+ close(con->fd);
+ con->fd = -1;
+ }
+ con->state = ConDead;
+ con->aok = 0;
+ con->flags = 0;
+ con->isconsole = 0;
+
+ vtLock(cbox.alock);
+ if(con->cprev != nil)
+ con->cprev->cnext = con->cnext;
+ else
+ cbox.chead = con->cnext;
+ if(con->cnext != nil)
+ con->cnext->cprev = con->cprev;
+ else
+ cbox.ctail = con->cprev;
+ con->cprev = con->cnext = nil;
+
+ if(cbox.ncon > cbox.maxcon){
+ if(con->name != nil)
+ vtMemFree(con->name);
+ vtLockFree(con->fidlock);
+ vtMemFree(con->data);
+ vtRendezFree(con->wrendez);
+ vtLockFree(con->wlock);
+ vtRendezFree(con->mrendez);
+ vtLockFree(con->mlock);
+ vtRendezFree(con->rendez);
+ vtLockFree(con->lock);
+ vtMemFree(con);
+ cbox.ncon--;
+ vtUnlock(cbox.alock);
+ return;
+ }
+ con->anext = cbox.ahead;
+ cbox.ahead = con;
+ if(con->anext == nil)
+ vtWakeup(cbox.arendez);
+ vtUnlock(cbox.alock);
+}
+
+static void
+msgFree(Msg* m)
+{
+ assert(m->rwnext == nil);
+ assert(m->flush == nil);
+
+ vtLock(mbox.alock);
+ if(mbox.nmsg > mbox.maxmsg){
+ vtMemFree(m->data);
+ vtMemFree(m);
+ mbox.nmsg--;
+ vtUnlock(mbox.alock);
+ return;
+ }
+ m->anext = mbox.ahead;
+ mbox.ahead = m;
+ if(m->anext == nil)
+ vtWakeup(mbox.arendez);
+ vtUnlock(mbox.alock);
+}
+
+static Msg*
+msgAlloc(Con* con)
+{
+ Msg *m;
+
+ vtLock(mbox.alock);
+ while(mbox.ahead == nil){
+ if(mbox.nmsg >= mbox.maxmsg){
+ mbox.nmsgstarve++;
+ vtSleep(mbox.arendez);
+ continue;
+ }
+ m = vtMemAllocZ(sizeof(Msg));
+ m->data = vtMemAlloc(mbox.msize);
+ m->msize = mbox.msize;
+ mbox.nmsg++;
+ mbox.ahead = m;
+ break;
+ }
+ m = mbox.ahead;
+ mbox.ahead = m->anext;
+ m->anext = nil;
+ vtUnlock(mbox.alock);
+
+ m->con = con;
+ m->state = MsgR;
+ m->nowq = 0;
+
+ return m;
+}
+
+static void
+msgMunlink(Msg* m)
+{
+ Con *con;
+
+ con = m->con;
+
+ if(m->mprev != nil)
+ m->mprev->mnext = m->mnext;
+ else
+ con->mhead = m->mnext;
+ if(m->mnext != nil)
+ m->mnext->mprev = m->mprev;
+ else
+ con->mtail = m->mprev;
+ m->mprev = m->mnext = nil;
+}
+
+void
+msgFlush(Msg* m)
+{
+ Con *con;
+ Msg *flush, *old;
+
+ con = m->con;
+
+ if(Dflag)
+ fprint(2, "msgFlush %F\n", &m->t);
+
+ /*
+ * If this Tflush has been flushed, nothing to do.
+ * Look for the message to be flushed in the
+ * queue of all messages still on this connection.
+ * If it's not found must assume Elvis has already
+ * left the building and reply normally.
+ */
+ vtLock(con->mlock);
+ if(m->state == MsgF){
+ vtUnlock(con->mlock);
+ return;
+ }
+ for(old = con->mhead; old != nil; old = old->mnext)
+ if(old->t.tag == m->t.oldtag)
+ break;
+ if(old == nil){
+ if(Dflag)
+ fprint(2, "msgFlush: cannot find %d\n", m->t.oldtag);
+ vtUnlock(con->mlock);
+ return;
+ }
+
+ if(Dflag)
+ fprint(2, "\tmsgFlush found %F\n", &old->t);
+
+ /*
+ * Found it.
+ * There are two cases where the old message can be
+ * truly flushed and no reply to the original message given.
+ * The first is when the old message is in MsgR state; no
+ * processing has been done yet and it is still on the read
+ * queue. The second is if old is a Tflush, which doesn't
+ * affect the server state. In both cases, put the old
+ * message into MsgF state and let MsgWrite toss it after
+ * pulling it off the queue.
+ */
+ if(old->state == MsgR || old->t.type == Tflush){
+ old->state = MsgF;
+ if(Dflag)
+ fprint(2, "msgFlush: change %d from MsgR to MsgF\n",
+ m->t.oldtag);
+ }
+
+ /*
+ * Link this flush message and the old message
+ * so multiple flushes can be coalesced (if there are
+ * multiple Tflush messages for a particular pending
+ * request, it is only necessary to respond to the last
+ * one, so any previous can be removed) and to be
+ * sure flushes wait for their corresponding old
+ * message to go out first.
+ * Waiting flush messages do not go on the write queue,
+ * they are processed after the old message is dealt
+ * with. There's no real need to protect the setting of
+ * Msg.nowq, the only code to check it runs in this
+ * process after this routine returns.
+ */
+ if((flush = old->flush) != nil){
+ if(Dflag)
+ fprint(2, "msgFlush: remove %d from %d list\n",
+ old->flush->t.tag, old->t.tag);
+ m->flush = flush->flush;
+ flush->flush = nil;
+ msgMunlink(flush);
+ msgFree(flush);
+ }
+ old->flush = m;
+ m->nowq = 1;
+
+ if(Dflag)
+ fprint(2, "msgFlush: add %d to %d queue\n",
+ m->t.tag, old->t.tag);
+ vtUnlock(con->mlock);
+}
+
+static void
+msgProc(void*)
+{
+ Msg *m;
+ char *e;
+ Con *con;
+
+ vtThreadSetName("msgProc");
+
+ for(;;){
+ /*
+ * If surplus to requirements, exit.
+ * If not, wait for and pull a message off
+ * the read queue.
+ */
+ vtLock(mbox.rlock);
+ if(mbox.nproc > mbox.maxproc){
+ mbox.nproc--;
+ vtUnlock(mbox.rlock);
+ break;
+ }
+ while(mbox.rhead == nil)
+ vtSleep(mbox.rrendez);
+ m = mbox.rhead;
+ mbox.rhead = m->rwnext;
+ m->rwnext = nil;
+ vtUnlock(mbox.rlock);
+
+ con = m->con;
+ e = nil;
+
+ /*
+ * If the message has been flushed before
+ * any 9P processing has started, mark it so
+ * none will be attempted.
+ */
+ vtLock(con->mlock);
+ if(m->state == MsgF)
+ e = "flushed";
+ else
+ m->state = Msg9;
+ vtUnlock(con->mlock);
+
+ if(e == nil){
+ /*
+ * explain this
+ */
+ vtLock(con->lock);
+ if(m->t.type == Tversion){
+ con->version = m;
+ con->state = ConDown;
+ while(con->mhead != m)
+ vtSleep(con->rendez);
+ assert(con->state == ConDown);
+ if(con->version == m){
+ con->version = nil;
+ con->state = ConInit;
+ }
+ else
+ e = "Tversion aborted";
+ }
+ else if(con->state != ConUp)
+ e = "connection not ready";
+ vtUnlock(con->lock);
+ }
+
+ /*
+ * Dispatch if not error already.
+ */
+ m->r.tag = m->t.tag;
+ if(e == nil && !(*rFcall[m->t.type])(m))
+ e = vtGetError();
+ if(e != nil){
+ m->r.type = Rerror;
+ m->r.ename = e;
+ }
+ else
+ m->r.type = m->t.type+1;
+
+ /*
+ * Put the message (with reply) on the
+ * write queue and wakeup the write process.
+ */
+ if(!m->nowq){
+ vtLock(con->wlock);
+ if(con->whead == nil)
+ con->whead = m;
+ else
+ con->wtail->rwnext = m;
+ con->wtail = m;
+ vtWakeup(con->wrendez);
+ vtUnlock(con->wlock);
+ }
+ }
+}
+
+static void
+msgRead(void* v)
+{
+ Msg *m;
+ Con *con;
+ int eof, fd, n;
+
+ vtThreadSetName("msgRead");
+
+ con = v;
+ fd = con->fd;
+ eof = 0;
+
+ while(!eof){
+ m = msgAlloc(con);
+
+ while((n = read9pmsg(fd, m->data, con->msize)) == 0)
+ ;
+ if(n < 0){
+ m->t.type = Tversion;
+ m->t.fid = NOFID;
+ m->t.tag = NOTAG;
+ m->t.msize = con->msize;
+ m->t.version = "9PEoF";
+ eof = 1;
+ }
+ else if(convM2S(m->data, n, &m->t) != n){
+ if(Dflag)
+ fprint(2, "msgRead: convM2S error: %s\n",
+ con->name);
+ msgFree(m);
+ continue;
+ }
+ if(Dflag)
+ fprint(2, "msgRead %p: t %F\n", con, &m->t);
+
+ vtLock(con->mlock);
+ if(con->mtail != nil){
+ m->mprev = con->mtail;
+ con->mtail->mnext = m;
+ }
+ else{
+ con->mhead = m;
+ m->mprev = nil;
+ }
+ con->mtail = m;
+ vtUnlock(con->mlock);
+
+ vtLock(mbox.rlock);
+ if(mbox.rhead == nil){
+ mbox.rhead = m;
+ if(!vtWakeup(mbox.rrendez)){
+ if(mbox.nproc < mbox.maxproc){
+ if(vtThread(msgProc, nil) > 0)
+ mbox.nproc++;
+ }
+ else
+ mbox.nprocstarve++;
+ }
+ /*
+ * don't need this surely?
+ vtWakeup(mbox.rrendez);
+ */
+ }
+ else
+ mbox.rtail->rwnext = m;
+ mbox.rtail = m;
+ vtUnlock(mbox.rlock);
+ }
+}
+
+static void
+msgWrite(void* v)
+{
+ Con *con;
+ int eof, n;
+ Msg *flush, *m;
+
+ vtThreadSetName("msgWrite");
+
+ con = v;
+ if(vtThread(msgRead, con) < 0){
+ conFree(con);
+ return;
+ }
+
+ for(;;){
+ /*
+ * Wait for and pull a message off the write queue.
+ */
+ vtLock(con->wlock);
+ while(con->whead == nil)
+ vtSleep(con->wrendez);
+ m = con->whead;
+ con->whead = m->rwnext;
+ m->rwnext = nil;
+ assert(!m->nowq);
+ vtUnlock(con->wlock);
+
+ eof = 0;
+
+ /*
+ * Write each message (if it hasn't been flushed)
+ * followed by any messages waiting for it to complete.
+ */
+ vtLock(con->mlock);
+ while(m != nil){
+ msgMunlink(m);
+
+ if(Dflag)
+ fprint(2, "msgWrite %d: r %F\n",
+ m->state, &m->r);
+
+ if(m->state != MsgF){
+ m->state = MsgW;
+ vtUnlock(con->mlock);
+
+ n = convS2M(&m->r, con->data, con->msize);
+ if(write(con->fd, con->data, n) != n)
+ eof = 1;
+
+ vtLock(con->mlock);
+ }
+
+ if((flush = m->flush) != nil){
+ assert(flush->nowq);
+ m->flush = nil;
+ }
+ msgFree(m);
+ m = flush;
+ }
+ vtUnlock(con->mlock);
+
+ vtLock(con->lock);
+ if(eof && con->fd >= 0){
+ close(con->fd);
+ con->fd = -1;
+ }
+ if(con->state == ConDown)
+ vtWakeup(con->rendez);
+ if(con->state == ConMoribund && con->mhead == nil){
+ vtUnlock(con->lock);
+ conFree(con);
+ break;
+ }
+ vtUnlock(con->lock);
+ }
+}
+
+Con*
+conAlloc(int fd, char* name, int flags)
+{
+ Con *con;
+ char buf[128], *p;
+ int rfd, n;
+
+ vtLock(cbox.alock);
+ while(cbox.ahead == nil){
+ if(cbox.ncon >= cbox.maxcon){
+ cbox.nconstarve++;
+ vtSleep(cbox.arendez);
+ continue;
+ }
+ con = vtMemAllocZ(sizeof(Con));
+ con->lock = vtLockAlloc();
+ con->rendez = vtRendezAlloc(con->lock);
+ con->data = vtMemAlloc(cbox.msize);
+ con->msize = cbox.msize;
+ con->alock = vtLockAlloc();
+ con->mlock = vtLockAlloc();
+ con->mrendez = vtRendezAlloc(con->mlock);
+ con->wlock = vtLockAlloc();
+ con->wrendez = vtRendezAlloc(con->wlock);
+ con->fidlock = vtLockAlloc();
+
+ cbox.ncon++;
+ cbox.ahead = con;
+ break;
+ }
+ con = cbox.ahead;
+ cbox.ahead = con->anext;
+ con->anext = nil;
+
+ if(cbox.ctail != nil){
+ con->cprev = cbox.ctail;
+ cbox.ctail->cnext = con;
+ }
+ else{
+ cbox.chead = con;
+ con->cprev = nil;
+ }
+ cbox.ctail = con;
+
+ assert(con->mhead == nil);
+ assert(con->whead == nil);
+ assert(con->fhead == nil);
+ assert(con->nfid == 0);
+
+ con->state = ConNew;
+ con->fd = fd;
+ if(con->name != nil){
+ vtMemFree(con->name);
+ con->name = nil;
+ }
+ if(name != nil)
+ con->name = vtStrDup(name);
+ else
+ con->name = vtStrDup("unknown");
+ con->remote[0] = 0;
+ snprint(buf, sizeof buf, "%s/remote", con->name);
+ if((rfd = open(buf, OREAD)) >= 0){
+ n = read(rfd, buf, sizeof buf-1);
+ close(rfd);
+ if(n > 0){
+ buf[n] = 0;
+ if((p = strchr(buf, '\n')) != nil)
+ *p = 0;
+ strecpy(con->remote, con->remote+sizeof con->remote, buf);
+ }
+ }
+ con->flags = flags;
+ con->isconsole = 0;
+ vtUnlock(cbox.alock);
+
+ if(vtThread(msgWrite, con) < 0){
+ conFree(con);
+ return nil;
+ }
+
+ return con;
+}
+
+static int
+cmdMsg(int argc, char* argv[])
+{
+ char *p;
+ char *usage = "usage: msg [-m nmsg] [-p nproc]";
+ int maxmsg, nmsg, nmsgstarve, maxproc, nproc, nprocstarve;
+
+ maxmsg = maxproc = 0;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'm':
+ p = ARGF();
+ if(p == nil)
+ return cliError(usage);
+ maxmsg = strtol(argv[0], &p, 0);
+ if(maxmsg <= 0 || p == argv[0] || *p != '\0')
+ return cliError(usage);
+ break;
+ case 'p':
+ p = ARGF();
+ if(p == nil)
+ return cliError(usage);
+ maxproc = strtol(argv[0], &p, 0);
+ if(maxproc <= 0 || p == argv[0] || *p != '\0')
+ return cliError(usage);
+ break;
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ vtLock(mbox.alock);
+ if(maxmsg)
+ mbox.maxmsg = maxmsg;
+ maxmsg = mbox.maxmsg;
+ nmsg = mbox.nmsg;
+ nmsgstarve = mbox.nmsgstarve;
+ vtUnlock(mbox.alock);
+
+ vtLock(mbox.rlock);
+ if(maxproc)
+ mbox.maxproc = maxproc;
+ maxproc = mbox.maxproc;
+ nproc = mbox.nproc;
+ nprocstarve = mbox.nprocstarve;
+ vtUnlock(mbox.rlock);
+
+ consPrint("\tmsg -m %d -p %d\n", maxmsg, maxproc);
+ consPrint("\tnmsg %d nmsgstarve %d nproc %d nprocstarve %d\n",
+ nmsg, nmsgstarve, nproc, nprocstarve);
+
+ return 1;
+}
+
+static int
+scmp(Fid *a, Fid *b)
+{
+ if(a == 0)
+ return 1;
+ if(b == 0)
+ return -1;
+ return strcmp(a->uname, b->uname);
+}
+
+static Fid*
+fidMerge(Fid *a, Fid *b)
+{
+ Fid *s, **l;
+
+ l = &s;
+ while(a || b){
+ if(scmp(a, b) < 0){
+ *l = a;
+ l = &a->sort;
+ a = a->sort;
+ }else{
+ *l = b;
+ l = &b->sort;
+ b = b->sort;
+ }
+ }
+ *l = 0;
+ return s;
+}
+
+static Fid*
+fidMergeSort(Fid *f)
+{
+ int delay;
+ Fid *a, *b;
+
+ if(f == nil)
+ return nil;
+ if(f->sort == nil)
+ return f;
+
+ a = b = f;
+ delay = 1;
+ while(a && b){
+ if(delay) /* easy way to handle 2-element list */
+ delay = 0;
+ else
+ a = a->sort;
+ if(b = b->sort)
+ b = b->sort;
+ }
+
+ b = a->sort;
+ a->sort = nil;
+
+ a = fidMergeSort(f);
+ b = fidMergeSort(b);
+
+ return fidMerge(a, b);
+}
+
+static int
+cmdWho(int argc, char* argv[])
+{
+ char *usage = "usage: who";
+ int i, l1, l2, l;
+ Con *con;
+ Fid *fid, *last;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc > 0)
+ return cliError(usage);
+
+ vtRLock(cbox.clock);
+ l1 = 0;
+ l2 = 0;
+ for(con=cbox.chead; con; con=con->cnext){
+ if((l = strlen(con->name)) > l1)
+ l1 = l;
+ if((l = strlen(con->remote)) > l2)
+ l2 = l;
+ }
+ for(con=cbox.chead; con; con=con->cnext){
+ consPrint("\t%-*s %-*s", l1, con->name, l2, con->remote);
+ vtLock(con->fidlock);
+ last = nil;
+ for(i=0; i<NFidHash; i++)
+ for(fid=con->fidhash[i]; fid; fid=fid->hash)
+ if(fid->fidno != NOFID && fid->uname){
+ fid->sort = last;
+ last = fid;
+ }
+ fid = fidMergeSort(last);
+ last = nil;
+ for(; fid; last=fid, fid=fid->sort)
+ if(last==nil || strcmp(fid->uname, last->uname) != 0)
+ consPrint(" %q", fid->uname);
+ vtUnlock(con->fidlock);
+ consPrint("\n");
+ }
+ vtRUnlock(cbox.clock);
+ return 1;
+}
+
+void
+msgInit(void)
+{
+ mbox.alock = vtLockAlloc();
+ mbox.arendez = vtRendezAlloc(mbox.alock);
+
+ mbox.rlock = vtLockAlloc();
+ mbox.rrendez = vtRendezAlloc(mbox.rlock);
+
+ mbox.maxmsg = NMsgInit;
+ mbox.maxproc = NMsgProcInit;
+ mbox.msize = NMsizeInit;
+
+ cliAddCmd("msg", cmdMsg);
+}
+
+static int
+cmdCon(int argc, char* argv[])
+{
+ char *p;
+ Con *con;
+ char *usage = "usage: con [-m ncon]";
+ int maxcon, ncon, nconstarve;
+
+ maxcon = 0;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'm':
+ p = ARGF();
+ if(p == nil)
+ return cliError(usage);
+ maxcon = strtol(argv[0], &p, 0);
+ if(maxcon <= 0 || p == argv[0] || *p != '\0')
+ return cliError(usage);
+ break;
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ vtLock(cbox.clock);
+ if(maxcon)
+ cbox.maxcon = maxcon;
+ maxcon = cbox.maxcon;
+ ncon = cbox.ncon;
+ nconstarve = cbox.nconstarve;
+ vtUnlock(cbox.clock);
+
+ consPrint("\tcon -m %d\n", maxcon);
+ consPrint("\tncon %d nconstarve %d\n", ncon, nconstarve);
+
+ vtRLock(cbox.clock);
+ for(con = cbox.chead; con != nil; con = con->cnext){
+ consPrint("\t%s\n", con->name);
+ }
+ vtRUnlock(cbox.clock);
+
+ return 1;
+}
+
+void
+conInit(void)
+{
+ cbox.alock = vtLockAlloc();
+ cbox.arendez = vtRendezAlloc(cbox.alock);
+
+ cbox.clock = vtLockAlloc();
+
+ cbox.maxcon = NConInit;
+ cbox.msize = NMsizeInit;
+
+ cliAddCmd("con", cmdCon);
+ cliAddCmd("who", cmdWho);
+}
diff --git a/src/cmd/fossil/9srv.c b/src/cmd/fossil/9srv.c
new file mode 100644
index 00000000..84696268
--- /dev/null
+++ b/src/cmd/fossil/9srv.c
@@ -0,0 +1,242 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+typedef struct Srv Srv;
+struct Srv {
+ int fd;
+ int srvfd;
+ char* service;
+ char* mntpnt;
+
+ Srv* next;
+ Srv* prev;
+};
+
+static struct {
+ VtLock* lock;
+
+ Srv* head;
+ Srv* tail;
+} sbox;
+
+static int
+srvFd(char* name, int mode, int fd, char** mntpnt)
+{
+ int n, srvfd;
+ char *p, buf[10];
+
+ /*
+ * Drop a file descriptor with given name and mode into /srv.
+ * Create with ORCLOSE and don't close srvfd so it will be removed
+ * automatically on process exit.
+ */
+ p = smprint("/srv/%s", name);
+ if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
+ vtMemFree(p);
+ p = smprint("#s/%s", name);
+ if((srvfd = create(p, ORCLOSE|OWRITE, mode)) < 0){
+ vtSetError("create %s: %r", p);
+ vtMemFree(p);
+ return -1;
+ }
+ }
+
+ n = snprint(buf, sizeof(buf), "%d", fd);
+ if(write(srvfd, buf, n) < 0){
+ close(srvfd);
+ vtSetError("write %s: %r", p);
+ vtMemFree(p);
+ return -1;
+ }
+
+ *mntpnt = p;
+
+ return srvfd;
+}
+
+static void
+srvFree(Srv* srv)
+{
+ if(srv->prev != nil)
+ srv->prev->next = srv->next;
+ else
+ sbox.head = srv->next;
+ if(srv->next != nil)
+ srv->next->prev = srv->prev;
+ else
+ sbox.tail = srv->prev;
+
+ if(srv->srvfd != -1)
+ close(srv->srvfd);
+ vtMemFree(srv->service);
+ vtMemFree(srv->mntpnt);
+ vtMemFree(srv);
+}
+
+static Srv*
+srvAlloc(char* service, int mode, int fd)
+{
+ Dir *dir;
+ Srv *srv;
+ int srvfd;
+ char *mntpnt;
+
+ vtLock(sbox.lock);
+ for(srv = sbox.head; srv != nil; srv = srv->next){
+ if(strcmp(srv->service, service) != 0)
+ continue;
+ /*
+ * If the service exists, but is stale,
+ * free it up and let the name be reused.
+ */
+ if((dir = dirfstat(srv->srvfd)) != nil){
+ free(dir);
+ vtSetError("srv: already serving '%s'", service);
+ vtUnlock(sbox.lock);
+ return nil;
+ }
+ srvFree(srv);
+ break;
+ }
+
+ if((srvfd = srvFd(service, mode, fd, &mntpnt)) < 0){
+ vtUnlock(sbox.lock);
+ return nil;
+ }
+ close(fd);
+
+ srv = vtMemAllocZ(sizeof(Srv));
+ srv->srvfd = srvfd;
+ srv->service = vtStrDup(service);
+ srv->mntpnt = mntpnt;
+
+ if(sbox.tail != nil){
+ srv->prev = sbox.tail;
+ sbox.tail->next = srv;
+ }
+ else{
+ sbox.head = srv;
+ srv->prev = nil;
+ }
+ sbox.tail = srv;
+ vtUnlock(sbox.lock);
+
+ return srv;
+}
+
+static int
+cmdSrv(int argc, char* argv[])
+{
+ Con *con;
+ Srv *srv;
+ char *usage = "usage: srv [-APWdp] [service]";
+ int conflags, dflag, fd[2], mode, pflag, r;
+
+ dflag = 0;
+ pflag = 0;
+ conflags = 0;
+ mode = 0666;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'A':
+ conflags |= ConNoAuthCheck;
+ break;
+ case 'I':
+ conflags |= ConIPCheck;
+ break;
+ case 'N':
+ conflags |= ConNoneAllow;
+ break;
+ case 'P':
+ conflags |= ConNoPermCheck;
+ mode = 0600;
+ break;
+ case 'W':
+ conflags |= ConWstatAllow;
+ mode = 0600;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'p':
+ pflag = 1;
+ mode = 0600;
+ break;
+ }ARGEND
+
+ if(pflag && (conflags&ConNoPermCheck)){
+ vtSetError("srv: cannot use -P with -p");
+ return 0;
+ }
+
+ switch(argc){
+ default:
+ return cliError(usage);
+ case 0:
+ vtRLock(sbox.lock);
+ for(srv = sbox.head; srv != nil; srv = srv->next)
+ consPrint("\t%s\t%d\n", srv->service, srv->srvfd);
+ vtRUnlock(sbox.lock);
+
+ return 1;
+ case 1:
+ if(!dflag)
+ break;
+
+ vtLock(sbox.lock);
+ for(srv = sbox.head; srv != nil; srv = srv->next){
+ if(strcmp(srv->service, argv[0]) != 0)
+ continue;
+ srvFree(srv);
+ break;
+ }
+ vtUnlock(sbox.lock);
+
+ if(srv == nil){
+ vtSetError("srv: '%s' not found", argv[0]);
+ return 0;
+ }
+
+ return 1;
+ }
+
+ if(pipe(fd) < 0){
+ vtSetError("srv pipe: %r");
+ return 0;
+ }
+ if((srv = srvAlloc(argv[0], mode, fd[0])) == nil){
+ close(fd[0]); close(fd[1]);
+ return 0;
+ }
+
+ if(pflag)
+ r = consOpen(fd[1], srv->srvfd, -1);
+ else{
+ con = conAlloc(fd[1], srv->mntpnt, conflags);
+ if(con == nil)
+ r = 0;
+ else
+ r = 1;
+ }
+ if(r == 0){
+ close(fd[1]);
+ vtLock(sbox.lock);
+ srvFree(srv);
+ vtUnlock(sbox.lock);
+ }
+
+ return r;
+}
+
+int
+srvInit(void)
+{
+ sbox.lock = vtLockAlloc();
+
+ cliAddCmd("srv", cmdSrv);
+
+ return 1;
+}
diff --git a/src/cmd/fossil/9user.c b/src/cmd/fossil/9user.c
new file mode 100644
index 00000000..98203d0b
--- /dev/null
+++ b/src/cmd/fossil/9user.c
@@ -0,0 +1,948 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+enum {
+ NUserHash = 1009,
+};
+
+typedef struct Ubox Ubox;
+typedef struct User User;
+
+struct User {
+ char* uid;
+ char* uname;
+ char* leader;
+ char** group;
+ int ngroup;
+
+ User* next; /* */
+ User* ihash; /* lookup by .uid */
+ User* nhash; /* lookup by .uname */
+};
+
+#pragma varargck type "U" User*
+
+struct Ubox {
+ User* head;
+ User* tail;
+ int nuser;
+ int len;
+
+ User* ihash[NUserHash]; /* lookup by .uid */
+ User* nhash[NUserHash]; /* lookup by .uname */
+};
+
+static struct {
+ VtLock* lock;
+
+ Ubox* box;
+} ubox;
+
+static char usersDefault[] = {
+ "adm:adm:adm:sys\n"
+ "none:none::\n"
+ "noworld:noworld::\n"
+ "sys:sys::glenda\n"
+ "glenda:glenda:glenda:\n"
+};
+
+static char* usersMandatory[] = {
+ "adm",
+ "none",
+ "noworld",
+ "sys",
+ nil,
+};
+
+char* uidadm = "adm";
+char* unamenone = "none";
+char* uidnoworld = "noworld";
+
+static u32int
+userHash(char* s)
+{
+ uchar *p;
+ u32int hash;
+
+ hash = 0;
+ for(p = (uchar*)s; *p != '\0'; p++)
+ hash = hash*7 + *p;
+
+ return hash % NUserHash;
+}
+
+static User*
+_userByUid(Ubox* box, char* uid)
+{
+ User *u;
+
+ if(box != nil){
+ for(u = box->ihash[userHash(uid)]; u != nil; u = u->ihash){
+ if(strcmp(u->uid, uid) == 0)
+ return u;
+ }
+ }
+ vtSetError("uname: uid '%s' not found", uid);
+ return nil;
+}
+
+char*
+unameByUid(char* uid)
+{
+ User *u;
+ char *uname;
+
+ vtRLock(ubox.lock);
+ if((u = _userByUid(ubox.box, uid)) == nil){
+ vtRUnlock(ubox.lock);
+ return nil;
+ }
+ uname = vtStrDup(u->uname);
+ vtRUnlock(ubox.lock);
+
+ return uname;
+}
+
+static User*
+_userByUname(Ubox* box, char* uname)
+{
+ User *u;
+
+ if(box != nil){
+ for(u = box->nhash[userHash(uname)]; u != nil; u = u->nhash){
+ if(strcmp(u->uname, uname) == 0)
+ return u;
+ }
+ }
+ vtSetError("uname: uname '%s' not found", uname);
+ return nil;
+}
+
+char*
+uidByUname(char* uname)
+{
+ User *u;
+ char *uid;
+
+ vtRLock(ubox.lock);
+ if((u = _userByUname(ubox.box, uname)) == nil){
+ vtRUnlock(ubox.lock);
+ return nil;
+ }
+ uid = vtStrDup(u->uid);
+ vtRUnlock(ubox.lock);
+
+ return uid;
+}
+
+static int
+_groupMember(Ubox* box, char* group, char* member, int whenNoGroup)
+{
+ int i;
+ User *g, *m;
+
+ /*
+ * Is 'member' a member of 'group'?
+ * Note that 'group' is a 'uid' and not a 'uname'.
+ * A 'member' is automatically in their own group.
+ */
+ if((g = _userByUid(box, group)) == nil)
+ return whenNoGroup;
+ if((m = _userByUname(box, member)) == nil)
+ return 0;
+ if(m == g)
+ return 1;
+ for(i = 0; i < g->ngroup; i++){
+ if(strcmp(g->group[i], member) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int
+groupWriteMember(char* uname)
+{
+ int ret;
+
+ /*
+ * If there is a ``write'' group, then only its members can write
+ * to the file system, no matter what the permission bits say.
+ *
+ * To users not in the ``write'' group, the file system appears
+ * read only. This is used to serve sources.cs.bell-labs.com
+ * to the world.
+ *
+ * Note that if there is no ``write'' group, then this routine
+ * makes it look like everyone is a member -- the opposite
+ * of what groupMember does.
+ *
+ * We use this for sources.cs.bell-labs.com.
+ * If this slows things down too much on systems that don't
+ * use this functionality, we could cache the write group lookup.
+ */
+
+ vtRLock(ubox.lock);
+ ret = _groupMember(ubox.box, "write", uname, 1);
+ vtRUnlock(ubox.lock);
+ return ret;
+}
+
+static int
+_groupRemMember(Ubox* box, User* g, char* member)
+{
+ int i;
+
+ if(_userByUname(box, member) == nil)
+ return 0;
+
+ for(i = 0; i < g->ngroup; i++){
+ if(strcmp(g->group[i], member) == 0)
+ break;
+ }
+ if(i >= g->ngroup){
+ if(strcmp(g->uname, member) == 0)
+ vtSetError("uname: '%s' always in own group", member);
+ else
+ vtSetError("uname: '%s' not in group '%s'",
+ member, g->uname);
+ return 0;
+ }
+
+ vtMemFree(g->group[i]);
+
+ box->len -= strlen(member);
+ if(g->ngroup > 1)
+ box->len--;
+ g->ngroup--;
+ switch(g->ngroup){
+ case 0:
+ vtMemFree(g->group);
+ g->group = nil;
+ break;
+ default:
+ for(; i < g->ngroup; i++)
+ g->group[i] = g->group[i+1];
+ g->group[i] = nil; /* prevent accidents */
+ g->group = vtMemRealloc(g->group, g->ngroup * sizeof(char*));
+ break;
+ }
+
+ return 1;
+}
+
+static int
+_groupAddMember(Ubox* box, User* g, char* member)
+{
+ User *u;
+
+ if((u = _userByUname(box, member)) == nil)
+ return 0;
+ if(_groupMember(box, g->uid, u->uname, 0)){
+ if(strcmp(g->uname, member) == 0)
+ vtSetError("uname: '%s' always in own group", member);
+ else
+ vtSetError("uname: '%s' already in group '%s'",
+ member, g->uname);
+ return 0;
+ }
+
+ g->group = vtMemRealloc(g->group, (g->ngroup+1)*sizeof(char*));
+ g->group[g->ngroup] = vtStrDup(member);
+ box->len += strlen(member);
+ g->ngroup++;
+ if(g->ngroup > 1)
+ box->len++;
+
+ return 1;
+}
+
+int
+groupMember(char* group, char* member)
+{
+ int r;
+
+ if(group == nil)
+ return 0;
+
+ vtRLock(ubox.lock);
+ r = _groupMember(ubox.box, group, member, 0);
+ vtRUnlock(ubox.lock);
+
+ return r;
+}
+
+int
+groupLeader(char* group, char* member)
+{
+ int r;
+ User *g;
+
+ /*
+ * Is 'member' the leader of 'group'?
+ * Note that 'group' is a 'uid' and not a 'uname'.
+ * Uname 'none' cannot be a group leader.
+ */
+ if(strcmp(member, unamenone) == 0 || group == nil)
+ return 0;
+
+ vtRLock(ubox.lock);
+ if((g = _userByUid(ubox.box, group)) == nil){
+ vtRUnlock(ubox.lock);
+ return 0;
+ }
+ if(g->leader != nil){
+ if(strcmp(g->leader, member) == 0){
+ vtRUnlock(ubox.lock);
+ return 1;
+ }
+ r = 0;
+ }
+ else
+ r = _groupMember(ubox.box, group, member, 0);
+ vtRUnlock(ubox.lock);
+
+ return r;
+}
+
+static void
+userFree(User* u)
+{
+ int i;
+
+ vtMemFree(u->uid);
+ vtMemFree(u->uname);
+ if(u->leader != nil)
+ vtMemFree(u->leader);
+ if(u->ngroup){
+ for(i = 0; i < u->ngroup; i++)
+ vtMemFree(u->group[i]);
+ vtMemFree(u->group);
+ }
+ vtMemFree(u);
+}
+
+static User*
+userAlloc(char* uid, char* uname)
+{
+ User *u;
+
+ u = vtMemAllocZ(sizeof(User));
+ u->uid = vtStrDup(uid);
+ u->uname = vtStrDup(uname);
+
+ return u;
+}
+
+int
+validUserName(char* name)
+{
+ Rune *r;
+ static Rune invalid[] = L"#:,()";
+
+ for(r = invalid; *r != '\0'; r++){
+ if(utfrune(name, *r))
+ return 0;
+ }
+ return 1;
+}
+
+static int
+userFmt(Fmt* fmt)
+{
+ User *u;
+ int i, r;
+
+ u = va_arg(fmt->args, User*);
+
+ r = fmtprint(fmt, "%s:%s:", u->uid, u->uname);
+ if(u->leader != nil)
+ r += fmtprint(fmt, u->leader);
+ r += fmtprint(fmt, ":");
+ if(u->ngroup){
+ r += fmtprint(fmt, u->group[0]);
+ for(i = 1; i < u->ngroup; i++)
+ r += fmtprint(fmt, ",%s", u->group[i]);
+ }
+
+ return r;
+}
+
+static int
+usersFileWrite(Ubox* box)
+{
+ Fs *fs;
+ User *u;
+ int i, r;
+ Fsys *fsys;
+ char *p, *q, *s;
+ File *dir, *file;
+
+ if((fsys = fsysGet("main")) == nil)
+ return 0;
+ fsysFsRlock(fsys);
+ fs = fsysGetFs(fsys);
+
+ /*
+ * BUG:
+ * the owner/group/permissions need to be thought out.
+ */
+ r = 0;
+ if((dir = fileOpen(fs, "/active")) == nil)
+ goto tidy0;
+ if((file = fileWalk(dir, uidadm)) == nil)
+ file = fileCreate(dir, uidadm, ModeDir|0775, uidadm);
+ fileDecRef(dir);
+ if(file == nil)
+ goto tidy;
+ dir = file;
+ if((file = fileWalk(dir, "users")) == nil)
+ file = fileCreate(dir, "users", 0664, uidadm);
+ fileDecRef(dir);
+ if(file == nil)
+ goto tidy;
+ if(!fileTruncate(file, uidadm))
+ goto tidy;
+
+ p = s = vtMemAlloc(box->len+1);
+ q = p + box->len+1;
+ for(u = box->head; u != nil; u = u->next){
+ p += snprint(p, q-p, "%s:%s:", u->uid, u->uname);
+ if(u->leader != nil)
+ p+= snprint(p, q-p, u->leader);
+ p += snprint(p, q-p, ":");
+ if(u->ngroup){
+ p += snprint(p, q-p, u->group[0]);
+ for(i = 1; i < u->ngroup; i++)
+ p += snprint(p, q-p, ",%s", u->group[i]);
+ }
+ p += snprint(p, q-p, "\n");
+ }
+ r = fileWrite(file, s, box->len, 0, uidadm);
+ vtMemFree(s);
+
+tidy:
+ if(file != nil)
+ fileDecRef(file);
+tidy0:
+ fsysFsRUnlock(fsys);
+ fsysPut(fsys);
+
+ return r;
+}
+
+static void
+uboxRemUser(Ubox* box, User *u)
+{
+ User **h, *up;
+
+ h = &box->ihash[userHash(u->uid)];
+ for(up = *h; up != nil && up != u; up = up->ihash)
+ h = &up->ihash;
+ assert(up == u);
+ *h = up->ihash;
+ box->len -= strlen(u->uid);
+
+ h = &box->nhash[userHash(u->uname)];
+ for(up = *h; up != nil && up != u; up = up->nhash)
+ h = &up->nhash;
+ assert(up == u);
+ *h = up->nhash;
+ box->len -= strlen(u->uname);
+
+ h = &box->head;
+ for(up = *h; up != nil && strcmp(up->uid, u->uid) != 0; up = up->next)
+ h = &up->next;
+ assert(up == u);
+ *h = u->next;
+ u->next = nil;
+
+ box->len -= 4;
+ box->nuser--;
+}
+
+static void
+uboxAddUser(Ubox* box, User* u)
+{
+ User **h, *up;
+
+ h = &box->ihash[userHash(u->uid)];
+ u->ihash = *h;
+ *h = u;
+ box->len += strlen(u->uid);
+
+ h = &box->nhash[userHash(u->uname)];
+ u->nhash = *h;
+ *h = u;
+ box->len += strlen(u->uname);
+
+ h = &box->head;
+ for(up = *h; up != nil && strcmp(up->uid, u->uid) < 0; up = up->next)
+ h = &up->next;
+ u->next = *h;
+ *h = u;
+
+ box->len += 4;
+ box->nuser++;
+}
+
+static void
+uboxDump(Ubox* box)
+{
+ User* u;
+
+ consPrint("nuser %d len = %d\n", box->nuser, box->len);
+
+ for(u = box->head; u != nil; u = u->next)
+ consPrint("%U\n", u);
+}
+
+static void
+uboxFree(Ubox* box)
+{
+ User *next, *u;
+
+ for(u = box->head; u != nil; u = next){
+ next = u->next;
+ userFree(u);
+ }
+ vtMemFree(box);
+}
+
+static int
+uboxInit(char* users, int len)
+{
+ User *g, *u;
+ Ubox *box, *obox;
+ int blank, comment, i, nline, nuser;
+ char *buf, *f[5], **line, *p, *q, *s;
+
+ /*
+ * Strip out whitespace and comments.
+ * Note that comments are pointless, they disappear
+ * when the server writes the database back out.
+ */
+ blank = 1;
+ comment = nline = 0;
+
+ s = p = buf = vtMemAlloc(len+1);
+ for(q = users; *q != '\0'; q++){
+ if(*q == '\r' || *q == '\t' || *q == ' ')
+ continue;
+ if(*q == '\n'){
+ if(!blank){
+ if(p != s){
+ *p++ = '\n';
+ nline++;
+ s = p;
+ }
+ blank = 1;
+ }
+ comment = 0;
+ continue;
+ }
+ if(*q == '#')
+ comment = 1;
+ blank = 0;
+ if(!comment)
+ *p++ = *q;
+ }
+ *p = '\0';
+
+ line = vtMemAllocZ((nline+2)*sizeof(char*));
+ if((i = gettokens(buf, line, nline+2, "\n")) != nline){
+ fprint(2, "nline %d (%d) botch\n", nline, i);
+ vtMemFree(line);
+ vtMemFree(buf);
+ return 0;
+ }
+
+ /*
+ * Everything is updated in a local Ubox until verified.
+ */
+ box = vtMemAllocZ(sizeof(Ubox));
+
+ /*
+ * First pass - check format, check for duplicates
+ * and enter in hash buckets.
+ */
+ nuser = 0;
+ for(i = 0; i < nline; i++){
+ s = vtStrDup(line[i]);
+ if(getfields(s, f, nelem(f), 0, ":") != 4){
+ fprint(2, "bad line '%s'\n", line[i]);
+ vtMemFree(s);
+ continue;
+ }
+ if(*f[0] == '\0' || *f[1] == '\0'){
+ fprint(2, "bad line '%s'\n", line[i]);
+ vtMemFree(s);
+ continue;
+ }
+ if(!validUserName(f[0])){
+ fprint(2, "invalid uid '%s'\n", f[0]);
+ vtMemFree(s);
+ continue;
+ }
+ if(_userByUid(box, f[0]) != nil){
+ fprint(2, "duplicate uid '%s'\n", f[0]);
+ vtMemFree(s);
+ continue;
+ }
+ if(!validUserName(f[1])){
+ fprint(2, "invalid uname '%s'\n", f[0]);
+ vtMemFree(s);
+ continue;
+ }
+ if(_userByUname(box, f[1]) != nil){
+ fprint(2, "duplicate uname '%s'\n", f[1]);
+ vtMemFree(s);
+ continue;
+ }
+
+ u = userAlloc(f[0], f[1]);
+ uboxAddUser(box, u);
+ line[nuser] = line[i];
+ nuser++;
+
+ vtMemFree(s);
+ }
+ assert(box->nuser == nuser);
+
+ /*
+ * Second pass - fill in leader and group information.
+ */
+ for(i = 0; i < nuser; i++){
+ s = vtStrDup(line[i]);
+ getfields(s, f, nelem(f), 0, ":");
+
+ assert(g = _userByUname(box, f[1]));
+ if(*f[2] != '\0'){
+ if((u = _userByUname(box, f[2])) == nil)
+ g->leader = vtStrDup(g->uname);
+ else
+ g->leader = vtStrDup(u->uname);
+ box->len += strlen(g->leader);
+ }
+ for(p = f[3]; p != nil; p = q){
+ if((q = utfrune(p, L',')) != nil)
+ *q++ = '\0';
+ if(!_groupAddMember(box, g, p)){
+ // print/log error here
+ }
+ }
+
+ vtMemFree(s);
+ }
+
+ vtMemFree(line);
+ vtMemFree(buf);
+
+ for(i = 0; usersMandatory[i] != nil; i++){
+ if((u = _userByUid(box, usersMandatory[i])) == nil){
+ vtSetError("user '%s' is mandatory", usersMandatory[i]);
+ uboxFree(box);
+ return 0;
+ }
+ if(strcmp(u->uid, u->uname) != 0){
+ vtSetError("uid/uname for user '%s' must match",
+ usersMandatory[i]);
+ uboxFree(box);
+ return 0;
+ }
+ }
+
+ vtLock(ubox.lock);
+ obox = ubox.box;
+ ubox.box = box;
+ vtUnlock(ubox.lock);
+
+ if(obox != nil)
+ uboxFree(obox);
+
+ return 1;
+}
+
+int
+usersFileRead(char* path)
+{
+ char *p;
+ File *file;
+ Fsys *fsys;
+ int len, r;
+ uvlong size;
+
+ if((fsys = fsysGet("main")) == nil)
+ return 0;
+ fsysFsRlock(fsys);
+
+ if(path == nil)
+ path = "/active/adm/users";
+
+ r = 0;
+ if((file = fileOpen(fsysGetFs(fsys), path)) != nil){
+ if(fileGetSize(file, &size)){
+ len = size;
+ p = vtMemAlloc(size+1);
+ if(fileRead(file, p, len, 0) == len){
+ p[len] = '\0';
+ r = uboxInit(p, len);
+ }
+ }
+ fileDecRef(file);
+ }
+
+ fsysFsRUnlock(fsys);
+ fsysPut(fsys);
+
+ return r;
+}
+
+static int
+cmdUname(int argc, char* argv[])
+{
+ User *u, *up;
+ int d, dflag, i, r;
+ char *p, *uid, *uname;
+ char *createfmt = "fsys main create /active/usr/%s %s %s d775";
+ char *usage = "usage: uname [-d] uname [uid|:uid|%%newname|=leader|+member|-member]";
+
+ dflag = 0;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'd':
+ dflag = 1;
+ break;
+ }ARGEND
+
+ if(argc < 1){
+ if(!dflag)
+ return cliError(usage);
+ vtRLock(ubox.lock);
+ uboxDump(ubox.box);
+ vtRUnlock(ubox.lock);
+ return 1;
+ }
+
+ uname = argv[0];
+ argc--; argv++;
+
+ if(argc == 0){
+ vtRLock(ubox.lock);
+ if((u = _userByUname(ubox.box, uname)) == nil){
+ vtRUnlock(ubox.lock);
+ return 0;
+ }
+ consPrint("\t%U\n", u);
+ vtRUnlock(ubox.lock);
+ return 1;
+ }
+
+ vtLock(ubox.lock);
+ u = _userByUname(ubox.box, uname);
+ while(argc--){
+ if(argv[0][0] == '%'){
+ if(u == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ p = &argv[0][1];
+ if((up = _userByUname(ubox.box, p)) != nil){
+ vtSetError("uname: uname '%s' already exists",
+ up->uname);
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ for(i = 0; usersMandatory[i] != nil; i++){
+ if(strcmp(usersMandatory[i], uname) != 0)
+ continue;
+ vtSetError("uname: uname '%s' is mandatory",
+ uname);
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+
+ d = strlen(p) - strlen(u->uname);
+ for(up = ubox.box->head; up != nil; up = up->next){
+ if(up->leader != nil){
+ if(strcmp(up->leader, u->uname) == 0){
+ vtMemFree(up->leader);
+ up->leader = vtStrDup(p);
+ ubox.box->len += d;
+ }
+ }
+ for(i = 0; i < up->ngroup; i++){
+ if(strcmp(up->group[i], u->uname) != 0)
+ continue;
+ vtMemFree(up->group[i]);
+ up->group[i] = vtStrDup(p);
+ ubox.box->len += d;
+ break;
+ }
+ }
+
+ uboxRemUser(ubox.box, u);
+ vtMemFree(u->uname);
+ u->uname = vtStrDup(p);
+ uboxAddUser(ubox.box, u);
+ }
+ else if(argv[0][0] == '='){
+ if(u == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
+ if(argv[0][1] != '\0'){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ }
+ if(u->leader != nil){
+ ubox.box->len -= strlen(u->leader);
+ vtMemFree(u->leader);
+ u->leader = nil;
+ }
+ if(up != nil){
+ u->leader = vtStrDup(up->uname);
+ ubox.box->len += strlen(u->leader);
+ }
+ }
+ else if(argv[0][0] == '+'){
+ if(u == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if(!_groupAddMember(ubox.box, u, up->uname)){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ }
+ else if(argv[0][0] == '-'){
+ if(u == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if((up = _userByUname(ubox.box, &argv[0][1])) == nil){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if(!_groupRemMember(ubox.box, u, up->uname)){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ }
+ else{
+ if(u != nil){
+ vtSetError("uname: uname '%s' already exists",
+ u->uname);
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+
+ uid = argv[0];
+ if(*uid == ':')
+ uid++;
+ if((u = _userByUid(ubox.box, uid)) != nil){
+ vtSetError("uname: uid '%s' already exists",
+ u->uid);
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+
+ u = userAlloc(uid, uname);
+ uboxAddUser(ubox.box, u);
+ if(argv[0][0] != ':'){
+ // should have an option for the mode and gid
+ p = smprint(createfmt, uname, uname, uname);
+ r = cliExec(p);
+ vtMemFree(p);
+ if(r == 0){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ }
+ }
+ argv++;
+ }
+
+ if(usersFileWrite(ubox.box) == 0){
+ vtUnlock(ubox.lock);
+ return 0;
+ }
+ if(dflag)
+ uboxDump(ubox.box);
+ vtUnlock(ubox.lock);
+
+ return 1;
+}
+
+static int
+cmdUsers(int argc, char* argv[])
+{
+ Ubox *box;
+ int dflag, r, wflag;
+ char *file;
+ char *usage = "usage: users [-d | -r file] [-w]";
+
+ dflag = wflag = 0;
+ file = nil;
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'd':
+ dflag = 1;
+ break;
+ case 'r':
+ file = ARGF();
+ if(file == nil)
+ return cliError(usage);
+ break;
+ case 'w':
+ wflag = 1;
+ break;
+ }ARGEND
+
+ if(argc)
+ return cliError(usage);
+
+ if(dflag && file)
+ return cliError("cannot use -d and -r together");
+
+ if(dflag)
+ uboxInit(usersDefault, sizeof(usersDefault));
+ else if(file){
+ if(usersFileRead(file) == 0)
+ return 0;
+ }
+
+ vtRLock(ubox.lock);
+ box = ubox.box;
+ consPrint("\tnuser %d len %d\n", box->nuser, box->len);
+
+ r = 1;
+ if(wflag)
+ r = usersFileWrite(box);
+ vtRUnlock(ubox.lock);
+ return r;
+}
+
+int
+usersInit(void)
+{
+ fmtinstall('U', userFmt);
+
+ ubox.lock = vtLockAlloc();
+ uboxInit(usersDefault, sizeof(usersDefault));
+
+ cliAddCmd("users", cmdUsers);
+ cliAddCmd("uname", cmdUname);
+
+ return 1;
+}
diff --git a/src/cmd/fossil/Ccli.c b/src/cmd/fossil/Ccli.c
new file mode 100644
index 00000000..775cb620
--- /dev/null
+++ b/src/cmd/fossil/Ccli.c
@@ -0,0 +1,112 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+typedef struct {
+ char* argv0;
+ int (*cmd)(int, char*[]);
+} Cmd;
+
+static struct {
+ VtLock* lock;
+ Cmd* cmd;
+ int ncmd;
+ int hi;
+} cbox;
+
+enum {
+ NCmdIncr = 20,
+};
+
+int
+cliError(char* fmt, ...)
+{
+ char *p;
+ va_list arg;
+
+ va_start(arg, fmt);
+ p = vsmprint(fmt, arg);
+ vtSetError("%s", p);
+ free(p);
+ va_end(arg);
+
+ return 0;
+}
+
+int
+cliExec(char* buf)
+{
+ int argc, i, r;
+ char *argv[20], *p;
+
+ p = vtStrDup(buf);
+ if((argc = tokenize(p, argv, nelem(argv)-1)) == 0){
+ vtMemFree(p);
+ return 1;
+ }
+ argv[argc] = 0;
+
+ if(argv[0][0] == '#'){
+ vtMemFree(p);
+ return 1;
+ }
+
+ vtLock(cbox.lock);
+ for(i = 0; i < cbox.hi; i++){
+ if(strcmp(cbox.cmd[i].argv0, argv[0]) == 0){
+ vtUnlock(cbox.lock);
+ if(!(r = cbox.cmd[i].cmd(argc, argv)))
+ consPrint("%s\n", vtGetError());
+ vtMemFree(p);
+ return r;
+ }
+ }
+ vtUnlock(cbox.lock);
+
+ consPrint("%s: - eh?\n", argv[0]);
+ vtMemFree(p);
+
+ return 0;
+}
+
+int
+cliAddCmd(char* argv0, int (*cmd)(int, char*[]))
+{
+ int i;
+ Cmd *opt;
+
+ vtLock(cbox.lock);
+ for(i = 0; i < cbox.hi; i++){
+ if(strcmp(argv0, cbox.cmd[i].argv0) == 0){
+ vtUnlock(cbox.lock);
+ return 0;
+ }
+ }
+ if(i >= cbox.hi){
+ if(cbox.hi >= cbox.ncmd){
+ cbox.cmd = vtMemRealloc(cbox.cmd,
+ (cbox.ncmd+NCmdIncr)*sizeof(Cmd));
+ memset(&cbox.cmd[cbox.ncmd], 0, NCmdIncr*sizeof(Cmd));
+ cbox.ncmd += NCmdIncr;
+ }
+ }
+
+ opt = &cbox.cmd[cbox.hi];
+ opt->argv0 = argv0;
+ opt->cmd = cmd;
+ cbox.hi++;
+ vtUnlock(cbox.lock);
+
+ return 1;
+}
+
+int
+cliInit(void)
+{
+ cbox.lock = vtLockAlloc();
+ cbox.cmd = vtMemAllocZ(NCmdIncr*sizeof(Cmd));
+ cbox.ncmd = NCmdIncr;
+ cbox.hi = 0;
+
+ return 1;
+}
diff --git a/src/cmd/fossil/Ccmd.c b/src/cmd/fossil/Ccmd.c
new file mode 100644
index 00000000..806c62ff
--- /dev/null
+++ b/src/cmd/fossil/Ccmd.c
@@ -0,0 +1,459 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+static struct {
+ VtLock* lock;
+
+ Con* con;
+ int confd[2];
+ ushort tag;
+} cbox;
+
+static ulong
+cmd9pStrtoul(char* s)
+{
+ if(strcmp(s, "~0") == 0)
+ return ~0UL;
+ return strtoul(s, 0, 0);
+}
+
+static uvlong
+cmd9pStrtoull(char* s)
+{
+ if(strcmp(s, "~0") == 0)
+ return ~0ULL;
+ return strtoull(s, 0, 0);
+}
+
+static int
+cmd9pTag(Fcall*, int, char **argv)
+{
+ cbox.tag = strtoul(argv[0], 0, 0)-1;
+
+ return 1;
+}
+
+static int
+cmd9pTwstat(Fcall* f, int, char **argv)
+{
+ Dir d;
+ static uchar buf[DIRMAX];
+
+ memset(&d, 0, sizeof d);
+ nulldir(&d);
+ d.name = argv[1];
+ d.uid = argv[2];
+ d.gid = argv[3];
+ d.mode = cmd9pStrtoul(argv[4]);
+ d.mtime = cmd9pStrtoul(argv[5]);
+ d.length = cmd9pStrtoull(argv[6]);
+
+ f->fid = strtol(argv[0], 0, 0);
+ f->stat = buf;
+ f->nstat = convD2M(&d, buf, sizeof buf);
+ if(f->nstat < BIT16SZ){
+ vtSetError("Twstat: convD2M failed (internal error)");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+cmd9pTstat(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTremove(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTclunk(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTwrite(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+ f->offset = strtoll(argv[1], 0, 0);
+ f->data = argv[2];
+ f->count = strlen(argv[2]);
+
+ return 1;
+}
+
+static int
+cmd9pTread(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+ f->offset = strtoll(argv[1], 0, 0);
+ f->count = strtol(argv[2], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTcreate(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+ f->name = argv[1];
+ f->perm = strtol(argv[2], 0, 8);
+ f->mode = strtol(argv[3], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTopen(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+ f->mode = strtol(argv[1], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTwalk(Fcall* f, int argc, char** argv)
+{
+ int i;
+
+ if(argc < 2){
+ vtSetError("usage: Twalk tag fid newfid [name...]");
+ return 0;
+ }
+ f->fid = strtol(argv[0], 0, 0);
+ f->newfid = strtol(argv[1], 0, 0);
+ f->nwname = argc-2;
+ if(f->nwname > MAXWELEM){
+ vtSetError("Twalk: too many names");
+ return 0;
+ }
+ for(i = 0; i < argc-2; i++)
+ f->wname[i] = argv[2+i];
+
+ return 1;
+}
+
+static int
+cmd9pTflush(Fcall* f, int, char** argv)
+{
+ f->oldtag = strtol(argv[0], 0, 0);
+
+ return 1;
+}
+
+static int
+cmd9pTattach(Fcall* f, int, char** argv)
+{
+ f->fid = strtol(argv[0], 0, 0);
+ f->afid = strtol(argv[1], 0, 0);
+ f->uname = argv[2];
+ f->aname = argv[3];
+
+ return 1;
+}
+
+static int
+cmd9pTauth(Fcall* f, int, char** argv)
+{
+ f->afid = strtol(argv[0], 0, 0);
+ f->uname = argv[1];
+ f->aname = argv[2];
+
+ return 1;
+}
+
+static int
+cmd9pTversion(Fcall* f, int, char** argv)
+{
+ f->msize = strtoul(argv[0], 0, 0);
+ if(f->msize > cbox.con->msize){
+ vtSetError("msize too big");
+ return 0;
+ }
+ f->version = argv[1];
+
+ return 1;
+}
+
+typedef struct Cmd9p Cmd9p;
+struct Cmd9p {
+ char* name;
+ int type;
+ int argc;
+ char* usage;
+ int (*f)(Fcall*, int, char**);
+};
+
+static Cmd9p cmd9pTmsg[] = {
+ "Tversion", Tversion, 2, "msize version", cmd9pTversion,
+ "Tauth", Tauth, 3, "afid uname aname", cmd9pTauth,
+ "Tflush", Tflush, 1, "oldtag", cmd9pTflush,
+ "Tattach", Tattach, 4, "fid afid uname aname", cmd9pTattach,
+ "Twalk", Twalk, 0, "fid newfid [name...]", cmd9pTwalk,
+ "Topen", Topen, 2, "fid mode", cmd9pTopen,
+ "Tcreate", Tcreate, 4, "fid name perm mode", cmd9pTcreate,
+ "Tread", Tread, 3, "fid offset count", cmd9pTread,
+ "Twrite", Twrite, 3, "fid offset data", cmd9pTwrite,
+ "Tclunk", Tclunk, 1, "fid", cmd9pTclunk,
+ "Tremove", Tremove, 1, "fid", cmd9pTremove,
+ "Tstat", Tstat, 1, "fid", cmd9pTstat,
+ "Twstat", Twstat, 7, "fid name uid gid mode mtime length", cmd9pTwstat,
+ "nexttag", 0, 0, "", cmd9pTag,
+};
+
+static int
+cmd9p(int argc, char* argv[])
+{
+ int i, n;
+ Fcall f, t;
+ uchar *buf;
+ char *usage;
+ u32int msize;
+
+ usage = "usage: 9p T-message ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc < 1)
+ return cliError(usage);
+
+ for(i = 0; i < nelem(cmd9pTmsg); i++){
+ if(strcmp(cmd9pTmsg[i].name, argv[0]) == 0)
+ break;
+ }
+ if(i == nelem(cmd9pTmsg))
+ return cliError(usage);
+ argc--;
+ argv++;
+ if(cmd9pTmsg[i].argc && argc != cmd9pTmsg[i].argc){
+ vtSetError("usage: %s %s",
+ cmd9pTmsg[i].name, cmd9pTmsg[i].usage);
+ return 0;
+ }
+
+ memset(&t, 0, sizeof(t));
+ t.type = cmd9pTmsg[i].type;
+ if(t.type == Tversion)
+ t.tag = NOTAG;
+ else
+ t.tag = ++cbox.tag;
+ msize = cbox.con->msize;
+ if(!cmd9pTmsg[i].f(&t, argc, argv))
+ return 0;
+ buf = vtMemAlloc(msize);
+ n = convS2M(&t, buf, msize);
+ if(n <= BIT16SZ){
+ vtSetError("%s: convS2M error", cmd9pTmsg[i].name);
+ vtMemFree(buf);
+ return 0;
+ }
+ if(write(cbox.confd[0], buf, n) != n){
+ vtSetError("%s: write error: %r", cmd9pTmsg[i].name);
+ vtMemFree(buf);
+ return 0;
+ }
+ consPrint("\t-> %F\n", &t);
+
+ if((n = read9pmsg(cbox.confd[0], buf, msize)) <= 0){
+ vtSetError("%s: read error: %r", cmd9pTmsg[i].name);
+ vtMemFree(buf);
+ return 0;
+ }
+ if(convM2S(buf, n, &f) == 0){
+ vtSetError("%s: convM2S error", cmd9pTmsg[i].name);
+ vtMemFree(buf);
+ return 0;
+ }
+ consPrint("\t<- %F\n", &f);
+
+ vtMemFree(buf);
+ return 1;
+}
+
+static int
+cmdDot(int argc, char* argv[])
+{
+ long l;
+ Dir *dir;
+ int fd, r;
+ vlong length;
+ char *f, *p, *s, *usage;
+
+ usage = "usage: . file";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc != 1)
+ return cliError(usage);
+
+ if((dir = dirstat(argv[0])) == nil)
+ return cliError(". dirstat %s: %r", argv[0]);
+ length = dir->length;
+ free(dir);
+
+ r = 1;
+ if(length != 0){
+ /*
+ * Read the whole file in.
+ */
+ if((fd = open(argv[0], OREAD)) < 0)
+ return cliError(". open %s: %r", argv[0]);
+ f = vtMemAlloc(dir->length+1);
+ if((l = read(fd, f, length)) < 0){
+ vtMemFree(f);
+ close(fd);
+ return cliError(". read %s: %r", argv[0]);
+ }
+ close(fd);
+ f[l] = '\0';
+
+ /*
+ * Call cliExec() for each line.
+ */
+ for(p = s = f; *p != '\0'; p++){
+ if(*p == '\n'){
+ *p = '\0';
+ if(cliExec(s) == 0){
+ r = 0;
+ consPrint("%s: %R\n", s);
+ }
+ s = p+1;
+ }
+ }
+ vtMemFree(f);
+ }
+
+ if(r == 0)
+ vtSetError("errors in . %#q", argv[0]);
+ return r;
+}
+
+static int
+cmdDflag(int argc, char* argv[])
+{
+ char *usage;
+
+ usage = "usage: dflag";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ }ARGEND
+ if(argc)
+ return cliError(usage);
+
+ Dflag ^= 1;
+ consPrint("dflag %d\n", Dflag);
+
+ return 1;
+}
+
+static int
+cmdEcho(int argc, char* argv[])
+{
+ char *usage;
+ int i, nflag;
+
+ nflag = 0;
+ usage = "usage: echo [-n] ...";
+
+ ARGBEGIN{
+ default:
+ return cliError(usage);
+ case 'n':
+ nflag = 1;
+ break;
+ }ARGEND
+
+ for(i = 0; i < argc; i++){
+ if(i != 0)
+ consPrint(" %s", argv[i]);
+ else
+ consPrint(argv[i]);
+ }
+ if(!nflag)
+ consPrint("\n");
+
+ return 1;
+}
+
+static int
+cmdBind(int argc, char* argv[])
+{
+ ulong flag = 0;
+ char *usage;
+
+ usage = "usage: bind [-b|-a|-c|-bc|-ac] new old";
+
+ ARGBEGIN{
+ case 'a':
+ flag |= MAFTER;
+ break;
+ case 'b':
+ flag |= MBEFORE;
+ break;
+ case 'c':
+ flag |= MCREATE;
+ break;
+ default:
+ return cliError(usage);
+ }ARGEND
+
+ if(argc != 2 || (flag&MAFTER)&&(flag&MBEFORE))
+ return cliError(usage);
+
+ if(bind(argv[0], argv[1], flag) < 0){
+ /* try to give a less confusing error than the default */
+ if(access(argv[0], 0) < 0)
+ return cliError("bind: %s: %r", argv[0]);
+ else if(access(argv[1], 0) < 0)
+ return cliError("bind: %s: %r", argv[1]);
+ else
+ return cliError("bind %s %s: %r", argv[0], argv[1]);
+ }
+ return 1;
+}
+
+int
+cmdInit(void)
+{
+ cbox.lock = vtLockAlloc();
+ cbox.confd[0] = cbox.confd[1] = -1;
+
+ cliAddCmd(".", cmdDot);
+ cliAddCmd("9p", cmd9p);
+ cliAddCmd("dflag", cmdDflag);
+ cliAddCmd("echo", cmdEcho);
+ cliAddCmd("bind", cmdBind);
+
+ if(pipe(cbox.confd) < 0)
+ return 0;
+ if((cbox.con = conAlloc(cbox.confd[1], "console", 0)) == nil){
+ close(cbox.confd[0]);
+ close(cbox.confd[1]);
+ cbox.confd[0] = cbox.confd[1] = -1;
+ return 0;
+
+ }
+ cbox.con->isconsole = 1;
+
+ return 1;
+}
diff --git a/src/cmd/fossil/Ccons.c b/src/cmd/fossil/Ccons.c
new file mode 100644
index 00000000..65b9a409
--- /dev/null
+++ b/src/cmd/fossil/Ccons.c
@@ -0,0 +1,398 @@
+#include "stdinc.h"
+
+#include "9.h"
+
+enum {
+ Nl = 256, /* max. command line length */
+ Nq = 8*1024, /* amount of I/O buffered */
+};
+
+typedef struct Q {
+ VtLock* lock;
+ VtRendez* full;
+ VtRendez* empty;
+
+ char q[Nq];
+ int n;
+ int r;
+ int w;
+} Q;
+
+typedef struct Cons {
+ VtLock* lock;
+ int ref;
+ int closed;
+ int fd;
+ int srvfd;
+ int ctlfd;
+ Q* iq; /* points to console.iq */
+ Q* oq; /* points to console.oq */
+} Cons;
+
+char *currfsysname;
+
+static struct {
+ Q* iq; /* input */
+ Q* oq; /* output */
+ char l[Nl]; /* command line assembly */
+ int nl; /* current line length */
+ int nopens;
+
+ char* prompt;
+ int np;
+} console;
+
+static void
+consClose(Cons* cons)
+{
+ vtLock(cons->lock);
+ cons->closed = 1;
+
+ cons->ref--;
+ if(cons->ref > 0){
+ vtLock(cons->iq->lock);
+ vtWakeup(cons->iq->full);
+ vtUnlock(cons->iq->lock);
+ vtLock(cons->oq->lock);
+ vtWakeup(cons->oq->empty);
+ vtUnlock(cons->oq->lock);
+ vtUnlock(cons->lock);
+ return;
+ }
+
+ if(cons->ctlfd != -1){
+ close(cons->ctlfd);
+ cons->srvfd = -1;
+ }
+ if(cons->srvfd != -1){
+ close(cons->srvfd);
+ cons->srvfd = -1;
+ }
+ if(cons->fd != -1){
+ close(cons->fd);
+ cons->fd = -1;
+ }
+ vtUnlock(cons->lock);
+ vtLockFree(cons->lock);
+ vtMemFree(cons);
+ console.nopens--;
+}
+
+static void
+consIProc(void* v)
+{
+ Q *q;
+ Cons *cons;
+ int n, w;
+ char buf[Nq/4];
+
+ vtThreadSetName("consI");
+
+ cons = v;
+ q = cons->iq;
+ for(;;){
+ /*
+ * Can't tell the difference between zero-length read
+ * and eof, so keep calling read until we get an error.
+ */
+ if(cons->closed || (n = read(cons->fd, buf, Nq/4)) < 0)
+ break;
+ vtLock(q->lock);
+ while(Nq - q->n < n && !cons->closed)
+ vtSleep(q->full);
+ w = Nq - q->w;
+ if(w < n){
+ memmove(&q->q[q->w], buf, w);
+ memmove(&q->q[0], buf + w, n - w);
+ }
+ else
+ memmove(&q->q[q->w], buf, n);
+ q->w = (q->w + n) % Nq;
+ q->n += n;
+ vtWakeup(q->empty);
+ vtUnlock(q->lock);
+ }
+ consClose(cons);
+}
+
+static void
+consOProc(void* v)
+{
+ Q *q;
+ Cons *cons;
+ char buf[Nq];
+ int lastn, n, r;
+
+ vtThreadSetName("consO");
+
+ cons = v;
+ q = cons->oq;
+ vtLock(q->lock);
+ lastn = 0;
+ for(;;){
+ while(lastn == q->n && !cons->closed)
+ vtSleep(q->empty);
+ if((n = q->n - lastn) > Nq)
+ n = Nq;
+ if(n > q->w){
+ r = n - q->w;
+ memmove(buf, &q->q[Nq - r], r);
+ memmove(buf+r, &q->q[0], n - r);
+ }
+ else
+ memmove(buf, &q->q[q->w - n], n);
+ lastn = q->n;
+ vtUnlock(q->lock);
+ if(cons->closed || write(cons->fd, buf, n) < 0)
+ break;
+ vtLock(q->lock);
+ vtWakeup(q->empty);
+ }
+ consClose(cons);
+}
+
+int
+consOpen(int fd, int srvfd, int ctlfd)
+{
+ Cons *cons;
+
+ cons = vtMemAllocZ(sizeof(Cons));
+ cons->lock = vtLockAlloc();
+ cons->fd = fd;
+ cons->srvfd = srvfd;
+ cons->ctlfd = ctlfd;
+ cons->iq = console.iq;
+ cons->oq = console.oq;
+ console.nopens++;
+
+ vtLock(cons->lock);
+ cons->ref = 2;
+ cons->closed = 0;
+ if(vtThread(consOProc, cons) < 0){
+ cons->ref--;
+ vtUnlock(cons->lock);
+ consClose(cons);
+ return 0;
+ }
+ vtUnlock(cons->lock);
+
+ if(ctlfd >= 0)
+ consIProc(cons);
+ else if(vtThread(consIProc, cons) < 0){
+ consClose(cons);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+qWrite(Q* q, char* p, int n)
+{
+ int w;
+
+ vtLock(q->lock);
+ if(n > Nq - q->w){
+ w = Nq - q->w;
+ memmove(&q->q[q->w], p, w);
+ memmove(&q->q[0], p + w, n - w);
+ q->w = n - w;
+ }
+ else{
+ memmove(&q->q[q->w], p, n);
+ q->w += n;
+ }
+ q->n += n;
+ vtWakeup(q->empty);
+ vtUnlock(q->lock);
+
+ return n;
+}
+
+static Q*
+qAlloc(void)
+{
+ Q *q;
+
+ q = vtMemAllocZ(sizeof(Q));
+ q->lock = vtLockAlloc();
+ q->full = vtRendezAlloc(q->lock);
+ q->empty = vtRendezAlloc(q->lock);
+ q->n = q->r = q->w = 0;
+
+ return q;
+}
+
+static void
+consProc(void*)
+{
+ Q *q;
+ int argc, i, n, r;
+ char *argv[20], buf[Nq], *lp, *wbuf;
+ char procname[64];
+
+ snprint(procname, sizeof procname, "cons %s", currfsysname);
+ vtThreadSetName(procname);
+
+ q = console.iq;
+ qWrite(console.oq, console.prompt, console.np);
+ vtLock(q->lock);
+ for(;;){
+ while((n = q->n) == 0)
+ vtSleep(q->empty);
+ r = Nq - q->r;
+ if(r < n){
+ memmove(buf, &q->q[q->r], r);
+ memmove(buf + r, &q->q[0], n - r);
+ }
+ else
+ memmove(buf, &q->q[q->r], n);
+ q->r = (q->r + n) % Nq;
+ q->n -= n;
+ vtWakeup(q->full);
+ vtUnlock(q->lock);
+
+ for(i = 0; i < n; i++){
+ switch(buf[i]){
+ case '\004': /* ^D */
+ if(console.nl == 0){
+ qWrite(console.oq, "\n", 1);
+ break;
+ }
+ /*FALLTHROUGH*/
+ default:
+ if(console.nl < Nl-1){
+ qWrite(console.oq, &buf[i], 1);
+ console.l[console.nl++] = buf[i];
+ }
+ continue;
+ case '\b':
+ if(console.nl != 0){
+ qWrite(console.oq, &buf[i], 1);
+ console.nl--;
+ }
+ continue;
+ case '\n':
+ qWrite(console.oq, &buf[i], 1);
+ break;
+ case '\025': /* ^U */
+ qWrite(console.oq, "^U\n", 3);
+ console.nl = 0;
+ break;
+ case '\027': /* ^W */
+ console.l[console.nl] = '\0';
+ wbuf = vtMemAlloc(console.nl+1);
+ memmove(wbuf, console.l, console.nl+1);
+ argc = tokenize(wbuf, argv, nelem(argv));
+ if(argc > 0)
+ argc--;
+ console.nl = 0;
+ lp = console.l;
+ for(i = 0; i < argc; i++)
+ lp += sprint(lp, "%q ", argv[i]);
+ console.nl = lp - console.l;
+ vtMemFree(wbuf);
+ qWrite(console.oq, "^W\n", 3);
+ if(console.nl == 0)
+ break;
+ qWrite(console.oq, console.l, console.nl);
+ continue;
+ case '\177':
+ qWrite(console.oq, "\n", 1);
+ console.nl = 0;
+ break;
+ }
+
+ console.l[console.nl] = '\0';
+ if(console.nl != 0)
+ cliExec(console.l);
+
+ console.nl = 0;
+ qWrite(console.oq, console.prompt, console.np);
+ }
+
+ vtLock(q->lock);
+ }
+}
+
+int
+consWrite(char* buf, int len)
+{
+ if(console.oq == nil)
+ return write(2, buf, len);
+ if(console.nopens == 0)
+ write(2, buf, len);
+ return qWrite(console.oq, buf, len);
+}
+
+int
+consPrompt(char* prompt)
+{
+ char buf[ERRMAX];
+
+ if(prompt == nil)
+ prompt = "prompt";
+
+ vtMemFree(console.prompt);
+ console.np = snprint(buf, sizeof(buf), "%s: ", prompt);
+ console.prompt = vtStrDup(buf);
+
+ return console.np;
+}
+
+int
+consTTY(void)
+{
+ int ctl, fd;
+ char *name, *p;
+
+ name = "/dev/cons";
+ if((fd = open(name, ORDWR)) < 0){
+ name = "#c/cons";
+ if((fd = open(name, ORDWR)) < 0){
+ vtSetError("consTTY: open %s: %r", name);
+ return 0;
+ }
+ }
+
+ p = smprint("%sctl", name);
+ if((ctl = open(p, OWRITE)) < 0){
+ close(fd);
+ vtSetError("consTTY: open %s: %r", p);
+ free(p);
+ return 0;
+ }
+ if(write(ctl, "rawon", 5) < 0){
+ close(ctl);
+ close(fd);
+ vtSetError("consTTY: write %s: %r", p);
+ free(p);
+ return 0;
+ }
+ free(p);
+
+ if(consOpen(fd, fd, ctl) == 0){
+ close(ctl);
+ close(fd);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+consInit(void)
+{
+ console.iq = qAlloc();
+ console.oq = qAlloc();
+ console.nl = 0;
+
+ consPrompt(nil);
+
+ if(vtThread(consProc, nil) < 0){
+ vtFatal("can't start console proc");
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/cmd/fossil/Clog.c b/src/cmd/fossil/Clog.c
new file mode 100644
index 00000000..95755345
--- /dev/null
+++ b/src/cmd/fossil/Clog.c
@@ -0,0 +1,40 @@
+#include "stdinc.h"
+#include "9.h"
+
+/*
+ * To do: This will become something else ('vprint'?).
+ */
+int
+consVPrint(char* fmt, va_list args)
+{
+ int len, ret;
+ char buf[256];
+
+ len = vsnprint(buf, sizeof(buf), fmt, args);
+ ret = consWrite(buf, len);
+
+ while (len-- > 0 && buf[len] == '\n')
+ buf[len] = '\0';
+ /*
+ * if we do this, checking the root fossil (if /sys/log/fossil is there)
+ * will spew all over the console.
+ */
+ if (0)
+ syslog(0, "fossil", "%s", buf);
+ return ret;
+}
+
+/*
+ * To do: This will become 'print'.
+ */
+int
+consPrint(char* fmt, ...)
+{
+ int ret;
+ va_list args;
+
+ va_start(args, fmt);
+ ret = consVPrint(fmt, args);
+ va_end(args);
+ return ret;
+}
diff --git a/src/cmd/fossil/archive.c b/src/cmd/fossil/archive.c
new file mode 100644
index 00000000..fc09e719
--- /dev/null
+++ b/src/cmd/fossil/archive.c
@@ -0,0 +1,466 @@
+/*
+ * Archiver. In charge of sending blocks to Venti.
+ */
+
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "9.h" /* for consPrint */
+
+#define DEBUG 0
+
+static void archThread(void*);
+
+struct Arch
+{
+ int ref;
+ uint blockSize;
+ uint diskSize;
+ Cache *c;
+ Fs *fs;
+ VtSession *z;
+
+ VtLock *lk;
+ VtRendez *starve;
+ VtRendez *die;
+};
+
+Arch *
+archInit(Cache *c, Disk *disk, Fs *fs, VtSession *z)
+{
+ Arch *a;
+
+ a = vtMemAllocZ(sizeof(Arch));
+
+ a->c = c;
+ a->z = z;
+ a->fs = fs;
+ a->blockSize = diskBlockSize(disk);
+ a->lk = vtLockAlloc();
+ a->starve = vtRendezAlloc(a->lk);
+
+ a->ref = 2;
+ vtThread(archThread, a);
+
+ return a;
+}
+
+void
+archFree(Arch *a)
+{
+ /* kill slave */
+ vtLock(a->lk);
+ a->die = vtRendezAlloc(a->lk);
+ vtWakeup(a->starve);
+ while(a->ref > 1)
+ vtSleep(a->die);
+ vtUnlock(a->lk);
+ vtRendezFree(a->starve);
+ vtRendezFree(a->die);
+ vtLockFree(a->lk);
+ vtMemFree(a);
+}
+
+static int
+ventiSend(Arch *a, Block *b, uchar *data)
+{
+ uint n;
+ uchar score[VtScoreSize];
+
+ if(DEBUG > 1)
+ fprint(2, "ventiSend: sending %#ux %L to venti\n", b->addr, &b->l);
+ n = vtZeroTruncate(vtType[b->l.type], data, a->blockSize);
+ if(DEBUG > 1)
+ fprint(2, "ventiSend: truncate %d to %d\n", a->blockSize, n);
+ if(!vtWrite(a->z, score, vtType[b->l.type], data, n)){
+ fprint(2, "ventiSend: vtWrite block %#ux failed: %R\n", b->addr);
+ return 0;
+ }
+ if(!vtSha1Check(score, data, n)){
+ uchar score2[VtScoreSize];
+ vtSha1(score2, data, n);
+ fprint(2, "ventiSend: vtWrite block %#ux failed vtSha1Check %V %V\n",
+ b->addr, score, score2);
+ return 0;
+ }
+ if(!vtSync(a->z))
+ return 0;
+ return 1;
+}
+
+/*
+ * parameters for recursion; there are so many,
+ * and some only change occasionally. this is
+ * easier than spelling things out at each call.
+ */
+typedef struct Param Param;
+struct Param
+{
+ /* these never change */
+ uint snapEpoch; /* epoch for snapshot being archived */
+ uint blockSize;
+ Cache *c;
+ Arch *a;
+
+ /* changes on every call */
+ uint depth;
+
+ /* statistics */
+ uint nfixed;
+ uint nsend;
+ uint nvisit;
+ uint nfailsend;
+ uint maxdepth;
+ uint nreclaim;
+ uint nfake;
+ uint nreal;
+
+ /* these occasionally change (must save old values and put back) */
+ uint dsize;
+ uint psize;
+
+ /* return value; avoids using stack space */
+ Label l;
+ uchar score[VtScoreSize];
+};
+
+static void
+shaBlock(uchar score[VtScoreSize], Block *b, uchar *data, uint bsize)
+{
+ vtSha1(score, data, vtZeroTruncate(vtType[b->l.type], data, bsize));
+}
+
+static uint
+etype(Entry *e)
+{
+ uint t;
+
+ if(e->flags&VtEntryDir)
+ t = BtDir;
+ else
+ t = BtData;
+ return t+e->depth;
+}
+
+static uchar*
+copyBlock(Block *b, u32int blockSize)
+{
+ uchar *data;
+
+ data = vtMemAlloc(blockSize);
+ if(data == nil)
+ return nil;
+ memmove(data, b->data, blockSize);
+ return data;
+}
+
+/*
+ * Walk over the block tree, archiving it to Venti.
+ *
+ * We don't archive the snapshots. Instead we zero the
+ * entries in a temporary copy of the block and archive that.
+ *
+ * Return value is:
+ *
+ * ArchFailure some error occurred
+ * ArchSuccess block and all children archived
+ * ArchFaked success, but block or children got copied
+ */
+enum
+{
+ ArchFailure,
+ ArchSuccess,
+ ArchFaked,
+};
+static int
+archWalk(Param *p, u32int addr, uchar type, u32int tag)
+{
+ int ret, i, x, psize, dsize;
+ uchar *data, score[VtScoreSize];
+ Block *b;
+ Label l;
+ Entry *e;
+ WalkPtr w;
+
+ p->nvisit++;
+
+ b = cacheLocalData(p->c, addr, type, tag, OReadWrite,0);
+ if(b == nil){
+ fprint(2, "archive(%ud, %#ux): cannot find block: %R\n", p->snapEpoch, addr);
+ if(strcmp(vtGetError(), ELabelMismatch) == 0){
+ /* might as well plod on so we write _something_ to Venti */
+ memmove(p->score, vtZeroScore, VtScoreSize);
+ return ArchFaked;
+ }
+ return ArchFailure;
+ }
+
+ if(DEBUG) fprint(2, "%*sarchive(%ud, %#ux): block label %L\n",
+ p->depth*2, "", p->snapEpoch, b->addr, &b->l);
+ p->depth++;
+ if(p->depth > p->maxdepth)
+ p->maxdepth = p->depth;
+
+ data = b->data;
+ if((b->l.state&BsVenti) == 0){
+ initWalk(&w, b, b->l.type==BtDir ? p->dsize : p->psize);
+ for(i=0; nextWalk(&w, score, &type, &tag, &e); i++){
+ if(e){
+ if(!(e->flags&VtEntryActive))
+ continue;
+ if((e->snap && !e->archive)
+ || (e->flags&VtEntryNoArchive)){
+ if(0) fprint(2, "snap; faking %#ux\n", b->addr);
+ if(data == b->data){
+ data = copyBlock(b, p->blockSize);
+ if(data == nil){
+ ret = ArchFailure;
+ goto Out;
+ }
+ w.data = data;
+ }
+ memmove(e->score, vtZeroScore, VtScoreSize);
+ e->depth = 0;
+ e->size = 0;
+ e->tag = 0;
+ e->flags &= ~VtEntryLocal;
+ entryPack(e, data, w.n-1);
+ continue;
+ }
+ }
+ addr = globalToLocal(score);
+ if(addr == NilBlock)
+ continue;
+ dsize = p->dsize;
+ psize = p->psize;
+ if(e){
+ p->dsize= e->dsize;
+ p->psize = e->psize;
+ }
+ vtUnlock(b->lk);
+ x = archWalk(p, addr, type, tag);
+ vtLock(b->lk);
+ if(e){
+ p->dsize = dsize;
+ p->psize = psize;
+ }
+ while(b->iostate != BioClean && b->iostate != BioDirty)
+ vtSleep(b->ioready);
+ switch(x){
+ case ArchFailure:
+ fprint(2, "archWalk %#ux failed; ptr is in %#ux offset %d\n",
+ addr, b->addr, i);
+ ret = ArchFailure;
+ goto Out;
+ case ArchFaked:
+ /*
+ * When we're writing the entry for an archive directory
+ * (like /archive/2003/1215) then even if we've faked
+ * any data, record the score unconditionally.
+ * This way, we will always record the Venti score here.
+ * Otherwise, temporary data or corrupted file system
+ * would cause us to keep holding onto the on-disk
+ * copy of the archive.
+ */
+ if(e==nil || !e->archive)
+ if(data == b->data){
+if(0) fprint(2, "faked %#ux, faking %#ux (%V)\n", addr, b->addr, p->score);
+ data = copyBlock(b, p->blockSize);
+ if(data == nil){
+ ret = ArchFailure;
+ goto Out;
+ }
+ w.data = data;
+ }
+ /* fall through */
+if(0) fprint(2, "falling\n");
+ case ArchSuccess:
+ if(e){
+ memmove(e->score, p->score, VtScoreSize);
+ e->flags &= ~VtEntryLocal;
+ entryPack(e, data, w.n-1);
+ }else
+ memmove(data+(w.n-1)*VtScoreSize, p->score, VtScoreSize);
+ if(data == b->data){
+ blockDirty(b);
+ /*
+ * If b is in the active tree, then we need to note that we've
+ * just removed addr from the active tree (replacing it with the
+ * copy we just stored to Venti). If addr is in other snapshots,
+ * this will close addr but not free it, since it has a non-empty
+ * epoch range.
+ *
+ * If b is in the active tree but has been copied (this can happen
+ * if we get killed at just the right moment), then we will
+ * mistakenly leak its kids.
+ *
+ * The children of an archive directory (e.g., /archive/2004/0604)
+ * are not treated as in the active tree.
+ */
+ if((b->l.state&BsCopied)==0 && (e==nil || e->snap==0))
+ blockRemoveLink(b, addr, p->l.type, p->l.tag, 0);
+ }
+ break;
+ }
+ }
+
+ if(!ventiSend(p->a, b, data)){
+ p->nfailsend++;
+ ret = ArchFailure;
+ goto Out;
+ }
+ p->nsend++;
+ if(data != b->data)
+ p->nfake++;
+ if(data == b->data){ /* not faking it, so update state */
+ p->nreal++;
+ l = b->l;
+ l.state |= BsVenti;
+ if(!blockSetLabel(b, &l, 0)){
+ ret = ArchFailure;
+ goto Out;
+ }
+ }
+ }
+
+ shaBlock(p->score, b, data, p->blockSize);
+if(0) fprint(2, "ventisend %V %p %p %p\n", p->score, data, b->data, w.data);
+ ret = data!=b->data ? ArchFaked : ArchSuccess;
+ p->l = b->l;
+Out:
+ if(data != b->data)
+ vtMemFree(data);
+ p->depth--;
+ blockPut(b);
+ return ret;
+}
+
+static void
+archThread(void *v)
+{
+ Arch *a = v;
+ Block *b;
+ Param p;
+ Super super;
+ int ret;
+ u32int addr;
+ uchar rbuf[VtRootSize];
+ VtRoot root;
+
+ vtThreadSetName("arch");
+
+ for(;;){
+ /* look for work */
+ vtLock(a->fs->elk);
+ b = superGet(a->c, &super);
+ if(b == nil){
+ vtUnlock(a->fs->elk);
+ fprint(2, "archThread: superGet: %R\n");
+ sleep(60*1000);
+ continue;
+ }
+ addr = super.next;
+ if(addr != NilBlock && super.current == NilBlock){
+ super.current = addr;
+ super.next = NilBlock;
+ superPack(&super, b->data);
+ blockDirty(b);
+ }else
+ addr = super.current;
+ blockPut(b);
+ vtUnlock(a->fs->elk);
+
+ if(addr == NilBlock){
+ /* wait for work */
+ vtLock(a->lk);
+ vtSleep(a->starve);
+ if(a->die != nil)
+ goto Done;
+ vtUnlock(a->lk);
+ continue;
+ }
+
+sleep(10*1000); /* window of opportunity to provoke races */
+
+ /* do work */
+ memset(&p, 0, sizeof p);
+ p.blockSize = a->blockSize;
+ p.dsize = 3*VtEntrySize; /* root has three Entries */
+ p.c = a->c;
+ p.a = a;
+
+ ret = archWalk(&p, addr, BtDir, RootTag);
+ switch(ret){
+ default:
+ abort();
+ case ArchFailure:
+ fprint(2, "archiveBlock %#ux: %R\n", addr);
+ sleep(60*1000);
+ continue;
+ case ArchSuccess:
+ case ArchFaked:
+ break;
+ }
+
+ if(0) fprint(2, "archiveSnapshot 0x%#ux: maxdepth %ud nfixed %ud"
+ " send %ud nfailsend %ud nvisit %ud"
+ " nreclaim %ud nfake %ud nreal %ud\n",
+ addr, p.maxdepth, p.nfixed,
+ p.nsend, p.nfailsend, p.nvisit,
+ p.nreclaim, p.nfake, p.nreal);
+ if(0) fprint(2, "archiveBlock %V (%ud)\n", p.score, p.blockSize);
+
+ /* tie up vac root */
+ memset(&root, 0, sizeof root);
+ root.version = VtRootVersion;
+ strecpy(root.type, root.type+sizeof root.type, "vac");
+ strecpy(root.name, root.name+sizeof root.name, "fossil");
+ memmove(root.score, p.score, VtScoreSize);
+ memmove(root.prev, super.last, VtScoreSize);
+ root.blockSize = a->blockSize;
+ vtRootPack(&root, rbuf);
+ if(!vtWrite(a->z, p.score, VtRootType, rbuf, VtRootSize)
+ || !vtSha1Check(p.score, rbuf, VtRootSize)){
+ fprint(2, "vtWriteBlock %#ux: %R\n", addr);
+ sleep(60*1000);
+ continue;
+ }
+
+ /* record success */
+ vtLock(a->fs->elk);
+ b = superGet(a->c, &super);
+ if(b == nil){
+ vtUnlock(a->fs->elk);
+ fprint(2, "archThread: superGet: %R\n");
+ sleep(60*1000);
+ continue;
+ }
+ super.current = NilBlock;
+ memmove(super.last, p.score, VtScoreSize);
+ superPack(&super, b->data);
+ blockDirty(b);
+ blockPut(b);
+ vtUnlock(a->fs->elk);
+
+ consPrint("archive vac:%V\n", p.score);
+ }
+
+Done:
+ a->ref--;
+ vtWakeup(a->die);
+ vtUnlock(a->lk);
+}
+
+void
+archKick(Arch *a)
+{
+ if(a == nil){
+ fprint(2, "warning: archKick nil\n");
+ return;
+ }
+ vtLock(a->lk);
+ vtWakeup(a->starve);
+ vtUnlock(a->lk);
+}
diff --git a/src/cmd/fossil/bwatch.c b/src/cmd/fossil/bwatch.c
new file mode 100644
index 00000000..51deb762
--- /dev/null
+++ b/src/cmd/fossil/bwatch.c
@@ -0,0 +1,421 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * Lock watcher. Check that locking of blocks is always down.
+ *
+ * This is REALLY slow, and it won't work when the blocks aren't
+ * arranged in a tree (e.g., after the first snapshot). But it's great
+ * for debugging.
+ */
+enum
+{
+ MaxLock = 16,
+ HashSize = 1009,
+};
+
+/*
+ * Thread-specific watch state.
+ */
+typedef struct WThread WThread;
+struct WThread
+{
+ Block *b[MaxLock]; /* blocks currently held */
+ uint nb;
+ uint pid;
+};
+
+typedef struct WMap WMap;
+typedef struct WEntry WEntry;
+
+struct WEntry
+{
+ uchar c[VtScoreSize];
+ uchar p[VtScoreSize];
+ int off;
+
+ WEntry *cprev;
+ WEntry *cnext;
+ WEntry *pprev;
+ WEntry *pnext;
+};
+
+struct WMap
+{
+ VtLock *lk;
+
+ WEntry *hchild[HashSize];
+ WEntry *hparent[HashSize];
+};
+
+static WMap map;
+static void **wp;
+static uint blockSize;
+static WEntry *pool;
+uint bwatchDisabled;
+
+static uint
+hash(uchar score[VtScoreSize])
+{
+ uint i, h;
+
+ h = 0;
+ for(i=0; i<VtScoreSize; i++)
+ h = h*37 + score[i];
+ return h%HashSize;
+}
+
+#include <pool.h>
+static void
+freeWEntry(WEntry *e)
+{
+ memset(e, 0, sizeof(WEntry));
+ e->pnext = pool;
+ pool = e;
+}
+
+static WEntry*
+allocWEntry(void)
+{
+ int i;
+ WEntry *w;
+
+ w = pool;
+ if(w == nil){
+ w = vtMemAllocZ(1024*sizeof(WEntry));
+ for(i=0; i<1024; i++)
+ freeWEntry(&w[i]);
+ w = pool;
+ }
+ pool = w->pnext;
+ memset(w, 0, sizeof(WEntry));
+ return w;
+}
+
+/*
+ * remove all dependencies with score as a parent
+ */
+static void
+_bwatchResetParent(uchar *score)
+{
+ WEntry *w, *next;
+ uint h;
+
+ h = hash(score);
+ for(w=map.hparent[h]; w; w=next){
+ next = w->pnext;
+ if(memcmp(w->p, score, VtScoreSize) == 0){
+ if(w->pnext)
+ w->pnext->pprev = w->pprev;
+ if(w->pprev)
+ w->pprev->pnext = w->pnext;
+ else
+ map.hparent[h] = w->pnext;
+ if(w->cnext)
+ w->cnext->cprev = w->cprev;
+ if(w->cprev)
+ w->cprev->cnext = w->cnext;
+ else
+ map.hchild[hash(w->c)] = w->cnext;
+ freeWEntry(w);
+ }
+ }
+}
+/*
+ * and child
+ */
+static void
+_bwatchResetChild(uchar *score)
+{
+ WEntry *w, *next;
+ uint h;
+
+ h = hash(score);
+ for(w=map.hchild[h]; w; w=next){
+ next = w->cnext;
+ if(memcmp(w->c, score, VtScoreSize) == 0){
+ if(w->pnext)
+ w->pnext->pprev = w->pprev;
+ if(w->pprev)
+ w->pprev->pnext = w->pnext;
+ else
+ map.hparent[hash(w->p)] = w->pnext;
+ if(w->cnext)
+ w->cnext->cprev = w->cprev;
+ if(w->cprev)
+ w->cprev->cnext = w->cnext;
+ else
+ map.hchild[h] = w->cnext;
+ freeWEntry(w);
+ }
+ }
+}
+
+static uchar*
+parent(uchar c[VtScoreSize], int *off)
+{
+ WEntry *w;
+ uint h;
+
+ h = hash(c);
+ for(w=map.hchild[h]; w; w=w->cnext)
+ if(memcmp(w->c, c, VtScoreSize) == 0){
+ *off = w->off;
+ return w->p;
+ }
+ return nil;
+}
+
+static void
+addChild(uchar p[VtEntrySize], uchar c[VtEntrySize], int off)
+{
+ uint h;
+ WEntry *w;
+
+ w = allocWEntry();
+ memmove(w->p, p, VtScoreSize);
+ memmove(w->c, c, VtScoreSize);
+ w->off = off;
+
+ h = hash(p);
+ w->pnext = map.hparent[h];
+ if(w->pnext)
+ w->pnext->pprev = w;
+ map.hparent[h] = w;
+
+ h = hash(c);
+ w->cnext = map.hchild[h];
+ if(w->cnext)
+ w->cnext->cprev = w;
+ map.hchild[h] = w;
+}
+
+void
+bwatchReset(uchar score[VtScoreSize])
+{
+ vtLock(map.lk);
+ _bwatchResetParent(score);
+ _bwatchResetChild(score);
+ vtUnlock(map.lk);
+}
+
+void
+bwatchInit(void)
+{
+ map.lk = vtLockAlloc();
+ wp = privalloc();
+ *wp = nil;
+}
+
+void
+bwatchSetBlockSize(uint bs)
+{
+ blockSize = bs;
+}
+
+static WThread*
+getWThread(void)
+{
+ WThread *w;
+
+ w = *wp;
+ if(w == nil || w->pid != getpid()){
+ w = vtMemAllocZ(sizeof(WThread));
+ *wp = w;
+ w->pid = getpid();
+ }
+ return w;
+}
+
+/*
+ * Derive dependencies from the contents of b.
+ */
+void
+bwatchDependency(Block *b)
+{
+ int i, epb, ppb;
+ Entry e;
+
+ if(bwatchDisabled)
+ return;
+
+ vtLock(map.lk);
+ _bwatchResetParent(b->score);
+
+ switch(b->l.type){
+ case BtData:
+ break;
+
+ case BtDir:
+ epb = blockSize / VtEntrySize;
+ for(i=0; i<epb; i++){
+ entryUnpack(&e, b->data, i);
+ if(!(e.flags & VtEntryActive))
+ continue;
+ addChild(b->score, e.score, i);
+ }
+ break;
+
+ default:
+ ppb = blockSize / VtScoreSize;
+ for(i=0; i<ppb; i++)
+ addChild(b->score, b->data+i*VtScoreSize, i);
+ break;
+ }
+ vtUnlock(map.lk);
+}
+
+static int
+depth(uchar *s)
+{
+ int d, x;
+
+ d = -1;
+ while(s){
+ d++;
+ s = parent(s, &x);
+ }
+ return d;
+}
+
+static int
+lockConflicts(uchar xhave[VtScoreSize], uchar xwant[VtScoreSize])
+{
+ uchar *have, *want;
+ int havedepth, wantdepth, havepos, wantpos;
+
+ have = xhave;
+ want = xwant;
+
+ havedepth = depth(have);
+ wantdepth = depth(want);
+
+ /*
+ * walk one or the other up until they're both
+ * at the same level.
+ */
+ havepos = -1;
+ wantpos = -1;
+ have = xhave;
+ want = xwant;
+ while(wantdepth > havedepth){
+ wantdepth--;
+ want = parent(want, &wantpos);
+ }
+ while(havedepth > wantdepth){
+ havedepth--;
+ have = parent(have, &havepos);
+ }
+
+ /*
+ * walk them up simultaneously until we reach
+ * a common ancestor.
+ */
+ while(have && want && memcmp(have, want, VtScoreSize) != 0){
+ have = parent(have, &havepos);
+ want = parent(want, &wantpos);
+ }
+
+ /*
+ * not part of same tree. happens mainly with
+ * newly allocated blocks.
+ */
+ if(!have || !want)
+ return 0;
+
+ /*
+ * never walked want: means we want to lock
+ * an ancestor of have. no no.
+ */
+ if(wantpos == -1)
+ return 1;
+
+ /*
+ * never walked have: means we want to lock a
+ * child of have. that's okay.
+ */
+ if(havepos == -1)
+ return 0;
+
+ /*
+ * walked both: they're from different places in the tree.
+ * require that the left one be locked before the right one.
+ * (this is questionable, but it puts a total order on the block tree).
+ */
+ return havepos < wantpos;
+}
+
+static void
+stop(void)
+{
+ int fd;
+ char buf[32];
+
+ snprint(buf, sizeof buf, "#p/%d/ctl", getpid());
+ fd = open(buf, OWRITE);
+ write(fd, "stop", 4);
+ close(fd);
+}
+
+/*
+ * Check whether the calling thread can validly lock b.
+ * That is, check that the calling thread doesn't hold
+ * locks for any of b's children.
+ */
+void
+bwatchLock(Block *b)
+{
+ int i;
+ WThread *w;
+
+ if(bwatchDisabled)
+ return;
+
+ if(b->part != PartData)
+ return;
+
+ vtLock(map.lk);
+ w = getWThread();
+ for(i=0; i<w->nb; i++){
+ if(lockConflicts(w->b[i]->score, b->score)){
+ fprint(2, "%d: have block %V; shouldn't lock %V\n",
+ w->pid, w->b[i]->score, b->score);
+ stop();
+ }
+ }
+ vtUnlock(map.lk);
+ if(w->nb >= MaxLock){
+ fprint(2, "%d: too many blocks held\n", w->pid);
+ stop();
+ }else
+ w->b[w->nb++] = b;
+}
+
+/*
+ * Note that the calling thread is about to unlock b.
+ */
+void
+bwatchUnlock(Block *b)
+{
+ int i;
+ WThread *w;
+
+ if(bwatchDisabled)
+ return;
+
+ if(b->part != PartData)
+ return;
+
+ w = getWThread();
+ for(i=0; i<w->nb; i++)
+ if(w->b[i] == b)
+ break;
+ if(i>=w->nb){
+ fprint(2, "%d: unlock of unlocked block %V\n", w->pid, b->score);
+ stop();
+ }else
+ w->b[i] = w->b[--w->nb];
+}
+
diff --git a/src/cmd/fossil/cache.c b/src/cmd/fossil/cache.c
new file mode 100644
index 00000000..e812f6b6
--- /dev/null
+++ b/src/cmd/fossil/cache.c
@@ -0,0 +1,2114 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "9.h" /* for cacheFlush */
+
+typedef struct FreeList FreeList;
+typedef struct BAddr BAddr;
+
+enum {
+ BadHeap = ~0,
+};
+
+/*
+ * Store data to the memory cache in c->size blocks
+ * with the block zero extended to fill it out. When writing to
+ * Venti, the block will be zero truncated. The walker will also check
+ * that the block fits within psize or dsize as the case may be.
+ */
+
+struct Cache
+{
+ VtLock *lk;
+ int ref;
+ int mode;
+
+ Disk *disk;
+ int size; /* block size */
+ int ndmap; /* size of per-block dirty pointer map used in blockWrite */
+ VtSession *z;
+ u32int now; /* ticks for usage timestamps */
+ Block **heads; /* hash table for finding address */
+ int nheap; /* number of available victims */
+ Block **heap; /* heap for locating victims */
+ long nblocks; /* number of blocks allocated */
+ Block *blocks; /* array of block descriptors */
+ u8int *mem; /* memory for all block data & blists */
+
+ BList *blfree;
+ VtRendez *blrend;
+
+ int ndirty; /* number of dirty blocks in the cache */
+ int maxdirty; /* max number of dirty blocks */
+ u32int vers;
+
+ long hashSize;
+
+ FreeList *fl;
+
+ VtRendez *die; /* daemon threads should die when != nil */
+
+ VtRendez *flush;
+ VtRendez *flushwait;
+ VtRendez *heapwait;
+ BAddr *baddr;
+ int bw, br, be;
+ int nflush;
+
+ Periodic *sync;
+
+ /* unlink daemon */
+ BList *uhead;
+ BList *utail;
+ VtRendez *unlink;
+
+ /* block counts */
+ int nused;
+ int ndisk;
+};
+
+struct BList {
+ int part;
+ u32int addr;
+ uchar type;
+ u32int tag;
+ u32int epoch;
+ u32int vers;
+
+ int recurse; /* for block unlink */
+
+ /* for roll back */
+ int index; /* -1 indicates not valid */
+ union {
+ uchar score[VtScoreSize];
+ uchar entry[VtEntrySize];
+ } old;
+ BList *next;
+};
+
+struct BAddr {
+ int part;
+ u32int addr;
+ u32int vers;
+};
+
+struct FreeList {
+ VtLock *lk;
+ u32int last; /* last block allocated */
+ u32int end; /* end of data partition */
+ u32int nused; /* number of used blocks */
+ u32int epochLow; /* low epoch when last updated nused */
+};
+
+static FreeList *flAlloc(u32int end);
+static void flFree(FreeList *fl);
+
+static Block *cacheBumpBlock(Cache *c);
+static void heapDel(Block*);
+static void heapIns(Block*);
+static void cacheCheck(Cache*);
+static void unlinkThread(void *a);
+static void flushThread(void *a);
+static void unlinkBody(Cache *c);
+static int cacheFlushBlock(Cache *c);
+static void cacheSync(void*);
+static BList *blistAlloc(Block*);
+static void blistFree(Cache*, BList*);
+static void doRemoveLink(Cache*, BList*);
+
+/*
+ * Mapping from local block type to Venti type
+ */
+int vtType[BtMax] = {
+ VtDataType, /* BtData | 0 */
+ VtPointerType0, /* BtData | 1 */
+ VtPointerType1, /* BtData | 2 */
+ VtPointerType2, /* BtData | 3 */
+ VtPointerType3, /* BtData | 4 */
+ VtPointerType4, /* BtData | 5 */
+ VtPointerType5, /* BtData | 6 */
+ VtPointerType6, /* BtData | 7 */
+ VtDirType, /* BtDir | 0 */
+ VtPointerType0, /* BtDir | 1 */
+ VtPointerType1, /* BtDir | 2 */
+ VtPointerType2, /* BtDir | 3 */
+ VtPointerType3, /* BtDir | 4 */
+ VtPointerType4, /* BtDir | 5 */
+ VtPointerType5, /* BtDir | 6 */
+ VtPointerType6, /* BtDir | 7 */
+};
+
+/*
+ * Allocate the memory cache.
+ */
+Cache *
+cacheAlloc(Disk *disk, VtSession *z, ulong nblocks, int mode)
+{
+ int i;
+ Cache *c;
+ Block *b;
+ BList *bl;
+ u8int *p;
+ int nbl;
+
+ c = vtMemAllocZ(sizeof(Cache));
+
+ /* reasonable number of BList elements */
+ nbl = nblocks * 4;
+
+ c->lk = vtLockAlloc();
+ c->ref = 1;
+ c->disk = disk;
+ c->z = z;
+ c->size = diskBlockSize(disk);
+bwatchSetBlockSize(c->size);
+ /* round c->size up to be a nice multiple */
+ c->size = (c->size + 127) & ~127;
+ c->ndmap = (c->size/20 + 7) / 8;
+ c->nblocks = nblocks;
+ c->hashSize = nblocks;
+ c->heads = vtMemAllocZ(c->hashSize*sizeof(Block*));
+ c->heap = vtMemAllocZ(nblocks*sizeof(Block*));
+ c->blocks = vtMemAllocZ(nblocks*sizeof(Block));
+ c->mem = vtMemAllocZ(nblocks * (c->size + c->ndmap) + nbl * sizeof(BList));
+ c->baddr = vtMemAllocZ(nblocks * sizeof(BAddr));
+ c->mode = mode;
+ c->vers++;
+ p = c->mem;
+ for(i = 0; i < nblocks; i++){
+ b = &c->blocks[i];
+ b->lk = vtLockAlloc();
+ b->c = c;
+ b->data = p;
+ b->heap = i;
+ b->ioready = vtRendezAlloc(b->lk);
+ c->heap[i] = b;
+ p += c->size;
+ }
+ c->nheap = nblocks;
+ for(i = 0; i < nbl; i++){
+ bl = (BList*)p;
+ bl->next = c->blfree;
+ c->blfree = bl;
+ p += sizeof(BList);
+ }
+ /* separate loop to keep blocks and blists reasonably aligned */
+ for(i = 0; i < nblocks; i++){
+ b = &c->blocks[i];
+ b->dmap = p;
+ p += c->ndmap;
+ }
+
+ c->blrend = vtRendezAlloc(c->lk);
+
+ c->maxdirty = nblocks*(DirtyPercentage*0.01);
+
+ c->fl = flAlloc(diskSize(disk, PartData));
+
+ c->unlink = vtRendezAlloc(c->lk);
+ c->flush = vtRendezAlloc(c->lk);
+ c->flushwait = vtRendezAlloc(c->lk);
+ c->heapwait = vtRendezAlloc(c->lk);
+ c->sync = periodicAlloc(cacheSync, c, 30*1000);
+
+ if(mode == OReadWrite){
+ c->ref += 2;
+ vtThread(unlinkThread, c);
+ vtThread(flushThread, c);
+ }
+ cacheCheck(c);
+
+ return c;
+}
+
+/*
+ * Free the whole memory cache, flushing all dirty blocks to the disk.
+ */
+void
+cacheFree(Cache *c)
+{
+ int i;
+
+ /* kill off daemon threads */
+ vtLock(c->lk);
+ c->die = vtRendezAlloc(c->lk);
+ periodicKill(c->sync);
+ vtWakeup(c->flush);
+ vtWakeup(c->unlink);
+ while(c->ref > 1)
+ vtSleep(c->die);
+
+ /* flush everything out */
+ do {
+ unlinkBody(c);
+ vtUnlock(c->lk);
+ while(cacheFlushBlock(c))
+ ;
+ diskFlush(c->disk);
+ vtLock(c->lk);
+ } while(c->uhead || c->ndirty);
+ vtUnlock(c->lk);
+
+ cacheCheck(c);
+
+ for(i = 0; i < c->nblocks; i++){
+ assert(c->blocks[i].ref == 0);
+ vtRendezFree(c->blocks[i].ioready);
+ vtLockFree(c->blocks[i].lk);
+ }
+ flFree(c->fl);
+ vtMemFree(c->baddr);
+ vtMemFree(c->heads);
+ vtMemFree(c->blocks);
+ vtMemFree(c->mem);
+ vtLockFree(c->lk);
+ diskFree(c->disk);
+ vtRendezFree(c->blrend);
+ /* don't close vtSession */
+ vtMemFree(c);
+}
+
+static void
+cacheDump(Cache *c)
+{
+ int i;
+ Block *b;
+
+ for(i = 0; i < c->nblocks; i++){
+ b = &c->blocks[i];
+ fprint(2, "%d. p=%d a=%ud %V t=%d ref=%d state=%s io=%s pc=%#p\n",
+ i, b->part, b->addr, b->score, b->l.type, b->ref,
+ bsStr(b->l.state), bioStr(b->iostate), b->pc);
+ }
+}
+
+static void
+cacheCheck(Cache *c)
+{
+ u32int size, now;
+ int i, k, refed;
+ static uchar zero[VtScoreSize];
+ Block *b;
+
+ size = c->size;
+ now = c->now;
+
+ for(i = 0; i < c->nheap; i++){
+ if(c->heap[i]->heap != i)
+ vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
+ if(i > 0 && c->heap[(i - 1) >> 1]->used - now > c->heap[i]->used - now)
+ vtFatal("bad heap ordering");
+ k = (i << 1) + 1;
+ if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
+ vtFatal("bad heap ordering");
+ k++;
+ if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now)
+ vtFatal("bad heap ordering");
+ }
+
+ refed = 0;
+ for(i = 0; i < c->nblocks; i++){
+ b = &c->blocks[i];
+ if(b->data != &c->mem[i * size])
+ vtFatal("mis-blocked at %d", i);
+ if(b->ref && b->heap == BadHeap){
+ refed++;
+ }
+ }
+if(c->nheap + refed != c->nblocks){
+fprint(2, "%s: cacheCheck: nheap %d refed %d nblocks %ld\n", argv0, c->nheap, refed, c->nblocks);
+cacheDump(c);
+}
+ assert(c->nheap + refed == c->nblocks);
+ refed = 0;
+ for(i = 0; i < c->nblocks; i++){
+ b = &c->blocks[i];
+ if(b->ref){
+if(1)fprint(2, "%s: p=%d a=%ud %V ref=%d %L\n", argv0, b->part, b->addr, b->score, b->ref, &b->l);
+ refed++;
+ }
+ }
+if(refed > 0)fprint(2, "%s: cacheCheck: in used %d\n", argv0, refed);
+}
+
+
+/*
+ * locate the block with the oldest second to last use.
+ * remove it from the heap, and fix up the heap.
+ */
+/* called with c->lk held */
+static Block *
+cacheBumpBlock(Cache *c)
+{
+ int printed;
+ Block *b;
+
+ /*
+ * locate the block with the oldest second to last use.
+ * remove it from the heap, and fix up the heap.
+ */
+ printed = 0;
+ if(c->nheap == 0){
+ while(c->nheap == 0){
+ vtWakeup(c->flush);
+ vtSleep(c->heapwait);
+ if(c->nheap == 0){
+ printed = 1;
+ fprint(2, "%s: entire cache is busy, %d dirty "
+ "-- waking flush thread\n",
+ argv0, c->ndirty);
+ }
+ }
+ if(printed)
+ fprint(2, "%s: cache is okay again, %d dirty\n",
+ argv0, c->ndirty);
+ }
+
+ b = c->heap[0];
+ heapDel(b);
+
+ assert(b->heap == BadHeap);
+ assert(b->ref == 0);
+ assert(b->iostate != BioDirty && b->iostate != BioReading && b->iostate != BioWriting);
+ assert(b->prior == nil);
+ assert(b->uhead == nil);
+
+ /*
+ * unchain the block from hash chain
+ */
+ if(b->prev){
+ *(b->prev) = b->next;
+ if(b->next)
+ b->next->prev = b->prev;
+ b->prev = nil;
+ }
+
+
+if(0)fprint(2, "%s: dropping %d:%x:%V\n", argv0, b->part, b->addr, b->score);
+ /* set block to a reasonable state */
+ b->ref = 1;
+ b->part = PartError;
+ memset(&b->l, 0, sizeof(b->l));
+ b->iostate = BioEmpty;
+
+ return b;
+}
+
+/*
+ * look for a particular version of the block in the memory cache.
+ */
+static Block *
+_cacheLocalLookup(Cache *c, int part, u32int addr, u32int vers,
+ int waitlock, int *lockfailure)
+{
+ Block *b;
+ ulong h;
+
+ h = addr % c->hashSize;
+
+ if(lockfailure)
+ *lockfailure = 0;
+
+ /*
+ * look for the block in the cache
+ */
+ vtLock(c->lk);
+ for(b = c->heads[h]; b != nil; b = b->next){
+ if(b->part == part && b->addr == addr)
+ break;
+ }
+ if(b == nil || b->vers != vers){
+ vtUnlock(c->lk);
+ return nil;
+ }
+ if(!waitlock && !vtCanLock(b->lk)){
+ *lockfailure = 1;
+ vtUnlock(c->lk);
+ return nil;
+ }
+ heapDel(b);
+ b->ref++;
+ vtUnlock(c->lk);
+
+ bwatchLock(b);
+ if(waitlock)
+ vtLock(b->lk);
+ b->nlock = 1;
+
+ for(;;){
+ switch(b->iostate){
+ default:
+ abort();
+ case BioEmpty:
+ case BioLabel:
+ case BioClean:
+ case BioDirty:
+ if(b->vers != vers){
+ blockPut(b);
+ return nil;
+ }
+ return b;
+ case BioReading:
+ case BioWriting:
+ vtSleep(b->ioready);
+ break;
+ case BioVentiError:
+ blockPut(b);
+ vtSetError("venti i/o error block 0x%.8ux", addr);
+ return nil;
+ case BioReadError:
+ blockPut(b);
+ vtSetError("error reading block 0x%.8ux", addr);
+ return nil;
+ }
+ }
+ /* NOT REACHED */
+}
+static Block*
+cacheLocalLookup(Cache *c, int part, u32int addr, u32int vers)
+{
+ return _cacheLocalLookup(c, part, addr, vers, Waitlock, 0);
+}
+
+
+/*
+ * fetch a local (on-disk) block from the memory cache.
+ * if it's not there, load it, bumping some other block.
+ */
+Block *
+_cacheLocal(Cache *c, int part, u32int addr, int mode, u32int epoch)
+{
+ Block *b;
+ ulong h;
+
+ assert(part != PartVenti);
+
+ h = addr % c->hashSize;
+
+ /*
+ * look for the block in the cache
+ */
+ vtLock(c->lk);
+ for(b = c->heads[h]; b != nil; b = b->next){
+ if(b->part != part || b->addr != addr)
+ continue;
+ if(epoch && b->l.epoch != epoch){
+fprint(2, "%s: _cacheLocal want epoch %ud got %ud\n", argv0, epoch, b->l.epoch);
+ vtUnlock(c->lk);
+ vtSetError(ELabelMismatch);
+ return nil;
+ }
+ heapDel(b);
+ b->ref++;
+ break;
+ }
+
+ if(b == nil){
+ b = cacheBumpBlock(c);
+
+ b->part = part;
+ b->addr = addr;
+ localToGlobal(addr, b->score);
+
+ /* chain onto correct hash */
+ b->next = c->heads[h];
+ c->heads[h] = b;
+ if(b->next != nil)
+ b->next->prev = &b->next;
+ b->prev = &c->heads[h];
+ }
+
+ vtUnlock(c->lk);
+
+ /*
+ * BUG: what if the epoch changes right here?
+ * In the worst case, we could end up in some weird
+ * lock loop, because the block we want no longer exists,
+ * and instead we're trying to lock a block we have no
+ * business grabbing.
+ *
+ * For now, I'm not going to worry about it.
+ */
+
+if(0)fprint(2, "%s: cacheLocal: %d: %d %x\n", argv0, getpid(), b->part, b->addr);
+ bwatchLock(b);
+ vtLock(b->lk);
+ b->nlock = 1;
+
+ if(part == PartData && b->iostate == BioEmpty){
+ if(!readLabel(c, &b->l, addr)){
+ blockPut(b);
+ return nil;
+ }
+ blockSetIOState(b, BioLabel);
+ }
+ if(epoch && b->l.epoch != epoch){
+ blockPut(b);
+fprint(2, "%s: _cacheLocal want epoch %ud got %ud\n", argv0, epoch, b->l.epoch);
+ vtSetError(ELabelMismatch);
+ return nil;
+ }
+
+ b->pc = getcallerpc(&c);
+ for(;;){
+ switch(b->iostate){
+ default:
+ abort();
+ case BioLabel:
+ if(mode == OOverWrite)
+ /*
+ * leave iostate as BioLabel because data
+ * hasn't been read.
+ */
+ return b;
+ /* fall through */
+ case BioEmpty:
+ diskRead(c->disk, b);
+ vtSleep(b->ioready);
+ break;
+ case BioClean:
+ case BioDirty:
+ return b;
+ case BioReading:
+ case BioWriting:
+ vtSleep(b->ioready);
+ break;
+ case BioReadError:
+ blockSetIOState(b, BioEmpty);
+ blockPut(b);
+ vtSetError("error reading block 0x%.8ux", addr);
+ return nil;
+ }
+ }
+ /* NOT REACHED */
+}
+
+Block *
+cacheLocal(Cache *c, int part, u32int addr, int mode)
+{
+ return _cacheLocal(c, part, addr, mode, 0);
+}
+
+/*
+ * fetch a local (on-disk) block from the memory cache.
+ * if it's not there, load it, bumping some other block.
+ * check tag and type.
+ */
+Block *
+cacheLocalData(Cache *c, u32int addr, int type, u32int tag, int mode, u32int epoch)
+{
+ Block *b;
+
+ b = _cacheLocal(c, PartData, addr, mode, epoch);
+ if(b == nil)
+ return nil;
+ if(b->l.type != type || b->l.tag != tag){
+ fprint(2, "%s: cacheLocalData: addr=%d type got %d exp %d: tag got %ux exp %ux\n",
+ argv0, addr, b->l.type, type, b->l.tag, tag);
+ vtSetError(ELabelMismatch);
+ blockPut(b);
+ return nil;
+ }
+ b->pc = getcallerpc(&c);
+ return b;
+}
+
+/*
+ * fetch a global (Venti) block from the memory cache.
+ * if it's not there, load it, bumping some other block.
+ * check tag and type if it's really a local block in disguise.
+ */
+Block *
+cacheGlobal(Cache *c, uchar score[VtScoreSize], int type, u32int tag, int mode)
+{
+ int n;
+ Block *b;
+ ulong h;
+ u32int addr;
+
+ addr = globalToLocal(score);
+ if(addr != NilBlock){
+ b = cacheLocalData(c, addr, type, tag, mode, 0);
+ if(b)
+ b->pc = getcallerpc(&c);
+ return b;
+ }
+
+ h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->hashSize;
+
+ /*
+ * look for the block in the cache
+ */
+ vtLock(c->lk);
+ for(b = c->heads[h]; b != nil; b = b->next){
+ if(b->part != PartVenti || memcmp(b->score, score, VtScoreSize) != 0 || b->l.type != type)
+ continue;
+ heapDel(b);
+ b->ref++;
+ break;
+ }
+
+ if(b == nil){
+if(0)fprint(2, "%s: cacheGlobal %V %d\n", argv0, score, type);
+
+ b = cacheBumpBlock(c);
+
+ b->part = PartVenti;
+ b->addr = NilBlock;
+ b->l.type = type;
+ memmove(b->score, score, VtScoreSize);
+
+ /* chain onto correct hash */
+ b->next = c->heads[h];
+ c->heads[h] = b;
+ if(b->next != nil)
+ b->next->prev = &b->next;
+ b->prev = &c->heads[h];
+ }
+ vtUnlock(c->lk);
+
+ bwatchLock(b);
+ vtLock(b->lk);
+ b->nlock = 1;
+ b->pc = getcallerpc(&c);
+
+ switch(b->iostate){
+ default:
+ abort();
+ case BioEmpty:
+ n = vtRead(c->z, score, vtType[type], b->data, c->size);
+ if(n < 0 || !vtSha1Check(score, b->data, n)){
+ blockSetIOState(b, BioVentiError);
+ blockPut(b);
+ vtSetError(
+ "venti error reading block %V or wrong score: %r",
+ score);
+ return nil;
+ }
+ vtZeroExtend(vtType[type], b->data, n, c->size);
+ blockSetIOState(b, BioClean);
+ return b;
+ case BioClean:
+ return b;
+ case BioVentiError:
+ blockPut(b);
+ vtSetError("venti i/o error or wrong score, block %V", score);
+ return nil;
+ case BioReadError:
+ blockPut(b);
+ vtSetError("error reading block %V", b->score);
+ return nil;
+ }
+ /* NOT REACHED */
+}
+
+/*
+ * allocate a new on-disk block and load it into the memory cache.
+ * BUG: if the disk is full, should we flush some of it to Venti?
+ */
+static u32int lastAlloc;
+
+Block *
+cacheAllocBlock(Cache *c, int type, u32int tag, u32int epoch, u32int epochLow)
+{
+ FreeList *fl;
+ u32int addr;
+ Block *b;
+ int n, nwrap;
+ Label lab;
+
+ n = c->size / LabelSize;
+ fl = c->fl;
+
+ vtLock(fl->lk);
+ addr = fl->last;
+ b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
+ if(b == nil){
+ fprint(2, "%s: cacheAllocBlock: xxx %R\n", argv0);
+ vtUnlock(fl->lk);
+ return nil;
+ }
+ nwrap = 0;
+ for(;;){
+ if(++addr >= fl->end){
+ addr = 0;
+ if(++nwrap >= 2){
+ blockPut(b);
+ vtSetError("disk is full");
+ /*
+ * try to avoid a continuous spew of console
+ * messages.
+ */
+ if (fl->last != 0)
+ fprint(2, "%s: cacheAllocBlock: xxx1 %R\n",
+ argv0);
+ fl->last = 0;
+ vtUnlock(fl->lk);
+ return nil;
+ }
+ }
+ if(addr%n == 0){
+ blockPut(b);
+ b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
+ if(b == nil){
+ fl->last = addr;
+ fprint(2, "%s: cacheAllocBlock: xxx2 %R\n", argv0);
+ vtUnlock(fl->lk);
+ return nil;
+ }
+ }
+ if(!labelUnpack(&lab, b->data, addr%n))
+ continue;
+ if(lab.state == BsFree)
+ goto Found;
+ if(lab.state&BsClosed)
+ if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose)
+ goto Found;
+ }
+Found:
+ blockPut(b);
+ b = cacheLocal(c, PartData, addr, OOverWrite);
+ if(b == nil){
+ fprint(2, "%s: cacheAllocBlock: xxx3 %R\n", argv0);
+ return nil;
+ }
+assert(b->iostate == BioLabel || b->iostate == BioClean);
+ fl->last = addr;
+ lab.type = type;
+ lab.tag = tag;
+ lab.state = BsAlloc;
+ lab.epoch = epoch;
+ lab.epochClose = ~(u32int)0;
+ if(!blockSetLabel(b, &lab, 1)){
+ fprint(2, "%s: cacheAllocBlock: xxx4 %R\n", argv0);
+ blockPut(b);
+ return nil;
+ }
+ vtZeroExtend(vtType[type], b->data, 0, c->size);
+if(0)diskWrite(c->disk, b);
+
+if(0)fprint(2, "%s: fsAlloc %ud type=%d tag = %ux\n", argv0, addr, type, tag);
+ lastAlloc = addr;
+ fl->nused++;
+ vtUnlock(fl->lk);
+ b->pc = getcallerpc(&c);
+ return b;
+}
+
+int
+cacheDirty(Cache *c)
+{
+ return c->ndirty;
+}
+
+void
+cacheCountUsed(Cache *c, u32int epochLow, u32int *used, u32int *total, u32int *bsize)
+{
+ int n;
+ u32int addr, nused;
+ Block *b;
+ Label lab;
+ FreeList *fl;
+
+ fl = c->fl;
+ n = c->size / LabelSize;
+ *bsize = c->size;
+ vtLock(fl->lk);
+ if(fl->epochLow == epochLow){
+ *used = fl->nused;
+ *total = fl->end;
+ vtUnlock(fl->lk);
+ return;
+ }
+ b = nil;
+ nused = 0;
+ for(addr=0; addr<fl->end; addr++){
+ if(addr%n == 0){
+ blockPut(b);
+ b = cacheLocal(c, PartLabel, addr/n, OReadOnly);
+ if(b == nil){
+ fprint(2, "%s: flCountUsed: loading %ux: %R\n",
+ argv0, addr/n);
+ break;
+ }
+ }
+ if(!labelUnpack(&lab, b->data, addr%n))
+ continue;
+ if(lab.state == BsFree)
+ continue;
+ if(lab.state&BsClosed)
+ if(lab.epochClose <= epochLow || lab.epoch==lab.epochClose)
+ continue;
+ nused++;
+ }
+ blockPut(b);
+ if(addr == fl->end){
+ fl->nused = nused;
+ fl->epochLow = epochLow;
+ }
+ *used = nused;
+ *total = fl->end;
+ vtUnlock(fl->lk);
+ return;
+}
+
+static FreeList *
+flAlloc(u32int end)
+{
+ FreeList *fl;
+
+ fl = vtMemAllocZ(sizeof(*fl));
+ fl->lk = vtLockAlloc();
+ fl->last = 0;
+ fl->end = end;
+ return fl;
+}
+
+static void
+flFree(FreeList *fl)
+{
+ vtLockFree(fl->lk);
+ vtMemFree(fl);
+}
+
+u32int
+cacheLocalSize(Cache *c, int part)
+{
+ return diskSize(c->disk, part);
+}
+
+/*
+ * The thread that has locked b may refer to it by
+ * multiple names. Nlock counts the number of
+ * references the locking thread holds. It will call
+ * blockPut once per reference.
+ */
+void
+blockDupLock(Block *b)
+{
+ assert(b->nlock > 0);
+ b->nlock++;
+}
+
+/*
+ * we're done with the block.
+ * unlock it. can't use it after calling this.
+ */
+void
+blockPut(Block* b)
+{
+ Cache *c;
+
+ if(b == nil)
+ return;
+
+if(0)fprint(2, "%s: blockPut: %d: %d %x %d %s\n", argv0, getpid(), b->part, b->addr, c->nheap, bioStr(b->iostate));
+
+ if(b->iostate == BioDirty)
+ bwatchDependency(b);
+
+ if(--b->nlock > 0)
+ return;
+
+ /*
+ * b->nlock should probably stay at zero while
+ * the block is unlocked, but diskThread and vtSleep
+ * conspire to assume that they can just vtLock(b->lk); blockPut(b),
+ * so we have to keep b->nlock set to 1 even
+ * when the block is unlocked.
+ */
+ assert(b->nlock == 0);
+ b->nlock = 1;
+// b->pc = 0;
+
+ bwatchUnlock(b);
+ vtUnlock(b->lk);
+ c = b->c;
+ vtLock(c->lk);
+
+ if(--b->ref > 0){
+ vtUnlock(c->lk);
+ return;
+ }
+
+ assert(b->ref == 0);
+ switch(b->iostate){
+ default:
+ b->used = c->now++;
+ heapIns(b);
+ break;
+ case BioEmpty:
+ case BioLabel:
+ if(c->nheap == 0)
+ b->used = c->now++;
+ else
+ b->used = c->heap[0]->used;
+ heapIns(b);
+ break;
+ case BioDirty:
+ break;
+ }
+ vtUnlock(c->lk);
+}
+
+/*
+ * set the label associated with a block.
+ */
+Block*
+_blockSetLabel(Block *b, Label *l)
+{
+ int lpb;
+ Block *bb;
+ u32int a;
+ Cache *c;
+
+ c = b->c;
+
+ assert(b->part == PartData);
+ assert(b->iostate == BioLabel || b->iostate == BioClean || b->iostate == BioDirty);
+ lpb = c->size / LabelSize;
+ a = b->addr / lpb;
+ bb = cacheLocal(c, PartLabel, a, OReadWrite);
+ if(bb == nil){
+ blockPut(b);
+ return nil;
+ }
+ b->l = *l;
+ labelPack(l, bb->data, b->addr%lpb);
+ blockDirty(bb);
+ return bb;
+}
+
+int
+blockSetLabel(Block *b, Label *l, int allocating)
+{
+ Block *lb;
+ Label oldl;
+
+ oldl = b->l;
+ lb = _blockSetLabel(b, l);
+ if(lb == nil)
+ return 0;
+
+ /*
+ * If we're allocating the block, make sure the label (bl)
+ * goes to disk before the data block (b) itself. This is to help
+ * the blocks that in turn depend on b.
+ *
+ * Suppose bx depends on (must be written out after) b.
+ * Once we write b we'll think it's safe to write bx.
+ * Bx can't get at b unless it has a valid label, though.
+ *
+ * Allocation is the only case in which having a current label
+ * is vital because:
+ *
+ * - l.type is set at allocation and never changes.
+ * - l.tag is set at allocation and never changes.
+ * - l.state is not checked when we load blocks.
+ * - the archiver cares deeply about l.state being
+ * BaActive vs. BaCopied, but that's handled
+ * by direct calls to _blockSetLabel.
+ */
+
+ if(allocating)
+ blockDependency(b, lb, -1, nil, nil);
+ blockPut(lb);
+ return 1;
+}
+
+/*
+ * Record that bb must be written out before b.
+ * If index is given, we're about to overwrite the score/e
+ * at that index in the block. Save the old value so we
+ * can write a safer ``old'' version of the block if pressed.
+ */
+void
+blockDependency(Block *b, Block *bb, int index, uchar *score, Entry *e)
+{
+ BList *p;
+
+ if(bb->iostate == BioClean)
+ return;
+
+ /*
+ * Dependencies for blocks containing Entry structures
+ * or scores must always be explained. The problem with
+ * only explaining some of them is this. Suppose we have two
+ * dependencies for the same field, the first explained
+ * and the second not. We try to write the block when the first
+ * dependency is not written but the second is. We will roll back
+ * the first change even though the second trumps it.
+ */
+ if(index == -1 && bb->part == PartData)
+ assert(b->l.type == BtData);
+
+ if(bb->iostate != BioDirty){
+ fprint(2, "%s: %d:%x:%d iostate is %d in blockDependency\n",
+ argv0, bb->part, bb->addr, bb->l.type, bb->iostate);
+ abort();
+ }
+
+ p = blistAlloc(bb);
+ if(p == nil)
+ return;
+
+ assert(bb->iostate == BioDirty);
+if(0)fprint(2, "%s: %d:%x:%d depends on %d:%x:%d\n", argv0, b->part, b->addr, b->l.type, bb->part, bb->addr, bb->l.type);
+
+ p->part = bb->part;
+ p->addr = bb->addr;
+ p->type = bb->l.type;
+ p->vers = bb->vers;
+ p->index = index;
+ if(p->index >= 0){
+ /*
+ * This test would just be b->l.type==BtDir except
+ * we need to exclude the super block.
+ */
+ if(b->l.type == BtDir && b->part == PartData)
+ entryPack(e, p->old.entry, 0);
+ else
+ memmove(p->old.score, score, VtScoreSize);
+ }
+ p->next = b->prior;
+ b->prior = p;
+}
+
+/*
+ * Mark an in-memory block as dirty. If there are too many
+ * dirty blocks, start writing some out to disk.
+ *
+ * If there were way too many dirty blocks, we used to
+ * try to do some flushing ourselves, but it's just too dangerous --
+ * it implies that the callers cannot have any of our priors locked,
+ * but this is hard to avoid in some cases.
+ */
+int
+blockDirty(Block *b)
+{
+ Cache *c;
+
+ c = b->c;
+
+ assert(b->part != PartVenti);
+
+ if(b->iostate == BioDirty)
+ return 1;
+ assert(b->iostate == BioClean || b->iostate == BioLabel);
+
+ vtLock(c->lk);
+ b->iostate = BioDirty;
+ c->ndirty++;
+ if(c->ndirty > (c->maxdirty>>1))
+ vtWakeup(c->flush);
+ vtUnlock(c->lk);
+
+ return 1;
+}
+
+/*
+ * We've decided to write out b. Maybe b has some pointers to blocks
+ * that haven't yet been written to disk. If so, construct a slightly out-of-date
+ * copy of b that is safe to write out. (diskThread will make sure the block
+ * remains marked as dirty.)
+ */
+uchar *
+blockRollback(Block *b, uchar *buf)
+{
+ u32int addr;
+ BList *p;
+ Super super;
+
+ /* easy case */
+ if(b->prior == nil)
+ return b->data;
+
+ memmove(buf, b->data, b->c->size);
+ for(p=b->prior; p; p=p->next){
+ /*
+ * we know p->index >= 0 because blockWrite has vetted this block for us.
+ */
+ assert(p->index >= 0);
+ assert(b->part == PartSuper || (b->part == PartData && b->l.type != BtData));
+ if(b->part == PartSuper){
+ assert(p->index == 0);
+ superUnpack(&super, buf);
+ addr = globalToLocal(p->old.score);
+ if(addr == NilBlock){
+ fprint(2, "%s: rolling back super block: "
+ "bad replacement addr %V\n",
+ argv0, p->old.score);
+ abort();
+ }
+ super.active = addr;
+ superPack(&super, buf);
+ continue;
+ }
+ if(b->l.type == BtDir)
+ memmove(buf+p->index*VtEntrySize, p->old.entry, VtEntrySize);
+ else
+ memmove(buf+p->index*VtScoreSize, p->old.score, VtScoreSize);
+ }
+ return buf;
+}
+
+/*
+ * Try to write block b.
+ * If b depends on other blocks:
+ *
+ * If the block has been written out, remove the dependency.
+ * If the dependency is replaced by a more recent dependency,
+ * throw it out.
+ * If we know how to write out an old version of b that doesn't
+ * depend on it, do that.
+ *
+ * Otherwise, bail.
+ */
+int
+blockWrite(Block *b, int waitlock)
+{
+ uchar *dmap;
+ Cache *c;
+ BList *p, **pp;
+ Block *bb;
+ int lockfail;
+
+ c = b->c;
+
+ if(b->iostate != BioDirty)
+ return 1;
+
+ dmap = b->dmap;
+ memset(dmap, 0, c->ndmap);
+ pp = &b->prior;
+ for(p=*pp; p; p=*pp){
+ if(p->index >= 0){
+ /* more recent dependency has succeeded; this one can go */
+ if(dmap[p->index/8] & (1<<(p->index%8)))
+ goto ignblock;
+ }
+
+ lockfail = 0;
+ bb = _cacheLocalLookup(c, p->part, p->addr, p->vers, waitlock,
+ &lockfail);
+ if(bb == nil){
+ if(lockfail)
+ return 0;
+ /* block not in cache => was written already */
+ dmap[p->index/8] |= 1<<(p->index%8);
+ goto ignblock;
+ }
+
+ /*
+ * same version of block is still in cache.
+ *
+ * the assertion is true because the block still has version p->vers,
+ * which means it hasn't been written out since we last saw it.
+ */
+ if(bb->iostate != BioDirty){
+ fprint(2, "%s: %d:%x:%d iostate is %d in blockWrite\n",
+ argv0, bb->part, bb->addr, bb->l.type, bb->iostate);
+ /* probably BioWriting if it happens? */
+ if(bb->iostate == BioClean)
+ goto ignblock;
+ }
+
+ blockPut(bb);
+
+ if(p->index < 0){
+ /*
+ * We don't know how to temporarily undo
+ * b's dependency on bb, so just don't write b yet.
+ */
+ if(0) fprint(2, "%s: blockWrite skipping %d %x %d %d; need to write %d %x %d\n",
+ argv0, b->part, b->addr, b->vers, b->l.type, p->part, p->addr, bb->vers);
+ return 0;
+ }
+ /* keep walking down the list */
+ pp = &p->next;
+ continue;
+
+ignblock:
+ *pp = p->next;
+ blistFree(c, p);
+ continue;
+ }
+
+ /*
+ * DiskWrite must never be called with a double-locked block.
+ * This call to diskWrite is okay because blockWrite is only called
+ * from the cache flush thread, which never double-locks a block.
+ */
+ diskWrite(c->disk, b);
+ return 1;
+}
+
+/*
+ * Change the I/O state of block b.
+ * Just an assignment except for magic in
+ * switch statement (read comments there).
+ */
+void
+blockSetIOState(Block *b, int iostate)
+{
+ int dowakeup;
+ Cache *c;
+ BList *p, *q;
+
+if(0) fprint(2, "%s: iostate part=%d addr=%x %s->%s\n", argv0, b->part, b->addr, bioStr(b->iostate), bioStr(iostate));
+
+ c = b->c;
+
+ dowakeup = 0;
+ switch(iostate){
+ default:
+ abort();
+ case BioEmpty:
+ assert(!b->uhead);
+ break;
+ case BioLabel:
+ assert(!b->uhead);
+ break;
+ case BioClean:
+ bwatchDependency(b);
+ /*
+ * If b->prior is set, it means a write just finished.
+ * The prior list isn't needed anymore.
+ */
+ for(p=b->prior; p; p=q){
+ q = p->next;
+ blistFree(c, p);
+ }
+ b->prior = nil;
+ /*
+ * Freeing a block or just finished a write.
+ * Move the blocks from the per-block unlink
+ * queue to the cache unlink queue.
+ */
+ if(b->iostate == BioDirty || b->iostate == BioWriting){
+ vtLock(c->lk);
+ c->ndirty--;
+ b->iostate = iostate; /* change here to keep in sync with ndirty */
+ b->vers = c->vers++;
+ if(b->uhead){
+ /* add unlink blocks to unlink queue */
+ if(c->uhead == nil){
+ c->uhead = b->uhead;
+ vtWakeup(c->unlink);
+ }else
+ c->utail->next = b->uhead;
+ c->utail = b->utail;
+ b->uhead = nil;
+ }
+ vtUnlock(c->lk);
+ }
+ assert(!b->uhead);
+ dowakeup = 1;
+ break;
+ case BioDirty:
+ /*
+ * Wrote out an old version of the block (see blockRollback).
+ * Bump a version count, leave it dirty.
+ */
+ if(b->iostate == BioWriting){
+ vtLock(c->lk);
+ b->vers = c->vers++;
+ vtUnlock(c->lk);
+ dowakeup = 1;
+ }
+ break;
+ case BioReading:
+ case BioWriting:
+ /*
+ * Adding block to disk queue. Bump reference count.
+ * diskThread decs the count later by calling blockPut.
+ * This is here because we need to lock c->lk to
+ * manipulate the ref count.
+ */
+ vtLock(c->lk);
+ b->ref++;
+ vtUnlock(c->lk);
+ break;
+ case BioReadError:
+ case BioVentiError:
+ /*
+ * Oops.
+ */
+ dowakeup = 1;
+ break;
+ }
+ b->iostate = iostate;
+ /*
+ * Now that the state has changed, we can wake the waiters.
+ */
+ if(dowakeup)
+ vtWakeupAll(b->ioready);
+}
+
+/*
+ * The active file system is a tree of blocks.
+ * When we add snapshots to the mix, the entire file system
+ * becomes a dag and thus requires a bit more care.
+ *
+ * The life of the file system is divided into epochs. A snapshot
+ * ends one epoch and begins the next. Each file system block
+ * is marked with the epoch in which it was created (b.epoch).
+ * When the block is unlinked from the file system (closed), it is marked
+ * with the epoch in which it was removed (b.epochClose).
+ * Once we have discarded or archived all snapshots up to
+ * b.epochClose, we can reclaim the block.
+ *
+ * If a block was created in a past epoch but is not yet closed,
+ * it is treated as copy-on-write. Of course, in order to insert the
+ * new pointer into the tree, the parent must be made writable,
+ * and so on up the tree. The recursion stops because the root
+ * block is always writable.
+ *
+ * If blocks are never closed, they will never be reused, and
+ * we will run out of disk space. But marking a block as closed
+ * requires some care about dependencies and write orderings.
+ *
+ * (1) If a block p points at a copy-on-write block b and we
+ * copy b to create bb, then p must be written out after bb and
+ * lbb (bb's label block).
+ *
+ * (2) We have to mark b as closed, but only after we switch
+ * the pointer, so lb must be written out after p. In fact, we
+ * can't even update the in-memory copy, or the cache might
+ * mistakenly give out b for reuse before p gets written.
+ *
+ * CacheAllocBlock's call to blockSetLabel records a "bb after lbb" dependency.
+ * The caller is expected to record a "p after bb" dependency
+ * to finish (1), and also expected to call blockRemoveLink
+ * to arrange for (2) to happen once p is written.
+ *
+ * Until (2) happens, some pieces of the code (e.g., the archiver)
+ * still need to know whether a block has been copied, so we
+ * set the BsCopied bit in the label and force that to disk *before*
+ * the copy gets written out.
+ */
+Block*
+blockCopy(Block *b, u32int tag, u32int ehi, u32int elo)
+{
+ Block *bb, *lb;
+ Label l;
+
+ if((b->l.state&BsClosed) || b->l.epoch >= ehi)
+ fprint(2, "%s: blockCopy %#ux %L but fs is [%ud,%ud]\n",
+ argv0, b->addr, &b->l, elo, ehi);
+
+ bb = cacheAllocBlock(b->c, b->l.type, tag, ehi, elo);
+ if(bb == nil){
+ blockPut(b);
+ return nil;
+ }
+
+ /*
+ * Update label so we know the block has been copied.
+ * (It will be marked closed once it has been unlinked from
+ * the tree.) This must follow cacheAllocBlock since we
+ * can't be holding onto lb when we call cacheAllocBlock.
+ */
+ if((b->l.state&BsCopied)==0)
+ if(b->part == PartData){ /* not the superblock */
+ l = b->l;
+ l.state |= BsCopied;
+ lb = _blockSetLabel(b, &l);
+ if(lb == nil){
+ /* can't set label => can't copy block */
+ blockPut(b);
+ l.type = BtMax;
+ l.state = BsFree;
+ l.epoch = 0;
+ l.epochClose = 0;
+ l.tag = 0;
+ blockSetLabel(bb, &l, 0);
+ blockPut(bb);
+ return nil;
+ }
+ blockDependency(bb, lb, -1, nil, nil);
+ blockPut(lb);
+ }
+
+ memmove(bb->data, b->data, b->c->size);
+ blockDirty(bb);
+ blockPut(b);
+ return bb;
+}
+
+/*
+ * Block b once pointed at the block bb at addr/type/tag, but no longer does.
+ * If recurse is set, we are unlinking all of bb's children as well.
+ *
+ * We can't reclaim bb (or its kids) until the block b gets written to disk. We add
+ * the relevant information to b's list of unlinked blocks. Once b is written,
+ * the list will be queued for processing.
+ *
+ * If b depends on bb, it doesn't anymore, so we remove bb from the prior list.
+ */
+void
+blockRemoveLink(Block *b, u32int addr, int type, u32int tag, int recurse)
+{
+ BList *p, **pp, bl;
+
+ /* remove bb from prior list */
+ for(pp=&b->prior; (p=*pp)!=nil; ){
+ if(p->part == PartData && p->addr == addr){
+ *pp = p->next;
+ blistFree(b->c, p);
+ }else
+ pp = &p->next;
+ }
+
+ bl.part = PartData;
+ bl.addr = addr;
+ bl.type = type;
+ bl.tag = tag;
+ if(b->l.epoch == 0)
+ assert(b->part == PartSuper);
+ bl.epoch = b->l.epoch;
+ bl.next = nil;
+ bl.recurse = recurse;
+
+ if(b->part == PartSuper && b->iostate == BioClean)
+ p = nil;
+ else
+ p = blistAlloc(b);
+ if(p == nil){
+ /*
+ * b has already been written to disk.
+ */
+ doRemoveLink(b->c, &bl);
+ return;
+ }
+
+ /* Uhead is only processed when the block goes from Dirty -> Clean */
+ assert(b->iostate == BioDirty);
+
+ *p = bl;
+ if(b->uhead == nil)
+ b->uhead = p;
+ else
+ b->utail->next = p;
+ b->utail = p;
+}
+
+/*
+ * Process removal of a single block and perhaps its children.
+ */
+static void
+doRemoveLink(Cache *c, BList *p)
+{
+ int i, n, recurse;
+ u32int a;
+ Block *b;
+ Label l;
+ BList bl;
+
+ recurse = (p->recurse && p->type != BtData && p->type != BtDir);
+
+ /*
+ * We're not really going to overwrite b, but if we're not
+ * going to look at its contents, there is no point in reading
+ * them from the disk.
+ */
+ b = cacheLocalData(c, p->addr, p->type, p->tag, recurse ? OReadOnly : OOverWrite, 0);
+ if(b == nil)
+ return;
+
+ /*
+ * When we're unlinking from the superblock, close with the next epoch.
+ */
+ if(p->epoch == 0)
+ p->epoch = b->l.epoch+1;
+
+ /* sanity check */
+ if(b->l.epoch > p->epoch){
+ fprint(2, "%s: doRemoveLink: strange epoch %ud > %ud\n",
+ argv0, b->l.epoch, p->epoch);
+ blockPut(b);
+ return;
+ }
+
+ if(recurse){
+ n = c->size / VtScoreSize;
+ for(i=0; i<n; i++){
+ a = globalToLocal(b->data + i*VtScoreSize);
+ if(a == NilBlock || !readLabel(c, &l, a))
+ continue;
+ if(l.state&BsClosed)
+ continue;
+ /*
+ * If stack space becomes an issue...
+ p->addr = a;
+ p->type = l.type;
+ p->tag = l.tag;
+ doRemoveLink(c, p);
+ */
+
+ bl.part = PartData;
+ bl.addr = a;
+ bl.type = l.type;
+ bl.tag = l.tag;
+ bl.epoch = p->epoch;
+ bl.next = nil;
+ bl.recurse = 1;
+ /* give up the block lock - share with others */
+ blockPut(b);
+ doRemoveLink(c, &bl);
+ b = cacheLocalData(c, p->addr, p->type, p->tag, OReadOnly, 0);
+ if(b == nil){
+ fprint(2, "%s: warning: lost block in doRemoveLink\n",
+ argv0);
+ return;
+ }
+ }
+ }
+
+ l = b->l;
+ l.state |= BsClosed;
+ l.epochClose = p->epoch;
+ if(l.epochClose == l.epoch){
+ vtLock(c->fl->lk);
+ if(l.epoch == c->fl->epochLow)
+ c->fl->nused--;
+ blockSetLabel(b, &l, 0);
+ vtUnlock(c->fl->lk);
+ }else
+ blockSetLabel(b, &l, 0);
+ blockPut(b);
+}
+
+/*
+ * Allocate a BList so that we can record a dependency
+ * or queue a removal related to block b.
+ * If we can't find a BList, we write out b and return nil.
+ */
+static BList *
+blistAlloc(Block *b)
+{
+ Cache *c;
+ BList *p;
+
+ if(b->iostate != BioDirty){
+ /*
+ * should not happen anymore -
+ * blockDirty used to flush but no longer does.
+ */
+ assert(b->iostate == BioClean);
+ fprint(2, "%s: blistAlloc: called on clean block\n", argv0);
+ return nil;
+ }
+
+ c = b->c;
+ vtLock(c->lk);
+ if(c->blfree == nil){
+ /*
+ * No free BLists. What are our options?
+ */
+
+ /* Block has no priors? Just write it. */
+ if(b->prior == nil){
+ vtUnlock(c->lk);
+ diskWriteAndWait(c->disk, b);
+ return nil;
+ }
+
+ /*
+ * Wake the flush thread, which will hopefully free up
+ * some BLists for us. We used to flush a block from
+ * our own prior list and reclaim that BList, but this is
+ * a no-no: some of the blocks on our prior list may
+ * be locked by our caller. Or maybe their label blocks
+ * are locked by our caller. In any event, it's too hard
+ * to make sure we can do I/O for ourselves. Instead,
+ * we assume the flush thread will find something.
+ * (The flush thread never blocks waiting for a block,
+ * so it can't deadlock like we can.)
+ */
+ while(c->blfree == nil){
+ vtWakeup(c->flush);
+ vtSleep(c->blrend);
+ if(c->blfree == nil)
+ fprint(2, "%s: flushing for blists\n", argv0);
+ }
+ }
+
+ p = c->blfree;
+ c->blfree = p->next;
+ vtUnlock(c->lk);
+ return p;
+}
+
+static void
+blistFree(Cache *c, BList *bl)
+{
+ vtLock(c->lk);
+ bl->next = c->blfree;
+ c->blfree = bl;
+ vtWakeup(c->blrend);
+ vtUnlock(c->lk);
+}
+
+char*
+bsStr(int state)
+{
+ static char s[100];
+
+ if(state == BsFree)
+ return "Free";
+ if(state == BsBad)
+ return "Bad";
+
+ sprint(s, "%x", state);
+ if(!(state&BsAlloc))
+ strcat(s, ",Free"); /* should not happen */
+ if(state&BsCopied)
+ strcat(s, ",Copied");
+ if(state&BsVenti)
+ strcat(s, ",Venti");
+ if(state&BsClosed)
+ strcat(s, ",Closed");
+ return s;
+}
+
+char *
+bioStr(int iostate)
+{
+ switch(iostate){
+ default:
+ return "Unknown!!";
+ case BioEmpty:
+ return "Empty";
+ case BioLabel:
+ return "Label";
+ case BioClean:
+ return "Clean";
+ case BioDirty:
+ return "Dirty";
+ case BioReading:
+ return "Reading";
+ case BioWriting:
+ return "Writing";
+ case BioReadError:
+ return "ReadError";
+ case BioVentiError:
+ return "VentiError";
+ case BioMax:
+ return "Max";
+ }
+}
+
+static char *bttab[] = {
+ "BtData",
+ "BtData+1",
+ "BtData+2",
+ "BtData+3",
+ "BtData+4",
+ "BtData+5",
+ "BtData+6",
+ "BtData+7",
+ "BtDir",
+ "BtDir+1",
+ "BtDir+2",
+ "BtDir+3",
+ "BtDir+4",
+ "BtDir+5",
+ "BtDir+6",
+ "BtDir+7",
+};
+
+char*
+btStr(int type)
+{
+ if(type < nelem(bttab))
+ return bttab[type];
+ return "unknown";
+}
+
+int
+labelFmt(Fmt *f)
+{
+ Label *l;
+
+ l = va_arg(f->args, Label*);
+ return fmtprint(f, "%s,%s,e=%ud,%d,tag=%#ux",
+ btStr(l->type), bsStr(l->state), l->epoch, (int)l->epochClose, l->tag);
+}
+
+int
+scoreFmt(Fmt *f)
+{
+ uchar *v;
+ int i;
+ u32int addr;
+
+ v = va_arg(f->args, uchar*);
+ if(v == nil){
+ fmtprint(f, "*");
+ }else if((addr = globalToLocal(v)) != NilBlock)
+ fmtprint(f, "0x%.8ux", addr);
+ else{
+ for(i = 0; i < VtScoreSize; i++)
+ fmtprint(f, "%2.2ux", v[i]);
+ }
+
+ return 0;
+}
+
+static int
+upHeap(int i, Block *b)
+{
+ Block *bb;
+ u32int now;
+ int p;
+ Cache *c;
+
+ c = b->c;
+ now = c->now;
+ for(; i != 0; i = p){
+ p = (i - 1) >> 1;
+ bb = c->heap[p];
+ if(b->used - now >= bb->used - now)
+ break;
+ c->heap[i] = bb;
+ bb->heap = i;
+ }
+ c->heap[i] = b;
+ b->heap = i;
+
+ return i;
+}
+
+static int
+downHeap(int i, Block *b)
+{
+ Block *bb;
+ u32int now;
+ int k;
+ Cache *c;
+
+ c = b->c;
+ now = c->now;
+ for(; ; i = k){
+ k = (i << 1) + 1;
+ if(k >= c->nheap)
+ break;
+ if(k + 1 < c->nheap && c->heap[k]->used - now > c->heap[k + 1]->used - now)
+ k++;
+ bb = c->heap[k];
+ if(b->used - now <= bb->used - now)
+ break;
+ c->heap[i] = bb;
+ bb->heap = i;
+ }
+ c->heap[i] = b;
+ b->heap = i;
+ return i;
+}
+
+/*
+ * Delete a block from the heap.
+ * Called with c->lk held.
+ */
+static void
+heapDel(Block *b)
+{
+ int i, si;
+ Cache *c;
+
+ c = b->c;
+
+ si = b->heap;
+ if(si == BadHeap)
+ return;
+ b->heap = BadHeap;
+ c->nheap--;
+ if(si == c->nheap)
+ return;
+ b = c->heap[c->nheap];
+ i = upHeap(si, b);
+ if(i == si)
+ downHeap(i, b);
+}
+
+/*
+ * Insert a block into the heap.
+ * Called with c->lk held.
+ */
+static void
+heapIns(Block *b)
+{
+ assert(b->heap == BadHeap);
+ upHeap(b->c->nheap++, b);
+ vtWakeup(b->c->heapwait);
+}
+
+/*
+ * Get just the label for a block.
+ */
+int
+readLabel(Cache *c, Label *l, u32int addr)
+{
+ int lpb;
+ Block *b;
+ u32int a;
+
+ lpb = c->size / LabelSize;
+ a = addr / lpb;
+ b = cacheLocal(c, PartLabel, a, OReadOnly);
+ if(b == nil){
+ blockPut(b);
+ return 0;
+ }
+
+ if(!labelUnpack(l, b->data, addr%lpb)){
+ blockPut(b);
+ return 0;
+ }
+ blockPut(b);
+ return 1;
+}
+
+/*
+ * Process unlink queue.
+ * Called with c->lk held.
+ */
+static void
+unlinkBody(Cache *c)
+{
+ BList *p;
+
+ while(c->uhead != nil){
+ p = c->uhead;
+ c->uhead = p->next;
+ vtUnlock(c->lk);
+ doRemoveLink(c, p);
+ vtLock(c->lk);
+ p->next = c->blfree;
+ c->blfree = p;
+ }
+}
+
+/*
+ * Occasionally unlink the blocks on the cache unlink queue.
+ */
+static void
+unlinkThread(void *a)
+{
+ Cache *c = a;
+
+ vtThreadSetName("unlink");
+
+ vtLock(c->lk);
+ for(;;){
+ while(c->uhead == nil && c->die == nil)
+ vtSleep(c->unlink);
+ if(c->die != nil)
+ break;
+ unlinkBody(c);
+ }
+ c->ref--;
+ vtWakeup(c->die);
+ vtUnlock(c->lk);
+}
+
+static int
+baddrCmp(void *a0, void *a1)
+{
+ BAddr *b0, *b1;
+ b0 = a0;
+ b1 = a1;
+
+ if(b0->part < b1->part)
+ return -1;
+ if(b0->part > b1->part)
+ return 1;
+ if(b0->addr < b1->addr)
+ return -1;
+ if(b0->addr > b1->addr)
+ return 1;
+ return 0;
+}
+
+/*
+ * Scan the block list for dirty blocks; add them to the list c->baddr.
+ */
+static void
+flushFill(Cache *c)
+{
+ int i, ndirty;
+ BAddr *p;
+ Block *b;
+
+ vtLock(c->lk);
+ if(c->ndirty == 0){
+ vtUnlock(c->lk);
+ return;
+ }
+
+ p = c->baddr;
+ ndirty = 0;
+ for(i=0; i<c->nblocks; i++){
+ b = c->blocks + i;
+ if(b->part == PartError)
+ continue;
+ if(b->iostate == BioDirty || b->iostate == BioWriting)
+ ndirty++;
+ if(b->iostate != BioDirty)
+ continue;
+ p->part = b->part;
+ p->addr = b->addr;
+ p->vers = b->vers;
+ p++;
+ }
+ if(ndirty != c->ndirty){
+ fprint(2, "%s: ndirty mismatch expected %d found %d\n",
+ argv0, c->ndirty, ndirty);
+ c->ndirty = ndirty;
+ }
+ vtUnlock(c->lk);
+
+ c->bw = p - c->baddr;
+ qsort(c->baddr, c->bw, sizeof(BAddr), baddrCmp);
+}
+
+/*
+ * This is not thread safe, i.e. it can't be called from multiple threads.
+ *
+ * It's okay how we use it, because it only gets called in
+ * the flushThread. And cacheFree, but only after
+ * cacheFree has killed off the flushThread.
+ */
+static int
+cacheFlushBlock(Cache *c)
+{
+ Block *b;
+ BAddr *p;
+ int lockfail, nfail;
+
+ nfail = 0;
+ for(;;){
+ if(c->br == c->be){
+ if(c->bw == 0 || c->bw == c->be)
+ flushFill(c);
+ c->br = 0;
+ c->be = c->bw;
+ c->bw = 0;
+ c->nflush = 0;
+ }
+
+ if(c->br == c->be)
+ return 0;
+ p = c->baddr + c->br;
+ c->br++;
+ b = _cacheLocalLookup(c, p->part, p->addr, p->vers, Nowaitlock,
+ &lockfail);
+
+ if(b && blockWrite(b, Nowaitlock)){
+ c->nflush++;
+ blockPut(b);
+ return 1;
+ }
+ if(b)
+ blockPut(b);
+
+ /*
+ * Why didn't we write the block?
+ */
+
+ /* Block already written out */
+ if(b == nil && !lockfail)
+ continue;
+
+ /* Failed to acquire lock; sleep if happens a lot. */
+ if(lockfail && ++nfail > 100){
+ sleep(500);
+ nfail = 0;
+ }
+ /* Requeue block. */
+ if(c->bw < c->be)
+ c->baddr[c->bw++] = *p;
+ }
+}
+
+/*
+ * Occasionally flush dirty blocks from memory to the disk.
+ */
+static void
+flushThread(void *a)
+{
+ Cache *c = a;
+ int i;
+
+ vtThreadSetName("flush");
+ vtLock(c->lk);
+ while(c->die == nil){
+ vtSleep(c->flush);
+ vtUnlock(c->lk);
+ for(i=0; i<FlushSize; i++)
+ if(!cacheFlushBlock(c)){
+ /*
+ * If i==0, could be someone is waking us repeatedly
+ * to flush the cache but there's no work to do.
+ * Pause a little.
+ */
+ if(i==0){
+ // fprint(2, "%s: flushthread found "
+ // "nothing to flush - %d dirty\n",
+ // argv0, c->ndirty);
+ sleep(250);
+ }
+ break;
+ }
+ if(i==0 && c->ndirty){
+ /*
+ * All the blocks are being written right now -- there's nothing to do.
+ * We might be spinning with cacheFlush though -- he'll just keep
+ * kicking us until c->ndirty goes down. Probably we should sleep
+ * on something that the diskThread can kick, but for now we'll
+ * just pause for a little while waiting for disks to finish.
+ */
+ sleep(100);
+ }
+ vtLock(c->lk);
+ vtWakeupAll(c->flushwait);
+ }
+ c->ref--;
+ vtWakeup(c->die);
+ vtUnlock(c->lk);
+}
+
+/*
+ * Flush the cache.
+ */
+void
+cacheFlush(Cache *c, int wait)
+{
+ vtLock(c->lk);
+ if(wait){
+ while(c->ndirty){
+ // consPrint("cacheFlush: %d dirty blocks, uhead %p\n",
+ // c->ndirty, c->uhead);
+ vtWakeup(c->flush);
+ vtSleep(c->flushwait);
+ }
+ // consPrint("cacheFlush: done (uhead %p)\n", c->ndirty, c->uhead);
+ }else if(c->ndirty)
+ vtWakeup(c->flush);
+ vtUnlock(c->lk);
+}
+
+/*
+ * Kick the flushThread every 30 seconds.
+ */
+static void
+cacheSync(void *v)
+{
+ Cache *c;
+
+ c = v;
+ cacheFlush(c, 0);
+}
diff --git a/src/cmd/fossil/check.c b/src/cmd/fossil/check.c
new file mode 100644
index 00000000..03ece44a
--- /dev/null
+++ b/src/cmd/fossil/check.c
@@ -0,0 +1,799 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+
+static void checkDirs(Fsck*);
+static void checkEpochs(Fsck*);
+static void checkLeak(Fsck*);
+static void closenop(Fsck*, Block*, u32int);
+static void clrenop(Fsck*, Block*, int);
+static void clrinop(Fsck*, char*, MetaBlock*, int, Block*);
+static void error(Fsck*, char*, ...);
+static int getBit(uchar*, u32int);
+static int printnop(char*, ...);
+static void setBit(uchar*, u32int);
+static int walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize],
+ int type, u32int tag, u32int epoch);
+static void warn(Fsck*, char*, ...);
+
+#pragma varargck argpos error 2
+#pragma varargck argpos printnop 1
+#pragma varargck argpos warn 2
+
+static Fsck*
+checkInit(Fsck *chk)
+{
+ chk->cache = chk->fs->cache;
+ chk->nblocks = cacheLocalSize(chk->cache, PartData);;
+ chk->bsize = chk->fs->blockSize;
+ chk->walkdepth = 0;
+ chk->hint = 0;
+ chk->quantum = chk->nblocks/100;
+ if(chk->quantum == 0)
+ chk->quantum = 1;
+ if(chk->print == nil)
+ chk->print = printnop;
+ if(chk->clre == nil)
+ chk->clre = clrenop;
+ if(chk->close == nil)
+ chk->close = closenop;
+ if(chk->clri == nil)
+ chk->clri = clrinop;
+ return chk;
+}
+
+/*
+ * BUG: Should merge checkEpochs and checkDirs so that
+ * bad blocks are only reported once, and so that errors in checkEpochs
+ * can have the affected file names attached, and so that the file system
+ * is only read once.
+ *
+ * Also should summarize the errors instead of printing for every one
+ * (e.g., XXX bad or unreachable blocks in /active/usr/rsc/foo).
+ */
+
+void
+fsCheck(Fsck *chk)
+{
+ Block *b;
+ Super super;
+
+ checkInit(chk);
+ b = superGet(chk->cache, &super);
+ if(b == nil){
+ chk->print("could not load super block: %R");
+ return;
+ }
+ blockPut(b);
+
+ chk->hint = super.active;
+ checkEpochs(chk);
+
+ chk->smap = vtMemAllocZ(chk->nblocks/8+1);
+ checkDirs(chk);
+ vtMemFree(chk->smap);
+}
+
+static void checkEpoch(Fsck*, u32int);
+
+/*
+ * Walk through all the blocks in the write buffer.
+ * Then we can look for ones we missed -- those are leaks.
+ */
+static void
+checkEpochs(Fsck *chk)
+{
+ u32int e;
+ uint nb;
+
+ nb = chk->nblocks;
+ chk->amap = vtMemAllocZ(nb/8+1);
+ chk->emap = vtMemAllocZ(nb/8+1);
+ chk->xmap = vtMemAllocZ(nb/8+1);
+ chk->errmap = vtMemAllocZ(nb/8+1);
+
+ for(e = chk->fs->ehi; e >= chk->fs->elo; e--){
+ memset(chk->emap, 0, chk->nblocks/8+1);
+ memset(chk->xmap, 0, chk->nblocks/8+1);
+ checkEpoch(chk, e);
+ }
+ checkLeak(chk);
+ vtMemFree(chk->amap);
+ vtMemFree(chk->emap);
+ vtMemFree(chk->xmap);
+ vtMemFree(chk->errmap);
+}
+
+static void
+checkEpoch(Fsck *chk, u32int epoch)
+{
+ u32int a;
+ Block *b;
+ Entry e;
+ Label l;
+
+ chk->print("checking epoch %ud...\n", epoch);
+
+ for(a=0; a<chk->nblocks; a++){
+ if(!readLabel(chk->cache, &l, (a+chk->hint)%chk->nblocks)){
+ error(chk, "could not read label for addr 0x%.8#ux", a);
+ continue;
+ }
+ if(l.tag == RootTag && l.epoch == epoch)
+ break;
+ }
+
+ if(a == chk->nblocks){
+ chk->print("could not find root block for epoch %ud", epoch);
+ return;
+ }
+
+ a = (a+chk->hint)%chk->nblocks;
+ b = cacheLocalData(chk->cache, a, BtDir, RootTag, OReadOnly, 0);
+ if(b == nil){
+ error(chk, "could not read root block 0x%.8#ux: %R", a);
+ return;
+ }
+
+ /* no one should point at root blocks */
+ setBit(chk->amap, a);
+ setBit(chk->emap, a);
+ setBit(chk->xmap, a);
+
+ /*
+ * First entry is the rest of the file system.
+ * Second entry is link to previous epoch root,
+ * just a convenience to help the search.
+ */
+ if(!entryUnpack(&e, b->data, 0)){
+ error(chk, "could not unpack root block 0x%.8#ux: %R", a);
+ blockPut(b);
+ return;
+ }
+ walkEpoch(chk, b, e.score, BtDir, e.tag, epoch);
+ if(entryUnpack(&e, b->data, 1))
+ chk->hint = globalToLocal(e.score);
+ blockPut(b);
+}
+
+/*
+ * When b points at bb, need to check:
+ *
+ * (i) b.e in [bb.e, bb.eClose)
+ * (ii) if b.e==bb.e, then no other b' in e points at bb.
+ * (iii) if !(b.state&Copied) and b.e==bb.e then no other b' points at bb.
+ * (iv) if b is active then no other active b' points at bb.
+ * (v) if b is a past life of b' then only one of b and b' is active
+ * (too hard to check)
+ */
+static int
+walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize], int type, u32int tag,
+ u32int epoch)
+{
+ int i, ret;
+ u32int addr, ep;
+ Block *bb;
+ Entry e;
+
+ if(b && chk->walkdepth == 0 && chk->printblocks)
+ chk->print("%V %d %#.8ux %#.8ux\n", b->score, b->l.type,
+ b->l.tag, b->l.epoch);
+
+ if(!chk->useventi && globalToLocal(score) == NilBlock)
+ return 1;
+
+ chk->walkdepth++;
+
+ bb = cacheGlobal(chk->cache, score, type, tag, OReadOnly);
+ if(bb == nil){
+ error(chk, "could not load block %V type %d tag %ux: %R",
+ score, type, tag);
+ chk->walkdepth--;
+ return 0;
+ }
+ if(chk->printblocks)
+ chk->print("%*s%V %d %#.8ux %#.8ux\n", chk->walkdepth*2, "",
+ score, type, tag, bb->l.epoch);
+
+ ret = 0;
+ addr = globalToLocal(score);
+ if(addr == NilBlock){
+ ret = 1;
+ goto Exit;
+ }
+
+ if(b){
+ /* (i) */
+ if(b->l.epoch < bb->l.epoch || bb->l.epochClose <= b->l.epoch){
+ error(chk, "walk: block %#ux [%ud, %ud) points at %#ux [%ud, %ud)",
+ b->addr, b->l.epoch, b->l.epochClose,
+ bb->addr, bb->l.epoch, bb->l.epochClose);
+ goto Exit;
+ }
+
+ /* (ii) */
+ if(b->l.epoch == epoch && bb->l.epoch == epoch){
+ if(getBit(chk->emap, addr)){
+ error(chk, "walk: epoch join detected: addr %#ux %L",
+ bb->addr, &bb->l);
+ goto Exit;
+ }
+ setBit(chk->emap, addr);
+ }
+
+ /* (iii) */
+ if(!(b->l.state&BsCopied) && b->l.epoch == bb->l.epoch){
+ if(getBit(chk->xmap, addr)){
+ error(chk, "walk: copy join detected; addr %#ux %L",
+ bb->addr, &bb->l);
+ goto Exit;
+ }
+ setBit(chk->xmap, addr);
+ }
+ }
+
+ /* (iv) */
+ if(epoch == chk->fs->ehi){
+ /*
+ * since epoch==fs->ehi is first, amap is same as
+ * ``have seen active''
+ */
+ if(getBit(chk->amap, addr)){
+ error(chk, "walk: active join detected: addr %#ux %L",
+ bb->addr, &bb->l);
+ goto Exit;
+ }
+ if(bb->l.state&BsClosed)
+ error(chk, "walk: addr %#ux: block is in active tree but is closed",
+ addr);
+ }else
+ if(!getBit(chk->amap, addr))
+ if(!(bb->l.state&BsClosed)){
+ // error(chk, "walk: addr %#ux: block is not in active tree, not closed (%d)",
+ // addr, bb->l.epochClose);
+ chk->close(chk, bb, epoch+1);
+ chk->nclose++;
+ }
+
+ if(getBit(chk->amap, addr)){
+ ret = 1;
+ goto Exit;
+ }
+ setBit(chk->amap, addr);
+
+ if(chk->nseen++%chk->quantum == 0)
+ chk->print("check: visited %d/%d blocks (%.0f%%)\n",
+ chk->nseen, chk->nblocks, chk->nseen*100./chk->nblocks);
+
+ b = nil; /* make sure no more refs to parent */
+ USED(b);
+
+ switch(type){
+ default:
+ /* pointer block */
+ for(i = 0; i < chk->bsize/VtScoreSize; i++)
+ if(!walkEpoch(chk, bb, bb->data + i*VtScoreSize,
+ type-1, tag, epoch)){
+ setBit(chk->errmap, bb->addr);
+ chk->clrp(chk, bb, i);
+ chk->nclrp++;
+ }
+ break;
+ case BtData:
+ break;
+ case BtDir:
+ for(i = 0; i < chk->bsize/VtEntrySize; i++){
+ if(!entryUnpack(&e, bb->data, i)){
+ // error(chk, "walk: could not unpack entry: %ux[%d]: %R",
+ // addr, i);
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, i);
+ chk->nclre++;
+ continue;
+ }
+ if(!(e.flags & VtEntryActive))
+ continue;
+if(0) fprint(2, "%x[%d] tag=%x snap=%d score=%V\n",
+ addr, i, e.tag, e.snap, e.score);
+ ep = epoch;
+ if(e.snap != 0){
+ if(e.snap >= epoch){
+ // error(chk, "bad snap in entry: %ux[%d] snap = %ud: epoch = %ud",
+ // addr, i, e.snap, epoch);
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, i);
+ chk->nclre++;
+ continue;
+ }
+ continue;
+ }
+ if(e.flags & VtEntryLocal){
+ if(e.tag < UserTag)
+ if(e.tag != RootTag || tag != RootTag || i != 1){
+ // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
+ // addr, i, e.tag);
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, i);
+ chk->nclre++;
+ continue;
+ }
+ }else
+ if(e.tag != 0){
+ // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
+ // addr, i, e.tag);
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, i);
+ chk->nclre++;
+ continue;
+ }
+ if(!walkEpoch(chk, bb, e.score, entryType(&e),
+ e.tag, ep)){
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, i);
+ chk->nclre++;
+ }
+ }
+ break;
+ }
+
+ ret = 1;
+
+Exit:
+ chk->walkdepth--;
+ blockPut(bb);
+ return ret;
+}
+
+/*
+ * We've just walked the whole write buffer. Notice blocks that
+ * aren't marked available but that we didn't visit. They are lost.
+ */
+static void
+checkLeak(Fsck *chk)
+{
+ u32int a, nfree, nlost;
+ Block *b;
+ Label l;
+
+ nfree = 0;
+ nlost = 0;
+
+ for(a = 0; a < chk->nblocks; a++){
+ if(!readLabel(chk->cache, &l, a)){
+ error(chk, "could not read label: addr 0x%ux %d %d: %R",
+ a, l.type, l.state);
+ continue;
+ }
+ if(getBit(chk->amap, a))
+ continue;
+ if(l.state == BsFree || l.epochClose <= chk->fs->elo ||
+ l.epochClose == l.epoch){
+ nfree++;
+ setBit(chk->amap, a);
+ continue;
+ }
+ if(l.state&BsClosed)
+ continue;
+ nlost++;
+// warn(chk, "unreachable block: addr 0x%ux type %d tag 0x%ux "
+// "state %s epoch %ud close %ud", a, l.type, l.tag,
+// bsStr(l.state), l.epoch, l.epochClose);
+ b = cacheLocal(chk->cache, PartData, a, OReadOnly);
+ if(b == nil){
+ error(chk, "could not read block 0x%#.8ux", a);
+ continue;
+ }
+ chk->close(chk, b, 0);
+ chk->nclose++;
+ setBit(chk->amap, a);
+ blockPut(b);
+ }
+ chk->print("fsys blocks: total=%ud used=%ud(%.1f%%) free=%ud(%.1f%%) lost=%ud(%.1f%%)\n",
+ chk->nblocks,
+ chk->nblocks - nfree-nlost,
+ 100.*(chk->nblocks - nfree - nlost)/chk->nblocks,
+ nfree, 100.*nfree/chk->nblocks,
+ nlost, 100.*nlost/chk->nblocks);
+}
+
+
+/*
+ * Check that all sources in the tree are accessible.
+ */
+static Source *
+openSource(Fsck *chk, Source *s, char *name, uchar *bm, u32int offset,
+ u32int gen, int dir, MetaBlock *mb, int i, Block *b)
+{
+ Source *r;
+
+ r = nil;
+ if(getBit(bm, offset)){
+ warn(chk, "multiple references to source: %s -> %d",
+ name, offset);
+ goto Err;
+ }
+ setBit(bm, offset);
+
+ r = sourceOpen(s, offset, OReadOnly, 0);
+ if(r == nil){
+ warn(chk, "could not open source: %s -> %d: %R", name, offset);
+ goto Err;
+ }
+
+ if(r->gen != gen){
+ warn(chk, "source has been removed: %s -> %d", name, offset);
+ goto Err;
+ }
+
+ if(r->dir != dir){
+ warn(chk, "dir mismatch: %s -> %d", name, offset);
+ goto Err;
+ }
+ return r;
+Err:
+ chk->clri(chk, name, mb, i, b);
+ chk->nclri++;
+ if(r)
+ sourceClose(r);
+ return nil;
+}
+
+typedef struct MetaChunk MetaChunk;
+struct MetaChunk {
+ ushort offset;
+ ushort size;
+ ushort index;
+};
+
+static int
+offsetCmp(void *s0, void *s1)
+{
+ MetaChunk *mc0, *mc1;
+
+ mc0 = s0;
+ mc1 = s1;
+ if(mc0->offset < mc1->offset)
+ return -1;
+ if(mc0->offset > mc1->offset)
+ return 1;
+ return 0;
+}
+
+/*
+ * Fsck that MetaBlock has reasonable header, sorted entries,
+ */
+static int
+chkMetaBlock(MetaBlock *mb)
+{
+ MetaChunk *mc;
+ int oo, o, n, i;
+ uchar *p;
+
+ mc = vtMemAlloc(mb->nindex*sizeof(MetaChunk));
+ p = mb->buf + MetaHeaderSize;
+ for(i = 0; i < mb->nindex; i++){
+ mc[i].offset = p[0]<<8 | p[1];
+ mc[i].size = p[2]<<8 | p[3];
+ mc[i].index = i;
+ p += MetaIndexSize;
+ }
+
+ qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
+
+ /* check block looks ok */
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ o = oo;
+ n = 0;
+ for(i = 0; i < mb->nindex; i++){
+ o = mc[i].offset;
+ n = mc[i].size;
+ if(o < oo)
+ goto Err;
+ oo += n;
+ }
+ if(o+n > mb->size || mb->size - oo != mb->free)
+ goto Err;
+
+ vtMemFree(mc);
+ return 1;
+
+Err:
+if(0){
+ fprint(2, "metaChunks failed!\n");
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ for(i=0; i<mb->nindex; i++){
+ fprint(2, "\t%d: %d %d\n", i, mc[i].offset,
+ mc[i].offset + mc[i].size);
+ oo += mc[i].size;
+ }
+ fprint(2, "\tused=%d size=%d free=%d free2=%d\n",
+ oo, mb->size, mb->free, mb->size - oo);
+}
+ vtMemFree(mc);
+ return 0;
+}
+
+static void
+scanSource(Fsck *chk, char *name, Source *r)
+{
+ u32int a, nb, o;
+ Block *b;
+ Entry e;
+
+ if(!chk->useventi && globalToLocal(r->score)==NilBlock)
+ return;
+ if(!sourceGetEntry(r, &e)){
+ error(chk, "could not get entry for %s", name);
+ return;
+ }
+ a = globalToLocal(e.score);
+ if(!chk->useventi && a==NilBlock)
+ return;
+ if(getBit(chk->smap, a))
+ return;
+ setBit(chk->smap, a);
+
+ nb = (sourceGetSize(r) + r->dsize-1) / r->dsize;
+ for(o = 0; o < nb; o++){
+ b = sourceBlock(r, o, OReadOnly);
+ if(b == nil){
+ error(chk, "could not read block in data file %s", name);
+ continue;
+ }
+ if(b->addr != NilBlock && getBit(chk->errmap, b->addr)){
+ warn(chk, "previously reported error in block %ux is in file %s",
+ b->addr, name);
+ }
+ blockPut(b);
+ }
+}
+
+/*
+ * Walk the source tree making sure that the BtData
+ * sources containing directory entries are okay.
+ */
+static void
+chkDir(Fsck *chk, char *name, Source *source, Source *meta)
+{
+ int i;
+ u32int a1, a2, nb, o;
+ char *s, *nn;
+ uchar *bm;
+ Block *b, *bb;
+ DirEntry de;
+ Entry e1, e2;
+ MetaBlock mb;
+ MetaEntry me;
+ Source *r, *mr;
+
+ if(!chk->useventi && globalToLocal(source->score)==NilBlock &&
+ globalToLocal(meta->score)==NilBlock)
+ return;
+
+ if(!sourceLock2(source, meta, OReadOnly)){
+ warn(chk, "could not lock sources for %s: %R", name);
+ return;
+ }
+ if(!sourceGetEntry(source, &e1) || !sourceGetEntry(meta, &e2)){
+ warn(chk, "could not load entries for %s: %R", name);
+ return;
+ }
+ a1 = globalToLocal(e1.score);
+ a2 = globalToLocal(e2.score);
+ if((!chk->useventi && a1==NilBlock && a2==NilBlock)
+ || (getBit(chk->smap, a1) && getBit(chk->smap, a2))){
+ sourceUnlock(source);
+ sourceUnlock(meta);
+ return;
+ }
+ setBit(chk->smap, a1);
+ setBit(chk->smap, a2);
+
+ bm = vtMemAllocZ(sourceGetDirSize(source)/8 + 1);
+
+ nb = (sourceGetSize(meta) + meta->dsize - 1)/meta->dsize;
+ for(o = 0; o < nb; o++){
+ b = sourceBlock(meta, o, OReadOnly);
+ if(b == nil){
+ error(chk, "could not read block in meta file: %s[%ud]: %R",
+ name, o);
+ continue;
+ }
+if(0) fprint(2, "source %V:%d block %d addr %d\n", source->score,
+ source->offset, o, b->addr);
+ if(b->addr != NilBlock && getBit(chk->errmap, b->addr))
+ warn(chk, "previously reported error in block %ux is in %s",
+ b->addr, name);
+
+ if(!mbUnpack(&mb, b->data, meta->dsize)){
+ error(chk, "could not unpack meta block: %s[%ud]: %R",
+ name, o);
+ blockPut(b);
+ continue;
+ }
+ if(!chkMetaBlock(&mb)){
+ error(chk, "bad meta block: %s[%ud]: %R", name, o);
+ blockPut(b);
+ continue;
+ }
+ s = nil;
+ for(i=mb.nindex-1; i>=0; i--){
+ meUnpack(&me, &mb, i);
+ if(!deUnpack(&de, &me)){
+ error(chk,
+ "could not unpack dir entry: %s[%ud][%d]: %R",
+ name, o, i);
+ continue;
+ }
+ if(s && strcmp(s, de.elem) <= 0)
+ error(chk,
+ "dir entry out of order: %s[%ud][%d] = %s last = %s",
+ name, o, i, de.elem, s);
+ vtMemFree(s);
+ s = vtStrDup(de.elem);
+ nn = smprint("%s/%s", name, de.elem);
+ if(nn == nil){
+ error(chk, "out of memory");
+ continue;
+ }
+ if(chk->printdirs)
+ if(de.mode&ModeDir)
+ chk->print("%s/\n", nn);
+ if(chk->printfiles)
+ if(!(de.mode&ModeDir))
+ chk->print("%s\n", nn);
+ if(!(de.mode & ModeDir)){
+ r = openSource(chk, source, nn, bm, de.entry,
+ de.gen, 0, &mb, i, b);
+ if(r != nil){
+ if(sourceLock(r, OReadOnly)){
+ scanSource(chk, nn, r);
+ sourceUnlock(r);
+ }
+ sourceClose(r);
+ }
+ deCleanup(&de);
+ free(nn);
+ continue;
+ }
+
+ r = openSource(chk, source, nn, bm, de.entry,
+ de.gen, 1, &mb, i, b);
+ if(r == nil){
+ deCleanup(&de);
+ free(nn);
+ continue;
+ }
+
+ mr = openSource(chk, source, nn, bm, de.mentry,
+ de.mgen, 0, &mb, i, b);
+ if(mr == nil){
+ sourceClose(r);
+ deCleanup(&de);
+ free(nn);
+ continue;
+ }
+
+ if(!(de.mode&ModeSnapshot) || chk->walksnapshots)
+ chkDir(chk, nn, r, mr);
+
+ sourceClose(mr);
+ sourceClose(r);
+ deCleanup(&de);
+ free(nn);
+ deCleanup(&de);
+
+ }
+ vtMemFree(s);
+ blockPut(b);
+ }
+
+ nb = sourceGetDirSize(source);
+ for(o=0; o<nb; o++){
+ if(getBit(bm, o))
+ continue;
+ r = sourceOpen(source, o, OReadOnly, 0);
+ if(r == nil)
+ continue;
+ warn(chk, "non referenced entry in source %s[%d]", name, o);
+ if((bb = sourceBlock(source, o/(source->dsize/VtEntrySize),
+ OReadOnly)) != nil){
+ if(bb->addr != NilBlock){
+ setBit(chk->errmap, bb->addr);
+ chk->clre(chk, bb, o%(source->dsize/VtEntrySize));
+ chk->nclre++;
+ }
+ blockPut(bb);
+ }
+ sourceClose(r);
+ }
+
+ sourceUnlock(source);
+ sourceUnlock(meta);
+ vtMemFree(bm);
+}
+
+static void
+checkDirs(Fsck *chk)
+{
+ Source *r, *mr;
+
+ sourceLock(chk->fs->source, OReadOnly);
+ r = sourceOpen(chk->fs->source, 0, OReadOnly, 0);
+ mr = sourceOpen(chk->fs->source, 1, OReadOnly, 0);
+ sourceUnlock(chk->fs->source);
+ chkDir(chk, "", r, mr);
+
+ sourceClose(r);
+ sourceClose(mr);
+}
+
+static void
+setBit(uchar *bmap, u32int addr)
+{
+ if(addr == NilBlock)
+ return;
+
+ bmap[addr>>3] |= 1 << (addr & 7);
+}
+
+static int
+getBit(uchar *bmap, u32int addr)
+{
+ if(addr == NilBlock)
+ return 0;
+
+ return (bmap[addr>>3] >> (addr & 7)) & 1;
+}
+
+static void
+error(Fsck *chk, char *fmt, ...)
+{
+ char buf[256];
+ va_list arg;
+ static int nerr;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, arg);
+ va_end(arg);
+
+ chk->print("error: %s\n", buf);
+
+// if(nerr++ > 20)
+// vtFatal("too many errors");
+}
+
+static void
+warn(Fsck *chk, char *fmt, ...)
+{
+ char buf[256];
+ va_list arg;
+ static int nerr;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof buf, fmt, arg);
+ va_end(arg);
+
+ chk->print("error: %s\n", buf);
+}
+
+static void
+clrenop(Fsck*, Block*, int)
+{
+}
+
+static void
+closenop(Fsck*, Block*, u32int)
+{
+}
+
+static void
+clrinop(Fsck*, char*, MetaBlock*, int, Block*)
+{
+}
+
+static int
+printnop(char*, ...)
+{
+ return 0;
+}
diff --git a/src/cmd/fossil/dat.h b/src/cmd/fossil/dat.h
new file mode 100644
index 00000000..9c9a5707
--- /dev/null
+++ b/src/cmd/fossil/dat.h
@@ -0,0 +1,332 @@
+typedef struct Arch Arch;
+typedef struct BList BList;
+typedef struct Block Block;
+typedef struct Cache Cache;
+typedef struct Disk Disk;
+typedef struct Entry Entry;
+typedef struct Fsck Fsck;
+typedef struct Header Header;
+typedef struct Label Label;
+typedef struct Periodic Periodic;
+typedef struct Snap Snap;
+typedef struct Source Source;
+typedef struct Super Super;
+typedef struct WalkPtr WalkPtr;
+
+#pragma incomplete Arch
+#pragma incomplete BList
+#pragma incomplete Cache
+#pragma incomplete Disk
+#pragma incomplete Periodic
+#pragma incomplete Snap
+
+/* tunable parameters - probably should not be constants */
+enum {
+ /*
+ * estimate of bytes per dir entries - determines number
+ * of index entries in the block
+ */
+ BytesPerEntry = 100,
+ /* don't allocate in block if more than this percentage full */
+ FullPercentage = 80,
+ FlushSize = 200, /* number of blocks to flush */
+ DirtyPercentage = 50, /* maximum percentage of dirty blocks */
+};
+
+enum {
+ Nowaitlock,
+ Waitlock,
+
+ NilBlock = (~0UL),
+ MaxBlock = (1UL<<31),
+};
+
+enum {
+ HeaderMagic = 0x3776ae89,
+ HeaderVersion = 1,
+ HeaderOffset = 128*1024,
+ HeaderSize = 512,
+ SuperMagic = 0x2340a3b1,
+ SuperSize = 512,
+ SuperVersion = 1,
+ LabelSize = 14,
+};
+
+/* well known tags */
+enum {
+ BadTag = 0, /* this tag should not be used */
+ RootTag = 1, /* root of fs */
+ EnumTag, /* root of a dir listing */
+ UserTag = 32, /* all other tags should be >= UserTag */
+};
+
+struct Super {
+ u16int version;
+ u32int epochLow;
+ u32int epochHigh;
+ u64int qid; /* next qid */
+ u32int active; /* root of active file system */
+ u32int next; /* root of next snapshot to archive */
+ u32int current; /* root of snapshot currently archiving */
+ uchar last[VtScoreSize]; /* last snapshot successfully archived */
+ char name[128]; /* label */
+};
+
+
+struct Fs {
+ Arch *arch; /* immutable */
+ Cache *cache; /* immutable */
+ int mode; /* immutable */
+ int noatimeupd; /* immutable */
+ int blockSize; /* immutable */
+ VtSession *z; /* immutable */
+ Snap *snap; /* immutable */
+ /* immutable; copy here & Fsys to ease error reporting */
+ char *name;
+
+ Periodic *metaFlush; /* periodically flushes metadata cached in files */
+
+ /*
+ * epoch lock.
+ * Most operations on the fs require a read lock of elk, ensuring that
+ * the current high and low epochs do not change under foot.
+ * This lock is mostly acquired via a call to fileLock or fileRlock.
+ * Deletion and creation of snapshots occurs under a write lock of elk,
+ * ensuring no file operations are occurring concurrently.
+ */
+ VtLock *elk; /* epoch lock */
+ u32int ehi; /* epoch high */
+ u32int elo; /* epoch low */
+
+ int halted; /* epoch lock is held to halt (console initiated) */
+
+ Source *source; /* immutable: root of sources */
+ File *file; /* immutable: root of files */
+};
+
+/*
+ * variant on VtEntry
+ * there are extra fields when stored locally
+ */
+struct Entry {
+ u32int gen; /* generation number */
+ ushort psize; /* pointer block size */
+ ushort dsize; /* data block size */
+ uchar depth; /* unpacked from flags */
+ uchar flags;
+ uvlong size;
+ uchar score[VtScoreSize];
+ u32int tag; /* tag for local blocks: zero if stored on Venti */
+ u32int snap; /* non-zero -> entering snapshot of given epoch */
+ uchar archive; /* archive this snapshot: only valid for snap != 0 */
+};
+
+/*
+ * This is called a `stream' in the fossil paper. There used to be Sinks too.
+ * We believe that Sources and Files are one-to-one.
+ */
+struct Source {
+ Fs *fs; /* immutable */
+ int mode; /* immutable */
+ int issnapshot; /* immutable */
+ u32int gen; /* immutable */
+ int dsize; /* immutable */
+ int dir; /* immutable */
+
+ Source *parent; /* immutable */
+ File *file; /* immutable; point back */
+
+ VtLock *lk;
+ int ref;
+ /*
+ * epoch for the source
+ * for ReadWrite sources, epoch is used to lazily notice
+ * sources that must be split from the snapshots.
+ * for ReadOnly sources, the epoch represents the minimum epoch
+ * along the chain from the root, and is used to lazily notice
+ * sources that have become invalid because they belong to an old
+ * snapshot.
+ */
+ u32int epoch;
+ Block *b; /* block containing this source */
+ uchar score[VtScoreSize]; /* score of block containing this source */
+ u32int scoreEpoch; /* epoch of block containing this source */
+ int epb; /* immutable: entries per block in parent */
+ u32int tag; /* immutable: tag of parent */
+ u32int offset; /* immutable: entry offset in parent */
+};
+
+
+struct Header {
+ ushort version;
+ ushort blockSize;
+ ulong super; /* super blocks */
+ ulong label; /* start of labels */
+ ulong data; /* end of labels - start of data blocks */
+ ulong end; /* end of data blocks */
+};
+
+/*
+ * contains a one block buffer
+ * to avoid problems of the block changing underfoot
+ * and to enable an interface that supports unget.
+ */
+struct DirEntryEnum {
+ File *file;
+
+ u32int boff; /* block offset */
+
+ int i, n;
+ DirEntry *buf;
+};
+
+/* Block states */
+enum {
+ BsFree = 0, /* available for allocation */
+ BsBad = 0xFF, /* something is wrong with this block */
+
+ /* bit fields */
+ BsAlloc = 1<<0, /* block is in use */
+ BsCopied = 1<<1,/* block has been copied (usually in preparation for unlink) */
+ BsVenti = 1<<2, /* block has been stored on Venti */
+ BsClosed = 1<<3,/* block has been unlinked on disk from active file system */
+ BsMask = BsAlloc|BsCopied|BsVenti|BsClosed,
+};
+
+/*
+ * block types
+ * more regular than Venti block types
+ * bit 3 -> block or data block
+ * bits 2-0 -> level of block
+ */
+enum {
+ BtData,
+ BtDir = 1<<3,
+ BtLevelMask = 7,
+ BtMax = 1<<4,
+};
+
+/* io states */
+enum {
+ BioEmpty, /* label & data are not valid */
+ BioLabel, /* label is good */
+ BioClean, /* data is on the disk */
+ BioDirty, /* data is not yet on the disk */
+ BioReading, /* in process of reading data */
+ BioWriting, /* in process of writing data */
+ BioReadError, /* error reading: assume disk always handles write errors */
+ BioVentiError, /* error reading from venti (probably disconnected) */
+ BioMax
+};
+
+struct Label {
+ uchar type;
+ uchar state;
+ u32int tag;
+ u32int epoch;
+ u32int epochClose;
+};
+
+struct Block {
+ Cache *c;
+ int ref;
+ int nlock;
+ uintptr pc; /* pc that fetched this block from the cache */
+
+ VtLock *lk;
+
+ int part;
+ u32int addr;
+ uchar score[VtScoreSize]; /* score */
+ Label l;
+
+ uchar *dmap;
+
+ uchar *data;
+
+ /* the following is private; used by cache */
+
+ Block *next; /* doubly linked hash chains */
+ Block **prev;
+ u32int heap; /* index in heap table */
+ u32int used; /* last reference times */
+
+ u32int vers; /* version of dirty flag */
+
+ BList *uhead; /* blocks to unlink when this block is written */
+ BList *utail;
+
+ /* block ordering for cache -> disk */
+ BList *prior; /* list of blocks before this one */
+
+ Block *ionext;
+ int iostate;
+ VtRendez *ioready;
+};
+
+/* tree walker, for gc and archiver */
+struct WalkPtr
+{
+ uchar *data;
+ int isEntry;
+ int n;
+ int m;
+ Entry e;
+ uchar type;
+ u32int tag;
+};
+
+enum
+{
+ DoClose = 1<<0,
+ DoClre = 1<<1,
+ DoClri = 1<<2,
+ DoClrp = 1<<3,
+};
+
+struct Fsck
+{
+ /* filled in by caller */
+ int printblocks;
+ int useventi;
+ int flags;
+ int printdirs;
+ int printfiles;
+ int walksnapshots;
+ int walkfs;
+ Fs *fs;
+ int (*print)(char*, ...);
+ void (*clre)(Fsck*, Block*, int);
+ void (*clrp)(Fsck*, Block*, int);
+ void (*close)(Fsck*, Block*, u32int);
+ void (*clri)(Fsck*, char*, MetaBlock*, int, Block*);
+
+ /* used internally */
+ Cache *cache;
+ uchar *amap; /* all blocks seen so far */
+ uchar *emap; /* all blocks seen in this epoch */
+ uchar *xmap; /* all blocks in this epoch with parents in this epoch */
+ uchar *errmap; /* blocks with errors */
+ uchar *smap; /* walked sources */
+ int nblocks;
+ int bsize;
+ int walkdepth;
+ u32int hint; /* where the next root probably is */
+ int nseen;
+ int quantum;
+ int nclre;
+ int nclrp;
+ int nclose;
+ int nclri;
+};
+
+/* disk partitions; keep in sync with partname[] in disk.c */
+enum {
+ PartError,
+ PartSuper,
+ PartLabel,
+ PartData,
+ PartVenti, /* fake partition */
+};
+
+extern vtType[BtMax];
diff --git a/src/cmd/fossil/disk.c b/src/cmd/fossil/disk.c
new file mode 100644
index 00000000..3ae43fb6
--- /dev/null
+++ b/src/cmd/fossil/disk.c
@@ -0,0 +1,406 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+static void diskThread(void *a);
+
+enum {
+ /*
+ * disable measurement since it gets alignment faults on BG
+ * and the guts used to be commented out.
+ */
+ Timing = 0, /* flag */
+ QueueSize = 100, /* maximum block to queue */
+};
+
+struct Disk {
+ VtLock *lk;
+ int ref;
+
+ int fd;
+ Header h;
+
+ VtRendez *flow;
+ VtRendez *starve;
+ VtRendez *flush;
+ VtRendez *die;
+
+ int nqueue;
+
+ Block *cur; /* block to do on current scan */
+ Block *next; /* blocks to do next scan */
+};
+
+/* keep in sync with Part* enum in dat.h */
+static char *partname[] = {
+ [PartError] "error",
+ [PartSuper] "super",
+ [PartLabel] "label",
+ [PartData] "data",
+ [PartVenti] "venti",
+};
+
+Disk *
+diskAlloc(int fd)
+{
+ u8int buf[HeaderSize];
+ Header h;
+ Disk *disk;
+
+ if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize){
+ vtSetError("short read: %r");
+ vtOSError();
+ return nil;
+ }
+
+ if(!headerUnpack(&h, buf)){
+ vtSetError("bad disk header");
+ return nil;
+ }
+ disk = vtMemAllocZ(sizeof(Disk));
+ disk->lk = vtLockAlloc();
+ disk->starve = vtRendezAlloc(disk->lk);
+ disk->flow = vtRendezAlloc(disk->lk);
+ disk->flush = vtRendezAlloc(disk->lk);
+ disk->fd = fd;
+ disk->h = h;
+
+ disk->ref = 2;
+ vtThread(diskThread, disk);
+
+ return disk;
+}
+
+void
+diskFree(Disk *disk)
+{
+ diskFlush(disk);
+
+ /* kill slave */
+ vtLock(disk->lk);
+ disk->die = vtRendezAlloc(disk->lk);
+ vtWakeup(disk->starve);
+ while(disk->ref > 1)
+ vtSleep(disk->die);
+ vtUnlock(disk->lk);
+ vtRendezFree(disk->flow);
+ vtRendezFree(disk->starve);
+ vtRendezFree(disk->die);
+ vtLockFree(disk->lk);
+ close(disk->fd);
+ vtMemFree(disk);
+}
+
+static u32int
+partStart(Disk *disk, int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return disk->h.super;
+ case PartLabel:
+ return disk->h.label;
+ case PartData:
+ return disk->h.data;
+ }
+}
+
+
+static u32int
+partEnd(Disk *disk, int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return disk->h.super+1;
+ case PartLabel:
+ return disk->h.data;
+ case PartData:
+ return disk->h.end;
+ }
+}
+
+int
+diskReadRaw(Disk *disk, int part, u32int addr, uchar *buf)
+{
+ ulong start, end;
+ u64int offset;
+ int n, nn;
+
+ start = partStart(disk, part);
+ end = partEnd(disk, part);
+
+ if(addr >= end-start){
+ vtSetError(EBadAddr);
+ return 0;
+ }
+
+ offset = ((u64int)(addr + start))*disk->h.blockSize;
+ n = disk->h.blockSize;
+ while(n > 0){
+ nn = pread(disk->fd, buf, n, offset);
+ if(nn < 0){
+ vtOSError();
+ return 0;
+ }
+ if(nn == 0){
+ vtSetError("eof reading disk");
+ return 0;
+ }
+ n -= nn;
+ offset += nn;
+ buf += nn;
+ }
+ return 1;
+}
+
+int
+diskWriteRaw(Disk *disk, int part, u32int addr, uchar *buf)
+{
+ ulong start, end;
+ u64int offset;
+ int n;
+
+ start = partStart(disk, part);
+ end = partEnd(disk, part);
+
+ if(addr >= end - start){
+ vtSetError(EBadAddr);
+ return 0;
+ }
+
+ offset = ((u64int)(addr + start))*disk->h.blockSize;
+ n = pwrite(disk->fd, buf, disk->h.blockSize, offset);
+ if(n < 0){
+ vtOSError();
+ return 0;
+ }
+ if(n < disk->h.blockSize) {
+ vtSetError("short write");
+ return 0;
+ }
+
+ return 1;
+}
+
+static void
+diskQueue(Disk *disk, Block *b)
+{
+ Block **bp, *bb;
+
+ vtLock(disk->lk);
+ while(disk->nqueue >= QueueSize)
+ vtSleep(disk->flow);
+ if(disk->cur == nil || b->addr > disk->cur->addr)
+ bp = &disk->cur;
+ else
+ bp = &disk->next;
+
+ for(bb=*bp; bb; bb=*bp){
+ if(b->addr < bb->addr)
+ break;
+ bp = &bb->ionext;
+ }
+ b->ionext = bb;
+ *bp = b;
+ if(disk->nqueue == 0)
+ vtWakeup(disk->starve);
+ disk->nqueue++;
+ vtUnlock(disk->lk);
+}
+
+
+void
+diskRead(Disk *disk, Block *b)
+{
+ assert(b->iostate == BioEmpty || b->iostate == BioLabel);
+ blockSetIOState(b, BioReading);
+ diskQueue(disk, b);
+}
+
+void
+diskWrite(Disk *disk, Block *b)
+{
+ assert(b->nlock == 1);
+ assert(b->iostate == BioDirty);
+ blockSetIOState(b, BioWriting);
+ diskQueue(disk, b);
+}
+
+void
+diskWriteAndWait(Disk *disk, Block *b)
+{
+ int nlock;
+
+ /*
+ * If b->nlock > 1, the block is aliased within
+ * a single thread. That thread is us.
+ * DiskWrite does some funny stuff with VtLock
+ * and blockPut that basically assumes b->nlock==1.
+ * We humor diskWrite by temporarily setting
+ * nlock to 1. This needs to be revisited.
+ */
+ nlock = b->nlock;
+ if(nlock > 1)
+ b->nlock = 1;
+ diskWrite(disk, b);
+ while(b->iostate != BioClean)
+ vtSleep(b->ioready);
+ b->nlock = nlock;
+}
+
+int
+diskBlockSize(Disk *disk)
+{
+ return disk->h.blockSize; /* immuttable */
+}
+
+int
+diskFlush(Disk *disk)
+{
+ Dir dir;
+
+ vtLock(disk->lk);
+ while(disk->nqueue > 0)
+ vtSleep(disk->flush);
+ vtUnlock(disk->lk);
+
+ /* there really should be a cleaner interface to flush an fd */
+ nulldir(&dir);
+ if(dirfwstat(disk->fd, &dir) < 0){
+ vtOSError();
+ return 0;
+ }
+ return 1;
+}
+
+u32int
+diskSize(Disk *disk, int part)
+{
+ return partEnd(disk, part) - partStart(disk, part);
+}
+
+static uintptr
+mypc(int x)
+{
+ return getcallerpc(&x);
+}
+
+static char *
+disk2file(Disk *disk)
+{
+ static char buf[256];
+
+ if (fd2path(disk->fd, buf, sizeof buf) < 0)
+ strncpy(buf, "GOK", sizeof buf);
+ return buf;
+}
+
+static void
+diskThread(void *a)
+{
+ Disk *disk = a;
+ Block *b;
+ uchar *buf, *p;
+ double t;
+ int nio;
+
+ vtThreadSetName("disk");
+
+//fprint(2, "diskThread %d\n", getpid());
+
+ buf = vtMemAlloc(disk->h.blockSize);
+
+ vtLock(disk->lk);
+ if (Timing) {
+ nio = 0;
+ t = -nsec();
+ }
+ for(;;){
+ while(disk->nqueue == 0){
+ if (Timing) {
+ t += nsec();
+ if(nio >= 10000){
+ fprint(2, "disk: io=%d at %.3fms\n",
+ nio, t*1e-6/nio);
+ nio = 0;
+ t = 0;
+ }
+ }
+ if(disk->die != nil)
+ goto Done;
+ vtSleep(disk->starve);
+ if (Timing)
+ t -= nsec();
+ }
+ assert(disk->cur != nil || disk->next != nil);
+
+ if(disk->cur == nil){
+ disk->cur = disk->next;
+ disk->next = nil;
+ }
+ b = disk->cur;
+ disk->cur = b->ionext;
+ vtUnlock(disk->lk);
+
+ /*
+ * no one should hold onto blocking in the
+ * reading or writing state, so this lock should
+ * not cause deadlock.
+ */
+if(0)fprint(2, "fossil: diskThread: %d:%d %x\n", getpid(), b->part, b->addr);
+ bwatchLock(b);
+ vtLock(b->lk);
+ b->pc = mypc(0);
+ assert(b->nlock == 1);
+ switch(b->iostate){
+ default:
+ abort();
+ case BioReading:
+ if(!diskReadRaw(disk, b->part, b->addr, b->data)){
+ fprint(2, "fossil: diskReadRaw failed: %s: "
+ "score %V: part=%s block %ud: %r\n",
+ disk2file(disk), b->score,
+ partname[b->part], b->addr);
+ blockSetIOState(b, BioReadError);
+ }else
+ blockSetIOState(b, BioClean);
+ break;
+ case BioWriting:
+ p = blockRollback(b, buf);
+ /* NB: ctime result ends with a newline */
+ if(!diskWriteRaw(disk, b->part, b->addr, p)){
+ fprint(2, "fossil: diskWriteRaw failed: %s: "
+ "score %V: date %s part=%s block %ud: %r\n",
+ disk2file(disk), b->score,
+ ctime(time(0)),
+ partname[b->part], b->addr);
+ break;
+ }
+ if(p != buf)
+ blockSetIOState(b, BioClean);
+ else
+ blockSetIOState(b, BioDirty);
+ break;
+ }
+
+ blockPut(b); /* remove extra reference, unlock */
+ vtLock(disk->lk);
+ disk->nqueue--;
+ if(disk->nqueue == QueueSize-1)
+ vtWakeup(disk->flow);
+ if(disk->nqueue == 0)
+ vtWakeup(disk->flush);
+ if(Timing)
+ nio++;
+ }
+Done:
+//fprint(2, "diskThread done\n");
+ disk->ref--;
+ vtWakeup(disk->die);
+ vtUnlock(disk->lk);
+ vtMemFree(buf);
+}
diff --git a/src/cmd/fossil/dump.c b/src/cmd/fossil/dump.c
new file mode 100644
index 00000000..4ad4f469
--- /dev/null
+++ b/src/cmd/fossil/dump.c
@@ -0,0 +1,86 @@
+/*
+ * Clumsy hack to take snapshots and dumps.
+ */
+#include <u.h>
+#include <libc.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: fossil/dump [-i snap-interval] [-n name] fscons /n/fossil\n");
+ exits("usage");
+}
+
+char*
+snapnow(void)
+{
+ Tm t;
+ static char buf[100];
+
+ t = *localtime(time(0)-5*60*60); /* take dumps at 5:00 am */
+
+ sprint(buf, "archive/%d/%02d%02d", t.year+1900, t.mon+1, t.mday);
+ return buf;
+}
+
+void
+main(int argc, char **argv)
+{
+ int onlyarchive, cons, s;
+ ulong t, i;
+ char *name;
+
+ name = "main";
+ s = 0;
+ onlyarchive = 0;
+ i = 60*60; /* one hour */
+ ARGBEGIN{
+ case 'i':
+ i = atoi(EARGF(usage()));
+ if(i == 0){
+ onlyarchive = 1;
+ i = 60*60;
+ }
+ break;
+ case 'n':
+ name = EARGF(usage());
+ break;
+ case 's':
+ s = atoi(EARGF(usage()));
+ break;
+ }ARGEND
+
+ if(argc != 2)
+ usage();
+
+ if((cons = open(argv[0], OWRITE)) < 0)
+ sysfatal("open %s: %r", argv[0]);
+
+ if(chdir(argv[1]) < 0)
+ sysfatal("chdir %s: %r", argv[1]);
+
+ rfork(RFNOTEG);
+ switch(fork()){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ break;
+ default:
+ exits(0);
+ }
+
+ /*
+ * pause at boot time to let clock stabilize.
+ */
+ if(s)
+ sleep(s*1000);
+
+ for(;;){
+ if(access(snapnow(), AEXIST) < 0)
+ fprint(cons, "\nfsys %s snap -a\n", name);
+ t = time(0);
+ sleep((i - t%i)*1000+200);
+ if(!onlyarchive)
+ fprint(cons, "\nfsys %s snap\n", name);
+ }
+}
diff --git a/src/cmd/fossil/epoch.c b/src/cmd/fossil/epoch.c
new file mode 100644
index 00000000..6aa73bbf
--- /dev/null
+++ b/src/cmd/fossil/epoch.c
@@ -0,0 +1,51 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+
+uchar buf[65536];
+
+void
+usage(void)
+{
+ fprint(2, "usage: fossil/epoch fs [new-low-epoch]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd;
+ Header h;
+ Super s;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc == 0 || argc > 2)
+ usage();
+
+ if((fd = open(argv[0], argc==2 ? ORDWR : OREAD)) < 0)
+ sysfatal("open %s: %r", argv[0]);
+
+ if(pread(fd, buf, HeaderSize, HeaderOffset) != HeaderSize)
+ sysfatal("reading header: %r");
+ if(!headerUnpack(&h, buf))
+ sysfatal("unpacking header: %r");
+
+ if(pread(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
+ sysfatal("reading super block: %r");
+
+ if(!superUnpack(&s, buf))
+ sysfatal("unpacking super block: %r");
+
+ print("epoch %d\n", s.epochLow);
+ if(argc == 2){
+ s.epochLow = strtoul(argv[1], 0, 0);
+ superPack(&s, buf);
+ if(pwrite(fd, buf, h.blockSize, (vlong)h.super*h.blockSize) != h.blockSize)
+ sysfatal("writing super block: %r");
+ }
+ exits(0);
+}
diff --git a/src/cmd/fossil/error.c b/src/cmd/fossil/error.c
new file mode 100644
index 00000000..3a0b701f
--- /dev/null
+++ b/src/cmd/fossil/error.c
@@ -0,0 +1,38 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+char EBadAddr[] = "illegal block address";
+char EBadDir[] = "corrupted directory entry";
+char EBadEntry[] = "corrupted file entry";
+char EBadLabel[] = "corrupted block label";
+char EBadMeta[] = "corrupted meta data";
+char EBadMode[] = "illegal mode";
+char EBadOffset[] = "illegal offset";
+char EBadPath[] = "illegal path element";
+char EBadRoot[] = "root of file system is corrupted";
+char EBadSuper[] = "corrupted super block";
+char EBlockTooBig[] = "block too big";
+char ECacheFull[] = "no free blocks in memory cache";
+char EConvert[] = "protocol botch";
+char EExists[] = "file already exists";
+char EFsFill[] = "file system is full";
+char EIO[] = "i/o error";
+char EInUse[] = "file is in use";
+char ELabelMismatch[] = "block label mismatch";
+char ENilBlock[] = "illegal block address";
+char ENoDir[] = "directory entry is not allocated";
+char ENoFile[] = "file does not exist";
+char ENotDir[] = "not a directory";
+char ENotEmpty[] = "directory not empty";
+char ENotFile[] = "not a file";
+char EReadOnly[] = "file is read only";
+char ERemoved[] = "file has been removed";
+char ENotArchived[] = "file is not archived";
+char EResize[] = "only support truncation to zero length";
+char ERoot[] = "cannot remove root";
+char ESnapOld[] = "snapshot has been deleted";
+char ESnapRO[] = "snapshot is read only";
+char ETooBig[] = "file too big";
+char EVentiIO[] = "venti i/o error";
diff --git a/src/cmd/fossil/error.h b/src/cmd/fossil/error.h
new file mode 100644
index 00000000..a57d21e5
--- /dev/null
+++ b/src/cmd/fossil/error.h
@@ -0,0 +1,33 @@
+extern char EBadAddr[];
+extern char EBadDir[];
+extern char EBadEntry[];
+extern char EBadLabel[];
+extern char EBadMeta[];
+extern char EBadMode[];
+extern char EBadOffset[];
+extern char EBadPath[];
+extern char EBadRoot[];
+extern char EBadSuper[];
+extern char EBlockTooBig[];
+extern char ECacheFull[];
+extern char EConvert[];
+extern char EExists[];
+extern char EFsFill[];
+extern char EIO[];
+extern char EInUse[];
+extern char ELabelMismatch[];
+extern char ENilBlock[];
+extern char ENoDir[];
+extern char ENoFile[];
+extern char ENotDir[];
+extern char ENotEmpty[];
+extern char ENotFile[];
+extern char EReadOnly[];
+extern char ERemoved[];
+extern char ENotArchived[];
+extern char EResize[];
+extern char ERoot[];
+extern char ESnapOld[];
+extern char ESnapRO[];
+extern char ETooBig[];
+extern char EVentiIO[];
diff --git a/src/cmd/fossil/file.c b/src/cmd/fossil/file.c
new file mode 100644
index 00000000..5566df33
--- /dev/null
+++ b/src/cmd/fossil/file.c
@@ -0,0 +1,1860 @@
+#include "stdinc.h"
+#include "9.h" /* for consPrint */
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * locking order is upwards. A thread can hold the lock for a File
+ * and then acquire the lock of its parent
+ */
+
+struct File {
+ Fs *fs; /* immutable */
+
+ /* meta data for file: protected by the lk in the parent */
+ int ref; /* holds this data structure up */
+
+ int partial; /* file was never really open */
+ int removed; /* file has been removed */
+ int dirty; /* dir is dirty with respect to meta data in block */
+ u32int boff; /* block offset within msource for this file's meta data */
+
+ DirEntry dir; /* meta data for this file, including component name */
+
+ File *up; /* parent file (directory) */
+ File *next; /* sibling */
+
+ /* data for file */
+ VtLock *lk; /* lock for the following */
+ Source *source;
+ Source *msource; /* for directories: meta data for children */
+ File *down; /* children */
+
+ int mode;
+ int issnapshot;
+};
+
+static int fileMetaFlush2(File*, char*);
+static u32int fileMetaAlloc(File*, DirEntry*, u32int);
+static int fileRLock(File*);
+static void fileRUnlock(File*);
+static int fileLock(File*);
+static void fileUnlock(File*);
+static void fileMetaLock(File*);
+static void fileMetaUnlock(File*);
+static void fileRAccess(File*);
+static void fileWAccess(File*, char*);
+
+static File *
+fileAlloc(Fs *fs)
+{
+ File *f;
+
+ f = vtMemAllocZ(sizeof(File));
+ f->lk = vtLockAlloc();
+ f->ref = 1;
+ f->fs = fs;
+ f->boff = NilBlock;
+ f->mode = fs->mode;
+ return f;
+}
+
+static void
+fileFree(File *f)
+{
+ sourceClose(f->source);
+ vtLockFree(f->lk);
+ sourceClose(f->msource);
+ deCleanup(&f->dir);
+
+ memset(f, ~0, sizeof(File));
+ vtMemFree(f);
+}
+
+/*
+ * the file is locked already
+ * f->msource is unlocked
+ */
+static File *
+dirLookup(File *f, char *elem)
+{
+ int i;
+ MetaBlock mb;
+ MetaEntry me;
+ Block *b;
+ Source *meta;
+ File *ff;
+ u32int bo, nb;
+
+ meta = f->msource;
+ b = nil;
+ if(!sourceLock(meta, -1))
+ return nil;
+ nb = (sourceGetSize(meta)+meta->dsize-1)/meta->dsize;
+ for(bo=0; bo<nb; bo++){
+ b = sourceBlock(meta, bo, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, meta->dsize))
+ goto Err;
+ if(mbSearch(&mb, elem, &i, &me)){
+ ff = fileAlloc(f->fs);
+ if(!deUnpack(&ff->dir, &me)){
+ fileFree(ff);
+ goto Err;
+ }
+ sourceUnlock(meta);
+ blockPut(b);
+ ff->boff = bo;
+ ff->mode = f->mode;
+ ff->issnapshot = f->issnapshot;
+ return ff;
+ }
+
+ blockPut(b);
+ b = nil;
+ }
+ vtSetError(ENoFile);
+ /* fall through */
+Err:
+ sourceUnlock(meta);
+ blockPut(b);
+ return nil;
+}
+
+File *
+fileRoot(Source *r)
+{
+ Block *b;
+ Source *r0, *r1, *r2;
+ MetaBlock mb;
+ MetaEntry me;
+ File *root, *mr;
+ Fs *fs;
+
+ b = nil;
+ root = nil;
+ mr = nil;
+ r1 = nil;
+ r2 = nil;
+
+ fs = r->fs;
+ if(!sourceLock(r, -1))
+ return nil;
+ r0 = sourceOpen(r, 0, fs->mode, 0);
+ if(r0 == nil)
+ goto Err;
+ r1 = sourceOpen(r, 1, fs->mode, 0);
+ if(r1 == nil)
+ goto Err;
+ r2 = sourceOpen(r, 2, fs->mode, 0);
+ if(r2 == nil)
+ goto Err;
+
+ mr = fileAlloc(fs);
+ mr->msource = r2;
+ r2 = nil;
+
+ root = fileAlloc(fs);
+ root->boff = 0;
+ root->up = mr;
+ root->source = r0;
+ r0->file = root; /* point back to source */
+ r0 = nil;
+ root->msource = r1;
+ r1 = nil;
+
+ mr->down = root;
+
+ if(!sourceLock(mr->msource, -1))
+ goto Err;
+ b = sourceBlock(mr->msource, 0, OReadOnly);
+ sourceUnlock(mr->msource);
+ if(b == nil)
+ goto Err;
+
+ if(!mbUnpack(&mb, b->data, mr->msource->dsize))
+ goto Err;
+
+ meUnpack(&me, &mb, 0);
+ if(!deUnpack(&root->dir, &me))
+ goto Err;
+ blockPut(b);
+ sourceUnlock(r);
+ fileRAccess(root);
+
+ return root;
+Err:
+ blockPut(b);
+ if(r0)
+ sourceClose(r0);
+ if(r1)
+ sourceClose(r1);
+ if(r2)
+ sourceClose(r2);
+ if(mr)
+ fileFree(mr);
+ if(root)
+ fileFree(root);
+ sourceUnlock(r);
+
+ return nil;
+}
+
+static Source *
+fileOpenSource(File *f, u32int offset, u32int gen, int dir, uint mode,
+ int issnapshot)
+{
+ char *rname, *fname;
+ Source *r;
+
+ if(!sourceLock(f->source, mode))
+ return nil;
+ r = sourceOpen(f->source, offset, mode, issnapshot);
+ sourceUnlock(f->source);
+ if(r == nil)
+ return nil;
+ if(r->gen != gen){
+ vtSetError(ERemoved);
+ goto Err;
+ }
+ if(r->dir != dir && r->mode != -1){
+ /* this hasn't been as useful as we hoped it would be. */
+ rname = sourceName(r);
+ fname = fileName(f);
+ consPrint("%s: source %s for file %s: fileOpenSource: "
+ "dir mismatch %d %d\n",
+ f->source->fs->name, rname, fname, r->dir, dir);
+ free(rname);
+ free(fname);
+
+ vtSetError(EBadMeta);
+ goto Err;
+ }
+ return r;
+Err:
+ sourceClose(r);
+ return nil;
+}
+
+File *
+_fileWalk(File *f, char *elem, int partial)
+{
+ File *ff;
+
+ fileRAccess(f);
+
+ if(elem[0] == 0){
+ vtSetError(EBadPath);
+ return nil;
+ }
+
+ if(!fileIsDir(f)){
+ vtSetError(ENotDir);
+ return nil;
+ }
+
+ if(strcmp(elem, ".") == 0){
+ return fileIncRef(f);
+ }
+
+ if(strcmp(elem, "..") == 0){
+ if(fileIsRoot(f))
+ return fileIncRef(f);
+ return fileIncRef(f->up);
+ }
+
+ if(!fileLock(f))
+ return nil;
+
+ for(ff = f->down; ff; ff=ff->next){
+ if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
+ ff->ref++;
+ goto Exit;
+ }
+ }
+
+ ff = dirLookup(f, elem);
+ if(ff == nil)
+ goto Err;
+
+ if(ff->dir.mode & ModeSnapshot){
+ ff->mode = OReadOnly;
+ ff->issnapshot = 1;
+ }
+
+ if(partial){
+ /*
+ * Do nothing. We're opening this file only so we can clri it.
+ * Usually the sources can't be opened, hence we won't even bother.
+ * Be VERY careful with the returned file. If you hand it to a routine
+ * expecting ff->source and/or ff->msource to be non-nil, we're
+ * likely to dereference nil. FileClri should be the only routine
+ * setting partial.
+ */
+ ff->partial = 1;
+ }else if(ff->dir.mode & ModeDir){
+ ff->source = fileOpenSource(f, ff->dir.entry, ff->dir.gen,
+ 1, ff->mode, ff->issnapshot);
+ ff->msource = fileOpenSource(f, ff->dir.mentry, ff->dir.mgen,
+ 0, ff->mode, ff->issnapshot);
+ if(ff->source == nil || ff->msource == nil)
+ goto Err;
+ }else{
+ ff->source = fileOpenSource(f, ff->dir.entry, ff->dir.gen,
+ 0, ff->mode, ff->issnapshot);
+ if(ff->source == nil)
+ goto Err;
+ }
+
+ /* link in and up parent ref count */
+ if (ff->source)
+ ff->source->file = ff; /* point back */
+ ff->next = f->down;
+ f->down = ff;
+ ff->up = f;
+ fileIncRef(f);
+Exit:
+ fileUnlock(f);
+ return ff;
+Err:
+ fileUnlock(f);
+ if(ff != nil)
+ fileDecRef(ff);
+ return nil;
+}
+
+File *
+fileWalk(File *f, char *elem)
+{
+ return _fileWalk(f, elem, 0);
+}
+
+File *
+_fileOpen(Fs *fs, char *path, int partial)
+{
+ File *f, *ff;
+ char *p, elem[VtMaxStringSize], *opath;
+ int n;
+
+ f = fs->file;
+ fileIncRef(f);
+ opath = path;
+ while(*path != 0){
+ for(p = path; *p && *p != '/'; p++)
+ ;
+ n = p - path;
+ if(n > 0){
+ if(n > VtMaxStringSize){
+ vtSetError("%s: element too long", EBadPath);
+ goto Err;
+ }
+ memmove(elem, path, n);
+ elem[n] = 0;
+ ff = _fileWalk(f, elem, partial && *p=='\0');
+ if(ff == nil){
+ vtSetError("%.*s: %R", utfnlen(opath, p-opath),
+ opath);
+ goto Err;
+ }
+ fileDecRef(f);
+ f = ff;
+ }
+ if(*p == '/')
+ p++;
+ path = p;
+ }
+ return f;
+Err:
+ fileDecRef(f);
+ return nil;
+}
+
+File*
+fileOpen(Fs *fs, char *path)
+{
+ return _fileOpen(fs, path, 0);
+}
+
+static void
+fileSetTmp(File *f, int istmp)
+{
+ int i;
+ Entry e;
+ Source *r;
+
+ for(i=0; i<2; i++){
+ if(i==0)
+ r = f->source;
+ else
+ r = f->msource;
+ if(r == nil)
+ continue;
+ if(!sourceGetEntry(r, &e)){
+ fprint(2, "sourceGetEntry failed (cannot happen): %r\n");
+ continue;
+ }
+ if(istmp)
+ e.flags |= VtEntryNoArchive;
+ else
+ e.flags &= ~VtEntryNoArchive;
+ if(!sourceSetEntry(r, &e)){
+ fprint(2, "sourceSetEntry failed (cannot happen): %r\n");
+ continue;
+ }
+ }
+}
+
+File *
+fileCreate(File *f, char *elem, ulong mode, char *uid)
+{
+ File *ff;
+ DirEntry *dir;
+ Source *pr, *r, *mr;
+ int isdir;
+
+ if(!fileLock(f))
+ return nil;
+
+ r = nil;
+ mr = nil;
+ for(ff = f->down; ff; ff=ff->next){
+ if(strcmp(elem, ff->dir.elem) == 0 && !ff->removed){
+ ff = nil;
+ vtSetError(EExists);
+ goto Err1;
+ }
+ }
+
+ ff = dirLookup(f, elem);
+ if(ff != nil){
+ vtSetError(EExists);
+ goto Err1;
+ }
+
+ pr = f->source;
+ if(pr->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err1;
+ }
+
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err1;
+
+ ff = fileAlloc(f->fs);
+ isdir = mode & ModeDir;
+
+ r = sourceCreate(pr, pr->dsize, isdir, 0);
+ if(r == nil)
+ goto Err;
+ if(isdir){
+ mr = sourceCreate(pr, pr->dsize, 0, r->offset);
+ if(mr == nil)
+ goto Err;
+ }
+
+ dir = &ff->dir;
+ dir->elem = vtStrDup(elem);
+ dir->entry = r->offset;
+ dir->gen = r->gen;
+ if(isdir){
+ dir->mentry = mr->offset;
+ dir->mgen = mr->gen;
+ }
+ dir->size = 0;
+ if(!fsNextQid(f->fs, &dir->qid))
+ goto Err;
+ dir->uid = vtStrDup(uid);
+ dir->gid = vtStrDup(f->dir.gid);
+ dir->mid = vtStrDup(uid);
+ dir->mtime = time(0L);
+ dir->mcount = 0;
+ dir->ctime = dir->mtime;
+ dir->atime = dir->mtime;
+ dir->mode = mode;
+
+ ff->boff = fileMetaAlloc(f, dir, 0);
+ if(ff->boff == NilBlock)
+ goto Err;
+
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+
+ ff->source = r;
+ r->file = ff; /* point back */
+ ff->msource = mr;
+
+ if(mode&ModeTemporary){
+ if(!sourceLock2(r, mr, -1))
+ goto Err1;
+ fileSetTmp(ff, 1);
+ sourceUnlock(r);
+ if(mr)
+ sourceUnlock(mr);
+ }
+
+ /* committed */
+
+ /* link in and up parent ref count */
+ ff->next = f->down;
+ f->down = ff;
+ ff->up = f;
+ fileIncRef(f);
+
+ fileWAccess(f, uid);
+
+ fileUnlock(f);
+ return ff;
+
+Err:
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+Err1:
+ if(r){
+ sourceLock(r, -1);
+ sourceRemove(r);
+ }
+ if(mr){
+ sourceLock(mr, -1);
+ sourceRemove(mr);
+ }
+ if(ff)
+ fileDecRef(ff);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileRead(File *f, void *buf, int cnt, vlong offset)
+{
+ Source *s;
+ uvlong size;
+ u32int bn;
+ int off, dsize, n, nn;
+ Block *b;
+ uchar *p;
+
+if(0)fprint(2, "fileRead: %s %d, %lld\n", f->dir.elem, cnt, offset);
+
+ if(!fileRLock(f))
+ return -1;
+
+ if(offset < 0){
+ vtSetError(EBadOffset);
+ goto Err1;
+ }
+
+ fileRAccess(f);
+
+ if(!sourceLock(f->source, OReadOnly))
+ goto Err1;
+
+ s = f->source;
+ dsize = s->dsize;
+ size = sourceGetSize(s);
+
+ if(offset >= size)
+ offset = size;
+
+ if(cnt > size-offset)
+ cnt = size-offset;
+ bn = offset/dsize;
+ off = offset%dsize;
+ p = buf;
+ while(cnt > 0){
+ b = sourceBlock(s, bn, OReadOnly);
+ if(b == nil)
+ goto Err;
+ n = cnt;
+ if(n > dsize-off)
+ n = dsize-off;
+ nn = dsize-off;
+ if(nn > n)
+ nn = n;
+ memmove(p, b->data+off, nn);
+ memset(p+nn, 0, nn-n);
+ off = 0;
+ bn++;
+ cnt -= n;
+ p += n;
+ blockPut(b);
+ }
+ sourceUnlock(s);
+ fileRUnlock(f);
+ return p-(uchar*)buf;
+
+Err:
+ sourceUnlock(s);
+Err1:
+ fileRUnlock(f);
+ return -1;
+}
+
+/*
+ * Changes the file block bn to be the given block score.
+ * Very sneaky. Only used by flfmt.
+ */
+int
+fileMapBlock(File *f, ulong bn, uchar score[VtScoreSize], ulong tag)
+{
+ Block *b;
+ Entry e;
+ Source *s;
+
+ if(!fileLock(f))
+ return 0;
+
+ s = nil;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+
+ if(!sourceLock(f->source, -1))
+ goto Err;
+
+ s = f->source;
+ b = _sourceBlock(s, bn, OReadWrite, 1, tag);
+ if(b == nil)
+ goto Err;
+
+ if(!sourceGetEntry(s, &e))
+ goto Err;
+ if(b->l.type == BtDir){
+ memmove(e.score, score, VtScoreSize);
+ assert(e.tag == tag || e.tag == 0);
+ e.tag = tag;
+ e.flags |= VtEntryLocal;
+ entryPack(&e, b->data, f->source->offset % f->source->epb);
+ }else
+ memmove(b->data + (bn%(e.psize/VtScoreSize))*VtScoreSize, score, VtScoreSize);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(s);
+ fileUnlock(f);
+ return 1;
+
+Err:
+ if(s)
+ sourceUnlock(s);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileSetSize(File *f, uvlong size)
+{
+ int r;
+
+ if(!fileLock(f))
+ return 0;
+ r = 0;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+ if(!sourceLock(f->source, -1))
+ goto Err;
+ r = sourceSetSize(f->source, size);
+ sourceUnlock(f->source);
+Err:
+ fileUnlock(f);
+ return r;
+}
+
+int
+fileWrite(File *f, void *buf, int cnt, vlong offset, char *uid)
+{
+ Source *s;
+ ulong bn;
+ int off, dsize, n;
+ Block *b;
+ uchar *p;
+ vlong eof;
+
+if(0)fprint(2, "fileWrite: %s %d, %lld\n", f->dir.elem, cnt, offset);
+
+ if(!fileLock(f))
+ return -1;
+
+ s = nil;
+ if(f->dir.mode & ModeDir){
+ vtSetError(ENotFile);
+ goto Err;
+ }
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err;
+ }
+ if(offset < 0){
+ vtSetError(EBadOffset);
+ goto Err;
+ }
+
+ fileWAccess(f, uid);
+
+ if(!sourceLock(f->source, -1))
+ goto Err;
+ s = f->source;
+ dsize = s->dsize;
+
+ eof = sourceGetSize(s);
+ if(f->dir.mode & ModeAppend)
+ offset = eof;
+ bn = offset/dsize;
+ off = offset%dsize;
+ p = buf;
+ while(cnt > 0){
+ n = cnt;
+ if(n > dsize-off)
+ n = dsize-off;
+ b = sourceBlock(s, bn, n<dsize?OReadWrite:OOverWrite);
+ if(b == nil){
+ if(offset > eof)
+ sourceSetSize(s, offset);
+ goto Err;
+ }
+ memmove(b->data+off, p, n);
+ off = 0;
+ cnt -= n;
+ p += n;
+ offset += n;
+ bn++;
+ blockDirty(b);
+ blockPut(b);
+ }
+ if(offset > eof && !sourceSetSize(s, offset))
+ goto Err;
+ sourceUnlock(s);
+ fileUnlock(f);
+ return p-(uchar*)buf;
+Err:
+ if(s)
+ sourceUnlock(s);
+ fileUnlock(f);
+ return -1;
+}
+
+int
+fileGetDir(File *f, DirEntry *dir)
+{
+ if(!fileRLock(f))
+ return 0;
+
+ fileMetaLock(f);
+ deCopy(dir, &f->dir);
+ fileMetaUnlock(f);
+
+ if(!fileIsDir(f)){
+ if(!sourceLock(f->source, OReadOnly)){
+ fileRUnlock(f);
+ return 0;
+ }
+ dir->size = sourceGetSize(f->source);
+ sourceUnlock(f->source);
+ }
+ fileRUnlock(f);
+
+ return 1;
+}
+
+int
+fileTruncate(File *f, char *uid)
+{
+ if(fileIsDir(f)){
+ vtSetError(ENotFile);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileUnlock(f);
+ return 0;
+ }
+ if(!sourceLock(f->source, -1)){
+ fileUnlock(f);
+ return 0;
+ }
+ if(!sourceTruncate(f->source)){
+ sourceUnlock(f->source);
+ fileUnlock(f);
+ return 0;
+ }
+ sourceUnlock(f->source);
+ fileUnlock(f);
+
+ fileWAccess(f, uid);
+
+ return 1;
+}
+
+int
+fileSetDir(File *f, DirEntry *dir, char *uid)
+{
+ File *ff;
+ char *oelem;
+ u32int mask;
+ u64int size;
+
+ /* can not set permissions for the root */
+ if(fileIsRoot(f)){
+ vtSetError(ERoot);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileUnlock(f);
+ return 0;
+ }
+
+ fileMetaLock(f);
+
+ /* check new name does not already exist */
+ if(strcmp(f->dir.elem, dir->elem) != 0){
+ for(ff = f->up->down; ff; ff=ff->next){
+ if(strcmp(dir->elem, ff->dir.elem) == 0 && !ff->removed){
+ vtSetError(EExists);
+ goto Err;
+ }
+ }
+
+ ff = dirLookup(f->up, dir->elem);
+ if(ff != nil){
+ fileDecRef(ff);
+ vtSetError(EExists);
+ goto Err;
+ }
+ }
+
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err;
+ if(!fileIsDir(f)){
+ size = sourceGetSize(f->source);
+ if(size != dir->size){
+ if(!sourceSetSize(f->source, dir->size)){
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(f->msource);
+ goto Err;
+ }
+ /* commited to changing it now */
+ }
+ }
+ /* commited to changing it now */
+ if((f->dir.mode&ModeTemporary) != (dir->mode&ModeTemporary))
+ fileSetTmp(f, dir->mode&ModeTemporary);
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(f->msource);
+
+ oelem = nil;
+ if(strcmp(f->dir.elem, dir->elem) != 0){
+ oelem = f->dir.elem;
+ f->dir.elem = vtStrDup(dir->elem);
+ }
+
+ if(strcmp(f->dir.uid, dir->uid) != 0){
+ vtMemFree(f->dir.uid);
+ f->dir.uid = vtStrDup(dir->uid);
+ }
+
+ if(strcmp(f->dir.gid, dir->gid) != 0){
+ vtMemFree(f->dir.gid);
+ f->dir.gid = vtStrDup(dir->gid);
+ }
+
+ f->dir.mtime = dir->mtime;
+ f->dir.atime = dir->atime;
+
+//fprint(2, "mode %x %x ", f->dir.mode, dir->mode);
+ mask = ~(ModeDir|ModeSnapshot);
+ f->dir.mode &= ~mask;
+ f->dir.mode |= mask & dir->mode;
+ f->dirty = 1;
+//fprint(2, "->%x\n", f->dir.mode);
+
+ fileMetaFlush2(f, oelem);
+ vtMemFree(oelem);
+
+ fileMetaUnlock(f);
+ fileUnlock(f);
+
+ fileWAccess(f->up, uid);
+
+ return 1;
+Err:
+ fileMetaUnlock(f);
+ fileUnlock(f);
+ return 0;
+}
+
+int
+fileSetQidSpace(File *f, u64int offset, u64int max)
+{
+ int ret;
+
+ if(!fileLock(f))
+ return 0;
+ fileMetaLock(f);
+ f->dir.qidSpace = 1;
+ f->dir.qidOffset = offset;
+ f->dir.qidMax = max;
+ ret = fileMetaFlush2(f, nil)>=0;
+ fileMetaUnlock(f);
+ fileUnlock(f);
+ return ret;
+}
+
+
+uvlong
+fileGetId(File *f)
+{
+ /* immutable */
+ return f->dir.qid;
+}
+
+ulong
+fileGetMcount(File *f)
+{
+ ulong mcount;
+
+ fileMetaLock(f);
+ mcount = f->dir.mcount;
+ fileMetaUnlock(f);
+ return mcount;
+}
+
+ulong
+fileGetMode(File *f)
+{
+ ulong mode;
+
+ fileMetaLock(f);
+ mode = f->dir.mode;
+ fileMetaUnlock(f);
+ return mode;
+}
+
+int
+fileIsDir(File *f)
+{
+ /* immutable */
+ return (f->dir.mode & ModeDir) != 0;
+}
+
+int
+fileIsAppend(File *f)
+{
+ return (f->dir.mode & ModeAppend) != 0;
+}
+
+int
+fileIsExclusive(File *f)
+{
+ return (f->dir.mode & ModeExclusive) != 0;
+}
+
+int
+fileIsTemporary(File *f)
+{
+ return (f->dir.mode & ModeTemporary) != 0;
+}
+
+int
+fileIsRoot(File *f)
+{
+ return f == f->fs->file;
+}
+
+int
+fileIsRoFs(File *f)
+{
+ return f->fs->mode == OReadOnly;
+}
+
+int
+fileGetSize(File *f, uvlong *size)
+{
+ if(!fileRLock(f))
+ return 0;
+ if(!sourceLock(f->source, OReadOnly)){
+ fileRUnlock(f);
+ return 0;
+ }
+ *size = sourceGetSize(f->source);
+ sourceUnlock(f->source);
+ fileRUnlock(f);
+
+ return 1;
+}
+
+int
+fileMetaFlush(File *f, int rec)
+{
+ File **kids, *p;
+ int nkids;
+ int i, rv;
+
+ fileMetaLock(f);
+ rv = fileMetaFlush2(f, nil);
+ fileMetaUnlock(f);
+
+ if(!rec || !fileIsDir(f))
+ return rv;
+
+ if(!fileLock(f))
+ return rv;
+ nkids = 0;
+ for(p=f->down; p; p=p->next)
+ nkids++;
+ kids = vtMemAlloc(nkids*sizeof(File*));
+ i = 0;
+ for(p=f->down; p; p=p->next){
+ kids[i++] = p;
+ p->ref++;
+ }
+ fileUnlock(f);
+
+ for(i=0; i<nkids; i++){
+ rv |= fileMetaFlush(kids[i], 1);
+ fileDecRef(kids[i]);
+ }
+ vtMemFree(kids);
+ return rv;
+}
+
+/* assumes metaLock is held */
+static int
+fileMetaFlush2(File *f, char *oelem)
+{
+ File *fp;
+ Block *b, *bb;
+ MetaBlock mb;
+ MetaEntry me, me2;
+ int i, n;
+ u32int boff;
+
+ if(!f->dirty)
+ return 0;
+
+ if(oelem == nil)
+ oelem = f->dir.elem;
+
+//print("fileMetaFlush %s->%s\n", oelem, f->dir.elem);
+
+ fp = f->up;
+
+ if(!sourceLock(fp->msource, -1))
+ return -1;
+ /* can happen if source is clri'ed out from under us */
+ if(f->boff == NilBlock)
+ goto Err1;
+ b = sourceBlock(fp->msource, f->boff, OReadWrite);
+ if(b == nil)
+ goto Err1;
+
+ if(!mbUnpack(&mb, b->data, fp->msource->dsize))
+ goto Err;
+ if(!mbSearch(&mb, oelem, &i, &me))
+ goto Err;
+
+ n = deSize(&f->dir);
+if(0)fprint(2, "old size %d new size %d\n", me.size, n);
+
+ if(mbResize(&mb, &me, n)){
+ /* fits in the block */
+ mbDelete(&mb, i);
+ if(strcmp(f->dir.elem, oelem) != 0)
+ mbSearch(&mb, f->dir.elem, &i, &me2);
+ dePack(&f->dir, &me);
+ mbInsert(&mb, i, &me);
+ mbPack(&mb);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(fp->msource);
+ f->dirty = 0;
+
+ return 1;
+ }
+
+ /*
+ * moving entry to another block
+ * it is feasible for the fs to crash leaving two copies
+ * of the directory entry. This is just too much work to
+ * fix. Given that entries are only allocated in a block that
+ * is less than PercentageFull, most modifications of meta data
+ * will fit within the block. i.e. this code should almost
+ * never be executed.
+ */
+ boff = fileMetaAlloc(fp, &f->dir, f->boff+1);
+ if(boff == NilBlock){
+ /* mbResize might have modified block */
+ mbPack(&mb);
+ blockDirty(b);
+ goto Err;
+ }
+fprint(2, "fileMetaFlush moving entry from %ud -> %ud\n", f->boff, boff);
+ f->boff = boff;
+
+ /* make sure deletion goes to disk after new entry */
+ bb = sourceBlock(fp->msource, f->boff, OReadWrite);
+ mbDelete(&mb, i);
+ mbPack(&mb);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ blockDirty(b);
+ blockPut(b);
+ sourceUnlock(fp->msource);
+
+ f->dirty = 0;
+
+ return 1;
+
+Err:
+ blockPut(b);
+Err1:
+ sourceUnlock(fp->msource);
+ return -1;
+}
+
+static int
+fileMetaRemove(File *f, char *uid)
+{
+ Block *b;
+ MetaBlock mb;
+ MetaEntry me;
+ int i;
+ File *up;
+
+ up = f->up;
+
+ fileWAccess(up, uid);
+
+ fileMetaLock(f);
+
+ sourceLock(up->msource, OReadWrite);
+ b = sourceBlock(up->msource, f->boff, OReadWrite);
+ if(b == nil)
+ goto Err;
+
+ if(!mbUnpack(&mb, b->data, up->msource->dsize))
+{
+fprint(2, "U\n");
+ goto Err;
+}
+ if(!mbSearch(&mb, f->dir.elem, &i, &me))
+{
+fprint(2, "S\n");
+ goto Err;
+}
+ mbDelete(&mb, i);
+ mbPack(&mb);
+ sourceUnlock(up->msource);
+
+ blockDirty(b);
+ blockPut(b);
+
+ f->removed = 1;
+ f->boff = NilBlock;
+ f->dirty = 0;
+
+ fileMetaUnlock(f);
+ return 1;
+
+Err:
+ sourceUnlock(up->msource);
+ blockPut(b);
+ fileMetaUnlock(f);
+ return 0;
+}
+
+/* assume file is locked, assume f->msource is locked */
+static int
+fileCheckEmpty(File *f)
+{
+ u32int i, n;
+ Block *b;
+ MetaBlock mb;
+ Source *r;
+
+ r = f->msource;
+ n = (sourceGetSize(r)+r->dsize-1)/r->dsize;
+ for(i=0; i<n; i++){
+ b = sourceBlock(r, i, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, r->dsize))
+ goto Err;
+ if(mb.nindex > 0){
+ vtSetError(ENotEmpty);
+ goto Err;
+ }
+ blockPut(b);
+ }
+ return 1;
+Err:
+ blockPut(b);
+ return 0;
+}
+
+int
+fileRemove(File *f, char *uid)
+{
+ File *ff;
+
+ /* can not remove the root */
+ if(fileIsRoot(f)){
+ vtSetError(ERoot);
+ return 0;
+ }
+
+ if(!fileLock(f))
+ return 0;
+
+ if(f->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ goto Err1;
+ }
+ if(!sourceLock2(f->source, f->msource, -1))
+ goto Err1;
+ if(fileIsDir(f) && !fileCheckEmpty(f))
+ goto Err;
+
+ for(ff=f->down; ff; ff=ff->next)
+ assert(ff->removed);
+
+ sourceRemove(f->source);
+ f->source->file = nil; /* erase back pointer */
+ f->source = nil;
+ if(f->msource){
+ sourceRemove(f->msource);
+ f->msource = nil;
+ }
+
+ fileUnlock(f);
+
+ if(!fileMetaRemove(f, uid))
+ return 0;
+
+ return 1;
+
+Err:
+ sourceUnlock(f->source);
+ if(f->msource)
+ sourceUnlock(f->msource);
+Err1:
+ fileUnlock(f);
+ return 0;
+}
+
+static int
+clri(File *f, char *uid)
+{
+ int r;
+
+ if(f == nil)
+ return 0;
+ if(f->up->source->mode != OReadWrite){
+ vtSetError(EReadOnly);
+ fileDecRef(f);
+ return 0;
+ }
+ r = fileMetaRemove(f, uid);
+ fileDecRef(f);
+ return r;
+}
+
+int
+fileClriPath(Fs *fs, char *path, char *uid)
+{
+ return clri(_fileOpen(fs, path, 1), uid);
+}
+
+int
+fileClri(File *dir, char *elem, char *uid)
+{
+ return clri(_fileWalk(dir, elem, 1), uid);
+}
+
+File *
+fileIncRef(File *vf)
+{
+ fileMetaLock(vf);
+ assert(vf->ref > 0);
+ vf->ref++;
+ fileMetaUnlock(vf);
+ return vf;
+}
+
+int
+fileDecRef(File *f)
+{
+ File *p, *q, **qq;
+
+ if(f->up == nil){
+ /* never linked in */
+ assert(f->ref == 1);
+ fileFree(f);
+ return 1;
+ }
+
+ fileMetaLock(f);
+ f->ref--;
+ if(f->ref > 0){
+ fileMetaUnlock(f);
+ return 0;
+ }
+ assert(f->ref == 0);
+ assert(f->down == nil);
+
+ fileMetaFlush2(f, nil);
+
+ p = f->up;
+ qq = &p->down;
+ for(q = *qq; q; q = *qq){
+ if(q == f)
+ break;
+ qq = &q->next;
+ }
+ assert(q != nil);
+ *qq = f->next;
+
+ fileMetaUnlock(f);
+ fileFree(f);
+
+ fileDecRef(p);
+ return 1;
+}
+
+File *
+fileGetParent(File *f)
+{
+ if(fileIsRoot(f))
+ return fileIncRef(f);
+ return fileIncRef(f->up);
+}
+
+DirEntryEnum *
+deeOpen(File *f)
+{
+ DirEntryEnum *dee;
+ File *p;
+
+ if(!fileIsDir(f)){
+ vtSetError(ENotDir);
+ fileDecRef(f);
+ return nil;
+ }
+
+ /* flush out meta data */
+ if(!fileLock(f))
+ return nil;
+ for(p=f->down; p; p=p->next)
+ fileMetaFlush2(p, nil);
+ fileUnlock(f);
+
+ dee = vtMemAllocZ(sizeof(DirEntryEnum));
+ dee->file = fileIncRef(f);
+
+ return dee;
+}
+
+static int
+dirEntrySize(Source *s, ulong elem, ulong gen, uvlong *size)
+{
+ Block *b;
+ ulong bn;
+ Entry e;
+ int epb;
+
+ epb = s->dsize/VtEntrySize;
+ bn = elem/epb;
+ elem -= bn*epb;
+
+ b = sourceBlock(s, bn, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!entryUnpack(&e, b->data, elem))
+ goto Err;
+
+ /* hanging entries are returned as zero size */
+ if(!(e.flags & VtEntryActive) || e.gen != gen)
+ *size = 0;
+ else
+ *size = e.size;
+ blockPut(b);
+ return 1;
+
+Err:
+ blockPut(b);
+ return 0;
+}
+
+static int
+deeFill(DirEntryEnum *dee)
+{
+ int i, n;
+ Source *meta, *source;
+ MetaBlock mb;
+ MetaEntry me;
+ File *f;
+ Block *b;
+ DirEntry *de;
+
+ /* clean up first */
+ for(i=dee->i; i<dee->n; i++)
+ deCleanup(dee->buf+i);
+ vtMemFree(dee->buf);
+ dee->buf = nil;
+ dee->i = 0;
+ dee->n = 0;
+
+ f = dee->file;
+
+ source = f->source;
+ meta = f->msource;
+
+ b = sourceBlock(meta, dee->boff, OReadOnly);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, meta->dsize))
+ goto Err;
+
+ n = mb.nindex;
+ dee->buf = vtMemAlloc(n * sizeof(DirEntry));
+
+ for(i=0; i<n; i++){
+ de = dee->buf + i;
+ meUnpack(&me, &mb, i);
+ if(!deUnpack(de, &me))
+ goto Err;
+ dee->n++;
+ if(!(de->mode & ModeDir))
+ if(!dirEntrySize(source, de->entry, de->gen, &de->size))
+ goto Err;
+ }
+ dee->boff++;
+ blockPut(b);
+ return 1;
+Err:
+ blockPut(b);
+ return 0;
+}
+
+int
+deeRead(DirEntryEnum *dee, DirEntry *de)
+{
+ int ret, didread;
+ File *f;
+ u32int nb;
+
+ if(dee == nil){
+ vtSetError("cannot happen in deeRead");
+ return -1;
+ }
+
+ f = dee->file;
+ if(!fileRLock(f))
+ return -1;
+
+ if(!sourceLock2(f->source, f->msource, OReadOnly)){
+ fileRUnlock(f);
+ return -1;
+ }
+
+ nb = (sourceGetSize(f->msource)+f->msource->dsize-1)/f->msource->dsize;
+
+ didread = 0;
+ while(dee->i >= dee->n){
+ if(dee->boff >= nb){
+ ret = 0;
+ goto Return;
+ }
+ didread = 1;
+ if(!deeFill(dee)){
+ ret = -1;
+ goto Return;
+ }
+ }
+
+ memmove(de, dee->buf + dee->i, sizeof(DirEntry));
+ dee->i++;
+ ret = 1;
+
+Return:
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+ fileRUnlock(f);
+
+ if(didread)
+ fileRAccess(f);
+ return ret;
+}
+
+void
+deeClose(DirEntryEnum *dee)
+{
+ int i;
+ if(dee == nil)
+ return;
+ for(i=dee->i; i<dee->n; i++)
+ deCleanup(dee->buf+i);
+ vtMemFree(dee->buf);
+ fileDecRef(dee->file);
+ vtMemFree(dee);
+}
+
+/*
+ * caller must lock f->source and f->msource
+ * caller must NOT lock the source and msource
+ * referenced by dir.
+ */
+static u32int
+fileMetaAlloc(File *f, DirEntry *dir, u32int start)
+{
+ u32int nb, bo;
+ Block *b, *bb;
+ MetaBlock mb;
+ int nn;
+ uchar *p;
+ int i, n, epb;
+ MetaEntry me;
+ Source *s, *ms;
+
+ s = f->source;
+ ms = f->msource;
+
+ n = deSize(dir);
+ nb = (sourceGetSize(ms)+ms->dsize-1)/ms->dsize;
+ b = nil;
+ if(start > nb)
+ start = nb;
+ for(bo=start; bo<nb; bo++){
+ b = sourceBlock(ms, bo, OReadWrite);
+ if(b == nil)
+ goto Err;
+ if(!mbUnpack(&mb, b->data, ms->dsize))
+ goto Err;
+ nn = (mb.maxsize*FullPercentage/100) - mb.size + mb.free;
+ if(n <= nn && mb.nindex < mb.maxindex)
+ break;
+ blockPut(b);
+ b = nil;
+ }
+
+ /* add block to meta file */
+ if(b == nil){
+ b = sourceBlock(ms, bo, OReadWrite);
+ if(b == nil)
+ goto Err;
+ sourceSetSize(ms, (nb+1)*ms->dsize);
+ mbInit(&mb, b->data, ms->dsize, ms->dsize/BytesPerEntry);
+ }
+
+ p = mbAlloc(&mb, n);
+ if(p == nil){
+ /* mbAlloc might have changed block */
+ mbPack(&mb);
+ blockDirty(b);
+ vtSetError(EBadMeta);
+ goto Err;
+ }
+
+ mbSearch(&mb, dir->elem, &i, &me);
+ assert(me.p == nil);
+ me.p = p;
+ me.size = n;
+ dePack(dir, &me);
+ mbInsert(&mb, i, &me);
+ mbPack(&mb);
+
+ /* meta block depends on super block for qid ... */
+ bb = cacheLocal(b->c, PartSuper, 0, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+
+ /* ... and one or two dir entries */
+ epb = s->dsize/VtEntrySize;
+ bb = sourceBlock(s, dir->entry/epb, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ if(dir->mode & ModeDir){
+ bb = sourceBlock(s, dir->mentry/epb, OReadOnly);
+ blockDependency(b, bb, -1, nil, nil);
+ blockPut(bb);
+ }
+
+ blockDirty(b);
+ blockPut(b);
+ return bo;
+Err:
+ blockPut(b);
+ return NilBlock;
+}
+
+static int
+chkSource(File *f)
+{
+ if(f->partial)
+ return 1;
+
+ if(f->source == nil || (f->dir.mode & ModeDir) && f->msource == nil){
+ vtSetError(ERemoved);
+ return 0;
+ }
+ return 1;
+}
+
+static int
+fileRLock(File *f)
+{
+ assert(!vtCanLock(f->fs->elk));
+ vtRLock(f->lk);
+ if(!chkSource(f)){
+ fileRUnlock(f);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+fileRUnlock(File *f)
+{
+ vtRUnlock(f->lk);
+}
+
+static int
+fileLock(File *f)
+{
+ assert(!vtCanLock(f->fs->elk));
+ vtLock(f->lk);
+ if(!chkSource(f)){
+ fileUnlock(f);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+fileUnlock(File *f)
+{
+ vtUnlock(f->lk);
+}
+
+/*
+ * f->source and f->msource must NOT be locked.
+ * fileMetaFlush locks the fileMeta and then the source (in fileMetaFlush2).
+ * We have to respect that ordering.
+ */
+static void
+fileMetaLock(File *f)
+{
+if(f->up == nil)
+fprint(2, "f->elem = %s\n", f->dir.elem);
+ assert(f->up != nil);
+ assert(!vtCanLock(f->fs->elk));
+ vtLock(f->up->lk);
+}
+
+static void
+fileMetaUnlock(File *f)
+{
+ vtUnlock(f->up->lk);
+}
+
+/*
+ * f->source and f->msource must NOT be locked.
+ * see fileMetaLock.
+ */
+static void
+fileRAccess(File* f)
+{
+ if(f->mode == OReadOnly || f->fs->noatimeupd)
+ return;
+
+ fileMetaLock(f);
+ f->dir.atime = time(0L);
+ f->dirty = 1;
+ fileMetaUnlock(f);
+}
+
+/*
+ * f->source and f->msource must NOT be locked.
+ * see fileMetaLock.
+ */
+static void
+fileWAccess(File* f, char *mid)
+{
+ if(f->mode == OReadOnly)
+ return;
+
+ fileMetaLock(f);
+ f->dir.atime = f->dir.mtime = time(0L);
+ if(strcmp(f->dir.mid, mid) != 0){
+ vtMemFree(f->dir.mid);
+ f->dir.mid = vtStrDup(mid);
+ }
+ f->dir.mcount++;
+ f->dirty = 1;
+ fileMetaUnlock(f);
+
+/*RSC: let's try this */
+/*presotto - lets not
+ if(f->up)
+ fileWAccess(f->up, mid);
+*/
+}
+
+static int
+getEntry(Source *r, Entry *e, int checkepoch)
+{
+ u32int epoch;
+ Block *b;
+
+ if(r == nil){
+ memset(&e, 0, sizeof e);
+ return 1;
+ }
+
+ b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadOnly);
+ if(b == nil)
+ return 0;
+ if(!entryUnpack(e, b->data, r->offset % r->epb)){
+ blockPut(b);
+ return 0;
+ }
+ epoch = b->l.epoch;
+ blockPut(b);
+
+ if(checkepoch){
+ b = cacheGlobal(r->fs->cache, e->score, entryType(e), e->tag, OReadOnly);
+ if(b){
+ if(b->l.epoch >= epoch)
+ fprint(2, "warning: entry %p epoch not older %#.8ux/%d %V/%d in getEntry\n",
+ r, b->addr, b->l.epoch, r->score, epoch);
+ blockPut(b);
+ }
+ }
+
+ return 1;
+}
+
+static int
+setEntry(Source *r, Entry *e)
+{
+ Block *b;
+ Entry oe;
+
+ b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
+ if(0) fprint(2, "setEntry: b %#ux %d score=%V\n", b->addr, r->offset % r->epb, e->score);
+ if(b == nil)
+ return 0;
+ if(!entryUnpack(&oe, b->data, r->offset % r->epb)){
+ blockPut(b);
+ return 0;
+ }
+ e->gen = oe.gen;
+ entryPack(e, b->data, r->offset % r->epb);
+
+ /* BUG b should depend on the entry pointer */
+
+ blockDirty(b);
+ blockPut(b);
+ return 1;
+}
+
+/* assumes hold elk */
+int
+fileSnapshot(File *dst, File *src, u32int epoch, int doarchive)
+{
+ Entry e, ee;
+
+ /* add link to snapshot */
+ if(!getEntry(src->source, &e, 1) || !getEntry(src->msource, &ee, 1))
+ return 0;
+
+ e.snap = epoch;
+ e.archive = doarchive;
+ ee.snap = epoch;
+ ee.archive = doarchive;
+
+ if(!setEntry(dst->source, &e) || !setEntry(dst->msource, &ee))
+ return 0;
+ return 1;
+}
+
+int
+fileGetSources(File *f, Entry *e, Entry *ee)
+{
+ if(!getEntry(f->source, e, 0)
+ || !getEntry(f->msource, ee, 0))
+ return 0;
+ return 1;
+}
+
+/*
+ * Walk down to the block(s) containing the Entries
+ * for f->source and f->msource, copying as we go.
+ */
+int
+fileWalkSources(File *f)
+{
+ if(f->mode == OReadOnly){
+ fprint(2, "readonly in fileWalkSources\n");
+ return 1;
+ }
+ if(!sourceLock2(f->source, f->msource, OReadWrite)){
+ fprint(2, "sourceLock2 failed in fileWalkSources\n");
+ return 0;
+ }
+ sourceUnlock(f->source);
+ sourceUnlock(f->msource);
+ return 1;
+}
+
+/*
+ * convert File* to full path name in malloced string.
+ * this hasn't been as useful as we hoped it would be.
+ */
+char *
+fileName(File *f)
+{
+ char *name, *pname;
+ File *p;
+ static char root[] = "/";
+
+ if (f == nil)
+ return vtStrDup("/**GOK**");
+
+ p = fileGetParent(f);
+ if (p == f)
+ name = vtStrDup(root);
+ else {
+ pname = fileName(p);
+ if (strcmp(pname, root) == 0)
+ name = smprint("/%s", f->dir.elem);
+ else
+ name = smprint("%s/%s", pname, f->dir.elem);
+ free(pname);
+ }
+ fileDecRef(p);
+ return name;
+}
diff --git a/src/cmd/fossil/flchk.c b/src/cmd/fossil/flchk.c
new file mode 100644
index 00000000..b19fd5be
--- /dev/null
+++ b/src/cmd/fossil/flchk.c
@@ -0,0 +1,118 @@
+#include "stdinc.h"
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+
+Biobuf bout;
+Fsck fsck;
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-c cachesize] [-h host] file\n", argv0);
+ exits("usage");
+}
+
+#pragma varargck argpos flprint 1
+
+static int
+flprint(char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = Bvprint(&bout, fmt, arg);
+ va_end(arg);
+ return n;
+}
+
+static void
+flclre(Fsck*, Block *b, int o)
+{
+ Bprint(&bout, "# clre 0x%ux %d\n", b->addr, o);
+}
+
+static void
+flclrp(Fsck*, Block *b, int o)
+{
+ Bprint(&bout, "# clrp 0x%ux %d\n", b->addr, o);
+}
+
+static void
+flclri(Fsck*, char *name, MetaBlock*, int, Block*)
+{
+ Bprint(&bout, "# clri %s\n", name);
+}
+
+static void
+flclose(Fsck*, Block *b, u32int epoch)
+{
+ Bprint(&bout, "# bclose 0x%ux %ud\n", b->addr, epoch);
+}
+
+void
+main(int argc, char *argv[])
+{
+ int csize = 1000;
+ VtSession *z;
+ char *host = nil;
+
+ fsck.useventi = 1;
+ Binit(&bout, 1, OWRITE);
+ ARGBEGIN{
+ default:
+ usage();
+ case 'c':
+ csize = atoi(ARGF());
+ if(csize <= 0)
+ usage();
+ break;
+ case 'f':
+ fsck.useventi = 0;
+ break;
+ case 'h':
+ host = ARGF();
+ break;
+ case 'v':
+ fsck.printdirs = 1;
+ break;
+ }ARGEND;
+
+ if(argc != 1)
+ usage();
+
+ vtAttach();
+
+ fmtinstall('L', labelFmt);
+ fmtinstall('V', scoreFmt);
+ fmtinstall('R', vtErrFmt);
+
+ /*
+ * Connect to Venti.
+ */
+ z = vtDial(host, 0);
+ if(z == nil){
+ if(fsck.useventi)
+ vtFatal("could not connect to server: %s", vtGetError());
+ }else if(!vtConnect(z, 0))
+ vtFatal("vtConnect: %s", vtGetError());
+
+ /*
+ * Initialize file system.
+ */
+ fsck.fs = fsOpen(argv[0], z, csize, OReadOnly);
+ if(fsck.fs == nil)
+ vtFatal("could not open file system: %R");
+
+ fsck.print = flprint;
+ fsck.clre = flclre;
+ fsck.clrp = flclrp;
+ fsck.close = flclose;
+ fsck.clri = flclri;
+
+ fsCheck(&fsck);
+
+ exits(0);
+}
+
diff --git a/src/cmd/fossil/flfmt.c b/src/cmd/fossil/flfmt.c
new file mode 100644
index 00000000..687c41e3
--- /dev/null
+++ b/src/cmd/fossil/flfmt.c
@@ -0,0 +1,571 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "flfmt9660.h"
+
+#define blockWrite _blockWrite /* hack */
+
+static void usage(void);
+static u64int fdsize(int fd);
+static void partition(int fd, int bsize, Header *h);
+static u64int unittoull(char *s);
+static u32int blockAlloc(int type, u32int tag);
+static void blockRead(int part, u32int addr);
+static void blockWrite(int part, u32int addr);
+static void superInit(char *label, u32int root, uchar[VtScoreSize]);
+static void rootMetaInit(Entry *e);
+static u32int rootInit(Entry *e);
+static void topLevel(char *name);
+static int parseScore(uchar[VtScoreSize], char*);
+static u32int ventiRoot(char*, char*);
+static VtSession *z;
+
+#define TWID64 ((u64int)~(u64int)0)
+
+Disk *disk;
+Fs *fs;
+uchar *buf;
+int bsize = 8*1024;
+u64int qid = 1;
+int iso9660off;
+char *iso9660file;
+
+int
+confirm(char *msg)
+{
+ char buf[100];
+ int n;
+
+ fprint(2, "%s [y/n]: ", msg);
+ n = read(0, buf, sizeof buf - 1);
+ if(n <= 0)
+ return 0;
+ if(buf[0] == 'y')
+ return 1;
+ return 0;
+}
+
+void
+main(int argc, char *argv[])
+{
+ int fd, force;
+ Header h;
+ ulong bn;
+ Entry e;
+ char *label = "vfs";
+ char *host = nil;
+ char *score = nil;
+ u32int root;
+ Dir *d;
+
+ force = 0;
+ ARGBEGIN{
+ default:
+ usage();
+ case 'b':
+ bsize = unittoull(EARGF(usage()));
+ if(bsize == ~0)
+ usage();
+ break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'i':
+ iso9660file = EARGF(usage());
+ iso9660off = atoi(EARGF(usage()));
+ break;
+ case 'l':
+ label = EARGF(usage());
+ break;
+ case 'v':
+ score = EARGF(usage());
+ break;
+
+ /*
+ * This is -y instead of -f because flchk has a
+ * (frequently used) -f option. I type flfmt instead
+ * of flchk all the time, and want to make it hard
+ * to reformat my file system accidentally.
+ */
+ case 'y':
+ force = 1;
+ break;
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ if(iso9660file && score)
+ vtFatal("cannot use -i with -v");
+
+ vtAttach();
+
+ fmtinstall('V', scoreFmt);
+ fmtinstall('R', vtErrFmt);
+ fmtinstall('L', labelFmt);
+
+ fd = open(argv[0], ORDWR);
+ if(fd < 0)
+ vtFatal("could not open file: %s: %r", argv[0]);
+
+ buf = vtMemAllocZ(bsize);
+ if(pread(fd, buf, bsize, HeaderOffset) != bsize)
+ vtFatal("could not read fs header block: %r");
+
+ if(headerUnpack(&h, buf) && !force
+ && !confirm("fs header block already exists; are you sure?"))
+ goto Out;
+
+ if((d = dirfstat(fd)) == nil)
+ vtFatal("dirfstat: %r");
+
+ if(d->type == 'M' && !force
+ && !confirm("fs file is mounted via devmnt (is not a kernel device); are you sure?"))
+ goto Out;
+
+ partition(fd, bsize, &h);
+ headerPack(&h, buf);
+ if(pwrite(fd, buf, bsize, HeaderOffset) < bsize)
+ vtFatal("could not write fs header: %r");
+
+ disk = diskAlloc(fd);
+ if(disk == nil)
+ vtFatal("could not open disk: %r");
+
+ if(iso9660file)
+ iso9660init(fd, &h, iso9660file, iso9660off);
+
+ /* zero labels */
+ memset(buf, 0, bsize);
+ for(bn = 0; bn < diskSize(disk, PartLabel); bn++)
+ blockWrite(PartLabel, bn);
+
+ if(iso9660file)
+ iso9660labels(disk, buf, blockWrite);
+
+ if(score)
+ root = ventiRoot(host, score);
+ else{
+ rootMetaInit(&e);
+ root = rootInit(&e);
+ }
+
+ superInit(label, root, vtZeroScore);
+ diskFree(disk);
+
+ if(score == nil)
+ topLevel(argv[0]);
+
+Out:
+ vtDetach();
+ exits(0);
+}
+
+static u64int
+fdsize(int fd)
+{
+ Dir *dir;
+ u64int size;
+
+ dir = dirfstat(fd);
+ if(dir == nil)
+ vtFatal("could not stat file: %r");
+ size = dir->length;
+ free(dir);
+ return size;
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-b blocksize] [-h host] [-i file offset] "
+ "[-l label] [-v score] [-y] file\n", argv0);
+ exits("usage");
+}
+
+static void
+partition(int fd, int bsize, Header *h)
+{
+ ulong nblock, ndata, nlabel;
+ ulong lpb;
+
+ if(bsize % 512 != 0)
+ sysfatal("block size must be a multiple of 512 bytes");
+ if(bsize > VtMaxLumpSize)
+ sysfatal("block size must be less than %d", VtMaxLumpSize);
+
+ memset(h, 0, sizeof(*h));
+ h->blockSize = bsize;
+
+ lpb = bsize/LabelSize;
+
+ nblock = fdsize(fd)/bsize;
+
+ /* sanity check */
+ if(nblock < (HeaderOffset*10)/bsize)
+ vtFatal("file too small");
+
+ h->super = (HeaderOffset + 2*bsize)/bsize;
+ h->label = h->super + 1;
+ ndata = ((u64int)lpb)*(nblock - h->label)/(lpb+1);
+ nlabel = (ndata + lpb - 1)/lpb;
+ h->data = h->label + nlabel;
+ h->end = h->data + ndata;
+
+}
+
+static u32int
+tagGen(void)
+{
+ u32int tag;
+
+ for(;;){
+ tag = lrand();
+ if(tag > RootTag)
+ break;
+ }
+ return tag;
+}
+
+static void
+entryInit(Entry *e)
+{
+ e->gen = 0;
+ e->dsize = bsize;
+ e->psize = bsize/VtEntrySize*VtEntrySize;
+ e->flags = VtEntryActive;
+ e->depth = 0;
+ e->size = 0;
+ memmove(e->score, vtZeroScore, VtScoreSize);
+ e->tag = tagGen();
+ e->snap = 0;
+ e->archive = 0;
+}
+
+static void
+rootMetaInit(Entry *e)
+{
+ u32int addr;
+ u32int tag;
+ DirEntry de;
+ MetaBlock mb;
+ MetaEntry me;
+
+ memset(&de, 0, sizeof(de));
+ de.elem = vtStrDup("root");
+ de.entry = 0;
+ de.gen = 0;
+ de.mentry = 1;
+ de.mgen = 0;
+ de.size = 0;
+ de.qid = qid++;
+ de.uid = vtStrDup("adm");
+ de.gid = vtStrDup("adm");
+ de.mid = vtStrDup("adm");
+ de.mtime = time(0);
+ de.mcount = 0;
+ de.ctime = time(0);
+ de.atime = time(0);
+ de.mode = ModeDir | 0555;
+
+ tag = tagGen();
+ addr = blockAlloc(BtData, tag);
+
+ /* build up meta block */
+ memset(buf, 0, bsize);
+ mbInit(&mb, buf, bsize, bsize/100);
+ me.size = deSize(&de);
+ me.p = mbAlloc(&mb, me.size);
+ assert(me.p != nil);
+ dePack(&de, &me);
+ mbInsert(&mb, 0, &me);
+ mbPack(&mb);
+ blockWrite(PartData, addr);
+ deCleanup(&de);
+
+ /* build up entry for meta block */
+ entryInit(e);
+ e->flags |= VtEntryLocal;
+ e->size = bsize;
+ e->tag = tag;
+ localToGlobal(addr, e->score);
+}
+
+static u32int
+rootInit(Entry *e)
+{
+ ulong addr;
+ u32int tag;
+
+ tag = tagGen();
+
+ addr = blockAlloc(BtDir, tag);
+ memset(buf, 0, bsize);
+
+ /* root meta data is in the third entry */
+ entryPack(e, buf, 2);
+
+ entryInit(e);
+ e->flags |= VtEntryDir;
+ entryPack(e, buf, 0);
+
+ entryInit(e);
+ entryPack(e, buf, 1);
+
+ blockWrite(PartData, addr);
+
+ entryInit(e);
+ e->flags |= VtEntryLocal|VtEntryDir;
+ e->size = VtEntrySize*3;
+ e->tag = tag;
+ localToGlobal(addr, e->score);
+
+ addr = blockAlloc(BtDir, RootTag);
+ memset(buf, 0, bsize);
+ entryPack(e, buf, 0);
+
+ blockWrite(PartData, addr);
+
+ return addr;
+}
+
+
+static u32int
+blockAlloc(int type, u32int tag)
+{
+ static u32int addr;
+ Label l;
+ int lpb;
+
+ lpb = bsize/LabelSize;
+
+ blockRead(PartLabel, addr/lpb);
+ if(!labelUnpack(&l, buf, addr % lpb))
+ vtFatal("bad label: %r");
+ if(l.state != BsFree)
+ vtFatal("want to allocate block already in use");
+ l.epoch = 1;
+ l.epochClose = ~(u32int)0;
+ l.type = type;
+ l.state = BsAlloc;
+ l.tag = tag;
+ labelPack(&l, buf, addr % lpb);
+ blockWrite(PartLabel, addr/lpb);
+ return addr++;
+}
+
+static void
+superInit(char *label, u32int root, uchar score[VtScoreSize])
+{
+ Super s;
+
+ memset(buf, 0, bsize);
+ memset(&s, 0, sizeof(s));
+ s.version = SuperVersion;
+ s.epochLow = 1;
+ s.epochHigh = 1;
+ s.qid = qid;
+ s.active = root;
+ s.next = NilBlock;
+ s.current = NilBlock;
+ strecpy(s.name, s.name+sizeof(s.name), label);
+ memmove(s.last, score, VtScoreSize);
+
+ superPack(&s, buf);
+ blockWrite(PartSuper, 0);
+}
+
+static u64int
+unittoull(char *s)
+{
+ char *es;
+ u64int n;
+
+ if(s == nil)
+ return TWID64;
+ n = strtoul(s, &es, 0);
+ if(*es == 'k' || *es == 'K'){
+ n *= 1024;
+ es++;
+ }else if(*es == 'm' || *es == 'M'){
+ n *= 1024*1024;
+ es++;
+ }else if(*es == 'g' || *es == 'G'){
+ n *= 1024*1024*1024;
+ es++;
+ }
+ if(*es != '\0')
+ return TWID64;
+ return n;
+}
+
+static void
+blockRead(int part, u32int addr)
+{
+ if(!diskReadRaw(disk, part, addr, buf))
+ vtFatal("read failed: %r");
+}
+
+static void
+blockWrite(int part, u32int addr)
+{
+ if(!diskWriteRaw(disk, part, addr, buf))
+ vtFatal("write failed: %r");
+}
+
+static void
+addFile(File *root, char *name, uint mode)
+{
+ File *f;
+
+ f = fileCreate(root, name, mode | ModeDir, "adm");
+ if(f == nil)
+ vtFatal("could not create file: %s: %r", name);
+ fileDecRef(f);
+}
+
+static void
+topLevel(char *name)
+{
+ Fs *fs;
+ File *root;
+
+ /* ok, now we can open as a fs */
+ fs = fsOpen(name, z, 100, OReadWrite);
+ if(fs == nil)
+ vtFatal("could not open file system: %r");
+ vtRLock(fs->elk);
+ root = fsGetRoot(fs);
+ if(root == nil)
+ vtFatal("could not open root: %r");
+ addFile(root, "active", 0555);
+ addFile(root, "archive", 0555);
+ addFile(root, "snapshot", 0555);
+ fileDecRef(root);
+ if(iso9660file)
+ iso9660copy(fs);
+ vtRUnlock(fs->elk);
+ fsClose(fs);
+}
+
+static int
+ventiRead(uchar score[VtScoreSize], int type)
+{
+ int n;
+
+ n = vtRead(z, score, type, buf, bsize);
+ if(n < 0)
+ vtFatal("ventiRead %V (%d) failed: %R", score, type);
+ vtZeroExtend(type, buf, n, bsize);
+ return n;
+}
+
+static u32int
+ventiRoot(char *host, char *s)
+{
+ int i, n;
+ uchar score[VtScoreSize];
+ u32int addr, tag;
+ DirEntry de;
+ MetaBlock mb;
+ MetaEntry me;
+ Entry e;
+ VtRoot root;
+
+ if(!parseScore(score, s))
+ vtFatal("bad score '%s'", s);
+
+ if((z = vtDial(host, 0)) == nil
+ || !vtConnect(z, nil))
+ vtFatal("connect to venti: %R");
+
+ tag = tagGen();
+ addr = blockAlloc(BtDir, tag);
+
+ ventiRead(score, VtRootType);
+ if(!vtRootUnpack(&root, buf))
+ vtFatal("corrupted root: vtRootUnpack");
+ n = ventiRead(root.score, VtDirType);
+
+ /*
+ * Fossil's vac archives start with an extra layer of source,
+ * but vac's don't.
+ */
+ if(n <= 2*VtEntrySize){
+ if(!entryUnpack(&e, buf, 0))
+ vtFatal("bad root: top entry");
+ n = ventiRead(e.score, VtDirType);
+ }
+
+ /*
+ * There should be three root sources (and nothing else) here.
+ */
+ for(i=0; i<3; i++){
+ if(!entryUnpack(&e, buf, i)
+ || !(e.flags&VtEntryActive)
+ || e.psize < 256
+ || e.dsize < 256)
+ vtFatal("bad root: entry %d", i);
+ fprint(2, "%V\n", e.score);
+ }
+ if(n > 3*VtEntrySize)
+ vtFatal("bad root: entry count");
+
+ blockWrite(PartData, addr);
+
+ /*
+ * Maximum qid is recorded in root's msource, entry #2 (conveniently in e).
+ */
+ ventiRead(e.score, VtDataType);
+ if(!mbUnpack(&mb, buf, bsize))
+ vtFatal("bad root: mbUnpack");
+ meUnpack(&me, &mb, 0);
+ if(!deUnpack(&de, &me))
+ vtFatal("bad root: dirUnpack");
+ if(!de.qidSpace)
+ vtFatal("bad root: no qidSpace");
+ qid = de.qidMax;
+
+ /*
+ * Recreate the top layer of source.
+ */
+ entryInit(&e);
+ e.flags |= VtEntryLocal|VtEntryDir;
+ e.size = VtEntrySize*3;
+ e.tag = tag;
+ localToGlobal(addr, e.score);
+
+ addr = blockAlloc(BtDir, RootTag);
+ memset(buf, 0, bsize);
+ entryPack(&e, buf, 0);
+ blockWrite(PartData, addr);
+
+ return addr;
+}
+
+static int
+parseScore(uchar *score, char *buf)
+{
+ int i, c;
+
+ memset(score, 0, VtScoreSize);
+
+ if(strlen(buf) < VtScoreSize*2)
+ return 0;
+ for(i=0; i<VtScoreSize*2; i++){
+ if(buf[i] >= '0' && buf[i] <= '9')
+ c = buf[i] - '0';
+ else if(buf[i] >= 'a' && buf[i] <= 'f')
+ c = buf[i] - 'a' + 10;
+ else if(buf[i] >= 'A' && buf[i] <= 'F')
+ c = buf[i] - 'A' + 10;
+ else
+ return 0;
+
+ if((i & 1) == 0)
+ c <<= 4;
+
+ score[i>>1] |= c;
+ }
+ return 1;
+}
diff --git a/src/cmd/fossil/flfmt9660.c b/src/cmd/fossil/flfmt9660.c
new file mode 100644
index 00000000..4464c9fb
--- /dev/null
+++ b/src/cmd/fossil/flfmt9660.c
@@ -0,0 +1,565 @@
+/*
+ * Initialize a fossil file system from an ISO9660 image already in the
+ * file system. This is a fairly bizarre thing to do, but it lets us generate
+ * installation CDs that double as valid Plan 9 disk partitions.
+ * People having trouble booting the CD can just copy it into a disk
+ * partition and you've got a working Plan 9 system.
+ *
+ * I've tried hard to keep all the associated cruft in this file.
+ * If you deleted this file and cut out the three calls into it from flfmt.c,
+ * no traces would remain.
+ */
+
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "flfmt9660.h"
+#include <bio.h>
+#include <ctype.h>
+
+static Biobuf *b;
+
+enum{
+ Tag = 0x96609660,
+ Blocksize = 2048,
+};
+
+#pragma varargck type "s" uchar*
+#pragma varargck type "L" uchar*
+#pragma varargck type "B" uchar*
+#pragma varargck type "N" uchar*
+#pragma varargck type "T" uchar*
+#pragma varargck type "D" uchar*
+
+typedef struct Voldesc Voldesc;
+struct Voldesc {
+ uchar magic[8]; /* 0x01, "CD001", 0x01, 0x00 */
+ uchar systemid[32]; /* system identifier */
+ uchar volumeid[32]; /* volume identifier */
+ uchar unused[8]; /* character set in secondary desc */
+ uchar volsize[8]; /* volume size */
+ uchar charset[32];
+ uchar volsetsize[4]; /* volume set size = 1 */
+ uchar volseqnum[4]; /* volume sequence number = 1 */
+ uchar blocksize[4]; /* logical block size */
+ uchar pathsize[8]; /* path table size */
+ uchar lpathloc[4]; /* Lpath */
+ uchar olpathloc[4]; /* optional Lpath */
+ uchar mpathloc[4]; /* Mpath */
+ uchar ompathloc[4]; /* optional Mpath */
+ uchar rootdir[34]; /* root directory */
+ uchar volsetid[128]; /* volume set identifier */
+ uchar publisher[128];
+ uchar prepid[128]; /* data preparer identifier */
+ uchar applid[128]; /* application identifier */
+ uchar notice[37]; /* copyright notice file */
+ uchar abstract[37]; /* abstract file */
+ uchar biblio[37]; /* bibliographic file */
+ uchar cdate[17]; /* creation date */
+ uchar mdate[17]; /* modification date */
+ uchar xdate[17]; /* expiration date */
+ uchar edate[17]; /* effective date */
+ uchar fsvers; /* file system version = 1 */
+};
+
+static void
+dumpbootvol(void *a)
+{
+ Voldesc *v;
+
+ v = a;
+ print("magic %.2ux %.5s %.2ux %2ux\n",
+ v->magic[0], v->magic+1, v->magic[6], v->magic[7]);
+ if(v->magic[0] == 0xFF)
+ return;
+
+ print("system %.32T\n", v->systemid);
+ print("volume %.32T\n", v->volumeid);
+ print("volume size %.4N\n", v->volsize);
+ print("charset %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n",
+ v->charset[0], v->charset[1], v->charset[2], v->charset[3],
+ v->charset[4], v->charset[5], v->charset[6], v->charset[7]);
+ print("volume set size %.2N\n", v->volsetsize);
+ print("volume sequence number %.2N\n", v->volseqnum);
+ print("logical block size %.2N\n", v->blocksize);
+ print("path size %.4L\n", v->pathsize);
+ print("lpath loc %.4L\n", v->lpathloc);
+ print("opt lpath loc %.4L\n", v->olpathloc);
+ print("mpath loc %.4B\n", v->mpathloc);
+ print("opt mpath loc %.4B\n", v->ompathloc);
+ print("rootdir %D\n", v->rootdir);
+ print("volume set identifier %.128T\n", v->volsetid);
+ print("publisher %.128T\n", v->publisher);
+ print("preparer %.128T\n", v->prepid);
+ print("application %.128T\n", v->applid);
+ print("notice %.37T\n", v->notice);
+ print("abstract %.37T\n", v->abstract);
+ print("biblio %.37T\n", v->biblio);
+ print("creation date %.17s\n", v->cdate);
+ print("modification date %.17s\n", v->mdate);
+ print("expiration date %.17s\n", v->xdate);
+ print("effective date %.17s\n", v->edate);
+ print("fs version %d\n", v->fsvers);
+}
+
+typedef struct Cdir Cdir;
+struct Cdir {
+ uchar len;
+ uchar xlen;
+ uchar dloc[8];
+ uchar dlen[8];
+ uchar date[7];
+ uchar flags;
+ uchar unitsize;
+ uchar gapsize;
+ uchar volseqnum[4];
+ uchar namelen;
+ uchar name[1]; /* chumminess */
+};
+#pragma varargck type "D" Cdir*
+
+static int
+Dfmt(Fmt *fmt)
+{
+ char buf[128];
+ Cdir *c;
+
+ c = va_arg(fmt->args, Cdir*);
+ if(c->namelen == 1 && c->name[0] == '\0' || c->name[0] == '\001') {
+ snprint(buf, sizeof buf, ".%s dloc %.4N dlen %.4N",
+ c->name[0] ? "." : "", c->dloc, c->dlen);
+ } else {
+ snprint(buf, sizeof buf, "%.*T dloc %.4N dlen %.4N", c->namelen, c->name,
+ c->dloc, c->dlen);
+ }
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+char longc, shortc;
+static void
+bigend(void)
+{
+ longc = 'B';
+}
+
+static void
+littleend(void)
+{
+ longc = 'L';
+}
+
+static ulong
+big(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v = (v<<8) | *p++;
+ return v;
+}
+
+static ulong
+little(void *a, int n)
+{
+ uchar *p;
+ ulong v;
+ int i;
+
+ p = a;
+ v = 0;
+ for(i=0; i<n; i++)
+ v |= (*p++<<(i*8));
+ return v;
+}
+
+/* numbers in big or little endian. */
+static int
+BLfmt(Fmt *fmt)
+{
+ ulong v;
+ uchar *p;
+ char buf[20];
+
+ p = va_arg(fmt->args, uchar*);
+
+ if(!(fmt->flags&FmtPrec)) {
+ fmtstrcpy(fmt, "*BL*");
+ return 0;
+ }
+
+ if(fmt->r == 'B')
+ v = big(p, fmt->prec);
+ else
+ v = little(p, fmt->prec);
+
+ sprint(buf, "0x%.*lux", fmt->prec*2, v);
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+/* numbers in both little and big endian */
+static int
+Nfmt(Fmt *fmt)
+{
+ char buf[100];
+ uchar *p;
+
+ p = va_arg(fmt->args, uchar*);
+
+ sprint(buf, "%.*L %.*B", fmt->prec, p, fmt->prec, p+fmt->prec);
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+static int
+asciiTfmt(Fmt *fmt)
+{
+ char *p, buf[256];
+ int i;
+
+ p = va_arg(fmt->args, char*);
+ for(i=0; i<fmt->prec; i++)
+ buf[i] = *p++;
+ buf[i] = '\0';
+ for(p=buf+strlen(buf); p>buf && p[-1]==' '; p--)
+ ;
+ p[0] = '\0';
+ fmt->flags &= ~FmtPrec;
+ fmtstrcpy(fmt, buf);
+ return 0;
+}
+
+static void
+ascii(void)
+{
+ fmtinstall('T', asciiTfmt);
+}
+
+static int
+runeTfmt(Fmt *fmt)
+{
+ Rune buf[256], *r;
+ int i;
+ uchar *p;
+
+ p = va_arg(fmt->args, uchar*);
+ for(i=0; i*2+2<=fmt->prec; i++, p+=2)
+ buf[i] = (p[0]<<8)|p[1];
+ buf[i] = L'\0';
+ for(r=buf+i; r>buf && r[-1]==L' '; r--)
+ ;
+ r[0] = L'\0';
+ fmt->flags &= ~FmtPrec;
+ return fmtprint(fmt, "%S", buf);
+}
+
+static void
+getsect(uchar *buf, int n)
+{
+ if(Bseek(b, n*2048, 0) != n*2048 || Bread(b, buf, 2048) != 2048)
+{
+abort();
+ sysfatal("reading block at %,d: %r", n*2048);
+}
+}
+
+static Header *h;
+static int fd;
+static char *file9660;
+static int off9660;
+static ulong startoff;
+static ulong endoff;
+static ulong fsoff;
+static uchar root[2048];
+static Voldesc *v;
+static ulong iso9660start(Cdir*);
+static void iso9660copydir(Fs*, File*, Cdir*);
+static void iso9660copyfile(Fs*, File*, Cdir*);
+
+void
+iso9660init(int xfd, Header *xh, char *xfile9660, int xoff9660)
+{
+ uchar sect[2048], sect2[2048];
+
+ fmtinstall('L', BLfmt);
+ fmtinstall('B', BLfmt);
+ fmtinstall('N', Nfmt);
+ fmtinstall('D', Dfmt);
+
+ fd = xfd;
+ h = xh;
+ file9660 = xfile9660;
+ off9660 = xoff9660;
+
+ if((b = Bopen(file9660, OREAD)) == nil)
+ vtFatal("Bopen %s: %r", file9660);
+
+ getsect(root, 16);
+ ascii();
+
+ v = (Voldesc*)root;
+ if(memcmp(v->magic, "\x01CD001\x01\x00", 8) != 0)
+ vtFatal("%s not a cd image", file9660);
+
+ startoff = iso9660start((Cdir*)v->rootdir)*Blocksize;
+ endoff = little(v->volsize, 4); /* already in bytes */
+
+ fsoff = off9660 + h->data*h->blockSize;
+ if(fsoff > startoff)
+ vtFatal("fossil data starts after cd data");
+ if(off9660 + (vlong)h->end*h->blockSize < endoff)
+ vtFatal("fossil data ends before cd data");
+ if(fsoff%h->blockSize)
+ vtFatal("cd offset not a multiple of fossil block size");
+
+ /* Read "same" block via CD image and via Fossil image */
+ getsect(sect, startoff/Blocksize);
+ if(seek(fd, startoff-off9660, 0) < 0)
+ vtFatal("cannot seek to first data sector on cd via fossil");
+fprint(2, "look for %lud at %lud\n", startoff, startoff-off9660);
+ if(readn(fd, sect2, Blocksize) != Blocksize)
+ vtFatal("cannot read first data sector on cd via fossil");
+ if(memcmp(sect, sect2, Blocksize) != 0)
+ vtFatal("iso9660 offset is a lie %08ux %08ux", *(long*)sect, *(long*)sect2);
+}
+
+void
+iso9660labels(Disk *disk, uchar *buf, void (*write)(int, u32int))
+{
+ ulong sb, eb, bn, lb, llb;
+ Label l;
+ int lpb;
+ uchar sect[Blocksize];
+
+ if(!diskReadRaw(disk, PartData, (startoff-fsoff)/h->blockSize, buf))
+ vtFatal("disk read failed: %r");
+ getsect(sect, startoff/Blocksize);
+ if(memcmp(buf, sect, Blocksize) != 0)
+ vtFatal("fsoff is wrong");
+
+ sb = (startoff-fsoff)/h->blockSize;
+ eb = (endoff-fsoff+h->blockSize-1)/h->blockSize;
+
+ lpb = h->blockSize/LabelSize;
+
+ /* for each reserved block, mark label */
+ llb = ~0;
+ l.type = BtData;
+ l.state = BsAlloc;
+ l.tag = Tag;
+ l.epoch = 1;
+ l.epochClose = ~(u32int)0;
+ for(bn=sb; bn<eb; bn++){
+ lb = bn/lpb;
+ if(lb != llb){
+ if(llb != ~0)
+ (*write)(PartLabel, llb);
+ memset(buf, 0, h->blockSize);
+ }
+ llb = lb;
+ labelPack(&l, buf, bn%lpb);
+ }
+ if(llb != ~0)
+ (*write)(PartLabel, llb);
+}
+
+void
+iso9660copy(Fs *fs)
+{
+ File *root;
+
+ root = fileOpen(fs, "/active");
+ iso9660copydir(fs, root, (Cdir*)v->rootdir);
+ fileDecRef(root);
+ vtRUnlock(fs->elk);
+ if(!fsSnapshot(fs, nil, nil, 0))
+ vtFatal("snapshot failed: %R");
+ vtRLock(fs->elk);
+}
+
+/*
+ * The first block used is the first data block of the leftmost file in the tree.
+ * (Just an artifact of how mk9660 works.)
+ */
+static ulong
+iso9660start(Cdir *c)
+{
+ uchar sect[Blocksize];
+
+ while(c->flags&2){
+ getsect(sect, little(c->dloc, 4));
+ c = (Cdir*)sect;
+ c = (Cdir*)((uchar*)c+c->len); /* skip dot */
+ c = (Cdir*)((uchar*)c+c->len); /* skip dotdot */
+ /* oops: might happen if leftmost directory is empty or leftmost file is zero length! */
+ if(little(c->dloc, 4) == 0)
+ vtFatal("error parsing cd image or unfortunate cd image");
+ }
+ return little(c->dloc, 4);
+}
+
+static void
+iso9660copydir(Fs *fs, File *dir, Cdir *cd)
+{
+ ulong off, end, len;
+ uchar sect[Blocksize], *esect, *p;
+ Cdir *c;
+
+ len = little(cd->dlen, 4);
+ off = little(cd->dloc, 4)*Blocksize;
+ end = off+len;
+ esect = sect+Blocksize;
+
+ for(; off<end; off+=Blocksize){
+ getsect(sect, off/Blocksize);
+ p = sect;
+ while(p < esect){
+ c = (Cdir*)p;
+ if(c->len <= 0)
+ break;
+ if(c->namelen!=1 || c->name[0]>1)
+ iso9660copyfile(fs, dir, c);
+ p += c->len;
+ }
+ }
+}
+
+static char*
+getname(uchar **pp)
+{
+ uchar *p;
+ int l;
+
+ p = *pp;
+ l = *p;
+ *pp = p+1+l;
+ if(l == 0)
+ return "";
+ memmove(p, p+1, l);
+ p[l] = 0;
+ return (char*)p;
+}
+
+static char*
+getcname(Cdir *c)
+{
+ uchar *up;
+ char *p, *q;
+
+ up = &c->namelen;
+ p = getname(&up);
+ for(q=p; *q; q++)
+ *q = tolower(*q);
+ return p;
+}
+
+static char
+dmsize[12] =
+{
+ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+};
+
+static ulong
+getcdate(uchar *p) /* yMdhmsz */
+{
+ Tm tm;
+ int y, M, d, h, m, s, tz;
+
+ y=p[0]; M=p[1]; d=p[2];
+ h=p[3]; m=p[4]; s=p[5]; tz=p[6];
+ USED(tz);
+ if (y < 70)
+ return 0;
+ if (M < 1 || M > 12)
+ return 0;
+ if (d < 1 || d > dmsize[M-1])
+ return 0;
+ if (h > 23)
+ return 0;
+ if (m > 59)
+ return 0;
+ if (s > 59)
+ return 0;
+
+ memset(&tm, 0, sizeof tm);
+ tm.sec = s;
+ tm.min = m;
+ tm.hour = h;
+ tm.mday = d;
+ tm.mon = M-1;
+ tm.year = 1900+y;
+ tm.zone[0] = 0;
+ return tm2sec(&tm);
+}
+
+static int ind;
+
+static void
+iso9660copyfile(Fs *fs, File *dir, Cdir *c)
+{
+ Dir d;
+ DirEntry de;
+ int sysl;
+ uchar score[VtScoreSize];
+ ulong off, foff, len, mode;
+ uchar *p;
+ File *f;
+
+ ind++;
+ memset(&d, 0, sizeof d);
+ p = c->name + c->namelen;
+ if(((uintptr)p) & 1)
+ p++;
+ sysl = (uchar*)c + c->len - p;
+ if(sysl <= 0)
+ vtFatal("missing plan9 directory entry on %d/%d/%.*s", c->namelen, c->name[0], c->namelen, c->name);
+ d.name = getname(&p);
+ d.uid = getname(&p);
+ d.gid = getname(&p);
+ if((uintptr)p & 1)
+ p++;
+ d.mode = little(p, 4);
+ if(d.name[0] == 0)
+ d.name = getcname(c);
+ d.mtime = getcdate(c->date);
+ d.atime = d.mtime;
+
+if(d.mode&DMDIR) print("%*scopy %s %s %s %luo\n", ind*2, "", d.name, d.uid, d.gid, d.mode);
+
+ mode = d.mode&0777;
+ if(d.mode&DMDIR)
+ mode |= ModeDir;
+ if((f = fileCreate(dir, d.name, mode, d.uid)) == nil)
+ vtFatal("could not create file '%s': %r", d.name);
+ if(d.mode&DMDIR)
+ iso9660copydir(fs, f, c);
+ else{
+ len = little(c->dlen, 4);
+ off = little(c->dloc, 4)*Blocksize;
+ for(foff=0; foff<len; foff+=h->blockSize){
+ localToGlobal((off+foff-fsoff)/h->blockSize, score);
+ if(!fileMapBlock(f, foff/h->blockSize, score, Tag))
+ vtFatal("fileMapBlock: %R");
+ }
+ if(!fileSetSize(f, len))
+ vtFatal("fileSetSize: %R");
+ }
+ if(!fileGetDir(f, &de))
+ vtFatal("fileGetDir: %R");
+ de.uid = d.uid;
+ de.gid = d.gid;
+ de.mtime = d.mtime;
+ de.atime = d.atime;
+ de.mode = d.mode&0777;
+ if(!fileSetDir(f, &de, "sys"))
+ vtFatal("fileSetDir: %R");
+ fileDecRef(f);
+ ind--;
+}
diff --git a/src/cmd/fossil/flfmt9660.h b/src/cmd/fossil/flfmt9660.h
new file mode 100644
index 00000000..f9be9ed8
--- /dev/null
+++ b/src/cmd/fossil/flfmt9660.h
@@ -0,0 +1,3 @@
+void iso9660init(int fd, Header *h, char*, int);
+void iso9660labels(Disk*, uchar*, void(*write)(int, u32int));
+void iso9660copy(Fs*);
diff --git a/src/cmd/fossil/fns.h b/src/cmd/fossil/fns.h
new file mode 100644
index 00000000..c22d3b79
--- /dev/null
+++ b/src/cmd/fossil/fns.h
@@ -0,0 +1,106 @@
+Block* sourceBlock(Source*, ulong, int);
+Block* _sourceBlock(Source*, ulong, int, int, ulong);
+void sourceClose(Source*);
+Source* sourceCreate(Source*, int, int, u32int);
+ulong sourceGetDirSize(Source*);
+int sourceGetEntry(Source*, Entry*);
+uvlong sourceGetSize(Source*);
+int sourceLock2(Source*, Source*, int);
+int sourceLock(Source*, int);
+char *sourceName(Source *s);
+Source* sourceOpen(Source*, ulong, int, int);
+int sourceRemove(Source*);
+Source* sourceRoot(Fs*, u32int, int);
+int sourceSetDirSize(Source*, ulong);
+int sourceSetEntry(Source*, Entry*);
+int sourceSetSize(Source*, uvlong);
+int sourceTruncate(Source*);
+void sourceUnlock(Source*);
+
+Block* cacheAllocBlock(Cache*, int, u32int, u32int, u32int);
+Cache* cacheAlloc(Disk*, VtSession*, ulong, int);
+void cacheCountUsed(Cache*, u32int, u32int*, u32int*, u32int*);
+int cacheDirty(Cache*);
+void cacheFlush(Cache*, int);
+void cacheFree(Cache*);
+Block* cacheGlobal(Cache*, uchar[VtScoreSize], int, u32int, int);
+Block* cacheLocal(Cache*, int, u32int, int);
+Block* cacheLocalData(Cache*, u32int, int, u32int, int, u32int);
+u32int cacheLocalSize(Cache*, int);
+int readLabel(Cache*, Label*, u32int addr);
+
+Block* blockCopy(Block*, u32int, u32int, u32int);
+void blockDependency(Block*, Block*, int, uchar*, Entry*);
+int blockDirty(Block*);
+void blockDupLock(Block*);
+void blockPut(Block*);
+void blockRemoveLink(Block*, u32int, int, u32int, int);
+uchar* blockRollback(Block*, uchar*);
+void blockSetIOState(Block*, int);
+Block* _blockSetLabel(Block*, Label*);
+int blockSetLabel(Block*, Label*, int);
+int blockWrite(Block*, int);
+
+Disk* diskAlloc(int);
+int diskBlockSize(Disk*);
+int diskFlush(Disk*);
+void diskFree(Disk*);
+void diskRead(Disk*, Block*);
+int diskReadRaw(Disk*, int, u32int, uchar*);
+u32int diskSize(Disk*, int);
+void diskWriteAndWait(Disk*, Block*);
+void diskWrite(Disk*, Block*);
+int diskWriteRaw(Disk*, int, u32int, uchar*);
+
+char* bioStr(int);
+char* bsStr(int);
+char* btStr(int);
+u32int globalToLocal(uchar[VtScoreSize]);
+void localToGlobal(u32int, uchar[VtScoreSize]);
+
+void headerPack(Header*, uchar*);
+int headerUnpack(Header*, uchar*);
+
+int labelFmt(Fmt*);
+void labelPack(Label*, uchar*, int);
+int labelUnpack(Label*, uchar*, int);
+
+int scoreFmt(Fmt*);
+
+void superPack(Super*, uchar*);
+int superUnpack(Super*, uchar*);
+
+void entryPack(Entry*, uchar*, int);
+int entryType(Entry*);
+int entryUnpack(Entry*, uchar*, int);
+
+Periodic* periodicAlloc(void (*)(void*), void*, int);
+void periodicKill(Periodic*);
+
+int fileGetSources(File*, Entry*, Entry*);
+File* fileRoot(Source*);
+int fileSnapshot(File*, File*, u32int, int);
+int fsNextQid(Fs*, u64int*);
+int mkVac(VtSession*, uint, Entry*, Entry*, DirEntry*, uchar[VtScoreSize]);
+Block* superGet(Cache*, Super*);
+
+void archFree(Arch*);
+Arch* archInit(Cache*, Disk*, Fs*, VtSession*);
+void archKick(Arch*);
+
+void bwatchDependency(Block*);
+void bwatchInit(void);
+void bwatchLock(Block*);
+void bwatchReset(uchar[VtScoreSize]);
+void bwatchSetBlockSize(uint);
+void bwatchUnlock(Block*);
+
+void initWalk(WalkPtr*, Block*, uint);
+int nextWalk(WalkPtr*, uchar[VtScoreSize], uchar*, u32int*, Entry**);
+
+void snapGetTimes(Snap*, u32int*, u32int*, u32int*);
+void snapSetTimes(Snap*, u32int, u32int, u32int);
+
+void fsCheck(Fsck*);
+
+#pragma varargck type "L" Label*
diff --git a/src/cmd/fossil/fossil.c b/src/cmd/fossil/fossil.c
new file mode 100644
index 00000000..8ef68080
--- /dev/null
+++ b/src/cmd/fossil/fossil.c
@@ -0,0 +1,143 @@
+#include "stdinc.h"
+#include <ctype.h>
+
+#include "9.h"
+
+int Dflag;
+int mempcnt; /* for 9fsys.c */
+char* none = "none";
+char* foptname = "/none/such";
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-Dt] [-c cmd] [-f partition] [-m %%]\n", argv0);
+ exits("usage");
+}
+
+static void
+readCmdPart(char *file, char ***pcmd, int *pncmd)
+{
+ char buf[1024+1], *f[1024];
+ char tbuf[1024];
+ int nf;
+ int i, fd, n;
+ char **cmd, *p;
+ int ncmd;
+
+ cmd = *pcmd;
+ ncmd = *pncmd;
+
+ if((fd = open(file, OREAD)) < 0)
+ sysfatal("open %s: %r", file);
+ if(seek(fd, 127*1024, 0) != 127*1024)
+ sysfatal("seek %s 127kB: %r", file);
+ n = readn(fd, buf, sizeof buf-1);
+ if(n == 0)
+ sysfatal("short read of %s at 127kB", file);
+ if(n < 0)
+ sysfatal("read %s: %r", file);
+ buf[n] = 0;
+ if(memcmp(buf, "fossil config\n", 6+1+6+1) != 0)
+ sysfatal("bad config magic in %s", file);
+ nf = getfields(buf+6+1+6+1, f, nelem(f), 1, "\n");
+ for(i=0; i<nf; i++){
+ if(f[i][0] == '#')
+ continue;
+ cmd = vtMemRealloc(cmd, (ncmd+1)*sizeof(char*));
+ /* expand argument '*' to mean current file */
+ if((p = strchr(f[i], '*')) && (p==f[i]||isspace(p[-1])) && (p[1]==0||isspace(p[1]))){
+ memmove(tbuf, f[i], p-f[i]);
+ strecpy(tbuf+(p-f[i]), tbuf+sizeof tbuf, file);
+ strecpy(tbuf+strlen(tbuf), tbuf+sizeof tbuf, p+1);
+ f[i] = tbuf;
+ }
+ cmd[ncmd++] = vtStrDup(f[i]);
+ }
+ close(fd);
+ *pcmd = cmd;
+ *pncmd = ncmd;
+}
+
+void
+main(int argc, char* argv[])
+{
+ char **cmd, *p;
+ int i, ncmd, tflag;
+
+ fmtinstall('D', dirfmt);
+ fmtinstall('F', fcallfmt);
+ fmtinstall('M', dirmodefmt);
+ quotefmtinstall();
+
+ /*
+ * Insulate from the invoker's environment.
+ */
+ if(rfork(RFREND|RFNOTEG|RFNAMEG) < 0)
+ sysfatal("rfork: %r");
+
+ close(0);
+ open("/dev/null", OREAD);
+ close(1);
+ open("/dev/null", OWRITE);
+
+ cmd = nil;
+ ncmd = tflag = 0;
+
+ vtAttach();
+
+ ARGBEGIN{
+ case '?':
+ default:
+ usage();
+ break;
+ case 'c':
+ p = EARGF(usage());
+ currfsysname = p;
+ cmd = vtMemRealloc(cmd, (ncmd+1)*sizeof(char*));
+ cmd[ncmd++] = p;
+ break;
+ case 'D':
+ Dflag ^= 1;
+ break;
+ case 'f':
+ p = EARGF(usage());
+ currfsysname = foptname = p;
+ readCmdPart(p, &cmd, &ncmd);
+ break;
+ case 'm':
+ mempcnt = atoi(EARGF(usage()));
+ if(mempcnt <= 0 || mempcnt >= 100)
+ usage();
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ }ARGEND
+ if(argc != 0)
+ usage();
+
+ consInit();
+ cliInit();
+ msgInit();
+ conInit();
+ cmdInit();
+ fsysInit();
+ exclInit();
+ fidInit();
+
+ srvInit();
+ lstnInit();
+ usersInit();
+
+ for(i = 0; i < ncmd; i++)
+ if(cliExec(cmd[i]) == 0)
+ fprint(2, "%s: %R\n", cmd[i]);
+ vtMemFree(cmd);
+
+ if(tflag && consTTY() == 0)
+ consPrint("%s\n", vtGetError());
+
+ vtDetach();
+ exits(0);
+}
diff --git a/src/cmd/fossil/fs.c b/src/cmd/fossil/fs.c
new file mode 100644
index 00000000..ba15e661
--- /dev/null
+++ b/src/cmd/fossil/fs.c
@@ -0,0 +1,1099 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+static void fsMetaFlush(void *a);
+static Snap *snapInit(Fs*);
+static void snapClose(Snap*);
+
+Fs *
+fsOpen(char *file, VtSession *z, long ncache, int mode)
+{
+ int fd, m;
+ uchar oscore[VtScoreSize];
+ Block *b, *bs;
+ Disk *disk;
+ Fs *fs;
+ Super super;
+
+ switch(mode){
+ default:
+ vtSetError(EBadMode);
+ return nil;
+ case OReadOnly:
+ m = OREAD;
+ break;
+ case OReadWrite:
+ m = ORDWR;
+ break;
+ }
+ fd = open(file, m);
+ if(fd < 0){
+ vtSetError("open %s: %r", file);
+ return nil;
+ }
+
+ bwatchInit();
+ disk = diskAlloc(fd);
+ if(disk == nil){
+ vtSetError("diskAlloc: %R");
+ close(fd);
+ return nil;
+ }
+
+ fs = vtMemAllocZ(sizeof(Fs));
+ fs->mode = mode;
+ fs->name = vtStrDup(file);
+ fs->blockSize = diskBlockSize(disk);
+ fs->elk = vtLockAlloc();
+ fs->cache = cacheAlloc(disk, z, ncache, mode);
+ if(mode == OReadWrite && z)
+ fs->arch = archInit(fs->cache, disk, fs, z);
+ fs->z = z;
+
+ b = cacheLocal(fs->cache, PartSuper, 0, mode);
+ if(b == nil)
+ goto Err;
+ if(!superUnpack(&super, b->data)){
+ blockPut(b);
+ vtSetError("bad super block");
+ goto Err;
+ }
+ blockPut(b);
+
+ fs->ehi = super.epochHigh;
+ fs->elo = super.epochLow;
+
+//fprint(2, "%s: fs->ehi %d fs->elo %d active=%d\n", argv0, fs->ehi, fs->elo, super.active);
+
+ fs->source = sourceRoot(fs, super.active, mode);
+ if(fs->source == nil){
+ /*
+ * Perhaps it failed because the block is copy-on-write.
+ * Do the copy and try again.
+ */
+ if(mode == OReadOnly || strcmp(vtGetError(), EBadRoot) != 0)
+ goto Err;
+ b = cacheLocalData(fs->cache, super.active, BtDir, RootTag,
+ OReadWrite, 0);
+ if(b == nil){
+ vtSetError("cacheLocalData: %R");
+ goto Err;
+ }
+ if(b->l.epoch == fs->ehi){
+ blockPut(b);
+ vtSetError("bad root source block");
+ goto Err;
+ }
+ b = blockCopy(b, RootTag, fs->ehi, fs->elo);
+ if(b == nil)
+ goto Err;
+ localToGlobal(super.active, oscore);
+ super.active = b->addr;
+ bs = cacheLocal(fs->cache, PartSuper, 0, OReadWrite);
+ if(bs == nil){
+ blockPut(b);
+ vtSetError("cacheLocal: %R");
+ goto Err;
+ }
+ superPack(&super, bs->data);
+ blockDependency(bs, b, 0, oscore, nil);
+ blockPut(b);
+ blockDirty(bs);
+ blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
+ blockPut(bs);
+ fs->source = sourceRoot(fs, super.active, mode);
+ if(fs->source == nil){
+ vtSetError("sourceRoot: %R");
+ goto Err;
+ }
+ }
+
+//fprint(2, "%s: got fs source\n", argv0);
+
+ vtRLock(fs->elk);
+ fs->file = fileRoot(fs->source);
+ fs->source->file = fs->file; /* point back */
+ vtRUnlock(fs->elk);
+ if(fs->file == nil){
+ vtSetError("fileRoot: %R");
+ goto Err;
+ }
+
+//fprint(2, "%s: got file root\n", argv0);
+
+ if(mode == OReadWrite){
+ fs->metaFlush = periodicAlloc(fsMetaFlush, fs, 1000);
+ fs->snap = snapInit(fs);
+ }
+ return fs;
+
+Err:
+fprint(2, "%s: fsOpen error\n", argv0);
+ fsClose(fs);
+ return nil;
+}
+
+void
+fsClose(Fs *fs)
+{
+ vtRLock(fs->elk);
+ periodicKill(fs->metaFlush);
+ snapClose(fs->snap);
+ if(fs->file){
+ fileMetaFlush(fs->file, 0);
+ if(!fileDecRef(fs->file))
+ vtFatal("fsClose: files still in use: %r\n");
+ }
+ fs->file = nil;
+ sourceClose(fs->source);
+ cacheFree(fs->cache);
+ if(fs->arch)
+ archFree(fs->arch);
+ vtMemFree(fs->name);
+ vtRUnlock(fs->elk);
+ vtLockFree(fs->elk);
+ memset(fs, ~0, sizeof(Fs));
+ vtMemFree(fs);
+}
+
+int
+fsRedial(Fs *fs, char *host)
+{
+ if(!vtRedial(fs->z, host))
+ return 0;
+ if(!vtConnect(fs->z, 0))
+ return 0;
+ return 1;
+}
+
+File *
+fsGetRoot(Fs *fs)
+{
+ return fileIncRef(fs->file);
+}
+
+int
+fsGetBlockSize(Fs *fs)
+{
+ return fs->blockSize;
+}
+
+Block*
+superGet(Cache *c, Super* super)
+{
+ Block *b;
+
+ if((b = cacheLocal(c, PartSuper, 0, OReadWrite)) == nil){
+ fprint(2, "%s: superGet: cacheLocal failed: %R\n", argv0);
+ return nil;
+ }
+ if(!superUnpack(super, b->data)){
+ fprint(2, "%s: superGet: superUnpack failed: %R\n", argv0);
+ blockPut(b);
+ return nil;
+ }
+
+ return b;
+}
+
+void
+superWrite(Block* b, Super* super, int forceWrite)
+{
+ superPack(super, b->data);
+ blockDirty(b);
+ if(forceWrite){
+ while(!blockWrite(b, Waitlock)){
+ /* this should no longer happen */
+ fprint(2, "%s: could not write super block; "
+ "waiting 10 seconds\n", argv0);
+ sleep(10*1000);
+ }
+ while(b->iostate != BioClean && b->iostate != BioDirty){
+ assert(b->iostate == BioWriting);
+ vtSleep(b->ioready);
+ }
+ /*
+ * it's okay that b might still be dirty.
+ * that means it got written out but with an old root pointer,
+ * but the other fields went out, and those are the ones
+ * we really care about. (specifically, epochHigh; see fsSnapshot).
+ */
+ }
+}
+
+/*
+ * Prepare the directory to store a snapshot.
+ * Temporary snapshots go into /snapshot/yyyy/mmdd/hhmm[.#]
+ * Archival snapshots go into /archive/yyyy/mmdd[.#].
+ *
+ * TODO This should be rewritten to eliminate most of the duplication.
+ */
+static File*
+fileOpenSnapshot(Fs *fs, char *dstpath, int doarchive)
+{
+ int n;
+ char buf[30], *s, *p, *elem;
+ File *dir, *f;
+ Tm now;
+
+ if(dstpath){
+ if((p = strrchr(dstpath, '/')) != nil){
+ *p++ = '\0';
+ elem = p;
+ p = dstpath;
+ if(*p == '\0')
+ p = "/";
+ }else{
+ p = "/";
+ elem = dstpath;
+ }
+ if((dir = fileOpen(fs, p)) == nil)
+ return nil;
+ f = fileCreate(dir, elem, ModeDir|ModeSnapshot|0555, "adm");
+ fileDecRef(dir);
+ return f;
+ }else if(doarchive){
+ /*
+ * a snapshot intended to be archived to venti.
+ */
+ dir = fileOpen(fs, "/archive");
+ if(dir == nil)
+ return nil;
+ now = *localtime(time(0));
+
+ /* yyyy */
+ snprint(buf, sizeof(buf), "%d", now.year+1900);
+ f = fileWalk(dir, buf);
+ if(f == nil)
+ f = fileCreate(dir, buf, ModeDir|0555, "adm");
+ fileDecRef(dir);
+ if(f == nil)
+ return nil;
+ dir = f;
+
+ /* mmdd[#] */
+ snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
+ s = buf+strlen(buf);
+ for(n=0;; n++){
+ if(n)
+ seprint(s, buf+sizeof(buf), ".%d", n);
+ f = fileWalk(dir, buf);
+ if(f != nil){
+ fileDecRef(f);
+ continue;
+ }
+ f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
+ break;
+ }
+ fileDecRef(dir);
+ return f;
+ }else{
+ /*
+ * Just a temporary snapshot
+ * We'll use /snapshot/yyyy/mmdd/hhmm.
+ * There may well be a better naming scheme.
+ * (I'd have used hh:mm but ':' is reserved in Microsoft file systems.)
+ */
+ dir = fileOpen(fs, "/snapshot");
+ if(dir == nil)
+ return nil;
+
+ now = *localtime(time(0));
+
+ /* yyyy */
+ snprint(buf, sizeof(buf), "%d", now.year+1900);
+ f = fileWalk(dir, buf);
+ if(f == nil)
+ f = fileCreate(dir, buf, ModeDir|0555, "adm");
+ fileDecRef(dir);
+ if(f == nil)
+ return nil;
+ dir = f;
+
+ /* mmdd */
+ snprint(buf, sizeof(buf), "%02d%02d", now.mon+1, now.mday);
+ f = fileWalk(dir, buf);
+ if(f == nil)
+ f = fileCreate(dir, buf, ModeDir|0555, "adm");
+ fileDecRef(dir);
+ if(f == nil)
+ return nil;
+ dir = f;
+
+ /* hhmm[.#] */
+ snprint(buf, sizeof buf, "%02d%02d", now.hour, now.min);
+ s = buf+strlen(buf);
+ for(n=0;; n++){
+ if(n)
+ seprint(s, buf+sizeof(buf), ".%d", n);
+ f = fileWalk(dir, buf);
+ if(f != nil){
+ fileDecRef(f);
+ continue;
+ }
+ f = fileCreate(dir, buf, ModeDir|ModeSnapshot|0555, "adm");
+ break;
+ }
+ fileDecRef(dir);
+ return f;
+ }
+}
+
+static int
+fsNeedArch(Fs *fs, uint archMinute)
+{
+ int need;
+ File *f;
+ char buf[100];
+ Tm now;
+ ulong then;
+
+ then = time(0);
+ now = *localtime(then);
+
+ /* back up to yesterday if necessary */
+ if(now.hour < archMinute/60
+ || now.hour == archMinute/60 && now.min < archMinute%60)
+ now = *localtime(then-86400);
+
+ snprint(buf, sizeof buf, "/archive/%d/%02d%02d",
+ now.year+1900, now.mon+1, now.mday);
+ need = 1;
+ vtRLock(fs->elk);
+ f = fileOpen(fs, buf);
+ if(f){
+ need = 0;
+ fileDecRef(f);
+ }
+ vtRUnlock(fs->elk);
+ return need;
+}
+
+int
+fsEpochLow(Fs *fs, u32int low)
+{
+ Block *bs;
+ Super super;
+
+ vtLock(fs->elk);
+ if(low > fs->ehi){
+ vtSetError("bad low epoch (must be <= %ud)", fs->ehi);
+ vtUnlock(fs->elk);
+ return 0;
+ }
+
+ if((bs = superGet(fs->cache, &super)) == nil){
+ vtUnlock(fs->elk);
+ return 0;
+ }
+
+ super.epochLow = low;
+ fs->elo = low;
+ superWrite(bs, &super, 1);
+ blockPut(bs);
+ vtUnlock(fs->elk);
+
+ return 1;
+}
+
+static int
+bumpEpoch(Fs *fs, int doarchive)
+{
+ uchar oscore[VtScoreSize];
+ u32int oldaddr;
+ Block *b, *bs;
+ Entry e;
+ Source *r;
+ Super super;
+
+ /*
+ * Duplicate the root block.
+ *
+ * As a hint to flchk, the garbage collector,
+ * and any (human) debuggers, store a pointer
+ * to the old root block in entry 1 of the new root block.
+ */
+ r = fs->source;
+ b = cacheGlobal(fs->cache, r->score, BtDir, RootTag, OReadOnly);
+ if(b == nil)
+ return 0;
+
+ memset(&e, 0, sizeof e);
+ e.flags = VtEntryActive | VtEntryLocal | VtEntryDir;
+ memmove(e.score, b->score, VtScoreSize);
+ e.tag = RootTag;
+ e.snap = b->l.epoch;
+
+ b = blockCopy(b, RootTag, fs->ehi+1, fs->elo);
+ if(b == nil){
+ fprint(2, "%s: bumpEpoch: blockCopy: %R\n", argv0);
+ return 0;
+ }
+
+ if(0) fprint(2, "%s: snapshot root from %d to %d\n", argv0, oldaddr, b->addr);
+ entryPack(&e, b->data, 1);
+ blockDirty(b);
+
+ /*
+ * Update the superblock with the new root and epoch.
+ */
+ if((bs = superGet(fs->cache, &super)) == nil)
+ return 0;
+
+ fs->ehi++;
+ memmove(r->score, b->score, VtScoreSize);
+ r->epoch = fs->ehi;
+
+ super.epochHigh = fs->ehi;
+ oldaddr = super.active;
+ super.active = b->addr;
+ if(doarchive)
+ super.next = oldaddr;
+
+ /*
+ * Record that the new super.active can't get written out until
+ * the new b gets written out. Until then, use the old value.
+ */
+ localToGlobal(oldaddr, oscore);
+ blockDependency(bs, b, 0, oscore, nil);
+ blockPut(b);
+
+ /*
+ * We force the super block to disk so that super.epochHigh gets updated.
+ * Otherwise, if we crash and come back, we might incorrectly treat as active
+ * some of the blocks that making up the snapshot we just created.
+ * Basically every block in the active file system and all the blocks in
+ * the recently-created snapshot depend on the super block now.
+ * Rather than record all those dependencies, we just force the block to disk.
+ *
+ * Note that blockWrite might actually (will probably) send a slightly outdated
+ * super.active to disk. It will be the address of the most recent root that has
+ * gone to disk.
+ */
+ superWrite(bs, &super, 1);
+ blockRemoveLink(bs, globalToLocal(oscore), BtDir, RootTag, 0);
+ blockPut(bs);
+
+ return 1;
+}
+
+int
+saveQid(Fs *fs)
+{
+ Block *b;
+ Super super;
+ u64int qidMax;
+
+ if((b = superGet(fs->cache, &super)) == nil)
+ return 0;
+ qidMax = super.qid;
+ blockPut(b);
+
+ if(!fileSetQidSpace(fs->file, 0, qidMax))
+ return 0;
+
+ return 1;
+}
+
+int
+fsSnapshot(Fs *fs, char *srcpath, char *dstpath, int doarchive)
+{
+ File *src, *dst;
+
+ assert(fs->mode == OReadWrite);
+
+ dst = nil;
+
+ if(fs->halted){
+ vtSetError("file system is halted");
+ return 0;
+ }
+
+ /*
+ * Freeze file system activity.
+ */
+ vtLock(fs->elk);
+
+ /*
+ * Get the root of the directory we're going to save.
+ */
+ if(srcpath == nil)
+ srcpath = "/active";
+ src = fileOpen(fs, srcpath);
+ if(src == nil)
+ goto Err;
+
+ /*
+ * It is important that we maintain the invariant that:
+ * if both b and bb are marked as Active with start epoch e
+ * and b points at bb, then no other pointers to bb exist.
+ *
+ * When bb is unlinked from b, its close epoch is set to b's epoch.
+ * A block with epoch == close epoch is
+ * treated as free by cacheAllocBlock; this aggressively
+ * reclaims blocks after they have been stored to Venti.
+ *
+ * Let's say src->source is block sb, and src->msource is block
+ * mb. Let's also say that block b holds the Entry structures for
+ * both src->source and src->msource (their Entry structures might
+ * be in different blocks, but the argument is the same).
+ * That is, right now we have:
+ *
+ * b Active w/ epoch e, holds ptrs to sb and mb.
+ * sb Active w/ epoch e.
+ * mb Active w/ epoch e.
+ *
+ * With things as they are now, the invariant requires that
+ * b holds the only pointers to sb and mb. We want to record
+ * pointers to sb and mb in new Entries corresponding to dst,
+ * which breaks the invariant. Thus we need to do something
+ * about b. Specifically, we bump the file system's epoch and
+ * then rewalk the path from the root down to and including b.
+ * This will copy-on-write as we walk, so now the state will be:
+ *
+ * b Snap w/ epoch e, holds ptrs to sb and mb.
+ * new-b Active w/ epoch e+1, holds ptrs to sb and mb.
+ * sb Active w/ epoch e.
+ * mb Active w/ epoch e.
+ *
+ * In this state, it's perfectly okay to make more pointers to sb and mb.
+ */
+ if(!bumpEpoch(fs, 0) || !fileWalkSources(src))
+ goto Err;
+
+ /*
+ * Sync to disk. I'm not sure this is necessary, but better safe than sorry.
+ */
+ cacheFlush(fs->cache, 1);
+
+ /*
+ * Create the directory where we will store the copy of src.
+ */
+ dst = fileOpenSnapshot(fs, dstpath, doarchive);
+ if(dst == nil)
+ goto Err;
+
+ /*
+ * Actually make the copy by setting dst's source and msource
+ * to be src's.
+ */
+ if(!fileSnapshot(dst, src, fs->ehi-1, doarchive))
+ goto Err;
+
+ fileDecRef(src);
+ fileDecRef(dst);
+ src = nil;
+ dst = nil;
+
+ /*
+ * Make another copy of the file system. This one is for the
+ * archiver, so that the file system we archive has the recently
+ * added snapshot both in /active and in /archive/yyyy/mmdd[.#].
+ */
+ if(doarchive){
+ if(!saveQid(fs))
+ goto Err;
+ if(!bumpEpoch(fs, 1))
+ goto Err;
+ }
+
+ vtUnlock(fs->elk);
+
+ /* BUG? can fs->arch fall out from under us here? */
+ if(doarchive && fs->arch)
+ archKick(fs->arch);
+
+ return 1;
+
+Err:
+ fprint(2, "%s: fsSnapshot: %R\n", argv0);
+ if(src)
+ fileDecRef(src);
+ if(dst)
+ fileDecRef(dst);
+ vtUnlock(fs->elk);
+ return 0;
+}
+
+int
+fsVac(Fs *fs, char *name, uchar score[VtScoreSize])
+{
+ int r;
+ DirEntry de;
+ Entry e, ee;
+ File *f;
+
+ vtRLock(fs->elk);
+ f = fileOpen(fs, name);
+ if(f == nil){
+ vtRUnlock(fs->elk);
+ return 0;
+ }
+
+ if(!fileGetSources(f, &e, &ee) || !fileGetDir(f, &de)){
+ fileDecRef(f);
+ vtRUnlock(fs->elk);
+ return 0;
+ }
+ fileDecRef(f);
+
+ r = mkVac(fs->z, fs->blockSize, &e, &ee, &de, score);
+ vtRUnlock(fs->elk);
+ return r;
+}
+
+static int
+vtWriteBlock(VtSession *z, uchar *buf, uint n, uint type, uchar score[VtScoreSize])
+{
+ if(!vtWrite(z, score, type, buf, n))
+ return 0;
+ if(!vtSha1Check(score, buf, n))
+ return 0;
+ return 1;
+}
+
+int
+mkVac(VtSession *z, uint blockSize, Entry *pe, Entry *pee, DirEntry *pde, uchar score[VtScoreSize])
+{
+ uchar buf[8192];
+ int i;
+ uchar *p;
+ uint n;
+ DirEntry de;
+ Entry e, ee, eee;
+ MetaBlock mb;
+ MetaEntry me;
+ VtRoot root;
+
+ e = *pe;
+ ee = *pee;
+ de = *pde;
+
+ if(globalToLocal(e.score) != NilBlock
+ || (ee.flags&VtEntryActive && globalToLocal(ee.score) != NilBlock)){
+ vtSetError("can only vac paths already stored on venti");
+ return 0;
+ }
+
+ /*
+ * Build metadata source for root.
+ */
+ n = deSize(&de);
+ if(n+MetaHeaderSize+MetaIndexSize > sizeof buf){
+ vtSetError("DirEntry too big");
+ return 0;
+ }
+ memset(buf, 0, sizeof buf);
+ mbInit(&mb, buf, n+MetaHeaderSize+MetaIndexSize, 1);
+ p = mbAlloc(&mb, n);
+ if(p == nil)
+ abort();
+ mbSearch(&mb, de.elem, &i, &me);
+ assert(me.p == nil);
+ me.p = p;
+ me.size = n;
+ dePack(&de, &me);
+ mbInsert(&mb, i, &me);
+ mbPack(&mb);
+
+ eee.size = n+MetaHeaderSize+MetaIndexSize;
+ if(!vtWriteBlock(z, buf, eee.size, VtDataType, eee.score))
+ return 0;
+ eee.psize = 8192;
+ eee.dsize = 8192;
+ eee.depth = 0;
+ eee.flags = VtEntryActive;
+
+ /*
+ * Build root source with three entries in it.
+ */
+ entryPack(&e, buf, 0);
+ entryPack(&ee, buf, 1);
+ entryPack(&eee, buf, 2);
+
+ n = VtEntrySize*3;
+ memset(&root, 0, sizeof root);
+ if(!vtWriteBlock(z, buf, n, VtDirType, root.score))
+ return 0;
+
+ /*
+ * Save root.
+ */
+ root.version = VtRootVersion;
+ strecpy(root.type, root.type+sizeof root.type, "vac");
+ strecpy(root.name, root.name+sizeof root.name, de.elem);
+ root.blockSize = blockSize;
+ vtRootPack(&root, buf);
+ if(!vtWriteBlock(z, buf, VtRootSize, VtRootType, score))
+ return 0;
+
+ return 1;
+}
+
+int
+fsSync(Fs *fs)
+{
+ vtLock(fs->elk);
+ fileMetaFlush(fs->file, 1);
+ cacheFlush(fs->cache, 1);
+ vtUnlock(fs->elk);
+ return 1;
+}
+
+int
+fsHalt(Fs *fs)
+{
+ vtLock(fs->elk);
+ fs->halted = 1;
+ fileMetaFlush(fs->file, 1);
+ cacheFlush(fs->cache, 1);
+ return 1;
+}
+
+int
+fsUnhalt(Fs *fs)
+{
+ if(!fs->halted)
+ return 0;
+ fs->halted = 0;
+ vtUnlock(fs->elk);
+ return 1;
+}
+
+int
+fsNextQid(Fs *fs, u64int *qid)
+{
+ Block *b;
+ Super super;
+
+ if((b = superGet(fs->cache, &super)) == nil)
+ return 0;
+
+ *qid = super.qid++;
+
+ /*
+ * It's okay if the super block doesn't go to disk immediately,
+ * since fileMetaAlloc will record a dependency between the
+ * block holding this qid and the super block. See file.c:/^fileMetaAlloc.
+ */
+ superWrite(b, &super, 0);
+ blockPut(b);
+ return 1;
+}
+
+static void
+fsMetaFlush(void *a)
+{
+ int rv;
+ Fs *fs = a;
+
+ vtRLock(fs->elk);
+ rv = fileMetaFlush(fs->file, 1);
+ vtRUnlock(fs->elk);
+ if(rv > 0)
+ cacheFlush(fs->cache, 0);
+}
+
+static int
+fsEsearch1(File *f, char *path, u32int savetime, u32int *plo)
+{
+ int n, r;
+ DirEntry de;
+ DirEntryEnum *dee;
+ File *ff;
+ Entry e, ee;
+ char *t;
+
+ dee = deeOpen(f);
+ if(dee == nil)
+ return 0;
+
+ n = 0;
+ for(;;){
+ r = deeRead(dee, &de);
+ if(r <= 0)
+ break;
+ if(de.mode & ModeSnapshot){
+ if((ff = fileWalk(f, de.elem)) != nil){
+ if(fileGetSources(ff, &e, &ee))
+ if(de.mtime >= savetime && e.snap != 0)
+ if(e.snap < *plo)
+ *plo = e.snap;
+ fileDecRef(ff);
+ }
+ }
+ else if(de.mode & ModeDir){
+ if((ff = fileWalk(f, de.elem)) != nil){
+ t = smprint("%s/%s", path, de.elem);
+ n += fsEsearch1(ff, t, savetime, plo);
+ vtMemFree(t);
+ fileDecRef(ff);
+ }
+ }
+ deCleanup(&de);
+ if(r < 0)
+ break;
+ }
+ deeClose(dee);
+
+ return n;
+}
+
+static int
+fsEsearch(Fs *fs, char *path, u32int savetime, u32int *plo)
+{
+ int n;
+ File *f;
+ DirEntry de;
+
+ f = fileOpen(fs, path);
+ if(f == nil)
+ return 0;
+ if(!fileGetDir(f, &de)){
+ fileDecRef(f);
+ return 0;
+ }
+ if((de.mode & ModeDir) == 0){
+ fileDecRef(f);
+ deCleanup(&de);
+ return 0;
+ }
+ deCleanup(&de);
+ n = fsEsearch1(f, path, savetime, plo);
+ fileDecRef(f);
+ return n;
+}
+
+void
+fsSnapshotCleanup(Fs *fs, u32int age)
+{
+ u32int lo;
+
+ /*
+ * Find the best low epoch we can use,
+ * given that we need to save all the unventied archives
+ * and all the snapshots younger than age.
+ */
+ vtRLock(fs->elk);
+ lo = fs->ehi;
+ fsEsearch(fs, "/archive", 0, &lo);
+ fsEsearch(fs, "/snapshot", time(0)-age*60, &lo);
+ vtRUnlock(fs->elk);
+
+ fsEpochLow(fs, lo);
+ fsSnapshotRemove(fs);
+}
+
+/* remove all snapshots that have expired */
+/* return number of directory entries remaining */
+static int
+fsRsearch1(File *f, char *s)
+{
+ int n, r;
+ DirEntry de;
+ DirEntryEnum *dee;
+ File *ff;
+ char *t;
+
+ dee = deeOpen(f);
+ if(dee == nil)
+ return 0;
+
+ n = 0;
+ for(;;){
+ r = deeRead(dee, &de);
+ if(r <= 0)
+ break;
+ n++;
+ if(de.mode & ModeSnapshot){
+ if((ff = fileWalk(f, de.elem)) != nil)
+ fileDecRef(ff);
+ else if(strcmp(vtGetError(), ESnapOld) == 0){
+ if(fileClri(f, de.elem, "adm"))
+ n--;
+ }
+ }
+ else if(de.mode & ModeDir){
+ if((ff = fileWalk(f, de.elem)) != nil){
+ t = smprint("%s/%s", s, de.elem);
+ if(fsRsearch1(ff, t) == 0)
+ if(fileRemove(ff, "adm"))
+ n--;
+ vtMemFree(t);
+ fileDecRef(ff);
+ }
+ }
+ deCleanup(&de);
+ if(r < 0)
+ break;
+ }
+ deeClose(dee);
+
+ return n;
+}
+
+static int
+fsRsearch(Fs *fs, char *path)
+{
+ File *f;
+ DirEntry de;
+
+ f = fileOpen(fs, path);
+ if(f == nil)
+ return 0;
+ if(!fileGetDir(f, &de)){
+ fileDecRef(f);
+ return 0;
+ }
+ if((de.mode & ModeDir) == 0){
+ fileDecRef(f);
+ deCleanup(&de);
+ return 0;
+ }
+ deCleanup(&de);
+ fsRsearch1(f, path);
+ fileDecRef(f);
+ return 1;
+}
+
+void
+fsSnapshotRemove(Fs *fs)
+{
+ vtRLock(fs->elk);
+ fsRsearch(fs, "/snapshot");
+ vtRUnlock(fs->elk);
+}
+
+struct Snap
+{
+ Fs *fs;
+ Periodic*tick;
+ VtLock *lk;
+ uint snapMinutes;
+ uint archMinute;
+ uint snapLife;
+ u32int lastSnap;
+ u32int lastArch;
+ u32int lastCleanup;
+ uint ignore;
+};
+
+static void
+snapEvent(void *v)
+{
+ Snap *s;
+ u32int now, min;
+ Tm tm;
+ int need;
+ u32int snaplife;
+
+ s = v;
+
+ now = time(0)/60;
+ vtLock(s->lk);
+
+ /*
+ * Snapshots happen every snapMinutes minutes.
+ * If we miss a snapshot (for example, because we
+ * were down), we wait for the next one.
+ */
+ if(s->snapMinutes != ~0 && s->snapMinutes != 0
+ && now%s->snapMinutes==0 && now != s->lastSnap){
+ if(!fsSnapshot(s->fs, nil, nil, 0))
+ fprint(2, "%s: fsSnapshot snap: %R\n", argv0);
+ s->lastSnap = now;
+ }
+
+ /*
+ * Archival snapshots happen at archMinute.
+ * If we miss an archive (for example, because we
+ * were down), we do it as soon as possible.
+ */
+ tm = *localtime(now*60);
+ min = tm.hour*60+tm.min;
+ if(s->archMinute != ~0){
+ need = 0;
+ if(min == s->archMinute && now != s->lastArch)
+ need = 1;
+ if(s->lastArch == 0){
+ s->lastArch = 1;
+ if(fsNeedArch(s->fs, s->archMinute))
+ need = 1;
+ }
+ if(need){
+ fsSnapshot(s->fs, nil, nil, 1);
+ s->lastArch = now;
+ }
+ }
+
+ /*
+ * Snapshot cleanup happens every snaplife or every day.
+ */
+ snaplife = s->snapLife;
+ if(snaplife == ~0)
+ snaplife = 24*60;
+ if(s->lastCleanup+snaplife < now){
+ fsSnapshotCleanup(s->fs, s->snapLife);
+ s->lastCleanup = now;
+ }
+ vtUnlock(s->lk);
+}
+
+static Snap*
+snapInit(Fs *fs)
+{
+ Snap *s;
+
+ s = vtMemAllocZ(sizeof(Snap));
+ s->fs = fs;
+ s->tick = periodicAlloc(snapEvent, s, 10*1000);
+ s->lk = vtLockAlloc();
+ s->snapMinutes = -1;
+ s->archMinute = -1;
+ s->snapLife = -1;
+ s->ignore = 5*2; /* wait five minutes for clock to stabilize */
+ return s;
+}
+
+void
+snapGetTimes(Snap *s, u32int *arch, u32int *snap, u32int *snaplen)
+{
+ if(s == nil){
+ *snap = -1;
+ *arch = -1;
+ *snaplen = -1;
+ return;
+ }
+
+ vtLock(s->lk);
+ *snap = s->snapMinutes;
+ *arch = s->archMinute;
+ *snaplen = s->snapLife;
+ vtUnlock(s->lk);
+}
+
+void
+snapSetTimes(Snap *s, u32int arch, u32int snap, u32int snaplen)
+{
+ if(s == nil)
+ return;
+
+ vtLock(s->lk);
+ s->snapMinutes = snap;
+ s->archMinute = arch;
+ s->snapLife = snaplen;
+ vtUnlock(s->lk);
+}
+
+static void
+snapClose(Snap *s)
+{
+ if(s == nil)
+ return;
+
+ periodicKill(s->tick);
+ vtMemFree(s);
+}
+
diff --git a/src/cmd/fossil/fs.h b/src/cmd/fossil/fs.h
new file mode 100644
index 00000000..67314d1f
--- /dev/null
+++ b/src/cmd/fossil/fs.h
@@ -0,0 +1,67 @@
+typedef struct Fs Fs;
+typedef struct File File;
+typedef struct DirEntryEnum DirEntryEnum;
+
+#pragma incomplete Fs
+#pragma incomplete File
+#pragma incomplete DirEntryEnum
+
+/* modes */
+
+enum {
+ OReadOnly,
+ OReadWrite,
+ OOverWrite,
+};
+
+extern char *currfsysname;
+extern char *foptname;
+
+void fsClose(Fs*);
+int fsEpochLow(Fs*, u32int);
+File *fsGetRoot(Fs*);
+int fsHalt(Fs*);
+Fs *fsOpen(char*, VtSession*, long, int);
+int fsRedial(Fs*, char*);
+void fsSnapshotCleanup(Fs*, u32int);
+int fsSnapshot(Fs*, char*, char*, int);
+void fsSnapshotRemove(Fs*);
+int fsSync(Fs*);
+int fsUnhalt(Fs*);
+int fsVac(Fs*, char*, uchar[VtScoreSize]);
+
+void deeClose(DirEntryEnum*);
+DirEntryEnum *deeOpen(File*);
+int deeRead(DirEntryEnum*, DirEntry*);
+int fileClri(File*, char*, char*);
+int fileClriPath(Fs*, char*, char*);
+File *fileCreate(File*, char*, ulong, char*);
+int fileDecRef(File*);
+int fileGetDir(File*, DirEntry*);
+uvlong fileGetId(File*);
+ulong fileGetMcount(File*);
+ulong fileGetMode(File*);
+File *fileGetParent(File*);
+int fileGetSize(File*, uvlong*);
+File *fileIncRef(File*);
+int fileIsDir(File*);
+int fileIsTemporary(File*);
+int fileIsAppend(File*);
+int fileIsExclusive(File*);
+int fileIsRoFs(File*);
+int fileIsRoot(File*);
+int fileMapBlock(File*, ulong, uchar[VtScoreSize], ulong);
+int fileMetaFlush(File*, int);
+char *fileName(File *f);
+File *fileOpen(Fs*, char*);
+int fileRead(File*, void *, int, vlong);
+int fileRemove(File*, char*);
+int fileSetDir(File*, DirEntry*, char*);
+int fileSetQidSpace(File*, u64int, u64int);
+int fileSetSize(File*, uvlong);
+int fileSync(File*);
+int fileTruncate(File*, char*);
+File *fileWalk(File*, char*);
+File *_fileWalk(File*, char*, int);
+int fileWalkSources(File*);
+int fileWrite(File*, void *, int, vlong, char*);
diff --git a/src/cmd/fossil/last.c b/src/cmd/fossil/last.c
new file mode 100644
index 00000000..21d80811
--- /dev/null
+++ b/src/cmd/fossil/last.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: fossil/last disk\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd, bs, addr;
+ char buf[20];
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ if((fd = open(argv[0], OREAD)) < 0)
+ sysfatal("open %s: %r", argv[0]);
+
+ werrstr("end of file");
+ if(seek(fd, 131072, 0) < 0 || readn(fd, buf, 20) != 20)
+ sysfatal("error reading %s: %r", argv[0]);
+ fmtinstall('H', encodefmt);
+ if(memcmp(buf, "\x37\x76\xAE\x89", 4) != 0)
+ sysfatal("bad magic %.4H != 3776AE89", buf);
+ bs = buf[7]|(buf[6]<<8);
+ addr = (buf[8]<<24)|(buf[9]<<16)|(buf[10]<<8)|buf[11];
+ if(seek(fd, (vlong)bs*addr+34, 0) < 0 || readn(fd, buf, 20) != 20)
+ sysfatal("error reading %s: %r", argv[0]);
+ print("vac:%.20lH\n", buf);
+ exits(0);
+}
diff --git a/src/cmd/fossil/mkfile b/src/cmd/fossil/mkfile
new file mode 100644
index 00000000..cf2b0bdc
--- /dev/null
+++ b/src/cmd/fossil/mkfile
@@ -0,0 +1,136 @@
+</$objtype/mkfile
+BIN=/$objtype/bin/fossil
+
+TARG=fossil flchk flfmt conf last
+
+LIBFILES=\
+ 9p\
+ 9auth\
+ 9dir\
+ 9excl\
+ 9fid\
+ 9fsys\
+ 9lstn\
+ 9proc\
+ 9srv\
+ 9user\
+ Ccmd\
+ Ccli\
+ Ccons\
+ Clog\
+ archive\
+ nobwatch\
+ cache\
+ check\
+ disk\
+ error\
+ file\
+ fs\
+ pack\
+ periodic\
+ source\
+ vac\
+ walk\
+
+LIBCFILES=${LIBFILES:%=%.c}
+LIBOFILES=${LIBFILES:%=%.$O}
+LIB=libfs.a$O
+
+HFILES=\
+ /sys/include/oventi.h\
+ stdinc.h\
+ vac.h\
+ dat.h\
+ fns.h\
+ fs.h\
+ error.h\
+ 9.h\
+ flfmt9660.h\
+
+CFILES=${TARG:%=%.c} $LIBCFILES flfmt9660.c
+
+UPDATE=\
+ mkfile\
+ $CFILES\
+ $HFILES\
+
+default:V: all
+
+test:V: all
+ rm -f /srv/test.fossil /srv/test.fscons
+ slay 8.flfmt | rc
+ slay 8.fossil | rc
+ unmount /n/fossil || status=''
+ {syscall seek 1 6400000000 0; echo} >>/tmp/fossil
+ 8.flfmt -y /tmp/fossil
+ 8.conf -w /tmp/fossil flproto
+ 8.fossil -f /tmp/fossil
+ cat /srv/test.fscons &
+ echo fsys main >>/srv/test.fscons
+ mount /srv/test.fossil /n/fossil
+ cd /n/fossil/tmp
+ dd -bs 1048576 -count 256 -if /dev/zero -of a
+ rm a
+ echo sync >>/srv/test.fscons
+ echo sync >>/srv/test.fscons
+ echo sync >>/srv/test.fscons
+ sleep 1
+ echo sync >>/srv/test.fscons
+ sleep 1
+ echo sync >>/srv/test.fscons
+ sleep 1
+ echo sync >>/srv/test.fscons
+ echo check >>/srv/test.fscons
+ echo check >>/srv/test.fscons
+ echo check >>/srv/test.fscons
+
+# cp /env/timezone /n/fossil/tmp
+# cp /lib/words /n/fossil/tmp
+# dircp /n/sources/plan9/sys/src/cmd/aux /n/fossil/tmp
+# >/n/fossil/tmp/lis
+# chmod +t /n/fossil/tmp/lis
+# echo SHOULD NOT SEE THIS >>/n/fossil/tmp/lis
+# echo snap >>/srv/test.fscons
+# sleep 2
+# mount /srv/test.fossil /n/dump main/archive
+# cat /n/dump/*/*/tmp/lis
+# @{cd /n/fossil/tmp && time tar xTf /sys/src/cmd/fossil/test.tar}
+# unmount /n/fossil
+# rm /srv/fossil
+
+</sys/src/cmd/mkmany
+
+$LIB(%.$O):N: %.$O
+$LIB: ${LIBOFILES:%=$LIB(%)}
+ names = `{echo $newprereq |sed 's/ /\n/g' |sed -n 's/'$LIB'\(([^)]+)\)/\1/gp'}
+ ar vu $LIB $names
+# rm $names
+
+$O.flfmt: flfmt9660.$O
+
+flfmt%.$O: flfmt9660.h
+
+%.page:V: %.ps
+ page -w $stem.ps
+
+%.ps:D: %.ms
+ tbl $stem.ms | pic | eqn | troff -ms | lp -dstdout >$target
+
+bundle:V:
+ rfork n
+ ramfs -m /n/kremvax >[2]/dev/null
+ bind -a /n/kremvax .
+ cp /sys/doc/fossil.ms /sys/doc/fossil.ps /n/kremvax
+ cp /sys/man/4/fossil /n/kremvax/fossil.4.man
+ cp /sys/man/8/fossilcons /n/kremvax/fossilcons.8.man
+ x=`{ls |grep -v 'TODO|test.tar|fossil.tar.gz'}
+ tar c $x | gzip > fossil.tar.gz
+
+$O.conf:D: conf.rc
+ {
+ echo '#!/bin/rc'
+ echo '# THIS FILE IS AUTOMATICALLY GENERATED'
+ echo '# FROM /sys/src/cmd/fossil/conf.rc. DO NOT EDIT.'
+ echo
+ sed 1d conf.rc
+ } >$target && chmod +x $target
diff --git a/src/cmd/fossil/nobwatch.c b/src/cmd/fossil/nobwatch.c
new file mode 100644
index 00000000..698e86f4
--- /dev/null
+++ b/src/cmd/fossil/nobwatch.c
@@ -0,0 +1,39 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+bwatchReset(uchar score[VtScoreSize])
+{
+ USED(score);
+}
+
+void
+bwatchInit(void)
+{
+}
+
+void
+bwatchSetBlockSize(uint)
+{
+}
+
+void
+bwatchDependency(Block *b)
+{
+ USED(b);
+}
+
+void
+bwatchLock(Block *b)
+{
+ USED(b);
+}
+
+void
+bwatchUnlock(Block *b)
+{
+ USED(b);
+}
+
diff --git a/src/cmd/fossil/pack.c b/src/cmd/fossil/pack.c
new file mode 100644
index 00000000..9fd2070c
--- /dev/null
+++ b/src/cmd/fossil/pack.c
@@ -0,0 +1,225 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+/*
+ * integer conversion routines
+ */
+#define U8GET(p) ((p)[0])
+#define U16GET(p) (((p)[0]<<8)|(p)[1])
+#define U32GET(p) (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
+#define U48GET(p) (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2))
+#define U64GET(p) (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4))
+
+#define U8PUT(p,v) (p)[0]=(v)
+#define U16PUT(p,v) (p)[0]=(v)>>8;(p)[1]=(v)
+#define U32PUT(p,v) (p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
+#define U48PUT(p,v,t32) t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32)
+#define U64PUT(p,v,t32) t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32)
+
+void
+headerPack(Header *h, uchar *p)
+{
+ memset(p, 0, HeaderSize);
+ U32PUT(p, HeaderMagic);
+ U16PUT(p+4, HeaderVersion);
+ U16PUT(p+6, h->blockSize);
+ U32PUT(p+8, h->super);
+ U32PUT(p+12, h->label);
+ U32PUT(p+16, h->data);
+ U32PUT(p+20, h->end);
+}
+
+int
+headerUnpack(Header *h, uchar *p)
+{
+ if(U32GET(p) != HeaderMagic){
+ vtSetError("vac header bad magic");
+ return 0;
+ }
+ h->version = U16GET(p+4);
+ if(h->version != HeaderVersion){
+ vtSetError("vac header bad version");
+ return 0;
+ }
+ h->blockSize = U16GET(p+6);
+ h->super = U32GET(p+8);
+ h->label = U32GET(p+12);
+ h->data = U32GET(p+16);
+ h->end = U32GET(p+20);
+ return 1;
+}
+
+void
+labelPack(Label *l, uchar *p, int i)
+{
+ p += i*LabelSize;
+ U8PUT(p, l->state);
+ U8PUT(p+1, l->type);
+ U32PUT(p+2, l->epoch);
+ U32PUT(p+6, l->epochClose);
+ U32PUT(p+10, l->tag);
+}
+
+int
+labelUnpack(Label *l, uchar *p, int i)
+{
+ p += i*LabelSize;
+ l->state = p[0];
+ l->type = p[1];
+ l->epoch = U32GET(p+2);
+ l->epochClose = U32GET(p+6);
+ l->tag = U32GET(p+10);
+
+ if(l->type > BtMax){
+Bad:
+ vtSetError(EBadLabel);
+ fprint(2, "%s: labelUnpack: bad label: 0x%.2ux 0x%.2ux 0x%.8ux "
+ "0x%.8ux 0x%.8ux\n", argv0, l->state, l->type, l->epoch,
+ l->epochClose, l->tag);
+ return 0;
+ }
+ if(l->state != BsBad && l->state != BsFree){
+ if(!(l->state&BsAlloc) || l->state & ~BsMask)
+ goto Bad;
+ if(l->state&BsClosed){
+ if(l->epochClose == ~(u32int)0)
+ goto Bad;
+ }else{
+ if(l->epochClose != ~(u32int)0)
+ goto Bad;
+ }
+ }
+ return 1;
+}
+
+u32int
+globalToLocal(uchar score[VtScoreSize])
+{
+ int i;
+
+ for(i=0; i<VtScoreSize-4; i++)
+ if(score[i] != 0)
+ return NilBlock;
+
+ return U32GET(score+VtScoreSize-4);
+}
+
+void
+localToGlobal(u32int addr, uchar score[VtScoreSize])
+{
+ memset(score, 0, VtScoreSize-4);
+ U32PUT(score+VtScoreSize-4, addr);
+}
+
+void
+entryPack(Entry *e, uchar *p, int index)
+{
+ ulong t32;
+ int flags;
+
+ p += index * VtEntrySize;
+
+ U32PUT(p, e->gen);
+ U16PUT(p+4, e->psize);
+ U16PUT(p+6, e->dsize);
+ flags = e->flags | ((e->depth << VtEntryDepthShift) & VtEntryDepthMask);
+ U8PUT(p+8, flags);
+ memset(p+9, 0, 5);
+ U48PUT(p+14, e->size, t32);
+
+ if(flags & VtEntryLocal){
+ if(globalToLocal(e->score) == NilBlock)
+ abort();
+ memset(p+20, 0, 7);
+ U8PUT(p+27, e->archive);
+ U32PUT(p+28, e->snap);
+ U32PUT(p+32, e->tag);
+ memmove(p+36, e->score+16, 4);
+ }else
+ memmove(p+20, e->score, VtScoreSize);
+}
+
+int
+entryUnpack(Entry *e, uchar *p, int index)
+{
+ p += index * VtEntrySize;
+
+ e->gen = U32GET(p);
+ e->psize = U16GET(p+4);
+ e->dsize = U16GET(p+6);
+ e->flags = U8GET(p+8);
+ e->depth = (e->flags & VtEntryDepthMask) >> VtEntryDepthShift;
+ e->flags &= ~VtEntryDepthMask;
+ e->size = U48GET(p+14);
+
+ if(e->flags & VtEntryLocal){
+ e->archive = p[27];
+ e->snap = U32GET(p+28);
+ e->tag = U32GET(p+32);
+ memset(e->score, 0, 16);
+ memmove(e->score+16, p+36, 4);
+ }else{
+ e->archive = 0;
+ e->snap = 0;
+ e->tag = 0;
+ memmove(e->score, p+20, VtScoreSize);
+ }
+
+ return 1;
+}
+
+int
+entryType(Entry *e)
+{
+ return (((e->flags & VtEntryDir) != 0) << 3) | e->depth;
+}
+
+
+void
+superPack(Super *s, uchar *p)
+{
+ u32int t32;
+
+ memset(p, 0, SuperSize);
+ U32PUT(p, SuperMagic);
+ assert(s->version == SuperVersion);
+ U16PUT(p+4, s->version);
+ U32PUT(p+6, s->epochLow);
+ U32PUT(p+10, s->epochHigh);
+ U64PUT(p+14, s->qid, t32);
+ U32PUT(p+22, s->active);
+ U32PUT(p+26, s->next);
+ U32PUT(p+30, s->current);
+ memmove(p+34, s->last, VtScoreSize);
+ memmove(p+54, s->name, sizeof(s->name));
+}
+
+int
+superUnpack(Super *s, uchar *p)
+{
+ memset(s, 0, sizeof(*s));
+ if(U32GET(p) != SuperMagic)
+ goto Err;
+ s->version = U16GET(p+4);
+ if(s->version != SuperVersion)
+ goto Err;
+ s->epochLow = U32GET(p+6);
+ s->epochHigh = U32GET(p+10);
+ s->qid = U64GET(p+14);
+ if(s->epochLow == 0 || s->epochLow > s->epochHigh || s->qid == 0)
+ goto Err;
+ s->active = U32GET(p+22);
+ s->next = U32GET(p+26);
+ s->current = U32GET(p+30);
+ memmove(s->last, p+34, VtScoreSize);
+ memmove(s->name, p+54, sizeof(s->name));
+ s->name[sizeof(s->name)-1] = 0;
+ return 1;
+Err:
+ memset(s, 0, sizeof(*s));
+ vtSetError(EBadSuper);
+ return 0;
+}
+
diff --git a/src/cmd/fossil/periodic.c b/src/cmd/fossil/periodic.c
new file mode 100644
index 00000000..8a0c2084
--- /dev/null
+++ b/src/cmd/fossil/periodic.c
@@ -0,0 +1,84 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+struct Periodic {
+ VtLock *lk;
+ int die; /* flag: quit if set */
+ void (*f)(void*); /* call this each period */
+ void *a; /* argument to f */
+ int msec; /* period */
+};
+
+static void periodicThread(void *a);
+
+Periodic *
+periodicAlloc(void (*f)(void*), void *a, int msec)
+{
+ Periodic *p;
+
+ p = vtMemAllocZ(sizeof(Periodic));
+ p->lk = vtLockAlloc();
+ p->f = f;
+ p->a = a;
+ p->msec = msec;
+ if(p->msec < 10)
+ p->msec = 10;
+
+ vtThread(periodicThread, p);
+ return p;
+}
+
+void
+periodicKill(Periodic *p)
+{
+ if(p == nil)
+ return;
+ vtLock(p->lk);
+ p->die = 1;
+ vtUnlock(p->lk);
+}
+
+static void
+periodicFree(Periodic *p)
+{
+ vtLockFree(p->lk);
+ vtMemFree(p);
+}
+
+static void
+periodicThread(void *a)
+{
+ Periodic *p = a;
+ vlong t, ct, ts; /* times in ms. */
+
+ vtThreadSetName("periodic");
+
+ ct = nsec() / 1000000;
+ t = ct + p->msec; /* call p->f at or after this time */
+
+ for(;;){
+ ts = t - ct; /* ms. to next cycle's start */
+ if(ts > 1000)
+ ts = 1000; /* bound sleep duration */
+ if(ts > 0)
+ sleep(ts); /* wait for cycle's start */
+
+ vtLock(p->lk);
+ if(p->die){
+ vtUnlock(p->lk);
+ break;
+ }
+ ct = nsec() / 1000000;
+ if(t <= ct){ /* due to call p->f? */
+ p->f(p->a);
+ ct = nsec() / 1000000;
+ while(t <= ct) /* advance t to future cycle start */
+ t += p->msec;
+ }
+ vtUnlock(p->lk);
+ }
+ periodicFree(p);
+}
+
diff --git a/src/cmd/fossil/source.c b/src/cmd/fossil/source.c
new file mode 100644
index 00000000..aa614192
--- /dev/null
+++ b/src/cmd/fossil/source.c
@@ -0,0 +1,1068 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+#include "9.h"
+
+static int sizeToDepth(uvlong s, int psize, int dsize);
+static u32int tagGen(void);
+static Block *sourceLoad(Source *r, Entry *e);
+static int sourceShrinkDepth(Source*, Block*, Entry*, int);
+static int sourceShrinkSize(Source*, Entry*, uvlong);
+static int sourceGrowDepth(Source*, Block*, Entry*, int);
+
+#define sourceIsLocked(r) ((r)->b != nil)
+
+static Source *
+sourceAlloc(Fs *fs, Block *b, Source *p, u32int offset, int mode, int issnapshot)
+{
+ int epb;
+ u32int epoch;
+ char *pname = nil;
+ Source *r;
+ Entry e;
+
+ assert(p==nil || sourceIsLocked(p));
+
+ if(p == nil){
+ assert(offset == 0);
+ epb = 1;
+ }else
+ epb = p->dsize / VtEntrySize;
+
+ if(b->l.type != BtDir)
+ goto Bad;
+
+ /*
+ * a non-active entry is the only thing that
+ * can legitimately happen here. all the others
+ * get prints.
+ */
+ if(!entryUnpack(&e, b->data, offset % epb)){
+ pname = sourceName(p);
+ consPrint("%s: %s %V: sourceAlloc: entryUnpack failed\n",
+ fs->name, pname, b->score);
+ goto Bad;
+ }
+ if(!(e.flags & VtEntryActive)){
+ pname = sourceName(p);
+ if(0) consPrint("%s: %s %V: sourceAlloc: not active\n",
+ fs->name, pname, e.score);
+ goto Bad;
+ }
+ if(e.psize < 256 || e.dsize < 256){
+ pname = sourceName(p);
+ consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud < 256\n",
+ fs->name, pname, e.score, e.psize, e.dsize);
+ goto Bad;
+ }
+
+ if(e.depth < sizeToDepth(e.size, e.psize, e.dsize)){
+ pname = sourceName(p);
+ consPrint("%s: %s %V: sourceAlloc: depth %ud size %llud "
+ "psize %ud dsize %ud\n", fs->name, pname,
+ e.score, e.depth, e.size, e.psize, e.dsize);
+ goto Bad;
+ }
+
+ if((e.flags & VtEntryLocal) && e.tag == 0){
+ pname = sourceName(p);
+ consPrint("%s: %s %V: sourceAlloc: flags %#ux tag %#ux\n",
+ fs->name, pname, e.score, e.flags, e.tag);
+ goto Bad;
+ }
+
+ if(e.dsize > fs->blockSize || e.psize > fs->blockSize){
+ pname = sourceName(p);
+ consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud "
+ "> blocksize %ud\n", fs->name, pname, e.score,
+ e.psize, e.dsize, fs->blockSize);
+ goto Bad;
+ }
+
+ epoch = b->l.epoch;
+ if(mode == OReadWrite){
+ if(e.snap != 0){
+ vtSetError(ESnapRO);
+ return nil;
+ }
+ }else if(e.snap != 0){
+ if(e.snap < fs->elo){
+ vtSetError(ESnapOld);
+ return nil;
+ }
+ if(e.snap >= fs->ehi)
+ goto Bad;
+ epoch = e.snap;
+ }
+
+ r = vtMemAllocZ(sizeof(Source));
+ r->fs = fs;
+ r->mode = mode;
+ r->issnapshot = issnapshot;
+ r->dsize = e.dsize;
+ r->gen = e.gen;
+ r->dir = (e.flags & VtEntryDir) != 0;
+ r->lk = vtLockAlloc();
+ r->ref = 1;
+ r->parent = p;
+ if(p){
+ vtLock(p->lk);
+ assert(mode == OReadOnly || p->mode == OReadWrite);
+ p->ref++;
+ vtUnlock(p->lk);
+ }
+ r->epoch = epoch;
+// consPrint("sourceAlloc: have %V be.%d fse.%d %s\n", b->score,
+// b->l.epoch, r->fs->ehi, mode == OReadWrite? "rw": "ro");
+ memmove(r->score, b->score, VtScoreSize);
+ r->scoreEpoch = b->l.epoch;
+ r->offset = offset;
+ r->epb = epb;
+ r->tag = b->l.tag;
+
+// consPrint("%s: sourceAlloc: %p -> %V %d\n", r, r->score, r->offset);
+
+ return r;
+Bad:
+ free(pname);
+ vtSetError(EBadEntry);
+ return nil;
+}
+
+Source *
+sourceRoot(Fs *fs, u32int addr, int mode)
+{
+ Source *r;
+ Block *b;
+
+ b = cacheLocalData(fs->cache, addr, BtDir, RootTag, mode, 0);
+ if(b == nil)
+ return nil;
+
+ if(mode == OReadWrite && b->l.epoch != fs->ehi){
+ consPrint("sourceRoot: fs->ehi = %ud, b->l = %L\n",
+ fs->ehi, &b->l);
+ blockPut(b);
+ vtSetError(EBadRoot);
+ return nil;
+ }
+
+ r = sourceAlloc(fs, b, nil, 0, mode, 0);
+ blockPut(b);
+ return r;
+}
+
+Source *
+sourceOpen(Source *r, ulong offset, int mode, int issnapshot)
+{
+ ulong bn;
+ Block *b;
+
+ assert(sourceIsLocked(r));
+ if(r->mode == OReadWrite)
+ assert(r->epoch == r->b->l.epoch);
+ if(!r->dir){
+ vtSetError(ENotDir);
+ return nil;
+ }
+
+ bn = offset/(r->dsize/VtEntrySize);
+
+ b = sourceBlock(r, bn, mode);
+ if(b == nil)
+ return nil;
+ r = sourceAlloc(r->fs, b, r, offset, mode, issnapshot);
+ blockPut(b);
+ return r;
+}
+
+Source *
+sourceCreate(Source *r, int dsize, int dir, u32int offset)
+{
+ int i, epb, psize;
+ u32int bn, size;
+ Block *b;
+ Entry e;
+ Source *rr;
+
+ assert(sourceIsLocked(r));
+
+ if(!r->dir){
+ vtSetError(ENotDir);
+ return nil;
+ }
+
+ epb = r->dsize/VtEntrySize;
+ psize = (dsize/VtScoreSize)*VtScoreSize;
+
+ size = sourceGetDirSize(r);
+ if(offset == 0){
+ /*
+ * look at a random block to see if we can find an empty entry
+ */
+ offset = lnrand(size+1);
+ offset -= offset % epb;
+ }
+
+ /* try the given block and then try the last block */
+ for(;;){
+ bn = offset/epb;
+ b = sourceBlock(r, bn, OReadWrite);
+ if(b == nil)
+ return nil;
+ for(i=offset%r->epb; i<epb; i++){
+ entryUnpack(&e, b->data, i);
+ if((e.flags&VtEntryActive) == 0 && e.gen != ~0)
+ goto Found;
+ }
+ blockPut(b);
+ if(offset == size){
+ fprint(2, "sourceCreate: cannot happen\n");
+ vtSetError("sourceCreate: cannot happen");
+ return nil;
+ }
+ offset = size;
+ }
+
+Found:
+ /* found an entry - gen already set */
+ e.psize = psize;
+ e.dsize = dsize;
+ assert(psize && dsize);
+ e.flags = VtEntryActive;
+ if(dir)
+ e.flags |= VtEntryDir;
+ e.depth = 0;
+ e.size = 0;
+ memmove(e.score, vtZeroScore, VtScoreSize);
+ e.tag = 0;
+ e.snap = 0;
+ e.archive = 0;
+ entryPack(&e, b->data, i);
+ blockDirty(b);
+
+ offset = bn*epb + i;
+ if(offset+1 > size){
+ if(!sourceSetDirSize(r, offset+1)){
+ blockPut(b);
+ return nil;
+ }
+ }
+
+ rr = sourceAlloc(r->fs, b, r, offset, OReadWrite, 0);
+ blockPut(b);
+ return rr;
+}
+
+static int
+sourceKill(Source *r, int doremove)
+{
+ Entry e;
+ Block *b;
+ u32int addr;
+ u32int tag;
+ int type;
+
+ assert(sourceIsLocked(r));
+ b = sourceLoad(r, &e);
+ if(b == nil)
+ return 0;
+
+ assert(b->l.epoch == r->fs->ehi);
+
+ if(doremove==0 && e.size == 0){
+ /* already truncated */
+ blockPut(b);
+ return 1;
+ }
+
+ /* remember info on link we are removing */
+ addr = globalToLocal(e.score);
+ type = entryType(&e);
+ tag = e.tag;
+
+ if(doremove){
+ if(e.gen != ~0)
+ e.gen++;
+ e.dsize = 0;
+ e.psize = 0;
+ e.flags = 0;
+ }else{
+ e.flags &= ~VtEntryLocal;
+ }
+ e.depth = 0;
+ e.size = 0;
+ e.tag = 0;
+ memmove(e.score, vtZeroScore, VtScoreSize);
+ entryPack(&e, b->data, r->offset % r->epb);
+ blockDirty(b);
+ if(addr != NilBlock)
+ blockRemoveLink(b, addr, type, tag, 1);
+ blockPut(b);
+
+ if(doremove){
+ sourceUnlock(r);
+ sourceClose(r);
+ }
+
+ return 1;
+}
+
+int
+sourceRemove(Source *r)
+{
+ return sourceKill(r, 1);
+}
+
+int
+sourceTruncate(Source *r)
+{
+ return sourceKill(r, 0);
+}
+
+uvlong
+sourceGetSize(Source *r)
+{
+ Entry e;
+ Block *b;
+
+ assert(sourceIsLocked(r));
+ b = sourceLoad(r, &e);
+ if(b == nil)
+ return 0;
+ blockPut(b);
+
+ return e.size;
+}
+
+static int
+sourceShrinkSize(Source *r, Entry *e, uvlong size)
+{
+ int i, type, ppb;
+ uvlong ptrsz;
+ u32int addr;
+ uchar score[VtScoreSize];
+ Block *b;
+
+ type = entryType(e);
+ b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
+ if(b == nil)
+ return 0;
+
+ ptrsz = e->dsize;
+ ppb = e->psize/VtScoreSize;
+ for(i=0; i+1<e->depth; i++)
+ ptrsz *= ppb;
+
+ while(type&BtLevelMask){
+ if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
+ /* not worth copying the block just so we can zero some of it */
+ blockPut(b);
+ return 0;
+ }
+
+ /*
+ * invariant: each pointer in the tree rooted at b accounts for ptrsz bytes
+ */
+
+ /* zero the pointers to unnecessary blocks */
+ i = (size+ptrsz-1)/ptrsz;
+ for(; i<ppb; i++){
+ addr = globalToLocal(b->data+i*VtScoreSize);
+ memmove(b->data+i*VtScoreSize, vtZeroScore, VtScoreSize);
+ blockDirty(b);
+ if(addr != NilBlock)
+ blockRemoveLink(b, addr, type-1, e->tag, 1);
+ }
+
+ /* recurse (go around again) on the partially necessary block */
+ i = size/ptrsz;
+ size = size%ptrsz;
+ if(size == 0){
+ blockPut(b);
+ return 1;
+ }
+ ptrsz /= ppb;
+ type--;
+ memmove(score, b->data+i*VtScoreSize, VtScoreSize);
+ blockPut(b);
+ b = cacheGlobal(r->fs->cache, score, type, e->tag, OReadWrite);
+ if(b == nil)
+ return 0;
+ }
+
+ if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
+ blockPut(b);
+ return 0;
+ }
+
+ /*
+ * No one ever truncates BtDir blocks.
+ */
+ if(type == BtData && e->dsize > size){
+ memset(b->data+size, 0, e->dsize-size);
+ blockDirty(b);
+ }
+ blockPut(b);
+ return 1;
+}
+
+int
+sourceSetSize(Source *r, uvlong size)
+{
+ int depth;
+ Entry e;
+ Block *b;
+
+ assert(sourceIsLocked(r));
+ if(size == 0)
+ return sourceTruncate(r);
+
+ if(size > VtMaxFileSize || size > ((uvlong)MaxBlock)*r->dsize){
+ vtSetError(ETooBig);
+ return 0;
+ }
+
+ b = sourceLoad(r, &e);
+ if(b == nil)
+ return 0;
+
+ /* quick out */
+ if(e.size == size){
+ blockPut(b);
+ return 1;
+ }
+
+ depth = sizeToDepth(size, e.psize, e.dsize);
+
+ if(depth < e.depth){
+ if(!sourceShrinkDepth(r, b, &e, depth)){
+ blockPut(b);
+ return 0;
+ }
+ }else if(depth > e.depth){
+ if(!sourceGrowDepth(r, b, &e, depth)){
+ blockPut(b);
+ return 0;
+ }
+ }
+
+ if(size < e.size)
+ sourceShrinkSize(r, &e, size);
+
+ e.size = size;
+ entryPack(&e, b->data, r->offset % r->epb);
+ blockDirty(b);
+ blockPut(b);
+
+ return 1;
+}
+
+int
+sourceSetDirSize(Source *r, ulong ds)
+{
+ uvlong size;
+ int epb;
+
+ assert(sourceIsLocked(r));
+ epb = r->dsize/VtEntrySize;
+
+ size = (uvlong)r->dsize*(ds/epb);
+ size += VtEntrySize*(ds%epb);
+ return sourceSetSize(r, size);
+}
+
+ulong
+sourceGetDirSize(Source *r)
+{
+ ulong ds;
+ uvlong size;
+ int epb;
+
+ assert(sourceIsLocked(r));
+ epb = r->dsize/VtEntrySize;
+
+ size = sourceGetSize(r);
+ ds = epb*(size/r->dsize);
+ ds += (size%r->dsize)/VtEntrySize;
+ return ds;
+}
+
+int
+sourceGetEntry(Source *r, Entry *e)
+{
+ Block *b;
+
+ assert(sourceIsLocked(r));
+ b = sourceLoad(r, e);
+ if(b == nil)
+ return 0;
+ blockPut(b);
+
+ return 1;
+}
+
+/*
+ * Must be careful with this. Doesn't record
+ * dependencies, so don't introduce any!
+ */
+int
+sourceSetEntry(Source *r, Entry *e)
+{
+ Block *b;
+ Entry oe;
+
+ assert(sourceIsLocked(r));
+ b = sourceLoad(r, &oe);
+ if(b == nil)
+ return 0;
+ entryPack(e, b->data, r->offset%r->epb);
+ blockDirty(b);
+ blockPut(b);
+
+ return 1;
+}
+
+static Block *
+blockWalk(Block *p, int index, int mode, Fs *fs, Entry *e)
+{
+ Block *b;
+ Cache *c;
+ u32int addr;
+ int type;
+ uchar oscore[VtScoreSize], score[VtScoreSize];
+ Entry oe;
+
+ c = fs->cache;
+
+ if((p->l.type & BtLevelMask) == 0){
+ assert(p->l.type == BtDir);
+ type = entryType(e);
+ b = cacheGlobal(c, e->score, type, e->tag, mode);
+ }else{
+ type = p->l.type - 1;
+ b = cacheGlobal(c, p->data + index*VtScoreSize, type, e->tag, mode);
+ }
+
+ if(b)
+ b->pc = getcallerpc(&p);
+
+ if(b == nil || mode == OReadOnly)
+ return b;
+
+ if(p->l.epoch != fs->ehi){
+ fprint(2, "blockWalk: parent not writable\n");
+ abort();
+ }
+ if(b->l.epoch == fs->ehi)
+ return b;
+
+ oe = *e;
+
+ /*
+ * Copy on write.
+ */
+ if(e->tag == 0){
+ assert(p->l.type == BtDir);
+ e->tag = tagGen();
+ e->flags |= VtEntryLocal;
+ }
+
+ addr = b->addr;
+ b = blockCopy(b, e->tag, fs->ehi, fs->elo);
+ if(b == nil)
+ return nil;
+
+ b->pc = getcallerpc(&p);
+ assert(b->l.epoch == fs->ehi);
+
+ blockDirty(b);
+ memmove(score, b->score, VtScoreSize);
+ if(p->l.type == BtDir){
+ memmove(e->score, b->score, VtScoreSize);
+ entryPack(e, p->data, index);
+ blockDependency(p, b, index, nil, &oe);
+ }else{
+ memmove(oscore, p->data+index*VtScoreSize, VtScoreSize);
+ memmove(p->data+index*VtScoreSize, b->score, VtScoreSize);
+ blockDependency(p, b, index, oscore, nil);
+ }
+ blockDirty(p);
+
+ if(addr != NilBlock)
+ blockRemoveLink(p, addr, type, e->tag, 0);
+
+ return b;
+}
+
+/*
+ * Change the depth of the source r.
+ * The entry e for r is contained in block p.
+ */
+static int
+sourceGrowDepth(Source *r, Block *p, Entry *e, int depth)
+{
+ Block *b, *bb;
+ u32int tag;
+ int type;
+ Entry oe;
+
+ assert(sourceIsLocked(r));
+ assert(depth <= VtPointerDepth);
+
+ type = entryType(e);
+ b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
+ if(b == nil)
+ return 0;
+
+ tag = e->tag;
+ if(tag == 0)
+ tag = tagGen();
+
+ oe = *e;
+
+ /*
+ * Keep adding layers until we get to the right depth
+ * or an error occurs.
+ */
+ while(e->depth < depth){
+ bb = cacheAllocBlock(r->fs->cache, type+1, tag, r->fs->ehi, r->fs->elo);
+ if(bb == nil)
+ break;
+//fprint(2, "alloc %lux grow %V\n", bb->addr, b->score);
+ memmove(bb->data, b->score, VtScoreSize);
+ memmove(e->score, bb->score, VtScoreSize);
+ e->depth++;
+ type++;
+ e->tag = tag;
+ e->flags |= VtEntryLocal;
+ blockDependency(bb, b, 0, vtZeroScore, nil);
+ blockPut(b);
+ b = bb;
+ blockDirty(b);
+ }
+
+ entryPack(e, p->data, r->offset % r->epb);
+ blockDependency(p, b, r->offset % r->epb, nil, &oe);
+ blockPut(b);
+ blockDirty(p);
+
+ return e->depth == depth;
+}
+
+static int
+sourceShrinkDepth(Source *r, Block *p, Entry *e, int depth)
+{
+ Block *b, *nb, *ob, *rb;
+ u32int tag;
+ int type, d;
+ Entry oe;
+
+ assert(sourceIsLocked(r));
+ assert(depth <= VtPointerDepth);
+
+ type = entryType(e);
+ rb = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
+ if(rb == nil)
+ return 0;
+
+ tag = e->tag;
+ if(tag == 0)
+ tag = tagGen();
+
+ /*
+ * Walk down to the new root block.
+ * We may stop early, but something is better than nothing.
+ */
+ oe = *e;
+
+ ob = nil;
+ b = rb;
+/* BUG: explain type++. i think it is a real bug */
+ for(d=e->depth; d > depth; d--, type++){
+ nb = cacheGlobal(r->fs->cache, b->data, type-1, tag, OReadWrite);
+ if(nb == nil)
+ break;
+ if(ob!=nil && ob!=rb)
+ blockPut(ob);
+ ob = b;
+ b = nb;
+ }
+
+ if(b == rb){
+ blockPut(rb);
+ return 0;
+ }
+
+ /*
+ * Right now, e points at the root block rb, b is the new root block,
+ * and ob points at b. To update:
+ *
+ * (i) change e to point at b
+ * (ii) zero the pointer ob -> b
+ * (iii) free the root block
+ *
+ * p (the block containing e) must be written before
+ * anything else.
+ */
+
+ /* (i) */
+ e->depth = d;
+ /* might have been local and now global; reverse cannot happen */
+ if(globalToLocal(b->score) == NilBlock)
+ e->flags &= ~VtEntryLocal;
+ memmove(e->score, b->score, VtScoreSize);
+ entryPack(e, p->data, r->offset % r->epb);
+ blockDependency(p, b, r->offset % r->epb, nil, &oe);
+ blockDirty(p);
+
+ /* (ii) */
+ memmove(ob->data, vtZeroScore, VtScoreSize);
+ blockDependency(ob, p, 0, b->score, nil);
+ blockDirty(ob);
+
+ /* (iii) */
+ if(rb->addr != NilBlock)
+ blockRemoveLink(p, rb->addr, rb->l.type, rb->l.tag, 1);
+
+ blockPut(rb);
+ if(ob!=nil && ob!=rb)
+ blockPut(ob);
+ blockPut(b);
+
+ return d == depth;
+}
+
+/*
+ * Normally we return the block at the given number.
+ * If early is set, we stop earlier in the tree. Setting early
+ * to 1 gives us the block that contains the pointer to bn.
+ */
+Block *
+_sourceBlock(Source *r, ulong bn, int mode, int early, ulong tag)
+{
+ Block *b, *bb;
+ int index[VtPointerDepth+1];
+ Entry e;
+ int i, np;
+ int m;
+
+ assert(sourceIsLocked(r));
+ assert(bn != NilBlock);
+
+ /* mode for intermediate block */
+ m = mode;
+ if(m == OOverWrite)
+ m = OReadWrite;
+
+ b = sourceLoad(r, &e);
+ if(b == nil)
+ return nil;
+ if(r->issnapshot && (e.flags & VtEntryNoArchive)){
+ blockPut(b);
+ vtSetError(ENotArchived);
+ return nil;
+ }
+
+ if(tag){
+ if(e.tag == 0)
+ e.tag = tag;
+ else if(e.tag != tag){
+ fprint(2, "tag mismatch\n");
+ vtSetError("tag mismatch");
+ goto Err;
+ }
+ }
+
+ np = e.psize/VtScoreSize;
+ memset(index, 0, sizeof(index));
+ for(i=0; bn > 0; i++){
+ if(i >= VtPointerDepth){
+ vtSetError(EBadAddr);
+ goto Err;
+ }
+ index[i] = bn % np;
+ bn /= np;
+ }
+
+ if(i > e.depth){
+ if(mode == OReadOnly){
+ vtSetError(EBadAddr);
+ goto Err;
+ }
+ if(!sourceGrowDepth(r, b, &e, i))
+ goto Err;
+ }
+
+ index[e.depth] = r->offset % r->epb;
+
+ for(i=e.depth; i>=early; i--){
+ bb = blockWalk(b, index[i], m, r->fs, &e);
+ if(bb == nil)
+ goto Err;
+ blockPut(b);
+ b = bb;
+ }
+ b->pc = getcallerpc(&r);
+ return b;
+Err:
+ blockPut(b);
+ return nil;
+}
+
+Block*
+sourceBlock(Source *r, ulong bn, int mode)
+{
+ Block *b;
+
+ b = _sourceBlock(r, bn, mode, 0, 0);
+ if(b)
+ b->pc = getcallerpc(&r);
+ return b;
+}
+
+void
+sourceClose(Source *r)
+{
+ if(r == nil)
+ return;
+ vtLock(r->lk);
+ r->ref--;
+ if(r->ref){
+ vtUnlock(r->lk);
+ return;
+ }
+ assert(r->ref == 0);
+ vtUnlock(r->lk);
+ if(r->parent)
+ sourceClose(r->parent);
+ vtLockFree(r->lk);
+ memset(r, ~0, sizeof(*r));
+ vtMemFree(r);
+}
+
+/*
+ * Retrieve the block containing the entry for r.
+ * If a snapshot has happened, we might need
+ * to get a new copy of the block. We avoid this
+ * in the common case by caching the score for
+ * the block and the last epoch in which it was valid.
+ *
+ * We use r->mode to tell the difference between active
+ * file system sources (OReadWrite) and sources for the
+ * snapshot file system (OReadOnly).
+ */
+static Block*
+sourceLoadBlock(Source *r, int mode)
+{
+ u32int addr;
+ Block *b;
+
+ switch(r->mode){
+ default:
+ assert(0);
+ case OReadWrite:
+ assert(r->mode == OReadWrite);
+ /*
+ * This needn't be true -- we might bump the low epoch
+ * to reclaim some old blocks, but since this score is
+ * OReadWrite, the blocks must all still be open, so none
+ * are reclaimed. Thus it's okay that the epoch is so low.
+ * Proceed.
+ assert(r->epoch >= r->fs->elo);
+ */
+ if(r->epoch == r->fs->ehi){
+ b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
+ if(b == nil)
+ return nil;
+ assert(r->epoch == b->l.epoch);
+ return b;
+ }
+ assert(r->parent != nil);
+ if(!sourceLock(r->parent, OReadWrite))
+ return nil;
+ b = sourceBlock(r->parent, r->offset/r->epb, OReadWrite);
+ sourceUnlock(r->parent);
+ if(b == nil)
+ return nil;
+ assert(b->l.epoch == r->fs->ehi);
+ // fprint(2, "sourceLoadBlock %p %V => %V\n", r, r->score, b->score);
+ memmove(r->score, b->score, VtScoreSize);
+ r->scoreEpoch = b->l.epoch;
+ r->tag = b->l.tag;
+ r->epoch = r->fs->ehi;
+ return b;
+
+ case OReadOnly:
+ addr = globalToLocal(r->score);
+ if(addr == NilBlock)
+ return cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, mode);
+
+ b = cacheLocalData(r->fs->cache, addr, BtDir, r->tag, mode, r->scoreEpoch);
+ if(b)
+ return b;
+
+ /*
+ * If it failed because the epochs don't match, the block has been
+ * archived and reclaimed. Rewalk from the parent and get the
+ * new pointer. This can't happen in the OReadWrite case
+ * above because blocks in the current epoch don't get
+ * reclaimed. The fact that we're OReadOnly means we're
+ * a snapshot. (Or else the file system is read-only, but then
+ * the archiver isn't going around deleting blocks.)
+ */
+ if(strcmp(vtGetError(), ELabelMismatch) == 0){
+ if(!sourceLock(r->parent, OReadOnly))
+ return nil;
+ b = sourceBlock(r->parent, r->offset/r->epb, OReadOnly);
+ sourceUnlock(r->parent);
+ if(b){
+ fprint(2, "sourceAlloc: lost %V found %V\n",
+ r->score, b->score);
+ memmove(r->score, b->score, VtScoreSize);
+ r->scoreEpoch = b->l.epoch;
+ return b;
+ }
+ }
+ return nil;
+ }
+}
+
+int
+sourceLock(Source *r, int mode)
+{
+ Block *b;
+
+ if(mode == -1)
+ mode = r->mode;
+
+ b = sourceLoadBlock(r, mode);
+ if(b == nil)
+ return 0;
+ /*
+ * The fact that we are holding b serves as the
+ * lock entitling us to write to r->b.
+ */
+ assert(r->b == nil);
+ r->b = b;
+ if(r->mode == OReadWrite)
+ assert(r->epoch == r->b->l.epoch);
+ return 1;
+}
+
+/*
+ * Lock two (usually sibling) sources. This needs special care
+ * because the Entries for both sources might be in the same block.
+ * We also try to lock blocks in left-to-right order within the tree.
+ */
+int
+sourceLock2(Source *r, Source *rr, int mode)
+{
+ Block *b, *bb;
+
+ if(rr == nil)
+ return sourceLock(r, mode);
+
+ if(mode == -1)
+ mode = r->mode;
+
+ if(r->parent==rr->parent && r->offset/r->epb == rr->offset/rr->epb){
+ b = sourceLoadBlock(r, mode);
+ if(b == nil)
+ return 0;
+ if(memcmp(r->score, rr->score, VtScoreSize) != 0){
+ memmove(rr->score, b->score, VtScoreSize);
+ rr->scoreEpoch = b->l.epoch;
+ rr->tag = b->l.tag;
+ rr->epoch = rr->fs->ehi;
+ }
+ blockDupLock(b);
+ bb = b;
+ }else if(r->parent==rr->parent || r->offset > rr->offset){
+ bb = sourceLoadBlock(rr, mode);
+ b = sourceLoadBlock(r, mode);
+ }else{
+ b = sourceLoadBlock(r, mode);
+ bb = sourceLoadBlock(rr, mode);
+ }
+ if(b == nil || bb == nil){
+ if(b)
+ blockPut(b);
+ if(bb)
+ blockPut(bb);
+ return 0;
+ }
+
+ /*
+ * The fact that we are holding b and bb serves
+ * as the lock entitling us to write to r->b and rr->b.
+ */
+ r->b = b;
+ rr->b = bb;
+ return 1;
+}
+
+void
+sourceUnlock(Source *r)
+{
+ Block *b;
+
+ if(r->b == nil){
+ fprint(2, "sourceUnlock: already unlocked\n");
+ abort();
+ }
+ b = r->b;
+ r->b = nil;
+ blockPut(b);
+}
+
+static Block*
+sourceLoad(Source *r, Entry *e)
+{
+ Block *b;
+
+ assert(sourceIsLocked(r));
+ b = r->b;
+ if(!entryUnpack(e, b->data, r->offset % r->epb))
+ return nil;
+ if(e->gen != r->gen){
+ vtSetError(ERemoved);
+ return nil;
+ }
+ blockDupLock(b);
+ return b;
+}
+
+static int
+sizeToDepth(uvlong s, int psize, int dsize)
+{
+ int np;
+ int d;
+
+ /* determine pointer depth */
+ np = psize/VtScoreSize;
+ s = (s + dsize - 1)/dsize;
+ for(d = 0; s > 1; d++)
+ s = (s + np - 1)/np;
+ return d;
+}
+
+static u32int
+tagGen(void)
+{
+ u32int tag;
+
+ for(;;){
+ tag = lrand();
+ if(tag >= UserTag)
+ break;
+ }
+ return tag;
+}
+
+char *
+sourceName(Source *s)
+{
+ return fileName(s->file);
+}
diff --git a/src/cmd/fossil/srcload.c b/src/cmd/fossil/srcload.c
new file mode 100644
index 00000000..8cf63b4f
--- /dev/null
+++ b/src/cmd/fossil/srcload.c
@@ -0,0 +1,270 @@
+#include "stdinc.h"
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+int num = 100;
+int length = 20*1024;
+int block= 1024;
+int bush = 4;
+int iter = 100;
+Biobuf *bout;
+int maxdepth;
+
+Source *mkroot(Cache*);
+void new(Source*, int trace, int);
+int delete(Source*);
+int count(Source *s, int);
+void stats(Source *s);
+void dump(Source *s, int ident, ulong entry);
+static void bench(Source *r);
+
+void
+main(int argc, char *argv[])
+{
+ int i;
+ Fs *fs;
+ int csize = 1000;
+ ulong t;
+ Source *r;
+
+ ARGBEGIN{
+ case 'i':
+ iter = atoi(ARGF());
+ break;
+ case 'n':
+ num = atoi(ARGF());
+ break;
+ case 'l':
+ length = atoi(ARGF());
+ break;
+ case 'b':
+ block = atoi(ARGF());
+ break;
+ case 'u':
+ bush = atoi(ARGF());
+ break;
+ case 'c':
+ csize = atoi(ARGF());
+ break;
+ }ARGEND;
+
+ vtAttach();
+
+ bout = vtMemAllocZ(sizeof(Biobuf));
+ Binit(bout, 1, OWRITE);
+
+ fmtinstall('V', vtScoreFmt);
+ fmtinstall('R', vtErrFmt);
+
+ fs = fsOpen(argv[0], nil, csize, OReadWrite);
+ if(fs == nil)
+ sysfatal("could not open fs: %r");
+
+ t = time(0);
+
+ srand(0);
+
+ r = fs->source;
+ dump(r, 0, 0);
+
+ fprint(2, "count = %d\n", count(r, 1));
+ for(i=0; i<num; i++)
+ new(r, 0, 0);
+
+ for(i=0; i<iter; i++){
+ if(i % 10000 == 0)
+ stats(r);
+ new(r, 0, 0);
+ delete(r);
+ }
+
+// dump(r, 0, 0);
+
+ fprint(2, "count = %d\n", count(r, 1));
+// cacheCheck(c);
+
+ fprint(2, "deleting\n");
+ for(i=0; i<num; i++)
+ delete(r);
+// dump(r, 0, 0);
+
+ fprint(2, "count = %d\n", count(r, 1));
+ fprint(2, "total time = %ld\n", time(0)-t);
+
+ fsClose(fs);
+ vtDetach();
+ exits(0);
+}
+
+static void
+bench(Source *r)
+{
+ vlong t;
+ Entry e;
+ int i;
+
+ t = nsec();
+
+ for(i=0; i<1000000; i++)
+ sourceGetEntry(r, &e);
+
+ fprint(2, "%f\n", 1e-9*(nsec() - t));
+}
+
+void
+new(Source *s, int trace, int depth)
+{
+ int i, n;
+ Source *ss;
+ Entry e;
+
+ if(depth > maxdepth)
+ maxdepth = depth;
+
+ Bflush(bout);
+
+ n = sourceGetDirSize(s);
+ for(i=0; i<n; i++){
+ ss = sourceOpen(s, nrand(n), OReadWrite);
+ if(ss == nil || !sourceGetEntry(ss, &e))
+ continue;
+ if((e.flags & VtEntryDir) && frand() < 1./bush){
+ if(trace){
+ int j;
+ for(j=0; j<trace; j++)
+ Bprint(bout, " ");
+ Bprint(bout, "decend %d\n", i);
+ }
+ new(ss, trace?trace+1:0, depth+1);
+ sourceClose(ss);
+ return;
+ }
+ sourceClose(ss);
+ }
+ ss = sourceCreate(s, s->dsize, 1+frand()>.5, 0);
+ if(ss == nil){
+ Bprint(bout, "could not create directory: %R\n");
+ return;
+ }
+ if(trace){
+ int j;
+ for(j=1; j<trace; j++)
+ Bprint(bout, " ");
+ Bprint(bout, "create %d\n", ss->offset);
+ }
+ sourceClose(ss);
+}
+
+int
+delete(Source *s)
+{
+ int i, n;
+ Source *ss;
+
+ n = sourceGetDirSize(s);
+ /* check if empty */
+ for(i=0; i<n; i++){
+ ss = sourceOpen(s, i, OReadWrite);
+ if(ss != nil){
+ sourceClose(ss);
+ break;
+ }
+ }
+ if(i == n)
+ return 0;
+
+ for(;;){
+ ss = sourceOpen(s, nrand(n), OReadWrite);
+ if(ss == nil)
+ continue;
+ if(s->dir && delete(ss)){
+ sourceClose(ss);
+ return 1;
+ }
+ if(1)
+ break;
+ sourceClose(ss);
+ }
+
+
+ sourceRemove(ss);
+ return 1;
+}
+
+void
+dump(Source *s, int ident, ulong entry)
+{
+ ulong i, n;
+ Source *ss;
+ Entry e;
+
+ for(i=0; i<ident; i++)
+ Bprint(bout, " ");
+
+ if(!sourceGetEntry(s, &e)){
+ fprint(2, "sourceGetEntry failed: %r\n");
+ return;
+ }
+
+ Bprint(bout, "%4lud: gen %4ud depth %d tag=%x score=%V",
+ entry, e.gen, e.depth, e.tag, e.score);
+ if(!s->dir){
+ Bprint(bout, " data size: %llud\n", e.size);
+ return;
+ }
+ n = sourceGetDirSize(s);
+ Bprint(bout, " dir size: %lud\n", n);
+ for(i=0; i<n; i++){
+ ss = sourceOpen(s, i, 1);
+ if(ss == nil)
+ continue;
+ dump(ss, ident+1, i);
+ sourceClose(ss);
+ }
+ return;
+}
+
+int
+count(Source *s, int rec)
+{
+ ulong i, n;
+ int c;
+ Source *ss;
+
+ n = sourceGetDirSize(s);
+ c = 0;
+ for(i=0; i<n; i++){
+ ss = sourceOpen(s, i, OReadOnly);
+ if(ss == nil)
+ continue;
+ if(rec)
+ c += count(ss, rec);
+ c++;
+ sourceClose(ss);
+ }
+ return c;
+}
+
+void
+stats(Source *s)
+{
+ int n, i, c, cc, max;
+ Source *ss;
+
+ cc = 0;
+ max = 0;
+ n = sourceGetDirSize(s);
+ for(i=0; i<n; i++){
+ ss = sourceOpen(s, i, 1);
+ if(ss == nil)
+ continue;
+ cc++;
+ c = count(ss, 1);
+ if(c > max)
+ max = c;
+ sourceClose(ss);
+ }
+fprint(2, "count = %d top = %d depth=%d maxcount %d\n", cc, n, maxdepth, max);
+}
diff --git a/src/cmd/fossil/stdinc.h b/src/cmd/fossil/stdinc.h
new file mode 100644
index 00000000..ef611898
--- /dev/null
+++ b/src/cmd/fossil/stdinc.h
@@ -0,0 +1,11 @@
+#include <u.h>
+#include <libc.h>
+
+typedef uvlong u64int;
+typedef uchar u8int;
+typedef ushort u16int;
+
+#include "oventi.h"
+#include "vac.h"
+#include "fs.h"
+
diff --git a/src/cmd/fossil/trunc.c b/src/cmd/fossil/trunc.c
new file mode 100644
index 00000000..db24333c
--- /dev/null
+++ b/src/cmd/fossil/trunc.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+
+void
+main(int argc, char **argv)
+{
+ Dir d;
+
+ if(argc != 3){
+ fprint(2, "usage: trunc file size\n");
+ exits("usage");
+ }
+
+ nulldir(&d);
+ d.length = strtoull(argv[2], 0, 0);
+ if(dirwstat(argv[1], &d) < 0)
+ sysfatal("dirwstat: %r");
+ exits(0);
+}
diff --git a/src/cmd/fossil/vac.c b/src/cmd/fossil/vac.c
new file mode 100644
index 00000000..526ff971
--- /dev/null
+++ b/src/cmd/fossil/vac.c
@@ -0,0 +1,746 @@
+#include "stdinc.h"
+
+typedef struct MetaChunk MetaChunk;
+
+struct MetaChunk {
+ ushort offset;
+ ushort size;
+ ushort index;
+};
+
+static int stringUnpack(char **s, uchar **p, int *n);
+static int meCmp(MetaEntry*, char *s);
+static int meCmpOld(MetaEntry*, char *s);
+
+
+
+static char EBadMeta[] = "corrupted meta data";
+static char ENoFile[] = "file does not exist";
+
+/*
+ * integer conversion routines
+ */
+#define U8GET(p) ((p)[0])
+#define U16GET(p) (((p)[0]<<8)|(p)[1])
+#define U32GET(p) (((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])
+#define U48GET(p) (((uvlong)U16GET(p)<<32)|(uvlong)U32GET((p)+2))
+#define U64GET(p) (((uvlong)U32GET(p)<<32)|(uvlong)U32GET((p)+4))
+
+#define U8PUT(p,v) (p)[0]=(v)
+#define U16PUT(p,v) (p)[0]=(v)>>8;(p)[1]=(v)
+#define U32PUT(p,v) (p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
+#define U48PUT(p,v,t32) t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32)
+#define U64PUT(p,v,t32) t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32)
+
+static int
+stringUnpack(char **s, uchar **p, int *n)
+{
+ int nn;
+
+ if(*n < 2)
+ return 0;
+
+ nn = U16GET(*p);
+ *p += 2;
+ *n -= 2;
+ if(nn > *n)
+ return 0;
+ *s = vtMemAlloc(nn+1);
+ memmove(*s, *p, nn);
+ (*s)[nn] = 0;
+ *p += nn;
+ *n -= nn;
+ return 1;
+}
+
+static int
+stringPack(char *s, uchar *p)
+{
+ int n;
+
+ n = strlen(s);
+ U16PUT(p, n);
+ memmove(p+2, s, n);
+ return n+2;
+}
+
+int
+mbSearch(MetaBlock *mb, char *elem, int *ri, MetaEntry *me)
+{
+ int i;
+ int b, t, x;
+if(0)fprint(2, "mbSearch %s\n", elem);
+
+ /* binary search within block */
+ b = 0;
+ t = mb->nindex;
+ while(b < t){
+ i = (b+t)>>1;
+ meUnpack(me, mb, i);
+
+ if(mb->botch)
+ x = meCmpOld(me, elem);
+ else
+ x = meCmp(me, elem);
+
+ if(x == 0){
+ *ri = i;
+ return 1;
+ }
+
+ if(x < 0)
+ b = i+1;
+ else /* x > 0 */
+ t = i;
+ }
+
+ assert(b == t);
+
+ *ri = b; /* b is the index to insert this entry */
+ memset(me, 0, sizeof(*me));
+
+ vtSetError(ENoFile);
+ return 0;
+}
+
+void
+mbInit(MetaBlock *mb, uchar *p, int n, int ne)
+{
+ memset(p, 0, n);
+ mb->maxsize = n;
+ mb->maxindex = ne;
+ mb->nindex = 0;
+ mb->free = 0;
+ mb->size = MetaHeaderSize + ne*MetaIndexSize;
+ mb->buf = p;
+ mb->botch = 0;
+}
+
+int
+mbUnpack(MetaBlock *mb, uchar *p, int n)
+{
+ u32int magic;
+ int i;
+ int eo, en, omin;
+ uchar *q;
+
+ mb->maxsize = n;
+ mb->buf = p;
+
+ if(n == 0){
+ memset(mb, 0, sizeof(MetaBlock));
+ return 1;
+ }
+
+ magic = U32GET(p);
+ if(magic != MetaMagic && magic != MetaMagic-1)
+ goto Err;
+ mb->size = U16GET(p+4);
+ mb->free = U16GET(p+6);
+ mb->maxindex = U16GET(p+8);
+ mb->nindex = U16GET(p+10);
+ mb->botch = magic != MetaMagic;
+ if(mb->size > n)
+ goto Err;
+
+ omin = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ if(n < omin)
+ goto Err;
+
+
+ p += MetaHeaderSize;
+
+ /* check the index table - ensures that meUnpack and meCmp never fail */
+ for(i=0; i<mb->nindex; i++){
+ eo = U16GET(p);
+ en = U16GET(p+2);
+ if(eo < omin || eo+en > mb->size || en < 8)
+ goto Err;
+ q = mb->buf + eo;
+ if(U32GET(q) != DirMagic)
+ goto Err;
+ p += 4;
+ }
+
+ return 1;
+Err:
+ vtSetError(EBadMeta);
+ return 0;
+}
+
+
+void
+mbPack(MetaBlock *mb)
+{
+ uchar *p;
+
+ p = mb->buf;
+
+ assert(!mb->botch);
+
+ U32PUT(p, MetaMagic);
+ U16PUT(p+4, mb->size);
+ U16PUT(p+6, mb->free);
+ U16PUT(p+8, mb->maxindex);
+ U16PUT(p+10, mb->nindex);
+}
+
+
+void
+mbDelete(MetaBlock *mb, int i)
+{
+ uchar *p;
+ int n;
+ MetaEntry me;
+
+ assert(i < mb->nindex);
+ meUnpack(&me, mb, i);
+ memset(me.p, 0, me.size);
+
+ if(me.p - mb->buf + me.size == mb->size)
+ mb->size -= me.size;
+ else
+ mb->free += me.size;
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ n = (mb->nindex-i-1)*MetaIndexSize;
+ memmove(p, p+MetaIndexSize, n);
+ memset(p+n, 0, MetaIndexSize);
+ mb->nindex--;
+}
+
+void
+mbInsert(MetaBlock *mb, int i, MetaEntry *me)
+{
+ uchar *p;
+ int o, n;
+
+ assert(mb->nindex < mb->maxindex);
+
+ o = me->p - mb->buf;
+ n = me->size;
+ if(o+n > mb->size){
+ mb->free -= mb->size - o;
+ mb->size = o + n;
+ }else
+ mb->free -= n;
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ n = (mb->nindex-i)*MetaIndexSize;
+ memmove(p+MetaIndexSize, p, n);
+ U16PUT(p, me->p - mb->buf);
+ U16PUT(p+2, me->size);
+ mb->nindex++;
+}
+
+int
+mbResize(MetaBlock *mb, MetaEntry *me, int n)
+{
+ uchar *p, *ep;
+
+ /* easy case */
+ if(n <= me->size){
+ me->size = n;
+ return 1;
+ }
+
+ /* try and expand entry */
+
+ p = me->p + me->size;
+ ep = mb->buf + mb->maxsize;
+ while(p < ep && *p == 0)
+ p++;
+ if(n <= p - me->p){
+ me->size = n;
+ return 1;
+ }
+
+ p = mbAlloc(mb, n);
+ if(p != nil){
+ me->p = p;
+ me->size = n;
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+meUnpack(MetaEntry *me, MetaBlock *mb, int i)
+{
+ uchar *p;
+ int eo, en;
+
+ assert(i >= 0 && i < mb->nindex);
+
+ p = mb->buf + MetaHeaderSize + i*MetaIndexSize;
+ eo = U16GET(p);
+ en = U16GET(p+2);
+
+ me->p = mb->buf + eo;
+ me->size = en;
+
+ /* checked by mbUnpack */
+ assert(me->size >= 8);
+}
+
+/* assumes a small amount of checking has been done in mbEntry */
+static int
+meCmp(MetaEntry *me, char *s)
+{
+ int n;
+ uchar *p;
+
+ p = me->p;
+
+ /* skip magic & version */
+ p += 6;
+ n = U16GET(p);
+ p += 2;
+
+ if(n > me->size - 8)
+ n = me->size - 8;
+
+ while(n > 0){
+ if(*s == 0)
+ return 1;
+ if(*p < (uchar)*s)
+ return -1;
+ if(*p > (uchar)*s)
+ return 1;
+ p++;
+ s++;
+ n--;
+ }
+ return -(*s != 0);
+}
+
+/*
+ * This is the old and broken meCmp.
+ * This cmp routine reverse the sense of the comparison
+ * when one string is a prefix of the other.
+ * In other words, it put "ab" after "abc" rather
+ * than before. This behaviour is ok; binary search
+ * and sort still work. However, it is goes against
+ * the usual convention.
+ */
+static int
+meCmpOld(MetaEntry *me, char *s)
+{
+ int n;
+ uchar *p;
+
+ p = me->p;
+
+ /* skip magic & version */
+ p += 6;
+ n = U16GET(p);
+ p += 2;
+
+ if(n > me->size - 8)
+ n = me->size - 8;
+
+ while(n > 0){
+ if(*s == 0)
+ return -1;
+ if(*p < (uchar)*s)
+ return -1;
+ if(*p > (uchar)*s)
+ return 1;
+ p++;
+ s++;
+ n--;
+ }
+ return *s != 0;
+}
+
+static int
+offsetCmp(void *s0, void *s1)
+{
+ MetaChunk *mc0, *mc1;
+
+ mc0 = s0;
+ mc1 = s1;
+ if(mc0->offset < mc1->offset)
+ return -1;
+ if(mc0->offset > mc1->offset)
+ return 1;
+ return 0;
+}
+
+static MetaChunk *
+metaChunks(MetaBlock *mb)
+{
+ MetaChunk *mc;
+ int oo, o, n, i;
+ uchar *p;
+
+ mc = vtMemAlloc(mb->nindex*sizeof(MetaChunk));
+ p = mb->buf + MetaHeaderSize;
+ for(i = 0; i<mb->nindex; i++){
+ mc[i].offset = U16GET(p);
+ mc[i].size = U16GET(p+2);
+ mc[i].index = i;
+ p += MetaIndexSize;
+ }
+
+ qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
+
+ /* check block looks ok */
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ o = oo;
+ n = 0;
+ for(i=0; i<mb->nindex; i++){
+ o = mc[i].offset;
+ n = mc[i].size;
+ if(o < oo)
+ goto Err;
+ oo += n;
+ }
+ if(o+n > mb->size)
+ goto Err;
+ if(mb->size - oo != mb->free)
+ goto Err;
+
+ return mc;
+Err:
+fprint(2, "metaChunks failed!\n");
+oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+for(i=0; i<mb->nindex; i++){
+fprint(2, "\t%d: %d %d\n", i, mc[i].offset, mc[i].offset + mc[i].size);
+oo += mc[i].size;
+}
+fprint(2, "\tused=%d size=%d free=%d free2=%d\n", oo, mb->size, mb->free, mb->size - oo);
+ vtSetError(EBadMeta);
+ vtMemFree(mc);
+ return nil;
+}
+
+static void
+mbCompact(MetaBlock *mb, MetaChunk *mc)
+{
+ int oo, o, n, i;
+
+ oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+
+ for(i=0; i<mb->nindex; i++){
+ o = mc[i].offset;
+ n = mc[i].size;
+ if(o != oo){
+ memmove(mb->buf + oo, mb->buf + o, n);
+ U16PUT(mb->buf + MetaHeaderSize + mc[i].index*MetaIndexSize, oo);
+ }
+ oo += n;
+ }
+
+ mb->size = oo;
+ mb->free = 0;
+}
+
+uchar *
+mbAlloc(MetaBlock *mb, int n)
+{
+ int i, o;
+ MetaChunk *mc;
+
+ /* off the end */
+ if(mb->maxsize - mb->size >= n)
+ return mb->buf + mb->size;
+
+ /* check if possible */
+ if(mb->maxsize - mb->size + mb->free < n)
+ return nil;
+
+ mc = metaChunks(mb);
+ if(mc == nil){
+fprint(2, "mbAlloc: metaChunks failed: %r\n");
+ return nil;
+ }
+
+ /* look for hole */
+ o = MetaHeaderSize + mb->maxindex*MetaIndexSize;
+ for(i=0; i<mb->nindex; i++){
+ if(mc[i].offset - o >= n){
+ vtMemFree(mc);
+ return mb->buf + o;
+ }
+ o = mc[i].offset + mc[i].size;
+ }
+
+ if(mb->maxsize - o >= n){
+ vtMemFree(mc);
+ return mb->buf + o;
+ }
+
+ /* compact and return off the end */
+ mbCompact(mb, mc);
+ vtMemFree(mc);
+
+ if(mb->maxsize - mb->size < n){
+ vtSetError(EBadMeta);
+ return nil;
+ }
+ return mb->buf + mb->size;
+}
+
+int
+deSize(DirEntry *dir)
+{
+ int n;
+
+ /* constant part */
+
+ n = 4 + /* magic */
+ 2 + /* version */
+ 4 + /* entry */
+ 4 + /* guid */
+ 4 + /* mentry */
+ 4 + /* mgen */
+ 8 + /* qid */
+ 4 + /* mtime */
+ 4 + /* mcount */
+ 4 + /* ctime */
+ 4 + /* atime */
+ 4 + /* mode */
+ 0;
+
+ /* strings */
+ n += 2 + strlen(dir->elem);
+ n += 2 + strlen(dir->uid);
+ n += 2 + strlen(dir->gid);
+ n += 2 + strlen(dir->mid);
+
+ /* optional sections */
+ if(dir->qidSpace){
+ n += 3 + /* option header */
+ 8 + /* qidOffset */
+ 8; /* qid Max */
+ }
+
+ return n;
+}
+
+void
+dePack(DirEntry *dir, MetaEntry *me)
+{
+ uchar *p;
+ ulong t32;
+
+ p = me->p;
+
+ U32PUT(p, DirMagic);
+ U16PUT(p+4, 9); /* version */
+ p += 6;
+
+ p += stringPack(dir->elem, p);
+
+ U32PUT(p, dir->entry);
+ U32PUT(p+4, dir->gen);
+ U32PUT(p+8, dir->mentry);
+ U32PUT(p+12, dir->mgen);
+ U64PUT(p+16, dir->qid, t32);
+ p += 24;
+
+ p += stringPack(dir->uid, p);
+ p += stringPack(dir->gid, p);
+ p += stringPack(dir->mid, p);
+
+ U32PUT(p, dir->mtime);
+ U32PUT(p+4, dir->mcount);
+ U32PUT(p+8, dir->ctime);
+ U32PUT(p+12, dir->atime);
+ U32PUT(p+16, dir->mode);
+ p += 5*4;
+
+ if(dir->qidSpace){
+ U8PUT(p, DeQidSpace);
+ U16PUT(p+1, 2*8);
+ p += 3;
+ U64PUT(p, dir->qidOffset, t32);
+ U64PUT(p+8, dir->qidMax, t32);
+ p += 16;
+ }
+
+ assert(p == me->p + me->size);
+}
+
+
+int
+deUnpack(DirEntry *dir, MetaEntry *me)
+{
+ int t, nn, n, version;
+ uchar *p;
+
+ p = me->p;
+ n = me->size;
+
+ memset(dir, 0, sizeof(DirEntry));
+
+if(0)print("deUnpack\n");
+ /* magic */
+ if(n < 4 || U32GET(p) != DirMagic)
+ goto Err;
+ p += 4;
+ n -= 4;
+
+if(0)print("deUnpack: got magic\n");
+ /* version */
+ if(n < 2)
+ goto Err;
+ version = U16GET(p);
+ if(version < 7 || version > 9)
+ goto Err;
+ p += 2;
+ n -= 2;
+
+if(0)print("deUnpack: got version\n");
+
+ /* elem */
+ if(!stringUnpack(&dir->elem, &p, &n))
+ goto Err;
+
+if(0)print("deUnpack: got elem\n");
+
+ /* entry */
+ if(n < 4)
+ goto Err;
+ dir->entry = U32GET(p);
+ p += 4;
+ n -= 4;
+
+if(0)print("deUnpack: got entry\n");
+
+ if(version < 9){
+ dir->gen = 0;
+ dir->mentry = dir->entry+1;
+ dir->mgen = 0;
+ }else{
+ if(n < 3*4)
+ goto Err;
+ dir->gen = U32GET(p);
+ dir->mentry = U32GET(p+4);
+ dir->mgen = U32GET(p+8);
+ p += 3*4;
+ n -= 3*4;
+ }
+
+if(0)print("deUnpack: got gen etc\n");
+
+ /* size is gotten from VtEntry */
+ dir->size = 0;
+
+ /* qid */
+ if(n < 8)
+ goto Err;
+ dir->qid = U64GET(p);
+ p += 8;
+ n -= 8;
+
+if(0)print("deUnpack: got qid\n");
+ /* skip replacement */
+ if(version == 7){
+ if(n < VtScoreSize)
+ goto Err;
+ p += VtScoreSize;
+ n -= VtScoreSize;
+ }
+
+ /* uid */
+ if(!stringUnpack(&dir->uid, &p, &n))
+ goto Err;
+
+ /* gid */
+ if(!stringUnpack(&dir->gid, &p, &n))
+ goto Err;
+
+ /* mid */
+ if(!stringUnpack(&dir->mid, &p, &n))
+ goto Err;
+
+if(0)print("deUnpack: got ids\n");
+ if(n < 5*4)
+ goto Err;
+ dir->mtime = U32GET(p);
+ dir->mcount = U32GET(p+4);
+ dir->ctime = U32GET(p+8);
+ dir->atime = U32GET(p+12);
+ dir->mode = U32GET(p+16);
+ p += 5*4;
+ n -= 5*4;
+
+if(0)print("deUnpack: got times\n");
+ /* optional meta data */
+ while(n > 0){
+ if(n < 3)
+ goto Err;
+ t = p[0];
+ nn = U16GET(p+1);
+ p += 3;
+ n -= 3;
+ if(n < nn)
+ goto Err;
+ switch(t){
+ case DePlan9:
+ /* not valid in version >= 9 */
+ if(version >= 9)
+ break;
+ if(dir->plan9 || nn != 12)
+ goto Err;
+ dir->plan9 = 1;
+ dir->p9path = U64GET(p);
+ dir->p9version = U32GET(p+8);
+ if(dir->mcount == 0)
+ dir->mcount = dir->p9version;
+ break;
+ case DeGen:
+ /* not valid in version >= 9 */
+ if(version >= 9)
+ break;
+ break;
+ case DeQidSpace:
+ if(dir->qidSpace || nn != 16)
+ goto Err;
+ dir->qidSpace = 1;
+ dir->qidOffset = U64GET(p);
+ dir->qidMax = U64GET(p+8);
+ break;
+ }
+ p += nn;
+ n -= nn;
+ }
+if(0)print("deUnpack: got options\n");
+
+ if(p != me->p + me->size)
+ goto Err;
+
+if(0)print("deUnpack: correct size\n");
+ return 1;
+Err:
+if(0)print("deUnpack: XXXXXXXXXXXX EBadMeta\n");
+ vtSetError(EBadMeta);
+ deCleanup(dir);
+ return 0;
+}
+
+void
+deCleanup(DirEntry *dir)
+{
+ vtMemFree(dir->elem);
+ dir->elem = nil;
+ vtMemFree(dir->uid);
+ dir->uid = nil;
+ vtMemFree(dir->gid);
+ dir->gid = nil;
+ vtMemFree(dir->mid);
+ dir->mid = nil;
+}
+
+void
+deCopy(DirEntry *dst, DirEntry *src)
+{
+ *dst = *src;
+ dst->elem = vtStrDup(src->elem);
+ dst->uid = vtStrDup(src->uid);
+ dst->gid = vtStrDup(src->gid);
+ dst->mid = vtStrDup(src->mid);
+}
diff --git a/src/cmd/fossil/vac.h b/src/cmd/fossil/vac.h
new file mode 100644
index 00000000..bd26c62d
--- /dev/null
+++ b/src/cmd/fossil/vac.h
@@ -0,0 +1,107 @@
+typedef struct DirEntry DirEntry;
+typedef struct MetaBlock MetaBlock;
+typedef struct MetaEntry MetaEntry;
+
+enum {
+ MetaMagic = 0x5656fc7a,
+ MetaHeaderSize = 12,
+ MetaIndexSize = 4,
+ IndexEntrySize = 8,
+ DirMagic = 0x1c4d9072,
+};
+
+/*
+ * Mode bits
+ */
+enum {
+ ModeOtherExec = (1<<0),
+ ModeOtherWrite = (1<<1),
+ ModeOtherRead = (1<<2),
+ ModeGroupExec = (1<<3),
+ ModeGroupWrite = (1<<4),
+ ModeGroupRead = (1<<5),
+ ModeOwnerExec = (1<<6),
+ ModeOwnerWrite = (1<<7),
+ ModeOwnerRead = (1<<8),
+ ModeSticky = (1<<9),
+ ModeSetUid = (1<<10),
+ ModeSetGid = (1<<11),
+ ModeAppend = (1<<12), /* append only file */
+ ModeExclusive = (1<<13), /* lock file - plan 9 */
+ ModeLink = (1<<14), /* sym link */
+ ModeDir = (1<<15), /* duplicate of DirEntry */
+ ModeHidden = (1<<16), /* MS-DOS */
+ ModeSystem = (1<<17), /* MS-DOS */
+ ModeArchive = (1<<18), /* MS-DOS */
+ ModeTemporary = (1<<19), /* MS-DOS */
+ ModeSnapshot = (1<<20), /* read only snapshot */
+};
+
+/* optional directory entry fields */
+enum {
+ DePlan9 = 1, /* not valid in version >= 9 */
+ DeNT, /* not valid in version >= 9 */
+ DeQidSpace,
+ DeGen, /* not valid in version >= 9 */
+};
+
+struct DirEntry {
+ char *elem; /* path element */
+ ulong entry; /* entry in directory for data */
+ ulong gen; /* generation of data entry */
+ ulong mentry; /* entry in directory for meta */
+ ulong mgen; /* generation of meta entry */
+ uvlong size; /* size of file */
+ uvlong qid; /* unique file id */
+
+ char *uid; /* owner id */
+ char *gid; /* group id */
+ char *mid; /* last modified by */
+ ulong mtime; /* last modified time */
+ ulong mcount; /* number of modifications: can wrap! */
+ ulong ctime; /* directory entry last changed */
+ ulong atime; /* last time accessed */
+ ulong mode; /* various mode bits */
+
+ /* plan 9 */
+ int plan9;
+ uvlong p9path;
+ ulong p9version;
+
+ /* sub space of qid */
+ int qidSpace;
+ uvlong qidOffset; /* qid offset */
+ uvlong qidMax; /* qid maximum */
+};
+
+struct MetaEntry {
+ uchar *p;
+ ushort size;
+};
+
+struct MetaBlock {
+ int maxsize; /* size of block */
+ int size; /* size used */
+ int free; /* free space within used size */
+ int maxindex; /* entries allocated for table */
+ int nindex; /* amount of table used */
+ int botch; /* compensate for my stupidity */
+ uchar *buf;
+};
+
+void deCleanup(DirEntry*);
+void deCopy(DirEntry*, DirEntry*);
+int deSize(DirEntry*);
+void dePack(DirEntry*, MetaEntry*);
+int deUnpack(DirEntry*, MetaEntry*);
+
+void mbInit(MetaBlock*, uchar*, int, int);
+int mbUnpack(MetaBlock*, uchar*, int);
+void mbInsert(MetaBlock*, int, MetaEntry*);
+void mbDelete(MetaBlock*, int);
+void mbPack(MetaBlock*);
+uchar *mbAlloc(MetaBlock*, int);
+int mbResize(MetaBlock*, MetaEntry*, int);
+int mbSearch(MetaBlock*, char*, int*, MetaEntry*);
+
+void meUnpack(MetaEntry*, MetaBlock*, int);
diff --git a/src/cmd/fossil/view.c b/src/cmd/fossil/view.c
new file mode 100644
index 00000000..c2ac9976
--- /dev/null
+++ b/src/cmd/fossil/view.c
@@ -0,0 +1,1127 @@
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+#include <draw.h>
+#include <event.h>
+
+/* --- tree.h */
+typedef struct Tree Tree;
+typedef struct Tnode Tnode;
+
+struct Tree
+{
+ Tnode *root;
+ Point offset;
+ Image *clipr;
+};
+
+struct Tnode
+{
+ Point offset;
+
+ char *str;
+// char *(*strfn)(Tnode*);
+// uint (*draw)(Tnode*, Image*, Image*, Point);
+ void (*expand)(Tnode*);
+ void (*collapse)(Tnode*);
+
+ uint expanded;
+ Tnode **kid;
+ int nkid;
+ void *aux;
+};
+
+typedef struct Atree Atree;
+struct Atree
+{
+ int resizefd;
+ Tnode *root;
+};
+
+Atree *atreeinit(char*);
+
+/* --- visfossil.c */
+Tnode *initxheader(void);
+Tnode *initxcache(char *name);
+Tnode *initxsuper(void);
+Tnode *initxlocalroot(char *name, u32int addr);
+Tnode *initxentry(Entry);
+Tnode *initxsource(Entry, int);
+Tnode *initxentryblock(Block*, Entry*);
+Tnode *initxdatablock(Block*, uint);
+Tnode *initxroot(char *name, uchar[VtScoreSize]);
+
+int fd;
+Header h;
+Super super;
+VtSession *z;
+VtRoot vac;
+int showinactive;
+
+/*
+ * dumbed down versions of fossil routines
+ */
+char*
+bsStr(int state)
+{
+ static char s[100];
+
+ if(state == BsFree)
+ return "Free";
+ if(state == BsBad)
+ return "Bad";
+
+ sprint(s, "%x", state);
+ if(!(state&BsAlloc))
+ strcat(s, ",Free"); /* should not happen */
+ if(state&BsVenti)
+ strcat(s, ",Venti");
+ if(state&BsClosed)
+ strcat(s, ",Closed");
+ return s;
+}
+
+char *bttab[] = {
+ "BtData",
+ "BtData+1",
+ "BtData+2",
+ "BtData+3",
+ "BtData+4",
+ "BtData+5",
+ "BtData+6",
+ "BtData+7",
+ "BtDir",
+ "BtDir+1",
+ "BtDir+2",
+ "BtDir+3",
+ "BtDir+4",
+ "BtDir+5",
+ "BtDir+6",
+ "BtDir+7",
+};
+
+char*
+btStr(int type)
+{
+ if(type < nelem(bttab))
+ return bttab[type];
+ return "unknown";
+}
+#pragma varargck argpos stringnode 1
+
+Block*
+allocBlock(void)
+{
+ Block *b;
+
+ b = mallocz(sizeof(Block)+h.blockSize, 1);
+ b->data = (void*)&b[1];
+ return b;
+}
+
+void
+blockPut(Block *b)
+{
+ free(b);
+}
+
+static u32int
+partStart(int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return h.super;
+ case PartLabel:
+ return h.label;
+ case PartData:
+ return h.data;
+ }
+}
+
+
+static u32int
+partEnd(int part)
+{
+ switch(part){
+ default:
+ assert(0);
+ case PartSuper:
+ return h.super+1;
+ case PartLabel:
+ return h.data;
+ case PartData:
+ return h.end;
+ }
+}
+
+Block*
+readBlock(int part, u32int addr)
+{
+ u32int start, end;
+ u64int offset;
+ int n, nn;
+ Block *b;
+ uchar *buf;
+
+ start = partStart(part);
+ end = partEnd(part);
+ if(addr >= end-start){
+ werrstr("bad addr 0x%.8ux; wanted 0x%.8ux - 0x%.8ux", addr, start, end);
+ return nil;
+ }
+
+ b = allocBlock();
+ b->addr = addr;
+ buf = b->data;
+ offset = ((u64int)(addr+start))*h.blockSize;
+ n = h.blockSize;
+ while(n > 0){
+ nn = pread(fd, buf, n, offset);
+ if(nn < 0){
+ blockPut(b);
+ return nil;
+ }
+ if(nn == 0){
+ werrstr("short read");
+ blockPut(b);
+ return nil;
+ }
+ n -= nn;
+ offset += nn;
+ buf += nn;
+ }
+ return b;
+}
+
+int vtType[BtMax] = {
+ VtDataType, /* BtData | 0 */
+ VtPointerType0, /* BtData | 1 */
+ VtPointerType1, /* BtData | 2 */
+ VtPointerType2, /* BtData | 3 */
+ VtPointerType3, /* BtData | 4 */
+ VtPointerType4, /* BtData | 5 */
+ VtPointerType5, /* BtData | 6 */
+ VtPointerType6, /* BtData | 7 */
+ VtDirType, /* BtDir | 0 */
+ VtPointerType0, /* BtDir | 1 */
+ VtPointerType1, /* BtDir | 2 */
+ VtPointerType2, /* BtDir | 3 */
+ VtPointerType3, /* BtDir | 4 */
+ VtPointerType4, /* BtDir | 5 */
+ VtPointerType5, /* BtDir | 6 */
+ VtPointerType6, /* BtDir | 7 */
+};
+
+Block*
+ventiBlock(uchar score[VtScoreSize], uint type)
+{
+ int n;
+ Block *b;
+
+ b = allocBlock();
+ memmove(b->score, score, VtScoreSize);
+ b->addr = NilBlock;
+
+ n = vtRead(z, b->score, vtType[type], b->data, h.blockSize);
+ if(n < 0){
+ fprint(2, "vtRead returns %d: %R\n", n);
+ blockPut(b);
+ return nil;
+ }
+ vtZeroExtend(vtType[type], b->data, n, h.blockSize);
+ b->l.type = type;
+ b->l.state = 0;
+ b->l.tag = 0;
+ b->l.epoch = 0;
+ return b;
+}
+
+Block*
+dataBlock(uchar score[VtScoreSize], uint type, uint tag)
+{
+ Block *b, *bl;
+ int lpb;
+ Label l;
+ u32int addr;
+
+ addr = globalToLocal(score);
+ if(addr == NilBlock)
+ return ventiBlock(score, type);
+
+ lpb = h.blockSize/LabelSize;
+ bl = readBlock(PartLabel, addr/lpb);
+ if(bl == nil)
+ return nil;
+ if(!labelUnpack(&l, bl->data, addr%lpb)){
+ werrstr("%R");
+ blockPut(bl);
+ return nil;
+ }
+ blockPut(bl);
+ if(l.type != type){
+ werrstr("type mismatch; got %d (%s) wanted %d (%s)",
+ l.type, btStr(l.type), type, btStr(type));
+ return nil;
+ }
+ if(tag && l.tag != tag){
+ werrstr("tag mismatch; got 0x%.8ux wanted 0x%.8ux",
+ l.tag, tag);
+ return nil;
+ }
+ b = readBlock(PartData, addr);
+ if(b == nil)
+ return nil;
+ b->l = l;
+ return b;
+}
+
+Entry*
+copyEntry(Entry e)
+{
+ Entry *p;
+
+ p = mallocz(sizeof *p, 1);
+ *p = e;
+ return p;
+}
+
+MetaBlock*
+copyMetaBlock(MetaBlock mb)
+{
+ MetaBlock *p;
+
+ p = mallocz(sizeof mb, 1);
+ *p = mb;
+ return p;
+}
+
+/*
+ * visualizer
+ */
+
+#pragma varargck argpos stringnode 1
+
+Tnode*
+stringnode(char *fmt, ...)
+{
+ va_list arg;
+ Tnode *t;
+
+ t = mallocz(sizeof(Tnode), 1);
+ va_start(arg, fmt);
+ t->str = vsmprint(fmt, arg);
+ va_end(arg);
+ t->nkid = -1;
+ return t;
+}
+
+void
+xcacheexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxheader();
+}
+
+Tnode*
+initxcache(char *name)
+{
+ Tnode *t;
+
+ if((fd = open(name, OREAD)) < 0)
+ sysfatal("cannot open %s: %r", name);
+
+ t = stringnode("%s", name);
+ t->expand = xcacheexpand;
+ return t;
+}
+
+void
+xheaderexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxsuper();
+ //t->kid[1] = initxlabel(h.label);
+ //t->kid[2] = initxdata(h.data);
+}
+
+Tnode*
+initxheader(void)
+{
+ u8int buf[HeaderSize];
+ Tnode *t;
+
+ if(pread(fd, buf, HeaderSize, HeaderOffset) < HeaderSize)
+ return stringnode("error reading header: %r");
+ if(!headerUnpack(&h, buf))
+ return stringnode("error unpacking header: %R");
+
+ t = stringnode("header "
+ "version=%#ux (%d) "
+ "blockSize=%#ux (%d) "
+ "super=%#lux (%ld) "
+ "label=%#lux (%ld) "
+ "data=%#lux (%ld) "
+ "end=%#lux (%ld)",
+ h.version, h.version, h.blockSize, h.blockSize,
+ h.super, h.super,
+ h.label, h.label, h.data, h.data, h.end, h.end);
+ t->expand = xheaderexpand;
+ return t;
+}
+
+void
+xsuperexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxlocalroot("active", super.active);
+// t->kid[1] = initxlocalroot("next", super.next);
+// t->kid[2] = initxlocalroot("current", super.current);
+}
+
+Tnode*
+initxsuper(void)
+{
+ Block *b;
+ Tnode *t;
+
+ b = readBlock(PartSuper, 0);
+ if(b == nil)
+ return stringnode("reading super: %r");
+ if(!superUnpack(&super, b->data)){
+ blockPut(b);
+ return stringnode("unpacking super: %R");
+ }
+ blockPut(b);
+ t = stringnode("super "
+ "version=%#ux "
+ "epoch=[%#ux,%#ux) "
+ "qid=%#llux "
+ "active=%#x "
+ "next=%#x "
+ "current=%#x "
+ "last=%V "
+ "name=%s",
+ super.version, super.epochLow, super.epochHigh,
+ super.qid, super.active, super.next, super.current,
+ super.last, super.name);
+ t->expand = xsuperexpand;
+ return t;
+}
+
+void
+xvacrootexpand(Tnode *t)
+{
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxroot("root", vac.score);
+}
+
+Tnode*
+initxvacroot(uchar score[VtScoreSize])
+{
+ Tnode *t;
+ uchar buf[VtRootSize];
+ int n;
+
+ if((n = vtRead(z, score, VtRootType, buf, VtRootSize)) < 0)
+ return stringnode("reading root %V: %R", score);
+
+ if(!vtRootUnpack(&vac, buf))
+ return stringnode("unpack %d-byte root: %R", n);
+
+ h.blockSize = vac.blockSize;
+ t = stringnode("vac version=%#ux name=%s type=%s blockSize=%ud score=%V prev=%V",
+ vac.version, vac.name, vac.type, vac.blockSize, vac.score, vac.prev);
+ t->expand = xvacrootexpand;
+ return t;
+}
+
+Tnode*
+initxlabel(Label l)
+{
+ return stringnode("label type=%s state=%s epoch=%#ux tag=%#ux",
+ btStr(l.type), bsStr(l.state), l.epoch, l.tag);
+}
+
+typedef struct Xblock Xblock;
+struct Xblock
+{
+ Tnode;
+ Block *b;
+ int (*gen)(void*, Block*, int, Tnode**);
+ void *arg;
+ int printlabel;
+};
+
+void
+xblockexpand(Tnode *tt)
+{
+ int i, j;
+ enum { Q = 32 };
+ Xblock *t = (Xblock*)tt;
+ Tnode *nn;
+
+ if(t->nkid >= 0)
+ return;
+
+ j = 0;
+ if(t->printlabel){
+ t->kid = mallocz(Q*sizeof(t->kid[0]), 1);
+ t->kid[0] = initxlabel(t->b->l);
+ j = 1;
+ }
+
+ for(i=0;; i++){
+ switch((*t->gen)(t->arg, t->b, i, &nn)){
+ case -1:
+ t->nkid = j;
+ return;
+ case 0:
+ break;
+ case 1:
+ if(j%Q == 0)
+ t->kid = realloc(t->kid, (j+Q)*sizeof(t->kid[0]));
+ t->kid[j++] = nn;
+ break;
+ }
+ }
+}
+
+int
+nilgen(void*, Block*, int, Tnode**)
+{
+ return -1;
+}
+
+Tnode*
+initxblock(Block *b, char *s, int (*gen)(void*, Block*, int, Tnode**), void *arg)
+{
+ Xblock *t;
+
+ if(gen == nil)
+ gen = nilgen;
+ t = mallocz(sizeof(Xblock), 1);
+ t->b = b;
+ t->gen = gen;
+ t->arg = arg;
+ if(b->addr == NilBlock)
+ t->str = smprint("Block %V: %s", b->score, s);
+ else
+ t->str = smprint("Block %#ux: %s", b->addr, s);
+ t->printlabel = 1;
+ t->nkid = -1;
+ t->expand = xblockexpand;
+ return t;
+}
+
+int
+xentrygen(void *v, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+ Entry *ed;
+
+ ed = v;
+ if(o >= ed->dsize/VtEntrySize)
+ return -1;
+
+ entryUnpack(&e, b->data, o);
+ if(!showinactive && !(e.flags & VtEntryActive))
+ return 0;
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxentryblock(Block *b, Entry *ed)
+{
+ return initxblock(b, "entry", xentrygen, ed);
+}
+
+typedef struct Xentry Xentry;
+struct Xentry
+{
+ Tnode;
+ Entry e;
+};
+
+void
+xentryexpand(Tnode *tt)
+{
+ Xentry *t = (Xentry*)tt;
+
+ if(t->nkid >= 0)
+ return;
+
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*t->nkid, 1);
+ t->kid[0] = initxsource(t->e, 1);
+}
+
+Tnode*
+initxentry(Entry e)
+{
+ Xentry *t;
+
+ t = mallocz(sizeof *t, 1);
+ t->nkid = -1;
+ t->str = smprint("Entry gen=%#ux psize=%d dsize=%d depth=%d flags=%#ux size=%lld score=%V",
+ e.gen, e.psize, e.dsize, e.depth, e.flags, e.size, e.score);
+ if(e.flags & VtEntryLocal)
+ t->str = smprint("%s archive=%d snap=%d tag=%#ux", t->str, e.archive, e.snap, e.tag);
+ t->expand = xentryexpand;
+ t->e = e;
+ return t;
+}
+
+int
+ptrgen(void *v, Block *b, int o, Tnode **tp)
+{
+ Entry *ed;
+ Entry e;
+
+ ed = v;
+ if(o >= ed->psize/VtScoreSize)
+ return -1;
+
+ e = *ed;
+ e.depth--;
+ memmove(e.score, b->data+o*VtScoreSize, VtScoreSize);
+ if(memcmp(e.score, vtZeroScore, VtScoreSize) == 0)
+ return 0;
+ *tp = initxsource(e, 0);
+ return 1;
+}
+
+static int
+etype(int flags, int depth)
+{
+ uint t;
+
+ if(flags&VtEntryDir)
+ t = BtDir;
+ else
+ t = BtData;
+ return t+depth;
+}
+
+Tnode*
+initxsource(Entry e, int dowrap)
+{
+ Block *b;
+ Tnode *t, *tt;
+
+ b = dataBlock(e.score, etype(e.flags, e.depth), e.tag);
+ if(b == nil)
+ return stringnode("dataBlock: %r");
+
+ if((e.flags & VtEntryActive) == 0)
+ return stringnode("inactive Entry");
+
+ if(e.depth == 0){
+ if(e.flags & VtEntryDir)
+ tt = initxentryblock(b, copyEntry(e));
+ else
+ tt = initxdatablock(b, e.dsize);
+ }else{
+ tt = initxblock(b, smprint("%s+%d pointer", (e.flags & VtEntryDir) ? "BtDir" : "BtData", e.depth),
+ ptrgen, copyEntry(e));
+ }
+
+ /*
+ * wrap the contents of the Source in a Source node,
+ * just so it's closer to what you see in the code.
+ */
+ if(dowrap){
+ t = stringnode("Source");
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(Tnode*)*1, 1);
+ t->kid[0] = tt;
+ tt = t;
+ }
+ return tt;
+}
+
+int
+xlocalrootgen(void*, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+
+ if(o >= 1)
+ return -1;
+ entryUnpack(&e, b->data, o);
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxlocalroot(char *name, u32int addr)
+{
+ uchar score[VtScoreSize];
+ Block *b;
+
+ localToGlobal(addr, score);
+ b = dataBlock(score, BtDir, RootTag);
+ if(b == nil)
+ return stringnode("read data block %#ux: %R", addr);
+ return initxblock(b, smprint("'%s' fs root", name), xlocalrootgen, nil);
+}
+
+int
+xvacrootgen(void*, Block *b, int o, Tnode **tp)
+{
+ Entry e;
+
+ if(o >= 3)
+ return -1;
+ entryUnpack(&e, b->data, o);
+ *tp = initxentry(e);
+ return 1;
+}
+
+Tnode*
+initxroot(char *name, uchar score[VtScoreSize])
+{
+ Block *b;
+
+ b = dataBlock(score, BtDir, RootTag);
+ if(b == nil)
+ return stringnode("read data block %V: %R", score);
+ return initxblock(b, smprint("'%s' fs root", name), xvacrootgen, nil);
+}
+Tnode*
+initxdirentry(MetaEntry *me)
+{
+ DirEntry dir;
+ Tnode *t;
+
+ if(!deUnpack(&dir, me))
+ return stringnode("deUnpack: %R");
+
+ t = stringnode("dirEntry elem=%s size=%llud data=%#lux/%#lux meta=%#lux/%#lux", dir.elem, dir.size, dir.entry, dir.gen, dir.mentry, dir.mgen);
+ t->nkid = 1;
+ t->kid = mallocz(sizeof(t->kid[0])*1, 1);
+ t->kid[0] = stringnode(
+ "qid=%#llux\n"
+ "uid=%s gid=%s mid=%s\n"
+ "mtime=%lud mcount=%lud ctime=%lud atime=%lud\n"
+ "mode=%luo\n"
+ "plan9 %d p9path %#llux p9version %lud\n"
+ "qidSpace %d offset %#llux max %#llux",
+ dir.qid,
+ dir.uid, dir.gid, dir.mid,
+ dir.mtime, dir.mcount, dir.ctime, dir.atime,
+ dir.mode,
+ dir.plan9, dir.p9path, dir.p9version,
+ dir.qidSpace, dir.qidOffset, dir.qidMax);
+ return t;
+}
+
+int
+metaentrygen(void *v, Block*, int o, Tnode **tp)
+{
+ Tnode *t;
+ MetaBlock *mb;
+ MetaEntry me;
+
+ mb = v;
+ if(o >= mb->nindex)
+ return -1;
+ meUnpack(&me, mb, o);
+
+ t = stringnode("MetaEntry %d bytes", mb->size);
+ t->kid = mallocz(sizeof(t->kid[0])*1, 1);
+ t->kid[0] = initxdirentry(&me);
+ t->nkid = 1;
+ *tp = t;
+ return 1;
+}
+
+int
+metablockgen(void *v, Block *b, int o, Tnode **tp)
+{
+ Xblock *t;
+ MetaBlock *mb;
+
+ if(o >= 1)
+ return -1;
+
+ /* hack: reuse initxblock as a generic iterator */
+ mb = v;
+ t = (Xblock*)initxblock(b, "", metaentrygen, mb);
+ t->str = smprint("MetaBlock %d/%d space used, %d add'l free %d/%d table used%s",
+ mb->size, mb->maxsize, mb->free, mb->nindex, mb->maxindex,
+ mb->botch ? " [BOTCH]" : "");
+ t->printlabel = 0;
+ *tp = t;
+ return 1;
+}
+
+/*
+ * attempt to guess at the type of data in the block.
+ * it could just be data from a file, but we're hoping it's MetaBlocks.
+ */
+Tnode*
+initxdatablock(Block *b, uint n)
+{
+ MetaBlock mb;
+
+ if(n > h.blockSize)
+ n = h.blockSize;
+
+ if(mbUnpack(&mb, b->data, n))
+ return initxblock(b, "metadata", metablockgen, copyMetaBlock(mb));
+
+ return initxblock(b, "data", nil, nil);
+}
+
+int
+parseScore(uchar *score, char *buf, int n)
+{
+ int i, c;
+
+ memset(score, 0, VtScoreSize);
+
+ if(n < VtScoreSize*2)
+ return 0;
+ for(i=0; i<VtScoreSize*2; i++){
+ if(buf[i] >= '0' && buf[i] <= '9')
+ c = buf[i] - '0';
+ else if(buf[i] >= 'a' && buf[i] <= 'f')
+ c = buf[i] - 'a' + 10;
+ else if(buf[i] >= 'A' && buf[i] <= 'F')
+ c = buf[i] - 'A' + 10;
+ else{
+ return 0;
+ }
+
+ if((i & 1) == 0)
+ c <<= 4;
+
+ score[i>>1] |= c;
+ }
+ return 1;
+}
+
+int
+scoreFmt(Fmt *f)
+{
+ uchar *v;
+ int i;
+ u32int addr;
+
+ v = va_arg(f->args, uchar*);
+ if(v == nil){
+ fmtprint(f, "*");
+ }else if((addr = globalToLocal(v)) != NilBlock)
+ fmtprint(f, "0x%.8ux", addr);
+ else{
+ for(i = 0; i < VtScoreSize; i++)
+ fmtprint(f, "%2.2ux", v[i]);
+ }
+
+ return 0;
+}
+
+Atree*
+atreeinit(char *arg)
+{
+ Atree *a;
+ uchar score[VtScoreSize];
+
+ vtAttach();
+
+ fmtinstall('V', scoreFmt);
+ fmtinstall('R', vtErrFmt);
+
+ z = vtDial(nil, 1);
+ if(z == nil)
+ fprint(2, "warning: cannot dial venti: %R\n");
+ if(!vtConnect(z, 0)){
+ fprint(2, "warning: cannot connect to venti: %R\n");
+ z = nil;
+ }
+ a = mallocz(sizeof(Atree), 1);
+ if(strncmp(arg, "vac:", 4) == 0){
+ if(!parseScore(score, arg+4, strlen(arg+4))){
+ fprint(2, "cannot parse score\n");
+ return nil;
+ }
+ a->root = initxvacroot(score);
+ }else
+ a->root = initxcache(arg);
+ a->resizefd = -1;
+ return a;
+}
+
+/* --- tree.c */
+enum
+{
+ Nubwidth = 11,
+ Nubheight = 11,
+ Linewidth = Nubwidth*2+4,
+};
+
+uint
+drawtext(char *s, Image *m, Image *clipr, Point o)
+{
+ char *t, *nt, *e;
+ uint dy;
+
+ if(s == nil)
+ s = "???";
+
+ dy = 0;
+ for(t=s; t&&*t; t=nt){
+ if(nt = strchr(t, '\n')){
+ e = nt;
+ nt++;
+ }else
+ e = t+strlen(t);
+
+ _string(m, Pt(o.x, o.y+dy), display->black, ZP, display->defaultfont,
+ t, nil, e-t, clipr->clipr, nil, ZP, SoverD);
+ dy += display->defaultfont->height;
+ }
+ return dy;
+}
+
+void
+drawnub(Image *m, Image *clipr, Point o, Tnode *t)
+{
+ clipr = nil;
+
+ if(t->nkid == 0)
+ return;
+ if(t->nkid == -1 && t->expand == nil)
+ return;
+
+ o.y += (display->defaultfont->height-Nubheight)/2;
+ draw(m, rectaddpt(Rect(0,0,1,Nubheight), o), display->black, clipr, ZP);
+ draw(m, rectaddpt(Rect(0,0,Nubwidth,1), o), display->black, clipr, o);
+ draw(m, rectaddpt(Rect(Nubwidth-1,0,Nubwidth,Nubheight), o),
+ display->black, clipr, addpt(o, Pt(Nubwidth-1, 0)));
+ draw(m, rectaddpt(Rect(0, Nubheight-1, Nubwidth, Nubheight), o),
+ display->black, clipr, addpt(o, Pt(0, Nubheight-1)));
+
+ draw(m, rectaddpt(Rect(0, Nubheight/2, Nubwidth, Nubheight/2+1), o),
+ display->black, clipr, addpt(o, Pt(0, Nubheight/2)));
+ if(!t->expanded)
+ draw(m, rectaddpt(Rect(Nubwidth/2, 0, Nubwidth/2+1, Nubheight), o),
+ display->black, clipr, addpt(o, Pt(Nubwidth/2, 0)));
+
+}
+
+uint
+drawnode(Tnode *t, Image *m, Image *clipr, Point o)
+{
+ int i;
+ char *fs, *s;
+ uint dy;
+ Point oo;
+
+ if(t == nil)
+ return 0;
+
+ t->offset = o;
+
+ oo = Pt(o.x+Nubwidth+2, o.y);
+// if(t->draw)
+// dy = (*t->draw)(t, m, clipr, oo);
+// else{
+ fs = nil;
+ if(t->str)
+ s = t->str;
+ // else if(t->strfn)
+ // fs = s = (*t->strfn)(t);
+ else
+ s = "???";
+ dy = drawtext(s, m, clipr, oo);
+ free(fs);
+// }
+
+ if(t->expanded){
+ if(t->nkid == -1 && t->expand)
+ (*t->expand)(t);
+ oo = Pt(o.x+Nubwidth+(Linewidth-Nubwidth)/2, o.y+dy);
+ for(i=0; i<t->nkid; i++)
+ oo.y += drawnode(t->kid[i], m, clipr, oo);
+ dy = oo.y - o.y;
+ }
+ drawnub(m, clipr, o, t);
+ return dy;
+}
+
+void
+drawtree(Tree *t, Image *m, Rectangle r)
+{
+ Point p;
+
+ draw(m, r, display->white, nil, ZP);
+
+ replclipr(t->clipr, 1, r);
+ p = addpt(t->offset, r.min);
+ drawnode(t->root, m, t->clipr, p);
+}
+
+Tnode*
+findnode(Tnode *t, Point p)
+{
+ int i;
+ Tnode *tt;
+
+ if(ptinrect(p, rectaddpt(Rect(0,0,Nubwidth, Nubheight), t->offset)))
+ return t;
+ if(!t->expanded)
+ return nil;
+ for(i=0; i<t->nkid; i++)
+ if(tt = findnode(t->kid[i], p))
+ return tt;
+ return nil;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: vtree /dev/sdC0/fossil\n");
+ exits("usage");
+}
+
+Tree t;
+
+void
+eresized(int new)
+{
+ Rectangle r;
+ r = screen->r;
+ if(new && getwindow(display, Refnone) < 0)
+ fprint(2,"can't reattach to window");
+ drawtree(&t, screen, screen->r);
+}
+
+enum
+{
+ Left = 1<<0,
+ Middle = 1<<1,
+ Right = 1<<2,
+
+ MMenu = 2,
+};
+
+char *items[] = { "exit", 0 };
+enum { IExit, };
+
+Menu menu;
+
+void
+main(int argc, char **argv)
+{
+ int n;
+ char *dir;
+ Event e;
+ Point op, p;
+ Tnode *tn;
+ Mouse m;
+ int Eready;
+ Atree *fs;
+
+ ARGBEGIN{
+ case 'a':
+ showinactive = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ switch(argc){
+ default:
+ usage();
+ case 1:
+ dir = argv[0];
+ break;
+ }
+
+ fs = atreeinit(dir);
+ initdraw(0, "/lib/font/bit/lucidasans/unicode.8.font", "tree");
+ t.root = fs->root;
+ t.offset = ZP;
+ t.clipr = allocimage(display, Rect(0,0,1,1), GREY1, 1, DOpaque);
+
+ eresized(0);
+ flushimage(display, 1);
+
+ einit(Emouse);
+
+ menu.item = items;
+ menu.gen = 0;
+ menu.lasthit = 0;
+ if(fs->resizefd > 0){
+ Eready = 1<<3;
+ estart(Eready, fs->resizefd, 1);
+ }else
+ Eready = 0;
+
+ for(;;){
+ switch(n=eread(Emouse|Eready, &e)){
+ default:
+ if(Eready && n==Eready)
+ eresized(0);
+ break;
+ case Emouse:
+ m = e.mouse;
+ switch(m.buttons){
+ case Left:
+ op = t.offset;
+ p = m.xy;
+ do {
+ t.offset = addpt(t.offset, subpt(m.xy, p));
+ p = m.xy;
+ eresized(0);
+ m = emouse();
+ }while(m.buttons == Left);
+ if(m.buttons){
+ t.offset = op;
+ eresized(0);
+ }
+ break;
+ case Middle:
+ n = emenuhit(MMenu, &m, &menu);
+ if(n == -1)
+ break;
+ switch(n){
+ case IExit:
+ exits(nil);
+ }
+ break;
+ case Right:
+ do
+ m = emouse();
+ while(m.buttons == Right);
+ if(m.buttons)
+ break;
+ tn = findnode(t.root, m.xy);
+ if(tn){
+ tn->expanded = !tn->expanded;
+ eresized(0);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/cmd/fossil/walk.c b/src/cmd/fossil/walk.c
new file mode 100644
index 00000000..802af640
--- /dev/null
+++ b/src/cmd/fossil/walk.c
@@ -0,0 +1,65 @@
+/*
+ * Generic traversal routines.
+ */
+
+#include "stdinc.h"
+#include "dat.h"
+#include "fns.h"
+
+static uint
+etype(Entry *e)
+{
+ uint t;
+
+ if(e->flags&VtEntryDir)
+ t = BtDir;
+ else
+ t = BtData;
+ return t+e->depth;
+}
+
+void
+initWalk(WalkPtr *w, Block *b, uint size)
+{
+ memset(w, 0, sizeof *w);
+ switch(b->l.type){
+ case BtData:
+ return;
+
+ case BtDir:
+ w->data = b->data;
+ w->m = size / VtEntrySize;
+ w->isEntry = 1;
+ return;
+
+ default:
+ w->data = b->data;
+ w->m = size / VtScoreSize;
+ w->type = b->l.type;
+ w->tag = b->l.tag;
+ return;
+ }
+}
+
+int
+nextWalk(WalkPtr *w, uchar score[VtScoreSize], uchar *type, u32int *tag, Entry **e)
+{
+ if(w->n >= w->m)
+ return 0;
+
+ if(w->isEntry){
+ *e = &w->e;
+ entryUnpack(&w->e, w->data, w->n);
+ memmove(score, w->e.score, VtScoreSize);
+ *type = etype(&w->e);
+ *tag = w->e.tag;
+ }else{
+ *e = nil;
+ memmove(score, w->data+w->n*VtScoreSize, VtScoreSize);
+ *type = w->type-1;
+ *tag = w->tag;
+ }
+ w->n++;
+ return 1;
+}
+