xref: /plan9/sys/src/cmd/fossil/check.c (revision 8deabd962e84f51c67a12f970084955d97d8a8f2)
1 #include "stdinc.h"
2 #include "dat.h"
3 #include "fns.h"
4 
5 static void	checkDirs(Fsck*);
6 static void	checkEpochs(Fsck*);
7 static void	checkLeak(Fsck*);
8 static void	closenop(Fsck*, Block*, u32int);
9 static void	clrenop(Fsck*, Block*, int);
10 static void	clrinop(Fsck*, char*, MetaBlock*, int, Block*);
11 static void	error(Fsck*, char*, ...);
12 static int	getBit(uchar*, u32int);
13 static int	printnop(char*, ...);
14 static void	setBit(uchar*, u32int);
15 static int	walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize],
16 			int type, u32int tag, u32int epoch);
17 static void	warn(Fsck*, char*, ...);
18 
19 #pragma varargck argpos error 2
20 #pragma varargck argpos warn 2
21 
22 static Fsck*
23 checkInit(Fsck *chk)
24 {
25 	chk->cache = chk->fs->cache;
26 	chk->nblocks = cacheLocalSize(chk->cache, PartData);;
27 	chk->bsize = chk->fs->blockSize;
28 	chk->walkdepth = 0;
29 	chk->hint = 0;
30 	chk->quantum = chk->nblocks/100;
31 	if(chk->quantum == 0)
32 		chk->quantum = 1;
33 	if(chk->print == nil)
34 		chk->print = printnop;
35 	if(chk->clre == nil)
36 		chk->clre = clrenop;
37 	if(chk->close == nil)
38 		chk->close = closenop;
39 	if(chk->clri == nil)
40 		chk->clri = clrinop;
41 	return chk;
42 }
43 
44 /*
45  * BUG: Should merge checkEpochs and checkDirs so that
46  * bad blocks are only reported once, and so that errors in checkEpochs
47  * can have the affected file names attached, and so that the file system
48  * is only read once.
49  *
50  * Also should summarize the errors instead of printing for every one
51  * (e.g., XXX bad or unreachable blocks in /active/usr/rsc/foo).
52  */
53 
54 void
55 fsCheck(Fsck *chk)
56 {
57 	Block *b;
58 	Super super;
59 
60 	checkInit(chk);
61 	b = superGet(chk->cache, &super);
62 	if(b == nil){
63 		chk->print("could not load super block: %R");
64 		return;
65 	}
66 	blockPut(b);
67 
68 	chk->hint = super.active;
69 	checkEpochs(chk);
70 
71 	chk->smap = vtMemAllocZ(chk->nblocks/8+1);
72 	checkDirs(chk);
73 	vtMemFree(chk->smap);
74 }
75 
76 static void checkEpoch(Fsck*, u32int);
77 
78 /*
79  * Walk through all the blocks in the write buffer.
80  * Then we can look for ones we missed -- those are leaks.
81  */
82 static void
83 checkEpochs(Fsck *chk)
84 {
85 	u32int e;
86 	uint nb;
87 
88 	nb = chk->nblocks;
89 	chk->amap = vtMemAllocZ(nb/8+1);
90 	chk->emap = vtMemAllocZ(nb/8+1);
91 	chk->xmap = vtMemAllocZ(nb/8+1);
92 	chk->errmap = vtMemAllocZ(nb/8+1);
93 
94 	for(e = chk->fs->ehi; e >= chk->fs->elo; e--){
95 		memset(chk->emap, 0, chk->nblocks/8+1);
96 		memset(chk->xmap, 0, chk->nblocks/8+1);
97 		checkEpoch(chk, e);
98 	}
99 	checkLeak(chk);
100 	vtMemFree(chk->amap);
101 	vtMemFree(chk->emap);
102 	vtMemFree(chk->xmap);
103 	vtMemFree(chk->errmap);
104 }
105 
106 static void
107 checkEpoch(Fsck *chk, u32int epoch)
108 {
109 	u32int a;
110 	Block *b;
111 	Entry e;
112 	Label l;
113 
114 	chk->print("checking epoch %ud...\n", epoch);
115 
116 	for(a=0; a<chk->nblocks; a++){
117 		if(!readLabel(chk->cache, &l, (a+chk->hint)%chk->nblocks)){
118 			error(chk, "could not read label for addr 0x%.8#ux", a);
119 			continue;
120 		}
121 		if(l.tag == RootTag && l.epoch == epoch)
122 			break;
123 	}
124 
125 	if(a == chk->nblocks){
126 		chk->print("could not find root block for epoch %ud", epoch);
127 		return;
128 	}
129 
130 	a = (a+chk->hint)%chk->nblocks;
131 	b = cacheLocalData(chk->cache, a, BtDir, RootTag, OReadOnly, 0);
132 	if(b == nil){
133 		error(chk, "could not read root block 0x%.8#ux: %R", a);
134 		return;
135 	}
136 
137 	/* no one should point at root blocks */
138 	setBit(chk->amap, a);
139 	setBit(chk->emap, a);
140 	setBit(chk->xmap, a);
141 
142 	/*
143 	 * First entry is the rest of the file system.
144 	 * Second entry is link to previous epoch root,
145 	 * just a convenience to help the search.
146 	 */
147 	if(!entryUnpack(&e, b->data, 0)){
148 		error(chk, "could not unpack root block 0x%.8#ux: %R", a);
149 		blockPut(b);
150 		return;
151 	}
152 	walkEpoch(chk, b, e.score, BtDir, e.tag, epoch);
153 	if(entryUnpack(&e, b->data, 1))
154 		chk->hint = globalToLocal(e.score);
155 	blockPut(b);
156 }
157 
158 /*
159  * When b points at bb, need to check:
160  *
161  * (i) b.e in [bb.e, bb.eClose)
162  * (ii) if b.e==bb.e,  then no other b' in e points at bb.
163  * (iii) if !(b.state&Copied) and b.e==bb.e then no other b' points at bb.
164  * (iv) if b is active then no other active b' points at bb.
165  * (v) if b is a past life of b' then only one of b and b' is active
166  *	(too hard to check)
167  */
168 static int
169 walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize], int type, u32int tag,
170 	u32int epoch)
171 {
172 	int i, ret;
173 	u32int addr, ep;
174 	Block *bb;
175 	Entry e;
176 
177 	if(b && chk->walkdepth == 0 && chk->printblocks)
178 		chk->print("%V %d %#.8ux %#.8ux\n", b->score, b->l.type,
179 			b->l.tag, b->l.epoch);
180 
181 	if(!chk->useventi && globalToLocal(score) == NilBlock)
182 		return 1;
183 
184 	chk->walkdepth++;
185 
186 	bb = cacheGlobal(chk->cache, score, type, tag, OReadOnly);
187 	if(bb == nil){
188 		error(chk, "could not load block %V type %d tag %ux: %R",
189 			score, type, tag);
190 		chk->walkdepth--;
191 		return 0;
192 	}
193 	if(chk->printblocks)
194 		chk->print("%*s%V %d %#.8ux %#.8ux\n", chk->walkdepth*2, "",
195 			score, type, tag, bb->l.epoch);
196 
197 	ret = 0;
198 	addr = globalToLocal(score);
199 	if(addr == NilBlock){
200 		ret = 1;
201 		goto Exit;
202 	}
203 
204 	if(b){
205 		/* (i) */
206 		if(b->l.epoch < bb->l.epoch || bb->l.epochClose <= b->l.epoch){
207 			error(chk, "walk: block %#ux [%ud, %ud) points at %#ux [%ud, %ud)",
208 				b->addr, b->l.epoch, b->l.epochClose,
209 				bb->addr, bb->l.epoch, bb->l.epochClose);
210 			goto Exit;
211 		}
212 
213 		/* (ii) */
214 		if(b->l.epoch == epoch && bb->l.epoch == epoch){
215 			if(getBit(chk->emap, addr)){
216 				error(chk, "walk: epoch join detected: addr %#ux %L",
217 					bb->addr, &bb->l);
218 				goto Exit;
219 			}
220 			setBit(chk->emap, addr);
221 		}
222 
223 		/* (iii) */
224 		if(!(b->l.state&BsCopied) && b->l.epoch == bb->l.epoch){
225 			if(getBit(chk->xmap, addr)){
226 				error(chk, "walk: copy join detected; addr %#ux %L",
227 					bb->addr, &bb->l);
228 				goto Exit;
229 			}
230 			setBit(chk->xmap, addr);
231 		}
232 	}
233 
234 	/* (iv) */
235 	if(epoch == chk->fs->ehi){
236 		/*
237 		 * since epoch==fs->ehi is first, amap is same as
238 		 * ``have seen active''
239 		 */
240 		if(getBit(chk->amap, addr)){
241 			error(chk, "walk: active join detected: addr %#ux %L",
242 				bb->addr, &bb->l);
243 			goto Exit;
244 		}
245 		if(bb->l.state&BsClosed)
246 			error(chk, "walk: addr %#ux: block is in active tree but is closed",
247 				addr);
248 	}else
249 		if(!getBit(chk->amap, addr))
250 			if(!(bb->l.state&BsClosed)){
251 				// error(chk, "walk: addr %#ux: block is not in active tree, not closed (%d)",
252 				// addr, bb->l.epochClose);
253 				chk->close(chk, bb, epoch+1);
254 				chk->nclose++;
255 			}
256 
257 	if(getBit(chk->amap, addr)){
258 		ret = 1;
259 		goto Exit;
260 	}
261 	setBit(chk->amap, addr);
262 
263 	if(chk->nseen++%chk->quantum == 0)
264 		chk->print("check: visited %d/%d blocks (%.0f%%)\n",
265 			chk->nseen, chk->nblocks, chk->nseen*100./chk->nblocks);
266 
267 	b = nil;		/* make sure no more refs to parent */
268 	USED(b);
269 
270 	switch(type){
271 	default:
272 		/* pointer block */
273 		for(i = 0; i < chk->bsize/VtScoreSize; i++)
274 			if(!walkEpoch(chk, bb, bb->data + i*VtScoreSize,
275 			    type-1, tag, epoch)){
276 				setBit(chk->errmap, bb->addr);
277 				chk->clrp(chk, bb, i);
278 				chk->nclrp++;
279 			}
280 		break;
281 	case BtData:
282 		break;
283 	case BtDir:
284 		for(i = 0; i < chk->bsize/VtEntrySize; i++){
285 			if(!entryUnpack(&e, bb->data, i)){
286 				// error(chk, "walk: could not unpack entry: %ux[%d]: %R",
287 				//	addr, i);
288 				setBit(chk->errmap, bb->addr);
289 				chk->clre(chk, bb, i);
290 				chk->nclre++;
291 				continue;
292 			}
293 			if(!(e.flags & VtEntryActive))
294 				continue;
295 if(0)			fprint(2, "%x[%d] tag=%x snap=%d score=%V\n",
296 				addr, i, e.tag, e.snap, e.score);
297 			ep = epoch;
298 			if(e.snap != 0){
299 				if(e.snap >= epoch){
300 					// error(chk, "bad snap in entry: %ux[%d] snap = %ud: epoch = %ud",
301 					//	addr, i, e.snap, epoch);
302 					setBit(chk->errmap, bb->addr);
303 					chk->clre(chk, bb, i);
304 					chk->nclre++;
305 					continue;
306 				}
307 				continue;
308 			}
309 			if(e.flags & VtEntryLocal){
310 				if(e.tag < UserTag)
311 				if(e.tag != RootTag || tag != RootTag || i != 1){
312 					// error(chk, "bad tag in entry: %ux[%d] tag = %ux",
313 					//	addr, i, e.tag);
314 					setBit(chk->errmap, bb->addr);
315 					chk->clre(chk, bb, i);
316 					chk->nclre++;
317 					continue;
318 				}
319 			}else
320 				if(e.tag != 0){
321 					// error(chk, "bad tag in entry: %ux[%d] tag = %ux",
322 					//	addr, i, e.tag);
323 					setBit(chk->errmap, bb->addr);
324 					chk->clre(chk, bb, i);
325 					chk->nclre++;
326 					continue;
327 				}
328 			if(!walkEpoch(chk, bb, e.score, entryType(&e),
329 			    e.tag, ep)){
330 				setBit(chk->errmap, bb->addr);
331 				chk->clre(chk, bb, i);
332 				chk->nclre++;
333 			}
334 		}
335 		break;
336 	}
337 
338 	ret = 1;
339 
340 Exit:
341 	chk->walkdepth--;
342 	blockPut(bb);
343 	return ret;
344 }
345 
346 /*
347  * We've just walked the whole write buffer.  Notice blocks that
348  * aren't marked available but that we didn't visit.  They are lost.
349  */
350 static void
351 checkLeak(Fsck *chk)
352 {
353 	u32int a, nfree, nlost;
354 	Block *b;
355 	Label l;
356 
357 	nfree = 0;
358 	nlost = 0;
359 
360 	for(a = 0; a < chk->nblocks; a++){
361 		if(!readLabel(chk->cache, &l, a)){
362 			error(chk, "could not read label: addr 0x%ux %d %d: %R",
363 				a, l.type, l.state);
364 			continue;
365 		}
366 		if(getBit(chk->amap, a))
367 			continue;
368 		if(l.state == BsFree || l.epochClose <= chk->fs->elo ||
369 		    l.epochClose == l.epoch){
370 			nfree++;
371 			setBit(chk->amap, a);
372 			continue;
373 		}
374 		if(l.state&BsClosed)
375 			continue;
376 		nlost++;
377 //		warn(chk, "unreachable block: addr 0x%ux type %d tag 0x%ux "
378 //			"state %s epoch %ud close %ud", a, l.type, l.tag,
379 //			bsStr(l.state), l.epoch, l.epochClose);
380 		b = cacheLocal(chk->cache, PartData, a, OReadOnly);
381 		if(b == nil){
382 			error(chk, "could not read block 0x%#.8ux", a);
383 			continue;
384 		}
385 		chk->close(chk, b, 0);
386 		chk->nclose++;
387 		setBit(chk->amap, a);
388 		blockPut(b);
389 	}
390 	chk->print("fsys blocks: total=%ud used=%ud(%.1f%%) free=%ud(%.1f%%) lost=%ud(%.1f%%)\n",
391 		chk->nblocks,
392 		chk->nblocks - nfree-nlost,
393 		100.*(chk->nblocks - nfree - nlost)/chk->nblocks,
394 		nfree, 100.*nfree/chk->nblocks,
395 		nlost, 100.*nlost/chk->nblocks);
396 }
397 
398 
399 /*
400  * Check that all sources in the tree are accessible.
401  */
402 static Source *
403 openSource(Fsck *chk, Source *s, char *name, uchar *bm, u32int offset,
404 	u32int gen, int dir, MetaBlock *mb, int i, Block *b)
405 {
406 	Source *r;
407 
408 	r = nil;
409 	if(getBit(bm, offset)){
410 		warn(chk, "multiple references to source: %s -> %d",
411 			name, offset);
412 		goto Err;
413 	}
414 	setBit(bm, offset);
415 
416 	r = sourceOpen(s, offset, OReadOnly, 0);
417 	if(r == nil){
418 		warn(chk, "could not open source: %s -> %d: %R", name, offset);
419 		goto Err;
420 	}
421 
422 	if(r->gen != gen){
423 		warn(chk, "source has been removed: %s -> %d", name, offset);
424 		goto Err;
425 	}
426 
427 	if(r->dir != dir){
428 		warn(chk, "dir mismatch: %s -> %d", name, offset);
429 		goto Err;
430 	}
431 	return r;
432 Err:
433 	chk->clri(chk, name, mb, i, b);
434 	chk->nclri++;
435 	if(r)
436 		sourceClose(r);
437 	return nil;
438 }
439 
440 typedef struct MetaChunk MetaChunk;
441 struct MetaChunk {
442 	ushort	offset;
443 	ushort	size;
444 	ushort	index;
445 };
446 
447 static int
448 offsetCmp(void *s0, void *s1)
449 {
450 	MetaChunk *mc0, *mc1;
451 
452 	mc0 = s0;
453 	mc1 = s1;
454 	if(mc0->offset < mc1->offset)
455 		return -1;
456 	if(mc0->offset > mc1->offset)
457 		return 1;
458 	return 0;
459 }
460 
461 /*
462  * Fsck that MetaBlock has reasonable header, sorted entries,
463  */
464 static int
465 chkMetaBlock(MetaBlock *mb)
466 {
467 	MetaChunk *mc;
468 	int oo, o, n, i;
469 	uchar *p;
470 
471 	mc = vtMemAlloc(mb->nindex*sizeof(MetaChunk));
472 	p = mb->buf + MetaHeaderSize;
473 	for(i = 0; i < mb->nindex; i++){
474 		mc[i].offset = p[0]<<8 | p[1];
475 		mc[i].size =   p[2]<<8 | p[3];
476 		mc[i].index = i;
477 		p += MetaIndexSize;
478 	}
479 
480 	qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
481 
482 	/* check block looks ok */
483 	oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
484 	o = oo;
485 	n = 0;
486 	for(i = 0; i < mb->nindex; i++){
487 		o = mc[i].offset;
488 		n = mc[i].size;
489 		if(o < oo)
490 			goto Err;
491 		oo += n;
492 	}
493 	if(o+n > mb->size || mb->size - oo != mb->free)
494 		goto Err;
495 
496 	vtMemFree(mc);
497 	return 1;
498 
499 Err:
500 if(0){
501 	fprint(2, "metaChunks failed!\n");
502 	oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
503 	for(i=0; i<mb->nindex; i++){
504 		fprint(2, "\t%d: %d %d\n", i, mc[i].offset,
505 			mc[i].offset + mc[i].size);
506 		oo += mc[i].size;
507 	}
508 	fprint(2, "\tused=%d size=%d free=%d free2=%d\n",
509 		oo, mb->size, mb->free, mb->size - oo);
510 }
511 	vtMemFree(mc);
512 	return 0;
513 }
514 
515 static void
516 scanSource(Fsck *chk, char *name, Source *r)
517 {
518 	u32int a, nb, o;
519 	Block *b;
520 	Entry e;
521 
522 	if(!chk->useventi && globalToLocal(r->score)==NilBlock)
523 		return;
524 	if(!sourceGetEntry(r, &e)){
525 		error(chk, "could not get entry for %s", name);
526 		return;
527 	}
528 	a = globalToLocal(e.score);
529 	if(!chk->useventi && a==NilBlock)
530 		return;
531 	if(getBit(chk->smap, a))
532 		return;
533 	setBit(chk->smap, a);
534 
535 	nb = (sourceGetSize(r) + r->dsize-1) / r->dsize;
536 	for(o = 0; o < nb; o++){
537 		b = sourceBlock(r, o, OReadOnly);
538 		if(b == nil){
539 			error(chk, "could not read block in data file %s", name);
540 			continue;
541 		}
542 		if(b->addr != NilBlock && getBit(chk->errmap, b->addr)){
543 			warn(chk, "previously reported error in block %ux is in file %s",
544 				b->addr, name);
545 		}
546 		blockPut(b);
547 	}
548 }
549 
550 /*
551  * Walk the source tree making sure that the BtData
552  * sources containing directory entries are okay.
553  */
554 static void
555 chkDir(Fsck *chk, char *name, Source *source, Source *meta)
556 {
557 	int i;
558 	u32int a1, a2, nb, o;
559 	char *s, *nn;
560 	uchar *bm;
561 	Block *b, *bb;
562 	DirEntry de;
563 	Entry e1, e2;
564 	MetaBlock mb;
565 	MetaEntry me;
566 	Source *r, *mr;
567 
568 	if(!chk->useventi && globalToLocal(source->score)==NilBlock &&
569 	    globalToLocal(meta->score)==NilBlock)
570 		return;
571 
572 	if(!sourceLock2(source, meta, OReadOnly)){
573 		warn(chk, "could not lock sources for %s: %R", name);
574 		return;
575 	}
576 	if(!sourceGetEntry(source, &e1) || !sourceGetEntry(meta, &e2)){
577 		warn(chk, "could not load entries for %s: %R", name);
578 		return;
579 	}
580 	a1 = globalToLocal(e1.score);
581 	a2 = globalToLocal(e2.score);
582 	if((!chk->useventi && a1==NilBlock && a2==NilBlock)
583 	|| (getBit(chk->smap, a1) && getBit(chk->smap, a2))){
584 		sourceUnlock(source);
585 		sourceUnlock(meta);
586 		return;
587 	}
588 	setBit(chk->smap, a1);
589 	setBit(chk->smap, a2);
590 
591 	bm = vtMemAllocZ(sourceGetDirSize(source)/8 + 1);
592 
593 	nb = (sourceGetSize(meta) + meta->dsize - 1)/meta->dsize;
594 	for(o = 0; o < nb; o++){
595 		b = sourceBlock(meta, o, OReadOnly);
596 		if(b == nil){
597 			error(chk, "could not read block in meta file: %s[%ud]: %R",
598 				name, o);
599 			continue;
600 		}
601 if(0)		fprint(2, "source %V:%d block %d addr %d\n", source->score,
602 			source->offset, o, b->addr);
603 		if(b->addr != NilBlock && getBit(chk->errmap, b->addr))
604 			warn(chk, "previously reported error in block %ux is in %s",
605 				b->addr, name);
606 
607 		if(!mbUnpack(&mb, b->data, meta->dsize)){
608 			error(chk, "could not unpack meta block: %s[%ud]: %R",
609 				name, o);
610 			blockPut(b);
611 			continue;
612 		}
613 		if(!chkMetaBlock(&mb)){
614 			error(chk, "bad meta block: %s[%ud]: %R", name, o);
615 			blockPut(b);
616 			continue;
617 		}
618 		s = nil;
619 		for(i=mb.nindex-1; i>=0; i--){
620 			meUnpack(&me, &mb, i);
621 			if(!deUnpack(&de, &me)){
622 				error(chk,
623 				  "could not unpack dir entry: %s[%ud][%d]: %R",
624 					name, o, i);
625 				continue;
626 			}
627 			if(s && strcmp(s, de.elem) <= 0)
628 				error(chk,
629 			   "dir entry out of order: %s[%ud][%d] = %s last = %s",
630 					name, o, i, de.elem, s);
631 			vtMemFree(s);
632 			s = vtStrDup(de.elem);
633 			nn = smprint("%s/%s", name, de.elem);
634 			if(nn == nil){
635 				error(chk, "out of memory");
636 				continue;
637 			}
638 			if(chk->printdirs)
639 				if(de.mode&ModeDir)
640 					chk->print("%s/\n", nn);
641 			if(chk->printfiles)
642 				if(!(de.mode&ModeDir))
643 					chk->print("%s\n", nn);
644 			if(!(de.mode & ModeDir)){
645 				r = openSource(chk, source, nn, bm, de.entry,
646 					de.gen, 0, &mb, i, b);
647 				if(r != nil){
648 					if(sourceLock(r, OReadOnly)){
649 						scanSource(chk, nn, r);
650 						sourceUnlock(r);
651 					}
652 					sourceClose(r);
653 				}
654 				deCleanup(&de);
655 				free(nn);
656 				continue;
657 			}
658 
659 			r = openSource(chk, source, nn, bm, de.entry,
660 				de.gen, 1, &mb, i, b);
661 			if(r == nil){
662 				deCleanup(&de);
663 				free(nn);
664 				continue;
665 			}
666 
667 			mr = openSource(chk, source, nn, bm, de.mentry,
668 				de.mgen, 0, &mb, i, b);
669 			if(mr == nil){
670 				sourceClose(r);
671 				deCleanup(&de);
672 				free(nn);
673 				continue;
674 			}
675 
676 			if(!(de.mode&ModeSnapshot) || chk->walksnapshots)
677 				chkDir(chk, nn, r, mr);
678 
679 			sourceClose(mr);
680 			sourceClose(r);
681 			deCleanup(&de);
682 			free(nn);
683 			deCleanup(&de);
684 
685 		}
686 		vtMemFree(s);
687 		blockPut(b);
688 	}
689 
690 	nb = sourceGetDirSize(source);
691 	for(o=0; o<nb; o++){
692 		if(getBit(bm, o))
693 			continue;
694 		r = sourceOpen(source, o, OReadOnly, 0);
695 		if(r == nil)
696 			continue;
697 		warn(chk, "non referenced entry in source %s[%d]", name, o);
698 		if((bb = sourceBlock(source, o/(source->dsize/VtEntrySize),
699 		    OReadOnly)) != nil){
700 			if(bb->addr != NilBlock){
701 				setBit(chk->errmap, bb->addr);
702 				chk->clre(chk, bb, o%(source->dsize/VtEntrySize));
703 				chk->nclre++;
704 			}
705 			blockPut(bb);
706 		}
707 		sourceClose(r);
708 	}
709 
710 	sourceUnlock(source);
711 	sourceUnlock(meta);
712 	vtMemFree(bm);
713 }
714 
715 static void
716 checkDirs(Fsck *chk)
717 {
718 	Source *r, *mr;
719 
720 	sourceLock(chk->fs->source, OReadOnly);
721 	r = sourceOpen(chk->fs->source, 0, OReadOnly, 0);
722 	mr = sourceOpen(chk->fs->source, 1, OReadOnly, 0);
723 	sourceUnlock(chk->fs->source);
724 	chkDir(chk, "", r, mr);
725 
726 	sourceClose(r);
727 	sourceClose(mr);
728 }
729 
730 static void
731 setBit(uchar *bmap, u32int addr)
732 {
733 	if(addr == NilBlock)
734 		return;
735 
736 	bmap[addr>>3] |= 1 << (addr & 7);
737 }
738 
739 static int
740 getBit(uchar *bmap, u32int addr)
741 {
742 	if(addr == NilBlock)
743 		return 0;
744 
745 	return (bmap[addr>>3] >> (addr & 7)) & 1;
746 }
747 
748 static void
749 error(Fsck *chk, char *fmt, ...)
750 {
751 	char buf[256];
752 	va_list arg;
753 	static int nerr;
754 
755 	va_start(arg, fmt);
756 	vseprint(buf, buf+sizeof buf, fmt, arg);
757 	va_end(arg);
758 
759 	chk->print("error: %s\n", buf);
760 
761 //	if(nerr++ > 20)
762 //		vtFatal("too many errors");
763 }
764 
765 static void
766 warn(Fsck *chk, char *fmt, ...)
767 {
768 	char buf[256];
769 	va_list arg;
770 	static int nerr;
771 
772 	va_start(arg, fmt);
773 	vseprint(buf, buf+sizeof buf, fmt, arg);
774 	va_end(arg);
775 
776 	chk->print("error: %s\n", buf);
777 }
778 
779 static void
780 clrenop(Fsck*, Block*, int)
781 {
782 }
783 
784 static void
785 closenop(Fsck*, Block*, u32int)
786 {
787 }
788 
789 static void
790 clrinop(Fsck*, char*, MetaBlock*, int, Block*)
791 {
792 }
793 
794 static int
795 printnop(char*, ...)
796 {
797 	return 0;
798 }
799