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