summaryrefslogtreecommitdiff
path: root/liblogfs
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /liblogfs
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'liblogfs')
-rw-r--r--liblogfs/NOTICE25
-rw-r--r--liblogfs/boot.c496
-rw-r--r--liblogfs/clunk.c19
-rw-r--r--liblogfs/conv.c285
-rw-r--r--liblogfs/create.c82
-rw-r--r--liblogfs/dump.c71
-rw-r--r--liblogfs/error.c17
-rw-r--r--liblogfs/extentlist.c276
-rw-r--r--liblogfs/fidmap.c66
-rw-r--r--liblogfs/findfreeblock.c31
-rw-r--r--liblogfs/flush.c17
-rw-r--r--liblogfs/format.c109
-rw-r--r--liblogfs/gn.c26
-rw-r--r--liblogfs/group.c93
-rw-r--r--liblogfs/groupset.c91
-rw-r--r--liblogfs/is.c305
-rw-r--r--liblogfs/local.h333
-rw-r--r--liblogfs/log.c270
-rw-r--r--liblogfs/map.c136
-rw-r--r--liblogfs/mkfile45
-rw-r--r--liblogfs/open.c72
-rw-r--r--liblogfs/path.c48
-rw-r--r--liblogfs/perm.c25
-rw-r--r--liblogfs/read.c253
-rw-r--r--liblogfs/remove.c147
-rw-r--r--liblogfs/replace.c175
-rw-r--r--liblogfs/replay.c386
-rw-r--r--liblogfs/scan.c293
-rw-r--r--liblogfs/srv.c308
-rw-r--r--liblogfs/sweep.c272
-rw-r--r--liblogfs/tagname.c20
-rw-r--r--liblogfs/test.c13
-rw-r--r--liblogfs/ust.c65
-rw-r--r--liblogfs/walk.c108
-rw-r--r--liblogfs/write.c593
-rw-r--r--liblogfs/wstat.c245
36 files changed, 5816 insertions, 0 deletions
diff --git a/liblogfs/NOTICE b/liblogfs/NOTICE
new file mode 100644
index 00000000..3fbc79b8
--- /dev/null
+++ b/liblogfs/NOTICE
@@ -0,0 +1,25 @@
+This copyright NOTICE applies to all files in this directory and
+subdirectories, unless another copyright notice appears in a given
+file or subdirectory. If you take substantial code from this software to use in
+other programs, you must somehow include with it an appropriate
+copyright notice that includes the copyright notice and the other
+notices below. It is fine (and often tidier) to do that in a separate
+file such as NOTICE, LICENCE or COPYING.
+
+Copyright © 1995-1999 Lucent Technologies Inc.
+Portions Copyright © 1997-2000 Vita Nuova Limited
+Portions Copyright © 2000-2005 Vita Nuova Holdings Limited
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/liblogfs/boot.c b/liblogfs/boot.c
new file mode 100644
index 00000000..201f3307
--- /dev/null
+++ b/liblogfs/boot.c
@@ -0,0 +1,496 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+struct LogfsBoot {
+ LogfsLowLevel *ll;
+ long bootblocks;
+ long blocksize;
+ long size;
+ long *map;
+ int trace;
+ int printbad;
+// ulong bootpathmask;
+// int bootgenshift;
+};
+
+typedef struct LogfsBootPath LogfsBootPath;
+
+//#define LogfsBootGenBits 2
+//#define LogfsBootGenMask ((1 << LogfsBootGenBits) - 1)
+#define LogfsBootGenMask ((1 << L2BlockCopies) - 1)
+
+struct LogfsBootPath {
+ ulong path;
+ uchar gen;
+};
+
+#define LOGFSMKBOOTPATH(lb, p) mkdatapath((p)->path, (p)->gen)
+#define LOGFSSPLITBOOTPATHEX(bgs, bpm, p, v) ((p)->path = dataseqof(v), (p)->gen = copygenof(v))
+#define LOGFSSPLITBOOTPATH(lb, p, v) LOGFSSPLITBOOTPATHEX(0, 0, p, v)
+
+//#define LOGFSMKBOOTPATH(lb, p) (((p)->path & (lb)->bootpathmask) | (((p)->gen & LogfsBootGenMask) << (lb)->bootgenshift))
+//#define LOGFSSPLITBOOTPATHEX(bgs, bpm, p, v) ((p)->path = (v) & (bpm), (p)->gen = ((v) >> (bgs)) & LogfsBootGenMask)
+//#define LOGFSSPLITBOOTPATH(lb, p, v) LOGFSSPLITBOOTPATHEX((lb)->bootgenshift, (lb)->bootpathmask, p, v)
+
+extern LogfsBootPath logfsbooterasedpath;
+
+static char Ecorrupt[] = "filesystem corrupt";
+static char Enospc[] = "no free blocks";
+static char Eaddress[] = "address out of range";
+
+static char *
+logfsbootblockupdate(LogfsBoot *lb, void *buf, LogfsBootPath *path, uchar tag, ulong block)
+{
+ LogfsLowLevel *ll = lb->ll;
+ char *errmsg;
+ ulong packedpath;
+
+ if(lb->trace > 1)
+ print("logfsbootblockupdate: path 0x%.8lux(%d) tag %s block %lud\n",
+ path->path, path->gen, logfstagname(tag), block);
+
+ packedpath = LOGFSMKBOOTPATH(lb, path);
+ errmsg = (*ll->writeblock)(ll, buf, tag, packedpath, 1, &lb->bootblocks, block);
+
+ if(errmsg) {
+ /*
+ * ensure block never used again until file system reinitialised
+ * We have absolutely no idea what state it's in. This is most
+ * likely if someone turns off the power (or at least threatens
+ * the power supply), during a block update. This way the block
+ * is protected until the file system in reinitialised. An alternative
+ * would be check the file system after a power fail false alarm,
+ * and erase any Tworse blocks
+ */
+ (*ll->setblocktag)(ll, block, LogfsTworse);
+ return errmsg;
+ }
+
+ (*ll->setblocktag)(ll, block, tag);
+ (*ll->setblockpath)(ll, block, packedpath);
+
+ return nil;
+}
+
+char *
+logfsbootfettleblock(LogfsBoot *lb, long block, uchar tag, long path, int *markedbad)
+{
+ LogfsLowLevel *ll = lb->ll;
+ char *errmsg;
+ void *llsave;
+
+ errmsg = (*ll->eraseblock)(ll, block, &llsave, markedbad);
+ if(errmsg || (markedbad && *markedbad)) {
+ logfsfreemem(llsave);
+ return errmsg;
+ }
+ errmsg = (*ll->reformatblock)(ll, block, tag, path, 1, &lb->bootblocks, llsave, markedbad);
+ logfsfreemem(llsave);
+ return errmsg;
+}
+
+/*
+ * block transfer is the critical unit of update
+ * we are going to assume that page writes and block erases are atomic
+ * this can pretty much be assured by not starting a page write or block erase
+ * if the device feels it is in power fail
+ */
+
+static char *
+logfsbootblocktransfer(LogfsBoot *lb, void *buf, ulong oldblock, int markbad)
+{
+ LogfsLowLevel *ll = lb->ll;
+ long bestnewblock;
+ ulong oldpackedpath;
+ LogfsBootPath oldpath;
+ short oldtag;
+ char *errmsg;
+ int markedbad;
+
+ oldpackedpath = (*ll->getblockpath)(ll, oldblock);
+ oldtag = (*ll->getblocktag)(ll, oldblock);
+
+ LOGFSSPLITBOOTPATH(lb, &oldpath, oldpackedpath);
+
+ for(;;) {
+ LogfsBootPath newpath;
+
+ bestnewblock = logfsfindfreeblock(ll, markbad ? AllocReasonReplace : AllocReasonTransfer);
+ if(lb->trace > 0 && markbad)
+ print("logfsbootblocktransfer: block %lud is bad, copying to %ld\n",
+ oldblock, bestnewblock);
+ if(lb->trace > 1 && !markbad)
+ print("logfsbootblocktransfer: copying block %lud to %ld\n",
+ oldblock, bestnewblock);
+ if(bestnewblock == -1)
+ return Enospc;
+ newpath = oldpath;
+// newpath.gen = (newpath.gen + 1) & LogfsBootGenMask;
+ newpath.gen = copygensucc(newpath.gen);
+ errmsg = logfsbootblockupdate(lb, buf, &newpath, oldtag, bestnewblock);
+ if(errmsg == nil)
+ break;
+ if(strcmp(errmsg, Eio) != 0)
+ return errmsg;
+ (*ll->markblockbad)(ll, bestnewblock);
+ }
+
+#ifdef LOGFSTEST
+ if(logfstest.partialupdate) {
+ print("skipping erase\n");
+ logfstest.partialupdate = 0;
+ return nil;
+ }
+ if(logfstest.updatenoerase) {
+ print("skipping erase\n");
+ logfstest.updatenoerase = 0;
+ return nil;
+ }
+#endif
+
+ if(oldtag == LogfsTboot)
+ lb->map[oldpath.path] = bestnewblock;
+
+ return logfsbootfettleblock(lb, oldblock, LogfsTnone, ~0, &markedbad);
+}
+
+static char *
+logfsbootblockread(LogfsBoot *lb, void *buf, long block, LogfsLowLevelReadResult *blocke)
+{
+ LogfsLowLevel *ll = lb->ll;
+ char *errmsg;
+
+ *blocke = LogfsLowLevelReadResultOk;
+ errmsg = (*ll->readblock)(ll, buf, block, blocke);
+ if(errmsg)
+ return errmsg;
+
+ if(*blocke != LogfsLowLevelReadResultOk) {
+ char *errmsg = logfsbootblocktransfer(lb, buf, block, 1);
+ if(errmsg)
+ return errmsg;
+ }
+
+ if(*blocke == LogfsLowLevelReadResultHardError)
+ return Eio;
+
+ return nil;
+}
+
+char *
+logfsbootread(LogfsBoot *lb, void *buf, long n, ulong offset)
+{
+ int i;
+
+ if(lb->trace > 0)
+ print("logfsbootread(0x%.8lux, 0x%lx, 0x%lux)\n", (ulong)buf, n, offset);
+ if(offset % lb->blocksize || n % lb->blocksize)
+ return Eio;
+ n /= lb->blocksize;
+ offset /= lb->blocksize;
+ if(offset + n > lb->bootblocks)
+ return Eio;
+ for(i = 0; i < n; i++) {
+ LogfsLowLevelReadResult result;
+ char *errmsg = logfsbootblockread(lb, buf, lb->map[offset + i], &result);
+ if(errmsg)
+ return errmsg;
+ buf = (uchar *)buf + lb->blocksize;
+ }
+ return nil;
+}
+
+static char *
+logfsbootblockreplace(LogfsBoot *lb, void *buf, ulong logicalblock)
+{
+ uchar *oldblockbuf;
+ ulong oldblock;
+ char *errmsg;
+ LogfsLowLevelReadResult result;
+
+ oldblock = lb->map[logicalblock];
+ oldblockbuf = logfsrealloc(nil, lb->blocksize);
+ if(oldblockbuf == nil)
+ return Enomem;
+
+ errmsg = logfsbootblockread(lb, oldblockbuf, oldblock, &result);
+ if(errmsg == nil && memcmp(oldblockbuf, buf, lb->blocksize) != 0)
+ errmsg = logfsbootblocktransfer(lb, buf, oldblock, 0);
+
+ logfsfreemem(oldblockbuf);
+ return errmsg;
+}
+
+char *
+logfsbootwrite(LogfsBoot *lb, void *buf, long n, ulong offset)
+{
+ int i;
+
+ if(lb->trace > 0)
+ print("logfsbootwrite(0x%.8lux, 0x%lux, 0x%lux)\n", (ulong)buf, n, offset);
+ /*
+ * don't even get started on a write if the power has failed
+ */
+ if(offset % lb->blocksize || n % lb->blocksize)
+ return Eio;
+ n /= lb->blocksize;
+ offset /= lb->blocksize;
+ if(offset + n > lb->bootblocks)
+ return Eio;
+ for(i = 0; i < n; i++) {
+ logfsbootblockreplace(lb, buf, offset + i);
+ buf = (uchar *)buf + lb->blocksize;
+ }
+ return nil;
+}
+
+char *
+logfsbootio(LogfsBoot *lb, void *buf, long n, ulong offset, int write)
+{
+ return (write ? logfsbootwrite : logfsbootread)(lb, buf, n, offset);
+}
+
+static char *
+eraseandformatblock(LogfsBoot *lb, long block, int trace)
+{
+ char *errmsg;
+ int markedbad;
+
+ errmsg = logfsbootfettleblock(lb, block, LogfsTnone, ~0, &markedbad);
+ if(errmsg)
+ return errmsg;
+ if(markedbad && trace > 1)
+ print("erase/format failed - marked bad\n");
+ return nil;
+}
+
+char *
+logfsbootopen(LogfsLowLevel *ll, long base, long limit, int trace, int printbad, LogfsBoot **lbp)
+{
+ long *reversemap;
+ ulong blocksize;
+ ulong blocks;
+ long i;
+ long bootblockmax;
+ LogfsBoot *lb = nil;
+ ulong baseblock;
+ char *errmsg;
+// int bootgenshift = ll->pathbits- LogfsBootGenBits;
+// ulong bootpathmask = (1 << (ll->pathbits - LogfsBootGenBits)) - 1;
+ long expectedbootblocks;
+
+ errmsg = (*ll->open)(ll, base, limit, trace, 1, &expectedbootblocks);
+ if(errmsg)
+ return errmsg;
+
+ bootblockmax = -1;
+ blocks = ll->blocks;
+ baseblock = (*ll->getbaseblock)(ll);
+ blocksize = (*ll->getblocksize)(ll);
+
+ for(i = 0; i < blocks; i++) {
+ if((*ll->getblocktag)(ll, i) == LogfsTboot) {
+ long path = (*ll->getblockpath)(ll, i);
+ LogfsBootPath lp;
+ LOGFSSPLITBOOTPATHEX(bootgenshift, bootpathmask, &lp, path);
+ if((long)lp.path > bootblockmax)
+ bootblockmax = lp.path;
+ }
+ }
+ if(bootblockmax + 1 >= blocks) {
+ if(printbad)
+ print("logfsbootinit: bootblockmax %ld exceeds number of blocks\n", bootblockmax);
+ return Ecorrupt;
+ }
+ if(bootblockmax < 0) {
+ if(printbad)
+ print("logfsbootopen: no boot area\n");
+ return Ecorrupt;
+ }
+ if(bootblockmax + 1 != expectedbootblocks) {
+ if(printbad)
+ print("logfsbootopen: wrong number of bootblocks (found %lud, expected %lud)\n",
+ bootblockmax + 1, expectedbootblocks);
+ }
+
+ reversemap = logfsrealloc(nil, sizeof(*reversemap) * (bootblockmax + 1));
+ if(reversemap == nil)
+ return Enomem;
+
+ for(i = 0; i <= bootblockmax; i++)
+ reversemap[i] = -1;
+ for(i = 0; i < blocks; i++) {
+ LogfsBootPath ipath;
+ long rm;
+ ulong ip;
+
+ if((*ll->getblocktag)(ll, i) != LogfsTboot)
+ continue;
+ ip = (*ll->getblockpath)(ll, i);
+ LOGFSSPLITBOOTPATHEX(bootgenshift, bootpathmask, &ipath, ip);
+ rm = reversemap[ipath.path];
+ if(rm != -1) {
+ if(printbad)
+ print("logfsbootopen: blockaddr 0x%.8lux: path %ld(%d): duplicate\n",
+ blocksize * (baseblock + i), ipath.path, ipath.gen);
+ /*
+ * resolve collision
+ * if this one is partial, then erase it
+ * if the existing one is partial, erase that
+ * if both valid, give up
+ */
+ if((*ll->getblockpartialformatstatus)(ll, i)) {
+ errmsg = eraseandformatblock(lb, i, trace);
+ if(errmsg)
+ goto error;
+ }
+ else if((*ll->getblockpartialformatstatus)(ll, rm)) {
+ errmsg = eraseandformatblock(lb, rm, trace);
+ if(errmsg)
+ goto error;
+ reversemap[ipath.path] = i;
+ }
+ else {
+ int d;
+ ulong rmp;
+ LogfsBootPath rmpath;
+ rmp = (*ll->getblockpath)(ll, rm);
+ LOGFSSPLITBOOTPATHEX(bootgenshift, bootpathmask, &rmpath, rmp);
+ d = (ipath.gen - rmpath.gen) & LogfsBootGenMask;
+ if(printbad)
+ print("i.gen = %d rm.gen = %d d = %d\n", ipath.gen, rmpath.gen, d);
+ if(d == 1) {
+ /* i is newer;
+ * keep the OLDER one because
+ * we might have had a write failure on the last page, but lost the
+ * power before being able to mark the first page bad
+ * if, worse, the auxiliary area's tag is the same for first and last page,
+ * this looks like a successfully written page. so, we cannot believe the
+ * data in the newer block unless we erased the old one, and then of
+ * course, we wouldn't have a duplicate.
+ */
+ errmsg = eraseandformatblock(lb, i, trace);
+ if(errmsg)
+ goto error;
+ }
+ else if(d == LogfsBootGenMask) {
+ /* rm is newer */
+ errmsg = eraseandformatblock(lb, rm, trace);
+ if(errmsg)
+ goto error;
+ reversemap[ipath.path] = i;
+ }
+ else {
+ errmsg = Ecorrupt;
+ goto error;
+ }
+ }
+ }
+ else
+ reversemap[ipath.path] = i;
+ }
+ /*
+ * final checks; not partial blocks, and no holes
+ */
+ for(i = 0; i <= bootblockmax; i++) {
+ long rm;
+ rm = reversemap[i];
+ if(rm == -1) {
+ if(printbad)
+ print("logfsbootopen: missing boot block %ld\n", i);
+ errmsg = Ecorrupt;
+ goto error;
+ }
+ if((*ll->getblockpartialformatstatus)(ll, rm)) {
+ if(printbad)
+ print("logfsbootopen: boot block %ld partially written\n", rm);
+ errmsg = Ecorrupt;
+ goto error;
+ }
+ }
+ /* the reverse map is consistent */
+ lb = logfsrealloc(nil, sizeof(*lb));
+ if(lb == nil) {
+ errmsg = Enomem;
+ goto error;
+ }
+
+ lb->blocksize = blocksize;
+ lb->bootblocks = bootblockmax + 1;
+ lb->map = reversemap;
+ lb->trace = trace;
+ lb->printbad = printbad;
+ lb->ll = ll;
+ lb->size = blocksize * lb->bootblocks;
+// lb->bootgenshift = bootgenshift;
+// lb->bootpathmask = bootpathmask;
+ *lbp = lb;
+ if(trace)
+ print("logfsbootopen: success\n");
+ return nil;
+
+error:
+ logfsfreemem(reversemap);
+ logfsfreemem(lb);
+ return errmsg;
+}
+
+void
+logfsbootfree(LogfsBoot *lb)
+{
+ if(lb) {
+ logfsfreemem(lb->map);
+ logfsfreemem(lb);
+ }
+}
+
+char *
+logfsbootmap(LogfsBoot *lb, ulong laddress, ulong *lblockp, int *lboffsetp, int *lpagep, int *lpageoffsetp, ulong *pblockp, ulong *paddressp)
+{
+ LogfsLowLevel *ll = lb->ll;
+ ulong lblock;
+ ulong lboffset, lpageoffset, lpage;
+ ulong pblock;
+ ulong paddress;
+
+ lblock = laddress / lb->blocksize;
+ if(lblock >= lb->bootblocks)
+ return Eaddress;
+ lboffset = laddress % lb->blocksize;
+ pblock = lb->map[lblock];
+ paddress = (*ll->calcrawaddress)(ll, pblock, lboffset);
+ lpage = lboffset >> ll->l2pagesize;
+ lpageoffset = lboffset & ((1 << ll->l2pagesize) - 1);
+ if(lblockp)
+ *lblockp = lblock;
+ if(lboffsetp)
+ *lboffsetp = lboffset;
+ if(lpagep)
+ *lpagep = lpage;
+ if(lpageoffsetp)
+ *lpageoffsetp = lpageoffset;
+ if(pblockp)
+ *pblockp = pblock;
+ if(paddressp)
+ *paddressp = paddress;
+ return nil;
+}
+
+long
+logfsbootgetiosize(LogfsBoot *lb)
+{
+ return lb->blocksize;
+}
+
+long
+logfsbootgetsize(LogfsBoot *lb)
+{
+ return lb->size;
+}
+
+void
+logfsboottrace(LogfsBoot *lb, int level)
+{
+ lb->trace = level;
+}
diff --git a/liblogfs/clunk.c b/liblogfs/clunk.c
new file mode 100644
index 00000000..329f3324
--- /dev/null
+++ b/liblogfs/clunk.c
@@ -0,0 +1,19 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+char *
+logfsserverclunk(LogfsServer *server, u32int fid)
+{
+ Fid *f;
+ if(server->trace > 1)
+ print("logfsserverclunk(%ud)\n", fid);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode >= 0 && (f->openmode & ORCLOSE) != 0)
+ return logfsserverremove(server, fid);
+ logfsfidmapclunk(server->fidmap, fid);
+ return nil;
+}
diff --git a/liblogfs/conv.c b/liblogfs/conv.c
new file mode 100644
index 00000000..fbc48130
--- /dev/null
+++ b/liblogfs/conv.c
@@ -0,0 +1,285 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+static void
+pn(uchar **pp, char *v)
+{
+ uchar *p = *pp;
+ int l;
+ l = v ? strlen(v) : 0;
+ PBIT16(p, l); p += BIT16SZ;
+ memmove(p, v, l);
+ p += l;
+ *pp = p;
+}
+
+static uint
+sn(char *p)
+{
+ if(p == nil)
+ return BIT16SZ;
+ return strlen(p) + BIT16SZ;
+}
+
+uint
+logfsconvM2S(uchar *ap, uint nap, LogMessage *f)
+{
+ uchar *p = ap;
+ uchar *ep = p + nap;
+ uchar *mep;
+ uint size;
+//print("conv(%d)\n", nap);
+ if(p + 1 > ep)
+ return 0;
+ f->type = *p++;
+//print("type %c\n", f->type);
+ switch(f->type) {
+ case LogfsLogTstart:
+ case LogfsLogTcreate:
+ case LogfsLogTtrunc:
+ case LogfsLogTremove:
+ case LogfsLogTwrite:
+ case LogfsLogTwstat:
+ break;
+ case LogfsLogTend:
+ return 1;
+ default:
+ return 0;
+ }
+ if(p + BIT16SZ > ep)
+ return 0;
+ size = GBIT16(p); p += BIT16SZ;
+//print("size %ud\n", size);
+ if(p + size > ep)
+ return 0;
+ mep = p + size;
+ if(p + BIT32SZ > mep)
+ return 0;
+ f->path = GBIT32(p); p += BIT32SZ;
+ switch(f->type) {
+ case LogfsLogTstart:
+ /* 's' size[2] path[4] nerase[4] */
+ if(p + BIT32SZ > ep)
+ return 0;
+ f->u.start.nerase = GBIT32(p); p += BIT32SZ;
+ break;
+ case LogfsLogTcreate:
+ /* 'c' size[2] path[4] perm[4] newpath[4] mtime[4] cvers[4] name[s] uid[s] gid[s] */
+ if(p + 4 * BIT32SZ > mep)
+ return 0;
+ f->u.create.perm = GBIT32(p); p+= BIT32SZ;
+ f->u.create.newpath = GBIT32(p); p+= BIT32SZ;
+ f->u.create.mtime = GBIT32(p); p+= BIT32SZ;
+ f->u.create.cvers = GBIT32(p); p+= BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.create.name)
+ || !logfsgn(&p, mep, &f->u.create.uid)
+ || !logfsgn(&p, mep, &f->u.create.gid))
+ return 0;
+ break;
+ case LogfsLogTremove:
+ /* 'r' size[2] path[4] mtime[4] muid[s] */
+ if(p + BIT32SZ > mep)
+ return 0;
+ f->u.remove.mtime = GBIT32(p); p += BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.remove.muid))
+ return 0;
+ break;
+ case LogfsLogTtrunc:
+ /* 't' size[2] path[4] mtime[4] cvers[4] muid[s] */
+ if(p + 2 * BIT32SZ > mep)
+ return 0;
+ f->u.trunc.mtime = GBIT32(p); p += BIT32SZ;
+ f->u.trunc.cvers = GBIT32(p); p += BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.trunc.muid))
+ return 0;
+ break;
+ case LogfsLogTwrite:
+ /* 'w' size[2] path[4] offset[4] count[2] mtime[4] cvers[4] muid[s] flashaddr[4] [data[n]] */
+ if(p + BIT32SZ + BIT16SZ + 2 * BIT32SZ > mep)
+ return 0;
+ f->u.write.offset = GBIT32(p); p += BIT32SZ;
+ f->u.write.count = GBIT16(p); p += BIT16SZ;
+ f->u.write.mtime = GBIT32(p); p += BIT32SZ;
+ f->u.write.cvers = GBIT32(p); p += BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.write.muid))
+ return 0;
+ if(p + BIT32SZ > mep)
+ return 0;
+ f->u.write.flashaddr = GBIT32(p); p += BIT32SZ;
+ if(f->u.write.flashaddr & LogAddr) {
+ if(p + f->u.write.count > mep)
+ return 0;
+ f->u.write.data = p;
+ p += f->u.write.count;
+ }
+ else
+ f->u.write.data = nil;
+ break;
+ case LogfsLogTwstat:
+ /* 'W' size[2] path[4] name[s] perm[4] uid[s] gid[s] mtime[4] muid[s] or */
+ /* 'W' size[2] path[4] name[s] perm[4] gid[s] mtime[4] muid[s] */
+ if(!logfsgn(&p, mep, &f->u.wstat.name))
+ return 0;
+ if(p + BIT32SZ > mep)
+ return 0;
+ f->u.wstat.perm = GBIT32(p); p += BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.wstat.uid))
+ return 0;
+ if(!logfsgn(&p, mep, &f->u.wstat.gid))
+ return 0;
+ if(p + BIT32SZ > mep)
+ return 0;
+ f->u.wstat.mtime = GBIT32(p); p += BIT32SZ;
+ if(!logfsgn(&p, mep, &f->u.wstat.muid))
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+ if(p != mep)
+ return 0;
+ return p - ap;
+}
+
+uint
+logfssizeS2M(LogMessage *m)
+{
+ switch(m->type) {
+ case LogfsLogTend:
+ return 1;
+ case LogfsLogTstart:
+ return 11;
+ case LogfsLogTcreate:
+ /* 'c' size[2] path[4] perm[4] newpath[4] mtime[4] cvers[4] name[s] uid[s] gid[s] */
+ return 1 + BIT16SZ + 5 * BIT32SZ
+ + sn(m->u.create.name) + sn(m->u.create.uid) + sn(m->u.create.gid);
+ case LogfsLogTremove:
+ /* 'r' size[2] path[4] mtime[4] muid[s] */
+ return 1 + BIT16SZ + 2 * BIT32SZ + sn(m->u.remove.muid);
+ case LogfsLogTtrunc:
+ /* 't' size[2] path[4] mtime[4] cvers[4] muid[s] */
+ return 1 + BIT16SZ + 3 * BIT32SZ + sn(m->u.trunc.muid);
+ case LogfsLogTwrite:
+ /* 'w' size[2] path[4] offset[4] count[2] mtime[4] cvers[4] muid[s] flashaddr[4] [data[n]] */
+ return 1 + BIT16SZ + 2 * BIT32SZ + BIT16SZ + 2 * BIT32SZ + sn(m->u.write.muid)
+ + BIT32SZ + (m->u.write.data ? m->u.write.count : 0);
+ case LogfsLogTwstat:
+ /* 'W' size[2] path[4] name[s] perm[4] uid[s] gid[s] mtime[4] muid[s] */
+ /* 'W' size[2] path[4] name[s] perm[4] gid[s] mtime[4] muid[s] */
+ return 1 + BIT16SZ + BIT32SZ + sn(m->u.wstat.name) + BIT32SZ
+ + sn(m->u.wstat.uid)
+ + sn(m->u.wstat.gid) + BIT32SZ + sn(m->u.wstat.muid);
+ default:
+ return 0;
+ }
+}
+
+uint
+logfsconvS2M(LogMessage *s, uchar *ap, uint nap)
+{
+ uint size;
+ uchar *p;
+ size = logfssizeS2M(s);
+ if(size == 0 || size > nap)
+ return 0;
+ p = ap;
+ *p++ = s->type;
+ if(s->type == LogfsLogTend)
+ return 1;
+ size -= 1 + BIT16SZ;
+ PBIT16(p, size); p += BIT16SZ;
+ PBIT32(p, s->path); p += BIT32SZ;
+ switch(s->type) {
+ case LogfsLogTstart:
+ PBIT32(p, s->u.start.nerase); p += BIT32SZ;
+ break;
+ case LogfsLogTcreate:
+ /* 'c' size[2] path[4] perm[4] newpath[4] mtime[4] cvers[4] name[s] uid[s] gid[s] */
+ PBIT32(p, s->u.create.perm); p += BIT32SZ;
+ PBIT32(p, s->u.create.newpath); p += BIT32SZ;
+ PBIT32(p, s->u.create.mtime); p += BIT32SZ;
+ PBIT32(p, s->u.create.cvers); p += BIT32SZ;
+ pn(&p, s->u.create.name);
+ pn(&p, s->u.create.uid);
+ pn(&p, s->u.create.gid);
+ break;
+ case LogfsLogTremove:
+ /* 'r' size[2] path[4] mtime[4] muid[s] */
+ PBIT32(p, s->u.remove.mtime); p += BIT32SZ;
+ pn(&p, s->u.remove.muid);
+ break;
+ case LogfsLogTtrunc:
+ /* 't' size[2] path[4] mtime[4] cvers[4] muid[s] */
+ PBIT32(p, s->u.trunc.mtime); p += BIT32SZ;
+ PBIT32(p, s->u.trunc.cvers); p += BIT32SZ;
+ pn(&p, s->u.trunc.muid);
+ break;
+ case LogfsLogTwrite:
+ /* 'w' size[2] path[4] offset[4] count[2] mtime[4] cvers[4] muid[s] flashaddr[4] [data[n]] */
+ PBIT32(p, s->u.write.offset); p += BIT32SZ;
+ PBIT16(p, s->u.write.count); p += BIT16SZ;
+ PBIT32(p, s->u.write.mtime); p += BIT32SZ;
+ PBIT32(p, s->u.write.cvers); p += BIT32SZ;
+ pn(&p, s->u.write.muid);
+ PBIT32(p, s->u.write.flashaddr); p += BIT32SZ;
+ if(s->u.write.data) {
+ memmove(p, s->u.write.data, s->u.write.count);
+ p += s->u.write.count;
+ }
+ break;
+ case LogfsLogTwstat:
+ /* 'W' size[2] path[4] name[s] perm[4] uid[s] gid[s] mtime[4] muid[s] */
+ /* 'W' size[2] path[4] name[s] perm[4] gid[s] mtime[4] muid[s] */
+ pn(&p, s->u.wstat.name);
+ PBIT32(p, s->u.wstat.perm); p += BIT32SZ;
+ pn(&p, s->u.wstat.uid);
+ pn(&p, s->u.wstat.gid);
+ PBIT32(p, s->u.wstat.mtime); p+= BIT32SZ;
+ pn(&p, s->u.wstat.muid);
+ break;
+ default:
+ return 0;
+ }
+ return p - ap;
+}
+
+void
+logfsdumpS(LogMessage *m)
+{
+ switch(m->type) {
+ case LogfsLogTend:
+ print("LogfsLogTend()");
+ break;
+ case LogfsLogTstart:
+ print("LogfsLogTstart(path=%ud, nerase=%ud)", m->path, m->u.start.nerase);
+ break;
+ case LogfsLogTcreate:
+ print("LogfsLogTcreate(path=%ud, perm=0%uo, newpath=%ud, mtime=%ud, cvers=%ud, name=%s, uid=%s, gid=%s)",
+ m->path, m->u.create.perm, m->u.create.newpath, m->u.create.mtime, m->u.create.cvers,
+ m->u.create.name, m->u.create.uid, m->u.create.gid);
+ break;
+ case LogfsLogTremove:
+ print("LogfsLogTremove(path=%ud, mtime=%ud, muid=%s)",
+ m->path, m->u.remove.mtime, m->u.remove.muid);
+ break;
+ case LogfsLogTtrunc:
+ print("LogfsLogTtrunc(path=%ud, mtime=%ud, cvers=%ud, muid=%s)",
+ m->path, m->u.trunc.mtime, m->u.trunc.cvers, m->u.trunc.muid);
+ break;
+ case LogfsLogTwrite:
+ print("LogfsLogTwrite(path=%ud, offset=%ud, count=%ud, mtime=%ud, cvers=%ud, muid=%s, flashaddr=0x%.8ux)",
+ m->path, m->u.write.offset, m->u.write.count, m->u.write.mtime, m->u.write.cvers, m->u.write.muid,
+ m->u.write.flashaddr);
+ break;
+ case LogfsLogTwstat:
+ print("LogfsLogTwstat(path=%ud, name=%s, perm=0%uo, uid=%s, gid=%s, mtime=%ud, muid=%s)",
+ m->path, m->u.wstat.name, m->u.wstat.perm, m->u.wstat.uid, m->u.wstat.gid,
+ m->u.wstat.mtime, m->u.wstat.muid);
+ break;
+ default:
+ print("LogfsLogTother(%c)", m->type);
+ break;
+ }
+}
diff --git a/liblogfs/create.c b/liblogfs/create.c
new file mode 100644
index 00000000..be78fdda
--- /dev/null
+++ b/liblogfs/create.c
@@ -0,0 +1,82 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+char *
+logfsservercreate(LogfsServer *server, u32int fid, char *name, u32int perm, uchar mode, Qid *qid)
+{
+ Fid *f;
+ char *uid;
+ ulong newpath;
+ char *errmsg;
+ Entry *e, *xe, *pe;
+ Path *pp;
+ LogMessage s;
+
+ if(server->trace > 1)
+ print("logfsservercreate(%ud, %s, 0%uo, %.2ux)\n", fid, name, perm, mode);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode >= 0)
+ return logfsefidopen;
+ pe = f->entry;
+ if((pe->qid.type & QTDIR) == 0)
+ return Enotdir;
+ if((perm & DMDIR) != 0 && ((mode & OTRUNC) != 0 || (mode & 3) != OREAD))
+ return Eperm;
+ if(!logfsuserpermcheck(server, pe, f, DMWRITE))
+ return Eperm;
+ /*
+ * illegal names
+ */
+ if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ return Eperm;
+ for(xe = pe->u.dir.list; xe; xe = xe->next)
+ if(strcmp(xe->name, name) == 0)
+ return Eexist;
+ newpath = ++server->path;
+ while(logfspathmapfindentry(server->pathmap, newpath))
+ newpath++; /* shouldn't happen */
+ uid = logfsisfindidfromname(server->is, f->uname);
+ errmsg = logfsentrynew(server, 1, newpath,
+ pe, name, uid, f->entry->gid, logfsnow(), uid, perm, 0, 0, &e);
+ if(errmsg)
+ return errmsg;
+ errmsg = logfspathmapnewentry(server->pathmap, newpath, e, &pp);
+ /* pp is guaranteed to be non-null */
+ if(errmsg) {
+ logfsfreemem(e);
+ return errmsg;
+ }
+ s.type = LogfsLogTcreate;
+ s.path = e->parent->qid.path;
+ s.u.create.perm = e->perm;
+ s.u.create.newpath = e->qid.path;
+ s.u.create.mtime = e->mtime;
+ /* TODO - check with forsyth whether cvers is needed in dirs */
+ s.u.create.cvers = (e->qid.type & QTDIR) ? 0 : e->u.file.cvers;
+ s.u.create.name = e->name;
+ s.u.create.uid = e->uid;
+ s.u.create.gid = e->gid;
+ errmsg = logfslog(server, 1, &s);
+ if(errmsg) {
+ logfsfreemem(e);
+ logfspathmapdeleteentry(server->pathmap, newpath);
+ return errmsg;
+ }
+ server->path = newpath;
+ e->inuse++;
+ e->qid.vers++;
+ e->next = pe->u.dir.list;
+ pe->u.dir.list = e;
+ f->openmode = mode;
+ /*
+ * TODO why does forsyth increment inuse for dir? - we're moving the fid onto the new file
+ * so a decrement seems better
+ */
+ logfsentryclunk(pe);
+ f->entry = e;
+ *qid = e->qid;
+ return nil;
+}
diff --git a/liblogfs/dump.c b/liblogfs/dump.c
new file mode 100644
index 00000000..90e2d1f4
--- /dev/null
+++ b/liblogfs/dump.c
@@ -0,0 +1,71 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+typedef struct WalkState {
+ u32int *flashaddrp;
+ u32int *lengthp;
+ int i;
+ int nth;
+} WalkState;
+
+static int
+walk(void *magic, Extent *e, int hole)
+{
+ WalkState *s = magic;
+ USED(hole);
+ if(s->i == s->nth) {
+ *s->flashaddrp = e->flashaddr;
+ *s->lengthp = e->max - e->min;
+ return 0;
+ }
+ s->i++;
+ return 1;
+}
+
+char *
+logfsserverreadpathextent(LogfsServer *server, u32int path, int nth, u32int *flashaddrp, u32int *lengthp,
+ long *blockp, int *pagep, int *offsetp)
+{
+ Entry *e;
+ WalkState s;
+ long index;
+ e = logfspathmapfinde(server->pathmap, path);
+ if(e == nil)
+ return logfseunknownpath;
+ if(e->perm & DMDIR)
+ return Eisdir;
+ s.flashaddrp = flashaddrp;
+ s.lengthp = lengthp;
+ s.i = 0;
+ s.nth = nth;
+ *lengthp = 0;
+ logfsextentlistwalk(e->u.file.extent, walk, &s);
+ if(*lengthp) {
+ logfsflashaddr2spo(server, *flashaddrp, &index, pagep, offsetp);
+ if(*flashaddrp & LogAddr)
+ if(index >= server->activelog->unsweptblockindex)
+ if(index <= server->activelog->curblockindex)
+ *blockp = server->activelog->blockmap[index];
+ else
+ *blockp = -1;
+ else if(server->sweptlog)
+ if(index <= server->sweptlog->curblockindex)
+ *blockp = server->sweptlog->blockmap[index];
+ else
+ *blockp = -1;
+ else
+ *blockp = -1;
+ else if(index < server->ndatablocks)
+ *blockp = server->datablock[index].block;
+ else
+ *blockp = -1;
+ }
+ else {
+ *blockp = 0;
+ *pagep = 0;
+ *offsetp = 0;
+ }
+ return nil;
+}
diff --git a/liblogfs/error.c b/liblogfs/error.c
new file mode 100644
index 00000000..742b5599
--- /dev/null
+++ b/liblogfs/error.c
@@ -0,0 +1,17 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+char logfsebadfid[] = "fid not in use";
+char logfsefidnotopen[] = "fid is not open for I/O";
+char logfsefidopen[] = "fid is open for I/O";
+char logfsenotadir[] = "fid not a dir";
+char logfsefidinuse[] = "fid in use";
+char logfseopen[] = "fid not open";
+char logfseaccess[] = "fid open in wrong mode";
+char logfselogfull[] = "log filled";
+char logfselogmsgtoobig[] = "message too big for log";
+char logfseinternal[] = "internal error";
+char logfsenotempty[] = "directory not empty";
+char logfsefullreplacing[] = "out of space trying to replace block";
+char logfseunknownpath[] = "unknown path";
diff --git a/liblogfs/extentlist.c b/liblogfs/extentlist.c
new file mode 100644
index 00000000..d045605e
--- /dev/null
+++ b/liblogfs/extentlist.c
@@ -0,0 +1,276 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+typedef struct ExtentNode {
+ Extent e;
+ struct ExtentNode *next;
+} ExtentNode;
+
+struct ExtentList {
+ ExtentNode *head;
+};
+
+char *
+logfsextentlistnew(ExtentList **ep)
+{
+ ExtentList *l;
+ l = logfsrealloc(nil, sizeof(*l));
+ if(l == nil)
+ return Enomem;
+ *ep = l;
+ return nil;
+}
+
+void
+logfsextentlistreset(ExtentList *l)
+{
+ ExtentNode *n;
+ n = l->head;
+ while(n) {
+ ExtentNode *next;
+ next = n->next;
+ logfsfreemem(n);
+ n = next;
+ }
+ l->head = nil;
+}
+
+void
+logfsextentlistfree(ExtentList **lp)
+{
+ ExtentList *l;
+ if(lp == nil || (l = *lp) == nil)
+ return;
+ logfsextentlistreset(l);
+ logfsfreemem(l);
+ *lp = nil;
+}
+
+char *
+logfsextentlistinsert(ExtentList *l, Extent *add, Extent **new)
+{
+ ExtentNode *old, *prev;
+ ExtentNode *saved = nil;
+
+ if(l == nil)
+ return "nil extentlist";
+
+ /* initially a's extents are non-empty, disjoint and sorted */
+ old = l->head;
+ prev = nil;
+ while(old) {
+ ExtentNode *next = old->next;
+ if(add->max <= old->e.min)
+ break;
+ if(old->e.min < add->max && add->min < old->e.max) { /* they intersect */
+ if(add->min <= old->e.min) {
+ /* add overlaps front of old */
+ if(add->max < old->e.max) {
+ int trimmed;
+ /* but doesn't overlap end */
+ /* retain tail of old */
+ if(saved == nil)
+ saved = logfsrealloc(nil, sizeof(*saved));
+ if(saved == nil)
+ goto nomem;
+ trimmed = add->max - old->e.min;
+ old->e.min += trimmed;
+ old->e.flashaddr += trimmed;
+ /* can't touch any further extents, so... */
+ break;
+ }
+ /* add.min ≤ old.min < old.max ≤ add.max ⇒ add completely covers old */
+ /* delete old */
+ if(prev)
+ prev->next = next;
+ else
+ l->head = next;
+ /* stash the deleted extent, so we can reuse it */
+ if(saved == nil)
+ saved = old;
+ else
+ logfsfreemem(old);
+ old = next;
+ continue;
+ }
+ else {
+ /* add.min > old.min, so overlaps end of old or splits it */
+ if(add->max < old->e.max) { /* add inside old, splitting it */
+ ExtentNode *frag;
+ /*
+ * will need at most two add extents, so ensure
+ * enough store exists before changing data structures
+ */
+ if(saved == nil)
+ saved = logfsrealloc(nil, sizeof(*saved));
+ frag = logfsrealloc(nil, sizeof(*frag));
+ if(saved == nil || frag == nil)
+ goto nomem;
+ frag->next = next;
+ old->next = frag;
+ frag->e.min = add->max;
+ frag->e.max = old->e.max;
+ frag->e.flashaddr = old->e.flashaddr + (add->max - old->e.min);
+ old->e.max = add->min;
+ prev = old;
+ break;
+ }
+ else {
+ /*
+ * will need at most one add extent, so create one
+ * now before changing data structures
+ */
+ if(saved == nil)
+ saved = logfsrealloc(nil, sizeof(*saved));
+ if(saved == nil)
+ goto nomem;
+ old->e.max = add->min; /* retain start of old */
+ }
+ /* old.max <= add.max ⇒ add covers tail of old */
+ }
+ }
+ prev = old;
+ old = next;
+ }
+ /*
+ * if here, and saved == nil, then there was no overlap
+ */
+ if(saved == nil)
+ saved = logfsrealloc(nil, sizeof(*saved));
+ if(saved == nil) {
+ nomem:
+ return Enomem;
+ }
+ saved->e = *add;
+ if(prev) {
+ saved->next = prev->next;
+ prev->next = saved;
+ }
+ else {
+ saved->next = l->head;
+ l->head = saved;
+ }
+ if(new)
+ *new = &saved->e;
+ return nil;
+}
+
+Extent *
+logfsextentlistmatch(ExtentList *l, Extent *e)
+{
+ ExtentNode *m;
+ u32int flashmax;
+
+ if(l == nil)
+ return nil;
+
+ flashmax = e->flashaddr + (e->max - e->min);
+
+ for(m = l->head; m; m = m->next) {
+ u32int l = m->e.max - m->e.min;
+ if(e->min < m->e.max && m->e.min < e->max /* they intersect */
+ && m->e.flashaddr < flashmax && e->flashaddr < m->e.flashaddr + l) /* the store intersects */
+ return &(m->e);
+ }
+ return nil;
+}
+
+int
+logfsextentlistmatchall(ExtentList *l, int (*func)(void *magic, Extent *), void *magic, Extent *e)
+{
+ ExtentNode *m;
+ u32int flashmax;
+
+ if(l == nil)
+ return 1;
+
+ flashmax = e->flashaddr + (e->max - e->min);
+
+ for(m = l->head; m; m = m->next) {
+ u32int l;
+ if(m->e.min >= e->max)
+ return 1;
+ l = m->e.max - m->e.min;
+ if(e->min < m->e.max /* they intersect */
+ && m->e.flashaddr < flashmax && e->flashaddr < m->e.flashaddr + l) {
+ /* the store intersects */
+ int rv = (*func)(magic, &(m->e));
+ if(rv <= 0)
+ return rv;
+ }
+ }
+ return 1;
+}
+
+int
+logfsextentlistwalk(ExtentList *l, int (*func)(void *magic, Extent *, int hole), void *magic)
+{
+ ExtentNode *n;
+ u32int last = 0;
+ if(l == nil)
+ return 1;
+ for(n = l->head; n; n = n->next) {
+ int rv;
+ if(last < n->e.min) {
+ Extent hole;
+ hole.min = last;
+ hole.max = n->e.min;
+ hole.flashaddr = ~0;
+ rv = (*func)(magic, &hole, 1);
+ if(rv <= 0)
+ return rv;
+ }
+ rv = (*func)(magic, &n->e, 0);
+ if(rv <= 0)
+ return rv;
+ last = n->e.max;
+ }
+ return 1;
+}
+
+int
+logfsextentlistwalkrange(ExtentList *l, int (*func)(void *magic, u32int baseoffset, u32int limitoffset, Extent *, u32int extentoffset), void *magic, u32int base, u32int limit)
+{
+ ExtentNode *n;
+ u32int last = 0;
+ if(l == nil)
+ return 1;
+ for(n = l->head; n; n = n->next) {
+ Extent hole;
+ Extent *e;
+ if(last < n->e.min) {
+ hole.min = last;
+ hole.max = n->e.min;
+ e = &hole;
+ }
+ else {
+ again:
+ e = &n->e;
+ }
+ if(e->min >= limit)
+ return 1;
+//print("walkrange %ud .. %ud\n", e->min, e->max);
+ if(e->max > base) {
+ ulong rangebase, rangelimit, extentoffset;
+ int rv;
+ rangebase = e->min;
+ if(rangebase < base) {
+ extentoffset = base - rangebase;
+ rangebase += extentoffset;
+ }
+ else
+ extentoffset = 0;
+ rangelimit = e->max;
+ if(rangelimit > limit)
+ rangelimit = limit;
+ rv = (*func)(magic, rangebase - base, rangelimit - base, e == &hole ? nil : e, extentoffset);
+ if(rv <= 0)
+ return rv;
+ }
+ last = e->max;
+ if(e == &hole)
+ goto again;
+ }
+ return 1;
+}
diff --git a/liblogfs/fidmap.c b/liblogfs/fidmap.c
new file mode 100644
index 00000000..9973c312
--- /dev/null
+++ b/liblogfs/fidmap.c
@@ -0,0 +1,66 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+enum {
+ FIDMOD = 127
+};
+
+int
+logfshashulong(void *v, int size)
+{
+ return (ulong)v % size;
+}
+
+static int
+compare(Fid *f, ulong fid)
+{
+//print("fidcompare(%ld, %ld)\n", f->fid, fid);
+ return f->fid == fid;
+}
+
+static int
+allocsize(void *key)
+{
+ USED(key);
+ return sizeof(Fid);
+}
+
+void
+fidfree(Fid *f)
+{
+ logfsdrsfree(&f->drs);
+}
+
+char *
+logfsfidmapnew(FidMap **fidmapp)
+{
+ return logfsmapnew(FIDMOD, logfshashulong, (int (*)(void *, void *))compare, allocsize, (void (*)(void *))fidfree, fidmapp);
+}
+
+int
+logfsfidmapclunk(FidMap *m, ulong fid)
+{
+ Fid *f = logfsfidmapfindentry(m, fid);
+ if(f) {
+ logfsentryclunk(f->entry);
+ logfsmapdeleteentry(m, (void *)fid);
+ return 1;
+ }
+ return 0;
+}
+
+char *
+logfsfidmapnewentry(FidMap *m, ulong fid, Fid **fidmapp)
+{
+ char *errmsg;
+ errmsg = logfsmapnewentry(m, (void *)fid, fidmapp);
+ if(errmsg)
+ return errmsg;
+ if(*fidmapp == nil)
+ return nil;
+ (*fidmapp)->fid = fid;
+ (*fidmapp)->openmode = -1;
+ return nil;
+}
+
diff --git a/liblogfs/findfreeblock.c b/liblogfs/findfreeblock.c
new file mode 100644
index 00000000..04d3be7e
--- /dev/null
+++ b/liblogfs/findfreeblock.c
@@ -0,0 +1,31 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+long
+logfsfindfreeblock(LogfsLowLevel *ll, AllocReason reason)
+{
+ long b;
+ long total;
+ b = (*ll->findfreeblock)(ll, &total);
+ if(b < 0)
+ return b;
+ switch(reason) {
+ case AllocReasonReplace:
+ break;
+ case AllocReasonTransfer:
+ if(total <= Replacements)
+ return -1;
+ break;
+ case AllocReasonLogExtend:
+ if(total <= Replacements + Transfers)
+ return -1;
+ break;
+ case AllocReasonDataExtend:
+ if(total <= Replacements + Transfers + LogSlack)
+ return -1;
+ break;
+ }
+//print("allocated free block %ld\n", b);
+ return b;
+}
diff --git a/liblogfs/flush.c b/liblogfs/flush.c
new file mode 100644
index 00000000..cf5dfb3c
--- /dev/null
+++ b/liblogfs/flush.c
@@ -0,0 +1,17 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+char *
+logfsserverflush(LogfsServer *server)
+{
+ char *errmsg = logfslogsegmentflush(server, 1);
+ if(errmsg == nil)
+ errmsg = logfslogsegmentflush(server, 0);
+ if(errmsg == nil)
+ errmsg = (*server->ll->sync)(server->ll);
+ if(server->trace > 1)
+ print("logfsserverflush\n");
+ return errmsg;
+}
diff --git a/liblogfs/format.c b/liblogfs/format.c
new file mode 100644
index 00000000..f270c84f
--- /dev/null
+++ b/liblogfs/format.c
@@ -0,0 +1,109 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "mp.h"
+#include "libsec.h"
+
+char *
+logfsformat(LogfsLowLevel *ll, long base, long limit, long bootsize, int trace)
+{
+ long bootblocksdone, logblocksdone;
+ long u;
+ long baseblock, limitblock, bootblocks, sizeinblocks;
+ int magicfound;
+ void *llsave;
+
+ if(trace > 1)
+ print("logfsformat: base %ld limit %ld bootsize %lud\n", base, limit, bootsize);
+
+ if((*ll->getopenstatus)(ll))
+ return Eperm;
+
+ if(!(*ll->calcformat)(ll, base, limit, bootsize, &baseblock, &limitblock, &bootblocks))
+ return Ebadarg;
+
+ if(trace > 0)
+ print("logfsformat: baseblock %ld limitblock %ld bootblocks %ld\n", baseblock, limitblock, bootblocks);
+
+ bootblocksdone = 0;
+ logblocksdone = 0;
+ /*
+ * we need to create some fs blocks, and some boot blocks
+ * the number of boot blocks is fixed; the number of fs blocks
+ * occupies the remainder
+ * the layout is randomised to:
+ * 1) test the software
+ * 2) spread wear around if a lot of format commands are issued by
+ * the bootloader
+ */
+
+ sizeinblocks = limitblock - baseblock;
+
+ for(u = 0; u < sizeinblocks; u++) {
+ int r;
+ uchar tag;
+ long path;
+ LogfsLowLevelReadResult e;
+ char *errmsg;
+ int markedbad;
+
+ if(trace > 1)
+ print("block %lud:", u);
+ llsave = nil;
+ errmsg = (*ll->getblockstatus)(ll, u + baseblock, &magicfound, &llsave, &e);
+ if(errmsg)
+ return errmsg;
+ if(e == LogfsLowLevelReadResultBad) {
+ if(trace > 1)
+ print(" marked bad\n");
+ continue;
+ }
+ errmsg = (*ll->eraseblock)(ll, u + baseblock, nil, &markedbad);
+ if(errmsg)
+ return errmsg;
+ if(markedbad) {
+ if(trace > 1)
+ print(" marked bad\n");
+ continue;
+ }
+ if(e != LogfsLowLevelReadResultHardError && magicfound) {
+ if(trace > 1)
+ print(" previously formatted");
+ }
+ r = rand() % (sizeinblocks - u);
+ if(bootblocksdone < bootblocks && r < (bootblocks - bootblocksdone)) {
+ tag = LogfsTboot;
+ path = mkdatapath(bootblocksdone, 0);
+ }
+ else {
+ tag = LogfsTnone;
+ path = ~0;
+ }
+ if(trace > 1)
+ print(" tag %s path %ld", logfstagname(tag), path);
+ errmsg = (*ll->formatblock)(ll, u + baseblock, tag, path, baseblock, sizeinblocks, 1, &bootblocks, llsave, &markedbad);
+ logfsfreemem(llsave);
+ if(errmsg)
+ return errmsg;
+ if(markedbad) {
+ if(trace > 1)
+ print(" marked bad\n");
+ continue;
+ }
+ switch(tag) {
+ case LogfsTboot:
+ bootblocksdone++;
+ break;
+ case LogfsTnone:
+ logblocksdone++;
+ break;
+ }
+ if(trace > 1)
+ print("\n");
+ }
+ if(bootblocksdone < bootblocks)
+ return "not enough capacity left for boot";
+ if(trace > 0)
+ print("log blocks %lud\n", logblocksdone);
+ return nil;
+}
diff --git a/liblogfs/gn.c b/liblogfs/gn.c
new file mode 100644
index 00000000..c5d02977
--- /dev/null
+++ b/liblogfs/gn.c
@@ -0,0 +1,26 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+int
+logfsgn(uchar **pp, uchar *mep, char **v)
+{
+ uchar *p = *pp;
+ int l;
+ if(p + BIT16SZ > mep)
+ return 0;
+ l = GBIT16(p); p += BIT16SZ;
+ if(p + l > mep)
+ return 0;
+ *pp = p + l;
+ if(l == 0) {
+ *v = 0;
+ return 1;
+ }
+ *v = (char *)(p - 1);
+ memmove(p - 1, p, l);
+ p[l - 1] = 0;
+ return 1;
+}
+
diff --git a/liblogfs/group.c b/liblogfs/group.c
new file mode 100644
index 00000000..7e187e63
--- /dev/null
+++ b/liblogfs/group.c
@@ -0,0 +1,93 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+enum {
+ GROUPMOD = 127
+};
+
+static int
+groupcompare(Group *g, char *uid)
+{
+ return g->uid == uid;
+}
+
+static int
+unamecompare(Uname *u, char *uname)
+{
+ return u->uname == uname;
+}
+
+static int
+groupallocsize(void *key)
+{
+ USED(key);
+ return sizeof(Group);
+}
+
+static int
+unameallocsize(void *key)
+{
+ USED(key);
+ return sizeof(Uname);
+}
+
+char *
+logfsgroupmapnew(GroupMap **groupmapp, UnameMap **unamemapp)
+{
+ char *errmsg;
+ errmsg = logfsmapnew(GROUPMOD, logfshashulong, (int (*)(void *, void *))groupcompare,
+ groupallocsize, nil, groupmapp);
+ if(errmsg)
+ return errmsg;
+ errmsg = logfsmapnew(GROUPMOD, logfshashulong, (int (*)(void *, void *))unamecompare,
+ unameallocsize, nil, unamemapp);
+ if(errmsg)
+ logfsmapfree(groupmapp);
+ return errmsg;
+}
+
+char *
+logfsgroupmapnewentry(GroupMap *gm, UnameMap *um, char *uid, char *uname, Group **groupp, Uname **unamep)
+{
+ char *errmsg;
+ errmsg = logfsmapnewentry(gm, uid, groupp);
+ if(errmsg)
+ return errmsg;
+ errmsg = logfsgroupsetnew(&(*groupp)->members);
+ if(errmsg) {
+ logfsmapdeleteentry(gm, uid);
+ return errmsg;
+ }
+ errmsg = logfsmapnewentry(um, uname, unamep);
+ if(errmsg) {
+ logfsgroupsetfree(&(*groupp)->members);
+ logfsmapdeleteentry(gm, uid);
+ return errmsg;
+ }
+ (*groupp)->uid = uid;
+ (*groupp)->uname = uname;
+ (*unamep)->uname = uname;
+ (*unamep)->g = *groupp;
+ return nil;
+}
+
+char *
+logfsgroupmapfinduname(GroupMap *m, char *uid)
+{
+ Group *g;
+ g = logfsgroupmapfindentry(m, uid);
+ if(g)
+ return g->uname;
+ return nil;
+}
+
+char *
+logfsunamemapfinduid(UnameMap *m, char *uname)
+{
+ Uname *u;
+ u = logfsunamemapfindentry(m, uname);
+ if(u)
+ return u->g->uid;
+ return nil;
+}
diff --git a/liblogfs/groupset.c b/liblogfs/groupset.c
new file mode 100644
index 00000000..91b18c9e
--- /dev/null
+++ b/liblogfs/groupset.c
@@ -0,0 +1,91 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+struct GroupSet {
+ int maxentries;
+ int nentries;
+ Group **entry;
+};
+
+char *
+logfsgroupsetnew(GroupSet **gsp)
+{
+ GroupSet *gs = logfsrealloc(nil, sizeof(*gs));
+ if(gs == nil)
+ return Enomem;
+ gs->entry = logfsrealloc(nil, sizeof(Group *));
+ if(gs->entry == nil) {
+ logfsfreemem(gs);
+ return Enomem;
+ }
+ gs->maxentries = 1; /* most groups have one member */
+ gs->nentries = 0;
+ *gsp = gs;
+ return nil;
+}
+
+void
+logfsgroupsetfree(GroupSet **gsp)
+{
+ GroupSet *gs = *gsp;
+ if(gs) {
+ logfsfreemem(gs->entry);
+ logfsfreemem(gs);
+ *gsp = nil;
+ }
+}
+
+int
+logfsgroupsetadd(GroupSet *gs, Group *g)
+{
+ int x;
+ for(x = 0; x < gs->nentries; x++)
+ if(gs->entry[x] == g)
+ return 1;
+ if(gs->nentries >= gs->maxentries) {
+ Group **ne = logfsrealloc(gs->entry, sizeof(Group *) + (gs->maxentries * 2));
+ if(ne)
+ return 0;
+ gs->entry = ne;
+ gs->maxentries *= 2;
+ }
+ gs->entry[gs->nentries++] = g;
+ return 1;
+}
+
+int
+logfsgroupsetremove(GroupSet *gs, Group *g)
+{
+ int x;
+ for(x = 0; x < gs->nentries; x++)
+ if(gs->entry[x] == g)
+ break;
+ if(x == gs->nentries)
+ return 0;
+ gs->nentries--;
+ memmove(&gs->entry[x], &gs->entry[x + 1], sizeof(Group *) * (gs->nentries - x));
+ return 1;
+}
+
+int
+logfsgroupsetwalk(GroupSet *gs, LOGFSGROUPSETWALKFN *func, void *magic)
+{
+ int x;
+ for(x = 0; x < gs->nentries; x++) {
+ int rv = (*func)(magic, gs->entry[x]);
+ if(rv <= 0)
+ return rv;
+ }
+ return 1;
+}
+
+int
+logfsgroupsetismember(GroupSet *gs, Group *g)
+{
+ int x;
+ for(x = 0; x < gs->nentries; x++)
+ if(gs->entry[x] == g)
+ return 1;
+ return 0;
+}
diff --git a/liblogfs/is.c b/liblogfs/is.c
new file mode 100644
index 00000000..dfd6f95f
--- /dev/null
+++ b/liblogfs/is.c
@@ -0,0 +1,305 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+char *logfsisgroupnonename = "none";
+
+char *
+logfsisnew(LogfsIdentityStore **isp)
+{
+ LogfsIdentityStore *is;
+ char *errmsg;
+
+ is = logfsrealloc(nil, sizeof(*is));
+ if(is == nil) {
+ memerror:
+ logfsisfree(&is);
+ return Enomem;
+ }
+ errmsg = logfsustnew(&is->ids);
+ if(errmsg)
+ goto memerror;
+ errmsg = logfsgroupmapnew(&is->groupmap, &is->unamemap);
+ if(errmsg)
+ goto memerror;
+ logfsisgroupnonename = logfsustadd(is->ids, logfsisgroupnonename);
+ *isp = is;
+ return nil;
+}
+
+void
+logfsisfree(LogfsIdentityStore **isp)
+{
+ LogfsIdentityStore *is = *isp;
+ if(is) {
+ logfsustfree(&is->ids);
+ logfsgroupmapfree(&is->groupmap);
+ logfsunamemapfree(&is->unamemap);
+ logfsfreemem(is);
+ *isp = nil;
+ }
+}
+
+char *
+logfsisgroupcreate(LogfsIdentityStore *is, char *groupname, char *groupid)
+{
+ Group *group;
+ Uname *uname;
+
+ if(strcmp(groupname, logfsisgroupnonename) == 0 || groupname[0] == '(')
+ return "group name reserved";
+ groupname = logfsisustadd(is, groupname);
+ groupid = logfsisustadd(is, groupid);
+ if(groupname == nil || groupid == nil)
+ return Enomem;
+ return logfsgroupmapnewentry(is->groupmap, is->unamemap, groupid, groupname, &group, &uname);
+}
+
+static Group *
+findgroupfromuname(LogfsIdentityStore *is, char *groupname)
+{
+ Uname *u = logfsunamemapfindentry(is->unamemap, groupname);
+ if(u == nil)
+ return nil;
+ return u->g;
+}
+
+char *
+logfsisgrouprename(LogfsIdentityStore *is, char *oldgroupname, char *newgroupname)
+{
+ Group *og, *ng;
+ oldgroupname = logfsisustadd(is, oldgroupname);
+ if(oldgroupname == nil)
+ return Enomem;
+ og =findgroupfromuname(is, oldgroupname);
+ if(og == nil)
+ return Enonexist;
+ newgroupname = logfsisustadd(is, newgroupname);
+ if(newgroupname == nil)
+ return Enomem;
+ ng = findgroupfromuname(is, newgroupname);
+ if(ng != nil)
+ return Eexist;
+ og->uname = newgroupname;
+ return nil;
+}
+
+char *
+logfsisgroupsetleader(LogfsIdentityStore *is, char *groupname, char *leadername)
+{
+ Group *g, *lg;
+ groupname = logfsisustadd(is, groupname);
+ if(groupname == nil)
+ return Enomem;
+ g = findgroupfromuname(is, groupname);
+ if(g == nil)
+ return Enonexist;
+ if(leadername && leadername[0]) {
+ leadername = logfsisustadd(is, leadername);
+ if(leadername == nil)
+ return Enomem;
+ lg = findgroupfromuname(is, leadername);
+ if(lg == nil)
+ return Enonexist;
+ if(!logfsgroupsetismember(g->members, lg))
+ return "not a member of the group";
+ g->leader = lg;
+ }
+ else
+ g->leader = nil;
+ return nil;
+}
+
+char *
+logfsisgroupaddmember(LogfsIdentityStore *is, char *groupname, char *membername)
+{
+ Group *g, *mg;
+ groupname = logfsisustadd(is, groupname);
+ if(groupname == nil)
+ return Enomem;
+ g =findgroupfromuname(is, groupname);
+ if(g == nil)
+ return Enonexist;
+ membername = logfsisustadd(is, membername);
+ if(membername == nil)
+ return Enomem;
+ mg = findgroupfromuname(is, membername);
+ if(mg == nil)
+ return Enonexist;
+ if(!logfsgroupsetadd(g->members, mg))
+ return Enomem;
+ return nil;
+}
+
+char *
+logfsisgroupremovemember(LogfsIdentityStore *is, char *groupname, char *nonmembername)
+{
+ Group *g, *nonmg;
+ groupname = logfsisustadd(is, groupname);
+ if(groupname == nil)
+ return Enomem;
+ g =findgroupfromuname(is, groupname);
+ if(g == nil)
+ return Enonexist;
+ nonmembername = logfsisustadd(is, nonmembername);
+ if(nonmembername == nil)
+ return Enomem;
+ nonmg = findgroupfromuname(is, nonmembername);
+ if(nonmg == nil)
+ return Enonexist;
+ if(!logfsgroupsetremove(g->members, nonmg))
+ return Enonexist;
+ if(g->leader == nonmg)
+ g->leader = nil;
+ return nil;
+}
+
+typedef struct DS {
+ char *printbuf;
+ long printbufsize;
+ void *buf;
+ ulong offset;
+ long n;
+ ulong printoffset;
+ int printn;
+ int comma;
+} DS;
+
+static int
+printmember(void *magic, Group *member)
+{
+ DS *ds = magic;
+ if(ds->comma) {
+ if(ds->printn < ds->printbufsize)
+ ds->printbuf[ds->printn++] = ',';
+ }
+ else
+ ds->comma = 1;
+ ds->printn += snprint(ds->printbuf + ds->printn, ds->printbufsize - ds->printn, "%s", member->uname);
+ return 1;
+}
+
+static int
+printgroup(void *magic, Group *g)
+{
+ DS *ds = magic;
+ ds->printn = snprint(ds->printbuf, ds->printbufsize, "%s:%s:%s:",
+ g->uid, g->uname, g->leader ? g->leader->uname : "");
+ /* do members */
+ ds->comma = 0;
+ logfsgroupsetwalk(g->members, printmember, ds);
+ if(ds->printn < ds->printbufsize)
+ ds->printbuf[ds->printn++] = '\n';
+ /*
+ * copy the appropriate part of the buffer
+ */
+ if(ds->printoffset < ds->offset + ds->n && ds->printoffset + ds->printn > ds->offset) {
+ char *printbuf = ds->printbuf;
+ uchar *buf = ds->buf;
+ long trim = ds->offset - ds->printoffset;
+ if(trim >= 0) {
+ printbuf += trim;
+ ds->printn -= trim;
+ }
+ else
+ buf -= trim;
+ if(ds->printoffset + ds->printn > ds->offset + ds->n)
+ ds->printn = ds->offset + ds->n - ds->printoffset;
+ memcpy(buf, printbuf, ds->printn);
+ }
+ /*
+ * advance print position
+ */
+ ds->printoffset += ds->printn;
+ /*
+ * stop if exceeding the buffer
+ */
+ if(ds->printoffset >= ds->offset + ds->n)
+ return 0;
+ return 1;
+}
+
+char *
+logfsisusersread(LogfsIdentityStore *is, void *buf, long n, ulong offset, long *nr)
+{
+ DS ds;
+ ds.buf = buf;
+ ds.n = n;
+ ds.printoffset = 0;
+ ds.offset = offset;
+ ds.printbufsize = 1024;
+ ds.printbuf = logfsrealloc(nil, ds.printbufsize);
+ if(ds.printbuf == nil)
+ return Enomem;
+ logfsmapwalk(is->groupmap, (LOGFSMAPWALKFN *)printgroup, &ds);
+ *nr = ds.printoffset - ds.offset;
+ logfsfreemem(ds.printbuf);
+ return nil;
+}
+
+int
+logfsisgroupunameismember(LogfsIdentityStore *is, Group *g, char *uname)
+{
+ Group *ug;
+ if(g == nil)
+ return 0;
+ if(g->uname == uname)
+ return 1;
+ ug = logfsisfindgroupfromname(is, uname);
+ if(ug == nil)
+ return 0;
+ return logfsgroupsetismember(g->members, ug);
+}
+
+int
+logfsisgroupuidismember(LogfsIdentityStore *is, Group *g, char *uid)
+{
+ Group *ug;
+ if(g == nil)
+ return 0;
+ if(g->uid == uid)
+ return 1;
+ ug = logfsisfindgroupfromid(is, uid);
+ if(ug == nil)
+ return 0;
+ return logfsgroupsetismember(g->members, ug);
+}
+
+int
+logfsisgroupuidisleader(LogfsIdentityStore *is, Group *g, char *id)
+{
+ if(g->leader)
+ return g->leader->uid == id;
+ return logfsisgroupuidismember(is, g, id);
+}
+
+Group *
+logfsisfindgroupfromname(LogfsIdentityStore *is, char *name)
+{
+ Uname *u;
+ u = logfsunamemapfindentry(is->unamemap, name);
+ if(u == nil)
+ return nil;
+ return u->g;
+}
+
+char *
+logfsisfindidfromname(LogfsIdentityStore *is, char *name)
+{
+ char *id;
+ id = logfsunamemapfinduid(is->unamemap, name);
+ if(id == nil)
+ return logfsisgroupnonename;
+ return id;
+}
+
+char *
+logfsisfindnamefromid(LogfsIdentityStore *is, char *id)
+{
+ Group *g;
+ g = logfsgroupmapfindentry(is->groupmap, id);
+ if(g == nil)
+ return nil;
+ return g->uname;
+}
diff --git a/liblogfs/local.h b/liblogfs/local.h
new file mode 100644
index 00000000..d614c062
--- /dev/null
+++ b/liblogfs/local.h
@@ -0,0 +1,333 @@
+
+enum {
+ L2LogSweeps = 2,
+ L2BlockCopies = 2,
+ LogDataLimit = 128,
+ LogAddr = (1 << 31),
+ Replacements = 2, /* how much free space must be available for replacements */
+ Transfers = 2, /* how much additional space must be available for transfers */
+ LogSlack = 1, /* how much additional space must be available for data allocation */
+};
+
+typedef struct Extent {
+ u32int min, max;
+ u32int flashaddr; /* encode block index, page number, and offset within page to min */
+} Extent;
+
+typedef struct ExtentList ExtentList;
+
+char *logfsextentlistnew(ExtentList **l);
+void logfsextentlistfree(ExtentList **l);
+char *logfsextentlistinsert(ExtentList *l, Extent *add, Extent **new);
+int logfsextentlistwalk(ExtentList *l, int (*func)(void *magic, Extent *e, int hole),void *magic);
+Extent *logfsextentlistmatch(ExtentList *l, Extent *e);
+int logfsextentlistwalkrange(ExtentList *l,
+ int (*func)(void *magic, u32int baseoffset, u32int limitoffset, Extent *, u32int extentoffset),
+ void *magic, u32int base, u32int limit);
+int logfsextentlistmatchall(ExtentList *l, int (*func)(void *magic, Extent *), void *magic, Extent *e);
+void logfsextentlistreset(ExtentList *l);
+
+typedef struct Entry {
+ int inuse;
+ int deadandgone; /* removed */
+ Qid qid;
+ struct Entry *parent;
+ char *name;
+ char *uid;
+ char *gid;
+ ulong mtime;
+ char *muid;
+ u32int perm;
+ struct Entry *next;
+ struct {
+ struct {
+ ulong cvers;
+ ulong length;
+ ExtentList *extent;
+ } file;
+ struct {
+ struct Entry *list;
+ } dir;
+ } u;
+} Entry;
+
+char *logfsentrynew(LogfsServer *server, int inuse, u32int path, Entry *parent,
+ char *name, char *uid, char *gid,
+ u32int mtime, char *muid, u32int perm, ulong cvers, ulong length, Entry **ep);
+void logfsentryclunk(Entry *e);
+
+typedef struct DirReadState DirReadState;
+void logfsdrsfree(DirReadState **drsp);
+
+typedef struct Fid {
+ ulong fid;
+ int openmode;
+ Entry *entry;
+ char *uname;
+ DirReadState *drs;
+} Fid;
+
+typedef struct Map Map;
+typedef int LOGFSMAPWALKFN(void *magic, void *entry);
+char *logfsmapnew(int size, int (*hash)(void *key, int size), int (*compare)(void *entry, void *key), int (*allocsize)(void *key), void (*free)(void *), Map **mapp);
+void logfsmapfree(Map **mp);
+char *logfsmapnewentry(Map *m, void *key, void **entryp);
+void *logfsmapfindentry(Map *m, void *key);
+int logfsmapdeleteentry(Map *m, void *key);
+int logfsmapwalk(Map *m, LOGFSMAPWALKFN *func, void *magic);
+
+typedef struct Map FidMap;
+
+char *logfsfidmapnew(FidMap **fidmapmapp);
+#define logfsfidmapfree(mp) logfsmapfree(mp)
+char *logfsfidmapnewentry(FidMap *m, ulong fid, Fid **fidmapp);
+#define logfsfidmapfindentry(m, fid) logfsmapfindentry(m, (void *)fid)
+int logfsfidmapclunk(FidMap *m, ulong fid);
+
+struct Logfs {
+ int trace;
+};
+
+typedef struct Map Ust;
+char *logfsustnew(Ust **ustp);
+#define logfsustfree(m) logfsmapfree(m)
+char *logfsustadd(Ust *m, char *s);
+
+typedef struct GroupSet GroupSet;
+
+typedef struct Group Group;
+typedef struct Map GroupMap;
+typedef struct Uname Uname;
+typedef struct Map UnameMap;
+
+struct Group {
+ char *uid;
+ char *uname;
+ Group *leader;
+ GroupSet *members;
+};
+
+struct Uname {
+ char *uname;
+ Group *g;
+};
+
+struct LogfsIdentityStore {
+ Ust *ids;
+ GroupMap *groupmap;
+ UnameMap *unamemap;
+};
+
+char *logfsgroupmapnew(GroupMap **groupmapp, UnameMap **unamemapp);
+#define logfsgroupmapfree(mp) logfsmapfree(mp)
+#define logfsunamemapfree(mp) logfsmapfree(mp)
+char *logfsgroupmapnewentry(GroupMap *gm, UnameMap *um, char *uid, char *uname, Group **groupp, Uname **unamep);
+#define logfsgroupmapdeleteentry(m, uid) logfsmapdeleteentry(m, (void *)uid)
+#define logfsgroupmapfindentry(m, uid) logfsmapfindentry(m, uid)
+#define logfsunamemapfindentry(m, uname) logfsmapfindentry(m, uname)
+char *logfsgroupmapfinduname(GroupMap *m, char *uid);
+char *logfsunamemapfinduid(UnameMap *m, char *uid);
+#define logfsunamemapdeleteentry(m, uname) logfsmapdeleteentry(m, (void *)uname)
+
+typedef int LOGFSGROUPSETWALKFN(void *magic, Group *g);
+char *logfsgroupsetnew(GroupSet **sp);
+void logfsgroupsetfree(GroupSet **sp);
+int logfsgroupsetadd(GroupSet *s, Group *g);
+int logfsgroupsetremove(GroupSet *s, Group *g);
+int logfsgroupsetwalk(GroupSet *s, LOGFSGROUPSETWALKFN *func, void *magic);
+int logfsgroupsetismember(GroupSet *gs, Group *g);
+char *logfsisfindidfromname(LogfsIdentityStore *is, char *name);
+char *logfsisfindnamefromid(LogfsIdentityStore *is, char *id);
+#define logfsisfindgroupfromid(is, id) logfsgroupmapfindentry((is)->groupmap, id)
+Group *logfsisfindgroupfromname(LogfsIdentityStore *is, char *uname);
+#define logfsisustadd(is, s) logfsustadd((is)->ids, s)
+int logfsisgroupunameismember(LogfsIdentityStore *is, Group *g, char *uname);
+int logfsisgroupuidismember(LogfsIdentityStore *is, Group *g, char *uid);
+int logfsisgroupuidisleader(LogfsIdentityStore *is, Group *g, char *uid);
+extern char *logfsisgroupnonename;
+
+typedef struct LogMessage {
+ uchar type;
+ u32int path;
+ union {
+ struct {
+ u32int nerase;
+ } start;
+ struct {
+ u32int perm;
+ u32int newpath;
+ u32int mtime;
+ u32int cvers;
+ char *name;
+ char *uid;
+ char *gid;
+ } create;
+ struct {
+ u32int mtime;
+ char *muid;
+ } remove;
+ struct {
+ u32int mtime;
+ u32int cvers;
+ char *muid;
+ } trunc;
+ struct {
+ u32int offset;
+ u32int count;
+ u32int mtime;
+ u32int cvers;
+ char *muid;
+ u32int flashaddr;
+ uchar *data;
+ } write;
+ struct {
+ char *name;
+ u32int perm;
+ char *uid;
+ char *gid;
+ u32int mtime;
+ char *muid;
+ } wstat;
+ } u;
+} LogMessage;
+
+uint logfsconvM2S(uchar *ap, uint nap, LogMessage *f);
+uint logfssizeS2M(LogMessage *f);
+uint logfsconvS2M(LogMessage *f, uchar *ap, uint nap);
+void logfsdumpS(LogMessage *s);
+
+typedef struct LogSegment LogSegment;
+
+struct LogSegment {
+ int gen; /* generation number of this log */
+ int dirty; /* written to since last sweep */
+ long curblockindex; /* index of block being filled, or -1 */
+ long unsweptblockindex; /* next block to sweep */
+ int curpage; /* page within block */
+ uchar *pagebuf; /* page buffer */
+ int nbytes; /* bytes used in page buffer */
+ long blockmap[1]; /* there are ll->blocks of these */
+};
+
+char *logfslogsegmentnew(LogfsServer *server, int gen, LogSegment **segp);
+void logfslogsegmentfree(LogSegment **segp);
+char *logfslogbytes(LogfsServer *server, int active, uchar *msg, uint size);
+char *logfslog(LogfsServer *server, int active, LogMessage *s);
+char *logfslogwrite(LogfsServer *server, int active, u32int path, u32int offset, int count, u32int mtime,
+ u32int cvers, char *muid, uchar *data, u32int *flashaddr);
+char *logfslogsegmentflush(LogfsServer *server, int active);
+int lognicesizeforwrite(LogfsServer *server, int active, u32int count, int muidlen);
+char *logfsscan(LogfsServer *server);
+
+typedef struct DataBlock DataBlock;
+
+struct DataBlock {
+ u32int free;
+ u32int dirty;
+ long path; /* includes generation */
+ long block;
+};
+
+u32int logfsdatapagemask(int pages, int base);
+
+typedef struct Path Path;
+
+struct Path {
+ ulong path;
+ Entry *entry;
+};
+
+typedef struct Map PathMap;
+
+char *logfspathmapnew(PathMap **pathmapmapp);
+#define logfspathmapfree(mp) logfsmapfree(mp)
+char *logfspathmapnewentry(PathMap *m, ulong path, Entry *e, Path **pathmapp);
+#define logfspathmapfindentry(m, path) (Path *)logfsmapfindentry(m, (void *)path)
+#define logfspathmapdeleteentry(m, path) logfsmapdeleteentry(m, (void *)path)
+Entry *logfspathmapfinde(PathMap *m, ulong path);
+
+enum {
+ LogfsTestDontFettleDataBlock = 1,
+};
+
+struct LogfsServer {
+ LogfsLowLevel *ll;
+ LogfsBoot *lb;
+ FidMap *fidmap;
+ LogfsIdentityStore *is;
+ PathMap *pathmap;
+ LogSegment *activelog;
+ LogSegment *sweptlog;
+ long ndatablocks;
+ DataBlock *datablock;
+ ulong path;
+ Entry root;
+ int trace;
+ ulong openflags;
+ ulong testflags;
+};
+
+int logfshashulong(void *v, int size);
+
+int logfsuserpermcheck(LogfsServer *s, Entry *e, Fid *f, ulong modemask);
+u32int logfsflattenentry(LogfsIdentityStore *is, uchar *buf, u32int buflen, Entry *e);
+char *logfsreplay(LogfsServer *server, LogSegment *seg, int disableerrorsforfirstblock);
+void logfsreplayfinddata(LogfsServer *server);
+
+#define dataseqof(path) ((path) >> L2BlockCopies)
+#define copygenof(path) ((path) & ((1 << L2BlockCopies) -1))
+#define mkdatapath(seq, gen) (((seq) << L2BlockCopies) | (gen))
+#define gensucc(g, l2) (((g) + 1) & ((1 << (l2)) - 1))
+#define copygensucc(g) gensucc(g, L2BlockCopies)
+#define loggenof(path) ((path >> L2BlockCopies) & ((1 << L2LogSweeps) - 1))
+#define logseqof(path) ((path) >> (L2BlockCopies + L2LogSweeps))
+#define mklogpath(seq, gen, copygen) (((((seq) << L2LogSweeps) | (gen)) << L2BlockCopies) | (copygen))
+#define loggensucc(g) gensucc(g, L2LogSweeps)
+
+int logfsunconditionallymarkfreeanddirty(void *magic, Extent *e, int hole);
+void logfsflashaddr2spo(LogfsServer *server, u32int flashaddr, long *seq, int *page, int *offset);
+int logfsgn(uchar **pp, uchar *mep, char **v);
+u32int logfsspo2flashaddr(LogfsServer *server, long seq, int page, int offset);
+int logfsgn(uchar **pp, uchar *mep, char **v);
+void logfsflashaddr2o(LogfsServer *server, u32int flashaddr, int *offset);
+void logfsfreedatapages(LogfsServer *server, long seq, u32int mask);
+void logfsfreeanddirtydatablockcheck(LogfsServer *server, long seq);
+
+typedef enum AllocReason {
+ AllocReasonReplace,
+ AllocReasonTransfer,
+ AllocReasonLogExtend,
+ AllocReasonDataExtend,
+} AllocReason;
+
+long logfsfindfreeblock(LogfsLowLevel *ll, AllocReason reason);
+char *logfsbootfettleblock(LogfsBoot *lb, long block, uchar tag, long path, int *markedbad);
+char *logfsserverreplacedatablock(LogfsServer *server, long index);
+char *logfsserverreplacelogblock(LogfsServer *server, LogSegment *seg, long index);
+char *logfsserverreplaceblock(LogfsServer *server, LogSegment *seg, long seq);
+char *logfsservercopyactivedata(LogfsServer *server, long newb, long oldblockindex, int forcepage0,
+ LogfsLowLevelReadResult *llrrp, int *markedbadp);
+
+char *logfsstrdup(char *);
+
+extern char Enomem[];
+extern char Emsgsize[];
+extern char Enonexist[];
+extern char Etoobig[];
+extern char Eexist[];
+extern char Eunknown[];
+extern char Enotdir[];
+extern char Eisdir[];
+extern char logfsebadfid[];
+extern char logfsefidopen[];
+extern char logfsefidnotopen[];
+extern char logfsefidinuse[];
+extern char logfseopen[];
+extern char logfseaccess[];
+extern char logfselogmsgtoobig[];
+extern char logfselogfull[];
+extern char logfseinternal[];
+extern char logfsenotempty[];
+extern char logfseexcl[];
+extern char logfsefullreplacing[];
+extern char logfseunknownpath[];
diff --git a/liblogfs/log.c b/liblogfs/log.c
new file mode 100644
index 00000000..83217152
--- /dev/null
+++ b/liblogfs/log.c
@@ -0,0 +1,270 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+void
+logfsflashaddr2spo(LogfsServer *server, u32int flashaddr, long *seq, int *page, int *offset)
+{
+ LogfsLowLevel *ll = server->ll;
+ flashaddr &= ~LogAddr;
+ *offset = flashaddr & ((1 << ll->l2pagesize) - 1);
+ flashaddr >>= ll->l2pagesize;
+ *page = flashaddr & ((1 << ll->l2pagesperblock) - 1);
+ flashaddr >>= ll->l2pagesperblock;
+ *seq = flashaddr;
+}
+
+u32int
+logfsspo2flashaddr(LogfsServer *server, long seq, int page, int offset)
+{
+//print("logfsspo2flashaddr(%ld, %d, %d)\n", seq, page, offset);
+ return (((seq << server->ll->l2pagesperblock) + page) << server->ll->l2pagesize) + offset;
+}
+
+void
+logfsflashaddr2o(LogfsServer *server, u32int flashaddr, int *offset)
+{
+ LogfsLowLevel *ll = server->ll;
+ flashaddr &= ~LogAddr;
+ *offset = flashaddr & ((1 << ll->l2pagesize) - 1);
+}
+
+char *
+logfslogsegmentnew(LogfsServer *server, int gen, LogSegment **segp)
+{
+ LogSegment *seg;
+ seg = logfsrealloc(nil, sizeof(LogSegment) + (server->ll->blocks - 1) * sizeof(long));
+ if(seg == nil)
+ return Enomem;
+ seg->pagebuf = logfsrealloc(nil, 1 << server->ll->l2pagesize);
+ if(seg->pagebuf == nil) {
+ logfsfreemem(seg);
+ return Enomem;
+ }
+ seg->curpage = -1;
+ seg->curblockindex = -1;
+ seg->gen = gen;
+ *segp = seg;
+ return nil;
+}
+
+void
+logfslogsegmentfree(LogSegment **segp)
+{
+ LogSegment *seg = *segp;
+ if(seg) {
+ logfsfreemem(seg->pagebuf);
+ logfsfreemem(seg);
+ *segp = nil;
+ }
+}
+
+char *
+logfslogsegmentflush(LogfsServer *server, int active)
+{
+ LogSegment *seg;
+ seg = active ? server->activelog : server->sweptlog;
+ if(seg == nil)
+ return nil;
+ if(seg->curpage >= 0 && seg->nbytes) {
+ char *errmsg;
+ LogfsLowLevel *ll = server->ll;
+ int pagesize = 1 << ll->l2pagesize;
+//print("curblockindex %ld curpage %d nbytes %d\n", seg->curblockindex, seg->curpage, seg->nbytes);
+ if(seg->nbytes < pagesize)
+ seg->pagebuf[seg->nbytes++] = LogfsLogTend;
+ memset(seg->pagebuf + seg->nbytes, 0xff, pagesize - seg->nbytes);
+ for(;;) {
+ errmsg = (*ll->writepage)(ll, seg->pagebuf,
+ seg->blockmap[seg->curblockindex], seg->curpage);
+ if(errmsg == nil)
+ break;
+ if(strcmp(errmsg, Eio) != 0)
+ return errmsg;
+ errmsg = logfsserverreplacelogblock(server, seg, seg->curblockindex);
+ if(errmsg)
+ return errmsg;
+ }
+ seg->curpage++;
+ if(seg->curpage == (1 << ll->l2pagesperblock))
+ seg->curpage = -1;
+ seg->nbytes = 0;
+ }
+ return nil;
+}
+
+static char *
+logspace(LogfsServer *server, int active, int takearisk, int nbytes, uchar **where, u32int *flashaddr)
+{
+ char *errmsg;
+ LogfsLowLevel *ll = server->ll;
+ int pagesize = 1 << ll->l2pagesize;
+ LogSegment *seg;
+
+ if(nbytes > pagesize)
+ return logfselogmsgtoobig;
+retry:
+ seg = active ? server->activelog : server->sweptlog;
+ for(;;) {
+//print("curpage %d nbytes %d\n", seg->curpage, seg->nbytes);
+ if(seg->curpage >= 0) {
+ if(seg->nbytes + nbytes < pagesize)
+ break;
+ errmsg = logfslogsegmentflush(server, active);
+ if(errmsg)
+ return errmsg;
+ }
+ if(seg->curpage < 0) {
+ long block;
+ long path;
+ block = logfsfindfreeblock(ll,
+ active ? (takearisk ? AllocReasonLogExtend : AllocReasonDataExtend) : AllocReasonTransfer);
+ if(block < 0) {
+ if(active) {
+ int didsomething;
+ errmsg = logfsserverlogsweep(server, 0, &didsomething);
+ if(errmsg)
+ return errmsg;
+ if(didsomething)
+ goto retry;
+ }
+ return logfselogfull;
+ }
+ seg->blockmap[++seg->curblockindex] = block;
+ path = mklogpath(seg->curblockindex, seg->gen, 0);
+ (*ll->setblocktag)(ll, block, LogfsTlog);
+ (*ll->setblockpath)(ll, block, path);
+ seg->curpage = 0;
+#ifdef FUTURE
+ /* TODO - do we need one of these if the underlying system supports erase counting? */
+ seg->pagebuf[0] = LogfsLogTstart;
+ PBIT16(seg->pagebuf + 1, 8);
+ PBIT32(seg->pagebuf + 3, path); /* TODO duplicate information */
+ PBIT32(seg->pagebuf + 7, 0); /* TODO don't have this - discuss with forsyth */
+ seg->nbytes = 11;
+#else
+ seg->nbytes = 0;
+#endif
+ }
+ }
+ *where = seg->pagebuf + seg->nbytes;
+ if(flashaddr)
+ *flashaddr = logfsspo2flashaddr(server, seg->curblockindex, seg->curpage, seg->nbytes);
+ seg->nbytes += nbytes;
+ return nil;
+}
+
+static void
+logdirty(LogfsServer *server, int active)
+{
+ if(active)
+ server->activelog->dirty = 1;
+ else
+ server->sweptlog->dirty = 1;
+}
+
+char *
+logfslogbytes(LogfsServer *server, int active, uchar *msg, uint size)
+{
+ char *errmsg;
+ uchar *p;
+
+ errmsg = logspace(server, active, 0, size, &p, nil);
+ if(errmsg)
+ return errmsg;
+ memcpy(p, msg, size);
+ logdirty(server, active);
+ return nil;
+}
+
+char *
+logfslog(LogfsServer *server, int active, LogMessage *s)
+{
+ uint size = logfssizeS2M(s);
+ char *errmsg;
+ uchar *p;
+ int takearisk;
+
+ if(server->trace > 1) {
+ print("%c<< ", active ? 'A' : 'S');
+ logfsdumpS(s);
+ print("\n");
+ }
+ if(active) {
+ switch(s->type) {
+ case LogfsLogTremove:
+ case LogfsLogTtrunc:
+ takearisk = 1;
+ break;
+ default:
+ takearisk = 0;
+ }
+ }
+ else
+ takearisk = 0;
+ errmsg = logspace(server, active, takearisk, size, &p, nil);
+ if(errmsg)
+ return errmsg;
+ if(logfsconvS2M(s, p, size) != size)
+ return "bad conversion";
+ logdirty(server, active);
+ return nil;
+}
+
+int
+lognicesizeforwrite(LogfsServer *server, int active, u32int count, int muidlen)
+{
+ int rawspace;
+ LogSegment *seg;
+ if(count > LogDataLimit)
+ return 0;
+ seg = active ? server->activelog : server->sweptlog;
+ if(seg->curpage < 0)
+ return LogDataLimit;
+ rawspace = (1 << server->ll->l2pagesize) - seg->nbytes;
+ if(rawspace < 5 * 4 + 2 + muidlen + 1)
+ return LogDataLimit;
+ return 5 * 4 + 2 + muidlen - rawspace;
+}
+
+char *
+logfslogwrite(LogfsServer *server, int active, u32int path, u32int offset, int count, u32int mtime, u32int cvers,
+ char *muid, uchar *data, u32int *flashaddr)
+{
+ /* 'w' size[2] path[4] offset[4] count[2] mtime[4] cvers[4] muid[s] flashaddr[4] [data[n]] */
+ LogMessage s;
+ uint size;
+ char *errmsg;
+ uchar *p;
+ u32int faddr;
+ uint asize;
+
+ s.type = LogfsLogTwrite;
+ s.path = path;
+ s.u.write.offset = offset;
+ s.u.write.count = count;
+ s.u.write.mtime = mtime;
+ s.u.write.cvers = cvers;
+ s.u.write.muid = muid;
+ s.u.write.data = data;
+ size = logfssizeS2M(&s);
+ errmsg = logspace(server, active, 0, size, &p, &faddr);
+ if(errmsg)
+ return errmsg;
+ if(data)
+ *flashaddr = (faddr + size - count) | LogAddr;
+ s.u.write.flashaddr = *flashaddr;
+ if(server->trace > 1) {
+ print("%c<< ", active ? 'A' : 'S');
+ logfsdumpS(&s);
+ print("\n");
+ }
+ if((asize = logfsconvS2M(&s, p, size)) != size) {
+ print("expected %d actual %d\n", size, asize);
+ return "bad conversion";
+ }
+ logdirty(server, active);
+ return nil;
+}
+
diff --git a/liblogfs/map.c b/liblogfs/map.c
new file mode 100644
index 00000000..7642475e
--- /dev/null
+++ b/liblogfs/map.c
@@ -0,0 +1,136 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+typedef struct MapNode {
+ struct MapNode *next;
+ uchar e[1]; // entry goes here, inline
+} MapNode;
+
+struct Map {
+ int size;
+ int (*hash)(void *key, int size);
+ int (*compare)(void *entry, void *key);
+ int (*allocsize)(void *key);
+ void (*free)(void *entry);
+ MapNode *head[1];
+};
+
+char *
+logfsmapnew(int size, int (*hash)(void *key, int size), int (*compare)(void *entry, void *key), int (*allocsize)(void *key), void (*free)(void *), Map **mapp)
+{
+ Map *p;
+ *mapp = p = logfsrealloc(nil, sizeof(Map) + (size - 1) * sizeof(MapNode *));
+ if(p == nil)
+ return Enomem;
+ p->size = size;
+ p->hash = hash;
+ p->compare = compare;
+ p->allocsize = allocsize;
+ p->free = free;
+ return nil;
+}
+
+void
+logfsmapfree(FidMap **mp)
+{
+ FidMap *m;
+ int i;
+
+ m = *mp;
+ if(m == nil)
+ return;
+
+ for(i = 0; i < m->size; i++) {
+ MapNode *n, *next;
+ n = m->head[i];
+ while(n) {
+ next = n->next;
+ if(m->free)
+ (*m->free)(n->e);
+ logfsfreemem(n);
+ n = next;
+ }
+ }
+ logfsfreemem(m);
+ *mp = nil;
+}
+
+static char *
+find(FidMap *m, void *key, int create, void **ep)
+{
+ MapNode *n;
+ int i;
+ i = (*m->hash)(key, m->size);
+ n = m->head[i];
+ while(n && !(*m->compare)(n->e, key))
+ n = n->next;
+ if(n) {
+ if(create) {
+ *ep = nil;
+ return nil;
+ }
+ *ep = n->e;
+ return nil;
+ }
+ if(!create) {
+ *ep = nil;
+ return nil;
+ }
+ n = logfsrealloc(nil, (*m->allocsize)(key) + sizeof(MapNode *));
+ if(n == nil) {
+ *ep = nil;
+ return Enomem;
+ }
+ n->next = m->head[i];
+ m->head[i] = n;
+ *ep = n->e;
+ return nil;
+}
+
+void *
+logfsmapfindentry(Map *m, void *key)
+{
+ void *rv;
+ find(m, key, 0, &rv);
+ return rv;
+}
+
+char *
+logfsmapnewentry(Map *m, void *key, void **entryp)
+{
+ return find(m, key, 1, entryp);
+}
+
+int
+logfsmapdeleteentry(Map *m, void *key)
+{
+ MapNode **np, *n;
+ np = &m->head[(*m->hash)(key, m->size)];
+ while((n = *np) && !(*m->compare)(n->e, key))
+ np = &n->next;
+ if(n) {
+ *np = n->next;
+ if(m->free)
+ (*m->free)(n->e);
+ logfsfreemem(n);
+ return 1;
+ }
+ return 0; // not there
+}
+
+int
+logfsmapwalk(Map *m, int (*func)(void *magic, void *), void *magic)
+{
+ int x;
+ MapNode *n;
+
+ for(x = 0; x < m->size; x++)
+ for(n = m->head[x]; n; n = n->next) {
+ int rv = (*func)(magic, n->e);
+ if(rv <= 0)
+ return rv;
+ }
+ return 1;
+}
+
diff --git a/liblogfs/mkfile b/liblogfs/mkfile
new file mode 100644
index 00000000..b7e0de43
--- /dev/null
+++ b/liblogfs/mkfile
@@ -0,0 +1,45 @@
+<../mkconfig
+
+LIB=liblogfs.a
+
+OFILES= \
+ boot.$O\
+ clunk.$O\
+ conv.$O\
+ create.$O\
+ dump.$O\
+ error.$O\
+ extentlist.$O\
+ fidmap.$O\
+ findfreeblock.$O\
+ flush.$O\
+ format.$O\
+ gn.$O\
+ group.$O\
+ groupset.$O\
+ is.$O\
+ log.$O\
+ map.$O\
+ open.$O\
+ path.$O\
+ perm.$O\
+ read.$O\
+ remove.$O\
+ replace.$O\
+ replay.$O\
+ scan.$O\
+ srv.$O\
+ sweep.$O\
+ tagname.$O\
+ test.$O\
+ ust.$O\
+ walk.$O\
+ write.$O\
+ wstat.$O\
+
+HFILES=\
+ local.h \
+ $ROOT/include/logfs.h
+
+<$ROOT/mkfiles/mksyslib-$SHELLTYPE
+
diff --git a/liblogfs/open.c b/liblogfs/open.c
new file mode 100644
index 00000000..688e8822
--- /dev/null
+++ b/liblogfs/open.c
@@ -0,0 +1,72 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+char *
+logfsserveropen(LogfsServer *server, u32int fid, uchar mode, Qid *qid)
+{
+ Fid *f;
+ Entry *e;
+ ulong modemask;
+
+ if(server->trace > 1)
+ print("logfsserveropen(%ud, %d)\n", fid, mode);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode >= 0)
+ return logfsefidopen;
+ e = f->entry;
+ SET(modemask);
+ switch(mode & 3) {
+ case OEXEC:
+ modemask = DMEXEC;
+ break;
+ case OREAD:
+ modemask = DMREAD;
+ break;
+ case OWRITE:
+ modemask = DMWRITE;
+ break;
+ case ORDWR:
+ modemask = DMWRITE | DMREAD;
+ break;
+ }
+ if(e->qid.type & QTDIR) {
+ if((modemask & DMWRITE) != 0 || (mode & (ORCLOSE | OTRUNC)) != 0)
+ return Eperm;
+ }
+ else {
+ if(mode & OTRUNC)
+ modemask |= DMWRITE;
+ if((mode & ORCLOSE) != 0 && !logfsuserpermcheck(server, e->parent, f, DMWRITE))
+ return Eperm;
+ }
+ if(!logfsuserpermcheck(server, e, f, modemask))
+ return Eperm;
+ if((e->qid.type & QTDIR) == 0 && (mode & OTRUNC) != 0 && (e->perm & DMAPPEND) == 0 && e->u.file.length != 0) {
+ LogMessage s;
+ char *errmsg;
+ s.type = LogfsLogTtrunc;
+ s.path = e->qid.path;
+ s.u.trunc.mtime = logfsnow();
+ s.u.trunc.cvers = e->u.file.cvers + 1;
+ s.u.trunc.muid = logfsisfindidfromname(server->is, f->uname);
+ errmsg = logfslog(server, 1, &s);
+ if(errmsg)
+ return errmsg;
+ e->muid = s.u.trunc.muid;
+ e->mtime = s.u.trunc.mtime;
+ e->qid.vers++;
+ e->u.file.cvers = s.u.trunc.cvers;
+ /*
+ * zap all data and extents
+ */
+ logfsextentlistwalk(e->u.file.extent, logfsunconditionallymarkfreeanddirty, server);
+ logfsextentlistreset(e->u.file.extent);
+ e->u.file.length = 0;
+ }
+ f->openmode = mode;
+ *qid = e->qid;
+ return nil;
+}
diff --git a/liblogfs/path.c b/liblogfs/path.c
new file mode 100644
index 00000000..81379e12
--- /dev/null
+++ b/liblogfs/path.c
@@ -0,0 +1,48 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+enum {
+ PATHMOD = 127
+};
+
+static int
+compare(Path *f, ulong path)
+{
+ return f->path == path;
+}
+
+static int
+allocsize(void *key)
+{
+ USED(key);
+ return sizeof(Path);
+}
+
+char *
+logfspathmapnew(PathMap **pathmapp)
+{
+ return logfsmapnew(PATHMOD, logfshashulong, (int (*)(void *, void *))compare, allocsize, nil, pathmapp);
+}
+
+char *
+logfspathmapnewentry(PathMap *m, ulong path, Entry *e, Path **pathmapp)
+{
+ char *errmsg;
+ errmsg = logfsmapnewentry(m, (void *)path, pathmapp);
+ if(errmsg)
+ return errmsg;
+ if(*pathmapp == nil)
+ return nil;
+ (*pathmapp)->path = path;
+ (*pathmapp)->entry = e;
+ return nil;
+}
+
+Entry *
+logfspathmapfinde(PathMap *m, ulong path)
+{
+ Path *p;
+ p = logfspathmapfindentry(m, path);
+ return p ? p->entry : nil;
+}
diff --git a/liblogfs/perm.c b/liblogfs/perm.c
new file mode 100644
index 00000000..9ac8d976
--- /dev/null
+++ b/liblogfs/perm.c
@@ -0,0 +1,25 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+int
+logfsuserpermcheck(LogfsServer *s, Entry *e, Fid *f, ulong permmask)
+{
+ if(s->openflags & LogfsOpenFlagNoPerm)
+ return 1;
+ if((e->perm & permmask) == permmask)
+ /* the whole world can do this */
+ return 1;
+ if(((e->perm >> 6) & permmask) == permmask) {
+ /* maybe we're the owner */
+ char *uname = logfsisfindnamefromid(s->is, e->uid);
+ if(uname == f->uname)
+ return 1;
+ }
+ if(((e->perm >> 3) & permmask) == permmask) {
+ /* maybe we're in the group */
+ Group *g = logfsisfindgroupfromid(s->is, e->gid);
+ return g && logfsisgroupunameismember(s->is, g, f->uname);
+ }
+ return 0;
+}
diff --git a/liblogfs/read.c b/liblogfs/read.c
new file mode 100644
index 00000000..e97a934f
--- /dev/null
+++ b/liblogfs/read.c
@@ -0,0 +1,253 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+struct DirReadState {
+ u32int offset;
+ u32int lastoffset;
+ u32int limit;
+ uchar *data;
+};
+
+typedef struct ReaderState {
+ uchar *buf;
+ u32int maxoffset;
+ LogfsServer *server;
+ char *errmsg;
+} ReaderState;
+
+static DirReadState *
+drsinit(LogfsIdentityStore *is, Entry *list, uchar *buf, u32int buflen, u32int *rcount)
+{
+ Entry *p, *q;
+ DirReadState *drs;
+ u32int k;
+ /*
+ * stash as many entries as will fit in the read buffer
+ */
+ *rcount = 0;
+ for(p = list; p; p = p->next) {
+ uint len = logfsflattenentry(is, buf, buflen, p);
+ if(len == 0)
+ break;
+ *rcount += len;
+ buf += len;
+ buflen -= len;
+ }
+ drs = logfsrealloc(nil, sizeof(*drs));
+ if(drs == nil)
+ return nil;
+ drs->offset = *rcount;
+ drs->lastoffset = drs->offset;
+ k = 0;
+ for(q = p; q; q = q->next)
+ k += logfsflattenentry(is, nil, 0, q);
+ if(k) {
+ u32int k2;
+// print("drsinit: %ud bytes extra\n", k);
+ drs->data = logfsrealloc(nil, k);
+ if(drs->data == nil) {
+ logfsfreemem(drs);
+ return nil;
+ }
+ k2 = 0;
+ for(q = p; q; q = q->next)
+ k2 += logfsflattenentry(is, drs->data + k2, k - k2, q);
+ drs->limit = drs->offset + k;
+ }
+// print("drsinit: rcount %ud\n", *rcount);
+ return drs;
+}
+
+static void
+drsread(DirReadState *drs, uchar *buf, u32int buflen, u32int *rcount)
+{
+ uchar *p;
+ *rcount = 0;
+ p = drs->data + drs->lastoffset - drs->offset;
+ while(drs->lastoffset < drs->limit) {
+ /*
+ * copy an entry, if it fits
+ */
+ uint len = GBIT16(p) + BIT16SZ;
+ if(len > buflen)
+ break;
+ memcpy(buf, p, len);
+ drs->lastoffset += len;
+ *rcount += len;
+ buf += len;
+ buflen -= len;
+ p += len;
+ }
+ if(drs->lastoffset >= drs->limit) {
+ logfsfreemem(drs->data);
+ drs->data = nil;
+ }
+}
+
+void
+logfsdrsfree(DirReadState **drsp)
+{
+ DirReadState *drs = *drsp;
+ if(drs) {
+ logfsfreemem(drs->data);
+ logfsfreemem(drs);
+ *drsp = nil;
+ }
+}
+
+static int
+reader(void *magic, u32int baseoffset, u32int limitoffset, Extent *e, u32int extentoffset)
+{
+ ReaderState *s = magic;
+ LogfsServer *server;
+ LogfsLowLevel *ll;
+ LogfsLowLevelReadResult llrr;
+ long seq;
+ int page;
+ int offset;
+ long block;
+ int pagesize;
+ LogSegment *seg;
+ int replace;
+
+ if(e == nil) {
+//print("fill(%d, %d)\n", baseoffset, limitoffset);
+ memset(s->buf + baseoffset, 0, limitoffset - baseoffset);
+ if(limitoffset > s->maxoffset)
+ s->maxoffset = limitoffset;
+ return 1;
+ }
+ server = s->server;
+ ll = server->ll;
+ /*
+ * extentoffset is how much to trim off the front of the extent
+ */
+ logfsflashaddr2spo(server, e->flashaddr + extentoffset, &seq, &page, &offset);
+ /*
+ * offset is the offset within the page to where e->min is stored
+ */
+//print("read(%d, %d, %c%ld/%ud/%ud)\n",
+// baseoffset, limitoffset, (e->flashaddr & LogAddr) ? 'L' : 'D', seq, page, offset);
+ if(e->flashaddr & LogAddr) {
+ if(seq >= server->activelog->unsweptblockindex && seq <= server->activelog->curblockindex)
+ seg = server->activelog;
+ else if(server->sweptlog && seq <= server->sweptlog->curblockindex)
+ seg = server->sweptlog;
+ else {
+ print("logfsserverread: illegal log sequence number %ld (active=[%ld, %ld], swept=[%ld, %ld])\n",
+ seq, server->activelog->unsweptblockindex, server->activelog->curblockindex,
+ server->sweptlog ? 0L : -1L, server->sweptlog ? server->sweptlog->curblockindex : -1L);
+ s->errmsg = logfseinternal;
+ return -1;
+ }
+ if(seg->curpage == page && seg->curblockindex == seq) {
+ /*
+ * it hasn't made it to disk yet
+ */
+ memcpy(s->buf + baseoffset, seg->pagebuf + offset, limitoffset - baseoffset);
+ goto done;
+ }
+ if(seq < seg->unsweptblockindex) {
+ /* data already swept */
+ print("logfsserverread: log address has been swept\n");
+ s->errmsg = logfseinternal;
+ return -1;
+ }
+ block = seg->blockmap[seq];
+ }
+ else {
+ seg = nil;
+ if(seq >= server->ndatablocks)
+ block = -1;
+ else
+ block = server->datablock[seq].block;
+ if(block < 0) {
+ print("logfsserveread: data address does not exist\n");
+ s->errmsg = logfseinternal;
+ return -1;
+ }
+ }
+ /*
+ * read as many pages as necessary to get to the limitoffset
+ */
+ pagesize = 1 << ll->l2pagesize;
+ replace = 0;
+ while(baseoffset < limitoffset) {
+ u32int thistime;
+ thistime = pagesize - offset;
+ if(thistime > (limitoffset - baseoffset))
+ thistime = limitoffset - baseoffset;
+ s->errmsg = (*ll->readpagerange)(ll, s->buf + baseoffset, block, page,
+ offset, thistime, &llrr);
+ if(s->errmsg)
+ return -1;
+ if(llrr != LogfsLowLevelReadResultOk) {
+ replace = 1;
+ }
+ baseoffset += thistime;
+ page++;
+ offset = 0;
+ }
+ if(replace) {
+ s->errmsg = logfsserverreplaceblock(server, seg, seq);
+ if(s->errmsg)
+ return -1;
+ }
+done:
+ if(limitoffset > s->maxoffset)
+ s->maxoffset = limitoffset;
+ return 1;
+}
+
+char *
+logfsserverread(LogfsServer *server, u32int fid, u32int offset, u32int count, uchar *buf, u32int buflen, u32int *rcount)
+{
+ Fid *f;
+ Entry *e;
+ ReaderState s;
+ int rv;
+
+ if(server->trace > 1)
+ print("logfsserverread(%ud, %ud, %ud)\n", fid, offset, count);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode < 0)
+ return logfsefidnotopen;
+ if((f->openmode & 3) == OWRITE)
+ return logfseaccess;
+ if(count > buflen)
+ return Etoobig;
+ e = f->entry;
+ if(e->deadandgone)
+ return Eio;
+ if(e->qid.type & QTDIR) {
+ if(offset != 0) {
+ if(f->drs == nil || f->drs->lastoffset != offset)
+ return Eio;
+ drsread(f->drs, buf, count, rcount);
+ }
+ else {
+ logfsdrsfree(&f->drs);
+ f->drs = drsinit(server->is, e->u.dir.list, buf, count, rcount);
+ if(f->drs == nil)
+ return Enomem;
+ }
+ return nil;
+ }
+ if(offset >= e->u.file.length) {
+ *rcount = 0;
+ return nil;
+ }
+ s.buf = buf;
+ s.server = server;
+ s.maxoffset = 0;
+ rv = logfsextentlistwalkrange(e->u.file.extent, reader, &s, offset, offset + count);
+ if(rv < 0)
+ return s.errmsg;
+ *rcount = s.maxoffset;
+ return nil;
+}
+
diff --git a/liblogfs/remove.c b/liblogfs/remove.c
new file mode 100644
index 00000000..acc85dd2
--- /dev/null
+++ b/liblogfs/remove.c
@@ -0,0 +1,147 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+void
+logfsfreeanddirtydatablockcheck(LogfsServer *server, long seq)
+{
+ DataBlock *db;
+ u32int mask;
+
+ if(seq >= server->ndatablocks)
+ return;
+ db = server->datablock + seq;
+ if(db->block < 0)
+ return;
+
+ mask = db->dirty & db->free;
+ if(mask) {
+ u32int allpages = logfsdatapagemask(1 << server->ll->l2pagesperblock, 0);
+ if((mask & allpages) == allpages) {
+//print("logfsfreedatapages: returning block to the wild\n");
+ logfsbootfettleblock(server->lb, db->block, LogfsTnone, ~0, nil);
+ db->block = -1;
+ if(seq == server->ndatablocks - 1)
+ server->ndatablocks--;
+ }
+ }
+}
+
+void
+logfsfreedatapages(LogfsServer *server, long seq, u32int mask)
+{
+ DataBlock *db;
+ if(seq >= server->ndatablocks)
+ return;
+ db = server->datablock + seq;
+ if(db->block < 0)
+ return;
+//print("logfsfreedatapages: index %ld mask 0x%.8ux\n", seq, mask);
+ db->dirty |= mask;
+ db->free |= mask;
+ logfsfreeanddirtydatablockcheck(server, seq);
+}
+
+int
+logfsunconditionallymarkfreeanddirty(void *magic, Extent *e, int hole)
+{
+ if(!hole && (e->flashaddr & LogAddr) == 0) {
+ LogfsServer *server = magic;
+ LogfsLowLevel *ll = server->ll;
+ DataBlock *db;
+ long blockindex;
+ int page, offset;
+ logfsflashaddr2spo(server, e->flashaddr, &blockindex, &page, &offset);
+ if(blockindex < server->ndatablocks && (db = server->datablock + blockindex)->block >= 0) {
+ int npages = ((offset + e->max - e->min) + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize;
+ u32int mask = logfsdatapagemask(npages, page);
+ if((db->dirty & mask) != mask)
+ print("markfreeandirty: not all pages dirty\n");
+//print("markfreeanddirty: datablock %ld mask 0x%.8ux\n", blockindex, mask);
+ logfsfreedatapages(server, blockindex, mask);
+ }
+ else
+ print("markfreeanddirty: data block index %ld invalid\n", blockindex);
+ }
+ return 1;
+}
+
+char *
+logfsserverremove(LogfsServer *server, u32int fid)
+{
+ Fid *f;
+ char *errmsg;
+ Entry *parent;
+ Entry *e, **ep;
+ ulong now;
+ char *uid;
+ LogMessage s;
+
+ if(server->trace > 1)
+ print("logfsserverremove(%ud)\n", fid);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil) {
+ errmsg = logfsebadfid;
+ goto clunk;
+ }
+ if((f->openmode & 3) == OWRITE) {
+ errmsg = logfseaccess;
+ goto clunk;
+ }
+ parent = f->entry->parent;
+ if(parent == f->entry) {
+ errmsg = Eperm;
+ goto clunk;
+ }
+ if((parent->qid.type & QTDIR) == 0) {
+ errmsg = logfseinternal;
+ goto clunk;
+ }
+ if(!logfsuserpermcheck(server, parent, f, DMWRITE)) {
+ errmsg = Eperm;
+ goto clunk;
+ }
+ if((f->entry->qid.type & QTDIR) != 0 && f->entry->u.dir.list) {
+ errmsg = logfsenotempty;
+ goto clunk;
+ }
+ if(f->entry->deadandgone) {
+ errmsg = Eio;
+ goto clunk;
+ }
+ for(ep = &parent->u.dir.list; e = *ep; ep = &e->next)
+ if(e == f->entry)
+ break;
+ if(e == nil) {
+ errmsg = logfseinternal;
+ goto clunk;
+ }
+ now = logfsnow();
+ uid = logfsisfindidfromname(server->is, f->uname);
+ /* log it */
+ s.type = LogfsLogTremove;
+ s.path = e->qid.path;
+ s.u.remove.mtime = e->mtime;
+ s.u.remove.muid = e->muid;
+ errmsg = logfslog(server, 1, &s);
+ if(errmsg)
+ goto clunk;
+ parent->mtime = now;
+ parent->muid = uid;
+ logfspathmapdeleteentry(server->pathmap, e->qid.path);
+ *ep = e->next; /* so open can't find it */
+ e->deadandgone = 1; /* so that other fids don't work any more */
+ /*
+ * lose the storage now, as deadandgone will prevent access
+ */
+ if((e->qid.type & QTDIR) == 0) {
+ logfsextentlistwalk(e->u.file.extent, logfsunconditionallymarkfreeanddirty, server);
+ logfsextentlistfree(&e->u.file.extent);
+ }
+ e->inuse--; /* so that the entryclunk removes the storage */
+ errmsg = nil;
+clunk:
+ logfsfidmapclunk(server->fidmap, fid);
+ return errmsg;
+}
+
diff --git a/liblogfs/replace.c b/liblogfs/replace.c
new file mode 100644
index 00000000..b29e4c28
--- /dev/null
+++ b/liblogfs/replace.c
@@ -0,0 +1,175 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+static char *
+copypages(LogfsServer *server, long newb, long oldb, ulong copymask, LogfsLowLevelReadResult *llrrp, int *markedbadp)
+{
+ char *errmsg;
+ int page;
+ LogfsLowLevel *ll;
+ int ppb;
+ int pagesize;
+ uchar *buf;
+
+ if(copymask == 0)
+ return nil;
+
+ ll = server->ll;
+ ppb = 1 << ll->l2pagesperblock;
+ pagesize = 1 << ll->l2pagesize;
+ *markedbadp = 0;
+ *llrrp = LogfsLowLevelReadResultOk;
+ errmsg = nil;
+
+ buf = logfsrealloc(nil, 1 << ll->l2pagesize);
+ if(buf == nil)
+ return Enomem;
+
+ for(page = ppb - 1; page >= 0; page--) {
+ ulong m;
+
+ m = logfsdatapagemask(1, page);
+
+ if(copymask & m) {
+ LogfsLowLevelReadResult llrr;
+ if(server->trace > 1)
+ print("copypages read page %d\n", page);
+ errmsg = (*ll->readpagerange)(ll, buf, oldb, page, 0, pagesize, &llrr);
+ if(errmsg != nil)
+ break;
+ if(llrr > *llrrp)
+ *llrrp = llrr;
+ if(server->trace > 1)
+ print("copypages write page %d\n", page);
+ errmsg = (*ll->writepage)(ll, buf, newb, page);
+ if(errmsg) {
+ if(strcmp(errmsg, Eio) == 0) {
+ (*ll->markblockbad)(ll, newb);
+ *markedbadp = 1;
+ }
+ break;
+ }
+ if(server->trace > 1)
+ print("copypages end page %d\n", page);
+ }
+ }
+ logfsfreemem(buf);
+ return errmsg;
+}
+
+char *
+logfsservercopyactivedata(LogfsServer *server, long newb, long oldblockindex, int forcepage0, LogfsLowLevelReadResult *llrrp, int *markedbadp)
+{
+ LogfsLowLevel *ll = server->ll;
+ ulong newpath;
+ DataBlock *ob;
+ char *errmsg;
+
+ ob = server->datablock + oldblockindex;
+ if(forcepage0) {
+ u32int mask;
+ mask = logfsdatapagemask(1, 0);
+ if(ob->free & mask) {
+ ob->dirty |= mask;
+ ob->free |= mask;
+ }
+ }
+ if(server->trace > 1)
+ print("copyactivedata %ld: (%ld -> %ld)\n", oldblockindex, ob->block, newb);
+ newpath = mkdatapath(dataseqof(ob->path), copygensucc(copygenof(ob->path)));
+ (*ll->setblocktag)(ll, newb, LogfsTdata);
+ (*ll->setblockpath)(ll, newb, newpath);
+ errmsg = copypages(server, newb, ob->block, ~ob->free, llrrp, markedbadp);
+ if(errmsg)
+ return errmsg;
+ /*
+ * anything dirty and free is now not dirty and free
+ */
+ ob->dirty &= ~(ob->dirty & ob->free);
+ ob->block = newb;
+ ob->path = newpath;
+ return nil;
+}
+
+/*
+ * unconditionally replace a datablock, and mark the old one bad
+ * NB: if page 0 is apparently unused, force it to be copied, and mark
+ * it free and dirty afterwards
+ */
+char *
+logfsserverreplacedatablock(LogfsServer *server, long index)
+{
+ long newb;
+ LogfsLowLevel *ll = server->ll;
+
+ newb = logfsfindfreeblock(ll, AllocReasonReplace);
+ /* TODO - recover space by scavenging other blocks, or recycling the log */
+ while(newb >= 0) {
+ char *errmsg;
+ LogfsLowLevelReadResult llrr;
+ long oldblock;
+ int markedbad;
+ DataBlock *db;
+
+ db = server->datablock + index;
+ oldblock = db->block;
+ errmsg = logfsservercopyactivedata(server, newb, index, 1, &llrr, &markedbad);
+ if(errmsg) {
+ if(!markedbad)
+ return errmsg;
+ newb = logfsfindfreeblock(ll, AllocReasonReplace);
+ continue;
+ }
+ (*ll->markblockbad)(ll, oldblock);
+ return nil;
+ }
+ return logfsefullreplacing;
+}
+
+char *
+logfsserverreplacelogblock(LogfsServer *server, LogSegment *seg, long index)
+{
+ ulong opath;
+ LogfsLowLevel *ll = server->ll;
+ long oldb = seg->blockmap[index];
+
+ opath = (*ll->getblockpath)(ll, oldb);
+
+ for(;;) {
+ long newb;
+ int pages;
+ char *errmsg;
+ LogfsLowLevelReadResult llrr;
+ int markedbad;
+
+ newb = logfsfindfreeblock(ll, AllocReasonReplace);
+ if(newb < 0)
+ return "full replacing log block";
+ /* TODO - scavenge data space for a spare block */
+ (*ll->setblocktag)(ll, newb, LogfsTlog);
+ (*ll->setblockpath)(ll, newb, mklogpath(seg->gen, index, copygensucc(copygenof(opath))));
+ if(index == seg->curblockindex)
+ pages = seg->curpage;
+ else
+ pages = 1 << server->ll->l2pagesperblock;
+ errmsg = copypages(server, newb, oldb, logfsdatapagemask(pages, 0), &llrr, &markedbad);
+ if(errmsg == nil) {
+ (*ll->markblockbad)(ll, seg->blockmap[index]);
+ seg->blockmap[index] = newb;
+ return nil;
+ }
+ if(!markedbad)
+ return errmsg;
+ }
+}
+
+char *
+logfsserverreplaceblock(LogfsServer *server, LogSegment *seg, long index)
+{
+ if(seg)
+ return logfsserverreplacelogblock(server, seg, index);
+ else
+ return logfsserverreplacedatablock(server, index);
+}
diff --git a/liblogfs/replay.c b/liblogfs/replay.c
new file mode 100644
index 00000000..a0f7a88d
--- /dev/null
+++ b/liblogfs/replay.c
@@ -0,0 +1,386 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+static void
+maxpath(LogfsServer *server, ulong p)
+{
+ if(p > server->path)
+ server->path = p;
+}
+
+static char *
+recreate(LogfsServer *server, LogMessage *s, int *ok)
+{
+ Entry *parent;
+ char *errmsg;
+ Entry *e;
+ Path *p;
+
+ parent = logfspathmapfinde(server->pathmap, s->path);
+ if(parent == nil)
+ return "can't find parent";
+ if(logfspathmapfindentry(server->pathmap, s->u.create.newpath) != nil){
+ Entry *d = logfspathmapfinde(server->pathmap, s->u.create.newpath);
+ if(d == nil)
+ print("existing was nil\n");
+ else{
+ print("existing: name=%q d=%d path=%8.8llux uid=%q gid=%q perm=%#uo\n",
+ d->name, d->deadandgone, d->qid.path, d->uid, d->gid, d->perm);
+ }
+ return "duplicate path";
+ }
+ if((parent->qid.type & QTDIR) == 0)
+ return Enotdir;
+ errmsg = logfsentrynew(server, 1, s->u.create.newpath, parent,
+ s->u.create.name, s->u.create.uid, s->u.create.gid, s->u.create.mtime, s->u.create.uid,
+ s->u.create.perm, s->u.create.cvers, 0, &e);
+ if(errmsg) {
+ *ok = 0;
+ return errmsg;
+ }
+ /* p guaranteed to be non null */
+ errmsg = logfspathmapnewentry(server->pathmap, s->u.create.newpath, e, &p);
+ if(errmsg) {
+ logfsfreemem(e);
+ *ok = 0;
+ return errmsg;
+ }
+ e->next = parent->u.dir.list;
+ parent->u.dir.list = e;
+ return nil;
+}
+
+static char *
+reremove(LogfsServer *server, LogMessage *s, int *ok)
+{
+ Entry *oe;
+ Entry *parent;
+ Entry **ep;
+ Entry *e;
+ char *ustmuid;
+
+ USED(ok);
+ oe = logfspathmapfinde(server->pathmap, s->path);
+ if(oe == nil)
+ return logfseunknownpath;
+ parent = oe->parent;
+ if(parent == oe)
+ return "tried to remove root";
+ if((parent->qid.type & QTDIR) == 0)
+ return Enotdir;
+ if((oe->qid.type & QTDIR) != 0 && oe->u.dir.list)
+ return logfsenotempty;
+ for(ep = &parent->u.dir.list; e = *ep; ep = &e->next)
+ if(e == oe)
+ break;
+ if(e == nil)
+ return logfseinternal;
+ ustmuid = logfsisustadd(server->is, s->u.remove.muid);
+ if(ustmuid == nil)
+ return Enomem;
+ parent->mtime = s->u.remove.mtime;
+ parent->muid = ustmuid;
+ logfspathmapdeleteentry(server->pathmap, s->path);
+ *ep = e->next;
+ if(e->inuse > 1) {
+ print("replay: entry inuse > 1\n");
+ e->inuse = 1;
+ }
+ logfsentryclunk(e);
+ return nil;
+}
+
+static char *
+retrunc(LogfsServer *server, LogMessage *s, int *ok)
+{
+ Entry *e;
+ char *ustmuid;
+
+ USED(ok);
+ e = logfspathmapfinde(server->pathmap, s->path);
+ if(e == nil)
+ return logfseunknownpath;
+ if((e->qid.type & QTDIR) != 0)
+ return Eperm;
+ if(e->u.file.cvers >= s->u.trunc.cvers)
+ return "old news";
+ ustmuid = logfsisustadd(server->is, s->u.trunc.muid);
+ if(ustmuid == nil)
+ return Enomem;
+ e->muid = ustmuid;
+ e->mtime = s->u.trunc.mtime;
+ e->qid.vers++;
+ e->u.file.cvers = s->u.trunc.cvers;
+ /*
+ * zap all extents
+ */
+ logfsextentlistreset(e->u.file.extent);
+ e->u.file.length = 0;
+ return nil;
+}
+
+static char *
+rewrite(LogfsServer *server, LogMessage *s, int *ok)
+{
+ Entry *e;
+ char *ustmuid;
+ Extent extent;
+ char *errmsg;
+
+ USED(ok);
+ e = logfspathmapfinde(server->pathmap, s->path);
+ if(e == nil)
+ return logfseunknownpath;
+ if((e->qid.type & QTDIR) != 0)
+ return Eperm;
+ if(e->u.file.cvers != s->u.write.cvers)
+ return nil;
+ ustmuid = logfsisustadd(server->is, s->u.write.muid);
+ if(ustmuid == nil)
+ return Enomem;
+ extent.min = s->u.write.offset;
+ extent.max = s->u.write.offset + s->u.write.count;
+ extent.flashaddr = s->u.write.flashaddr;
+ errmsg = logfsextentlistinsert(e->u.file.extent, &extent, nil);
+ if(errmsg)
+ return errmsg;
+ e->mtime = s->u.write.mtime;
+ e->muid = ustmuid;
+ if(extent.max > e->u.file.length)
+ e->u.file.length = extent.max;
+ /* TODO forsyth increments vers here; not sure whether necessary */
+ return nil;
+}
+
+static char *
+rewstat(LogfsServer *server, LogMessage *s, int *ok)
+{
+ Entry *e;
+ char *errmsg;
+ char *cname, *ustgid, *ustmuid;
+ char *ustuid;
+
+ USED(ok);
+ e = logfspathmapfinde(server->pathmap, s->path);
+ if(e == nil)
+ return logfseunknownpath;
+ cname = nil;
+ ustuid = nil;
+ ustgid = nil;
+ ustmuid = nil;
+ if(s->u.wstat.name) {
+ cname = strdup(s->u.wstat.name);
+ if(cname == nil) {
+ memerror:
+ errmsg = Enomem;
+ goto fail;
+ }
+ }
+ if(s->u.wstat.uid) {
+ ustuid = logfsisustadd(server->is, s->u.wstat.uid);
+ if(ustuid == nil)
+ goto memerror;
+ }
+ if(s->u.wstat.gid) {
+ ustgid = logfsisustadd(server->is, s->u.wstat.gid);
+ if(ustgid == nil)
+ goto memerror;
+ }
+ if(s->u.wstat.muid) {
+ ustmuid = logfsisustadd(server->is, s->u.wstat.muid);
+ if(ustmuid == nil)
+ goto memerror;
+ }
+ if(cname) {
+ logfsfreemem(e->name);
+ e->name = cname;
+ cname = nil;
+ }
+ if(ustuid)
+ e->uid = ustuid;
+ if(ustgid)
+ e->gid = ustgid;
+ if(ustmuid)
+ e->muid = ustmuid;
+ if(s->u.wstat.perm != ~0)
+ e->perm = (e->perm & DMDIR) | (s->u.wstat.perm & ~DMDIR);
+ if(s->u.wstat.mtime != ~0)
+ e->mtime = s->u.wstat.mtime;
+ errmsg = nil;
+fail:
+ logfsfreemem(cname);
+ return errmsg;
+}
+
+static char *
+replayblock(LogfsServer *server, LogSegment *seg, uchar *buf, long i, int *pagep, int disableerrors)
+{
+ int page;
+ LogfsLowLevel *ll = server->ll;
+ LogfsLowLevelReadResult llrr;
+ ushort size;
+ LogMessage s;
+ int ppb = 1 << ll->l2pagesperblock;
+ int pagesize = 1 << ll->l2pagesize;
+
+ for(page = 0; page < ppb; page++) {
+ uchar *p, *bufend;
+ char *errmsg = (*ll->readpagerange)(ll, buf, seg->blockmap[i], page, 0, pagesize, &llrr);
+ if(errmsg)
+ return errmsg;
+ if(llrr != LogfsLowLevelReadResultOk)
+ logfsserverreplacelogblock(server, seg, i);
+ /* ignore failure to replace block */
+ if(server->trace > 1)
+ print("replaying seq %ld block %ld page %d\n", i, seg->blockmap[i], page);
+ p = buf;
+ if(*p == 0xff)
+ break;
+ bufend = p + pagesize;
+ while(p < bufend) {
+ int ok = 1;
+ size = logfsconvM2S(p, bufend - p, &s);
+ if(size == 0)
+ return "parse failure";
+ if(server->trace > 1) {
+ print(">> ");
+ logfsdumpS(&s);
+ print("\n");
+ }
+ if(s.type == LogfsLogTend)
+ break;
+ switch(s.type) {
+ case LogfsLogTstart:
+ break;
+ case LogfsLogTcreate:
+ maxpath(server, s.path);
+ maxpath(server, s.u.create.newpath);
+ errmsg = recreate(server, &s, &ok);
+ break;
+ case LogfsLogTtrunc:
+ maxpath(server, s.path);
+ errmsg = retrunc(server, &s, &ok);
+ break;
+ case LogfsLogTremove:
+ maxpath(server, s.path);
+ errmsg = reremove(server, &s, &ok);
+ break;
+ case LogfsLogTwrite:
+ maxpath(server, s.path);
+ errmsg = rewrite(server, &s, &ok);
+ break;
+ case LogfsLogTwstat:
+ maxpath(server, s.path);
+ errmsg = rewstat(server, &s, &ok);
+ break;
+ default:
+ return "bad tag in log page";
+ }
+ if(!ok)
+ return errmsg;
+ if(errmsg && !disableerrors){
+ print("bad replay: %s\n", errmsg);
+ print("on: "); logfsdumpS(&s); print("\n");
+ }
+ p += size;
+ }
+ }
+ *pagep = page;
+ return nil;
+}
+
+static int
+map(void *magic, Extent *x, int hole)
+{
+ LogfsServer *server;
+ LogfsLowLevel *ll;
+ long seq;
+ int page;
+ int offset;
+ u32int mask;
+ DataBlock *db;
+
+ if(hole || (x->flashaddr & LogAddr) != 0)
+ return 1;
+ server = magic;
+ ll = server->ll;
+ logfsflashaddr2spo(server, x->flashaddr, &seq, &page, &offset);
+ if(seq >= server->ndatablocks || (db = server->datablock + seq)->block < 0) {
+ print("huntfordata: seq %ld invalid\n", seq);
+ return 1;
+ }
+ mask = logfsdatapagemask((x->max - x->min + offset + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize, page);
+//print("mask 0x%.8ux free 0x%.8ux dirty 0x%.8ux\n", mask, db->free, db->dirty);
+ if((db->free & mask) != mask)
+ print("huntfordata: data referenced more than once: block %ld(%ld) free 0x%.8ux mask 0x%.8ux\n",
+ seq, db->block, db->free, mask);
+ db->free &= ~mask;
+ db->dirty |= mask;
+ return 1;
+}
+
+static void
+huntfordatainfile(LogfsServer *server, Entry *e)
+{
+ logfsextentlistwalk(e->u.file.extent, map, server);
+}
+
+static void
+huntfordataindir(LogfsServer *server, Entry *pe)
+{
+ Entry *e;
+ for(e = pe->u.dir.list; e; e = e->next)
+ if(e->qid.type & QTDIR)
+ huntfordataindir(server, e);
+ else
+ huntfordatainfile(server, e);
+}
+
+char *
+logfsreplay(LogfsServer *server, LogSegment *seg, int disableerrorsforfirstblock)
+{
+ uchar *buf;
+ long i;
+ int page;
+ char *errmsg;
+
+ if(seg == nil || seg->curblockindex < 0)
+ return nil;
+ buf = logfsrealloc(nil, 1 << server->ll->l2pagesize);
+ if(buf == nil)
+ return Enomem;
+ for(i = 0; i <= seg->curblockindex; i++) {
+ errmsg = replayblock(server, seg, buf, i, &page, disableerrorsforfirstblock);
+ disableerrorsforfirstblock = 0;
+ if(errmsg) {
+ print("logfsreplay: error: %s\n", errmsg);
+ goto fail;
+ }
+ }
+ /*
+ * if the last block ended early, restart at the first free page
+ */
+ if(page < (1 << server->ll->l2pagesperblock))
+ seg->curpage = page;
+ errmsg = nil;
+fail:
+ logfsfreemem(buf);
+ return errmsg;
+}
+
+void
+logfsreplayfinddata(LogfsServer *server)
+{
+ huntfordataindir(server, &server->root);
+ if(server->trace > 0) {
+ long i;
+ DataBlock *db;
+ for(i = 0, db = server->datablock; i < server->ndatablocks; i++, db++) {
+ logfsfreeanddirtydatablockcheck(server, i);
+ if(db->block >= 0)
+ print("%4ld: free 0x%.8ux dirty 0x%.8ux\n", i, server->datablock[i].free, server->datablock[i].dirty);
+ }
+ }
+}
diff --git a/liblogfs/scan.c b/liblogfs/scan.c
new file mode 100644
index 00000000..5f4d9cf7
--- /dev/null
+++ b/liblogfs/scan.c
@@ -0,0 +1,293 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+#include "fcall.h"
+
+typedef struct PathEnt {
+ ulong path;
+ long block;
+} PathEnt;
+
+typedef struct GenInfo {
+ long start;
+ long end;
+ int gaps;
+} GenInfo;
+
+static int
+dataorder(ulong p1, ulong p2)
+{
+ int o;
+ o = dataseqof(p1) - dataseqof(p2);
+ if(o != 0)
+ return o;
+ return copygenof(p1) - copygenof(p2);
+}
+
+static int
+logorder(ulong p1, ulong p2)
+{
+ int o;
+ o = loggenof(p1) - loggenof(p2);
+ if(o != 0)
+ return o;
+ o = logseqof(p1) - logseqof(p2);
+ if(o != 0)
+ return o;
+ return copygenof(p1) - copygenof(p2);
+}
+
+static void
+insert(PathEnt *pathmap, long entries, ulong path, long block, int (*order)(ulong p1, ulong p2))
+{
+ long i;
+ for(i = 0; i < entries; i++)
+ if((*order)(path, pathmap[i].path) < 0)
+ break;
+ memmove(&pathmap[i + 1], &pathmap[i], (entries - i) * sizeof(PathEnt));
+ pathmap[i].path = path;
+ pathmap[i].block = block;
+}
+
+static void
+populate(LogSegment *seg, int gen, long unsweptblockindex, long curblockindex, PathEnt *pathent)
+{
+ long i;
+ seg->gen = gen;
+ seg->unsweptblockindex = unsweptblockindex;
+ seg->curblockindex = curblockindex;
+ for(i = unsweptblockindex; i <= curblockindex; i++) {
+// print("populate %d: %d\n", i, pathent[i - unsweptblockindex].block);
+ seg->blockmap[i] = pathent->block;
+ pathent++;
+ }
+}
+
+static int
+dataduplicate(PathEnt *p1, PathEnt *p2)
+{
+ return dataseqof(p2->path) == dataseqof(p1->path)
+ && copygenof(p2->path) == copygensucc(copygenof(p1->path));
+}
+
+static char *
+eliminateduplicates(LogfsServer *server, char *name, PathEnt *map, long *entriesp)
+{
+ long i;
+ long k = *entriesp;
+ for(i = 0; i < k;) {
+ PathEnt *prev = &map[i - 1];
+ PathEnt *this = &map[i];
+ if(i > 0 && dataduplicate(prev, this)) {
+ print("%s duplicate detected\n", name);
+ if(i + 1 < k && dataduplicate(this, &map[i + 1]))
+ return "three or more copies of same block";
+ /*
+ * check that the copy generations are in order
+ */
+ if(copygensucc(copygenof(this->path)) == copygenof(prev->path)) {
+ PathEnt m;
+ /*
+ * previous entry is newer, so swap
+ */
+ m = *this;
+ *this = *prev;
+ *prev = m;
+ }
+ else if(copygensucc(copygenof(prev->path)) != copygenof(this->path))
+ return "duplicate blocks but copy generations not sequential";
+ /* erase and format previous block */
+ logfsbootfettleblock(server->lb, prev->block, LogfsTnone, ~0, nil);
+ /*
+ * remove entry from table
+ */
+ memmove(prev, this, sizeof(PathEnt) * (k - i));
+ k--;
+ continue;
+ }
+ i++;
+ }
+ *entriesp = k;
+ return nil;
+}
+
+char *
+logfsscan(LogfsServer *server)
+{
+ LogfsLowLevel *ll = server->ll;
+ long b;
+ long i;
+ long logfound = 0;
+ long datafound = 0;
+ PathEnt *logpathmap, *datapathmap;
+ GenInfo geninfo[1 << L2LogSweeps];
+ int gensfound, lastgenfound;
+ int g0, g1;
+ char *errmsg;
+//print("logfsscan %ld blocks\n", server->ll->blocks);
+ logpathmap = logfsrealloc(nil, sizeof(PathEnt) * server->ll->blocks);
+ datapathmap = logfsrealloc(nil, sizeof(PathEnt) * server->ll->blocks);
+ if(logpathmap == nil || datapathmap == nil)
+ return Enomem;
+ for(b = 0; b < ll->blocks; b++) {
+ short tag = (*ll->getblocktag)(ll, b);
+ ulong path = (*ll->getblockpath)(ll, b);
+//print("scan: %ld: %d %ld\n", b, tag, path);
+ switch(tag) {
+ case LogfsTlog:
+ insert(logpathmap, logfound++, path, b, logorder);
+ break;
+ case LogfsTdata:
+ insert(datapathmap, datafound++, path, b, dataorder);
+ break;
+ }
+ }
+ if(server->trace > 1) {
+ for(i = 0; i < logfound; i++)
+ print("log gen %lud seq %lud copygen %lud block %ld\n",
+ loggenof(logpathmap[i].path), logseqof(logpathmap[i].path), copygenof(datapathmap[i].path), logpathmap[i].block);
+ for(i = 0; i < datafound; i++)
+ print("data seq %lud copygen %lud block %ld\n",
+ dataseqof(datapathmap[i].path), copygenof(datapathmap[i].path), datapathmap[i].block);
+ }
+ /*
+ * sort out data first
+ */
+ errmsg = eliminateduplicates(server, "data", datapathmap, &datafound);
+ if(errmsg)
+ goto fail;
+ /*
+ * data blocks guaranteed to be ordered
+ */
+ if(datafound)
+ server->ndatablocks = dataseqof(datapathmap[datafound - 1].path) + 1;
+ else
+ server->ndatablocks = 0;
+ for(i = 0; i < server->ndatablocks; i++)
+ server->datablock[i].block = -1;
+ for(i = 0; i < datafound; i++) {
+ long j;
+ j = dataseqof(datapathmap[i].path);
+ server->datablock[j].path = datapathmap[i].path;
+ server->datablock[j].block = datapathmap[i].block;
+ /*
+ * mark pages as free and dirty, which indicates they cannot be used
+ */
+ server->datablock[j].dirty = server->datablock[j].free = logfsdatapagemask(1 << ll->l2pagesperblock, 0);
+ }
+ /*
+ * find how many generations are present, and whether there are any gaps
+ */
+ errmsg = eliminateduplicates(server, "log", logpathmap, &logfound);
+ if(errmsg)
+ goto fail;
+ gensfound = 0;
+ lastgenfound = -1;
+ for(i = 0; i < nelem(geninfo); i++)
+ geninfo[i].start = -1;
+ for(i = 0; i < logfound; i++) {
+ int gen;
+ gen = loggenof(logpathmap[i].path);
+ if(geninfo[gen].start < 0) {
+ if(lastgenfound >= 0)
+ geninfo[lastgenfound].end = i;
+ geninfo[gen].start = i;
+ lastgenfound = gen;
+ geninfo[gen].gaps = 0;
+ gensfound++;
+ }
+ else if(!geninfo[lastgenfound].gaps && logseqof(logpathmap[i - 1].path) + 1 != logseqof(logpathmap[i].path)) {
+ geninfo[lastgenfound].gaps = 1;
+ print("generation %d has gaps (%lud, %lud)\n", lastgenfound,
+ logseqof(logpathmap[i - 1].path), logseqof(logpathmap[i].path));
+ }
+ }
+ if(lastgenfound >= 0)
+ geninfo[lastgenfound].end = i;
+ if(server->trace > 1) {
+ for(i = 0; i < nelem(geninfo); i++)
+ print("geninfo: %ld: start %ld end %ld gaps %d\n", i, geninfo[i].start, geninfo[i].end, geninfo[i].gaps);
+ }
+ switch(gensfound) {
+ case 0:
+ /* active log - empty */
+ break;
+ case 1:
+ /*
+ * one log, active
+ */
+ for(g0 = 0; g0 < nelem(geninfo); g0++)
+ if(geninfo[g0].start >= 0)
+ break;
+ if(geninfo[g0].gaps || geninfo[g0].start != 0) {
+ errmsg = "missing log blocks";
+ goto fail;
+ }
+ populate(server->activelog, g0, 0, geninfo[g0].end - geninfo[g0].start - 1, logpathmap + geninfo[g0].start);
+ break;
+ case 2:
+ /*
+ * two logs, active, swept
+ */
+ g0 = -1;
+ for(g1 = 0; g1 < nelem(geninfo); g1++)
+ if(geninfo[g1].start >= 0) {
+ if(g0 < 0)
+ g0 = g1;
+ else
+ break;
+ }
+ if(geninfo[g0].gaps || geninfo[g1].gaps) {
+ errmsg = "missing log blocks";
+ goto fail;
+ }
+ if(g0 == loggensucc(g1)) {
+ int tmp = g0;
+ g0 = g1;
+ g1 = tmp;
+ }
+ else if(g1 != loggensucc(g0)) {
+ errmsg = "nonsequential generations in log";
+ goto fail;
+ }
+ if(logseqof(logpathmap[geninfo[g1].start].path) != 0) {
+ errmsg = "swept log does not start at 0";
+ goto fail;
+ }
+ if(logseqof(logpathmap[geninfo[g0].start].path) == logseqof(logpathmap[geninfo[g1].end - 1].path)) {
+ /*
+ * duplicate block
+ * as the log never gets bigger, information from active[n] is either entirely in swept[n],
+ * or split between swept[n-1] and swept[n]. we can safely remove swept[n]. this might
+ * leave some duplication between swept[n - 1] and active[n], but this is always true
+ * for a partially swept log
+ */
+ logfsbootfettleblock(server->lb, logpathmap[geninfo[g1].end - 1].block, LogfsTnone, ~0, nil);
+ geninfo[g1].end--;
+ }
+ if(logseqof(logpathmap[geninfo[g0].start].path) < logseqof(logpathmap[geninfo[g1].end - 1].path)) {
+ errmsg = "active log overlaps end of swept log";
+ goto fail;
+ }
+ populate(server->activelog, g0, logseqof(logpathmap[geninfo[g0].start].path),
+ logseqof(logpathmap[geninfo[g0].end - 1].path), logpathmap + geninfo[g0].start);
+ if(server->sweptlog == nil) {
+ errmsg = logfslogsegmentnew(server, g1, &server->sweptlog);
+ if(errmsg)
+ goto fail;
+ }
+ populate(server->sweptlog, g1, logseqof(logpathmap[geninfo[g1].start].path),
+ logseqof(logpathmap[geninfo[g1].end - 1].path), logpathmap + geninfo[g1].start);
+ break;
+ default:
+ errmsg = "more than two generations in log";
+ goto fail;
+ }
+ goto ok;
+fail:
+ logfslogsegmentfree(&server->sweptlog);
+ok:
+ logfsfreemem(logpathmap);
+ logfsfreemem(datapathmap);
+ return errmsg;
+}
diff --git a/liblogfs/srv.c b/liblogfs/srv.c
new file mode 100644
index 00000000..efb590d3
--- /dev/null
+++ b/liblogfs/srv.c
@@ -0,0 +1,308 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+static char *unimp = "unimplemented";
+char *logfsbadfid = "invalid fid";
+
+char *
+logfsstrdup(char *p)
+{
+ int l;
+ char *q;
+ if(p == nil)
+ return nil;
+ l = strlen(p);
+ q = logfsrealloc(nil, l + 1);
+ if(q == nil)
+ return nil;
+ return strcpy(q, p);
+}
+
+static
+mkdirentry(LogfsServer *server, Entry *e, int inuse, ulong path, Entry *parent, char *name, char *uid, char *gid,
+ ulong mtime, char *muid, ulong perm)
+{
+//print("mkdirentry 0x%.8lux\n", e);
+ e->inuse = inuse;
+ e->qid.path = path;
+ e->qid.vers = 0;
+ e->qid.type = QTDIR;
+ e->parent = parent;
+ e->name = name;
+ e->uid = logfsisustadd(server->is, uid);
+ e->gid = logfsisustadd(server->is, gid);
+ e->mtime = mtime;
+ e->muid = logfsisustadd(server->is, muid);
+ e->perm = perm | DMDIR;
+ e->next = nil;
+ return e->uid != nil && e->muid != nil && e->name != nil;
+}
+
+void
+logfsentryfree(Entry *e)
+{
+ logfsfreemem(e->name);
+ if((e->qid.type & QTDIR) == 0)
+ logfsextentlistfree(&e->u.file.extent);
+ logfsfreemem(e);
+}
+
+char *
+logfsentrynew(LogfsServer *server, int inuse, u32int path, Entry *parent, char *name, char *uid, char *gid,
+u32int mtime, char *muid, u32int perm, ulong cvers, ulong length, Entry **ep)
+{
+ Entry *e;
+ char *errmsg;
+ e = logfsrealloc(nil, sizeof(*e));
+ if(e == nil)
+ return Enomem;
+ e->inuse = inuse;
+ e->qid.path = path;
+ e->qid.vers = 0;
+ e->qid.type = perm >> 24;
+ e->parent = parent;
+ e->name = logfsstrdup(name);
+ e->uid = logfsisustadd(server->is, uid);
+ e->gid = logfsisustadd(server->is, gid);
+ e->muid = logfsisustadd(server->is, muid);
+ if(e->uid == nil || e->gid == nil || e->muid == nil || e->name == nil) {
+ logfsentryfree(e);
+ return Enomem;
+ }
+ e->mtime = mtime;
+ if(perm & DMDIR)
+ e->perm = perm & (~0777 | (parent->perm & 0777));
+ else {
+ e->perm = perm & (~0666 | (parent->perm & 0666));
+ e->u.file.cvers = cvers;
+ e->u.file.length = length;
+ errmsg = logfsextentlistnew(&e->u.file.extent);
+ if(errmsg) {
+ logfsentryfree(e);
+ return errmsg;
+ }
+ }
+//print("e 0x%.8lux perm 0%.uo\n", e, e->perm);
+ *ep = e;
+ return nil;
+
+}
+
+void
+logfsentryclunk(Entry *e)
+{
+ e->inuse--;
+ if(e->inuse <= 0)
+ logfsentryfree(e);
+}
+
+char *
+logfsservernew(LogfsBoot *lb, LogfsLowLevel *ll, LogfsIdentityStore *is, ulong openflags, int trace, LogfsServer **srvp)
+{
+ LogfsServer *srv;
+ char *errmsg;
+ Path *p;
+
+ if(trace > 1)
+ print("logfsservernew()\n");
+ if(ll->l2pagesperblock > 5)
+ return "more than 32 pages per block";
+ if((1 << (ll->pathbits - L2LogSweeps - L2BlockCopies)) < ll->blocks)
+ return "too many blocks";
+ srv = logfsrealloc(nil, sizeof(*srv));
+ if(srv == nil) {
+ memerror:
+ errmsg = Enomem;
+ err:
+ logfsserverfree(&srv);
+ return errmsg;
+ }
+ errmsg = logfsfidmapnew(&srv->fidmap);
+ if(errmsg)
+ goto memerror;
+ errmsg = logfspathmapnew(&srv->pathmap);
+ if(errmsg)
+ goto memerror;
+ srv->is = is;
+ srv->ll = ll;
+ srv->trace = trace;
+ srv->lb = lb;
+ srv->openflags = openflags;
+ if(!mkdirentry(srv, &srv->root, 1, 0, &srv->root, "", "inferno", "sys", logfsnow(), "inferno", 0777))
+ goto memerror;
+ errmsg = logfspathmapnewentry(srv->pathmap, 0, &srv->root, &p);
+ /* p is guaranteed to be non null */
+ if(errmsg)
+ goto memerror;
+ errmsg = logfslogsegmentnew(srv, 0, &srv->activelog);
+ if(errmsg)
+ goto memerror;
+ srv->ndatablocks = 0;
+ srv->datablock = logfsrealloc(nil, sizeof(DataBlock) * ll->blocks);
+ if(srv->datablock == nil)
+ goto memerror;
+ errmsg = logfsscan(srv);
+ if(errmsg)
+ goto err;
+ errmsg = logfsreplay(srv, srv->sweptlog, 0);
+ if(errmsg)
+ goto err;
+ errmsg = logfsreplay(srv, srv->activelog, srv->sweptlog != nil);
+ if(errmsg)
+ goto err;
+ logfsreplayfinddata(srv);
+ *srvp = srv;
+ return nil;
+}
+
+static void
+freeentrylist(Entry *e)
+{
+ Entry *next;
+ while(e) {
+ next = e->next;
+ if(e->qid.type & QTDIR)
+ freeentrylist(e->u.dir.list);
+ logfsentryfree(e);
+ e = next;
+ }
+}
+
+void
+logfsserverfree(LogfsServer **serverp)
+{
+ LogfsServer *server = *serverp;
+ if(server) {
+ logfsfidmapfree(&server->fidmap);
+ logfslogsegmentfree(&server->activelog);
+ logfslogsegmentfree(&server->sweptlog);
+ logfspathmapfree(&server->pathmap);
+ logfsfreemem(server->datablock);
+ logfsfreemem(server);
+ freeentrylist(server->root.u.dir.list);
+ *serverp = nil;
+ }
+}
+
+char *
+logfsserverattach(LogfsServer *server, u32int fid, char *uname, Qid *qid)
+{
+ char *errmsg;
+ Fid *f;
+ if(server->trace > 1)
+ print("logfsserverattach(%ud, %s)\n", fid, uname);
+ errmsg = logfsfidmapnewentry(server->fidmap, fid, &f);
+ if(errmsg)
+ return errmsg;
+ f->uname = logfsisustadd(server->is, uname);
+ if(f->uname == nil) {
+ logfsfidmapclunk(server->fidmap, fid);
+ return Enomem;
+ }
+ f->entry = &server->root;
+ f->entry->inuse++;
+ *qid = f->entry->qid;
+ return nil;
+}
+
+static void
+id2name(LogfsIdentityStore *is, char *id, char **namep, int *badp, int *lenp)
+{
+ char *name;
+ if(id == logfsisgroupnonename)
+ name = id;
+ else {
+ name = logfsisfindnamefromid(is, id);
+ if(name == nil) {
+ *badp = 2;
+ name = id;
+ }
+ }
+ *lenp = strlen(name);
+ *namep = name;
+}
+
+u32int
+logfsflattenentry(LogfsIdentityStore *is, uchar *buf, u32int limit, Entry *e)
+{
+ int unamelen, gnamelen, munamelen, namelen;
+ uint len;
+ uchar *p;
+ int unamebad = 0, gnamebad = 0, munamebad = 0;
+ char *uname, *gname, *muname;
+
+ id2name(is, e->uid, &uname, &unamebad, &unamelen);
+ id2name(is, e->gid, &gname, &gnamebad, &gnamelen);
+ id2name(is, e->muid, &muname, &munamebad, &munamelen);
+ namelen = strlen(e->name);
+ len = 49 + unamelen + unamebad + gnamelen + gnamebad + munamelen + munamebad + namelen;
+ if(buf == nil)
+ return len;
+ if(len > limit)
+ return 0;
+ p = buf;
+ /* size */ PBIT16(p, len - BIT16SZ); p += BIT16SZ;
+ /* type */ p += BIT16SZ;
+ /* dev */ p += BIT32SZ;
+ /* qid.type */ *p++ = e->qid.type;
+ /* qid.vers */ PBIT32(p, e->qid.vers); p += BIT32SZ;
+ /* qid.path */ PBIT64(p, e->qid.path); p+= 8;
+ /* mode */ PBIT32(p, e->perm); p+= BIT32SZ;
+ /* atime */ PBIT32(p, e->mtime); p+= BIT32SZ;
+ /* mtime */ PBIT32(p, e->mtime); p+= BIT32SZ;
+ /* length */ if(e->qid.type & QTDIR) {
+ PBIT64(p, 0);
+ p += 8;
+ }
+ else {
+ PBIT32(p, e->u.file.length); p += BIT32SZ;
+ PBIT32(p, 0); p += BIT32SZ;
+ }
+ /* name */ PBIT16(p, namelen); p += BIT16SZ; memcpy(p, e->name, namelen); p+= namelen;
+ /* uid */ PBIT16(p, unamelen + unamebad); p += BIT16SZ;
+ if(unamebad)
+ *p++ = '(';
+ memcpy(p, uname, unamelen + unamebad); p+= unamelen;
+ if(unamebad)
+ *p++ = ')';
+ /* gid */ PBIT16(p, gnamelen + gnamebad); p += BIT16SZ;
+ if(gnamebad)
+ *p++ = '(';
+ memcpy(p, gname, gnamelen); p+= gnamelen;
+ if(gnamebad)
+ *p++ = ')';
+ /* muid */ PBIT16(p, munamelen + munamebad); p += BIT16SZ;
+ if(munamebad)
+ *p++ = '(';
+ memcpy(p, muname, munamelen); p+= munamelen;
+ if(munamebad)
+ *p = ')';
+//print("len %ud p - buf %ld\n", len, p - buf);
+ return len;
+}
+
+char *
+logfsserverstat(LogfsServer *server, u32int fid, uchar *buf, u32int bufsize, ushort *nstat)
+{
+ Fid *f;
+ if(server->trace > 1)
+ print("logfsserverstat(%ud)\n", fid);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsbadfid;
+ if(f->entry->deadandgone)
+ return Eio;
+ *nstat = logfsflattenentry(server->is, buf, bufsize, f->entry);
+ if(*nstat == 0)
+ return Emsgsize;
+ return nil;
+}
+
+
+void
+logfsservertrace(LogfsServer *server, int level)
+{
+ server->trace = level;
+}
diff --git a/liblogfs/sweep.c b/liblogfs/sweep.c
new file mode 100644
index 00000000..f52509fd
--- /dev/null
+++ b/liblogfs/sweep.c
@@ -0,0 +1,272 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+enum {
+ ThrowAway,
+ Keep,
+ Repack,
+ Error,
+};
+
+#define setaction(a) if(*actionp < (a)) *actionp = a
+#define REPACK setaction(Repack)
+#define KEEP setaction(Keep)
+#define OPTCOPYEX(name, etag, stag) \
+ if(e->etag != s->stag) { \
+ s->stag = e->etag; \
+ REPACK; \
+ }
+#define OPTSTRCOPYEX(name, etag, stag) \
+ if(strcmp(e->etag, s->stag) != 0) { \
+ s->stag = e->etag; \
+ REPACK; \
+ }
+
+#define OPTCOPY(name, tag, sunion) OPTCOPYEX(name, tag, u.sunion.tag)
+#define OPTSTRCOPY(name, tag, sunion) OPTSTRCOPYEX(name, tag, u.sunion.tag)
+
+static char *
+sweepcreate(LogfsServer *server, LogMessage *s, int *actionp)
+{
+ Entry *pe, *e;
+ e = logfspathmapfinde(server->pathmap, s->u.create.newpath);
+ if(e == nil)
+ /* file no longer exists */
+ return nil;
+ pe = logfspathmapfinde(server->pathmap, s->path);
+ if(pe == nil)
+ /* file exists but parent doesn't - not good, but can continue */
+ return "parent missing";
+ if((pe->perm & DMDIR) == 0 || (e->perm & DMDIR) != (s->u.create.perm & DMDIR))
+ /* parent must be a directory, and
+ * the directory mode cannot change
+ */
+ return logfseinternal;
+ if((e->perm & DMDIR) == 0) {
+ OPTCOPYEX("cvers", u.file.cvers, u.create.cvers);
+ }
+ OPTSTRCOPY("name", name, create);
+ OPTCOPY("mtime", mtime, create);
+ OPTCOPY("perm", perm, create);
+ OPTSTRCOPY("uid", uid, create);
+ OPTSTRCOPY("gid", gid, create);
+ KEEP;
+ return nil;
+}
+
+static char *
+sweepwrite(LogfsServer *server, LogMessage *s, int readoffset, Entry **ep, int *trimp, int *actionp)
+{
+ Entry *e;
+ Extent extent;
+ Extent *ext;
+ *ep = nil;
+ e = logfspathmapfinde(server->pathmap, s->path);
+ if(e == nil)
+ /* gone, gone */
+ return nil;
+ if(e->perm & DMDIR)
+ return logfseinternal;
+ if(e->u.file.cvers != s->u.write.cvers)
+ /* trunced more recently */
+ return nil;
+ extent.min = s->u.write.offset;
+ extent.max = extent.min + s->u.write.count;
+ extent.flashaddr = s->u.write.flashaddr;
+ ext = logfsextentlistmatch(e->u.file.extent, &extent);
+ if(ext == nil)
+ return nil;
+ if(s->u.write.data) {
+ /*
+ * trim the front of the data so that when fixing up extents,
+ * flashaddr refers to the first byte
+ */
+ int offset;
+ logfsflashaddr2o(server, ext->flashaddr, &offset);
+ *trimp = offset - readoffset;
+ *ep = e;
+ }
+ KEEP;
+ return nil;
+}
+
+typedef struct FixupState {
+ LogfsServer *server;
+ int oldoffset;
+ u32int newflashaddr;
+} FixupState;
+
+static int
+fixup(void *magic, Extent *e)
+{
+ FixupState *state = magic;
+ int offset;
+ logfsflashaddr2o(state->server, e->flashaddr, &offset);
+ e->flashaddr = state->newflashaddr + (offset - state->oldoffset);
+ return 1;
+}
+
+static char *
+sweepblock(LogfsServer *server, uchar *buf)
+{
+ char *errmsg;
+ LogSegment *active = server->activelog;
+ LogSegment *swept = server->sweptlog;
+ int pagesize, ppb, page;
+ LogfsLowLevel *ll = server->ll;
+ LogfsLowLevelReadResult llrr;
+ int markedbad;
+ long oblock;
+
+ if(active == nil)
+ return nil;
+ if(swept == nil) {
+ errmsg = logfslogsegmentnew(server, loggensucc(active->gen), &server->sweptlog);
+ if(errmsg)
+ return errmsg;
+ swept = server->sweptlog;
+ }
+ /*
+ * if this is last block in the active log, flush it, so that the read of the last page works
+ */
+ if(active->unsweptblockindex == active->curblockindex)
+ logfslogsegmentflush(server, 1);
+ ppb = (1 << ll->l2pagesperblock);
+ pagesize = (1 << ll->l2pagesize);
+ for(page = 0; page < ppb; page++) {
+ uchar *p, *bufend;
+ errmsg = (*ll->readpagerange)(ll, buf, active->blockmap[active->unsweptblockindex], page, 0, pagesize, &llrr);
+ if(errmsg)
+ goto fail;
+ if(llrr != LogfsLowLevelReadResultOk)
+ logfsserverreplacelogblock(server, active, active->unsweptblockindex);
+ p = buf;
+ if(*p == 0xff)
+ break;
+ bufend = p + pagesize;
+ while(p < bufend) {
+ int action;
+ uint size;
+ LogMessage s;
+ Entry *e;
+ int trim;
+
+ size = logfsconvM2S(p, bufend - p, &s);
+ if(size == 0)
+ return "parse failure";
+ if(server->trace > 1) {
+ print("A>> ");
+ logfsdumpS(&s);
+ print("\n");
+ }
+ if(s.type == LogfsLogTend)
+ break;
+ action = ThrowAway;
+ switch(s.type) {
+ case LogfsLogTstart:
+ break;
+ case LogfsLogTcreate:
+ errmsg = sweepcreate(server, &s, &action);
+ break;
+ case LogfsLogTremove:
+ /* always obsolete; might check that path really doesn't exist */
+ break;
+ case LogfsLogTtrunc:
+ /* always obsolete, unless collecting out of order */
+ break;
+ case LogfsLogTwrite:
+ errmsg = sweepwrite(server, &s, s.u.write.data ? s.u.write.data - buf : 0, &e, &trim, &action);
+ break;
+ case LogfsLogTwstat:
+ /* always obsolete, unless collecting out of order */
+ break;
+ default:
+ return "bad tag in log page";
+ }
+ if(action == Error)
+ return errmsg;
+ if(errmsg)
+ print("bad sweep: %s\n", errmsg);
+ if(action == Keep)
+ action = Repack; /* input buffer has been wrecked, so can't just copy it */
+ if(action == Keep) {
+ /* write 'size' bytes to log */
+ errmsg = logfslogbytes(server, 0, p, size);
+ if(errmsg)
+ goto fail;
+ }
+ else if(action == Repack) {
+ /* TODO - handle writes */
+ if(s.type == LogfsLogTwrite && s.u.write.data) {
+ FixupState state;
+ errmsg = logfslogwrite(server, 0, s.path, s.u.write.offset + trim, s.u.write.count - trim,
+ s.u.write.mtime, s.u.write.cvers,
+ s.u.write.muid, s.u.write.data + trim, &state.newflashaddr);
+ if(errmsg == nil && s.u.write.data != nil) {
+ Extent extent;
+ /* TODO - deal with a failure to write the changes */
+ state.oldoffset = s.u.write.data - buf + trim;
+ state.server = server;
+ extent.min = s.u.write.offset;
+ extent.max = extent.min + s.u.write.count;
+ extent.flashaddr = s.u.write.flashaddr;
+ logfsextentlistmatchall(e->u.file.extent, fixup, &state, &extent);
+ }
+ }
+ else
+ errmsg = logfslog(server, 0, &s);
+ if(errmsg)
+ goto fail;
+ }
+ p += size;
+ }
+ }
+ /*
+ * this log block is no longer needed
+ */
+ oblock = active->blockmap[active->unsweptblockindex++];
+ errmsg = logfsbootfettleblock(server->lb, oblock, LogfsTnone, ~0, &markedbad);
+ if(errmsg)
+ goto fail;
+ if(active->unsweptblockindex > active->curblockindex) {
+ /*
+ * the activelog is now empty, so make the sweptlog the active one
+ */
+ logfslogsegmentfree(&active);
+ server->activelog = swept;
+ server->sweptlog = nil;
+ swept->dirty = 0;
+ }
+ return nil;
+fail:
+ return errmsg;
+}
+
+char *
+logfsserverlogsweep(LogfsServer *server, int justone, int *didsomething)
+{
+ uchar *buf;
+ char *errmsg;
+
+ /*
+ * TODO - is it even worth doing?
+ */
+ *didsomething = 0;
+ if(!server->activelog->dirty)
+ return nil;
+ buf = logfsrealloc(nil, (1 << server->ll->l2pagesize));
+ if(buf == nil)
+ return Enomem;
+ errmsg = nil;
+ while(server->activelog->unsweptblockindex <= server->activelog->curblockindex) {
+ errmsg = sweepblock(server, buf);
+ if(errmsg)
+ break;
+ if(server->sweptlog == nil || justone)
+ break;
+ }
+ logfsfreemem(buf);
+ *didsomething = 1;
+ return errmsg;
+}
diff --git a/liblogfs/tagname.c b/liblogfs/tagname.c
new file mode 100644
index 00000000..88713c3d
--- /dev/null
+++ b/liblogfs/tagname.c
@@ -0,0 +1,20 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+char *
+logfstagname(uchar tag)
+{
+ switch(tag) {
+ case LogfsTboot:
+ return "boot";
+ case LogfsTnone:
+ return "free";
+ case LogfsTlog:
+ return "log";
+ case LogfsTdata:
+ return "data";
+ }
+ return "???";
+}
+
diff --git a/liblogfs/test.c b/liblogfs/test.c
new file mode 100644
index 00000000..bff41492
--- /dev/null
+++ b/liblogfs/test.c
@@ -0,0 +1,13 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+char *
+logfsservertestcmd(LogfsServer *s, int argc, char **argv)
+{
+ if(argc == 1 && strcmp(argv[0], "dontfettledatablock") == 0)
+ s->testflags |= LogfsTestDontFettleDataBlock;
+ else
+ return Ebadarg;
+ return nil;
+}
diff --git a/liblogfs/ust.c b/liblogfs/ust.c
new file mode 100644
index 00000000..119ed33a
--- /dev/null
+++ b/liblogfs/ust.c
@@ -0,0 +1,65 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+enum {
+ USTMOD = 127
+};
+
+typedef struct UstNode {
+ char s[1];
+} UstNode;
+
+struct Ust {
+ UstNode *head[USTMOD];
+};
+
+int
+logfshashstring(void *s, int n)
+{
+ int h = 0;
+ char *p;
+ for(p = s; *p; p++) {
+ ulong g;
+ h = (h << 4) + *p;
+ g = h & 0xf0000000;
+ if(g != 0)
+ h ^= ((g >> 24) & 0xff) | g;
+ }
+ return (h & ~(1 << 31)) % n;
+}
+
+static int
+compare(char *entry, char *key)
+{
+ return strcmp(entry, key) == 0;
+}
+
+static int
+allocsize(void *key)
+{
+ return strlen(key) + 1;
+}
+
+char *
+logfsustnew(Ust **ustp)
+{
+ return logfsmapnew(USTMOD, logfshashstring, (int (*)(void *, void *))compare, allocsize, nil, ustp);
+}
+
+char *
+logfsustadd(Ust *ust, char *s)
+{
+ char *errmsg;
+ char *ep;
+ ep = logfsmapfindentry(ust, s);
+ if(ep) {
+// print("ust: found %s\n", s);
+ return ep;
+ }
+ errmsg = logfsmapnewentry(ust, s, &ep);
+ if(errmsg)
+ return errmsg;
+// print("ust: new %s\n", s);
+ return strcpy(ep, s);
+}
diff --git a/liblogfs/walk.c b/liblogfs/walk.c
new file mode 100644
index 00000000..05ae3f9c
--- /dev/null
+++ b/liblogfs/walk.c
@@ -0,0 +1,108 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+char *
+logfsserverwalk(LogfsServer *server, u32int fid, u32int newfid, ushort nwname, char **wname, ushort *nwqid, Qid *wqid)
+{
+ ushort i;
+ Entry *e;
+ char *errmsg;
+ Fid *f;
+ if(server->trace > 1) {
+ print("logfsserverwalk(%ud, %ud, %ud, \"", fid, newfid, nwname);
+ for(i = 0; i < nwname; i++) {
+ if(i > 0)
+ print("/");
+ print("%s", wname[i]);
+ }
+ print("\")\n");
+ }
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode >= 0)
+ return logfsefidopen;
+ errmsg = nil;
+ e = f->entry;
+ if(e->deadandgone)
+ return Eio;
+ for(i = 0; i < nwname; i++) {
+ Entry *se;
+ /*
+ * deal with ..
+ */
+ if(strcmp(wname[i], "..") == 0)
+ se = e->parent;
+ else if(strcmp(wname[i], ".") == 0)
+ se = e;
+ else {
+ /*
+ * is it a directory?
+ */
+ if((e->qid.type & QTDIR) == 0) {
+ errmsg = Enotdir;
+ break;
+ }
+ /*
+ * can we walk the walk, or just talk the protocol?
+ */
+ if(!logfsuserpermcheck(server, e, f, DMEXEC)) {
+ errmsg = Eperm;
+ break;
+ }
+ /*
+ * search current entry for nwname[i]
+ */
+ for(se = e->u.dir.list; se; se = se->next)
+ if(strcmp(se->name, wname[i]) == 0)
+ break;
+ if(se == nil) {
+ errmsg = Enonexist;
+ break;
+ }
+ }
+ wqid[i] = se->qid;
+ e = se;
+ }
+ if(nwname > 0 && i == 0) {
+ /*
+ * fell at the first fence
+ */
+ return errmsg;
+ }
+ *nwqid = i;
+ if(i < nwname)
+ return nil;
+ /*
+ * new fid required?
+ */
+ if(fid != newfid) {
+ Fid *newf;
+ char *errmsg;
+ errmsg = logfsfidmapnewentry(server->fidmap, newfid, &newf);
+ if(errmsg)
+ return errmsg;
+ if(newf == nil)
+ return logfsefidinuse;
+ newf->entry = e;
+ newf->uname = f->uname;
+ e->inuse++;
+ }
+ else {
+ /*
+ * this may now be right
+ * 1. increment reference on new entry first in case e and f->entry are the same
+ * 2. clunk the old one in case this has the effect of removing an old entry
+ * 3. dump the directory read state if the entry has changed
+ */
+ e->inuse++;
+ logfsentryclunk(f->entry);
+ if(e != f->entry)
+ logfsdrsfree(&f->drs);
+ f->entry = e;
+ }
+ return nil;
+}
+
diff --git a/liblogfs/write.c b/liblogfs/write.c
new file mode 100644
index 00000000..3c1d9a6b
--- /dev/null
+++ b/liblogfs/write.c
@@ -0,0 +1,593 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "local.h"
+
+typedef struct AllocState {
+ long oldblock;
+ int markbad;
+} AllocState;
+
+u32int
+logfsdatapagemask(int pages, int base)
+{
+ if(pages == 32)
+ return 0xffffffff;
+ return (((u32int)1 << pages) - 1) << (32 - base - pages);
+}
+
+static u32int
+fastgap(u32int w, u32int n)
+{
+ u32int s;
+//print("fastgap(0x%.8ux, %d)\n", w, n);
+ if(w == 0 || n < 1 || n > 32)
+ return 0;
+/*
+# unroll the following loop 5 times:
+# while(n > 1){
+# s := n >> 1;
+# w &= w<<s;
+# n -= s;
+# }
+*/
+ s = n >> 1;
+ w &= w << s;
+ n -= s;
+ s = n >> 1;
+ w &= w << s;
+ n -= s;
+ s = n >> 1;
+ w &= w << s;
+ n -= s;
+ s = n >> 1;
+ w &= w << s;
+ n -= s;
+ s = n >> 1;
+ return w & (w << s);
+}
+
+static u32int
+page0gap(u32int w, u32int n)
+{
+ int p;
+ for(p = 1; p <= n; p++) {
+ u32int m = logfsdatapagemask(p, 0);
+ if((w & m) != m)
+ return logfsdatapagemask(p - 1, 0);
+ }
+ return 0;
+}
+
+int
+nlz(u32int x)
+{
+ int n, c;
+ if(x == 0)
+ return 32;
+ if(x & 0x80000000)
+ return (~x >> 26) & 0x20;
+ n = 32;
+ c = 16;
+ do {
+ u32int y;
+ y = x >> c;
+ if(y != 0) {
+ n -= c;
+ x = y;
+ }
+ } while((c >>= 1) != 0);
+ return n - x;
+}
+
+static u32int
+findgap(u32int w, u32int n)
+{
+ u32int m;
+ do {
+ m = fastgap(w, n);
+ if(m)
+ break;
+ n--;
+ } while(n);
+ if(n == 0)
+ return 0;
+ return logfsdatapagemask(n, nlz(m));
+}
+
+static int
+bitcount(ulong mask)
+{
+ ulong m;
+ int rv;
+ for(rv = 0, m = 0x80000000; m; m >>= 1)
+ if(mask & m)
+ rv++;
+ return rv;
+}
+
+static char *
+allocdatapages(LogfsServer *server, u32int count, int *countp, long *blockindexp, int *pagep, u32int *flashaddr, AllocState *state)
+{
+ LogfsLowLevel *ll = server->ll;
+ long b, blockindex;
+ DataBlock *db;
+ int pagebase;
+ u32int pages = (count + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize;
+ u32int gapmask;
+ long bestfreeblockindex;
+ int bestfree;
+ int pagesperblock = 1 << ll->l2pagesperblock;
+ int apages;
+ char *errmsg;
+ int didsomething;
+
+ state->oldblock = -1;
+ state->markbad = 0;
+ if(pages > pagesperblock)
+ pages = pagesperblock;
+ /*
+ * fill in gaps first
+ */
+ bestfreeblockindex = -1;
+ bestfree = 0;
+ for(blockindex = 0; blockindex < server->ndatablocks; blockindex++) {
+ db = server->datablock + blockindex;
+ if(db->block < 0)
+ continue;
+ gapmask = findgap(db->free & ~db->dirty, pages);
+//print("blockindex %ld free 0x%.8ux dirty 0x%.8ux gapmask %.8ux\n", blockindex, db->free, db->dirty, gapmask);
+ if(gapmask != 0) {
+ /*
+ * this is free and !dirty
+ */
+ b = db->block;
+ USED(b);
+ goto done;
+ }
+ else {
+ int free = bitcount(db->free & logfsdatapagemask(pagesperblock, 0));
+ if(free > 0 && (bestfreeblockindex < 0 || free > bestfree)) {
+ bestfreeblockindex = blockindex;
+ bestfree = free;
+ }
+ }
+ }
+//print("out of space - need to clean up a data block\n");
+ if(bestfreeblockindex >= 0) {
+//print("best block index %ld (%ld) %d bits\n", bestfreeblockindex, server->datablock[bestfreeblockindex].block, bestfree);
+ /*
+ * clean up data block
+ */
+ b = logfsfindfreeblock(ll, AllocReasonTransfer);
+ while(b >= 0) {
+ char *errmsg;
+ LogfsLowLevelReadResult llrr;
+ long oldblock;
+ int markedbad;
+
+ db = server->datablock + bestfreeblockindex;
+ oldblock = db->block;
+ errmsg = logfsservercopyactivedata(server, b, bestfreeblockindex, 0, &llrr, &markedbad);
+ if(errmsg) {
+ if(!markedbad)
+ return errmsg;
+ b = logfsfindfreeblock(ll, AllocReasonTransfer);
+ }
+ else {
+ u32int available;
+ /*
+ * if page0 is free, then we must ensure that we use it otherwise
+ * in tagged storage such as nand, the block tag is not written
+ * in all cases, it is safer to erase the block afterwards to
+ * preserve the data for as long as possible (we could choose
+ * to erase the old block now if page0 has already been copied)
+ */
+ blockindex = bestfreeblockindex;
+ state->oldblock = oldblock;
+ state->markbad = llrr != LogfsLowLevelReadResultOk;
+ available = db->free & ~db->dirty;
+ gapmask = findgap(available, pages);
+ goto done;
+ }
+ }
+ }
+ /*
+ * use already erased blocks, so long as there are a few free
+ */
+ b = logfsfindfreeblock(ll, AllocReasonDataExtend);
+ if(b >= 0) {
+useerased:
+ for(blockindex = 0, db = server->datablock; blockindex < server->ndatablocks; blockindex++, db++)
+ if(db->block < 0)
+ break;
+ if(blockindex == server->ndatablocks)
+ server->ndatablocks++;
+ db->path = mkdatapath(blockindex, 0);
+ db->block = b;
+ (*ll->setblocktag)(ll, b, LogfsTdata);
+ (*ll->setblockpath)(ll, b, db->path);
+ db->free = logfsdatapagemask(pagesperblock, 0);
+ db->dirty = 0;
+ gapmask = db->free;
+ goto done;
+ }
+ /*
+ * last resort; try to steal from log
+ */
+//print("last resort\n");
+ errmsg = logfsserverlogsweep(server, 0, &didsomething);
+ if(errmsg)
+ return errmsg;
+ if(didsomething) {
+ /*
+ * this can only create whole free blocks, so...
+ */
+//print("findfree after last resort\n");
+ b = logfsfindfreeblock(ll, AllocReasonDataExtend);
+ if(b >= 0) {
+//print("*********************************************************\n");
+ goto useerased;
+ }
+ }
+ *countp = 0;
+ return nil;
+done:
+ /*
+ * common finish - needs gapmask, blockindex, db
+ */
+ apages = bitcount(gapmask);
+ pagebase = nlz(gapmask);
+ if(apages > pages)
+ apages = pages;
+ gapmask = logfsdatapagemask(apages, pagebase);
+ if(server->trace > 1)
+ print("allocdatapages: block %ld(%ld) pages %d mask 0x%.8ux pagebase %d apages %d\n",
+ blockindex, db->block, pages, gapmask, pagebase, apages);
+// db->free &= ~gapmask;
+// db->dirty |= gapmask;
+ *pagep = pagebase;
+ *blockindexp = blockindex;
+ *flashaddr = logfsspo2flashaddr(server, blockindex, pagebase, 0);
+ *countp = apages << ll->l2pagesize;
+ if(*countp > count)
+ *countp = count;
+ return nil;
+}
+
+typedef struct Page {
+ u32int pageaddr;
+ int ref;
+} Page;
+
+typedef struct DataStructure {
+ LogfsServer *server;
+ int nentries;
+ int maxentries;
+ Page *array;
+} DataStructure;
+
+static int
+deltapage(DataStructure *ds, u32int pageaddr, int add, int delta)
+{
+ int i;
+ for(i = 0; i < ds->nentries; i++)
+ if(ds->array[i].pageaddr == pageaddr) {
+ ds->array[i].ref += delta;
+ return 1;
+ }
+ if(!add)
+ return 1;
+ if(ds->maxentries == 0) {
+ ds->array = logfsrealloc(nil, sizeof(Page) * 100);
+ if(ds->array == nil)
+ return 0;
+ ds->maxentries = 100;
+ }
+ else if(ds->nentries >= ds->maxentries) {
+ void *a = logfsrealloc(ds->array, ds->maxentries * 2 * sizeof(Page));
+ if(a == nil)
+ return 0;
+ ds->array = a;
+ ds->maxentries *= 2;
+ }
+ ds->array[ds->nentries].pageaddr = pageaddr;
+ ds->array[ds->nentries++].ref = delta;
+ return 1;
+}
+
+/*
+ * only called for data addresses
+ */
+static int
+deltapages(DataStructure *ds, LogfsLowLevel *ll, u32int baseflashaddr, int range, int add, int delta)
+{
+ long seq;
+ int page, offset;
+ int pages;
+ u32int pageaddr;
+ int x;
+
+//print("deltapages(%ud, %ud, %d, %d)\n", baseflashaddr, limitflashaddr, add, delta);
+ logfsflashaddr2spo(ds->server, baseflashaddr, &seq, &page, &offset);
+ pages = (range + (1 << ll->l2pagesize) - 1) >> ll->l2pagesize;
+ pageaddr = (seq << ll->l2pagesperblock) + page;
+ for(x = 0; x < pages; x++, pageaddr++)
+ if(!deltapage(ds, pageaddr, add, delta))
+ return 0;
+ return 1;
+}
+
+static int
+findpageset(void *magic, u32int baseoffset, u32int limitoffset, Extent *e, u32int extentoffset)
+{
+ DataStructure *ds = magic;
+ LogfsLowLevel *ll;
+ u32int flashaddr;
+ u32int range;
+ u32int residue;
+
+ if(e == nil || (e->flashaddr & LogAddr) != 0)
+ return 1;
+ ll = ds->server->ll;
+//print("baseoffset %ud limitoffset %ud min %ud max %ud\n", baseoffset, limitoffset, e->min, e->max);
+ flashaddr = e->flashaddr;
+ if(extentoffset)
+ if(!deltapages(ds, ll, flashaddr, extentoffset, 1, 1))
+ return -1;
+ flashaddr += extentoffset;
+ range = limitoffset - baseoffset;
+ if(!deltapages(ds, ll, flashaddr, range, 1, -1))
+ return -1;
+ flashaddr += range;
+ residue = e->max - e->min - (extentoffset + range);
+ if(residue)
+ if(!deltapages(ds, ll, flashaddr, residue, 1, 1))
+ return -1;
+ return 1;
+}
+
+static int
+addpagereferences(void *magic, Extent *e, int hole)
+{
+ DataStructure *ds = magic;
+
+ if(hole || (e->flashaddr & LogAddr) != 0)
+ return 1;
+ return deltapages(ds, ds->server->ll, e->flashaddr, e->max - e->min, 0, 1) ? 1 : -1;
+}
+
+static char *
+zappages(LogfsServer *server, Entry *e, u32int min, u32int max)
+{
+ DataStructure ds;
+ int x, rv;
+
+ if(min >= e->u.file.length)
+ /* no checks necessary */
+ return nil;
+ if(min == 0 && max >= e->u.file.length) {
+ /* replacing entire file */
+ logfsextentlistwalk(e->u.file.extent, logfsunconditionallymarkfreeanddirty, server);
+ return nil;
+ }
+ /* hard after that - this will need to be improved */
+ /*
+ * current algorithm
+ * build a list of all pages referenced by the extents being removed, and count the
+ * number of references
+ * then subtract the number of references to each page in entire file
+ * any pages with a reference count == 0 can be removed
+ */
+ ds.server = server;
+ ds.nentries = 0;
+ ds.maxentries = 0;
+ ds.array = nil;
+ rv = logfsextentlistwalkrange(e->u.file.extent, findpageset, &ds, min, max);
+/*
+ print("pass 1\n");
+ for(x = 0; x < ds.nentries; x++)
+ print("block %ud page %ud ref %d\n", ds.array[x].pageaddr / server->ll->pagesperblock,
+ ds.array[x].pageaddr % server->ll->pagesperblock, ds.array[x].ref);
+*/
+ if(rv >= 0) {
+ Page *p;
+ if(ds.nentries == 0)
+ print("pass 2 cancelled\n");
+ else {
+ rv = logfsextentlistwalk(e->u.file.extent, addpagereferences, &ds);
+// print("pass 2\n");
+ for(x = 0, p = ds.array; x < ds.nentries; x++, p++) {
+// print("block %ud page %ud ref %d\n", p->pageaddr / server->ll->pagesperblock,
+// p->pageaddr % server->ll->pagesperblock, p->ref);
+ if(rv >= 0 && p->ref == 0) {
+ long seq = p->pageaddr >> server->ll->l2pagesperblock;
+ int page = p->pageaddr & ((1 << server->ll->l2pagesperblock) - 1);
+ logfsfreedatapages(server, seq, 1 << (31 - page));
+ }
+ }
+ }
+ }
+ logfsfreemem(ds.array);
+ return rv < 0 ? Enomem : nil;
+}
+
+static void
+disposeofoldblock(LogfsServer *server, AllocState *state)
+{
+ if(state->oldblock >= 0) {
+ if(server->testflags & LogfsTestDontFettleDataBlock) {
+ /* take the block out of commission */
+ (*server->ll->setblocktag)(server->ll, state->oldblock, LogfsTworse);
+ server->testflags &= ~LogfsTestDontFettleDataBlock;
+ }
+ else {
+ if(state->markbad)
+ (*server->ll->markblockbad)(server->ll, state->oldblock);
+ else
+ logfsbootfettleblock(server->lb, state->oldblock, LogfsTnone, ~0, nil);
+ }
+ state->oldblock = -1;
+ }
+}
+
+char *
+logfsserverwrite(LogfsServer *server, u32int fid, u32int offset, u32int count, uchar *buf, u32int *rcount)
+{
+ Fid *f;
+ Entry *e;
+ u32int now;
+ char *muid;
+ int muidlen;
+ LogfsLowLevel *ll = server->ll;
+
+ if(server->trace > 1)
+ print("logfsserverwrite(%ud, %ud, %ud)\n", fid, offset, count);
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ if(f->openmode < 0)
+ return logfsefidnotopen;
+ if((f->openmode & 3) == OREAD)
+ return logfseaccess;
+ e = f->entry;
+ if(e->deadandgone)
+ return Eio;
+ if(e->qid.type & QTDIR)
+ return Eperm;
+ if(e->perm & DMAPPEND)
+ offset = e->u.file.length;
+ now = logfsnow();
+ *rcount = count;
+ muid = logfsisfindidfromname(server->is, f->uname);
+ muidlen = strlen(muid);
+ while(count) {
+ Extent extent;
+ int thistime;
+ char *errmsg;
+ thistime = lognicesizeforwrite(server, 1, count, muidlen);
+ if(thistime == 0) {
+ int p;
+ u32int n;
+ long blockindex;
+ int pagebase;
+ AllocState state;
+ int pagesize = 1 << ll->l2pagesize;
+ reallocate:
+ errmsg = allocdatapages(server, count, &thistime, &blockindex, &pagebase, &extent.flashaddr, &state);
+ if(errmsg)
+ return errmsg;
+ if(thistime == 0)
+ return logfselogfull;
+ for(p = pagebase, n = 0; n < thistime; p++, n += pagesize) {
+ u32int mask;
+ DataBlock *db = server->datablock + blockindex;
+ errmsg = (*ll->writepage)(ll, buf + n, db->block, p);
+ if(errmsg) {
+ if(strcmp(errmsg, Eio) != 0) {
+ /*
+ * something horrid happened down below
+ * recover without writing any more than we have to
+ */
+ if(p != 0) {
+ /*
+ * page 0 was either written already, or has been written in this loop
+ * thus the block referenced is valid on the media. all we need to do
+ * is lose the old block, mark the written pages as free (so they can
+ * be scavenged), and don't bother with the log message
+ */
+ disposeofoldblock(server, &state);
+ mask = logfsdatapagemask(p - pagebase - 1, pagebase);
+ db->free |= mask;
+ db->dirty |= mask;
+ return errmsg;
+ }
+ /*
+ * page 0 failed to write (so nothing written at all)
+ * this is either an entirely free block (no erased block in savestate),
+ * or a copy of a scavenged block (erased block in savestate)
+ */
+ if(state.oldblock < 0) {
+ /*
+ * newly selected erased block (blockindex == server->ndatablocks - 1)
+ * mark it bad, lose it from the datablock table
+ */
+ (*ll->markblockbad)(ll, db->block);
+ db->block = -1;
+ if(blockindex == server->ndatablocks - 1)
+ server->ndatablocks--;
+ return errmsg;
+ }
+ /*
+ * page 0 of a data scavenge copy
+ * mark it bad, restore state (old block)
+ */
+ (*ll->markblockbad)(ll, db->block);
+ db->block = state.oldblock;
+ return errmsg;
+ }
+ /*
+ * write error on target block
+ *
+ * if it is a replacement (state saved)
+ * mark the new block bad, restore state and try again
+ *
+ * if it is not replaced (no state saved)
+ * replace block, and try again
+ */
+ if(state.oldblock >= 0) {
+ (*ll->markblockbad)(ll, db->block);
+ db->block = state.oldblock;
+ }
+ else {
+ errmsg = logfsserverreplacedatablock(server, blockindex);
+ if(errmsg)
+ return errmsg;
+ }
+ goto reallocate;
+ }
+ mask = logfsdatapagemask(1, p);
+ db->free &= ~mask;
+ db->dirty |= mask;
+ }
+ /* well, we managed to write the data out */
+ errmsg = logfslogwrite(server, 1, e->qid.path, offset, thistime, now, e->u.file.cvers,
+ muid, nil, &extent.flashaddr);
+ /*
+ * now we can dispose of the original data block, if any
+ * this is regardless of whether we succeeded in writing a log message, as
+ * if this block is not erased, there will be a duplicate
+ */
+ disposeofoldblock(server, &state);
+ }
+ else {
+ if(thistime > count)
+ thistime = count;
+ errmsg = logfslogwrite(server, 1, e->qid.path, offset, thistime, now, e->u.file.cvers,
+ muid, buf, &extent.flashaddr);
+ }
+ /*
+ * here if we failed to write the log message
+ */
+ if(errmsg)
+ return errmsg;
+ if(server->trace > 1)
+ print("logfsserverwrite: %d bytes at flashaddr 0x%.8ux\n", thistime, extent.flashaddr);
+ extent.min = offset;
+ extent.max = offset + thistime;
+ errmsg = zappages(server, e, extent.min, extent.max);
+ if(errmsg)
+ return errmsg;
+ errmsg = logfsextentlistinsert(e->u.file.extent, &extent, nil);
+ if(errmsg)
+ return errmsg;
+ e->muid = muid;
+ e->mtime = now;
+ offset += thistime;
+ if(e->u.file.length < offset)
+ e->u.file.length = offset;
+ count -= thistime;
+ buf += thistime;
+ e->qid.vers++;
+ }
+ return nil;
+}
diff --git a/liblogfs/wstat.c b/liblogfs/wstat.c
new file mode 100644
index 00000000..85f112da
--- /dev/null
+++ b/liblogfs/wstat.c
@@ -0,0 +1,245 @@
+#include "lib9.h"
+#include "logfs.h"
+#include "fcall.h"
+#include "local.h"
+
+char *
+logfsserverwstat(LogfsServer *server, u32int fid, uchar *stat, ushort nstat)
+{
+ Fid *f;
+ uchar *p;
+ ushort len;
+ uchar *mep;
+ Qid qid;
+ u32int perm, mtime;
+ uvlong length;
+ char *name, *uname, *gname, *muname;
+ int qiddonttouch, permdonttouch, mtimedonttouch, lengthdonttouch;
+ Entry *e, *parent;
+ LogMessage s;
+ char *cuid, *ngid;
+ Group *eg, *ng;
+ char *cname;
+ char *errmsg;
+ char *nuid;
+
+ if(server->trace > 1)
+ print("logfsserverwstat(%ud, %ud)\n", fid, nstat);
+ if(nstat < 49)
+ return Emsgsize;
+ p = stat;
+ len = GBIT16(p); p += BIT16SZ;
+ if(len + BIT16SZ != nstat)
+ return Emsgsize;
+ mep = p + len;
+ p += BIT16SZ + BIT32SZ; /* skip type and dev */
+ qid.type = *p++;
+ qid.vers = GBIT32(p); p += BIT32SZ;
+ qid.path = GBIT64(p); p += BIT64SZ;
+ perm = GBIT32(p); p += BIT32SZ;
+ p += BIT32SZ; /* skip atime */
+ mtime = GBIT32(p); p += BIT32SZ;
+ length = GBIT64(p); p+= BIT64SZ;
+ if(!logfsgn(&p, mep, &name) || !logfsgn(&p, mep, &uname)
+ || !logfsgn(&p, mep, &gname) || !logfsgn(&p, mep, &muname))
+ return Emsgsize;
+ if(p != mep)
+ return Emsgsize;
+ qiddonttouch = qid.type == (uchar)~0 && qid.vers == ~0 && qid.path == ~(uvlong)0;
+ permdonttouch = perm == ~0;
+ mtimedonttouch = mtime == ~0;
+ lengthdonttouch = length == ~(uvlong)0;
+ if(server->trace > 1) {
+ int comma = 0;
+ print("logfsserverwstat(");
+ if(!qiddonttouch) {
+ comma = 1;
+ print("qid=0x%.2ux/%lud/%llud", qid.type, qid.vers, qid.path);
+ }
+ if(!permdonttouch) {
+ if(comma)
+ print(", ");
+ print("perm=0%uo", perm);
+ comma = 1;
+ }
+ if(!mtimedonttouch) {
+ if(comma)
+ print(", ");
+ print("mtime=%ud", mtime);
+ comma = 1;
+ }
+ if(!lengthdonttouch) {
+ if(comma)
+ print(", ");
+ print("length=%llud", length);
+ comma = 1;
+ }
+ if(name != nil) {
+ if(comma)
+ print(", ");
+ print("name=%s", name);
+ comma = 1;
+ }
+ if(uname != nil) {
+ if(comma)
+ print(", ");
+ print("uid=%s", uname);
+ comma = 1;
+ }
+ if(gname != nil) {
+ if(comma)
+ print(", ");
+ print("gid=%s", gname);
+ comma = 1;
+ }
+ if(muname != nil) {
+ if(comma)
+ print(", ");
+ print("muname=%s", muname);
+ comma = 1;
+ }
+ USED(comma);
+ print(")\n");
+ }
+ f = logfsfidmapfindentry(server->fidmap, fid);
+ if(f == nil)
+ return logfsebadfid;
+ e = f->entry;
+ if(e->deadandgone)
+ return Eio;
+ parent = e->parent;
+ if(name) {
+ Entry *oe;
+ if(parent == e)
+ return Eperm;
+ if(!logfsuserpermcheck(server, e->parent, f, DMWRITE))
+ return Eperm;
+ for(oe = parent->u.dir.list; oe; oe = oe->next) {
+ if(oe == e)
+ continue;
+ if(strcmp(oe->name, name) == 0)
+ return Eexist;
+ }
+ }
+ if(!lengthdonttouch) {
+ if(!logfsuserpermcheck(server, e, f, DMWRITE))
+ return Eperm;
+ if(e->qid.type & QTDIR) {
+ if(length != 0)
+ return Eperm;
+ }else if(length != e->u.file.length){
+ /*
+ * TODO - truncate directory
+ * TODO - truncate file
+ */
+ return "wstat -- can't change length";
+ }
+ }
+ cuid = logfsisfindidfromname(server->is, f->uname);
+ /* TODO - change entries to have a group pointer */
+ eg = logfsisfindgroupfromid(server->is, e->uid);
+ if(gname) {
+ gname = logfsisustadd(server->is, gname);
+ if(gname == nil)
+ return Enomem;
+ ngid = logfsisfindidfromname(server->is, gname);
+ if(ngid == nil)
+ return Eunknown;
+ }
+ else
+ ngid = nil;
+ if(uname) {
+ uname = logfsisustadd(server->is, uname);
+ if(uname == nil)
+ return Enomem;
+ nuid = logfsisfindidfromname(server->is, uname);
+ if(nuid == nil)
+ return Eunknown;
+ }
+ else
+ nuid = nil;
+ if(!permdonttouch || !mtimedonttouch) {
+ /*
+ * same permissions rules - change by owner, or by group leader
+ */
+ if((server->openflags & LogfsOpenFlagWstatAllow) == 0 &&
+ e->uid != cuid && (eg == nil || !logfsisgroupuidisleader(server->is, eg, cuid)))
+ return Eperm;
+ }
+ if(!permdonttouch){
+ if((perm^e->perm) & DMDIR)
+ return "wstat -- attempt to change directory";
+ if(perm & ~(DMDIR|DMAPPEND|DMEXCL|0777))
+ return Eperm;
+ }
+ if(gname) {
+ int ok;
+ ng = logfsisfindgroupfromid(server->is, ngid);
+ ok = 0;
+ if(e->uid == cuid && logfsisgroupuidismember(server->is, ng, e->uid))
+ ok = 1;
+ if(!ok && eg && logfsisgroupuidisleader(server->is, eg, cuid)
+ && logfsisgroupuidisleader(server->is, ng, cuid))
+ ok = 1;
+ if(!ok && (server->openflags & LogfsOpenFlagWstatAllow) == 0)
+ return Eperm;
+ }
+ if(!qiddonttouch)
+ return Eperm;
+ if(uname){
+ if((server->openflags & LogfsOpenFlagWstatAllow) == 0)
+ return Eperm;
+ }
+ if(muname)
+ return Eperm;
+ /*
+ * we can do this
+ */
+ if(mtimedonttouch && permdonttouch && lengthdonttouch
+ && name == nil && uname == nil && gname == nil) {
+ /*
+ * but we aren't doing anything - this is a wstat flush
+ */
+ return logfsserverflush(server);
+ }
+ if(name) {
+ cname = logfsstrdup(name);
+ if(cname == nil)
+ return Enomem;
+ }
+ else
+ cname = nil;
+ /*
+ * send the log message
+ */
+ s.type = LogfsLogTwstat;
+ s.path = e->qid.path;
+ s.u.wstat.name = cname;
+ s.u.wstat.perm = perm;
+ s.u.wstat.uid = nuid;
+ s.u.wstat.gid = ngid;
+ s.u.wstat.mtime = mtime;
+ s.u.wstat.muid = cuid;
+ errmsg = logfslog(server, 1, &s);
+ if(errmsg) {
+ logfsfreemem(cname);
+ return errmsg;
+ }
+ if(!mtimedonttouch)
+ e->mtime = mtime;
+ if(!permdonttouch)
+ e->perm = (e->perm & DMDIR) | perm;
+ if(!lengthdonttouch) {
+ /* TODO */
+ }
+ if(name) {
+ logfsfreemem(e->name);
+ e->name = cname;
+ }
+ if(uname)
+ e->uid = nuid;
+ if(ngid)
+ e->gid = ngid;
+ return nil;
+}
+