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