xref: /dragonfly/sbin/ffsinfo/ffsinfo.c (revision 89a89091)
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  * $FreeBSD: src/sbin/ffsinfo/ffsinfo.c,v 1.3.2.1 2001/07/16 15:01:56 tomsoft Exp $
40  *
41  * @(#) Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz Copyright (c) 1980, 1989, 1993 The Regents of the University of California. All rights reserved.
42  * $FreeBSD: src/sbin/ffsinfo/ffsinfo.c,v 1.3.2.1 2001/07/16 15:01:56 tomsoft Exp $
43  */
44 
45 /* ********************************************************** INCLUDES ***** */
46 #include <sys/param.h>
47 #include <sys/diskslice.h>
48 #include <sys/stat.h>
49 
50 #include <stdio.h>
51 #include <paths.h>
52 #include <ctype.h>
53 #include <err.h>
54 #include <fcntl.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58 
59 #include "debug.h"
60 
61 /* *********************************************************** GLOBALS ***** */
62 #ifdef FS_DEBUG
63 int	_dbg_lvl_ = (DL_INFO); /* DL_TRC */
64 #endif /* FS_DEBUG */
65 
66 static union {
67 	struct fs	fs;
68 	char	pad[SBSIZE];
69 } fsun1, fsun2;
70 #define	sblock	fsun1.fs
71 #define	osblock	fsun2.fs
72 
73 static union {
74 	struct cg	cg;
75 	char	pad[MAXBSIZE];
76 } cgun1;
77 #define	acg	cgun1.cg
78 
79 static char	ablk[MAXBSIZE];
80 static char	i1blk[MAXBSIZE];
81 static char	i2blk[MAXBSIZE];
82 static char	i3blk[MAXBSIZE];
83 
84 static struct csum	*fscs;
85 
86 /* ******************************************************** PROTOTYPES ***** */
87 static void	rdfs(daddr_t, size_t, void *, int);
88 static void	usage(void);
89 static struct ufs1_dinode	*ginode(ino_t, int);
90 static void	dump_whole_inode(ino_t, int, int);
91 
92 /* ************************************************************** rdfs ***** */
93 /*
94  * Here we read some block(s) from disk.
95  */
96 void
97 rdfs(daddr_t bno, size_t size, void *bf, int fsi)
98 {
99 	DBG_FUNC("rdfs")
100 	ssize_t	n;
101 
102 	DBG_ENTER;
103 
104 	if (lseek(fsi, (off_t)bno * DEV_BSIZE, 0) < 0) {
105 		err(33, "rdfs: seek error: %ld", (long)bno);
106 	}
107 	n = read(fsi, bf, size);
108 	if (n != (ssize_t)size) {
109 		err(34, "rdfs: read error: %ld", (long)bno);
110 	}
111 
112 	DBG_LEAVE;
113 	return;
114 }
115 
116 /* ************************************************************** main ***** */
117 /*
118  * ffsinfo(8) is a tool to dump all metadata of a filesystem. It helps to find
119  * errors is the filesystem much easier. You can run ffsinfo before and  after
120  * an  fsck(8),  and compare the two ascii dumps easy with diff, and  you  see
121  * directly where the problem is. You can control how much detail you want  to
122  * see  with some command line arguments. You can also easy check  the  status
123  * of  a filesystem, like is there is enough space for growing  a  filesystem,
124  * or  how  many active snapshots do we have. It provides much  more  detailed
125  * information  then dumpfs. Snapshots, as they are very new, are  not  really
126  * supported.  They  are just mentioned currently, but it is  planned  to  run
127  * also over active snapshots, to even get that output.
128  */
129 int
130 main(int argc, char **argv)
131 {
132 	DBG_FUNC("main")
133 	char	*device, *special, *cp;
134 	char	ch;
135 	size_t	len;
136 	struct stat	st;
137 	struct partinfo pinfo;
138 	int	fsi;
139 	struct csum	*dbg_csp;
140 	int	dbg_csc;
141 	char	dbg_line[80];
142 	int	cylno,i;
143 	int	cfg_cg, cfg_in, cfg_lv;
144 	int	cg_start, cg_stop;
145 	ino_t	in;
146 	char	*out_file = NULL;
147 	int	Lflag=0;
148 
149 	DBG_ENTER;
150 
151 	cfg_lv=0xff;
152 	cfg_in=-2;
153 	cfg_cg=-2;
154 
155 	while ((ch=getopt(argc, argv, "Lg:i:l:o:")) != -1) {
156 		switch(ch) {
157 		case 'L':
158 			Lflag=1;
159 			break;
160 		case 'g':
161 			cfg_cg=atol(optarg);
162 			if(cfg_cg < -1) {
163 				usage();
164 			}
165 			break;
166 		case 'i':
167 			cfg_in=atol(optarg);
168 			if(cfg_in < 0) {
169 				usage();
170 			}
171 			break;
172 		case 'l':
173 			cfg_lv=atol(optarg);
174 			if(cfg_lv < 0x1||cfg_lv > 0x3ff) {
175 				usage();
176 			}
177 			break;
178 		case 'o':
179 			if (out_file)
180 				free(out_file);
181 			out_file = strdup(optarg);
182 			break;
183 		case '?':
184 			/* FALLTHROUGH */
185 		default:
186 			usage();
187 		}
188 	}
189 	argc -= optind;
190 	argv += optind;
191 
192 	if(argc != 1) {
193 		usage();
194 	}
195 	device=*argv;
196 
197 	/*
198 	 * Now we try to guess the (raw)device name.
199 	 */
200 	if (0 == strrchr(device, '/') && (stat(device, &st) == -1)) {
201 		/*
202 		 * No path prefix was given, so try in that order:
203 		 *     /dev/r%s
204 		 *     /dev/%s
205 		 *     /dev/vinum/r%s
206 		 *     /dev/vinum/%s.
207 		 *
208 		 * FreeBSD now doesn't distinguish between raw and  block
209 		 * devices any longer, but it should still work this way.
210 		 */
211 		len=strlen(device)+strlen(_PATH_DEV)+2+strlen("vinum/");
212 		special=(char *)malloc(len);
213 		if(special == NULL) {
214 			errx(1, "malloc failed");
215 		}
216 		snprintf(special, len, "%sr%s", _PATH_DEV, device);
217 		if (stat(special, &st) == -1) {
218 			snprintf(special, len, "%s%s", _PATH_DEV, device);
219 			if (stat(special, &st) == -1) {
220 				snprintf(special, len, "%svinum/r%s",
221 				    _PATH_DEV, device);
222 				if (stat(special, &st) == -1) {
223 					/*
224 					 * For now this is the 'last resort'.
225 					 */
226 					snprintf(special, len, "%svinum/%s",
227 					    _PATH_DEV, device);
228 				}
229 			}
230 		}
231 		device = special;
232 	}
233 
234 	/*
235 	 * Open our device for reading.
236 	 */
237 	fsi = open(device, O_RDONLY);
238 	if (fsi < 0) {
239 		err(1, "%s", device);
240 	}
241 
242 	stat(device, &st);
243 
244 	if(S_ISREG(st.st_mode)) { /* label check not supported for files */
245 		Lflag=1;
246 	}
247 
248 	if(!Lflag) {
249 		/*
250 		 * Try  to read a label and gess the slice if not  specified.
251 		 * This code should guess the right thing and avaid to bother
252 		 * the user user with the task of specifying the option -v on
253 		 * vinum volumes.
254 		 */
255 		cp = device+strlen(device)-1;
256 		if (ioctl(fsi, DIOCGPART, &pinfo) < 0) {
257 			pinfo.media_size = st.st_size;
258 			pinfo.media_blksize = DEV_BSIZE;
259 			pinfo.media_blocks = pinfo.media_size / DEV_BSIZE;
260 		}
261 
262 		/*
263 		 * Check if that partition looks suited for dumping.
264 		 */
265 		if (pinfo.media_size == 0) {
266 			errx(1, "partition is unavailable");
267 		}
268 	}
269 
270 	/*
271 	 * Read the current superblock.
272 	 */
273 	rdfs((daddr_t)(SBOFF/DEV_BSIZE), (size_t)SBSIZE, &sblock, fsi);
274 	if (sblock.fs_magic != FS_MAGIC) {
275 		errx(1, "superblock not recognized");
276 	}
277 
278 	DBG_OPEN(out_file); /* already here we need a superblock */
279 
280 	if(cfg_lv & 0x001) {
281 		DBG_DUMP_FS(&sblock,
282 		    "primary sblock");
283 	}
284 
285 	/*
286 	 * Determine here what cylinder groups to dump.
287 	 */
288 	if(cfg_cg==-2) {
289 		cg_start=0;
290 		cg_stop=sblock.fs_ncg;
291 	} else if (cfg_cg==-1) {
292 		cg_start=sblock.fs_ncg-1;
293 		cg_stop=sblock.fs_ncg;
294 	} else if (cfg_cg<sblock.fs_ncg) {
295 		cg_start=cfg_cg;
296 		cg_stop=cfg_cg+1;
297 	} else {
298 		cg_start=sblock.fs_ncg;
299 		cg_stop=sblock.fs_ncg;
300 	}
301 
302 	if (cfg_lv & 0x004) {
303 		fscs = (struct csum *)calloc((size_t)1,
304 		    (size_t)sblock.fs_cssize);
305 		if(fscs == NULL) {
306 			errx(1, "calloc failed");
307 		}
308 
309 		/*
310 		 * Get the cylinder summary into the memory ...
311 		 */
312 		for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) {
313 			rdfs(fsbtodb(&sblock, sblock.fs_csaddr +
314 			    numfrags(&sblock, i)), (size_t)(sblock.fs_cssize-i<
315 			    sblock.fs_bsize ? sblock.fs_cssize - i :
316 			    sblock.fs_bsize), (void *)(((char *)fscs)+i), fsi);
317 		}
318 
319 		dbg_csp=fscs;
320 		/*
321 		 * ... and dump it.
322 		 */
323 		for(dbg_csc=0; dbg_csc<sblock.fs_ncg; dbg_csc++) {
324 			snprintf(dbg_line, sizeof(dbg_line),
325 			    "%d. csum in fscs", dbg_csc);
326 			DBG_DUMP_CSUM(&sblock,
327 			    dbg_line,
328 			    dbg_csp++);
329 		}
330 	}
331 
332 	/*
333 	 * For each requested cylinder group ...
334 	 */
335 	for(cylno=cg_start; cylno<cg_stop; cylno++) {
336 		snprintf(dbg_line, sizeof(dbg_line), "cgr %d", cylno);
337 		if(cfg_lv & 0x002) {
338 			/*
339 			 * ... dump the superblock copies ...
340 			 */
341 			rdfs(fsbtodb(&sblock, cgsblock(&sblock, cylno)),
342 			    (size_t)SBSIZE, &osblock, fsi);
343 			DBG_DUMP_FS(&osblock,
344 			    dbg_line);
345 		}
346 		/*
347 		 * ... read the cylinder group and dump whatever was requested.
348 		 */
349 		rdfs(fsbtodb(&sblock, cgtod(&sblock, cylno)),
350 		    (size_t)sblock.fs_cgsize, &acg, fsi);
351 		if(cfg_lv & 0x008) {
352 			DBG_DUMP_CG(&sblock,
353 			    dbg_line,
354 			    &acg);
355 		}
356 		if(cfg_lv & 0x010) {
357 			DBG_DUMP_INMAP(&sblock,
358 			    dbg_line,
359 			    &acg);
360 		}
361 		if(cfg_lv & 0x020) {
362 			DBG_DUMP_FRMAP(&sblock,
363 			    dbg_line,
364 			    &acg);
365 		}
366 		if(cfg_lv & 0x040) {
367 			DBG_DUMP_CLMAP(&sblock,
368 			    dbg_line,
369 			    &acg);
370 			DBG_DUMP_CLSUM(&sblock,
371 			    dbg_line,
372 			    &acg);
373 		}
374 		if(cfg_lv & 0x080) {
375 			DBG_DUMP_SPTBL(&sblock,
376 			    dbg_line,
377 			    &acg);
378 		}
379 	}
380 	/*
381 	 * Dump the requested inode(s).
382 	 */
383 	if(cfg_in != -2) {
384 		dump_whole_inode((ino_t)cfg_in, fsi, cfg_lv);
385 	} else {
386 		for(in=cg_start*sblock.fs_ipg; in<(ino_t)cg_stop*sblock.fs_ipg;
387 		    in++) {
388 			dump_whole_inode(in, fsi, cfg_lv);
389 		}
390 	}
391 
392 	DBG_CLOSE;
393 
394 	close(fsi);
395 
396 	DBG_LEAVE;
397 	return 0;
398 }
399 
400 /* ************************************************** dump_whole_inode ***** */
401 /*
402  * Here we dump a list of all blocks allocated by this inode. We follow
403  * all indirect blocks.
404  */
405 void
406 dump_whole_inode(ino_t inode, int fsi, int level)
407 {
408 	DBG_FUNC("dump_whole_inode")
409 	struct ufs1_dinode	*ino;
410 	int	rb;
411 	unsigned int	ind2ctr, ind3ctr;
412 	ufs_daddr_t	*ind2ptr, *ind3ptr;
413 	char	comment[80];
414 
415 	DBG_ENTER;
416 
417 	/*
418 	 * Read the inode from disk/cache.
419 	 */
420 	ino=ginode(inode, fsi);
421 
422 	if(ino->di_nlink==0) {
423 		DBG_LEAVE;
424 		return;	/* inode not in use */
425 	}
426 
427 	/*
428 	 * Dump the main inode structure.
429 	 */
430 	snprintf(comment, sizeof(comment), "Inode 0x%08jx", (uintmax_t)inode);
431 	if (level & 0x100) {
432 		DBG_DUMP_INO(&sblock,
433 		    comment,
434 		    ino);
435 	}
436 
437 	if (!(level & 0x200)) {
438 		DBG_LEAVE;
439 		return;
440 	}
441 
442 	/*
443 	 * Ok, now prepare for dumping all direct and indirect pointers.
444 	 */
445 	rb=howmany(ino->di_size, sblock.fs_bsize)-NDADDR;
446 	if(rb>0) {
447 		/*
448 		 * Dump single indirect block.
449 		 */
450 		rdfs(fsbtodb(&sblock, ino->di_ib[0]), (size_t)sblock.fs_bsize,
451 		    &i1blk, fsi);
452 		snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 0",
453 		    (uintmax_t)inode);
454 		DBG_DUMP_IBLK(&sblock,
455 		    comment,
456 		    i1blk,
457 		    (size_t)rb);
458 		rb-=howmany(sblock.fs_bsize, sizeof(ufs_daddr_t));
459 	}
460 	if(rb>0) {
461 		/*
462 		 * Dump double indirect blocks.
463 		 */
464 		rdfs(fsbtodb(&sblock, ino->di_ib[1]), (size_t)sblock.fs_bsize,
465 		    &i2blk, fsi);
466 		snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 1",
467 		    (uintmax_t)inode);
468 		DBG_DUMP_IBLK(&sblock,
469 		    comment,
470 		    i2blk,
471 		    howmany(rb, howmany(sblock.fs_bsize, sizeof(ufs_daddr_t))));
472 		for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
473 		    sizeof(ufs_daddr_t)))&&(rb>0)); ind2ctr++) {
474 			ind2ptr=&((ufs_daddr_t *)(void *)&i2blk)[ind2ctr];
475 
476 			rdfs(fsbtodb(&sblock, *ind2ptr),
477 			    (size_t)sblock.fs_bsize, &i1blk, fsi);
478 			snprintf(comment, sizeof(comment),
479 			    "Inode 0x%08jx: indirect 1->%d", (uintmax_t)inode,
480 			    ind2ctr);
481 			DBG_DUMP_IBLK(&sblock,
482 			    comment,
483 			    i1blk,
484 			    (size_t)rb);
485 			rb-=howmany(sblock.fs_bsize, sizeof(ufs_daddr_t));
486 		}
487 	}
488 	if(rb>0) {
489 		/*
490 		 * Dump triple indirect blocks.
491 		 */
492 		rdfs(fsbtodb(&sblock, ino->di_ib[2]), (size_t)sblock.fs_bsize,
493 		    &i3blk, fsi);
494 		snprintf(comment, sizeof(comment), "Inode 0x%08jx: indirect 2",
495 		    (uintmax_t)inode);
496 #define SQUARE(a) ((a)*(a))
497 		DBG_DUMP_IBLK(&sblock,
498 		    comment,
499 		    i3blk,
500 		    howmany(rb,
501 		      SQUARE(howmany(sblock.fs_bsize, sizeof(ufs_daddr_t)))));
502 #undef SQUARE
503 		for(ind3ctr=0; ((ind3ctr < howmany(sblock.fs_bsize,
504 		    sizeof(ufs_daddr_t)))&&(rb>0)); ind3ctr ++) {
505 			ind3ptr=&((ufs_daddr_t *)(void *)&i3blk)[ind3ctr];
506 
507 			rdfs(fsbtodb(&sblock, *ind3ptr),
508 			    (size_t)sblock.fs_bsize, &i2blk, fsi);
509 			snprintf(comment, sizeof(comment),
510 			    "Inode 0x%08jx: indirect 2->%d", (uintmax_t)inode,
511 			    ind3ctr);
512 			DBG_DUMP_IBLK(&sblock,
513 			    comment,
514 			    i2blk,
515 			    howmany(rb,
516 			      howmany(sblock.fs_bsize, sizeof(ufs_daddr_t))));
517 			for(ind2ctr=0; ((ind2ctr < howmany(sblock.fs_bsize,
518 			    sizeof(ufs_daddr_t)))&&(rb>0)); ind2ctr ++) {
519 				ind2ptr=&((ufs_daddr_t *)(void *)&i2blk)
520 				    [ind2ctr];
521 				rdfs(fsbtodb(&sblock, *ind2ptr),
522 				    (size_t)sblock.fs_bsize, &i1blk, fsi);
523 				snprintf(comment, sizeof(comment),
524 				    "Inode 0x%08jx: indirect 2->%d->%d",
525 				    (uintmax_t)inode, ind3ctr, ind3ctr);
526 				DBG_DUMP_IBLK(&sblock,
527 				    comment,
528 				    i1blk,
529 				    (size_t)rb);
530 				rb-=howmany(sblock.fs_bsize,
531 				    sizeof(ufs_daddr_t));
532 			}
533 		}
534 	}
535 
536 	DBG_LEAVE;
537 	return;
538 }
539 
540 /* ************************************************************* usage ***** */
541 /*
542  * Dump a line of usage.
543  */
544 void
545 usage(void)
546 {
547 	DBG_FUNC("usage")
548 
549 	DBG_ENTER;
550 
551 	fprintf(stderr,
552 	    "usage: ffsinfo [-L] [-g cylgrp] [-i inode] [-l level] "
553 	    "[-o outfile]\n"
554 	    "               special | file\n");
555 
556 	DBG_LEAVE;
557 	exit(1);
558 }
559 
560 /* ************************************************************ ginode ***** */
561 /*
562  * This function provides access to an individual inode. We find out in which
563  * block  the  requested inode is located, read it from disk if  needed,  and
564  * return  the pointer into that block. We maintain a cache of one  block  to
565  * not  read the same block again and again if we iterate linearly  over  all
566  * inodes.
567  */
568 struct ufs1_dinode *
569 ginode(ino_t inumber, int fsi)
570 {
571 	DBG_FUNC("ginode")
572 	ufs_daddr_t	iblk;
573 	static ino_t	startinum=0;	/* first inode in cached block */
574 	struct ufs1_dinode	*pi;
575 
576 	DBG_ENTER;
577 
578 	pi=(struct ufs1_dinode *)(void *)ablk;
579 	if (startinum == 0 || inumber < startinum ||
580 	    inumber >= startinum + INOPB(&sblock)) {
581 		/*
582 		 * The block needed is not cached, so we have to read it from
583 		 * disk now.
584 		 */
585 		iblk = ino_to_fsba(&sblock, inumber);
586 		rdfs(fsbtodb(&sblock, iblk), (size_t)sblock.fs_bsize,
587 		    &ablk, fsi);
588 		startinum = (inumber / INOPB(&sblock)) * INOPB(&sblock);
589 	}
590 
591 	DBG_LEAVE;
592 	return (&(pi[inumber % INOPB(&sblock)]));
593 }
594 
595