#include "all.h" /* * fs checks */ void checktag(u64int tag, uint type, daddrt addr) { if((tag|DFdir) != TAG(type, DFdir, addr)){ if(type == DBref) if((tag|DFdir) == TAG(type, DFdir, addr+Dblksz)) return; /* odd refs */ warn("bad tag: %#ullx %#ullx\n", tag, TAG(type, 0, addr)); error("bad tag"); } } static int validaddr(daddrt addr) { if(addr&Fakeaddr) return 0; if(addr == 0) return 1; return addr >= Dblk0addr && addr < fs->super->d.eaddr; } void checkblk(Memblk *b) { int i; daddrt eaddr, *de; long doff, sz; checktag(b->d.tag, b->type, b->addr); switch(b->type){ case DBfree: warnerror("free block on disk"); break; case DBref: eaddr = fs->super->d.eaddr; for(i = 0; i < Drefperblk; i++) if(b->d.ref[i] >= eaddr) warnerror("ref out of range"); break; case DBsuper: if(b->d.magic != MAGIC) warnerror("super: magic"); if(b->d.eaddr >= fs->limit || b->d.eaddr < Dblk0addr) warnerror("super: eaddr out of range"); if(b->d.free >= b->d.eaddr || (b->d.free && b->d.free < Dblk0addr)) warnerror("super: free out of range"); if(b->d.root >= b->d.eaddr || b->d.root < Dblk0addr) warnerror("super: root out of range"); break; case DBattr: if(!validaddr(b->d.next)) warnerror("attr: next out of range"); break; case DBdata: if(DBDIR(b) == 0) break; for(i = 0; i < Dblkdatasz/Daddrsz; i++) if(!validaddr(b->d.ptr[i])) warnerror("dentry out of range"); break; case DBfile: if(!validaddr(b->d.aptr)) warnerror("file: attr out of range"); for(i = 0; i < nelem(b->d.dptr); i++) if(!validaddr(b->d.dptr[i])) warnerror("file: dptr out of range"); for(i = 0; i < nelem(b->d.iptr); i++) if(!validaddr(b->d.iptr[i])) warnerror("file: iptr out of range"); if(DBDIR(b) != 0){ doff = embedattrsz(b); if(doff > Embedsz) warnerror("file: wrong attr size"); sz = Embedsz-doff; de = (daddrt*)(b->d.embed+doff); for(i = 0; i < sz/Daddrsz; i++) if(!validaddr(de[i])) warnerror("file: dentry out of range"); } break; default: if(b->type < DBptr0 || b->type >= DBptr0 + Niptr) warnerror("unknown block type"); for(i = 0; i < Dptrperblk; i++) if(!validaddr(b->d.ptr[i])) warnerror("ptr: address out of range"); } } static uvlong clearrefs(int disktoo) { Memblk *b; daddrt addr, eaddr; uvlong nhash; int i; nhash = 0; for(i = 0; i < nelem(fs->fhash); i++) for(b = fs->fhash[i].b; b != nil; b = b->next){ nhash++; b->d.cnt = 0; } if(disktoo == 0) return nhash; eaddr = fs->super->d.eaddr; for(addr = Dblk0addr+2*Dblksz; addr < eaddr; addr += Dblksz*Nblkgrpsz){ if(catcherror()){ warn("clearrefs: %r"); return 1; } b = dbget(DBref, addr); memset(b->d.data, 0, Dblkdatasz); mbput(b); noerror(); } return nhash; } static int mbcounted(Memblk *b) { if(b == nil) return 0; if(b < fs->blk || b >= fs->blk + fs->nablk) fatal("mbcountref: m%#p not in global array", b); if(b->ref != b->d.cnt){ warn("check: m%#p: found %ulld != ref %ud\n%H", b, b->d.cnt, b->ref, b); return 1; } return 0; } daddrt dbcounted(daddrt addr) { Memblk *rb; daddrt n, raddr; int i; raddr = refaddr(addr, &i) + 2*Dblksz; if(catcherror()){ warn("dbcounted: %r"); return 1; } rb = dbget(DBref, raddr); noerror(); n = rb->d.ref[i]; mbput(rb); return n; } daddrt mbcountref(Memblk *b) { daddrt old; if(b == nil) return 0; if(b < fs->blk || b >= fs->blk + fs->nablk) fatal("mbcountref: m%#p not in global array", b); old = b->d.cnt++; if(old == 0 && b->type == DBfile) mbcountref(b->mf->melted); return old; } u64int dbcountref(daddrt addr) { Memblk *rb; daddrt n, raddr; int i; raddr = refaddr(addr, &i) + 2*Dblksz; if(catcherror()){ warn("dbcountref: %r"); return 1; } rb = dbget(DBref, raddr); noerror(); n = rb->d.ref[i]++; mbput(rb); return n; } static int bcountrefs(Memblk *b, void*) { if(dbcountref(b->addr) != 0) /* already counted; prune */ return -1; return 0; } static int dbcountfree(daddrt addr, int oktohash) { Memblk *rb; daddrt n, raddr; int i; if(!oktohash && mbhashed(addr)){ warn("check: d%#010ullx: free block in use", addr); return 1; } raddr = refaddr(addr, &i) + 2*Dblksz; if(catcherror()){ warn("dbcountref: %r"); return 1; } rb = dbget(DBref, raddr); noerror(); n = rb->d.ref[i]; if(n != 0){ warn("check: d%#010ullx: double free", addr); mbput(rb); return 1; } rb->d.ref[i] = ~0; mbput(rb); return 0; } static long dfcountrefs(Memblk*); static int fcountref(Memblk *, daddrt *de, void *a) { Memblk *b; long *nfails; nfails = a; if(*de == 0) return 0; if(catcherror()){ warn("check: d%#010ullx %r", *de); (*nfails)++; }else{ b = dbget(DBfile, *de); (*nfails) += dfcountrefs(b); noerror(); mbput(b); } return 0; } static long dfcountrefs(Memblk *f) { int i; long nfails; nfails = 0; isfile(f); if((f->addr&Fakeaddr) == 0 && f->addr >= fs->limit){ warn("check: '%s' d%#010ullx: out of range", f->mf->name, f->addr); return 1; } if((f->addr&Fakeaddr) == 0) if(dbcountref(f->addr) != 0) /* already visited */ return 0; /* skip children */ rwlock(f, Rd); if(catcherror()){ warn("check: '%s' d%#010ullx: data: %r", f->mf->name, f->addr); rwunlock(f, Rd); return 1; } for(i = 0; i < nelem(f->d.dptr); i++) ptrmap(f->d.dptr[i], 0, bcountrefs, nil, Disk); for(i = 0; i < nelem(f->d.iptr); i++) ptrmap(f->d.iptr[i], i+1, bcountrefs, nil, Disk); if(DBDIR(f)) dfdirmap(f, fcountref, &nfails, Rd); noerror(); rwunlock(f, Rd); return nfails; } static long fscheckrefs(void) { long nfails; int i; Memblk *b; dprint("mblk refs...\n"); clearrefs(Mem); mbcountref(fs->super); mbcountref(fs->root); mbcountref(fs->active); mbcountref(fs->archive); mbcountref(fs->cons); mbcountref(fs->stats); mbcountref(fs->fzsuper); countfidrefs(); for(i = 0; i < nelem(fs->fhash); i++) for(b = fs->fhash[i].b; b != nil; b = b->next) mbcountref(b); nfails = 0; for(i = 0; i < nelem(fs->fhash); i++) for(b = fs->fhash[i].b; b != nil; b = b->next) nfails += mbcounted(b); nfails += mbcounted(fs->super); nfails += mbcounted(fs->root); nfails += mbcounted(fs->active); nfails += mbcounted(fs->archive); nfails += mbcounted(fs->cons); nfails += mbcounted(fs->stats); nfails += mbcounted(fs->fzsuper); if(nfails > 0 && dbg['D']){ dprint("fscheckrefs: %ld fails. sleeping\n", nfails); fsdump(1, 0); while(1)sleep(5000); } return nfails; } static void dfcountfree(void) { daddrt addr; dprint("list...\n"); addr = fs->super->d.free; while(addr != 0){ if(addr < Dblksz){ warn("check: d%#010ullx in free list", addr); break; } if(addr >fs->limit){ warn("check: d%#010ullx: free overflow", addr); break; } dbcountfree(addr, 0); addr = dbgetref(addr); } /* DBref blocks */ dprint("refs...\n"); for(addr = Dblk0addr; addr < fs->super->d.eaddr; addr += Dblksz*Nblkgrpsz){ dbcountfree(addr, 1); /* even DBref */ dbcountfree(addr+Dblksz, 1); /* odd DBref */ dbcountfree(addr+2*Dblksz, 1); /* check DBref */ } } static uvlong mleaks(void) { uvlong nblk, nfails, n; Memblk *p; dprint("mblk leaks...\n"); nfails = 0; if(fs->nblk != fs->nmused + fs->nmfree){ warn("block leaks: %ulld blks != %ulld used + %ulld free", fs->nblk, fs->nmused, fs->nmfree); nfails++; } nblk = fs->clean.n + fs->dirty.n + fs->refs.n; nblk++; /* super */ nblk++; /* cons */ nblk++; /* root */ nblk++; /* stats */ if(nblk != fs->nmused){ warn("check: %ulld blocks linked != %ulld blocks used", nblk, fs->nmused); fs->super->unlinkpc = 0; fs->root->unlinkpc = 0; fs->cons->unlinkpc = 0; fs->stats->unlinkpc = 0; for(p = fs->free; p != nil; p = p->next) p->unlinkpc = 0; for(p = fs->dirty.hd; p != nil; p = p->lnext) p->unlinkpc = 0; for(p = fs->clean.hd; p != nil; p = p->lnext) p->unlinkpc = 0; for(p = fs->refs.hd; p != nil; p = p->lnext) p->unlinkpc = 0; for(n = 0; n < fs->nblk; n++) if(fs->blk[n].unlinkpc != 0){ warn("check: block unlinked at %#p:\n%H", fs->blk[n].unlinkpc, &fs->blk[n]); nfails++; } } return nfails; } static uvlong dleaks(void) { daddrt n, addr, c; long nfails; dprint("dblk leaks...\n"); clearrefs(Disk); nfails = dfcountrefs(fs->root); dfcountfree(); for(addr = Dblk0addr; addr < fs->super->d.eaddr; addr += Dblksz){ c = dbcounted(addr); if(c == 0){ warn("check: d%#010ullx: leak", addr); nfails++; continue; } if(addr < Dblk0addr || c == ~0) continue; n = dbgetref(addr); if(n != c){ warn("check: d%#010ullx: found %ulld != ref %ulld", addr, c, n); nfails++; } } return nfails; } /* * Failed checks are reported but not fixed (but for leaked blocks). * The user is expected to format the partition and restore contents from venti. * We might easily remove the dir entries for corrupt files, and restore */ int fscheck(void) { long nfails; xqlock(&fs->fzlk); xrwlock(&fs->quiescence, Wr); nfails = 0; if(catcherror()){ xrwunlock(&fs->quiescence, Wr); xqunlock(&fs->fzlk); warn("check: %r"); nfails++; return nfails; } warn("check..."); nfails += mleaks(); nfails += fscheckrefs(); if(nfails == 0) nfails += dleaks(); xrwunlock(&fs->quiescence, Wr); xqunlock(&fs->fzlk); noerror(); if(nfails > 0 && dbg['D']){ dprint("fscheck: %ld fails. sleeping\n", nfails); fsdump(0, 1); while(1)sleep(5000); } if(nfails) warn("check fails"); else warn("check passes"); return nfails; }