xref: /freebsd/sbin/ffsinfo/ffsinfo.c (revision 7660b554bc59a07be0431c17e0e33815818baa69)
1 /*
2  * Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
3  * Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgment:
19  *      This product includes software developed by the University of
20  *      California, Berkeley and its contributors, as well as Christoph
21  *      Herrmann and Thomas-Henning von Kamptz.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  *
38  * $TSHeader: src/sbin/ffsinfo/ffsinfo.c,v 1.4 2000/12/12 19:30:55 tomsoft Exp $
39  *
40  */
41 
42 #ifndef lint
43 static const char copyright[] =
44 "@(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz\n\
45 Copyright (c) 1980, 1989, 1993 The Regents of the University of California.\n\
46 All rights reserved.\n";
47 #endif /* not lint */
48 
49 #ifndef lint
50 static const char rcsid[] =
51   "$FreeBSD$";
52 #endif /* not lint */
53 
54 /* ********************************************************** INCLUDES ***** */
55 #include <sys/param.h>
56 #include <sys/disklabel.h>
57 #include <sys/mount.h>
58 #include <sys/stat.h>
59 
60 #include <ufs/ufs/ufsmount.h>
61 #include <ufs/ufs/dinode.h>
62 #include <ufs/ffs/fs.h>
63 
64 #include <ctype.h>
65 #include <err.h>
66 #include <fcntl.h>
67 #include <libufs.h>
68 #include <paths.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <unistd.h>
73 
74 #include "debug.h"
75 
76 /* *********************************************************** GLOBALS ***** */
77 #ifdef FS_DEBUG
78 int	_dbg_lvl_ = (DL_INFO); /* DL_TRC */
79 #endif /* FS_DEBUG */
80 
81 struct uufsd disk;
82 
83 #define sblock disk.d_fs
84 #define acg    disk.d_cg
85 
86 static union {
87 	struct fs fs;
88 	char pad[SBLOCKSIZE];
89 } fsun;
90 
91 #define osblock fsun.fs
92 
93 static char	i1blk[MAXBSIZE];
94 static char	i2blk[MAXBSIZE];
95 static char	i3blk[MAXBSIZE];
96 
97 static struct csum	*fscs;
98 
99 /* ******************************************************** PROTOTYPES ***** */
100 static void	usage(void);
101 static void	dump_whole_ufs1_inode(ino_t, int);
102 static void	dump_whole_ufs2_inode(ino_t, int);
103 
104 #define DUMP_WHOLE_INODE(A,B) \
105 	( disk.d_ufs == 1 \
106 		? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
107 
108 /* ************************************************************** main ***** */
109 /*
110  * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
111  * errors is the file system much easier. You can run ffsinfo before and  after
112  * an  fsck(8),  and compare the two ascii dumps easy with diff, and  you  see
113  * directly where the problem is. You can control how much detail you want  to
114  * see  with some command line arguments. You can also easy check  the  status
115  * of  a file system, like is there is enough space for growing  a  file system,
116  * or  how  many active snapshots do we have. It provides much  more  detailed
117  * information  then dumpfs. Snapshots, as they are very new, are  not  really
118  * supported.  They  are just mentioned currently, but it is  planned  to  run
119  * also over active snapshots, to even get that output.
120  */
121 int
122 main(int argc, char **argv)
123 {
124 	DBG_FUNC("main")
125 	char	*device, *special;
126 	char	ch;
127 	size_t	len;
128 	struct stat	st;
129 	struct csum	*dbg_csp;
130 	int	dbg_csc;
131 	char	dbg_line[80];
132 	int	cylno,i;
133 	int	cfg_cg, cfg_in, cfg_lv;
134 	int	cg_start, cg_stop;
135 	ino_t	in;
136 	char	*out_file;
137 
138 	DBG_ENTER;
139 
140 	cfg_lv=0xff;
141 	cfg_in=-2;
142 	cfg_cg=-2;
143 	out_file=strdup("/var/tmp/ffsinfo");
144 	if(out_file == NULL) {
145 		errx(1, "strdup failed");
146 	}
147 
148 	while ((ch=getopt(argc, argv, "g:i:l:o:")) != -1) {
149 		switch(ch) {
150 		case 'g':
151 			cfg_cg=atol(optarg);
152 			if(cfg_cg < -1) {
153 				usage();
154 			}
155 			break;
156 		case 'i':
157 			cfg_in=atol(optarg);
158 			if(cfg_in < 0) {
159 				usage();
160 			}
161 			break;
162 		case 'l':
163 			cfg_lv=atol(optarg);
164 			if(cfg_lv < 0x1||cfg_lv > 0x3ff) {
165 				usage();
166 			}
167 			break;
168 		case 'o':
169 			free(out_file);
170 			out_file=strdup(optarg);
171 			if(out_file == NULL) {
172 				errx(1, "strdup failed");
173 			}
174 			break;
175 		case '?':
176 			/* FALLTHROUGH */
177 		default:
178 			usage();
179 		}
180 	}
181 	argc -= optind;
182 	argv += optind;
183 
184 	if(argc != 1) {
185 		usage();
186 	}
187 	device=*argv;
188 
189 	/*
190 	 * Now we try to guess the (raw)device name.
191 	 */
192 	if (0 == strrchr(device, '/') && (stat(device, &st) == -1)) {
193 		/*
194 		 * No path prefix was given, so try in that order:
195 		 *     /dev/r%s
196 		 *     /dev/%s
197 		 *     /dev/vinum/r%s
198 		 *     /dev/vinum/%s.
199 		 *
200 		 * FreeBSD now doesn't distinguish between raw and  block
201 		 * devices any longer, but it should still work this way.
202 		 */
203 		len=strlen(device)+strlen(_PATH_DEV)+2+strlen("vinum/");
204 		special=(char *)malloc(len);
205 		if(special == NULL) {
206 			errx(1, "malloc failed");
207 		}
208 		snprintf(special, len, "%sr%s", _PATH_DEV, device);
209 		if (stat(special, &st) == -1) {
210 			snprintf(special, len, "%s%s", _PATH_DEV, device);
211 			if (stat(special, &st) == -1) {
212 				snprintf(special, len, "%svinum/r%s",
213 				    _PATH_DEV, device);
214 				if (stat(special, &st) == -1) {
215 					/*
216 					 * For now this is the 'last resort'.
217 					 */
218 					snprintf(special, len, "%svinum/%s",
219 					    _PATH_DEV, device);
220 				}
221 			}
222 		}
223 		device = special;
224 	}
225 
226 	if (ufs_disk_fillout(&disk, device) == -1)
227 		err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
228 
229 	DBG_OPEN(out_file); /* already here we need a superblock */
230 
231 	if(cfg_lv & 0x001) {
232 		DBG_DUMP_FS(&sblock,
233 		    "primary sblock");
234 	}
235 
236 	/*
237 	 * Determine here what cylinder groups to dump.
238 	 */
239 	if(cfg_cg==-2) {
240 		cg_start=0;
241 		cg_stop=sblock.fs_ncg;
242 	} else if (cfg_cg==-1) {
243 		cg_start=sblock.fs_ncg-1;
244 		cg_stop=sblock.fs_ncg;
245 	} else if (cfg_cg<sblock.fs_ncg) {
246 		cg_start=cfg_cg;
247 		cg_stop=cfg_cg+1;
248 	} else {
249 		cg_start=sblock.fs_ncg;
250 		cg_stop=sblock.fs_ncg;
251 	}
252 
253 	if (cfg_lv & 0x004) {
254 		fscs = (struct csum *)calloc((size_t)1,
255 		    (size_t)sblock.fs_cssize);
256 		if(fscs == NULL) {
257 			errx(1, "calloc failed");
258 		}
259 
260 		/*
261 		 * Get the cylinder summary into the memory ...
262 		 */
263 		for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
264 			if (bread(&disk,
265 				fsbtodb(&sblock, sblock.fs_csaddr + numfrags(&sblock, i)),
266 				(void *)(((char *)fscs)+i),
267 				(size_t)(sblock.fs_cssize-i < sblock.fs_bsize
268 					? sblock.fs_cssize - i
269 					: sblock.fs_bsize)) == -1) {
270 				err(1, "bread: %s", disk.d_error);
271 			}
272 		}
273 
274 		dbg_csp=fscs;
275 		/*
276 		 * ... and dump it.
277 		 */
278 		for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
279 			snprintf(dbg_line, sizeof(dbg_line),
280 			    "%d. csum in fscs", dbg_csc);
281 			DBG_DUMP_CSUM(&sblock,
282 			    dbg_line,
283 			    dbg_csp++);
284 		}
285 	}
286 
287 	/*
288 	 * For each requested cylinder group ...
289 	 */
290 	for(cylno=cg_start; cylno<cg_stop; cylno++) {
291 		snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
292 		if(cfg_lv & 0x002) {
293 			/*
294 			 * ... dump the superblock copies ...
295 			 */
296 			if (bread(&disk, fsbtodb(&sblock, cgsblock(&sblock, cylno)),
297 				(void *)&osblock, SBLOCKSIZE) == -1) {
298 				err(1, "bread: %s", disk.d_error);
299 			}
300 			DBG_DUMP_FS(&osblock,
301 			    dbg_line);
302 		}
303 		/*
304 		 * ... read the cylinder group and dump whatever was requested.
305 		 */
306 		if (bread(&disk, fsbtodb(&sblock, cgtod(&sblock, cylno)),
307 			(void *)&acg, (size_t)sblock.fs_cgsize) == -1) {
308 			err(1, "bread: %s", disk.d_error);
309 		}
310 		if(cfg_lv & 0x008) {
311 			DBG_DUMP_CG(&sblock,
312 			    dbg_line,
313 			    &acg);
314 		}
315 		if(cfg_lv & 0x010) {
316 			DBG_DUMP_INMAP(&sblock,
317 			    dbg_line,
318 			    &acg);
319 		}
320 		if(cfg_lv & 0x020) {
321 			DBG_DUMP_FRMAP(&sblock,
322 			    dbg_line,
323 			    &acg);
324 		}
325 		if(cfg_lv & 0x040) {
326 			DBG_DUMP_CLMAP(&sblock,
327 			    dbg_line,
328 			    &acg);
329 			DBG_DUMP_CLSUM(&sblock,
330 			    dbg_line,
331 			    &acg);
332 		}
333 #ifdef NOT_CURRENTLY
334 		/*
335 		 * See the comment in sbin/growfs/debug.c for why this
336 		 * is currently disabled, and what needs to be done to
337 		 * re-enable it.
338 		 */
339 		if(disk.d_ufs == 1 && cfg_lv & 0x080) {
340 			DBG_DUMP_SPTBL(&sblock,
341 			    dbg_line,
342 			    &acg);
343 		}
344 #endif
345 	}
346 	/*
347 	 * Dump the requested inode(s).
348 	 */
349 	if(cfg_in != -2) {
350 		DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
351 	} else {
352 		for(in=cg_start*sblock.fs_ipg; in<(ino_t)cg_stop*sblock.fs_ipg;
353 		    in++) {
354 			DUMP_WHOLE_INODE(in, cfg_lv);
355 		}
356 	}
357 
358 	DBG_CLOSE;
359 
360 	DBG_LEAVE;
361 	return 0;
362 }
363 
364 /* ********************************************** dump_whole_ufs1_inode ***** */
365 /*
366  * Here we dump a list of all blocks allocated by this inode. We follow
367  * all indirect blocks.
368  */
369 void
370 dump_whole_ufs1_inode(ino_t inode, int level)
371 {
372 	DBG_FUNC("dump_whole_ufs1_inode")
373 	struct ufs1_dinode	*ino;
374 	int	rb, mode;
375 	unsigned int	ind2ctr, ind3ctr;
376 	ufs1_daddr_t	*ind2ptr, *ind3ptr;
377 	char	comment[80];
378 
379 	DBG_ENTER;
380 
381 	/*
382 	 * Read the inode from disk/cache.
383 	 */
384 	if (getino(&disk, (void **)&ino, inode, &mode) == -1)
385 		err(1, "getino: %s", disk.d_error);
386 
387 	if(ino->di_nlink==0) {
388 		DBG_LEAVE;
389 		return;	/* inode not in use */
390 	}
391 
392 	/*
393 	 * Dump the main inode structure.
394 	 */
395 	snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
396 	if (level & 0x100) {
397 		DBG_DUMP_INO(&sblock,
398 		    comment,
399 		    ino);
400 	}
401 
402 	if (!(level & 0x200)) {
403 		DBG_LEAVE;
404 		return;
405 	}
406 
407 	/*
408 	 * Ok, now prepare for dumping all direct and indirect pointers.
409 	 */
410 	rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
411 	if(rb>0) {
412 		/*
413 		 * Dump single indirect block.
414 		 */
415 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
416 			(size_t)sblock.fs_bsize) == -1) {
417 			err(1, "bread: %s", disk.d_error);
418 		}
419 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0",
420 		    inode);
421 		DBG_DUMP_IBLK(&sblock,
422 		    comment,
423 		    i1blk,
424 		    (size_t)rb);
425 		rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
426 	}
427 	if(rb>0) {
428 		/*
429 		 * Dump double indirect blocks.
430 		 */
431 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
432 			(size_t)sblock.fs_bsize) == -1) {
433 			err(1, "bread: %s", disk.d_error);
434 		}
435 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1",
436 		    inode);
437 		DBG_DUMP_IBLK(&sblock,
438 		    comment,
439 		    i2blk,
440 		    howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
441 		for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
442 			sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) {
443 			ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
444 
445 			if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
446 				(size_t)sblock.fs_bsize) == -1) {
447 				err(1, "bread: %s", disk.d_error);
448 			}
449 			snprintf(comment, sizeof(comment),
450 			    "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
451 			DBG_DUMP_IBLK(&sblock,
452 			    comment,
453 			    i1blk,
454 			    (size_t)rb);
455 			rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
456 		}
457 	}
458 	if(rb>0) {
459 		/*
460 		 * Dump triple indirect blocks.
461 		 */
462 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
463 			(size_t)sblock.fs_bsize) == -1) {
464 			err(1, "bread: %s", disk.d_error);
465 		}
466 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2",
467 		    inode);
468 #define SQUARE(a) ((a)*(a))
469 		DBG_DUMP_IBLK(&sblock,
470 		    comment,
471 		    i3blk,
472 		    howmany(rb,
473 		      SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
474 #undef SQUARE
475 		for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize,
476 			sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) {
477 			ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
478 
479 			if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
480 				(size_t)sblock.fs_bsize) == -1) {
481 				err(1, "bread: %s", disk.d_error);
482 			}
483 			snprintf(comment, sizeof(comment),
484 			    "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
485 			DBG_DUMP_IBLK(&sblock,
486 			    comment,
487 			    i2blk,
488 			    howmany(rb,
489 			      howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
490 			for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
491 			    sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) {
492 				ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
493 				    [ind2ctr];
494 				if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
495 				    (void *)&i1blk, (size_t)sblock.fs_bsize)
496 				    == -1) {
497 					err(1, "bread: %s", disk.d_error);
498 				}
499 				snprintf(comment, sizeof(comment),
500 				    "Inode 0x%08x: indirect 2->%d->%d", inode,
501 				    ind3ctr, ind3ctr);
502 				DBG_DUMP_IBLK(&sblock,
503 				    comment,
504 				    i1blk,
505 				    (size_t)rb);
506 				rb-=howmany(sblock.fs_bsize,
507 				    sizeof(ufs1_daddr_t));
508 			}
509 		}
510 	}
511 
512 	DBG_LEAVE;
513 	return;
514 }
515 
516 /* ********************************************** dump_whole_ufs2_inode ***** */
517 /*
518  * Here we dump a list of all blocks allocated by this inode. We follow
519  * all indirect blocks.
520  */
521 void
522 dump_whole_ufs2_inode(ino_t inode, int level)
523 {
524 	DBG_FUNC("dump_whole_ufs2_inode")
525 	struct ufs2_dinode	*ino;
526 	int	rb, mode;
527 	unsigned int	ind2ctr, ind3ctr;
528 	ufs2_daddr_t	*ind2ptr, *ind3ptr;
529 	char	comment[80];
530 
531 	DBG_ENTER;
532 
533 	/*
534 	 * Read the inode from disk/cache.
535 	 */
536 	if (getino(&disk, (void **)&ino, inode, &mode) == -1)
537 		err(1, "getino: %s", disk.d_error);
538 
539 	if (ino->di_nlink == 0) {
540 		DBG_LEAVE;
541 		return;	/* inode not in use */
542 	}
543 
544 	/*
545 	 * Dump the main inode structure.
546 	 */
547 	snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
548 	if (level & 0x100) {
549 		DBG_DUMP_INO(&sblock, comment, ino);
550 	}
551 
552 	if (!(level & 0x200)) {
553 		DBG_LEAVE;
554 		return;
555 	}
556 
557 	/*
558 	 * Ok, now prepare for dumping all direct and indirect pointers.
559 	 */
560 	rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR;
561 	if (rb > 0) {
562 		/*
563 		 * Dump single indirect block.
564 		 */
565 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
566 			(size_t)sblock.fs_bsize) == -1) {
567 			err(1, "bread: %s", disk.d_error);
568 		}
569 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", inode);
570 		DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
571 		rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
572 	}
573 	if (rb > 0) {
574 		/*
575 		 * Dump double indirect blocks.
576 		 */
577 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
578 			(size_t)sblock.fs_bsize) == -1) {
579 			err(1, "bread: %s", disk.d_error);
580 		}
581 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", inode);
582 		DBG_DUMP_IBLK(&sblock,
583 			comment,
584 			i2blk,
585 			howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
586 		for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
587 			sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
588 			ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
589 
590 			if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
591 				(size_t)sblock.fs_bsize) == -1) {
592 				err(1, "bread: %s", disk.d_error);
593 			}
594 			snprintf(comment, sizeof(comment),
595 				"Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
596 			DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
597 			rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
598 		}
599 	}
600 	if (rb > 0) {
601 		/*
602 		 * Dump triple indirect blocks.
603 		 */
604 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
605 			(size_t)sblock.fs_bsize) == -1) {
606 			err(1, "bread: %s", disk.d_error);
607 		}
608 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", inode);
609 #define SQUARE(a) ((a)*(a))
610 		DBG_DUMP_IBLK(&sblock,
611 			comment,
612 			i3blk,
613 			howmany(rb,
614 				SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
615 #undef SQUARE
616 		for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
617 			sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
618 			ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
619 
620 			if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
621 				(size_t)sblock.fs_bsize) == -1) {
622 				err(1, "bread: %s", disk.d_error);
623 			}
624 			snprintf(comment, sizeof(comment),
625 				"Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
626 			DBG_DUMP_IBLK(&sblock,
627 				comment,
628 				i2blk,
629 				howmany(rb,
630 					howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
631 			for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
632 				sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
633 				ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
634 				if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
635 					(size_t)sblock.fs_bsize) == -1) {
636 					err(1, "bread: %s", disk.d_error);
637 				}
638 				snprintf(comment, sizeof(comment),
639 					"Inode 0x%08x: indirect 2->%d->%d", inode,
640 					ind3ctr, ind3ctr);
641 				DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
642 				rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
643 			}
644 		}
645 	}
646 
647 	DBG_LEAVE;
648 	return;
649 }
650 
651 /* ************************************************************* usage ***** */
652 /*
653  * Dump a line of usage.
654  */
655 void
656 usage(void)
657 {
658 	DBG_FUNC("usage")
659 
660 	DBG_ENTER;
661 
662 	fprintf(stderr,
663 	    "usage: ffsinfo [-L] [-g cylgrp] [-i inode] [-l level] "
664 	    "[-o outfile]\n"
665 	    "               special | file\n");
666 
667 	DBG_LEAVE;
668 	exit(1);
669 }
670