diff options
Diffstat (limited to 'liblogfs')
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; +} + |
