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