check-all revision 1.3 1 #!/usr/pkg/bin/perl
2 #
3 # $NetBSD: check-all,v 1.3 2006/04/27 22:37:54 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 $rollid = 0;
54 open(DUMPLFS, "dumplfs $rdev |");
55
56 # Look for "roll_id" so we don't use garbage
57 while (<DUMPLFS>) {
58 if ($ssize == 0 && m/ssize *([0-9]*)/) {
59 $ssize = $1;
60 }
61 if ($fsize == 0 && m/fsize *([0-9]*)/) {
62 $fsize = $1;
63 }
64 if (m/roll_id *([x0-9a-f]*)/) {
65 $rollid = $1;
66 last;
67 }
68 }
69
70 # Now look for inodes and segment summaries. Build a hash table of these
71 # based on serial number. Ignore any with serial numbers lower than $sstart.
72
73 %iloc = ();
74 %snloc = ();
75 %sumloc = ();
76 print "Reading segments:";
77 while (<DUMPLFS>) {
78 if (m/roll_id *([0-9a-f]*)/) {
79 # print "rollid $1\n";
80 if ("0x$1" ne $rollid) {
81 # Skip the rest of this segment
82 print "{skip bad rollid 0x$1}";
83 while(<DUMPLFS>) {
84 last if m/SEGMENT/;
85 }
86 # Fall through
87 }
88 }
89 if (m/roll_id.*serial *([0-9]*)/) {
90 $serno = $1;
91 $snloc{$serno} = $segnum;
92 $sumloc{$serno} = $sumloc;
93 print "($serno)";
94 if ($serno < $sstart) {
95 # Skip the rest of this partial segment
96 #print "{skip bad serno $serno}";
97 while(<DUMPLFS>) {
98 last if m/Segment Summary/ ||
99 m/SEGMENT/;
100 }
101 # Fall through
102 }
103 }
104 if (m/Segment Summary Info at 0x([0-9a-f]*)/) {
105 $sumloc = $1;
106 next;
107 }
108 if (m/0x([0-9a-f]*)/) {
109 foreach $ss (split "0x", $_) {
110 if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) {
111 # print "iblk 0x$1\n";
112 $daddr = $1;
113 if (m/[^0-9]1v1/) {
114 # print "** ifblk 0x$daddr\n";
115 $iloc{$serno} = $daddr;
116 $lastaddr = $daddr;
117 }
118 }
119 }
120 }
121 if (m/SEGMENT *([0-9]*)/) {
122 $segnum = $1;
123 print "[$segnum]";
124 }
125 }
126 print "\n";
127 close(DUMPLFS);
128
129 # Complain about missing partial-segments
130 for ($i = $sstart; $i < $serno; ++$i) {
131 if (hex $sumloc{$i} == 0 && $i > 0) {
132 print "Oops, couldn't find pseg $i\n";
133 }
134 }
135
136 # If there were no checkpoints, print *something*
137 if ($#iloc == 0) {
138 print "0 $sstart 0\n";
139 exit 0;
140 }
141
142 #
143 # Now fsck each checkpoint in turn, beginning with $sstart.
144 # Because the log wraps we will have to reconstruct the filesystem image
145 # as it existed at each checkpoint before running fsck.
146 #
147 # Look for lines containing only caps or "!", but ignore known
148 # false positives.
149 #
150 $error = 0;
151 $lastgood = $sstart - 1;
152 open(LOG, ">>check-all.log");
153 print "Available checkpoints:";
154 print LOG "Available checkpoints:";
155 foreach $k (sort { $a <=> $b } keys %iloc) {
156 $a = $iloc{$k};
157 print " $a";
158 print LOG " $a";
159 }
160 print "\n";
161 print LOG "\n";
162
163 #
164 # Copy the partial segments $_[0]--$_[1] from the raw device onto
165 # the working file. Return the next partial-segment serial number
166 # after the last one we copied (usually $_[1] + 1, except in case of
167 # an error).
168 #
169 sub copypseg
170 {
171 my ($blstart, $blstop, $segstop, $cmd);
172 my ($totalstart, $totalstop);
173
174 $totalstart = 0;
175 $totalstop = 0;
176 for ($i = $_[0]; $i <= $_[1]; ++$i) {
177 $blstart = hex $sumloc{$i};
178 last if $blstart <= 0;
179 $totalstart = $blstart if $totalstart == 0;
180 $blstop = hex $sumloc{$i + 1};
181 $segstop = ((int ($blstart / $fps)) + 1) * $fps;
182 if ($segstop < $blstop || $blstop < $blstart) {
183 #print "Adjusting $blstop -> $segstop\n";
184 $blstop = $segstop;
185 }
186 $totalstop = $blstop;
187
188 print "pseg $i: write blocks $blstart-", $blstop - 1, "\n";
189 $blstart = $blstop;
190 }
191 $cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$totalstart " .
192 "skip=$totalstart conv=notrunc count=" .
193 ($totalstop - $totalstart);
194 # print "$cmd\n";
195 system("$cmd >/dev/null 2>&1");
196
197 return $i;
198 }
199
200 print "Recreating filesystem image as of $sstart:\n";
201 if ($sstart == 0) {
202 $cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage
203 } else {
204 $cmd = "dd if=$gfile of=$wfile bs=1m";
205 }
206 print "$cmd\n";
207 system("$cmd >/dev/null 2>&1");
208
209 print "Copying over first superblock\n";
210 system("dd if=$rdev of=$wfile bs=8k count=2 conv=notrunc >/dev/null 2>&1");
211
212 $blstart = 0;
213 $fps = $ssize / $fsize;
214 $oind = ($sstart ? $sstart : 1);
215 foreach $k (sort { $a <=> $b } keys %iloc) {
216 $a = $iloc{$k};
217
218 if (hex($a) > hex($lastaddr)) {
219 print "Skipping out-of-place checkpoint $k at $a\n";
220 next;
221 }
222
223 print "Recreate fs state at checkpoint pseg $k\n";
224 $oind = ©pseg($oind, $k);
225
226 $cmd = "fsck_lfs -n -f -i 0x$a $wfile";
227 print "$cmd\n";
228 print LOG "$cmd\n";
229 open(FSCK, "$cmd 2>&1 |");
230 while(<FSCK>) {
231 print LOG;
232 chomp;
233
234 # Known false positives (mismatch between sb and ifile,
235 # which should be expected given we're using an arbitrarily
236 # old version of the ifile)
237 if (m/AVAIL GIVEN/ ||
238 m/BFREE GIVEN/ ||
239 m/DMETA GIVEN/ ||
240 m/NCLEAN GIVEN/ ||
241 m/FREE BUT NOT ON FREE LIST/ || # UNWRITTEN inodes OK
242 m/FREE LIST HEAD IN SUPERBLOCK/ ) {
243 next;
244 }
245
246 # Fsck reports errors in ALL CAPS
247 # But don't count hex numbers as "lowercase".
248 s/0x[0-9a-f]*//g;
249 if (m/[A-Z]/ && ! m/[a-z]/) {
250 $error = 1;
251 $errsn = $k;
252 $errstr = "1 $k 0x$a";
253 last;
254 }
255 }
256 close(FSCK);
257 last if $error;
258 $lastgood = $k; # record last good serial number
259 }
260 if (!$errstr) {
261 print "Bring filesystem state up to log wrap\n";
262 $lastgood = ©pseg($oind, 100000000000) - 1;
263
264 print "Copying this good image to $gfile\n";
265 system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1");
266 print "0 $lastgood 0x$a\n";
267 exit 0;
268 }
269
270 #
271 # Ifile write-checking paranoia.
272 #
273 # If we found an error, try to find which blocks of the Ifile inode changed
274 # between the last good checkpoint and this checkpoint; and which blocks
275 # *should* have changed. This means (1) which segments were written; and
276 # (2) which inodes were written. The 0 block of the Ifile should always
277 # have changed since lfs_avail is always in flux.
278 #
279
280 $cmd = "dumplfs";
281 $oseg = -1;
282 %iblk = ();
283 %iblk_done = ();
284 %why = ();
285 $iblk{0} = 1;
286 for ($i = $lastgood + 1; $i <= $errsn; $i++) {
287 if ($oseg != $snloc{$i}) {
288 $oseg = 0 + $snloc{$i};
289 $cmd .= " -s$oseg";
290 }
291 }
292 $cmd .= " $rdev";
293
294 open(DUMPLFS, "$cmd |");
295 while(<DUMPLFS>) {
296 if (m/ifpb *([0-9]*)/) {
297 $ifpb = $1;
298 }
299 if (m/sepb *([0-9]*)/) {
300 $sepb = $1;
301 }
302 if (m/cleansz *([0-9]*)/) {
303 $cleansz = $1;
304 }
305 if (m/segtabsz *([0-9]*)/) {
306 $segtabsz = $1;
307 }
308 last if m/SEGMENT/;
309 }
310 while(<DUMPLFS>) {
311 chomp;
312
313 # Skip over partial segments outside our range of interest
314 if (m/roll_id.*serial *([0-9]*)/) {
315 $serno = $1;
316 if ($serno <= $lastgood || $serno > $errsn) {
317 # Skip the rest of this partial segment
318 while(<DUMPLFS>) {
319 last if m/Segment Summary/ || m/SEGMENT/;
320 }
321 next;
322 }
323 }
324
325 # Look for inodes
326 if (m/Inode addresses/) {
327 s/^[^{]*{/ /o;
328 s/}[^{]*$/ /o;
329 s/}[^{]*{/,/og;
330 s/v[0-9]*//og;
331 @ilist = split(',');
332 foreach $i (@ilist) {
333 $i =~ s/ *//og;
334 next if $i == 1;
335 $iaddr = $cleansz + $segtabsz + int ($i / $ifpb);
336 $iblk{$iaddr} = 1;
337 $why{$iaddr} .= " $i";
338 }
339 }
340
341 # Look for Ifile blocks actually written
342 if (m/FINFO for inode: ([0-9]*) version/) {
343 $i = $1;
344 $inoblkmode = ($i == 1);
345 }
346 if ($inoblkmode && m/^[-\t 0-9]*$/) {
347 s/\t/ /og;
348 s/^ *//o;
349 s/ *$//o;
350 @bn = split(' ');
351 foreach $b (@bn) {
352 $iblk_done{$b} = 1;
353 }
354 }
355 }
356 close(DUMPLFS);
357
358 # Report found and missing Ifile blocks
359 print "Ifile blocks found:";
360 foreach $b (sort { $a <=> $b } keys %iblk) {
361 if ($iblk_done{$b} == 1) {
362 print " $b";
363 }
364 }
365 print "\n";
366
367 print "Ifile blocks missing:";
368 foreach $b (sort { $a <=> $b } keys %iblk) {
369 if ($iblk_done{$b} == 0) {
370 $why{$b} =~ s/^ *//o;
371 print " $b ($why{$b})";
372 }
373 }
374 print "\n";
375
376 print "$errstr\n";
377 exit 0;
378