| /* |
| * 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 (!ext2fs_has_feature_huge_file(current_fs->super) || |
| !(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); |
| if (ext2fs_inode_has_valid_blocks2(current_fs, inode)) { |
| 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 = ext2fs_dirent_name_len(dirent); |
| 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; |
| } |