check-all revision 1.4
11.1Sperseant#!/usr/pkg/bin/perl
21.3Sperseant#
31.4Sperseant#	$NetBSD: check-all,v 1.4 2006/07/21 00:29:23 perseant Exp $
41.3Sperseant#
51.3Sperseant# Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc.
61.3Sperseant# All rights reserved.
71.3Sperseant#
81.3Sperseant# This code is derived from software contributed to The NetBSD Foundation
91.3Sperseant# by Konrad E. Schroder <perseant@hhhh.org>.
101.3Sperseant#
111.3Sperseant# Redistribution and use in source and binary forms, with or without
121.3Sperseant# modification, are permitted provided that the following conditions
131.3Sperseant# are met:
141.3Sperseant# 1. Redistributions of source code must retain the above copyright
151.3Sperseant#    notice, this list of conditions and the following disclaimer.
161.3Sperseant# 2. Redistributions in binary form must reproduce the above copyright
171.3Sperseant#    notice, this list of conditions and the following disclaimer in the
181.3Sperseant#    documentation and/or other materials provided with the distribution.
191.3Sperseant# 3. All advertising materials mentioning features or use of this software
201.3Sperseant#    must display the following acknowledgement:
211.3Sperseant#	This product includes software developed by the NetBSD
221.3Sperseant#	Foundation, Inc. and its contributors.
231.3Sperseant# 4. Neither the name of The NetBSD Foundation nor the names of its
241.3Sperseant#    contributors may be used to endorse or promote products derived
251.3Sperseant#    from this software without specific prior written permission.
261.3Sperseant#
271.3Sperseant# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
281.3Sperseant# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
291.3Sperseant# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
301.3Sperseant# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
311.3Sperseant# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
321.3Sperseant# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
331.3Sperseant# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
341.3Sperseant# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
351.3Sperseant# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
361.3Sperseant# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
371.3Sperseant# POSSIBILITY OF SUCH DAMAGE.
381.3Sperseant#
391.1Sperseant
401.1Sperseant#
411.1Sperseant# Use dumplfs to find all locations of the Ifile inode on a given disk.
421.1Sperseant# Order these by serial number and call fsck_lfs on the raw disk for each.
431.3Sperseant# If any fsck gives errors (any line of all capital letters, with a few
441.3Sperseant# exceptions) print an error code with the daddr of the failing Ifile inode
451.3Sperseant# location.
461.1Sperseant#
471.1Sperseant
481.2Sperseant$| = 1;
491.1Sperseant$rdev = $ARGV[0];
501.2Sperseant$gfile = $ARGV[1];
511.2Sperseant$wfile = $ARGV[2];
521.2Sperseant$sstart = $ARGV[3];
531.4Sperseant$test_rfw = 1; # $ARGV[4];
541.1Sperseant$rollid = 0;
551.1Sperseantopen(DUMPLFS, "dumplfs $rdev |");
561.1Sperseant
571.1Sperseant# Look for "roll_id" so we don't use garbage
581.1Sperseantwhile (<DUMPLFS>) {
591.2Sperseant	if ($ssize == 0 && m/ssize *([0-9]*)/) {
601.2Sperseant		$ssize = $1;
611.2Sperseant	}
621.2Sperseant	if ($fsize == 0 && m/fsize *([0-9]*)/) {
631.2Sperseant		$fsize = $1;
641.2Sperseant	}
651.1Sperseant	if (m/roll_id *([x0-9a-f]*)/) {
661.1Sperseant		$rollid = $1;
671.1Sperseant		last;
681.1Sperseant	}
691.1Sperseant}
701.1Sperseant
711.1Sperseant# Now look for inodes and segment summaries.  Build a hash table of these
721.1Sperseant# based on serial number.  Ignore any with serial numbers lower than $sstart.
731.1Sperseant
741.1Sperseant%iloc = ();
751.2Sperseant%snloc = ();
761.2Sperseant%sumloc = ();
771.2Sperseantprint "Reading segments:";
781.1Sperseantwhile (<DUMPLFS>) {
791.1Sperseant	if (m/roll_id *([0-9a-f]*)/) {
801.1Sperseant		# print "rollid $1\n";
811.1Sperseant		if ("0x$1" ne $rollid) {
821.1Sperseant			# Skip the rest of this segment
831.3Sperseant			print "{skip bad rollid 0x$1}";
841.1Sperseant			while(<DUMPLFS>) {
851.1Sperseant				last if m/SEGMENT/;
861.1Sperseant			}
871.2Sperseant			# Fall through
881.1Sperseant		}
891.1Sperseant	}
901.2Sperseant	if (m/roll_id.*serial *([0-9]*)/) {
911.1Sperseant		$serno = $1;
921.2Sperseant		$snloc{$serno} = $segnum;
931.2Sperseant		$sumloc{$serno} = $sumloc;
941.3Sperseant		print "($serno)";
951.1Sperseant		if ($serno < $sstart) {
961.1Sperseant			# Skip the rest of this partial segment
971.3Sperseant			#print "{skip bad serno $serno}";
981.1Sperseant			while(<DUMPLFS>) {
991.2Sperseant				last if m/Segment Summary/ ||
1001.2Sperseant					m/SEGMENT/;
1011.1Sperseant			}
1021.2Sperseant			# Fall through
1031.1Sperseant		}
1041.1Sperseant	}
1051.2Sperseant	if (m/Segment Summary Info at 0x([0-9a-f]*)/) {
1061.2Sperseant		$sumloc = $1;
1071.3Sperseant		next;
1081.2Sperseant	}
1091.1Sperseant	if (m/0x([0-9a-f]*)/) {
1101.1Sperseant		foreach $ss (split "0x", $_) {
1111.1Sperseant			if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) {
1121.1Sperseant				# print "iblk 0x$1\n";
1131.1Sperseant				$daddr = $1;
1141.1Sperseant				if (m/[^0-9]1v1/) {
1151.1Sperseant					# print "** ifblk 0x$daddr\n";
1161.1Sperseant					$iloc{$serno} = $daddr;
1171.2Sperseant					$lastaddr = $daddr;
1181.1Sperseant				}
1191.1Sperseant			}
1201.1Sperseant		}
1211.1Sperseant	}
1221.2Sperseant	if (m/SEGMENT *([0-9]*)/) {
1231.2Sperseant		$segnum = $1;
1241.3Sperseant		print "[$segnum]";
1251.1Sperseant	}
1261.1Sperseant}
1271.2Sperseantprint "\n";
1281.1Sperseantclose(DUMPLFS);
1291.1Sperseant
1301.3Sperseant# Complain about missing partial-segments
1311.3Sperseantfor ($i = $sstart; $i < $serno; ++$i) {
1321.3Sperseant	if (hex $sumloc{$i} == 0 && $i > 0) {
1331.3Sperseant		print "Oops, couldn't find pseg $i\n";
1341.3Sperseant	}
1351.3Sperseant}
1361.3Sperseant
1371.2Sperseant# If there were no checkpoints, print *something*
1381.2Sperseantif ($#iloc == 0) {
1391.2Sperseant	print "0 $sstart 0\n";
1401.2Sperseant	exit 0;
1411.2Sperseant}
1421.2Sperseant
1431.1Sperseant#
1441.1Sperseant# Now fsck each checkpoint in turn, beginning with $sstart.
1451.2Sperseant# Because the log wraps we will have to reconstruct the filesystem image
1461.2Sperseant# as it existed at each checkpoint before running fsck.
1471.2Sperseant#
1481.1Sperseant# Look for lines containing only caps or "!", but ignore known
1491.1Sperseant# false positives.
1501.1Sperseant#
1511.1Sperseant$error = 0;
1521.2Sperseant$lastgood = $sstart - 1;
1531.1Sperseantopen(LOG, ">>check-all.log");
1541.2Sperseantprint "Available checkpoints:";
1551.2Sperseantprint LOG "Available checkpoints:";
1561.1Sperseantforeach $k (sort { $a <=> $b } keys %iloc) {
1571.1Sperseant	$a = $iloc{$k};
1581.2Sperseant	print " $a";
1591.2Sperseant	print LOG " $a";
1601.2Sperseant}
1611.2Sperseantprint "\n";
1621.2Sperseantprint LOG "\n";
1631.2Sperseant
1641.3Sperseant#
1651.3Sperseant# Copy the partial segments $_[0]--$_[1] from the raw device onto
1661.3Sperseant# the working file.  Return the next partial-segment serial number
1671.3Sperseant# after the last one we copied (usually $_[1] + 1, except in case of
1681.3Sperseant# an error).
1691.3Sperseant#
1701.3Sperseantsub copypseg
1711.3Sperseant{
1721.3Sperseant	my ($blstart, $blstop, $segstop, $cmd);
1731.3Sperseant	my ($totalstart, $totalstop);
1741.3Sperseant
1751.3Sperseant	$totalstart = 0;
1761.3Sperseant	$totalstop = 0;
1771.3Sperseant	for ($i = $_[0]; $i <= $_[1]; ++$i) {
1781.3Sperseant		$blstart = hex $sumloc{$i};
1791.3Sperseant		last if $blstart <= 0;
1801.3Sperseant		$totalstart = $blstart if $totalstart == 0;
1811.3Sperseant		$blstop = hex $sumloc{$i + 1};
1821.3Sperseant		$segstop = ((int ($blstart / $fps)) + 1) * $fps;
1831.3Sperseant		if ($segstop < $blstop || $blstop < $blstart) {
1841.3Sperseant			#print "Adjusting $blstop -> $segstop\n";
1851.3Sperseant			$blstop = $segstop;
1861.3Sperseant		}
1871.3Sperseant		$totalstop = $blstop;
1881.3Sperseant
1891.4Sperseant		print "pseg $i: write blocks ", hex $blstart, "-", hex ($blstop - 1), "\n";
1901.3Sperseant		$blstart = $blstop;
1911.3Sperseant	}
1921.3Sperseant	$cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$totalstart " .
1931.3Sperseant		"skip=$totalstart conv=notrunc count=" .
1941.3Sperseant		($totalstop - $totalstart);
1951.3Sperseant#	print "$cmd\n";
1961.3Sperseant	system("$cmd >/dev/null 2>&1");
1971.3Sperseant
1981.3Sperseant	return $i;
1991.3Sperseant}
2001.3Sperseant
2011.2Sperseantprint "Recreating filesystem image as of $sstart:\n";
2021.2Sperseantif ($sstart == 0) {
2031.2Sperseant	$cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage
2041.2Sperseant} else {
2051.2Sperseant	$cmd = "dd if=$gfile of=$wfile bs=1m";
2061.2Sperseant}
2071.2Sperseantprint "$cmd\n";
2081.2Sperseantsystem("$cmd >/dev/null 2>&1");
2091.2Sperseant
2101.3Sperseantprint "Copying over first superblock\n";
2111.3Sperseantsystem("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1");
2121.3Sperseant
2131.4Sperseantsub test_fsck
2141.4Sperseant{
2151.4Sperseant	my $a = $_[0];
2161.4Sperseant	my $flags = $_[1];
2171.4Sperseant	my $printit = $_[2];
2181.4Sperseant	my $output = "";
2191.2Sperseant
2201.4Sperseant	$flags = "-n -f -i 0x$a $wfile" unless $flags;
2211.2Sperseant
2221.4Sperseant	$cmd = "fsck_lfs $flags";
2231.2Sperseant	print "$cmd\n";
2241.2Sperseant	print LOG "$cmd\n";
2251.2Sperseant	open(FSCK, "$cmd 2>&1 |");
2261.1Sperseant	while(<FSCK>) {
2271.1Sperseant		print LOG;
2281.4Sperseant		$rline = "$_";
2291.1Sperseant		chomp;
2301.1Sperseant
2311.1Sperseant		# Known false positives (mismatch between sb and ifile,
2321.1Sperseant		# which should be expected given we're using an arbitrarily
2331.3Sperseant		# old version of the ifile)
2341.1Sperseant		if (m/AVAIL GIVEN/ ||
2351.1Sperseant		    m/BFREE GIVEN/ ||
2361.1Sperseant		    m/DMETA GIVEN/ ||
2371.2Sperseant		    m/NCLEAN GIVEN/ ||
2381.2Sperseant		    m/FREE BUT NOT ON FREE LIST/ ||	# UNWRITTEN inodes OK
2391.4Sperseant		    m/FILE SYSTEM WAS MODIFIED/ ||
2401.2Sperseant		    m/FREE LIST HEAD IN SUPERBLOCK/ ) {
2411.1Sperseant			next;
2421.1Sperseant		}
2431.1Sperseant
2441.1Sperseant		# Fsck reports errors in ALL CAPS
2451.1Sperseant		# But don't count hex numbers as "lowercase".
2461.4Sperseant		$oline = "$_";
2471.1Sperseant		s/0x[0-9a-f]*//g;
2481.1Sperseant		if (m/[A-Z]/ && ! m/[a-z]/) {
2491.1Sperseant			$error = 1;
2501.2Sperseant			$errsn = $k;
2511.4Sperseant			$errstr = "1 $k 0x$a $oline";
2521.4Sperseant			# last;
2531.1Sperseant		}
2541.4Sperseant
2551.4Sperseant		# Log everything we get, except for some things we
2561.4Sperseant		# will see every single time.
2571.4Sperseant		if (m/checkpoint invalid/ ||
2581.4Sperseant		    m/skipping free list check/ ||
2591.4Sperseant		    m/expect discrepancies/) {
2601.4Sperseant			next;
2611.4Sperseant		}
2621.4Sperseant		$output .= $rline;
2631.1Sperseant	}
2641.1Sperseant	close(FSCK);
2651.4Sperseant
2661.4Sperseant	if ($? != 0) {
2671.4Sperseant		$error = 1;
2681.4Sperseant		$errsn = $k;
2691.4Sperseant		$errstr = "1 $k 0x$a <" . (hex $?) . ">";
2701.4Sperseant	}
2711.4Sperseant
2721.4Sperseant	if ($error || $printit) {
2731.4Sperseant		print $output;
2741.4Sperseant	}
2751.4Sperseant}
2761.4Sperseant
2771.4Sperseant$blstart = 0;
2781.4Sperseant$fps = $ssize / $fsize;
2791.4Sperseant$oind = ($sstart ? $sstart : 1);
2801.4SperseantBIGLOOP: foreach $k (sort { $a <=> $b } keys %iloc) {
2811.4Sperseant	$a = $iloc{$k};
2821.4Sperseant
2831.4Sperseant	if (hex($a) > hex($lastaddr)) {
2841.4Sperseant		print "Skipping out-of-place checkpoint $k at $a\n";
2851.4Sperseant		next;
2861.4Sperseant	}
2871.4Sperseant
2881.4Sperseant	if ($test_rfw && $iloc{$oind - 1}) {
2891.4Sperseant		for ($tk = $oind; $tk < $k; $tk++) {
2901.4Sperseant			print "Test roll-forward agent at non-checkpoint pseg $tk\n";
2911.4Sperseant			print LOG "Test roll-forward agent at non-checkpoint pseg $tk\n";
2921.4Sperseant			&copypseg($oind, $tk);
2931.4Sperseant			# Add -d flag here for verbose debugging info
2941.4Sperseant			$flags = "-p -f -i 0x" . $iloc{$oind - 1} . " $wfile";
2951.4Sperseant			&test_fsck($iloc{$oind - 1}, $flags, 1);
2961.4Sperseant			last BIGLOOP if $error;
2971.4Sperseant
2981.4Sperseant			# note lack of -i flag, since the roll-forward
2991.4Sperseant			# will have rewritten the superblocks.
3001.4Sperseant			&test_fsck($iloc{$oind - 1}, "-n -f $wfile", 0);
3011.4Sperseant			last BIGLOOP if $error;
3021.4Sperseant		}
3031.4Sperseant	}
3041.4Sperseant
3051.4Sperseant	print "Recreate fs state at checkpoint pseg $k (from " . ($oind - 1) .
3061.4Sperseant	      ")\n";
3071.4Sperseant	$oind = &copypseg($oind, $k);
3081.4Sperseant
3091.4Sperseant	&test_fsck($a, "", 0);
3101.4Sperseant
3111.1Sperseant	last if $error;
3121.2Sperseant	$lastgood = $k;	# record last good serial number
3131.2Sperseant}
3141.4Sperseant
3151.4Sperseantif ($errstr) {
3161.4Sperseant	print "$errstr\n";
3171.4Sperseant	exit 0;
3181.4Sperseant}
3191.4Sperseant
3201.3Sperseantif (!$errstr) {
3211.3Sperseant	print "Bring filesystem state up to log wrap\n";
3221.3Sperseant	$lastgood = &copypseg($oind, 100000000000) - 1;
3231.2Sperseant
3241.2Sperseant	print "Copying this good image to $gfile\n";
3251.2Sperseant	system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1");
3261.3Sperseant	print "0 $lastgood 0x$a\n";
3271.2Sperseant	exit 0;
3281.2Sperseant}
3291.2Sperseant
3301.2Sperseant#
3311.3Sperseant# Ifile write-checking paranoia.
3321.3Sperseant#
3331.2Sperseant# If we found an error, try to find which blocks of the Ifile inode changed
3341.2Sperseant# between the last good checkpoint and this checkpoint; and which blocks
3351.2Sperseant# *should* have changed.  This means (1) which segments were written; and
3361.2Sperseant# (2) which inodes were written.  The 0 block of the Ifile should always
3371.2Sperseant# have changed since lfs_avail is always in flux.
3381.2Sperseant#
3391.2Sperseant
3401.2Sperseant$cmd = "dumplfs";
3411.2Sperseant$oseg = -1;
3421.2Sperseant%iblk = ();
3431.2Sperseant%iblk_done = ();
3441.2Sperseant%why = ();
3451.2Sperseant$iblk{0} = 1;
3461.2Sperseantfor ($i = $lastgood + 1; $i <= $errsn; $i++) {
3471.2Sperseant	if ($oseg != $snloc{$i}) {
3481.2Sperseant		$oseg = 0 + $snloc{$i};
3491.2Sperseant		$cmd .= " -s$oseg";
3501.2Sperseant	}
3511.1Sperseant}
3521.2Sperseant$cmd .= " $rdev";
3531.1Sperseant
3541.2Sperseantopen(DUMPLFS, "$cmd |");
3551.2Sperseantwhile(<DUMPLFS>) {
3561.2Sperseant	if (m/ifpb *([0-9]*)/) {
3571.2Sperseant		$ifpb = $1;
3581.2Sperseant	}
3591.2Sperseant	if (m/sepb *([0-9]*)/) {
3601.2Sperseant		$sepb = $1;
3611.2Sperseant	}
3621.2Sperseant	if (m/cleansz *([0-9]*)/) {
3631.2Sperseant		$cleansz = $1;
3641.2Sperseant	}
3651.2Sperseant	if (m/segtabsz *([0-9]*)/) {
3661.2Sperseant		$segtabsz = $1;
3671.2Sperseant	}
3681.2Sperseant	last if m/SEGMENT/;
3691.2Sperseant}
3701.2Sperseantwhile(<DUMPLFS>) {
3711.2Sperseant	chomp;
3721.2Sperseant
3731.2Sperseant	# Skip over partial segments outside our range of interest
3741.2Sperseant	if (m/roll_id.*serial *([0-9]*)/) {
3751.2Sperseant		$serno = $1;
3761.2Sperseant		if ($serno <= $lastgood || $serno > $errsn) {
3771.2Sperseant			# Skip the rest of this partial segment
3781.2Sperseant			while(<DUMPLFS>) {
3791.2Sperseant				last if m/Segment Summary/ || m/SEGMENT/;
3801.2Sperseant			}
3811.2Sperseant			next;
3821.2Sperseant		}
3831.2Sperseant	}
3841.2Sperseant
3851.2Sperseant	# Look for inodes
3861.2Sperseant	if (m/Inode addresses/) {
3871.2Sperseant		s/^[^{]*{/ /o;
3881.2Sperseant		s/}[^{]*$/ /o;
3891.2Sperseant		s/}[^{]*{/,/og;
3901.2Sperseant		s/v[0-9]*//og;
3911.2Sperseant		@ilist = split(',');
3921.2Sperseant		foreach $i (@ilist) {
3931.2Sperseant			$i =~ s/ *//og;
3941.2Sperseant			next if $i == 1;
3951.2Sperseant			$iaddr = $cleansz + $segtabsz + int ($i / $ifpb);
3961.2Sperseant			$iblk{$iaddr} = 1;
3971.2Sperseant			$why{$iaddr} .= " $i";
3981.2Sperseant		}
3991.2Sperseant	}
4001.2Sperseant
4011.2Sperseant	# Look for Ifile blocks actually written
4021.2Sperseant	if (m/FINFO for inode: ([0-9]*) version/) {
4031.2Sperseant		$i = $1;
4041.2Sperseant		$inoblkmode = ($i == 1);
4051.2Sperseant	}
4061.2Sperseant	if ($inoblkmode && m/^[-\t 0-9]*$/) {
4071.2Sperseant		s/\t/ /og;
4081.2Sperseant		s/^ *//o;
4091.2Sperseant		s/ *$//o;
4101.2Sperseant		@bn = split(' ');
4111.2Sperseant		foreach $b (@bn) {
4121.2Sperseant			$iblk_done{$b} = 1;
4131.2Sperseant		}
4141.2Sperseant	}
4151.2Sperseant}
4161.2Sperseantclose(DUMPLFS);
4171.2Sperseant
4181.2Sperseant# Report found and missing Ifile blocks
4191.2Sperseantprint "Ifile blocks found:";
4201.2Sperseantforeach $b (sort { $a <=> $b } keys %iblk) {
4211.2Sperseant	if ($iblk_done{$b} == 1) {
4221.2Sperseant		print " $b";
4231.2Sperseant	}
4241.2Sperseant}
4251.2Sperseantprint "\n";
4261.4Sperseant	
4271.2Sperseantprint "Ifile blocks missing:";
4281.2Sperseantforeach $b (sort { $a <=> $b } keys %iblk) {
4291.2Sperseant	if ($iblk_done{$b} == 0) {
4301.2Sperseant		$why{$b} =~ s/^ *//o;
4311.2Sperseant		print " $b ($why{$b})";
4321.2Sperseant	}
4331.1Sperseant}
4341.2Sperseantprint "\n";
4351.2Sperseant
4361.2Sperseantprint "$errstr\n";
4371.2Sperseantexit 0;
438