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 ++++++++++++++++++++++++++++++++++++++++++++++ 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; -- 1.7.4.1.22.gec8e1.dirty -- 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