Home | History | Annotate | Line # | Download | only in ckckp
check-all revision 1.2
      1 #!/usr/pkg/bin/perl
      2 
      3 #
      4 # Use dumplfs to find all locations of the Ifile inode on a given disk.
      5 # Order these by serial number and call fsck_lfs on the raw disk for each.
      6 # If any fsck gives errors (lines containing "!", with a few exceptions)
      7 # print an error code with the daddr of the failing Ifile inode location.
      8 #
      9 
     10 $| = 1;
     11 $rdev = $ARGV[0];
     12 $gfile = $ARGV[1];
     13 $wfile = $ARGV[2];
     14 $sstart = $ARGV[3];
     15 $rollid = 0;
     16 open(DUMPLFS, "dumplfs $rdev |");
     17 
     18 # Look for "roll_id" so we don't use garbage
     19 while (<DUMPLFS>) {
     20 	if ($ssize == 0 && m/ssize *([0-9]*)/) {
     21 		$ssize = $1;
     22 	}
     23 	if ($fsize == 0 && m/fsize *([0-9]*)/) {
     24 		$fsize = $1;
     25 	}
     26 	if (m/roll_id *([x0-9a-f]*)/) {
     27 		$rollid = $1;
     28 		last;
     29 	}
     30 }
     31 
     32 # Now look for inodes and segment summaries.  Build a hash table of these
     33 # based on serial number.  Ignore any with serial numbers lower than $sstart.
     34 
     35 %iloc = ();
     36 %snloc = ();
     37 %sumloc = ();
     38 print "Reading segments:";
     39 while (<DUMPLFS>) {
     40 	if (m/roll_id *([0-9a-f]*)/) {
     41 		# print "rollid $1\n";
     42 		if ("0x$1" ne $rollid) {
     43 			# Skip the rest of this segment
     44 			while(<DUMPLFS>) {
     45 				last if m/SEGMENT/;
     46 			}
     47 			# Fall through
     48 		}
     49 	}
     50 	if (m/roll_id.*serial *([0-9]*)/) {
     51 		$serno = $1;
     52 		$snloc{$serno} = $segnum;
     53 		$sumloc{$serno} = $sumloc;
     54 		# print "serno $serno\n";
     55 		if ($serno < $sstart) {
     56 			# Skip the rest of this partial segment
     57 			while(<DUMPLFS>) {
     58 				last if m/Segment Summary/ ||
     59 					m/SEGMENT/;
     60 			}
     61 			# Fall through
     62 		}
     63 	}
     64 	if (m/Segment Summary Info at 0x([0-9a-f]*)/) {
     65 		$sumloc = $1;
     66 	}
     67 	if (m/0x([0-9a-f]*)/) {
     68 		foreach $ss (split "0x", $_) {
     69 			if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) {
     70 				# print "iblk 0x$1\n";
     71 				$daddr = $1;
     72 				if (m/[^0-9]1v1/) {
     73 					# print "** ifblk 0x$daddr\n";
     74 					$iloc{$serno} = $daddr;
     75 					$lastaddr = $daddr;
     76 				}
     77 			}
     78 		}
     79 	}
     80 	if (m/SEGMENT *([0-9]*)/) {
     81 		$segnum = $1;
     82 		print " $segnum";
     83 	}
     84 }
     85 print "\n";
     86 close(DUMPLFS);
     87 
     88 # If there were no checkpoints, print *something*
     89 if ($#iloc == 0) {
     90 	print "0 $sstart 0\n";
     91 	exit 0;
     92 }
     93 
     94 #
     95 # Now fsck each checkpoint in turn, beginning with $sstart.
     96 # Because the log wraps we will have to reconstruct the filesystem image
     97 # as it existed at each checkpoint before running fsck.
     98 #
     99 # Look for lines containing only caps or "!", but ignore known
    100 # false positives.
    101 #
    102 $error = 0;
    103 $lastgood = $sstart - 1;
    104 open(LOG, ">>check-all.log");
    105 print "Available checkpoints:";
    106 print LOG "Available checkpoints:";
    107 foreach $k (sort { $a <=> $b } keys %iloc) {
    108 	$a = $iloc{$k};
    109 	print " $a";
    110 	print LOG " $a";
    111 }
    112 print "\n";
    113 print LOG "\n";
    114 
    115 print "Recreating filesystem image as of $sstart:\n";
    116 if ($sstart == 0) {
    117 	$cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage
    118 } else {
    119 	$cmd = "dd if=$gfile of=$wfile bs=1m";
    120 }
    121 print "$cmd\n";
    122 system("$cmd >/dev/null 2>&1");
    123 
    124 $blstart = 0;
    125 $fps = $ssize / $fsize;
    126 foreach $k (sort { $a <=> $b } keys %iloc) {
    127 	$a = $iloc{$k};
    128 
    129 	if (hex($a) > hex($lastaddr)) {
    130 		print "Skipping out-of-place checkpoint $k at $a\n";
    131 		next;
    132 	}
    133 
    134 	# Copy the balance of the "new" fs image over the old one.
    135 	$blstop = int ((hex $a) / $fps) + 1;
    136 	$blstop *= $fps;
    137 	if ($blstop != $blstart) {
    138 		$cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$blstart " .
    139 			"skip=$blstart conv=notrunc";
    140 		if ($blstop) {
    141 			$cmd .= " count=" . ($blstop - $blstart);
    142 		}
    143 		$blstart = $blstop;
    144 		print "$cmd\n";
    145 		system("$cmd >/dev/null 2>&1");
    146 	}
    147 
    148 	$cmd = "fsck_lfs -n -f -i 0x$a $wfile";
    149 	print "$cmd\n";
    150 	print LOG "$cmd\n";
    151 	open(FSCK, "$cmd 2>&1 |");
    152 	while(<FSCK>) {
    153 		print LOG;
    154 		chomp;
    155 
    156 		# Known false positives (mismatch between sb and ifile,
    157 		# which should be expected given we're using an arbitrarily
    158 		# old version fo the ifile)
    159 		if (m/AVAIL GIVEN/ ||
    160 		    m/BFREE GIVEN/ ||
    161 		    m/DMETA GIVEN/ ||
    162 		    m/NCLEAN GIVEN/ ||
    163 		    m/FREE BUT NOT ON FREE LIST/ ||	# UNWRITTEN inodes OK
    164 		    m/FREE LIST HEAD IN SUPERBLOCK/ ) {
    165 			next;
    166 		}
    167 
    168 		# Fsck reports errors in ALL CAPS
    169 		# But don't count hex numbers as "lowercase".
    170 		s/0x[0-9a-f]*//g;
    171 		if (m/[A-Z]/ && ! m/[a-z]/) {
    172 			$error = 1;
    173 			$errsn = $k;
    174 			$errstr = "1 $k 0x$a";
    175 			last;
    176 		}
    177 	}
    178 	close(FSCK);
    179 	last if $error;
    180 	$lastgood = $k;	# record last good serial number
    181 }
    182 $errstr = "0 $lastgood 0x$a" unless $errstr;
    183 
    184 if ($error == 0) {
    185 	print "Copying this good image to $gfile\n";
    186 	system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1");
    187 	print "$errstr\n";
    188 	exit 0;
    189 }
    190 
    191 #
    192 # If we found an error, try to find which blocks of the Ifile inode changed
    193 # between the last good checkpoint and this checkpoint; and which blocks
    194 # *should* have changed.  This means (1) which segments were written; and
    195 # (2) which inodes were written.  The 0 block of the Ifile should always
    196 # have changed since lfs_avail is always in flux.
    197 #
    198 
    199 $cmd = "dumplfs";
    200 $oseg = -1;
    201 %iblk = ();
    202 %iblk_done = ();
    203 %why = ();
    204 $iblk{0} = 1;
    205 for ($i = $lastgood + 1; $i <= $errsn; $i++) {
    206 	if ($oseg != $snloc{$i}) {
    207 		$oseg = 0 + $snloc{$i};
    208 		$cmd .= " -s$oseg";
    209 	}
    210 }
    211 $cmd .= " $rdev";
    212 
    213 open(DUMPLFS, "$cmd |");
    214 while(<DUMPLFS>) {
    215 	if (m/ifpb *([0-9]*)/) {
    216 		$ifpb = $1;
    217 	}
    218 	if (m/sepb *([0-9]*)/) {
    219 		$sepb = $1;
    220 	}
    221 	if (m/cleansz *([0-9]*)/) {
    222 		$cleansz = $1;
    223 	}
    224 	if (m/segtabsz *([0-9]*)/) {
    225 		$segtabsz = $1;
    226 	}
    227 	last if m/SEGMENT/;
    228 }
    229 while(<DUMPLFS>) {
    230 	chomp;
    231 
    232 	# Skip over partial segments outside our range of interest
    233 	if (m/roll_id.*serial *([0-9]*)/) {
    234 		$serno = $1;
    235 		if ($serno <= $lastgood || $serno > $errsn) {
    236 			# Skip the rest of this partial segment
    237 			while(<DUMPLFS>) {
    238 				last if m/Segment Summary/ || m/SEGMENT/;
    239 			}
    240 			next;
    241 		}
    242 	}
    243 
    244 	# Look for inodes
    245 	if (m/Inode addresses/) {
    246 		s/^[^{]*{/ /o;
    247 		s/}[^{]*$/ /o;
    248 		s/}[^{]*{/,/og;
    249 		s/v[0-9]*//og;
    250 		@ilist = split(',');
    251 		foreach $i (@ilist) {
    252 			$i =~ s/ *//og;
    253 			next if $i == 1;
    254 			$iaddr = $cleansz + $segtabsz + int ($i / $ifpb);
    255 			$iblk{$iaddr} = 1;
    256 			$why{$iaddr} .= " $i";
    257 		}
    258 	}
    259 
    260 	# Look for Ifile blocks actually written
    261 	if (m/FINFO for inode: ([0-9]*) version/) {
    262 		$i = $1;
    263 		$inoblkmode = ($i == 1);
    264 	}
    265 	if ($inoblkmode && m/^[-\t 0-9]*$/) {
    266 		s/\t/ /og;
    267 		s/^ *//o;
    268 		s/ *$//o;
    269 		@bn = split(' ');
    270 		foreach $b (@bn) {
    271 			$iblk_done{$b} = 1;
    272 		}
    273 	}
    274 }
    275 close(DUMPLFS);
    276 
    277 # Report found and missing Ifile blocks
    278 print "Ifile blocks found:";
    279 foreach $b (sort { $a <=> $b } keys %iblk) {
    280 	if ($iblk_done{$b} == 1) {
    281 		print " $b";
    282 	}
    283 }
    284 print "\n";
    285 
    286 print "Ifile blocks missing:";
    287 foreach $b (sort { $a <=> $b } keys %iblk) {
    288 	if ($iblk_done{$b} == 0) {
    289 		$why{$b} =~ s/^ *//o;
    290 		print " $b ($why{$b})";
    291 	}
    292 }
    293 print "\n";
    294 
    295 print "$errstr\n";
    296 exit 0;
    297