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