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@xxxxxxx> > --- > 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@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html