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