Home | History | Annotate | Download | only in misc
      1 /*
      2  * filefrag.c -- report if a particular file is fragmented
      3  *
      4  * Copyright 2003 by Theodore Ts'o.
      5  *
      6  * %Begin-Header%
      7  * This file may be redistributed under the terms of the GNU Public
      8  * License.
      9  * %End-Header%
     10  */
     11 
     12 #include "config.h"
     13 #ifndef __linux__
     14 #include <stdio.h>
     15 #include <stdlib.h>
     16 #include <unistd.h>
     17 
     18 int main(void) {
     19 	fputs("This program is only supported on Linux!\n", stderr);
     20 	exit(EXIT_FAILURE);
     21 }
     22 #else
     23 #ifndef _LARGEFILE_SOURCE
     24 #define _LARGEFILE_SOURCE
     25 #endif
     26 #ifndef _LARGEFILE64_SOURCE
     27 #define _LARGEFILE64_SOURCE
     28 #endif
     29 
     30 
     31 #include <stdio.h>
     32 #include <stdlib.h>
     33 #include <unistd.h>
     34 #include <string.h>
     35 #include <time.h>
     36 #include <fcntl.h>
     37 #include <errno.h>
     38 #ifdef HAVE_GETOPT_H
     39 #include <getopt.h>
     40 #else
     41 extern char *optarg;
     42 extern int optind;
     43 #endif
     44 #include <sys/types.h>
     45 #include <sys/stat.h>
     46 #include <sys/vfs.h>
     47 #include <sys/ioctl.h>
     48 #ifdef HAVE_LINUX_FD_H
     49 #include <linux/fd.h>
     50 #endif
     51 #include <ext2fs/ext2fs.h>
     52 #include <ext2fs/ext2_types.h>
     53 #include <ext2fs/fiemap.h>
     54 
     55 int verbose = 0;
     56 int blocksize;		/* Use specified blocksize (default 1kB) */
     57 int sync_file = 0;	/* fsync file before getting the mapping */
     58 int xattr_map = 0;	/* get xattr mapping */
     59 int force_bmap;	/* force use of FIBMAP instead of FIEMAP */
     60 int force_extent;	/* print output in extent format always */
     61 int logical_width = 8;
     62 int physical_width = 10;
     63 const char *ext_fmt = "%4d: %*llu..%*llu: %*llu..%*llu: %6llu: %s\n";
     64 const char *hex_fmt = "%4d: %*llx..%*llx: %*llx..%*llx: %6llx: %s\n";
     65 
     66 #define FILEFRAG_FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR)
     67 
     68 #define FIBMAP		_IO(0x00, 1)	/* bmap access */
     69 #define FIGETBSZ	_IO(0x00, 2)	/* get the block size used for bmap */
     70 
     71 #define LUSTRE_SUPER_MAGIC 0x0BD00BD0
     72 
     73 #define	EXT4_EXTENTS_FL			0x00080000 /* Inode uses extents */
     74 #define	EXT3_IOC_GETFLAGS		_IOR('f', 1, long)
     75 
     76 static int int_log2(int arg)
     77 {
     78 	int     l = 0;
     79 
     80 	arg >>= 1;
     81 	while (arg) {
     82 		l++;
     83 		arg >>= 1;
     84 	}
     85 	return l;
     86 }
     87 
     88 static int int_log10(unsigned long long arg)
     89 {
     90 	int     l = 0;
     91 
     92 	arg = arg / 10;
     93 	while (arg) {
     94 		l++;
     95 		arg = arg / 10;
     96 	}
     97 	return l;
     98 }
     99 
    100 static unsigned int div_ceil(unsigned int a, unsigned int b)
    101 {
    102 	if (!a)
    103 		return 0;
    104 	return ((a - 1) / b) + 1;
    105 }
    106 
    107 static int get_bmap(int fd, unsigned long block, unsigned long *phy_blk)
    108 {
    109 	int	ret;
    110 	unsigned int b;
    111 
    112 	b = block;
    113 	ret = ioctl(fd, FIBMAP, &b); /* FIBMAP takes pointer to integer */
    114 	if (ret < 0)
    115 		return -errno;
    116 	*phy_blk = b;
    117 
    118 	return ret;
    119 }
    120 
    121 static void print_extent_header(void)
    122 {
    123 	printf(" ext: %*s %*s length: %*s flags:\n",
    124 	       logical_width * 2 + 3,
    125 	       "logical_offset:",
    126 	       physical_width * 2 + 3, "physical_offset:",
    127 	       physical_width + 1,
    128 	       "expected:");
    129 }
    130 
    131 static void print_flag(__u32 *flags, __u32 mask, char *buf, const char *name)
    132 {
    133 	if ((*flags & mask) == 0)
    134 		return;
    135 
    136 	strcat(buf, name);
    137 	*flags &= ~mask;
    138 }
    139 
    140 static void print_extent_info(struct fiemap_extent *fm_extent, int cur_ex,
    141 			      unsigned long long expected, int blk_shift,
    142 			      ext2fs_struct_stat *st)
    143 {
    144 	unsigned long long physical_blk;
    145 	unsigned long long logical_blk;
    146 	unsigned long long ext_len;
    147 	unsigned long long ext_blks;
    148 	__u32 fe_flags, mask;
    149 	char flags[256] = "";
    150 
    151 	/* For inline data all offsets should be in bytes, not blocks */
    152 	if (fm_extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE)
    153 		blk_shift = 0;
    154 
    155 	ext_len = fm_extent->fe_length >> blk_shift;
    156 	ext_blks = (fm_extent->fe_length - 1) >> blk_shift;
    157 	logical_blk = fm_extent->fe_logical >> blk_shift;
    158 	if (fm_extent->fe_flags & FIEMAP_EXTENT_UNKNOWN) {
    159 		physical_blk = 0;
    160 	} else {
    161 		physical_blk = fm_extent->fe_physical >> blk_shift;
    162 	}
    163 
    164 	if (expected)
    165 		sprintf(flags, ext_fmt == hex_fmt ? "%*llx: " : "%*llu: ",
    166 			physical_width, expected >> blk_shift);
    167 	else
    168 		sprintf(flags, "%.*s  ", physical_width, "                   ");
    169 
    170 	fe_flags = fm_extent->fe_flags;
    171 	print_flag(&fe_flags, FIEMAP_EXTENT_LAST, flags, "last,");
    172 	print_flag(&fe_flags, FIEMAP_EXTENT_UNKNOWN, flags, "unknown_loc,");
    173 	print_flag(&fe_flags, FIEMAP_EXTENT_DELALLOC, flags, "delalloc,");
    174 	print_flag(&fe_flags, FIEMAP_EXTENT_ENCODED, flags, "encoded,");
    175 	print_flag(&fe_flags, FIEMAP_EXTENT_DATA_ENCRYPTED, flags,"encrypted,");
    176 	print_flag(&fe_flags, FIEMAP_EXTENT_NOT_ALIGNED, flags, "not_aligned,");
    177 	print_flag(&fe_flags, FIEMAP_EXTENT_DATA_INLINE, flags, "inline,");
    178 	print_flag(&fe_flags, FIEMAP_EXTENT_DATA_TAIL, flags, "tail_packed,");
    179 	print_flag(&fe_flags, FIEMAP_EXTENT_UNWRITTEN, flags, "unwritten,");
    180 	print_flag(&fe_flags, FIEMAP_EXTENT_MERGED, flags, "merged,");
    181 	print_flag(&fe_flags, FIEMAP_EXTENT_SHARED, flags, "shared,");
    182 	/* print any unknown flags as hex values */
    183 	for (mask = 1; fe_flags != 0 && mask != 0; mask <<= 1) {
    184 		char hex[sizeof(mask) * 2 + 4]; /* 2 chars/byte + 0x, + NUL */
    185 
    186 		if ((fe_flags & mask) == 0)
    187 			continue;
    188 		sprintf(hex, "%#04x,", mask);
    189 		print_flag(&fe_flags, mask, flags, hex);
    190 	}
    191 
    192 	if (fm_extent->fe_logical + fm_extent->fe_length >=
    193 	    (unsigned long long) st->st_size)
    194 		strcat(flags, "eof,");
    195 
    196 	/* Remove trailing comma, if any */
    197 	if (flags[0] != '\0')
    198 		flags[strnlen(flags, sizeof(flags)) - 1] = '\0';
    199 
    200 	printf(ext_fmt, cur_ex, logical_width, logical_blk,
    201 	       logical_width, logical_blk + ext_blks,
    202 	       physical_width, physical_blk,
    203 	       physical_width, physical_blk + ext_blks,
    204 	       ext_len, flags);
    205 }
    206 
    207 static int filefrag_fiemap(int fd, int blk_shift, int *num_extents,
    208 			   ext2fs_struct_stat *st)
    209 {
    210 	__u64 buf[2048];	/* __u64 for proper field alignment */
    211 	struct fiemap *fiemap = (struct fiemap *)buf;
    212 	struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
    213 	struct fiemap_extent fm_last;
    214 	int count = (sizeof(buf) - sizeof(*fiemap)) /
    215 			sizeof(struct fiemap_extent);
    216 	unsigned long long expected = 0;
    217 	unsigned long long expected_dense = 0;
    218 	unsigned long flags = 0;
    219 	unsigned int i;
    220 	int fiemap_header_printed = 0;
    221 	int tot_extents = 0, n = 0;
    222 	int last = 0;
    223 	int rc;
    224 
    225 	memset(fiemap, 0, sizeof(struct fiemap));
    226 	memset(&fm_last, 0, sizeof(fm_last));
    227 
    228 	if (sync_file)
    229 		flags |= FIEMAP_FLAG_SYNC;
    230 
    231 	if (xattr_map)
    232 		flags |= FIEMAP_FLAG_XATTR;
    233 
    234 	do {
    235 		fiemap->fm_length = ~0ULL;
    236 		fiemap->fm_flags = flags;
    237 		fiemap->fm_extent_count = count;
    238 		rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
    239 		if (rc < 0) {
    240 			static int fiemap_incompat_printed;
    241 
    242 			rc = -errno;
    243 			if (rc == -EBADR && !fiemap_incompat_printed) {
    244 				fprintf(stderr, "FIEMAP failed with unknown "
    245 						"flags %x\n",
    246 				       fiemap->fm_flags);
    247 				fiemap_incompat_printed = 1;
    248 			}
    249 			return rc;
    250 		}
    251 
    252 		/* If 0 extents are returned, then more ioctls are not needed */
    253 		if (fiemap->fm_mapped_extents == 0)
    254 			break;
    255 
    256 		if (verbose && !fiemap_header_printed) {
    257 			print_extent_header();
    258 			fiemap_header_printed = 1;
    259 		}
    260 
    261 		for (i = 0; i < fiemap->fm_mapped_extents; i++) {
    262 			expected_dense = fm_last.fe_physical +
    263 					 fm_last.fe_length;
    264 			expected = fm_last.fe_physical +
    265 				   fm_ext[i].fe_logical - fm_last.fe_logical;
    266 			if (fm_ext[i].fe_logical != 0 &&
    267 			    fm_ext[i].fe_physical != expected &&
    268 			    fm_ext[i].fe_physical != expected_dense) {
    269 				tot_extents++;
    270 			} else {
    271 				expected = 0;
    272 				if (!tot_extents)
    273 					tot_extents = 1;
    274 			}
    275 			if (verbose)
    276 				print_extent_info(&fm_ext[i], n, expected,
    277 						  blk_shift, st);
    278 			if (fm_ext[i].fe_flags & FIEMAP_EXTENT_LAST)
    279 				last = 1;
    280 			fm_last = fm_ext[i];
    281 			n++;
    282 		}
    283 
    284 		fiemap->fm_start = (fm_ext[i - 1].fe_logical +
    285 				    fm_ext[i - 1].fe_length);
    286 	} while (last == 0);
    287 
    288 	*num_extents = tot_extents;
    289 
    290 	return 0;
    291 }
    292 
    293 #define EXT2_DIRECT	12
    294 
    295 static int filefrag_fibmap(int fd, int blk_shift, int *num_extents,
    296 			   ext2fs_struct_stat *st,
    297 			   unsigned long numblocks, int is_ext2)
    298 {
    299 	struct fiemap_extent	fm_ext, fm_last;
    300 	unsigned long		i, last_block;
    301 	unsigned long long	logical, expected = 0;
    302 				/* Blocks per indirect block */
    303 	const long		bpib = st->st_blksize / 4;
    304 	int			count;
    305 
    306 	memset(&fm_ext, 0, sizeof(fm_ext));
    307 	memset(&fm_last, 0, sizeof(fm_last));
    308 	if (force_extent) {
    309 		fm_ext.fe_flags = FIEMAP_EXTENT_MERGED;
    310 	}
    311 
    312 	if (sync_file && fsync(fd) != 0)
    313 		return -errno;
    314 
    315 	for (i = 0, logical = 0, *num_extents = 0, count = last_block = 0;
    316 	     i < numblocks;
    317 	     i++, logical += st->st_blksize) {
    318 		unsigned long block = 0;
    319 		int rc;
    320 
    321 		if (is_ext2 && last_block) {
    322 			if (((i - EXT2_DIRECT) % bpib) == 0)
    323 				last_block++;
    324 			if (((i - EXT2_DIRECT - bpib) % (bpib * bpib)) == 0)
    325 				last_block++;
    326 			if (((i - EXT2_DIRECT - bpib - bpib * bpib) %
    327 			     (((unsigned long long)bpib) * bpib * bpib)) == 0)
    328 				last_block++;
    329 		}
    330 		rc = get_bmap(fd, i, &block);
    331 		if (rc < 0)
    332 			return rc;
    333 		if (block == 0)
    334 			continue;
    335 
    336 		if (*num_extents == 0 || block != last_block + 1 ||
    337 		    fm_ext.fe_logical + fm_ext.fe_length != logical) {
    338 			/*
    339 			 * This is the start of a new extent; figure out where
    340 			 * we expected it to be and report the extent.
    341 			 */
    342 			if (*num_extents != 0 && fm_last.fe_length) {
    343 				expected = fm_last.fe_physical +
    344 					(fm_ext.fe_logical - fm_last.fe_logical);
    345 				if (expected == fm_ext.fe_physical)
    346 					expected = 0;
    347 			}
    348 			if (force_extent && *num_extents == 0)
    349 				print_extent_header();
    350 			if (force_extent && *num_extents != 0) {
    351 				print_extent_info(&fm_ext, *num_extents - 1,
    352 						  expected, blk_shift, st);
    353 			}
    354 			if (verbose && expected != 0) {
    355 				printf("Discontinuity: Block %llu is at %llu "
    356 				       "(was %llu)\n",
    357 					fm_ext.fe_logical / st->st_blksize,
    358 					fm_ext.fe_physical / st->st_blksize,
    359 					expected / st->st_blksize);
    360 			}
    361 			/* create the new extent */
    362 			fm_last = fm_ext;
    363 			(*num_extents)++;
    364 			fm_ext.fe_physical = block * st->st_blksize;
    365 			fm_ext.fe_logical = logical;
    366 			fm_ext.fe_length = 0;
    367 		}
    368 		fm_ext.fe_length += st->st_blksize;
    369 		last_block = block;
    370 	}
    371 	if (force_extent && *num_extents != 0) {
    372 		if (fm_last.fe_length) {
    373 			expected = fm_last.fe_physical +
    374 				   (fm_ext.fe_logical - fm_last.fe_logical);
    375 			if (expected == fm_ext.fe_physical)
    376 				expected = 0;
    377 		}
    378 		print_extent_info(&fm_ext, *num_extents - 1, expected,
    379 				  blk_shift, st);
    380 	}
    381 
    382 	return count;
    383 }
    384 
    385 static int frag_report(const char *filename)
    386 {
    387 	static struct statfs fsinfo;
    388 	static unsigned int blksize;
    389 	ext2fs_struct_stat st;
    390 	int		blk_shift;
    391 	long		fd;
    392 	unsigned long long	numblocks;
    393 	int		data_blocks_per_cyl = 1;
    394 	int		num_extents = 1, expected = ~0;
    395 	int		is_ext2 = 0;
    396 	static dev_t	last_device;
    397 	int		width;
    398 	int		rc = 0;
    399 
    400 #if defined(HAVE_OPEN64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
    401 	fd = open64(filename, O_RDONLY);
    402 #else
    403 	fd = open(filename, O_RDONLY);
    404 #endif
    405 	if (fd < 0) {
    406 		rc = -errno;
    407 		perror("open");
    408 		return rc;
    409 	}
    410 
    411 #if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
    412 	if (fstat64(fd, &st) < 0) {
    413 #else
    414 	if (fstat(fd, &st) < 0) {
    415 #endif
    416 		rc = -errno;
    417 		perror("stat");
    418 		goto out_close;
    419 	}
    420 
    421 	if (last_device != st.st_dev) {
    422 		if (fstatfs(fd, &fsinfo) < 0) {
    423 			rc = -errno;
    424 			perror("fstatfs");
    425 			goto out_close;
    426 		}
    427 		if (ioctl(fd, FIGETBSZ, &blksize) < 0)
    428 			blksize = fsinfo.f_bsize;
    429 		if (verbose)
    430 			printf("Filesystem type is: %lx\n",
    431 			       (unsigned long)fsinfo.f_type);
    432 	}
    433 	st.st_blksize = blksize;
    434 	if (fsinfo.f_type == 0xef51 || fsinfo.f_type == 0xef52 ||
    435 	    fsinfo.f_type == 0xef53) {
    436 		unsigned int	flags;
    437 
    438 		if (ioctl(fd, EXT3_IOC_GETFLAGS, &flags) == 0 &&
    439 		    !(flags & EXT4_EXTENTS_FL))
    440 			is_ext2 = 1;
    441 	}
    442 
    443 	if (is_ext2) {
    444 		long cylgroups = div_ceil(fsinfo.f_blocks, blksize * 8);
    445 
    446 		if (verbose && last_device != st.st_dev)
    447 			printf("Filesystem cylinder groups approximately %ld\n",
    448 			       cylgroups);
    449 
    450 		data_blocks_per_cyl = blksize * 8 -
    451 					(fsinfo.f_files / 8 / cylgroups) - 3;
    452 	}
    453 	last_device = st.st_dev;
    454 
    455 	width = int_log10(fsinfo.f_blocks);
    456 	if (width > physical_width)
    457 		physical_width = width;
    458 
    459 	numblocks = (st.st_size + blksize - 1) / blksize;
    460 	if (blocksize != 0)
    461 		blk_shift = int_log2(blocksize);
    462 	else
    463 		blk_shift = int_log2(blksize);
    464 
    465 	width = int_log10(numblocks);
    466 	if (width > logical_width)
    467 		logical_width = width;
    468 	if (verbose)
    469 		printf("File size of %s is %llu (%llu block%s of %d bytes)\n",
    470 		       filename, (unsigned long long)st.st_size,
    471 		       numblocks * blksize >> blk_shift,
    472 		       numblocks == 1 ? "" : "s", 1 << blk_shift);
    473 
    474 	if (!force_bmap) {
    475 		rc = filefrag_fiemap(fd, blk_shift, &num_extents, &st);
    476 		expected = 0;
    477 	}
    478 
    479 	if (force_bmap || rc < 0) { /* FIEMAP failed, try FIBMAP instead */
    480 		expected = filefrag_fibmap(fd, blk_shift, &num_extents,
    481 					   &st, numblocks, is_ext2);
    482 		if (expected < 0) {
    483 			if (expected == -EINVAL || expected == -ENOTTY) {
    484 				fprintf(stderr, "%s: FIBMAP unsupported\n",
    485 					filename);
    486 			} else if (expected == -EPERM) {
    487 				fprintf(stderr,
    488 					"%s: FIBMAP requires root privileges\n",
    489 					filename);
    490 			} else {
    491 				fprintf(stderr, "%s: FIBMAP error: %s",
    492 					filename, strerror(expected));
    493 			}
    494 			rc = expected;
    495 			goto out_close;
    496 		} else {
    497 			rc = 0;
    498 		}
    499 		expected = expected / data_blocks_per_cyl + 1;
    500 	}
    501 
    502 	if (num_extents == 1)
    503 		printf("%s: 1 extent found", filename);
    504 	else
    505 		printf("%s: %d extents found", filename, num_extents);
    506 	/* count, and thus expected, only set for indirect FIBMAP'd files */
    507 	if (is_ext2 && expected && expected < num_extents)
    508 		printf(", perfection would be %d extent%s\n", expected,
    509 			(expected > 1) ? "s" : "");
    510 	else
    511 		fputc('\n', stdout);
    512 out_close:
    513 	close(fd);
    514 
    515 	return rc;
    516 }
    517 
    518 static void usage(const char *progname)
    519 {
    520 	fprintf(stderr, "Usage: %s [-b{blocksize}] [-BeksvxX] file ...\n",
    521 		progname);
    522 	exit(1);
    523 }
    524 
    525 int main(int argc, char**argv)
    526 {
    527 	char **cpp;
    528 	int rc = 0, c;
    529 
    530 	while ((c = getopt(argc, argv, "Bb::eksvxX")) != EOF) {
    531 		switch (c) {
    532 		case 'B':
    533 			force_bmap++;
    534 			break;
    535 		case 'b':
    536 			if (optarg) {
    537 				char *end;
    538 				blocksize = strtoul(optarg, &end, 0);
    539 				if (end) {
    540 #if __GNUC_PREREQ (7, 0)
    541 #pragma GCC diagnostic push
    542 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
    543 #endif
    544 					switch (end[0]) {
    545 					case 'g':
    546 					case 'G':
    547 						blocksize *= 1024;
    548 						/* fall through */
    549 					case 'm':
    550 					case 'M':
    551 						blocksize *= 1024;
    552 						/* fall through */
    553 					case 'k':
    554 					case 'K':
    555 						blocksize *= 1024;
    556 						break;
    557 					default:
    558 						break;
    559 					}
    560 #if __GNUC_PREREQ (7, 0)
    561 #pragma GCC diagnostic pop
    562 #endif
    563 				}
    564 			} else { /* Allow -b without argument for compat. Remove
    565 				  * this eventually so "-b {blocksize}" works */
    566 				fprintf(stderr, "%s: -b needs a blocksize "
    567 					"option, assuming 1024-byte blocks.\n",
    568 					argv[0]);
    569 				blocksize = 1024;
    570 			}
    571 			break;
    572 		case 'e':
    573 			force_extent++;
    574 			if (!verbose)
    575 				verbose++;
    576 			break;
    577 		case 'k':
    578 			blocksize = 1024;
    579 			break;
    580 		case 's':
    581 			sync_file++;
    582 			break;
    583 		case 'v':
    584 			verbose++;
    585 			break;
    586 		case 'x':
    587 			xattr_map++;
    588 			break;
    589 		case 'X':
    590 			ext_fmt = hex_fmt;
    591 			break;
    592 		default:
    593 			usage(argv[0]);
    594 			break;
    595 		}
    596 	}
    597 
    598 	if (optind == argc)
    599 		usage(argv[0]);
    600 
    601 	for (cpp = argv + optind; *cpp != NULL; cpp++) {
    602 		int rc2 = frag_report(*cpp);
    603 
    604 		if (rc2 < 0 && rc == 0)
    605 			rc = rc2;
    606 	}
    607 
    608 	return -rc;
    609 }
    610 #endif
    611