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