#!/usr/pkg/bin/perl # # $NetBSD: check-all,v 1.5 2008/04/30 13:10:52 martin Exp $ # # Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Konrad E. Schroder . # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # # 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 (any line of all capital letters, 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]; $test_rfw = 1; # $ARGV[4]; $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 print "{skip bad rollid 0x$1}"; while() { last if m/SEGMENT/; } # Fall through } } if (m/roll_id.*serial *([0-9]*)/) { $serno = $1; $snloc{$serno} = $segnum; $sumloc{$serno} = $sumloc; print "($serno)"; if ($serno < $sstart) { # Skip the rest of this partial segment #print "{skip bad serno $serno}"; while() { last if m/Segment Summary/ || m/SEGMENT/; } # Fall through } } if (m/Segment Summary Info at 0x([0-9a-f]*)/) { $sumloc = $1; next; } 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); # Complain about missing partial-segments for ($i = $sstart; $i < $serno; ++$i) { if (hex $sumloc{$i} == 0 && $i > 0) { print "Oops, couldn't find pseg $i\n"; } } # 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"; # # Copy the partial segments $_[0]--$_[1] from the raw device onto # the working file. Return the next partial-segment serial number # after the last one we copied (usually $_[1] + 1, except in case of # an error). # sub copypseg { my ($blstart, $blstop, $segstop, $cmd); my ($totalstart, $totalstop); $totalstart = 0; $totalstop = 0; for ($i = $_[0]; $i <= $_[1]; ++$i) { $blstart = hex $sumloc{$i}; last if $blstart <= 0; $totalstart = $blstart if $totalstart == 0; $blstop = hex $sumloc{$i + 1}; $segstop = ((int ($blstart / $fps)) + 1) * $fps; if ($segstop < $blstop || $blstop < $blstart) { #print "Adjusting $blstop -> $segstop\n"; $blstop = $segstop; } $totalstop = $blstop; print "pseg $i: write blocks ", hex $blstart, "-", hex ($blstop - 1), "\n"; $blstart = $blstop; } $cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$totalstart " . "skip=$totalstart conv=notrunc count=" . ($totalstop - $totalstart); # print "$cmd\n"; system("$cmd >/dev/null 2>&1"); return $i; } 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"); print "Copying over first superblock\n"; system("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1"); sub test_fsck { my $a = $_[0]; my $flags = $_[1]; my $printit = $_[2]; my $output = ""; $flags = "-n -f -i 0x$a $wfile" unless $flags; $cmd = "fsck_lfs $flags"; print "$cmd\n"; print LOG "$cmd\n"; open(FSCK, "$cmd 2>&1 |"); while() { print LOG; $rline = "$_"; chomp; # Known false positives (mismatch between sb and ifile, # which should be expected given we're using an arbitrarily # old version of 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/FILE SYSTEM WAS MODIFIED/ || m/FREE LIST HEAD IN SUPERBLOCK/ ) { next; } # Fsck reports errors in ALL CAPS # But don't count hex numbers as "lowercase". $oline = "$_"; s/0x[0-9a-f]*//g; if (m/[A-Z]/ && ! m/[a-z]/) { $error = 1; $errsn = $k; $errstr = "1 $k 0x$a $oline"; # last; } # Log everything we get, except for some things we # will see every single time. if (m/checkpoint invalid/ || m/skipping free list check/ || m/expect discrepancies/) { next; } $output .= $rline; } close(FSCK); if ($? != 0) { $error = 1; $errsn = $k; $errstr = "1 $k 0x$a <" . (hex $?) . ">"; } if ($error || $printit) { print $output; } } $blstart = 0; $fps = $ssize / $fsize; $oind = ($sstart ? $sstart : 1); BIGLOOP: 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; } if ($test_rfw && $iloc{$oind - 1}) { for ($tk = $oind; $tk < $k; $tk++) { print "Test roll-forward agent at non-checkpoint pseg $tk\n"; print LOG "Test roll-forward agent at non-checkpoint pseg $tk\n"; ©pseg($oind, $tk); # Add -d flag here for verbose debugging info $flags = "-p -f -i 0x" . $iloc{$oind - 1} . " $wfile"; &test_fsck($iloc{$oind - 1}, $flags, 1); last BIGLOOP if $error; # note lack of -i flag, since the roll-forward # will have rewritten the superblocks. &test_fsck($iloc{$oind - 1}, "-n -f $wfile", 0); last BIGLOOP if $error; } } print "Recreate fs state at checkpoint pseg $k (from " . ($oind - 1) . ")\n"; $oind = ©pseg($oind, $k); &test_fsck($a, "", 0); last if $error; $lastgood = $k; # record last good serial number } if ($errstr) { print "$errstr\n"; exit 0; } if (!$errstr) { print "Bring filesystem state up to log wrap\n"; $lastgood = ©pseg($oind, 100000000000) - 1; print "Copying this good image to $gfile\n"; system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1"); print "0 $lastgood 0x$a\n"; exit 0; } # # Ifile write-checking paranoia. # # 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;