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