You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
7.0 KiB
327 lines
7.0 KiB
/*
|
|
* 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[], int sci_idx EXT2FS_ATTR((unused)),
|
|
void *infop EXT2FS_ATTR((unused)))
|
|
{
|
|
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;
|
|
}
|