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; 16open(DUMPLFS, "dumplfs $rdev |"); 17 18# Look for "roll_id" so we don't use garbage 19while (<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 = (); 38print "Reading segments:"; 39while (<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} 85print "\n"; 86close(DUMPLFS); 87 88# If there were no checkpoints, print *something* 89if ($#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; 104open(LOG, ">>check-all.log"); 105print "Available checkpoints:"; 106print LOG "Available checkpoints:"; 107foreach $k (sort { $a <=> $b } keys %iloc) { 108 $a = $iloc{$k}; 109 print " $a"; 110 print LOG " $a"; 111} 112print "\n"; 113print LOG "\n"; 114 115print "Recreating filesystem image as of $sstart:\n"; 116if ($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} 121print "$cmd\n"; 122system("$cmd >/dev/null 2>&1"); 123 124$blstart = 0; 125$fps = $ssize / $fsize; 126foreach $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 184if ($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; 205for ($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 213open(DUMPLFS, "$cmd |"); 214while(<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} 229while(<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} 275close(DUMPLFS); 276 277# Report found and missing Ifile blocks 278print "Ifile blocks found:"; 279foreach $b (sort { $a <=> $b } keys %iblk) { 280 if ($iblk_done{$b} == 1) { 281 print " $b"; 282 } 283} 284print "\n"; 285 286print "Ifile blocks missing:"; 287foreach $b (sort { $a <=> $b } keys %iblk) { 288 if ($iblk_done{$b} == 0) { 289 $why{$b} =~ s/^ *//o; 290 print " $b ($why{$b})"; 291 } 292} 293print "\n"; 294 295print "$errstr\n"; 296exit 0; 297