1#!/usr/pkg/bin/perl 2# 3# $NetBSD: check-all,v 1.5 2008/04/30 13:10:52 martin Exp $ 4# 5# Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc. 6# All rights reserved. 7# 8# This code is derived from software contributed to The NetBSD Foundation 9# by Konrad E. Schroder <perseant@hhhh.org>. 10# 11# Redistribution and use in source and binary forms, with or without 12# modification, are permitted provided that the following conditions 13# are met: 14# 1. Redistributions of source code must retain the above copyright 15# notice, this list of conditions and the following disclaimer. 16# 2. Redistributions in binary form must reproduce the above copyright 17# notice, this list of conditions and the following disclaimer in the 18# documentation and/or other materials provided with the distribution. 19# 20# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30# POSSIBILITY OF SUCH DAMAGE. 31# 32 33# 34# Use dumplfs to find all locations of the Ifile inode on a given disk. 35# Order these by serial number and call fsck_lfs on the raw disk for each. 36# If any fsck gives errors (any line of all capital letters, with a few 37# exceptions) print an error code with the daddr of the failing Ifile inode 38# location. 39# 40 41$| = 1; 42$rdev = $ARGV[0]; 43$gfile = $ARGV[1]; 44$wfile = $ARGV[2]; 45$sstart = $ARGV[3]; 46$test_rfw = 1; # $ARGV[4]; 47$rollid = 0; 48open(DUMPLFS, "dumplfs $rdev |"); 49 50# Look for "roll_id" so we don't use garbage 51while (<DUMPLFS>) { 52 if ($ssize == 0 && m/ssize *([0-9]*)/) { 53 $ssize = $1; 54 } 55 if ($fsize == 0 && m/fsize *([0-9]*)/) { 56 $fsize = $1; 57 } 58 if (m/roll_id *([x0-9a-f]*)/) { 59 $rollid = $1; 60 last; 61 } 62} 63 64# Now look for inodes and segment summaries. Build a hash table of these 65# based on serial number. Ignore any with serial numbers lower than $sstart. 66 67%iloc = (); 68%snloc = (); 69%sumloc = (); 70print "Reading segments:"; 71while (<DUMPLFS>) { 72 if (m/roll_id *([0-9a-f]*)/) { 73 # print "rollid $1\n"; 74 if ("0x$1" ne $rollid) { 75 # Skip the rest of this segment 76 print "{skip bad rollid 0x$1}"; 77 while(<DUMPLFS>) { 78 last if m/SEGMENT/; 79 } 80 # Fall through 81 } 82 } 83 if (m/roll_id.*serial *([0-9]*)/) { 84 $serno = $1; 85 $snloc{$serno} = $segnum; 86 $sumloc{$serno} = $sumloc; 87 print "($serno)"; 88 if ($serno < $sstart) { 89 # Skip the rest of this partial segment 90 #print "{skip bad serno $serno}"; 91 while(<DUMPLFS>) { 92 last if m/Segment Summary/ || 93 m/SEGMENT/; 94 } 95 # Fall through 96 } 97 } 98 if (m/Segment Summary Info at 0x([0-9a-f]*)/) { 99 $sumloc = $1; 100 next; 101 } 102 if (m/0x([0-9a-f]*)/) { 103 foreach $ss (split "0x", $_) { 104 if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) { 105 # print "iblk 0x$1\n"; 106 $daddr = $1; 107 if (m/[^0-9]1v1/) { 108 # print "** ifblk 0x$daddr\n"; 109 $iloc{$serno} = $daddr; 110 $lastaddr = $daddr; 111 } 112 } 113 } 114 } 115 if (m/SEGMENT *([0-9]*)/) { 116 $segnum = $1; 117 print "[$segnum]"; 118 } 119} 120print "\n"; 121close(DUMPLFS); 122 123# Complain about missing partial-segments 124for ($i = $sstart; $i < $serno; ++$i) { 125 if (hex $sumloc{$i} == 0 && $i > 0) { 126 print "Oops, couldn't find pseg $i\n"; 127 } 128} 129 130# If there were no checkpoints, print *something* 131if ($#iloc == 0) { 132 print "0 $sstart 0\n"; 133 exit 0; 134} 135 136# 137# Now fsck each checkpoint in turn, beginning with $sstart. 138# Because the log wraps we will have to reconstruct the filesystem image 139# as it existed at each checkpoint before running fsck. 140# 141# Look for lines containing only caps or "!", but ignore known 142# false positives. 143# 144$error = 0; 145$lastgood = $sstart - 1; 146open(LOG, ">>check-all.log"); 147print "Available checkpoints:"; 148print LOG "Available checkpoints:"; 149foreach $k (sort { $a <=> $b } keys %iloc) { 150 $a = $iloc{$k}; 151 print " $a"; 152 print LOG " $a"; 153} 154print "\n"; 155print LOG "\n"; 156 157# 158# Copy the partial segments $_[0]--$_[1] from the raw device onto 159# the working file. Return the next partial-segment serial number 160# after the last one we copied (usually $_[1] + 1, except in case of 161# an error). 162# 163sub copypseg 164{ 165 my ($blstart, $blstop, $segstop, $cmd); 166 my ($totalstart, $totalstop); 167 168 $totalstart = 0; 169 $totalstop = 0; 170 for ($i = $_[0]; $i <= $_[1]; ++$i) { 171 $blstart = hex $sumloc{$i}; 172 last if $blstart <= 0; 173 $totalstart = $blstart if $totalstart == 0; 174 $blstop = hex $sumloc{$i + 1}; 175 $segstop = ((int ($blstart / $fps)) + 1) * $fps; 176 if ($segstop < $blstop || $blstop < $blstart) { 177 #print "Adjusting $blstop -> $segstop\n"; 178 $blstop = $segstop; 179 } 180 $totalstop = $blstop; 181 182 print "pseg $i: write blocks ", hex $blstart, "-", hex ($blstop - 1), "\n"; 183 $blstart = $blstop; 184 } 185 $cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$totalstart " . 186 "skip=$totalstart conv=notrunc count=" . 187 ($totalstop - $totalstart); 188# print "$cmd\n"; 189 system("$cmd >/dev/null 2>&1"); 190 191 return $i; 192} 193 194print "Recreating filesystem image as of $sstart:\n"; 195if ($sstart == 0) { 196 $cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage 197} else { 198 $cmd = "dd if=$gfile of=$wfile bs=1m"; 199} 200print "$cmd\n"; 201system("$cmd >/dev/null 2>&1"); 202 203print "Copying over first superblock\n"; 204system("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1"); 205 206sub test_fsck 207{ 208 my $a = $_[0]; 209 my $flags = $_[1]; 210 my $printit = $_[2]; 211 my $output = ""; 212 213 $flags = "-n -f -i 0x$a $wfile" unless $flags; 214 215 $cmd = "fsck_lfs $flags"; 216 print "$cmd\n"; 217 print LOG "$cmd\n"; 218 open(FSCK, "$cmd 2>&1 |"); 219 while(<FSCK>) { 220 print LOG; 221 $rline = "$_"; 222 chomp; 223 224 # Known false positives (mismatch between sb and ifile, 225 # which should be expected given we're using an arbitrarily 226 # old version of the ifile) 227 if (m/AVAIL GIVEN/ || 228 m/BFREE GIVEN/ || 229 m/DMETA GIVEN/ || 230 m/NCLEAN GIVEN/ || 231 m/FREE BUT NOT ON FREE LIST/ || # UNWRITTEN inodes OK 232 m/FILE SYSTEM WAS MODIFIED/ || 233 m/FREE LIST HEAD IN SUPERBLOCK/ ) { 234 next; 235 } 236 237 # Fsck reports errors in ALL CAPS 238 # But don't count hex numbers as "lowercase". 239 $oline = "$_"; 240 s/0x[0-9a-f]*//g; 241 if (m/[A-Z]/ && ! m/[a-z]/) { 242 $error = 1; 243 $errsn = $k; 244 $errstr = "1 $k 0x$a $oline"; 245 # last; 246 } 247 248 # Log everything we get, except for some things we 249 # will see every single time. 250 if (m/checkpoint invalid/ || 251 m/skipping free list check/ || 252 m/expect discrepancies/) { 253 next; 254 } 255 $output .= $rline; 256 } 257 close(FSCK); 258 259 if ($? != 0) { 260 $error = 1; 261 $errsn = $k; 262 $errstr = "1 $k 0x$a <" . (hex $?) . ">"; 263 } 264 265 if ($error || $printit) { 266 print $output; 267 } 268} 269 270$blstart = 0; 271$fps = $ssize / $fsize; 272$oind = ($sstart ? $sstart : 1); 273BIGLOOP: foreach $k (sort { $a <=> $b } keys %iloc) { 274 $a = $iloc{$k}; 275 276 if (hex($a) > hex($lastaddr)) { 277 print "Skipping out-of-place checkpoint $k at $a\n"; 278 next; 279 } 280 281 if ($test_rfw && $iloc{$oind - 1}) { 282 for ($tk = $oind; $tk < $k; $tk++) { 283 print "Test roll-forward agent at non-checkpoint pseg $tk\n"; 284 print LOG "Test roll-forward agent at non-checkpoint pseg $tk\n"; 285 ©pseg($oind, $tk); 286 # Add -d flag here for verbose debugging info 287 $flags = "-p -f -i 0x" . $iloc{$oind - 1} . " $wfile"; 288 &test_fsck($iloc{$oind - 1}, $flags, 1); 289 last BIGLOOP if $error; 290 291 # note lack of -i flag, since the roll-forward 292 # will have rewritten the superblocks. 293 &test_fsck($iloc{$oind - 1}, "-n -f $wfile", 0); 294 last BIGLOOP if $error; 295 } 296 } 297 298 print "Recreate fs state at checkpoint pseg $k (from " . ($oind - 1) . 299 ")\n"; 300 $oind = ©pseg($oind, $k); 301 302 &test_fsck($a, "", 0); 303 304 last if $error; 305 $lastgood = $k; # record last good serial number 306} 307 308if ($errstr) { 309 print "$errstr\n"; 310 exit 0; 311} 312 313if (!$errstr) { 314 print "Bring filesystem state up to log wrap\n"; 315 $lastgood = ©pseg($oind, 100000000000) - 1; 316 317 print "Copying this good image to $gfile\n"; 318 system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1"); 319 print "0 $lastgood 0x$a\n"; 320 exit 0; 321} 322 323# 324# Ifile write-checking paranoia. 325# 326# If we found an error, try to find which blocks of the Ifile inode changed 327# between the last good checkpoint and this checkpoint; and which blocks 328# *should* have changed. This means (1) which segments were written; and 329# (2) which inodes were written. The 0 block of the Ifile should always 330# have changed since lfs_avail is always in flux. 331# 332 333$cmd = "dumplfs"; 334$oseg = -1; 335%iblk = (); 336%iblk_done = (); 337%why = (); 338$iblk{0} = 1; 339for ($i = $lastgood + 1; $i <= $errsn; $i++) { 340 if ($oseg != $snloc{$i}) { 341 $oseg = 0 + $snloc{$i}; 342 $cmd .= " -s$oseg"; 343 } 344} 345$cmd .= " $rdev"; 346 347open(DUMPLFS, "$cmd |"); 348while(<DUMPLFS>) { 349 if (m/ifpb *([0-9]*)/) { 350 $ifpb = $1; 351 } 352 if (m/sepb *([0-9]*)/) { 353 $sepb = $1; 354 } 355 if (m/cleansz *([0-9]*)/) { 356 $cleansz = $1; 357 } 358 if (m/segtabsz *([0-9]*)/) { 359 $segtabsz = $1; 360 } 361 last if m/SEGMENT/; 362} 363while(<DUMPLFS>) { 364 chomp; 365 366 # Skip over partial segments outside our range of interest 367 if (m/roll_id.*serial *([0-9]*)/) { 368 $serno = $1; 369 if ($serno <= $lastgood || $serno > $errsn) { 370 # Skip the rest of this partial segment 371 while(<DUMPLFS>) { 372 last if m/Segment Summary/ || m/SEGMENT/; 373 } 374 next; 375 } 376 } 377 378 # Look for inodes 379 if (m/Inode addresses/) { 380 s/^[^{]*{/ /o; 381 s/}[^{]*$/ /o; 382 s/}[^{]*{/,/og; 383 s/v[0-9]*//og; 384 @ilist = split(','); 385 foreach $i (@ilist) { 386 $i =~ s/ *//og; 387 next if $i == 1; 388 $iaddr = $cleansz + $segtabsz + int ($i / $ifpb); 389 $iblk{$iaddr} = 1; 390 $why{$iaddr} .= " $i"; 391 } 392 } 393 394 # Look for Ifile blocks actually written 395 if (m/FINFO for inode: ([0-9]*) version/) { 396 $i = $1; 397 $inoblkmode = ($i == 1); 398 } 399 if ($inoblkmode && m/^[-\t 0-9]*$/) { 400 s/\t/ /og; 401 s/^ *//o; 402 s/ *$//o; 403 @bn = split(' '); 404 foreach $b (@bn) { 405 $iblk_done{$b} = 1; 406 } 407 } 408} 409close(DUMPLFS); 410 411# Report found and missing Ifile blocks 412print "Ifile blocks found:"; 413foreach $b (sort { $a <=> $b } keys %iblk) { 414 if ($iblk_done{$b} == 1) { 415 print " $b"; 416 } 417} 418print "\n"; 419 420print "Ifile blocks missing:"; 421foreach $b (sort { $a <=> $b } keys %iblk) { 422 if ($iblk_done{$b} == 0) { 423 $why{$b} =~ s/^ *//o; 424 print " $b ($why{$b})"; 425 } 426} 427print "\n"; 428 429print "$errstr\n"; 430exit 0; 431