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