Plan 9 from Bell Labs’s /usr/web/sources/nix/nix/sys/src/cmd/creepy/check.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#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;
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.