Whamcloud - gitweb
debugfs: add filefrag command
authorTheodore Ts'o <tytso@mit.edu>
Thu, 17 Nov 2011 20:15:38 +0000 (15:15 -0500)
committerTheodore Ts'o <tytso@mit.edu>
Sat, 19 Nov 2011 03:14:15 +0000 (22:14 -0500)
Add the ability to report on the fragmentation of a file on a file
system opened using debugfs.

Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
debugfs/Makefile.in
debugfs/debug_cmds.ct
debugfs/debugfs.8.in
debugfs/debugfs.h
debugfs/filefrag.c [new file with mode: 0644]
debugfs/ro_debug_cmds.ct
lib/ext2fs/Makefile.in

index e03a3c6..c6aaa3a 100644 (file)
@@ -17,15 +17,17 @@ MANPAGES=   debugfs.8
 MK_CMDS=       _SS_DIR_OVERRIDE=../lib/ss ../lib/ss/mk_cmds
 
 DEBUG_OBJS= debug_cmds.o debugfs.o util.o ncheck.o icheck.o ls.o \
-       lsdel.o dump.o set_fields.o logdump.o htree.o unused.o e2freefrag.o
+       lsdel.o dump.o set_fields.o logdump.o htree.o unused.o e2freefrag.o \
+       filefrag.o
 
 RO_DEBUG_OBJS= ro_debug_cmds.o ro_debugfs.o util.o ncheck.o icheck.o ls.o \
-       lsdel.o logdump.o htree.o e2freefrag.o
+       lsdel.o logdump.o htree.o e2freefrag.o filefrag.o
 
 SRCS= debug_cmds.c $(srcdir)/debugfs.c $(srcdir)/util.c $(srcdir)/ls.c \
        $(srcdir)/ncheck.c $(srcdir)/icheck.c $(srcdir)/lsdel.c \
        $(srcdir)/dump.c $(srcdir)/set_fields.c ${srcdir}/logdump.c \
-       $(srcdir)/htree.c $(srcdir)/unused.c
+       $(srcdir)/htree.c $(srcdir)/unused.c ${srcdir}/../misc/e2freefrag.c \
+       $(srcdir)/filefrag.c
 
 LIBS= $(LIBEXT2FS) $(LIBE2P) $(LIBSS) $(LIBCOM_ERR) $(LIBBLKID) \
        $(LIBUUID)
index 47de672..af969b1 100644 (file)
@@ -52,6 +52,9 @@ request do_dump_extents, "Dump extents information ",
 request do_blocks, "Dump blocks used by an inode ",
        blocks;
 
+request do_filefrag, "Report fragmentation information for an inode",
+       filefrag;
+
 request do_link, "Create directory link",
        link, ln;
 
index 69490ff..70c8326 100644 (file)
@@ -251,6 +251,27 @@ Set or clear various filesystem features in the superblock.  After setting
 or clearing any filesystem features that were requested, print the current
 state of the filesystem feature set.
 .TP
+.I filefrag [-dvr] filespec
+Print the number of contiguous extents in
+.IR filespec .
+If
+.I filespec
+is a directory and the
+.I -d
+option is not specified,
+.I filefrag
+will print the number of contiguous extents for each file in
+the directory.  The
+.I -v
+option will cause
+.I filefrag
+print a tabular listing of the contiguous extents in the
+file.  The
+.I -r
+option will cause
+.I filefrag
+to do a recursive listing of the directory.
+.TP
 .I find_free_block [count [goal]]
 Find the first 
 .I count
index 6d7dfcd..0afa1df 100644 (file)
@@ -134,3 +134,4 @@ extern void do_supported_features(int argc, char **argv);
 extern void do_punch(int argc, char **argv);
 
 extern void do_freefrag(int argc, char **argv);
+extern void do_filefrag(int argc, char *argv[]);
diff --git a/debugfs/filefrag.c b/debugfs/filefrag.c
new file mode 100644 (file)
index 0000000..30933b6
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * filefrag.c --- display the fragmentation information for a file
+ *
+ * Copyright (C) 2011 Theodore Ts'o.  This file may be redistributed
+ * under the terms of the GNU Public License.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <utime.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern int optind;
+extern char *optarg;
+#endif
+
+#include "debugfs.h"
+
+#define VERBOSE_OPT    0x0001
+#define DIR_OPT                0x0002
+#define RECURSIVE_OPT  0x0004
+
+struct dir_list {
+       char            *name;
+       ext2_ino_t      ino;
+       struct dir_list *next;
+};
+
+struct filefrag_struct {
+       FILE            *f;
+       const char      *name;
+       const char      *dir_name;
+       int             options;
+       int             logical_width;
+       int             physical_width;
+       int             ext;
+       int             cont_ext;
+       e2_blkcnt_t     num;
+       e2_blkcnt_t     logical_start;
+       blk64_t         physical_start;
+       blk64_t         expected;
+       struct dir_list *dir_list, *dir_last;
+};
+
+static int int_log10(unsigned long long arg)
+{
+       int     l = 0;
+
+       arg = arg / 10;
+       while (arg) {
+               l++;
+               arg = arg / 10;
+       }
+       return l;
+}
+
+static void print_header(struct filefrag_struct *fs)
+{
+       if (fs->options & VERBOSE_OPT) {
+               fprintf(fs->f, "%4s %*s %*s %*s %*s\n", "ext",
+                       fs->logical_width, "logical", fs->physical_width,
+                       "physical", fs->physical_width, "expected",
+                       fs->logical_width, "length");
+       }
+}
+
+static void report_filefrag(struct filefrag_struct *fs)
+{
+       if (fs->num == 0)
+               return;
+       if (fs->options & VERBOSE_OPT) {
+               if (fs->expected)
+                       fprintf(fs->f, "%4d %*lu %*llu %*llu %*lu\n", fs->ext,
+                               fs->logical_width,
+                               (unsigned long) fs->logical_start,
+                               fs->physical_width, fs->physical_start,
+                               fs->physical_width, fs->expected,
+                               fs->logical_width, (unsigned long) fs->num);
+               else
+                       fprintf(fs->f, "%4d %*lu %*llu %*s %*lu\n", fs->ext,
+                               fs->logical_width,
+                               (unsigned long) fs->logical_start,
+                               fs->physical_width, fs->physical_start,
+                               fs->physical_width, "",
+                               fs->logical_width, (unsigned long) fs->num);
+       }
+       fs->ext++;
+}
+
+static int filefrag_blocks_proc(ext2_filsys ext4_fs EXT2FS_ATTR((unused)),
+                               blk64_t *blocknr, e2_blkcnt_t blockcnt,
+                               blk64_t ref_block EXT2FS_ATTR((unused)),
+                               int ref_offset EXT2FS_ATTR((unused)),
+                               void *private)
+{
+       struct filefrag_struct *fs = private;
+
+       if (blockcnt < 0 || *blocknr == 0)
+               return 0;
+
+       if ((fs->num == 0) || (blockcnt != fs->logical_start + fs->num) ||
+           (*blocknr != fs->physical_start + fs->num)) {
+               report_filefrag(fs);
+               if (blockcnt == fs->logical_start + fs->num)
+                       fs->expected = fs->physical_start + fs->num;
+               else
+                       fs->expected = 0;
+               fs->logical_start = blockcnt;
+               fs->physical_start = *blocknr;
+               fs->num = 1;
+               fs->cont_ext++;
+       } else
+               fs->num++;
+       return 0;
+}
+
+static void filefrag(ext2_ino_t ino, struct ext2_inode *inode,
+                    struct filefrag_struct *fs)
+{
+       errcode_t       retval;
+       int             blocksize = current_fs->blocksize;
+
+       fs->logical_width = int_log10((EXT2_I_SIZE(inode) + blocksize - 1) /
+                                     blocksize) + 1;
+       if (fs->logical_width < 7)
+               fs->logical_width = 7;
+       fs->ext = 0;
+       fs->cont_ext = 0;
+       fs->logical_start = 0;
+       fs->physical_start = 0;
+       fs->num = 0;
+
+       if (fs->options & VERBOSE_OPT) {
+               blk64_t num_blocks = ext2fs_inode_i_blocks(current_fs, inode);
+
+               if (!(current_fs->super->s_feature_ro_compat &
+                    EXT4_FEATURE_RO_COMPAT_HUGE_FILE) ||
+                   !(inode->i_flags & EXT4_HUGE_FILE_FL))
+                       num_blocks /= current_fs->blocksize / 512;
+
+               fprintf(fs->f, "\n%s has %llu block(s), i_size is %llu\n",
+                       fs->name, num_blocks, EXT2_I_SIZE(inode));
+       }
+       print_header(fs);
+       retval = ext2fs_block_iterate3(current_fs, ino,
+                                      BLOCK_FLAG_READ_ONLY, NULL,
+                                      filefrag_blocks_proc, fs);
+       if (retval)
+               com_err("ext2fs_block_iterate3", retval, 0);
+
+       report_filefrag(fs);
+       fprintf(fs->f, "%s: %d contiguous extents%s\n", fs->name, fs->ext,
+               LINUX_S_ISDIR(inode->i_mode) ? " (dir)" : "");
+}
+
+static int filefrag_dir_proc(ext2_ino_t dir EXT2FS_ATTR((unused)),
+                            int        entry,
+                            struct ext2_dir_entry *dirent,
+                            int        offset EXT2FS_ATTR((unused)),
+                            int        blocksize EXT2FS_ATTR((unused)),
+                            char       *buf EXT2FS_ATTR((unused)),
+                            void       *private)
+{
+       struct filefrag_struct *fs = private;
+       struct ext2_inode       inode;
+       ext2_ino_t              ino;
+       char                    name[EXT2_NAME_LEN + 1];
+       char                    *cp;
+       int                     thislen;
+
+       if (entry == DIRENT_DELETED_FILE)
+               return 0;
+
+       thislen = dirent->name_len & 0xFF;
+       strncpy(name, dirent->name, thislen);
+       name[thislen] = '\0';
+       ino = dirent->inode;
+
+       if (!strcmp(name, ".") || !strcmp(name, ".."))
+               return 0;
+
+       cp = malloc(strlen(fs->dir_name) + strlen(name) + 2);
+       if (!cp) {
+               fprintf(stderr, "Couldn't allocate memory for %s/%s\n",
+                       fs->dir_name, name);
+               return 0;
+       }
+
+       sprintf(cp, "%s/%s", fs->dir_name, name);
+       fs->name = cp;
+
+       if (debugfs_read_inode(ino, &inode, fs->name))
+               goto errout;
+
+       filefrag(ino, &inode, fs);
+
+       if ((fs->options & RECURSIVE_OPT) && LINUX_S_ISDIR(inode.i_mode)) {
+               struct dir_list *p;
+
+               p = malloc(sizeof(struct dir_list));
+               if (!p) {
+                       fprintf(stderr, "Couldn't allocate dir_list for %s\n",
+                               fs->name);
+                       goto errout;
+               }
+               memset(p, 0, sizeof(struct dir_list));
+               p->name = cp;
+               p->ino = ino;
+               if (fs->dir_last)
+                       fs->dir_last->next = p;
+               else
+                       fs->dir_list = p;
+               fs->dir_last = p;
+               return 0;
+       }
+errout:
+       free(cp);
+       fs->name = 0;
+       return 0;
+}
+
+
+static void dir_iterate(ext2_ino_t ino, struct filefrag_struct *fs)
+{
+       errcode_t       retval;
+       struct dir_list *p = NULL;
+
+       fs->dir_name = fs->name;
+
+       while (1) {
+               retval = ext2fs_dir_iterate2(current_fs, ino, 0,
+                                            0, filefrag_dir_proc, fs);
+               if (retval)
+                       com_err("ext2fs_dir_iterate2", retval, 0);
+               if (p) {
+                       free(p->name);
+                       fs->dir_list = p->next;
+                       if (!fs->dir_list)
+                               fs->dir_last = 0;
+                       free(p);
+               }
+               p = fs->dir_list;
+               if (!p)
+                       break;
+               ino = p->ino;
+               fs->dir_name = p->name;
+       }
+}
+
+void do_filefrag(int argc, char *argv[])
+{
+       struct filefrag_struct fs;
+       struct ext2_inode inode;
+       ext2_ino_t      ino;
+       int             c;
+
+       memset(&fs, 0, sizeof(fs));
+       if (check_fs_open(argv[0]))
+               return;
+
+       reset_getopt();
+       while ((c = getopt (argc, argv, "dvr")) != EOF) {
+               switch (c) {
+               case 'd':
+                       fs.options |= DIR_OPT;
+                       break;
+               case 'v':
+                       fs.options |= VERBOSE_OPT;
+                       break;
+               case 'r':
+                       fs.options |= RECURSIVE_OPT;
+                       break;
+               default:
+                       goto print_usage;
+               }
+       }
+
+       if (argc > optind+1) {
+       print_usage:
+               com_err(0, 0, "Usage: filefrag [-dv] file");
+               return;
+       }
+
+       if (argc == optind) {
+               ino = cwd;
+               fs.name = ".";
+       } else {
+               ino = string_to_inode(argv[optind]);
+               fs.name = argv[optind];
+       }
+       if (!ino)
+               return;
+
+       if (debugfs_read_inode(ino, &inode, argv[0]))
+               return;
+
+       fs.f = open_pager();
+       fs.physical_width = int_log10(ext2fs_blocks_count(current_fs->super));
+       fs.physical_width++;
+       if (fs.physical_width < 8)
+               fs.physical_width = 8;
+
+       if (!LINUX_S_ISDIR(inode.i_mode) || (fs.options & DIR_OPT))
+               filefrag(ino, &inode, &fs);
+       else
+               dir_iterate(ino, &fs);
+
+       fprintf(fs.f, "\n");
+       close_pager(fs.f);
+
+       return;
+}
index 7eb552d..4feb621 100644 (file)
@@ -45,6 +45,9 @@ request do_dump_extents, "Dump extents information ",
 request do_blocks, "Dump blocks used by an inode ",
        blocks;
 
+request do_filefrag, "Report fragmentation information for an inode",
+       filefrag;
+
 request do_testi, "Test an inode's in-use flag",
        testi;
 
index 9edbcb5..a9795a3 100644 (file)
@@ -287,7 +287,7 @@ debug_cmds.c debug_cmds.h: $(top_srcdir)/debugfs/debug_cmds.ct
 
 DEBUG_OBJS= debug_cmds.o debugfs.o util.o ncheck.o icheck.o ls.o \
        lsdel.o dump.o set_fields.o logdump.o htree.o unused.o \
-       e2freefrag.o
+       e2freefrag.o filefrag.o
 
 debugfs.o: $(top_srcdir)/debugfs/debugfs.c
        $(E) "  CC $<"
@@ -337,6 +337,10 @@ e2freefrag.o: $(top_srcdir)/misc/e2freefrag.c
        $(E) "  CC $<"
        $(Q) $(CC) $(ALL_CFLAGS) -DDEBUGFS -I$(top_srcdir)/debugfs -c $< -o $@
 
+filefrag.o: $(top_srcdir)/debugfs/filefrag.c
+       $(E) "  CC $<"
+       $(Q) $(CC) $(ALL_CFLAGS) -c $< -o $@
+
 tst_extents: $(srcdir)/extent.c extent_dbg.c $(DEBUG_OBJS) $(DEPLIBSS) \
        $(LIBE2P) $(DEPLIBUUID) $(DEPLIBBLKID) $(DEPLIBCOM_ERR)
        $(E) "  LD $@"