mirror of
https://github.com/Stichting-MINIX-Research-Foundation/netbsd.git
synced 2025-09-12 00:24:52 -04:00
431 lines
10 KiB
Perl
Executable File
431 lines
10 KiB
Perl
Executable File
#!/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 <perseant@hhhh.org>.
|
|
#
|
|
# 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 (<DUMPLFS>) {
|
|
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 (<DUMPLFS>) {
|
|
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(<DUMPLFS>) {
|
|
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(<DUMPLFS>) {
|
|
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(<FSCK>) {
|
|
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(<DUMPLFS>) {
|
|
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(<DUMPLFS>) {
|
|
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(<DUMPLFS>) {
|
|
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;
|