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@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;
54open(DUMPLFS, "dumplfs $rdev |");
55
56# Look for "roll_id" so we don't use garbage
57while (<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 = ();
76print "Reading segments:";
77while (<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}
126print "\n";
127close(DUMPLFS);
128
129# Complain about missing partial-segments
130for ($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*
137if ($#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;
152open(LOG, ">>check-all.log");
153print "Available checkpoints:";
154print LOG "Available checkpoints:";
155foreach $k (sort { $a <=> $b } keys %iloc) {
156	$a = $iloc{$k};
157	print " $a";
158	print LOG " $a";
159}
160print "\n";
161print 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#
169sub 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
200print "Recreating filesystem image as of $sstart:\n";
201if ($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}
206print "$cmd\n";
207system("$cmd >/dev/null 2>&1");
208
209print "Copying over first superblock\n";
210system("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);
215foreach $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 = &copypseg($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}
260if (!$errstr) {
261	print "Bring filesystem state up to log wrap\n";
262	$lastgood = &copypseg($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;
286for ($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
294open(DUMPLFS, "$cmd |");
295while(<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}
310while(<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}
356close(DUMPLFS);
357
358# Report found and missing Ifile blocks
359print "Ifile blocks found:";
360foreach $b (sort { $a <=> $b } keys %iblk) {
361	if ($iblk_done{$b} == 1) {
362		print " $b";
363	}
364}
365print "\n";
366
367print "Ifile blocks missing:";
368foreach $b (sort { $a <=> $b } keys %iblk) {
369	if ($iblk_done{$b} == 0) {
370		$why{$b} =~ s/^ *//o;
371		print " $b ($why{$b})";
372	}
373}
374print "\n";
375
376print "$errstr\n";
377exit 0;
378