xref: /netbsd-src/regress/sys/fs/lfs/ckckp/check-all (revision 11a6dbe72840351315e0652b2fc6663628c84cad)
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