xref: /freebsd/sbin/ffsinfo/ffsinfo.c (revision 87569f75a91f298c52a71823c04d41cf53c88889)
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 <errno.h>
67 #include <fcntl.h>
68 #include <libufs.h>
69 #include <paths.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74 
75 #include "debug.h"
76 
77 /* *********************************************************** GLOBALS ***** */
78 #ifdef FS_DEBUG
79 int	_dbg_lvl_ = (DL_INFO); /* DL_TRC */
80 #endif /* FS_DEBUG */
81 
82 struct uufsd disk;
83 
84 #define sblock disk.d_fs
85 #define acg    disk.d_cg
86 
87 static union {
88 	struct fs fs;
89 	char pad[SBLOCKSIZE];
90 } fsun;
91 
92 #define osblock fsun.fs
93 
94 static char	i1blk[MAXBSIZE];
95 static char	i2blk[MAXBSIZE];
96 static char	i3blk[MAXBSIZE];
97 
98 static struct csum	*fscs;
99 
100 /* ******************************************************** PROTOTYPES ***** */
101 static void	usage(void);
102 static void	dump_whole_ufs1_inode(ino_t, int);
103 static void	dump_whole_ufs2_inode(ino_t, int);
104 
105 #define DUMP_WHOLE_INODE(A,B) \
106 	( disk.d_ufs == 1 \
107 		? dump_whole_ufs1_inode((A),(B)) : dump_whole_ufs2_inode((A),(B)) )
108 
109 /* ************************************************************** main ***** */
110 /*
111  * ffsinfo(8) is a tool to dump all metadata of a file system. It helps to find
112  * errors is the file system much easier. You can run ffsinfo before and  after
113  * an  fsck(8),  and compare the two ascii dumps easy with diff, and  you  see
114  * directly where the problem is. You can control how much detail you want  to
115  * see  with some command line arguments. You can also easy check  the  status
116  * of  a file system, like is there is enough space for growing  a  file system,
117  * or  how  many active snapshots do we have. It provides much  more  detailed
118  * information  then dumpfs. Snapshots, as they are very new, are  not  really
119  * supported.  They  are just mentioned currently, but it is  planned  to  run
120  * also over active snapshots, to even get that output.
121  */
122 int
123 main(int argc, char **argv)
124 {
125 	DBG_FUNC("main")
126 	char	*device, *special;
127 	int	ch;
128 	size_t	len;
129 	struct stat	st;
130 	struct csum	*dbg_csp;
131 	int	dbg_csc;
132 	char	dbg_line[80];
133 	int	cylno,i;
134 	int	cfg_cg, cfg_in, cfg_lv;
135 	int	cg_start, cg_stop;
136 	ino_t	in;
137 	char	*out_file;
138 
139 	DBG_ENTER;
140 
141 	cfg_lv=0xff;
142 	cfg_in=-2;
143 	cfg_cg=-2;
144 	out_file=NULL;
145 
146 	while ((ch=getopt(argc, argv, "g:i:l:o:")) != -1) {
147 		switch(ch) {
148 		case 'g':
149 			cfg_cg=strtol(optarg, NULL, 0);
150 			if(errno == EINVAL||errno == ERANGE)
151 				err(1, "%s", optarg);
152 			if(cfg_cg < -1) {
153 				usage();
154 			}
155 			break;
156 		case 'i':
157 			cfg_in=strtol(optarg, NULL, 0);
158 			if(errno == EINVAL||errno == ERANGE)
159 				err(1, "%s", optarg);
160 			if(cfg_in < 0) {
161 				usage();
162 			}
163 			break;
164 		case 'l':
165 			cfg_lv=strtol(optarg, NULL, 0);
166 			if(errno == EINVAL||errno == ERANGE)
167 				err(1, "%s", optarg);
168 			if(cfg_lv < 0x1||cfg_lv > 0x3ff) {
169 				usage();
170 			}
171 			break;
172 		case 'o':
173 			free(out_file);
174 			out_file=strdup(optarg);
175 			if(out_file == NULL) {
176 				errx(1, "strdup failed");
177 			}
178 			break;
179 		case '?':
180 			/* FALLTHROUGH */
181 		default:
182 			usage();
183 		}
184 	}
185 	argc -= optind;
186 	argv += optind;
187 
188 	if(argc != 1) {
189 		usage();
190 	}
191 	device=*argv;
192 	if (out_file == NULL)
193 		errx(1, "out_file not specified");
194 
195 	/*
196 	 * Now we try to guess the (raw)device name.
197 	 */
198 	if (0 == strrchr(device, '/') && (stat(device, &st) == -1)) {
199 		/*
200 		 * No path prefix was given, so try in that order:
201 		 *     /dev/r%s
202 		 *     /dev/%s
203 		 *     /dev/vinum/r%s
204 		 *     /dev/vinum/%s.
205 		 *
206 		 * FreeBSD now doesn't distinguish between raw and  block
207 		 * devices any longer, but it should still work this way.
208 		 */
209 		len=strlen(device)+strlen(_PATH_DEV)+2+strlen("vinum/");
210 		special=(char *)malloc(len);
211 		if(special == NULL) {
212 			errx(1, "malloc failed");
213 		}
214 		snprintf(special, len, "%sr%s", _PATH_DEV, device);
215 		if (stat(special, &st) == -1) {
216 			snprintf(special, len, "%s%s", _PATH_DEV, device);
217 			if (stat(special, &st) == -1) {
218 				snprintf(special, len, "%svinum/r%s",
219 				    _PATH_DEV, device);
220 				if (stat(special, &st) == -1) {
221 					/*
222 					 * For now this is the 'last resort'.
223 					 */
224 					snprintf(special, len, "%svinum/%s",
225 					    _PATH_DEV, device);
226 				}
227 			}
228 		}
229 		device = special;
230 	}
231 
232 	if (ufs_disk_fillout(&disk, device) == -1)
233 		err(1, "ufs_disk_fillout(%s) failed: %s", device, disk.d_error);
234 
235 	DBG_OPEN(out_file); /* already here we need a superblock */
236 
237 	if(cfg_lv & 0x001) {
238 		DBG_DUMP_FS(&sblock,
239 		    "primary sblock");
240 	}
241 
242 	/*
243 	 * Determine here what cylinder groups to dump.
244 	 */
245 	if(cfg_cg==-2) {
246 		cg_start=0;
247 		cg_stop=sblock.fs_ncg;
248 	} else if (cfg_cg==-1) {
249 		cg_start=sblock.fs_ncg-1;
250 		cg_stop=sblock.fs_ncg;
251 	} else if (cfg_cg<sblock.fs_ncg) {
252 		cg_start=cfg_cg;
253 		cg_stop=cfg_cg+1;
254 	} else {
255 		cg_start=sblock.fs_ncg;
256 		cg_stop=sblock.fs_ncg;
257 	}
258 
259 	if (cfg_lv & 0x004) {
260 		fscs = (struct csum *)calloc((size_t)1,
261 		    (size_t)sblock.fs_cssize);
262 		if(fscs == NULL) {
263 			errx(1, "calloc failed");
264 		}
265 
266 		/*
267 		 * Get the cylinder summary into the memory ...
268 		 */
269 		for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
270 			if (bread(&disk,
271 				fsbtodb(&sblock, sblock.fs_csaddr + numfrags(&sblock, i)),
272 				(void *)(((char *)fscs)+i),
273 				(size_t)(sblock.fs_cssize-i < sblock.fs_bsize
274 					? sblock.fs_cssize - i
275 					: sblock.fs_bsize)) == -1) {
276 				err(1, "bread: %s", disk.d_error);
277 			}
278 		}
279 
280 		dbg_csp=fscs;
281 		/*
282 		 * ... and dump it.
283 		 */
284 		for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
285 			snprintf(dbg_line, sizeof(dbg_line),
286 			    "%d. csum in fscs", dbg_csc);
287 			DBG_DUMP_CSUM(&sblock,
288 			    dbg_line,
289 			    dbg_csp++);
290 		}
291 	}
292 
293 	/*
294 	 * For each requested cylinder group ...
295 	 */
296 	for(cylno=cg_start; cylno<cg_stop; cylno++) {
297 		snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
298 		if(cfg_lv & 0x002) {
299 			/*
300 			 * ... dump the superblock copies ...
301 			 */
302 			if (bread(&disk, fsbtodb(&sblock, cgsblock(&sblock, cylno)),
303 				(void *)&osblock, SBLOCKSIZE) == -1) {
304 				err(1, "bread: %s", disk.d_error);
305 			}
306 			DBG_DUMP_FS(&osblock,
307 			    dbg_line);
308 		}
309 		/*
310 		 * ... read the cylinder group and dump whatever was requested.
311 		 */
312 		if (bread(&disk, fsbtodb(&sblock, cgtod(&sblock, cylno)),
313 			(void *)&acg, (size_t)sblock.fs_cgsize) == -1) {
314 			err(1, "bread: %s", disk.d_error);
315 		}
316 		if(cfg_lv & 0x008) {
317 			DBG_DUMP_CG(&sblock,
318 			    dbg_line,
319 			    &acg);
320 		}
321 		if(cfg_lv & 0x010) {
322 			DBG_DUMP_INMAP(&sblock,
323 			    dbg_line,
324 			    &acg);
325 		}
326 		if(cfg_lv & 0x020) {
327 			DBG_DUMP_FRMAP(&sblock,
328 			    dbg_line,
329 			    &acg);
330 		}
331 		if(cfg_lv & 0x040) {
332 			DBG_DUMP_CLMAP(&sblock,
333 			    dbg_line,
334 			    &acg);
335 			DBG_DUMP_CLSUM(&sblock,
336 			    dbg_line,
337 			    &acg);
338 		}
339 #ifdef NOT_CURRENTLY
340 		/*
341 		 * See the comment in sbin/growfs/debug.c for why this
342 		 * is currently disabled, and what needs to be done to
343 		 * re-enable it.
344 		 */
345 		if(disk.d_ufs == 1 && cfg_lv & 0x080) {
346 			DBG_DUMP_SPTBL(&sblock,
347 			    dbg_line,
348 			    &acg);
349 		}
350 #endif
351 	}
352 	/*
353 	 * Dump the requested inode(s).
354 	 */
355 	if(cfg_in != -2) {
356 		DUMP_WHOLE_INODE((ino_t)cfg_in, cfg_lv);
357 	} else {
358 		for(in=cg_start*sblock.fs_ipg; in<(ino_t)cg_stop*sblock.fs_ipg;
359 		    in++) {
360 			DUMP_WHOLE_INODE(in, cfg_lv);
361 		}
362 	}
363 
364 	DBG_CLOSE;
365 
366 	DBG_LEAVE;
367 	return 0;
368 }
369 
370 /* ********************************************** dump_whole_ufs1_inode ***** */
371 /*
372  * Here we dump a list of all blocks allocated by this inode. We follow
373  * all indirect blocks.
374  */
375 void
376 dump_whole_ufs1_inode(ino_t inode, int level)
377 {
378 	DBG_FUNC("dump_whole_ufs1_inode")
379 	struct ufs1_dinode	*ino;
380 	int	rb, mode;
381 	unsigned int	ind2ctr, ind3ctr;
382 	ufs1_daddr_t	*ind2ptr, *ind3ptr;
383 	char	comment[80];
384 
385 	DBG_ENTER;
386 
387 	/*
388 	 * Read the inode from disk/cache.
389 	 */
390 	if (getino(&disk, (void **)&ino, inode, &mode) == -1)
391 		err(1, "getino: %s", disk.d_error);
392 
393 	if(ino->di_nlink==0) {
394 		DBG_LEAVE;
395 		return;	/* inode not in use */
396 	}
397 
398 	/*
399 	 * Dump the main inode structure.
400 	 */
401 	snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
402 	if (level & 0x100) {
403 		DBG_DUMP_INO(&sblock,
404 		    comment,
405 		    ino);
406 	}
407 
408 	if (!(level & 0x200)) {
409 		DBG_LEAVE;
410 		return;
411 	}
412 
413 	/*
414 	 * Ok, now prepare for dumping all direct and indirect pointers.
415 	 */
416 	rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
417 	if(rb>0) {
418 		/*
419 		 * Dump single indirect block.
420 		 */
421 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
422 			(size_t)sblock.fs_bsize) == -1) {
423 			err(1, "bread: %s", disk.d_error);
424 		}
425 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0",
426 		    inode);
427 		DBG_DUMP_IBLK(&sblock,
428 		    comment,
429 		    i1blk,
430 		    (size_t)rb);
431 		rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
432 	}
433 	if(rb>0) {
434 		/*
435 		 * Dump double indirect blocks.
436 		 */
437 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
438 			(size_t)sblock.fs_bsize) == -1) {
439 			err(1, "bread: %s", disk.d_error);
440 		}
441 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1",
442 		    inode);
443 		DBG_DUMP_IBLK(&sblock,
444 		    comment,
445 		    i2blk,
446 		    howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
447 		for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
448 			sizeof(ufs1_daddr_t))) && (rb>0)); ind2ctr++) {
449 			ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)[ind2ctr];
450 
451 			if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
452 				(size_t)sblock.fs_bsize) == -1) {
453 				err(1, "bread: %s", disk.d_error);
454 			}
455 			snprintf(comment, sizeof(comment),
456 			    "Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
457 			DBG_DUMP_IBLK(&sblock,
458 			    comment,
459 			    i1blk,
460 			    (size_t)rb);
461 			rb-=howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t));
462 		}
463 	}
464 	if(rb>0) {
465 		/*
466 		 * Dump triple indirect blocks.
467 		 */
468 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
469 			(size_t)sblock.fs_bsize) == -1) {
470 			err(1, "bread: %s", disk.d_error);
471 		}
472 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2",
473 		    inode);
474 #define SQUARE(a) ((a)*(a))
475 		DBG_DUMP_IBLK(&sblock,
476 		    comment,
477 		    i3blk,
478 		    howmany(rb,
479 		      SQUARE(howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t)))));
480 #undef SQUARE
481 		for(ind3ctr=0; ((ind3ctr<howmany(sblock.fs_bsize,
482 			sizeof(ufs1_daddr_t)))&&(rb>0)); ind3ctr++) {
483 			ind3ptr=&((ufs1_daddr_t *)(void *)&i3blk)[ind3ctr];
484 
485 			if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
486 				(size_t)sblock.fs_bsize) == -1) {
487 				err(1, "bread: %s", disk.d_error);
488 			}
489 			snprintf(comment, sizeof(comment),
490 			    "Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
491 			DBG_DUMP_IBLK(&sblock,
492 			    comment,
493 			    i2blk,
494 			    howmany(rb,
495 			      howmany(sblock.fs_bsize, sizeof(ufs1_daddr_t))));
496 			for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
497 			    sizeof(ufs1_daddr_t)))&&(rb>0)); ind2ctr++) {
498 				ind2ptr=&((ufs1_daddr_t *)(void *)&i2blk)
499 				    [ind2ctr];
500 				if (bread(&disk, fsbtodb(&sblock, *ind2ptr),
501 				    (void *)&i1blk, (size_t)sblock.fs_bsize)
502 				    == -1) {
503 					err(1, "bread: %s", disk.d_error);
504 				}
505 				snprintf(comment, sizeof(comment),
506 				    "Inode 0x%08x: indirect 2->%d->%d", inode,
507 				    ind3ctr, ind3ctr);
508 				DBG_DUMP_IBLK(&sblock,
509 				    comment,
510 				    i1blk,
511 				    (size_t)rb);
512 				rb-=howmany(sblock.fs_bsize,
513 				    sizeof(ufs1_daddr_t));
514 			}
515 		}
516 	}
517 
518 	DBG_LEAVE;
519 	return;
520 }
521 
522 /* ********************************************** dump_whole_ufs2_inode ***** */
523 /*
524  * Here we dump a list of all blocks allocated by this inode. We follow
525  * all indirect blocks.
526  */
527 void
528 dump_whole_ufs2_inode(ino_t inode, int level)
529 {
530 	DBG_FUNC("dump_whole_ufs2_inode")
531 	struct ufs2_dinode	*ino;
532 	int	rb, mode;
533 	unsigned int	ind2ctr, ind3ctr;
534 	ufs2_daddr_t	*ind2ptr, *ind3ptr;
535 	char	comment[80];
536 
537 	DBG_ENTER;
538 
539 	/*
540 	 * Read the inode from disk/cache.
541 	 */
542 	if (getino(&disk, (void **)&ino, inode, &mode) == -1)
543 		err(1, "getino: %s", disk.d_error);
544 
545 	if (ino->di_nlink == 0) {
546 		DBG_LEAVE;
547 		return;	/* inode not in use */
548 	}
549 
550 	/*
551 	 * Dump the main inode structure.
552 	 */
553 	snprintf(comment, sizeof(comment), "Inode 0x%08x", inode);
554 	if (level & 0x100) {
555 		DBG_DUMP_INO(&sblock, comment, ino);
556 	}
557 
558 	if (!(level & 0x200)) {
559 		DBG_LEAVE;
560 		return;
561 	}
562 
563 	/*
564 	 * Ok, now prepare for dumping all direct and indirect pointers.
565 	 */
566 	rb = howmany(ino->di_size, sblock.fs_bsize) - NDADDR;
567 	if (rb > 0) {
568 		/*
569 		 * Dump single indirect block.
570 		 */
571 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[0]), (void *)&i1blk,
572 			(size_t)sblock.fs_bsize) == -1) {
573 			err(1, "bread: %s", disk.d_error);
574 		}
575 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 0", inode);
576 		DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
577 		rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
578 	}
579 	if (rb > 0) {
580 		/*
581 		 * Dump double indirect blocks.
582 		 */
583 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[1]), (void *)&i2blk,
584 			(size_t)sblock.fs_bsize) == -1) {
585 			err(1, "bread: %s", disk.d_error);
586 		}
587 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 1", inode);
588 		DBG_DUMP_IBLK(&sblock,
589 			comment,
590 			i2blk,
591 			howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
592 		for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
593 			sizeof(ufs2_daddr_t))) && (rb>0)); ind2ctr++) {
594 			ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk)[ind2ctr];
595 
596 			if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
597 				(size_t)sblock.fs_bsize) == -1) {
598 				err(1, "bread: %s", disk.d_error);
599 			}
600 			snprintf(comment, sizeof(comment),
601 				"Inode 0x%08x: indirect 1->%d", inode, ind2ctr);
602 			DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
603 			rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
604 		}
605 	}
606 	if (rb > 0) {
607 		/*
608 		 * Dump triple indirect blocks.
609 		 */
610 		if (bread(&disk, fsbtodb(&sblock, ino->di_ib[2]), (void *)&i3blk,
611 			(size_t)sblock.fs_bsize) == -1) {
612 			err(1, "bread: %s", disk.d_error);
613 		}
614 		snprintf(comment, sizeof(comment), "Inode 0x%08x: indirect 2", inode);
615 #define SQUARE(a) ((a)*(a))
616 		DBG_DUMP_IBLK(&sblock,
617 			comment,
618 			i3blk,
619 			howmany(rb,
620 				SQUARE(howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t)))));
621 #undef SQUARE
622 		for (ind3ctr = 0; ((ind3ctr < howmany(sblock.fs_bsize,
623 			sizeof(ufs2_daddr_t))) && (rb > 0)); ind3ctr++) {
624 			ind3ptr = &((ufs2_daddr_t *)(void *)&i3blk)[ind3ctr];
625 
626 			if (bread(&disk, fsbtodb(&sblock, *ind3ptr), (void *)&i2blk,
627 				(size_t)sblock.fs_bsize) == -1) {
628 				err(1, "bread: %s", disk.d_error);
629 			}
630 			snprintf(comment, sizeof(comment),
631 				"Inode 0x%08x: indirect 2->%d", inode, ind3ctr);
632 			DBG_DUMP_IBLK(&sblock,
633 				comment,
634 				i2blk,
635 				howmany(rb,
636 					howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t))));
637 			for (ind2ctr = 0; ((ind2ctr < howmany(sblock.fs_bsize,
638 				sizeof(ufs2_daddr_t))) && (rb > 0)); ind2ctr++) {
639 				ind2ptr = &((ufs2_daddr_t *)(void *)&i2blk) [ind2ctr];
640 				if (bread(&disk, fsbtodb(&sblock, *ind2ptr), (void *)&i1blk,
641 					(size_t)sblock.fs_bsize) == -1) {
642 					err(1, "bread: %s", disk.d_error);
643 				}
644 				snprintf(comment, sizeof(comment),
645 					"Inode 0x%08x: indirect 2->%d->%d", inode,
646 					ind3ctr, ind3ctr);
647 				DBG_DUMP_IBLK(&sblock, comment, i1blk, (size_t)rb);
648 				rb -= howmany(sblock.fs_bsize, sizeof(ufs2_daddr_t));
649 			}
650 		}
651 	}
652 
653 	DBG_LEAVE;
654 	return;
655 }
656 
657 /* ************************************************************* usage ***** */
658 /*
659  * Dump a line of usage.
660  */
661 void
662 usage(void)
663 {
664 	DBG_FUNC("usage")
665 
666 	DBG_ENTER;
667 
668 	fprintf(stderr,
669 	    "usage: ffsinfo [-g cylinder_group] [-i inode] [-l level] "
670 	    "[-o outfile]\n"
671 	    "               special | file\n");
672 
673 	DBG_LEAVE;
674 	exit(1);
675 }
676