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