check-all revision 1.2
1#!/usr/pkg/bin/perl
2
3#
4# Use dumplfs to find all locations of the Ifile inode on a given disk.
5# Order these by serial number and call fsck_lfs on the raw disk for each.
6# If any fsck gives errors (lines containing "!", with a few exceptions)
7# print an error code with the daddr of the failing Ifile inode location.
8#
9
10$| = 1;
11$rdev = $ARGV[0];
12$gfile = $ARGV[1];
13$wfile = $ARGV[2];
14$sstart = $ARGV[3];
15$rollid = 0;
16open(DUMPLFS, "dumplfs $rdev |");
17
18# Look for "roll_id" so we don't use garbage
19while (<DUMPLFS>) {
20	if ($ssize == 0 && m/ssize *([0-9]*)/) {
21		$ssize = $1;
22	}
23	if ($fsize == 0 && m/fsize *([0-9]*)/) {
24		$fsize = $1;
25	}
26	if (m/roll_id *([x0-9a-f]*)/) {
27		$rollid = $1;
28		last;
29	}
30}
31
32# Now look for inodes and segment summaries.  Build a hash table of these
33# based on serial number.  Ignore any with serial numbers lower than $sstart.
34
35%iloc = ();
36%snloc = ();
37%sumloc = ();
38print "Reading segments:";
39while (<DUMPLFS>) {
40	if (m/roll_id *([0-9a-f]*)/) {
41		# print "rollid $1\n";
42		if ("0x$1" ne $rollid) {
43			# Skip the rest of this segment
44			while(<DUMPLFS>) {
45				last if m/SEGMENT/;
46			}
47			# Fall through
48		}
49	}
50	if (m/roll_id.*serial *([0-9]*)/) {
51		$serno = $1;
52		$snloc{$serno} = $segnum;
53		$sumloc{$serno} = $sumloc;
54		# print "serno $serno\n";
55		if ($serno < $sstart) {
56			# Skip the rest of this partial segment
57			while(<DUMPLFS>) {
58				last if m/Segment Summary/ ||
59					m/SEGMENT/;
60			}
61			# Fall through
62		}
63	}
64	if (m/Segment Summary Info at 0x([0-9a-f]*)/) {
65		$sumloc = $1;
66	}
67	if (m/0x([0-9a-f]*)/) {
68		foreach $ss (split "0x", $_) {
69			if ($ss =~ m/^([0-9a-f][0-9a-f]*)/) {
70				# print "iblk 0x$1\n";
71				$daddr = $1;
72				if (m/[^0-9]1v1/) {
73					# print "** ifblk 0x$daddr\n";
74					$iloc{$serno} = $daddr;
75					$lastaddr = $daddr;
76				}
77			}
78		}
79	}
80	if (m/SEGMENT *([0-9]*)/) {
81		$segnum = $1;
82		print " $segnum";
83	}
84}
85print "\n";
86close(DUMPLFS);
87
88# If there were no checkpoints, print *something*
89if ($#iloc == 0) {
90	print "0 $sstart 0\n";
91	exit 0;
92}
93
94#
95# Now fsck each checkpoint in turn, beginning with $sstart.
96# Because the log wraps we will have to reconstruct the filesystem image
97# as it existed at each checkpoint before running fsck.
98#
99# Look for lines containing only caps or "!", but ignore known
100# false positives.
101#
102$error = 0;
103$lastgood = $sstart - 1;
104open(LOG, ">>check-all.log");
105print "Available checkpoints:";
106print LOG "Available checkpoints:";
107foreach $k (sort { $a <=> $b } keys %iloc) {
108	$a = $iloc{$k};
109	print " $a";
110	print LOG " $a";
111}
112print "\n";
113print LOG "\n";
114
115print "Recreating filesystem image as of $sstart:\n";
116if ($sstart == 0) {
117	$cmd = "dd if=$rdev of=$wfile bs=1m conv=swab,oldebcdic"; # garbage
118} else {
119	$cmd = "dd if=$gfile of=$wfile bs=1m";
120}
121print "$cmd\n";
122system("$cmd >/dev/null 2>&1");
123
124$blstart = 0;
125$fps = $ssize / $fsize;
126foreach $k (sort { $a <=> $b } keys %iloc) {
127	$a = $iloc{$k};
128
129	if (hex($a) > hex($lastaddr)) {
130		print "Skipping out-of-place checkpoint $k at $a\n";
131		next;
132	}
133
134	# Copy the balance of the "new" fs image over the old one.
135	$blstop = int ((hex $a) / $fps) + 1;
136	$blstop *= $fps;
137	if ($blstop != $blstart) {
138		$cmd = "dd if=$rdev of=$wfile bs=$fsize seek=$blstart " .
139			"skip=$blstart conv=notrunc";
140		if ($blstop) {
141			$cmd .= " count=" . ($blstop - $blstart);
142		}
143		$blstart = $blstop;
144		print "$cmd\n";
145		system("$cmd >/dev/null 2>&1");
146	}
147
148	$cmd = "fsck_lfs -n -f -i 0x$a $wfile";
149	print "$cmd\n";
150	print LOG "$cmd\n";
151	open(FSCK, "$cmd 2>&1 |");
152	while(<FSCK>) {
153		print LOG;
154		chomp;
155
156		# Known false positives (mismatch between sb and ifile,
157		# which should be expected given we're using an arbitrarily
158		# old version fo the ifile)
159		if (m/AVAIL GIVEN/ ||
160		    m/BFREE GIVEN/ ||
161		    m/DMETA GIVEN/ ||
162		    m/NCLEAN GIVEN/ ||
163		    m/FREE BUT NOT ON FREE LIST/ ||	# UNWRITTEN inodes OK
164		    m/FREE LIST HEAD IN SUPERBLOCK/ ) {
165			next;
166		}
167
168		# Fsck reports errors in ALL CAPS
169		# But don't count hex numbers as "lowercase".
170		s/0x[0-9a-f]*//g;
171		if (m/[A-Z]/ && ! m/[a-z]/) {
172			$error = 1;
173			$errsn = $k;
174			$errstr = "1 $k 0x$a";
175			last;
176		}
177	}
178	close(FSCK);
179	last if $error;
180	$lastgood = $k;	# record last good serial number
181}
182$errstr = "0 $lastgood 0x$a" unless $errstr;
183
184if ($error == 0) {
185	print "Copying this good image to $gfile\n";
186	system("dd bs=1m if=$rdev of=$gfile >/dev/null 2>&1");
187	print "$errstr\n";
188	exit 0;
189}
190
191#
192# If we found an error, try to find which blocks of the Ifile inode changed
193# between the last good checkpoint and this checkpoint; and which blocks
194# *should* have changed.  This means (1) which segments were written; and
195# (2) which inodes were written.  The 0 block of the Ifile should always
196# have changed since lfs_avail is always in flux.
197#
198
199$cmd = "dumplfs";
200$oseg = -1;
201%iblk = ();
202%iblk_done = ();
203%why = ();
204$iblk{0} = 1;
205for ($i = $lastgood + 1; $i <= $errsn; $i++) {
206	if ($oseg != $snloc{$i}) {
207		$oseg = 0 + $snloc{$i};
208		$cmd .= " -s$oseg";
209	}
210}
211$cmd .= " $rdev";
212
213open(DUMPLFS, "$cmd |");
214while(<DUMPLFS>) {
215	if (m/ifpb *([0-9]*)/) {
216		$ifpb = $1;
217	}
218	if (m/sepb *([0-9]*)/) {
219		$sepb = $1;
220	}
221	if (m/cleansz *([0-9]*)/) {
222		$cleansz = $1;
223	}
224	if (m/segtabsz *([0-9]*)/) {
225		$segtabsz = $1;
226	}
227	last if m/SEGMENT/;
228}
229while(<DUMPLFS>) {
230	chomp;
231
232	# Skip over partial segments outside our range of interest
233	if (m/roll_id.*serial *([0-9]*)/) {
234		$serno = $1;
235		if ($serno <= $lastgood || $serno > $errsn) {
236			# Skip the rest of this partial segment
237			while(<DUMPLFS>) {
238				last if m/Segment Summary/ || m/SEGMENT/;
239			}
240			next;
241		}
242	}
243
244	# Look for inodes
245	if (m/Inode addresses/) {
246		s/^[^{]*{/ /o;
247		s/}[^{]*$/ /o;
248		s/}[^{]*{/,/og;
249		s/v[0-9]*//og;
250		@ilist = split(',');
251		foreach $i (@ilist) {
252			$i =~ s/ *//og;
253			next if $i == 1;
254			$iaddr = $cleansz + $segtabsz + int ($i / $ifpb);
255			$iblk{$iaddr} = 1;
256			$why{$iaddr} .= " $i";
257		}
258	}
259
260	# Look for Ifile blocks actually written
261	if (m/FINFO for inode: ([0-9]*) version/) {
262		$i = $1;
263		$inoblkmode = ($i == 1);
264	}
265	if ($inoblkmode && m/^[-\t 0-9]*$/) {
266		s/\t/ /og;
267		s/^ *//o;
268		s/ *$//o;
269		@bn = split(' ');
270		foreach $b (@bn) {
271			$iblk_done{$b} = 1;
272		}
273	}
274}
275close(DUMPLFS);
276
277# Report found and missing Ifile blocks
278print "Ifile blocks found:";
279foreach $b (sort { $a <=> $b } keys %iblk) {
280	if ($iblk_done{$b} == 1) {
281		print " $b";
282	}
283}
284print "\n";
285
286print "Ifile blocks missing:";
287foreach $b (sort { $a <=> $b } keys %iblk) {
288	if ($iblk_done{$b} == 0) {
289		$why{$b} =~ s/^ *//o;
290		print " $b ($why{$b})";
291	}
292}
293print "\n";
294
295print "$errstr\n";
296exit 0;
297