diff mbox

[1/2] debugfs: add filefrag command

Message ID 1321564364-22803-1-git-send-email-tytso@mit.edu
State Accepted, archived
Headers show

Commit Message

Theodore Ts'o Nov. 17, 2011, 9:12 p.m. UTC
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      |    8 +-
 debugfs/debug_cmds.ct    |    3 +
 debugfs/debugfs.8.in     |   21 +++
 debugfs/debugfs.h        |    1 +
 debugfs/filefrag.c       |  324 ++++++++++++++++++++++++++++++++++++++++++++++
 debugfs/ro_debug_cmds.ct |    3 +
 6 files changed, 357 insertions(+), 3 deletions(-)
 create mode 100644 debugfs/filefrag.c

Comments

Eric Sandeen Nov. 17, 2011, 9:19 p.m. UTC | #1
On 11/17/11 3:12 PM, Theodore Ts'o wrote:
> 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      |    8 +-
>  debugfs/debug_cmds.ct    |    3 +
>  debugfs/debugfs.8.in     |   21 +++
>  debugfs/debugfs.h        |    1 +
>  debugfs/filefrag.c       |  324 ++++++++++++++++++++++++++++++++++++++++++++++

Is it possible to share any of ^^^ that code ^^^ with misc/filefrag.c somehow?

-Eric

>  debugfs/ro_debug_cmds.ct |    3 +
>  6 files changed, 357 insertions(+), 3 deletions(-)
>  create mode 100644 debugfs/filefrag.c
> 
> diff --git a/debugfs/Makefile.in b/debugfs/Makefile.in
> index e03a3c6..c6aaa3a 100644
> --- a/debugfs/Makefile.in
> +++ b/debugfs/Makefile.in
> @@ -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)
> diff --git a/debugfs/debug_cmds.ct b/debugfs/debug_cmds.ct
> index 47de672..af969b1 100644
> --- a/debugfs/debug_cmds.ct
> +++ b/debugfs/debug_cmds.ct
> @@ -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;
>  
> diff --git a/debugfs/debugfs.8.in b/debugfs/debugfs.8.in
> index 69490ff..70c8326 100644
> --- a/debugfs/debugfs.8.in
> +++ b/debugfs/debugfs.8.in
> @@ -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
> diff --git a/debugfs/debugfs.h b/debugfs/debugfs.h
> index 6d7dfcd..0afa1df 100644
> --- a/debugfs/debugfs.h
> +++ b/debugfs/debugfs.h
> @@ -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
> index 0000000..30933b6
> --- /dev/null
> +++ b/debugfs/filefrag.c
> @@ -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;
> +}
> diff --git a/debugfs/ro_debug_cmds.ct b/debugfs/ro_debug_cmds.ct
> index 7eb552d..4feb621 100644
> --- a/debugfs/ro_debug_cmds.ct
> +++ b/debugfs/ro_debug_cmds.ct
> @@ -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;
>  

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Theodore Ts'o Nov. 17, 2011, 9:27 p.m. UTC | #2
On Nov 17, 2011, at 4:19 PM, Eric Sandeen wrote:

> On 11/17/11 3:12 PM, Theodore Ts'o wrote:
>> 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      |    8 +-
>> debugfs/debug_cmds.ct    |    3 +
>> debugfs/debugfs.8.in     |   21 +++
>> debugfs/debugfs.h        |    1 +
>> debugfs/filefrag.c       |  324 ++++++++++++++++++++++++++++++++++++++++++++++
> 
> Is it possible to share any of ^^^ that code ^^^ with misc/filefrag.c somehow?

Well, the debugs code accesses the file system directly; I thought about creating glue code that created an interface similar to FIEMAP ioctl, but the glue code ends up being more than the code to do just do the fragmentation calculation.

-- Ted

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/debugfs/Makefile.in b/debugfs/Makefile.in
index e03a3c6..c6aaa3a 100644
--- a/debugfs/Makefile.in
+++ b/debugfs/Makefile.in
@@ -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)
diff --git a/debugfs/debug_cmds.ct b/debugfs/debug_cmds.ct
index 47de672..af969b1 100644
--- a/debugfs/debug_cmds.ct
+++ b/debugfs/debug_cmds.ct
@@ -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;
 
diff --git a/debugfs/debugfs.8.in b/debugfs/debugfs.8.in
index 69490ff..70c8326 100644
--- a/debugfs/debugfs.8.in
+++ b/debugfs/debugfs.8.in
@@ -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
diff --git a/debugfs/debugfs.h b/debugfs/debugfs.h
index 6d7dfcd..0afa1df 100644
--- a/debugfs/debugfs.h
+++ b/debugfs/debugfs.h
@@ -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
index 0000000..30933b6
--- /dev/null
+++ b/debugfs/filefrag.c
@@ -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;
+}
diff --git a/debugfs/ro_debug_cmds.ct b/debugfs/ro_debug_cmds.ct
index 7eb552d..4feb621 100644
--- a/debugfs/ro_debug_cmds.ct
+++ b/debugfs/ro_debug_cmds.ct
@@ -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;