#!/usr/pkg/bin/perl # # Use dumplfs to find all locations of the Ifile inode on a given disk. # Order these by serial number and call fsck_lfs on the raw disk for each. # If any fsck gives errors (lines containing "!", with a few exceptions) # print an error code with the daddr of the failing Ifile inode location. # $| = 1; $rdev = $ARGV[0]; $gfile = $ARGV[1]; $wfile = $ARGV[2]; $sstart = $ARGV[3]; $rollid = 0; open(DUMPLFS, "dumplfs $rdev |"); # Look for "roll_id" so we don't use garbage while () { if ($ssize == 0 && m/ssize *([0-9]*)/) { $ssize = $1; } if ($fsize == 0 && m/fsize *([0-9]*)/) { $fsize = $1; } if (m/roll_id *([x0-9a-f]*)/) { $rollid = $1; last; } } # Now look for inodes and segment summaries. Build a hash table of these # based on serial number. Ignore any with serial numbers lower than $sstart. %iloc = (); %snloc = (); %sumloc = (); print "Reading segments:"; while () { if (m/roll_id *([0-9a-f]*)/) { # print "rollid $1\n"; if ("0x$1" ne $rollid) { # Skip the rest of this segment while() { last if m/SEGMENT/; } # Fall through } } if (m/roll_id.*serial *([0-9]*)/) { $serno = $1; $snloc{$serno} = $segnum; $sumloc{$serno} = $sumloc; # print "serno $serno\n"; if ($serno < $sstart) { # Skip the rest of this partial segment while() { last if m/Segment Summary/ || m/SEGMENT/; } # Fall through } } if (m/Segment Summary Info at 0x([0-9a-f]*)/) { $sumloc = $1; } if (m/0x([0-9a-f]*)/) { foreach $ss (split "0x", $_) { if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) { # print "iblk 0x$1\n"; $daddr = $1; if (m/[^0-9]1v1/) { # print "** ifblk 0x$daddr\n"; $iloc{$serno} = $daddr; $lastaddr = $daddr; } } } } if (m/SEGMENT *([0-9]*)/) { $segnum = $1; print " $segnum"; } } print "\n"; close(DUMPLFS); # If there were no checkpoints, print *something* if ($#iloc == 0) { print "0 $sstart 0\n"; exit 0; } # # Now fsck each checkpoint in turn, beginning with $sstart. # Because the log wraps we will have to reconstruct the filesystem image # as it existed at each checkpoint before running fsck. # # Look for lines containing only caps or "!", but ignore known # false positives. # $error = 0; $lastgood = $sstart - 1; open(LOG, ">>check-all.log"); print "Available checkpoints:"; print LOG "Available checkpoints:"; foreach $k (sort { $a <=> $b } keys %iloc) { $a = $iloc{$k}; print " $a"; print LOG " $a"; } print "\n"; print LOG "\n"; print "Recreating filesystem image as of $sstart:\n"; if ($sstart == 0) { $cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage } else { $cmd = "dd if=$gfile of=$wfile bs=1m"; } print "$cmd\n"; system("$cmd >/dev/null 2>&1"); $blstart = 0; $fps = $ssize / $fsize; foreach $k (sort { $a <=> $b } keys %iloc) { $a = $iloc{$k}; if (hex($a) > hex($lastaddr)) { print "Skipping out-of-place checkpoint $k at $a\n"; next; } # Copy the balance of the "new" fs image over the old one. $blstop = int ((hex $a) / $fps) + 1; $blstop *= $fps; if ($blstop != $blstart) { $cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$blstart " . "skip=$blstart conv=notrunc"; if ($blstop) { $cmd .= " count=" . ($blstop - $blstart); } $blstart = $blstop; print "$cmd\n"; system("$cmd >/dev/null 2>&1"); } $cmd = "fsck_lfs -n -f -i 0x$a $wfile"; print "$cmd\n"; print LOG "$cmd\n"; open(FSCK, "$cmd 2>&1 |"); while() { print LOG; chomp; # Known false positives (mismatch between sb and ifile, # which should be expected given we're using an arbitrarily # old version fo the ifile) if (m/AVAIL GIVEN/ || m/BFREE GIVEN/ || m/DMETA GIVEN/ || m/NCLEAN GIVEN/ || m/FREE BUT NOT ON FREE LIST/ || # UNWRITTEN inodes OK m/FREE LIST HEAD IN SUPERBLOCK/ ) { next; } # Fsck reports errors in ALL CAPS # But don't count hex numbers as "lowercase". s/0x[0-9a-f]*//g; if (m/[A-Z]/ && ! m/[a-z]/) { $error = 1; $errsn = $k; $errstr = "1 $k 0x$a"; last; } } close(FSCK); last if $error; $lastgood = $k; # record last good serial number } $errstr = "0 $lastgood 0x$a" unless $errstr; if ($error == 0) { print "Copying this good image to $gfile\n"; system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1"); print "$errstr\n"; exit 0; } # # If we found an error, try to find which blocks of the Ifile inode changed # between the last good checkpoint and this checkpoint; and which blocks # *should* have changed. This means (1) which segments were written; and # (2) which inodes were written. The 0 block of the Ifile should always # have changed since lfs_avail is always in flux. # $cmd = "dumplfs"; $oseg = -1; %iblk = (); %iblk_done = (); %why = (); $iblk{0} = 1; for ($i = $lastgood + 1; $i <= $errsn; $i++) { if ($oseg != $snloc{$i}) { $oseg = 0 + $snloc{$i}; $cmd .= " -s$oseg"; } } $cmd .= " $rdev"; open(DUMPLFS, "$cmd |"); while() { if (m/ifpb *([0-9]*)/) { $ifpb = $1; } if (m/sepb *([0-9]*)/) { $sepb = $1; } if (m/cleansz *([0-9]*)/) { $cleansz = $1; } if (m/segtabsz *([0-9]*)/) { $segtabsz = $1; } last if m/SEGMENT/; } while() { chomp; # Skip over partial segments outside our range of interest if (m/roll_id.*serial *([0-9]*)/) { $serno = $1; if ($serno <= $lastgood || $serno > $errsn) { # Skip the rest of this partial segment while() { last if m/Segment Summary/ || m/SEGMENT/; } next; } } # Look for inodes if (m/Inode addresses/) { s/^[^{]*{/ /o; s/}[^{]*$/ /o; s/}[^{]*{/,/og; s/v[0-9]*//og; @ilist = split(','); foreach $i (@ilist) { $i =~ s/ *//og; next if $i == 1; $iaddr = $cleansz + $segtabsz + int ($i / $ifpb); $iblk{$iaddr} = 1; $why{$iaddr} .= " $i"; } } # Look for Ifile blocks actually written if (m/FINFO for inode: ([0-9]*) version/) { $i = $1; $inoblkmode = ($i == 1); } if ($inoblkmode && m/^[-\t 0-9]*$/) { s/\t/ /og; s/^ *//o; s/ *$//o; @bn = split(' '); foreach $b (@bn) { $iblk_done{$b} = 1; } } } close(DUMPLFS); # Report found and missing Ifile blocks print "Ifile blocks found:"; foreach $b (sort { $a <=> $b } keys %iblk) { if ($iblk_done{$b} == 1) { print " $b"; } } print "\n"; print "Ifile blocks missing:"; foreach $b (sort { $a <=> $b } keys %iblk) { if ($iblk_done{$b} == 0) { $why{$b} =~ s/^ *//o; print " $b ($why{$b})"; } } print "\n"; print "$errstr\n"; exit 0;