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