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;
16 open(DUMPLFS, "dumplfs $rdev |");
17
18 # Look for "roll_id" so we don't use garbage
19 while (<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 = ();
38 print "Reading segments:";
39 while (<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 }
85 print "\n";
86 close(DUMPLFS);
87
88 # If there were no checkpoints, print *something*
89 if ($#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;
104 open(LOG, ">>check-all.log");
105 print "Available checkpoints:";
106 print LOG "Available checkpoints:";
107 foreach $k (sort { $a <=> $b } keys %iloc) {
108 $a = $iloc{$k};
109 print " $a";
110 print LOG " $a";
111 }
112 print "\n";
113 print LOG "\n";
114
115 print "Recreating filesystem image as of $sstart:\n";
116 if ($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 }
121 print "$cmd\n";
122 system("$cmd >/dev/null 2>&1");
123
124 $blstart = 0;
125 $fps = $ssize / $fsize;
126 foreach $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
184 if ($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;
205 for ($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
213 open(DUMPLFS, "$cmd |");
214 while(<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 }
229 while(<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 }
275 close(DUMPLFS);
276
277 # Report found and missing Ifile blocks
278 print "Ifile blocks found:";
279 foreach $b (sort { $a <=> $b } keys %iblk) {
280 if ($iblk_done{$b} == 1) {
281 print " $b";
282 }
283 }
284 print "\n";
285
286 print "Ifile blocks missing:";
287 foreach $b (sort { $a <=> $b } keys %iblk) {
288 if ($iblk_done{$b} == 0) {
289 $why{$b} =~ s/^ *//o;
290 print " $b ($why{$b})";
291 }
292 }
293 print "\n";
294
295 print "$errstr\n";
296 exit 0;
297