/* * 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 <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 [-dvr] 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; }