Blob


1 #include "stdinc.h"
2 #include "dat.h"
3 #include "fns.h"
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*, ...);
19 #pragma varargck argpos error 2
20 #pragma varargck argpos printnop 1
21 #pragma varargck argpos warn 2
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 }
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 */
55 void
56 fsCheck(Fsck *chk)
57 {
58 Block *b;
59 Super super;
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);
69 chk->hint = super.active;
70 checkEpochs(chk);
72 chk->smap = vtmallocz(chk->nblocks/8+1);
73 checkDirs(chk);
74 vtfree(chk->smap);
75 }
77 static void checkEpoch(Fsck*, u32int);
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;
89 nb = chk->nblocks;
90 chk->amap = vtmallocz(nb/8+1);
91 chk->emap = vtmallocz(nb/8+1);
92 chk->xmap = vtmallocz(nb/8+1);
93 chk->errmap = vtmallocz(nb/8+1);
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 vtfree(chk->amap);
102 vtfree(chk->emap);
103 vtfree(chk->xmap);
104 vtfree(chk->errmap);
107 static void
108 checkEpoch(Fsck *chk, u32int epoch)
110 u32int a;
111 Block *b;
112 Entry e;
113 Label l;
115 chk->print("checking epoch %ud...\n", epoch);
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 0x%.8#ux", a);
120 continue;
122 if(l.tag == RootTag && l.epoch == epoch)
123 break;
126 if(a == chk->nblocks){
127 chk->print("could not find root block for epoch %ud", epoch);
128 return;
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 0x%.8#ux: %r", a);
135 return;
138 /* no one should point at root blocks */
139 setBit(chk->amap, a);
140 setBit(chk->emap, a);
141 setBit(chk->xmap, a);
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 0x%.8#ux: %r", a);
150 blockPut(b);
151 return;
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);
159 /*
160 * When b points at bb, need to check:
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
167 * (too hard to check)
168 */
169 static int
170 walkEpoch(Fsck *chk, Block *b, uchar score[VtScoreSize], int type, u32int tag,
171 u32int epoch)
173 int i, ret;
174 u32int addr, ep;
175 Block *bb;
176 Entry e;
178 if(b && chk->walkdepth == 0 && chk->printblocks)
179 chk->print("%V %d %#.8ux %#.8ux\n", b->score, b->l.type,
180 b->l.tag, b->l.epoch);
182 if(!chk->useventi && globalToLocal(score) == NilBlock)
183 return 1;
185 chk->walkdepth++;
187 bb = cacheGlobal(chk->cache, score, type, tag, OReadOnly);
188 if(bb == nil){
189 error(chk, "could not load block %V type %d tag %ux: %r",
190 score, type, tag);
191 chk->walkdepth--;
192 return 0;
194 if(chk->printblocks)
195 chk->print("%*s%V %d %#.8ux %#.8ux\n", chk->walkdepth*2, "",
196 score, type, tag, bb->l.epoch);
198 ret = 0;
199 addr = globalToLocal(score);
200 if(addr == NilBlock){
201 ret = 1;
202 goto Exit;
205 if(b){
206 /* (i) */
207 if(b->l.epoch < bb->l.epoch || bb->l.epochClose <= b->l.epoch){
208 error(chk, "walk: block %#ux [%ud, %ud) points at %#ux [%ud, %ud)",
209 b->addr, b->l.epoch, b->l.epochClose,
210 bb->addr, bb->l.epoch, bb->l.epochClose);
211 goto Exit;
214 /* (ii) */
215 if(b->l.epoch == epoch && bb->l.epoch == epoch){
216 if(getBit(chk->emap, addr)){
217 error(chk, "walk: epoch join detected: addr %#ux %L",
218 bb->addr, &bb->l);
219 goto Exit;
221 setBit(chk->emap, addr);
224 /* (iii) */
225 if(!(b->l.state&BsCopied) && b->l.epoch == bb->l.epoch){
226 if(getBit(chk->xmap, addr)){
227 error(chk, "walk: copy join detected; addr %#ux %L",
228 bb->addr, &bb->l);
229 goto Exit;
231 setBit(chk->xmap, addr);
235 /* (iv) */
236 if(epoch == chk->fs->ehi){
237 /*
238 * since epoch==fs->ehi is first, amap is same as
239 * ``have seen active''
240 */
241 if(getBit(chk->amap, addr)){
242 error(chk, "walk: active join detected: addr %#ux %L",
243 bb->addr, &bb->l);
244 goto Exit;
246 if(bb->l.state&BsClosed)
247 error(chk, "walk: addr %#ux: block is in active tree but is closed",
248 addr);
249 }else
250 if(!getBit(chk->amap, addr))
251 if(!(bb->l.state&BsClosed)){
252 // error(chk, "walk: addr %#ux: block is not in active tree, not closed (%d)",
253 // addr, bb->l.epochClose);
254 chk->close(chk, bb, epoch+1);
255 chk->nclose++;
258 if(getBit(chk->amap, addr)){
259 ret = 1;
260 goto Exit;
262 setBit(chk->amap, addr);
264 if(chk->nseen++%chk->quantum == 0)
265 chk->print("check: visited %d/%d blocks (%.0f%%)\n",
266 chk->nseen, chk->nblocks, chk->nseen*100./chk->nblocks);
268 b = nil; /* make sure no more refs to parent */
269 USED(b);
271 switch(type){
272 default:
273 /* pointer block */
274 for(i = 0; i < chk->bsize/VtScoreSize; i++)
275 if(!walkEpoch(chk, bb, bb->data + i*VtScoreSize,
276 type-1, tag, epoch)){
277 setBit(chk->errmap, bb->addr);
278 chk->clrp(chk, bb, i);
279 chk->nclrp++;
281 break;
282 case BtData:
283 break;
284 case BtDir:
285 for(i = 0; i < chk->bsize/VtEntrySize; i++){
286 if(!entryUnpack(&e, bb->data, i)){
287 // error(chk, "walk: could not unpack entry: %ux[%d]: %r",
288 // addr, i);
289 setBit(chk->errmap, bb->addr);
290 chk->clre(chk, bb, i);
291 chk->nclre++;
292 continue;
294 if(!(e.flags & VtEntryActive))
295 continue;
296 if(0) fprint(2, "%x[%d] tag=%x snap=%d score=%V\n",
297 addr, i, e.tag, e.snap, e.score);
298 ep = epoch;
299 if(e.snap != 0){
300 if(e.snap >= epoch){
301 // error(chk, "bad snap in entry: %ux[%d] snap = %ud: epoch = %ud",
302 // addr, i, e.snap, epoch);
303 setBit(chk->errmap, bb->addr);
304 chk->clre(chk, bb, i);
305 chk->nclre++;
306 continue;
308 continue;
310 if(e.flags & VtEntryLocal){
311 if(e.tag < UserTag)
312 if(e.tag != RootTag || tag != RootTag || i != 1){
313 // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
314 // addr, i, e.tag);
315 setBit(chk->errmap, bb->addr);
316 chk->clre(chk, bb, i);
317 chk->nclre++;
318 continue;
320 }else
321 if(e.tag != 0){
322 // error(chk, "bad tag in entry: %ux[%d] tag = %ux",
323 // addr, i, e.tag);
324 setBit(chk->errmap, bb->addr);
325 chk->clre(chk, bb, i);
326 chk->nclre++;
327 continue;
329 if(!walkEpoch(chk, bb, e.score, entryType(&e),
330 e.tag, ep)){
331 setBit(chk->errmap, bb->addr);
332 chk->clre(chk, bb, i);
333 chk->nclre++;
336 break;
339 ret = 1;
341 Exit:
342 chk->walkdepth--;
343 blockPut(bb);
344 return ret;
347 /*
348 * We've just walked the whole write buffer. Notice blocks that
349 * aren't marked available but that we didn't visit. They are lost.
350 */
351 static void
352 checkLeak(Fsck *chk)
354 u32int a, nfree, nlost;
355 Block *b;
356 Label l;
358 nfree = 0;
359 nlost = 0;
361 for(a = 0; a < chk->nblocks; a++){
362 if(!readLabel(chk->cache, &l, a)){
363 error(chk, "could not read label: addr 0x%ux %d %d: %r",
364 a, l.type, l.state);
365 continue;
367 if(getBit(chk->amap, a))
368 continue;
369 if(l.state == BsFree || l.epochClose <= chk->fs->elo ||
370 l.epochClose == l.epoch){
371 nfree++;
372 setBit(chk->amap, a);
373 continue;
375 if(l.state&BsClosed)
376 continue;
377 nlost++;
378 // warn(chk, "unreachable block: addr 0x%ux type %d tag 0x%ux "
379 // "state %s epoch %ud close %ud", a, l.type, l.tag,
380 // bsStr(l.state), l.epoch, l.epochClose);
381 b = cacheLocal(chk->cache, PartData, a, OReadOnly);
382 if(b == nil){
383 error(chk, "could not read block 0x%#.8ux", a);
384 continue;
386 chk->close(chk, b, 0);
387 chk->nclose++;
388 setBit(chk->amap, a);
389 blockPut(b);
391 chk->print("fsys blocks: total=%ud used=%ud(%.1f%%) free=%ud(%.1f%%) lost=%ud(%.1f%%)\n",
392 chk->nblocks,
393 chk->nblocks - nfree-nlost,
394 100.*(chk->nblocks - nfree - nlost)/chk->nblocks,
395 nfree, 100.*nfree/chk->nblocks,
396 nlost, 100.*nlost/chk->nblocks);
400 /*
401 * Check that all sources in the tree are accessible.
402 */
403 static Source *
404 openSource(Fsck *chk, Source *s, char *name, uchar *bm, u32int offset,
405 u32int gen, int dir, MetaBlock *mb, int i, Block *b)
407 Source *r;
409 r = nil;
410 if(getBit(bm, offset)){
411 warn(chk, "multiple references to source: %s -> %d",
412 name, offset);
413 goto Err;
415 setBit(bm, offset);
417 r = sourceOpen(s, offset, OReadOnly, 0);
418 if(r == nil){
419 warn(chk, "could not open source: %s -> %d: %r", name, offset);
420 goto Err;
423 if(r->gen != gen){
424 warn(chk, "source has been removed: %s -> %d", name, offset);
425 goto Err;
428 if(r->dir != dir){
429 warn(chk, "dir mismatch: %s -> %d", name, offset);
430 goto Err;
432 return r;
433 Err:
434 chk->clri(chk, name, mb, i, b);
435 chk->nclri++;
436 if(r)
437 sourceClose(r);
438 return nil;
441 typedef struct MetaChunk MetaChunk;
442 struct MetaChunk {
443 ushort offset;
444 ushort size;
445 ushort index;
446 };
448 static int
449 offsetCmp(const void *s0, const void *s1)
451 MetaChunk *mc0, *mc1;
453 mc0 = (MetaChunk*)s0;
454 mc1 = (MetaChunk*)s1;
455 if(mc0->offset < mc1->offset)
456 return -1;
457 if(mc0->offset > mc1->offset)
458 return 1;
459 return 0;
462 /*
463 * Fsck that MetaBlock has reasonable header, sorted entries,
464 */
465 static int
466 chkMetaBlock(MetaBlock *mb)
468 MetaChunk *mc;
469 int oo, o, n, i;
470 uchar *p;
472 mc = vtmalloc(mb->nindex*sizeof(MetaChunk));
473 p = mb->buf + MetaHeaderSize;
474 for(i = 0; i < mb->nindex; i++){
475 mc[i].offset = p[0]<<8 | p[1];
476 mc[i].size = p[2]<<8 | p[3];
477 mc[i].index = i;
478 p += MetaIndexSize;
481 qsort(mc, mb->nindex, sizeof(MetaChunk), offsetCmp);
483 /* check block looks ok */
484 oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
485 o = oo;
486 n = 0;
487 for(i = 0; i < mb->nindex; i++){
488 o = mc[i].offset;
489 n = mc[i].size;
490 if(o < oo)
491 goto Err;
492 oo += n;
494 if(o+n > mb->size || mb->size - oo != mb->free)
495 goto Err;
497 vtfree(mc);
498 return 1;
500 Err:
501 if(0){
502 fprint(2, "metaChunks failed!\n");
503 oo = MetaHeaderSize + mb->maxindex*MetaIndexSize;
504 for(i=0; i<mb->nindex; i++){
505 fprint(2, "\t%d: %d %d\n", i, mc[i].offset,
506 mc[i].offset + mc[i].size);
507 oo += mc[i].size;
509 fprint(2, "\tused=%d size=%d free=%d free2=%d\n",
510 oo, mb->size, mb->free, mb->size - oo);
512 vtfree(mc);
513 return 0;
516 static void
517 scanSource(Fsck *chk, char *name, Source *r)
519 u32int a, nb, o;
520 Block *b;
521 Entry e;
523 if(!chk->useventi && globalToLocal(r->score)==NilBlock)
524 return;
525 if(!sourceGetEntry(r, &e)){
526 error(chk, "could not get entry for %s", name);
527 return;
529 a = globalToLocal(e.score);
530 if(!chk->useventi && a==NilBlock)
531 return;
532 if(getBit(chk->smap, a))
533 return;
534 setBit(chk->smap, a);
536 nb = (sourceGetSize(r) + r->dsize-1) / r->dsize;
537 for(o = 0; o < nb; o++){
538 b = sourceBlock(r, o, OReadOnly);
539 if(b == nil){
540 error(chk, "could not read block in data file %s", name);
541 continue;
543 if(b->addr != NilBlock && getBit(chk->errmap, b->addr)){
544 warn(chk, "previously reported error in block %ux is in file %s",
545 b->addr, name);
547 blockPut(b);
551 /*
552 * Walk the source tree making sure that the BtData
553 * sources containing directory entries are okay.
554 */
555 static void
556 chkDir(Fsck *chk, char *name, Source *source, Source *meta)
558 int i;
559 u32int a1, a2, nb, o;
560 char *s, *nn;
561 uchar *bm;
562 Block *b, *bb;
563 DirEntry de;
564 Entry e1, e2;
565 MetaBlock mb;
566 MetaEntry me;
567 Source *r, *mr;
569 if(!chk->useventi && globalToLocal(source->score)==NilBlock &&
570 globalToLocal(meta->score)==NilBlock)
571 return;
573 if(!sourceLock2(source, meta, OReadOnly)){
574 warn(chk, "could not lock sources for %s: %r", name);
575 return;
577 if(!sourceGetEntry(source, &e1) || !sourceGetEntry(meta, &e2)){
578 warn(chk, "could not load entries for %s: %r", name);
579 return;
581 a1 = globalToLocal(e1.score);
582 a2 = globalToLocal(e2.score);
583 if((!chk->useventi && a1==NilBlock && a2==NilBlock)
584 || (getBit(chk->smap, a1) && getBit(chk->smap, a2))){
585 sourceUnlock(source);
586 sourceUnlock(meta);
587 return;
589 setBit(chk->smap, a1);
590 setBit(chk->smap, a2);
592 bm = vtmallocz(sourceGetDirSize(source)/8 + 1);
594 nb = (sourceGetSize(meta) + meta->dsize - 1)/meta->dsize;
595 for(o = 0; o < nb; o++){
596 b = sourceBlock(meta, o, OReadOnly);
597 if(b == nil){
598 error(chk, "could not read block in meta file: %s[%ud]: %r",
599 name, o);
600 continue;
602 if(0) fprint(2, "source %V:%d block %d addr %d\n", source->score,
603 source->offset, o, b->addr);
604 if(b->addr != NilBlock && getBit(chk->errmap, b->addr))
605 warn(chk, "previously reported error in block %ux is in %s",
606 b->addr, name);
608 if(!mbUnpack(&mb, b->data, meta->dsize)){
609 error(chk, "could not unpack meta block: %s[%ud]: %r",
610 name, o);
611 blockPut(b);
612 continue;
614 if(!chkMetaBlock(&mb)){
615 error(chk, "bad meta block: %s[%ud]: %r", name, o);
616 blockPut(b);
617 continue;
619 s = nil;
620 for(i=mb.nindex-1; i>=0; i--){
621 meUnpack(&me, &mb, i);
622 if(!deUnpack(&de, &me)){
623 error(chk,
624 "could not unpack dir entry: %s[%ud][%d]: %r",
625 name, o, i);
626 continue;
628 if(s && strcmp(s, de.elem) <= 0)
629 error(chk,
630 "dir entry out of order: %s[%ud][%d] = %s last = %s",
631 name, o, i, de.elem, s);
632 vtfree(s);
633 s = vtstrdup(de.elem);
634 nn = smprint("%s/%s", name, de.elem);
635 if(nn == nil){
636 error(chk, "out of memory");
637 continue;
639 if(chk->printdirs)
640 if(de.mode&ModeDir)
641 chk->print("%s/\n", nn);
642 if(chk->printfiles)
643 if(!(de.mode&ModeDir))
644 chk->print("%s\n", nn);
645 if(!(de.mode & ModeDir)){
646 r = openSource(chk, source, nn, bm, de.entry,
647 de.gen, 0, &mb, i, b);
648 if(r != nil){
649 if(sourceLock(r, OReadOnly)){
650 scanSource(chk, nn, r);
651 sourceUnlock(r);
653 sourceClose(r);
655 deCleanup(&de);
656 free(nn);
657 continue;
660 r = openSource(chk, source, nn, bm, de.entry,
661 de.gen, 1, &mb, i, b);
662 if(r == nil){
663 deCleanup(&de);
664 free(nn);
665 continue;
668 mr = openSource(chk, source, nn, bm, de.mentry,
669 de.mgen, 0, &mb, i, b);
670 if(mr == nil){
671 sourceClose(r);
672 deCleanup(&de);
673 free(nn);
674 continue;
677 if(!(de.mode&ModeSnapshot) || chk->walksnapshots)
678 chkDir(chk, nn, r, mr);
680 sourceClose(mr);
681 sourceClose(r);
682 deCleanup(&de);
683 free(nn);
684 deCleanup(&de);
687 vtfree(s);
688 blockPut(b);
691 nb = sourceGetDirSize(source);
692 for(o=0; o<nb; o++){
693 if(getBit(bm, o))
694 continue;
695 r = sourceOpen(source, o, OReadOnly, 0);
696 if(r == nil)
697 continue;
698 warn(chk, "non referenced entry in source %s[%d]", name, o);
699 if((bb = sourceBlock(source, o/(source->dsize/VtEntrySize),
700 OReadOnly)) != nil){
701 if(bb->addr != NilBlock){
702 setBit(chk->errmap, bb->addr);
703 chk->clre(chk, bb, o%(source->dsize/VtEntrySize));
704 chk->nclre++;
706 blockPut(bb);
708 sourceClose(r);
711 sourceUnlock(source);
712 sourceUnlock(meta);
713 vtfree(bm);
716 static void
717 checkDirs(Fsck *chk)
719 Source *r, *mr;
721 sourceLock(chk->fs->source, OReadOnly);
722 r = sourceOpen(chk->fs->source, 0, OReadOnly, 0);
723 mr = sourceOpen(chk->fs->source, 1, OReadOnly, 0);
724 sourceUnlock(chk->fs->source);
725 chkDir(chk, "", r, mr);
727 sourceClose(r);
728 sourceClose(mr);
731 static void
732 setBit(uchar *bmap, u32int addr)
734 if(addr == NilBlock)
735 return;
737 bmap[addr>>3] |= 1 << (addr & 7);
740 static int
741 getBit(uchar *bmap, u32int addr)
743 if(addr == NilBlock)
744 return 0;
746 return (bmap[addr>>3] >> (addr & 7)) & 1;
749 static void
750 error(Fsck *chk, char *fmt, ...)
752 char buf[256];
753 va_list arg;
755 va_start(arg, fmt);
756 vseprint(buf, buf+sizeof buf, fmt, arg);
757 va_end(arg);
759 chk->print("error: %s\n", buf);
761 // if(nerr++ > 20)
762 // sysfatal("too many errors");
765 static void
766 warn(Fsck *chk, char *fmt, ...)
768 char buf[256];
769 va_list arg;
771 va_start(arg, fmt);
772 vseprint(buf, buf+sizeof buf, fmt, arg);
773 va_end(arg);
775 chk->print("error: %s\n", buf);
778 static void
779 clrenop(Fsck *chk, Block *b, int i)
781 USED(chk);
782 USED(b);
783 USED(i);
786 static void
787 closenop(Fsck *chk, Block *b, u32int i)
789 USED(chk);
790 USED(b);
791 USED(i);
794 static void
795 clrinop(Fsck *chk, char *c, MetaBlock *mb, int i, Block *b)
797 USED(chk);
798 USED(c);
799 USED(mb);
800 USED(i);
801 USED(b);
804 static int
805 printnop(char *c, ...)
807 USED(c);
808 return 0;