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