Home | History | Annotate | Line # | Download | only in ckckp
check-all revision 1.5
      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 (at] 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;
     48 open(DUMPLFS, "dumplfs $rdev |");
     49 
     50 # Look for "roll_id" so we don't use garbage
     51 while (<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 = ();
     70 print "Reading segments:";
     71 while (<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 }
    120 print "\n";
    121 close(DUMPLFS);
    122 
    123 # Complain about missing partial-segments
    124 for ($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*
    131 if ($#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;
    146 open(LOG, ">>check-all.log");
    147 print "Available checkpoints:";
    148 print LOG "Available checkpoints:";
    149 foreach $k (sort { $a <=> $b } keys %iloc) {
    150 	$a = $iloc{$k};
    151 	print " $a";
    152 	print LOG " $a";
    153 }
    154 print "\n";
    155 print 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 #
    163 sub 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 
    194 print "Recreating filesystem image as of $sstart:\n";
    195 if ($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 }
    200 print "$cmd\n";
    201 system("$cmd >/dev/null 2>&1");
    202 
    203 print "Copying over first superblock\n";
    204 system("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1");
    205 
    206 sub 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);
    273 BIGLOOP: 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 			&copypseg($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 = &copypseg($oind, $k);
    301 
    302 	&test_fsck($a, "", 0);
    303 
    304 	last if $error;
    305 	$lastgood = $k;	# record last good serial number
    306 }
    307 
    308 if ($errstr) {
    309 	print "$errstr\n";
    310 	exit 0;
    311 }
    312 
    313 if (!$errstr) {
    314 	print "Bring filesystem state up to log wrap\n";
    315 	$lastgood = &copypseg($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;
    339 for ($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 
    347 open(DUMPLFS, "$cmd |");
    348 while(<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 }
    363 while(<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 }
    409 close(DUMPLFS);
    410 
    411 # Report found and missing Ifile blocks
    412 print "Ifile blocks found:";
    413 foreach $b (sort { $a <=> $b } keys %iblk) {
    414 	if ($iblk_done{$b} == 1) {
    415 		print " $b";
    416 	}
    417 }
    418 print "\n";
    419 	
    420 print "Ifile blocks missing:";
    421 foreach $b (sort { $a <=> $b } keys %iblk) {
    422 	if ($iblk_done{$b} == 0) {
    423 		$why{$b} =~ s/^ *//o;
    424 		print " $b ($why{$b})";
    425 	}
    426 }
    427 print "\n";
    428 
    429 print "$errstr\n";
    430 exit 0;
    431