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