2 * filefrag.c -- report if a particular file is fragmented
4 * Copyright 2003 by Theodore Ts'o.
7 * This file may be redistributed under the terms of the GNU Public
19 fputs("This program is only supported on Linux!\n", stderr
);
23 #ifndef _LARGEFILE_SOURCE
24 #define _LARGEFILE_SOURCE
26 #ifndef _LARGEFILE64_SOURCE
27 #define _LARGEFILE64_SOURCE
44 #include <sys/types.h>
47 #include <sys/ioctl.h>
48 #ifdef HAVE_LINUX_FD_H
51 #include <ext2fs/ext2fs.h>
52 #include <ext2fs/ext2_types.h>
53 #include <ext2fs/fiemap.h>
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";
66 #define FILEFRAG_FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR)
68 #define FIBMAP _IO(0x00, 1) /* bmap access */
69 #define FIGETBSZ _IO(0x00, 2) /* get the block size used for bmap */
71 #define LUSTRE_SUPER_MAGIC 0x0BD00BD0
73 #define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */
74 #define EXT3_IOC_GETFLAGS _IOR('f', 1, long)
76 static int int_log2(int arg
)
88 static int int_log10(unsigned long long arg
)
100 static unsigned int div_ceil(unsigned int a
, unsigned int b
)
104 return ((a
- 1) / b
) + 1;
107 static int get_bmap(int fd
, unsigned long block
, unsigned long *phy_blk
)
113 ret
= ioctl(fd
, FIBMAP
, &b
); /* FIBMAP takes pointer to integer */
121 static void print_extent_header(void)
123 printf(" ext: %*s %*s length: %*s flags:\n",
124 logical_width
* 2 + 3,
126 physical_width
* 2 + 3, "physical_offset:",
131 static void print_flag(__u32
*flags
, __u32 mask
, char *buf
, const char *name
)
133 if ((*flags
& mask
) == 0)
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
)
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] = "";
151 /* For inline data all offsets should be in bytes, not blocks */
152 if (fm_extent
->fe_flags
& FIEMAP_EXTENT_DATA_INLINE
)
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
) {
161 physical_blk
= fm_extent
->fe_physical
>> blk_shift
;
165 sprintf(flags
, ext_fmt
== hex_fmt
? "%*llx: " : "%*llu: ",
166 physical_width
, expected
>> blk_shift
);
168 sprintf(flags
, "%.*s ", physical_width
, " ");
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 */
186 if ((fe_flags
& mask
) == 0)
188 sprintf(hex
, "%#04x,", mask
);
189 print_flag(&fe_flags
, mask
, flags
, hex
);
192 if (fm_extent
->fe_logical
+ fm_extent
->fe_length
>=
193 (unsigned long long) st
->st_size
)
194 strcat(flags
, "eof,");
196 /* Remove trailing comma, if any */
197 if (flags
[0] != '\0')
198 flags
[strnlen(flags
, sizeof(flags
)) - 1] = '\0';
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
,
207 static int filefrag_fiemap(int fd
, int blk_shift
, int *num_extents
,
208 ext2fs_struct_stat
*st
)
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;
220 int fiemap_header_printed
= 0;
221 int tot_extents
= 0, n
= 0;
225 memset(fiemap
, 0, sizeof(struct fiemap
));
226 memset(&fm_last
, 0, sizeof(fm_last
));
229 flags
|= FIEMAP_FLAG_SYNC
;
232 flags
|= FIEMAP_FLAG_XATTR
;
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
);
240 static int fiemap_incompat_printed
;
243 if (rc
== -EBADR
&& !fiemap_incompat_printed
) {
244 fprintf(stderr
, "FIEMAP failed with unknown "
247 fiemap_incompat_printed
= 1;
252 /* If 0 extents are returned, then more ioctls are not needed */
253 if (fiemap
->fm_mapped_extents
== 0)
256 if (verbose
&& !fiemap_header_printed
) {
257 print_extent_header();
258 fiemap_header_printed
= 1;
261 for (i
= 0; i
< fiemap
->fm_mapped_extents
; i
++) {
262 expected_dense
= fm_last
.fe_physical
+
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
) {
276 print_extent_info(&fm_ext
[i
], n
, expected
,
278 if (fm_ext
[i
].fe_flags
& FIEMAP_EXTENT_LAST
)
284 fiemap
->fm_start
= (fm_ext
[i
- 1].fe_logical
+
285 fm_ext
[i
- 1].fe_length
);
288 *num_extents
= tot_extents
;
293 #define EXT2_DIRECT 12
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
)
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;
306 memset(&fm_ext
, 0, sizeof(fm_ext
));
307 memset(&fm_last
, 0, sizeof(fm_last
));
309 fm_ext
.fe_flags
= FIEMAP_EXTENT_MERGED
;
312 if (sync_file
&& fsync(fd
) != 0)
315 for (i
= 0, logical
= 0, *num_extents
= 0, count
= last_block
= 0;
317 i
++, logical
+= st
->st_blksize
) {
318 unsigned long block
= 0;
321 if (is_ext2
&& last_block
) {
322 if (((i
- EXT2_DIRECT
) % bpib
) == 0)
324 if (((i
- EXT2_DIRECT
- bpib
) % (bpib
* bpib
)) == 0)
326 if (((i
- EXT2_DIRECT
- bpib
- bpib
* bpib
) %
327 (((unsigned long long)bpib
) * bpib
* bpib
)) == 0)
330 rc
= get_bmap(fd
, i
, &block
);
336 if (*num_extents
== 0 || block
!= last_block
+ 1 ||
337 fm_ext
.fe_logical
+ fm_ext
.fe_length
!= logical
) {
339 * This is the start of a new extent; figure out where
340 * we expected it to be and report the extent.
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
)
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
);
354 if (verbose
&& expected
!= 0) {
355 printf("Discontinuity: Block %llu is at %llu "
357 fm_ext
.fe_logical
/ st
->st_blksize
,
358 fm_ext
.fe_physical
/ st
->st_blksize
,
359 expected
/ st
->st_blksize
);
361 /* create the new extent */
364 fm_ext
.fe_physical
= block
* st
->st_blksize
;
365 fm_ext
.fe_logical
= logical
;
366 fm_ext
.fe_length
= 0;
368 fm_ext
.fe_length
+= st
->st_blksize
;
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
)
378 print_extent_info(&fm_ext
, *num_extents
- 1, expected
,
385 static int frag_report(const char *filename
)
387 static struct statfs fsinfo
;
388 static unsigned int blksize
;
389 ext2fs_struct_stat st
;
392 unsigned long long numblocks
;
393 int data_blocks_per_cyl
= 1;
394 int num_extents
= 1, expected
= ~0;
396 static dev_t last_device
;
400 #if defined(HAVE_OPEN64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
401 fd
= open64(filename
, O_RDONLY
);
403 fd
= open(filename
, O_RDONLY
);
411 #if defined(HAVE_FSTAT64) && !defined(__OSX_AVAILABLE_BUT_DEPRECATED)
412 if (fstat64(fd
, &st
) < 0) {
414 if (fstat(fd
, &st
) < 0) {
421 if (last_device
!= st
.st_dev
) {
422 if (fstatfs(fd
, &fsinfo
) < 0) {
427 if (ioctl(fd
, FIGETBSZ
, &blksize
) < 0)
428 blksize
= fsinfo
.f_bsize
;
430 printf("Filesystem type is: %lx\n",
431 (unsigned long)fsinfo
.f_type
);
433 st
.st_blksize
= blksize
;
434 if (fsinfo
.f_type
== 0xef51 || fsinfo
.f_type
== 0xef52 ||
435 fsinfo
.f_type
== 0xef53) {
438 if (ioctl(fd
, EXT3_IOC_GETFLAGS
, &flags
) == 0 &&
439 !(flags
& EXT4_EXTENTS_FL
))
444 long cylgroups
= div_ceil(fsinfo
.f_blocks
, blksize
* 8);
446 if (verbose
&& last_device
!= st
.st_dev
)
447 printf("Filesystem cylinder groups approximately %ld\n",
450 data_blocks_per_cyl
= blksize
* 8 -
451 (fsinfo
.f_files
/ 8 / cylgroups
) - 3;
453 last_device
= st
.st_dev
;
455 width
= int_log10(fsinfo
.f_blocks
);
456 if (width
> physical_width
)
457 physical_width
= width
;
459 numblocks
= (st
.st_size
+ blksize
- 1) / blksize
;
461 blk_shift
= int_log2(blocksize
);
463 blk_shift
= int_log2(blksize
);
465 width
= int_log10(numblocks
);
466 if (width
> logical_width
)
467 logical_width
= width
;
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
);
475 rc
= filefrag_fiemap(fd
, blk_shift
, &num_extents
, &st
);
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
);
483 if (expected
== -EINVAL
|| expected
== -ENOTTY
) {
484 fprintf(stderr
, "%s: FIBMAP unsupported\n",
486 } else if (expected
== -EPERM
) {
488 "%s: FIBMAP requires root privileges\n",
491 fprintf(stderr
, "%s: FIBMAP error: %s",
492 filename
, strerror(expected
));
499 expected
= expected
/ data_blocks_per_cyl
+ 1;
502 if (num_extents
== 1)
503 printf("%s: 1 extent found", filename
);
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" : "");
518 static void usage(const char *progname
)
520 fprintf(stderr
, "Usage: %s [-b{blocksize}] [-BeksvxX] file ...\n",
525 int main(int argc
, char**argv
)
530 while ((c
= getopt(argc
, argv
, "Bb::eksvxX")) != EOF
) {
538 blocksize
= strtoul(optarg
, &end
, 0);
540 #if __GNUC_PREREQ (7, 0)
541 #pragma GCC diagnostic push
542 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
560 #if __GNUC_PREREQ (7, 0)
561 #pragma GCC diagnostic pop
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",
601 for (cpp
= argv
+ optind
; *cpp
!= NULL
; cpp
++) {
602 int rc2
= frag_report(*cpp
);
604 if (rc2
< 0 && rc
== 0)