#include "logfsos.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; }