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