1#!/usr/pkg/bin/perl
2#
3#         $NetBSD: check-all,v 1.5 2008/04/30 13:10:52 martin Exp $
4#
5# Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc.
6# All rights reserved.
7#
8# This code is derived from software contributed to The NetBSD Foundation
9# by Konrad E. Schroder <perseant@hhhh.org>.
10#
11# Redistribution and use in source and binary forms, with or without
12# modification, are permitted provided that the following conditions
13# are met:
14# 1. Redistributions of source code must retain the above copyright
15#    notice, this list of conditions and the following disclaimer.
16# 2. Redistributions in binary form must reproduce the above copyright
17#    notice, this list of conditions and the following disclaimer in the
18#    documentation and/or other materials provided with the distribution.
19#
20# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
31#
32
33#
34# Use dumplfs to find all locations of the Ifile inode on a given disk.
35# Order these by serial number and call fsck_lfs on the raw disk for each.
36# If any fsck gives errors (any line of all capital letters, with a few
37# exceptions) print an error code with the daddr of the failing Ifile inode
38# location.
39#
40
41$| = 1;
42$rdev = $ARGV[0];
43$gfile = $ARGV[1];
44$wfile = $ARGV[2];
45$sstart = $ARGV[3];
46$test_rfw = 1; # $ARGV[4];
47$rollid = 0;
48open(DUMPLFS, "dumplfs $rdev |");
49
50# Look for "roll_id" so we don't use garbage
51while (<DUMPLFS>) {
52          if ($ssize == 0 && m/ssize *([0-9]*)/) {
53                    $ssize = $1;
54          }
55          if ($fsize == 0 && m/fsize *([0-9]*)/) {
56                    $fsize = $1;
57          }
58          if (m/roll_id *([x0-9a-f]*)/) {
59                    $rollid = $1;
60                    last;
61          }
62}
63
64# Now look for inodes and segment summaries.  Build a hash table of these
65# based on serial number.  Ignore any with serial numbers lower than $sstart.
66
67%iloc = ();
68%snloc = ();
69%sumloc = ();
70print "Reading segments:";
71while (<DUMPLFS>) {
72          if (m/roll_id *([0-9a-f]*)/) {
73                    # print "rollid $1\n";
74                    if ("0x$1" ne $rollid) {
75                              # Skip the rest of this segment
76                              print "{skip bad rollid 0x$1}";
77                              while(<DUMPLFS>) {
78                                        last if m/SEGMENT/;
79                              }
80                              # Fall through
81                    }
82          }
83          if (m/roll_id.*serial *([0-9]*)/) {
84                    $serno = $1;
85                    $snloc{$serno} = $segnum;
86                    $sumloc{$serno} = $sumloc;
87                    print "($serno)";
88                    if ($serno < $sstart) {
89                              # Skip the rest of this partial segment
90                              #print "{skip bad serno $serno}";
91                              while(<DUMPLFS>) {
92                                        last if m/Segment Summary/ ||
93                                                  m/SEGMENT/;
94                              }
95                              # Fall through
96                    }
97          }
98          if (m/Segment Summary Info at 0x([0-9a-f]*)/) {
99                    $sumloc = $1;
100                    next;
101          }
102          if (m/0x([0-9a-f]*)/) {
103                    foreach $ss (split "0x", $_) {
104                              if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) {
105                                        # print "iblk 0x$1\n";
106                                        $daddr = $1;
107                                        if (m/[^0-9]1v1/) {
108                                                  # print "** ifblk 0x$daddr\n";
109                                                  $iloc{$serno} = $daddr;
110                                                  $lastaddr = $daddr;
111                                        }
112                              }
113                    }
114          }
115          if (m/SEGMENT *([0-9]*)/) {
116                    $segnum = $1;
117                    print "[$segnum]";
118          }
119}
120print "\n";
121close(DUMPLFS);
122
123# Complain about missing partial-segments
124for ($i = $sstart; $i < $serno; ++$i) {
125          if (hex $sumloc{$i} == 0 && $i > 0) {
126                    print "Oops, couldn't find pseg $i\n";
127          }
128}
129
130# If there were no checkpoints, print *something*
131if ($#iloc == 0) {
132          print "0 $sstart 0\n";
133          exit 0;
134}
135
136#
137# Now fsck each checkpoint in turn, beginning with $sstart.
138# Because the log wraps we will have to reconstruct the filesystem image
139# as it existed at each checkpoint before running fsck.
140#
141# Look for lines containing only caps or "!", but ignore known
142# false positives.
143#
144$error = 0;
145$lastgood = $sstart - 1;
146open(LOG, ">>check-all.log");
147print "Available checkpoints:";
148print LOG "Available checkpoints:";
149foreach $k (sort { $a <=> $b } keys %iloc) {
150          $a = $iloc{$k};
151          print " $a";
152          print LOG " $a";
153}
154print "\n";
155print LOG "\n";
156
157#
158# Copy the partial segments $_[0]--$_[1] from the raw device onto
159# the working file.  Return the next partial-segment serial number
160# after the last one we copied (usually $_[1] + 1, except in case of
161# an error).
162#
163sub copypseg
164{
165          my ($blstart, $blstop, $segstop, $cmd);
166          my ($totalstart, $totalstop);
167
168          $totalstart = 0;
169          $totalstop = 0;
170          for ($i = $_[0]; $i <= $_[1]; ++$i) {
171                    $blstart = hex $sumloc{$i};
172                    last if $blstart <= 0;
173                    $totalstart = $blstart if $totalstart == 0;
174                    $blstop = hex $sumloc{$i + 1};
175                    $segstop = ((int ($blstart / $fps)) + 1) * $fps;
176                    if ($segstop < $blstop || $blstop < $blstart) {
177                              #print "Adjusting $blstop -> $segstop\n";
178                              $blstop = $segstop;
179                    }
180                    $totalstop = $blstop;
181
182                    print "pseg $i: write blocks ", hex $blstart, "-", hex ($blstop - 1), "\n";
183                    $blstart = $blstop;
184          }
185          $cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$totalstart " .
186                    "skip=$totalstart conv=notrunc count=" .
187                    ($totalstop - $totalstart);
188#         print "$cmd\n";
189          system("$cmd >/dev/null 2>&1");
190
191          return $i;
192}
193
194print "Recreating filesystem image as of $sstart:\n";
195if ($sstart == 0) {
196          $cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage
197} else {
198          $cmd = "dd if=$gfile of=$wfile bs=1m";
199}
200print "$cmd\n";
201system("$cmd >/dev/null 2>&1");
202
203print "Copying over first superblock\n";
204system("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1");
205
206sub test_fsck
207{
208          my $a = $_[0];
209          my $flags = $_[1];
210          my $printit = $_[2];
211          my $output = "";
212
213          $flags = "-n -f -i 0x$a $wfile" unless $flags;
214
215          $cmd = "fsck_lfs $flags";
216          print "$cmd\n";
217          print LOG "$cmd\n";
218          open(FSCK, "$cmd 2>&1 |");
219          while(<FSCK>) {
220                    print LOG;
221                    $rline = "$_";
222                    chomp;
223
224                    # Known false positives (mismatch between sb and ifile,
225                    # which should be expected given we're using an arbitrarily
226                    # old version of the ifile)
227                    if (m/AVAIL GIVEN/ ||
228                        m/BFREE GIVEN/ ||
229                        m/DMETA GIVEN/ ||
230                        m/NCLEAN GIVEN/ ||
231                        m/FREE BUT NOT ON FREE LIST/ ||     # UNWRITTEN inodes OK
232                        m/FILE SYSTEM WAS MODIFIED/ ||
233                        m/FREE LIST HEAD IN SUPERBLOCK/ ) {
234                              next;
235                    }
236
237                    # Fsck reports errors in ALL CAPS
238                    # But don't count hex numbers as "lowercase".
239                    $oline = "$_";
240                    s/0x[0-9a-f]*//g;
241                    if (m/[A-Z]/ && ! m/[a-z]/) {
242                              $error = 1;
243                              $errsn = $k;
244                              $errstr = "1 $k 0x$a $oline";
245                              # last;
246                    }
247
248                    # Log everything we get, except for some things we
249                    # will see every single time.
250                    if (m/checkpoint invalid/ ||
251                        m/skipping free list check/ ||
252                        m/expect discrepancies/) {
253                              next;
254                    }
255                    $output .= $rline;
256          }
257          close(FSCK);
258
259          if ($? != 0) {
260                    $error = 1;
261                    $errsn = $k;
262                    $errstr = "1 $k 0x$a <" . (hex $?) . ">";
263          }
264
265          if ($error || $printit) {
266                    print $output;
267          }
268}
269
270$blstart = 0;
271$fps = $ssize / $fsize;
272$oind = ($sstart ? $sstart : 1);
273BIGLOOP: foreach $k (sort { $a <=> $b } keys %iloc) {
274          $a = $iloc{$k};
275
276          if (hex($a) > hex($lastaddr)) {
277                    print "Skipping out-of-place checkpoint $k at $a\n";
278                    next;
279          }
280
281          if ($test_rfw && $iloc{$oind - 1}) {
282                    for ($tk = $oind; $tk < $k; $tk++) {
283                              print "Test roll-forward agent at non-checkpoint pseg $tk\n";
284                              print LOG "Test roll-forward agent at non-checkpoint pseg $tk\n";
285                              &copypseg($oind, $tk);
286                              # Add -d flag here for verbose debugging info
287                              $flags = "-p -f -i 0x" . $iloc{$oind - 1} . " $wfile";
288                              &test_fsck($iloc{$oind - 1}, $flags, 1);
289                              last BIGLOOP if $error;
290
291                              # note lack of -i flag, since the roll-forward
292                              # will have rewritten the superblocks.
293                              &test_fsck($iloc{$oind - 1}, "-n -f $wfile", 0);
294                              last BIGLOOP if $error;
295                    }
296          }
297
298          print "Recreate fs state at checkpoint pseg $k (from " . ($oind - 1) .
299                ")\n";
300          $oind = &copypseg($oind, $k);
301
302          &test_fsck($a, "", 0);
303
304          last if $error;
305          $lastgood = $k;     # record last good serial number
306}
307
308if ($errstr) {
309          print "$errstr\n";
310          exit 0;
311}
312
313if (!$errstr) {
314          print "Bring filesystem state up to log wrap\n";
315          $lastgood = &copypseg($oind, 100000000000) - 1;
316
317          print "Copying this good image to $gfile\n";
318          system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1");
319          print "0 $lastgood 0x$a\n";
320          exit 0;
321}
322
323#
324# Ifile write-checking paranoia.
325#
326# If we found an error, try to find which blocks of the Ifile inode changed
327# between the last good checkpoint and this checkpoint; and which blocks
328# *should* have changed.  This means (1) which segments were written; and
329# (2) which inodes were written.  The 0 block of the Ifile should always
330# have changed since lfs_avail is always in flux.
331#
332
333$cmd = "dumplfs";
334$oseg = -1;
335%iblk = ();
336%iblk_done = ();
337%why = ();
338$iblk{0} = 1;
339for ($i = $lastgood + 1; $i <= $errsn; $i++) {
340          if ($oseg != $snloc{$i}) {
341                    $oseg = 0 + $snloc{$i};
342                    $cmd .= " -s$oseg";
343          }
344}
345$cmd .= " $rdev";
346
347open(DUMPLFS, "$cmd |");
348while(<DUMPLFS>) {
349          if (m/ifpb *([0-9]*)/) {
350                    $ifpb = $1;
351          }
352          if (m/sepb *([0-9]*)/) {
353                    $sepb = $1;
354          }
355          if (m/cleansz *([0-9]*)/) {
356                    $cleansz = $1;
357          }
358          if (m/segtabsz *([0-9]*)/) {
359                    $segtabsz = $1;
360          }
361          last if m/SEGMENT/;
362}
363while(<DUMPLFS>) {
364          chomp;
365
366          # Skip over partial segments outside our range of interest
367          if (m/roll_id.*serial *([0-9]*)/) {
368                    $serno = $1;
369                    if ($serno <= $lastgood || $serno > $errsn) {
370                              # Skip the rest of this partial segment
371                              while(<DUMPLFS>) {
372                                        last if m/Segment Summary/ || m/SEGMENT/;
373                              }
374                              next;
375                    }
376          }
377
378          # Look for inodes
379          if (m/Inode addresses/) {
380                    s/^[^{]*{/ /o;
381                    s/}[^{]*$/ /o;
382                    s/}[^{]*{/,/og;
383                    s/v[0-9]*//og;
384                    @ilist = split(',');
385                    foreach $i (@ilist) {
386                              $i =~ s/ *//og;
387                              next if $i == 1;
388                              $iaddr = $cleansz + $segtabsz + int ($i / $ifpb);
389                              $iblk{$iaddr} = 1;
390                              $why{$iaddr} .= " $i";
391                    }
392          }
393
394          # Look for Ifile blocks actually written
395          if (m/FINFO for inode: ([0-9]*) version/) {
396                    $i = $1;
397                    $inoblkmode = ($i == 1);
398          }
399          if ($inoblkmode && m/^[-\t 0-9]*$/) {
400                    s/\t/ /og;
401                    s/^ *//o;
402                    s/ *$//o;
403                    @bn = split(' ');
404                    foreach $b (@bn) {
405                              $iblk_done{$b} = 1;
406                    }
407          }
408}
409close(DUMPLFS);
410
411# Report found and missing Ifile blocks
412print "Ifile blocks found:";
413foreach $b (sort { $a <=> $b } keys %iblk) {
414          if ($iblk_done{$b} == 1) {
415                    print " $b";
416          }
417}
418print "\n";
419
420print "Ifile blocks missing:";
421foreach $b (sort { $a <=> $b } keys %iblk) {
422          if ($iblk_done{$b} == 0) {
423                    $why{$b} =~ s/^ *//o;
424                    print " $b ($why{$b})";
425          }
426}
427print "\n";
428
429print "$errstr\n";
430exit 0;
431