Skip to contentSkip to conversation

ext4: online defrag command

RFC PATCH In review Akira Fujita 1 participant 1 message Oct 24, 2008 VFS

lore
Share & tools
lore
#0 lore raw

RFC PATCH 9/9 ext4: online defrag command

ext4: online defrag -- Online defrag command

From: Akira Fujita <[email protected]>

- The defrag command. Usage is as follows:
- Put the multiple files closer together.

    # e4defrag -r directory-name
  - Defrag for free space fragmentation.
    # e4defrag -f file-name
  - Defrag for a single file.
    # e4defrag file-name
  - Defrag for all files on ext4.
    # e4defrag device-name

---
/*
* e4defrag.c - ext4 filesystem defragmenter
*
* Copyright (C) 2008 NEC Software Tohoku, Ltd.
*
* Author: Akira Fujita <[email protected]>
* Takashi Sato <[email protected]>
*/

#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
#endif

#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif

#define _XOPEN_SOURCE 500
#define _GNU_SOURCE
#include <ftw.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/statfs.h>
#include <sys/vfs.h>
#include <sys/ioctl.h>
#include <mntent.h>
#include <linux/fs.h>
#include <ctype.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <ext2fs/ext2_types.h>
#include <endian.h>

/* Ioctl command */
#define EXT4_IOC_DEFRAG _IOW('f', 15, struct ext4_ext_defrag_data)
#define EXT4_IOC_SUPER_INFO _IOR('f', 16, struct ext4_super_block)
#define EXT4_IOC_FREE_BLOCKS_INFO _IOWR('f', 17, struct ext4_extents_info)
#define EXT4_IOC_FIEMAP_INO _IOWR('f', 18, struct fiemap_ino)
#define EXT4_IOC_MOVE_VICTIM _IOW('f', 19, struct ext4_extents_info)
#define FS_IOC_FIEMAP _IOWR('f', 11, struct fiemap)

/* Macro functions */
#define PRINT_ERR_MSG(msg) fprintf(stderr, "%s\n", (msg));

#define IN_FTW_PRINT_ERR_MSG(msg)	\
	fprintf(stderr, "\t%s\t\t[ NG ]\n", (msg));
#define PRINT_FILE_NAME(file)	fprintf(stderr, " \"%s\"\n", (file));
#define PRINT_ERR_MSG_WITH_ERRNO(msg)	\
	fprintf(stderr, "\t%s:%s\t[ NG ]\n", (msg), strerror(errno));
#define min(x, y) (((x) > (y)) ? (y) : (x))
/* Wrap up the free function */
#define FREE(tmp)				\
	do {					\
		if (tmp)			\
			free(tmp);		\
	} while (0)				\
/* Insert list2 after list1 */
#define insert(list1, list2)			\
	do {					\
		list2->next = list1->next;	\
		list1->next->prev = list2;	\
		list2->prev = list1;		\
		list1->next = list2;		\
	} while (0)

#ifndef __NR_fadvise64
#define __NR_fadvise64 250
#endif

#ifndef __NR_sync_file_range
#define __NR_sync_file_range 314
#endif

#ifndef POSIX_FADV_DONTNEED
#if defined(__s390x__)
#define POSIX_FADV_DONTNEED 6 /* Don't need these pages */
#else
#define POSIX_FADV_DONTNEED 4 /* Don't need these pages */
#endif
#endif

#ifndef SYNC_FILE_RANGE_WAIT_BEFORE
#define SYNC_FILE_RANGE_WAIT_BEFORE 1
#endif
#ifndef SYNC_FILE_RANGE_WRITE
#define SYNC_FILE_RANGE_WRITE 2
#endif
#ifndef SYNC_FILE_RANGE_WAIT_AFTER
#define SYNC_FILE_RANGE_WAIT_AFTER 4
#endif

#define DEVNAME 0
#define DIRNAME 1
#define FILENAME 2

#define RETURN_OK 0
#define RETURN_NG -1
#define FTW_CONT 0
#define FTW_OPEN_FD 2000

#define FS_EXT4 "ext4"
#define ROOT_UID 0
#define CHECK_FRAG_COUNT 1

/* Extent status which are used in extent_t */
#define EXT4_EXT_USE 0
#define EXT4_EXT_FREE 1

/* The maximum number of extents for exchanging between
* kernel-space and user-space per ioctl
*/
#define DEFRAG_MAX_ENT 32

/* The phase of force defrag mode */
#define DEFRAG_FORCE_TRY 1
#define DEFRAG_FORCE_GATHER 3

/* Magic number for ext4 */
#define EXT4_SUPER_MAGIC 0xEF53

/* Defrag size(64MB) per ioctl(not including force mode) */
#define DEFRAG_SIZE ((unsigned long)1 << 26)

/* Force defrag mode: Max file size in bytes (128MB) */
#define MAX_FILE_SIZE ((unsigned long)1 << 27)

/* The following four macros are used for ioctl FS_IOC_FIEMAP
* FIEMAP_FLAG_SYNC: sync file data before map.
* FIEMAP_EXTENT_LAST: last extent in file.
* FIEMAP_MAX_OFFSET: max file offset.
* EXTENT_MAX_COUNT: the maximum number of extents for exchanging between
kernel-space and user-space per ioctl
*/
#define FIEMAP_FLAG_SYNC 0x00000001
#define FIEMAP_EXTENT_LAST 0x00000001
#define FIEMAP_MAX_OFFSET (~0ULL)
#define EXTENT_MAX_COUNT 512

/* The following macros are error message */
#define MSG_USAGE \

"Usage : e4defrag [-v] file...| directory...| device...\n\
      : e4defrag -f file [blocknr] \n\
      : e4defrag -r directory... | device... \n"

#define MSG_R_OPTION " with regional block allocation mode.\n"
#define NGMSG_MTAB "Can not access /etc/mtab"
#define NGMSG_UNMOUNT "FS is not mounted"
#define NGMSG_EXT4 "FS is not ext4 File System"
#define NGMSG_FS_INFO "Get FSInfo fail"
#define NGMSG_FILE_INFO "Get FileInfo fail"
#define NGMSG_FILE_OPEN "Open fail"
#define NGMSG_FILE_SYNC "Sync(fsync) fail"
#define NGMSG_FILE_DEFRAG "Defrag fail"
#define NGMSG_FILE_BLOCKSIZE "Can't get blocksize"
#define NGMSG_FILE_SUPER "Can't get super block info"
#define NGMSG_FILE_UNREG "File is not regular file"
#define NGMSG_VICTIM_UID "Victim file is not current user's file"

#define NGMSG_FILE_LARGE \
"Defrag size is larger than FileSystem's free space"

#define NGMSG_FILE_PRIORITY \
"File is not current user's file or current user is not root"

#define NGMSG_FILE_LOCK "File is locked"
#define NGMSG_FILE_BLANK "File size is 0"
#define NGMSG_GET_LCKINFO "Get LockInfo fail"
#define NGMSG_TYPE \
"Can not process %s in regional mode.\n"
#define NGMSG_LOST_FOUND "Can not process \"lost+found\""
#define NGMSG_REALPATH "Can not get full path"
#define NGMSG_FILE_MAP "Get file map fail"
#define NGMSG_FILE_DROP_BUFFER "Free page fail"
#define NGMSG_FADVISE_SYSCALL "\tFadvise fail"
#define NGMSG_FORCE_DEFRAG_SIZE \
"Cannot specify a file larger than %luMB in force defrag mode"
#define NGMSG_ENDIAN "Endian type is not big/little endian"

/* Data type for filesystem-wide blocks number */
typedef unsigned long long ext4_fsblk_t;

/* Data type for file logical block number */
typedef unsigned int ext4_lblk_t;

/* Data type for block offset of block group */
typedef int ext4_grpblk_t;

struct ext4_extent_data {
ext4_lblk_t block; /* start logical block number */
ext4_fsblk_t start; /* start physical block number */
int len; /* blocks count */
};

/* Used for defrag */
struct ext4_ext_defrag_data {
ext4_lblk_t start_offset; /* start offset to defrag in blocks */
ext4_lblk_t defrag_size; /* size of defrag in blocks */
ext4_fsblk_t goal; /* block offset for allocation */
int flag; /* free space mode flag */
struct ext4_extent_data ext;
};

typedef __u16 __le16;
typedef __u32 __le32;
typedef __u64 __le64;

/*
* Structure of the super block
*/
struct ext4_super_block {
/*00*/ __le32 s_inodes_count; /* Inodes count */

	__le32	s_blocks_count_lo;	/* Blocks count */
	__le32	s_r_blocks_count_lo;	/* Reserved blocks count */
	__le32	s_free_blocks_count_lo;	/* Free blocks count */
/*10*/	__le32	s_free_inodes_count;	/* Free inodes count */
	__le32	s_first_data_block;	/* First Data Block */
	__le32	s_log_block_size;	/* Block size */
	__le32	s_obso_log_frag_size;	/* Obsoleted fragment size */
/*20*/	__le32	s_blocks_per_group;	/* # Blocks per group */
	__le32	s_obso_frags_per_group;	/* Obsoleted fragments per group */
	__le32	s_inodes_per_group;	/* # Inodes per group */
	__le32	s_mtime;		/* Mount time */
/*30*/	__le32	s_wtime;		/* Write time */
	__le16	s_mnt_count;		/* Mount count */
	__le16	s_max_mnt_count;	/* Maximal mount count */
	__le16	s_magic;		/* Magic signature */
	__le16	s_state;		/* File system state */
	__le16	s_errors;		/* Behaviour when detecting errors */
	__le16	s_minor_rev_level;	/* minor revision level */
/*40*/	__le32	s_lastcheck;		/* time of last check */
	__le32	s_checkinterval;	/* max. time between checks */
	__le32	s_creator_os;		/* OS */
	__le32	s_rev_level;		/* Revision level */

/*50*/ __le16 s_def_resuid; /* Default uid for reserved blocks */
__le16 s_def_resgid; /* Default gid for reserved blocks */
/*
* These fields are for EXT4_DYNAMIC_REV superblocks only. *
* Note: the difference between the compatible feature set and
* the incompatible feature set is that if there is a bit set
* in the incompatible feature set that the kernel doesn't
* know about, it should refuse to mount the filesystem. *
* e2fsck's requirements are more strict; if it doesn't know
* about a feature in either the compatible or incompatible
* feature set, it must abort and not try to meddle with
* things it doesn't understand...
*/
__le32 s_first_ino; /* First non-reserved inode */
__le16 s_inode_size; /* size of inode structure */
__le16 s_block_group_nr; /* block group # of this superblock */
__le32 s_feature_compat; /* compatible feature set */
/*60*/ __le32 s_feature_incompat; /* incompatible feature set */
__le32 s_feature_ro_compat; /* readonly-compatible feature set */
/*68*/ __u8 s_uuid[16]; /* 128-bit uuid for volume */
/*78*/ char s_volume_name[16]; /* volume name */
/*88*/ char s_last_mounted[64]; /* directory where last mounted */
/*C8*/ __le32 s_algorithm_usage_bitmap; /* For compression */
/*
* Performance hints. Directory preallocation should only
* happen if the EXT4_FEATURE_COMPAT_DIR_PREALLOC flag is on. */
__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__le16 s_reserved_gdt_blocks; /* Per group desc for online growth */
/*
* Journaling support valid if EXT4_FEATURE_COMPAT_HAS_JOURNAL set. */
/*D0*/ __u8 s_journal_uuid[16]; /* uuid of journal superblock */
/*E0*/ __le32 s_journal_inum; /* inode number of journal file */
__le32 s_journal_dev; /* device number of journal file */
__le32 s_last_orphan; /* start of list of inodes to delete */
__le32 s_hash_seed[4]; /* HTREE hash seed */
__u8 s_def_hash_version; /* Default hash version to use */
__u8 s_reserved_char_pad;
__le16 s_desc_size; /* size of group descriptor */

/*100*/	__le32	s_default_mount_opts;
	__le32	s_first_meta_bg;	/* First metablock block group */
	__le32	s_mkfs_time;		/* When the filesystem was created */
	__le32	s_jnl_blocks[17];	/* Backup of the journal inode */
	/* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */
/*150*/	__le32	s_blocks_count_hi;	/* Blocks count */
	__le32	s_r_blocks_count_hi;	/* Reserved blocks count */
	__le32	s_free_blocks_count_hi;	/* Free blocks count */
	__le16	s_min_extra_isize;	/* All inodes have at least # bytes */
	__le16	s_want_extra_isize; 	/* New inodes should reserve # bytes */
	__le32	s_flags;		/* Miscellaneous flags */
	__le16  s_raid_stride;		/* RAID stride */
	__le16  s_mmp_interval;         /* # seconds to wait in MMP checking */
	__le64  s_mmp_block;            /* Block for multi-mount protection */
	__le32  s_raid_stripe_width;    /* blocks on all data disks (N*stride)*/
	__u8	s_log_groups_per_flex;  /* FLEX_BG group size */
	__u8	s_reserved_char_pad2;
	__le16  s_reserved_pad;
	__u32   s_reserved[162];        /* Padding to the end of the block */
};
struct ext4_extents_info {
	unsigned long long ino;		/* inode number */
	int max_entries;		/* maximum extents count */
	int entries; 	 		/* extent number/count */
	ext4_lblk_t  f_offset;		/* file offset */
	ext4_grpblk_t g_offset;		/* group offset */
	ext4_fsblk_t goal;
	struct ext4_extent_data ext[DEFRAG_MAX_ENT];
};

typedef struct extent {
struct extent *prev;
unsigned long tag; /* extent status */
unsigned long ino; /* file's inode number */
struct ext4_extent_data data; /* extent belong to file */
struct extent *next;
} extent_t;

typedef struct exts_group {
	struct exts_group *prev;
	extent_t *start;		/* start ext */
	extent_t *end;			/* end ext */
	int len;			/* length of this continuous region */
	struct exts_group *next;
} exts_group_t;

struct fiemap_extent {
__u64 fe_logical; /* logical offset in bytes for the start of
* the extent from the beginning of the file */
__u64 fe_physical; /* physical offset in bytes for the start
* of the extent from the beginning
* of the disk */
__u64 fe_length; /* length in bytes for this extent */
__u64 fe_reserved64[2];
__u32 fe_flags; /* FIEMAP_EXTENT_* flags for this extent */
__u32 fe_reserved[3];
};

struct fiemap {
__u64 fm_start; /* logical offset (inclusive) at
* which to start mapping (in) */
__u64 fm_length; /* logical length of mapping which
* userspace wants (in) */
__u32 fm_flags; /* FIEMAP_FLAG_* flags for request (in/out) */
__u32 fm_mapped_extents;/* number of extents that were mapped (out) */
__u32 fm_extent_count; /* size of fm_extents array (in) */
__u32 fm_reserved;
struct fiemap_extent fm_extents[0];/* array of mapped extents (out) */
};

struct fiemap_ino {
	__u64 ino;
	struct fiemap fiemap;
};

int force_flag;
int detail_flag;
int regional_flag;
int extents_before_defrag;
int extents_after_defrag;
unsigned long succeed_cnt;
unsigned long regular_count;
unsigned long total_count;
unsigned long frag_files_before_defrag;
unsigned long frag_files_after_defrag;
unsigned long defraged_file_count;
char lost_found_dir[PATH_MAX + 1];
ext4_fsblk_t goal;
ext4_fsblk_t fgoal = -1;

/*
* ext2fs_swab32() - Change endian.
*
* @val: the entry used for change.
*/
__u32 ext2fs_swab32(__u32 val)
{

#if BYTE_ORDER == BIG_ENDIAN
	return (val>>24) | ((val>>8)&0xFF00) |
		((val<<8)&0xFF0000) | (val<<24);
#else
	return val;

#endif
}

/*
* fadvise() - Give advice about file access.
*
* @fd: the file's descriptor.
* @offset: file offset.
* @len: area length.
* @advise: process flag.
*/
int fadvise(int fd, loff_t offset, size_t len, int advise)

{
	return syscall(__NR_fadvise64, fd, offset, len, advise);
}

/*
* sync_file_range() - Sync file region.
*
* @fd: the file's descriptor.
* @offset: file offset.
* @length: area length.
* @flag: process flag.
*/
int sync_file_range(int fd, loff_t offset, loff_t length, unsigned int flag)

{
	return syscall(__NR_sync_file_range, fd, offset, length, flag);
}

/*
* page_in_core() - Get information on whether pages are in core.
*
* @fd: the file's descriptor.
* @defrag_data: data used for defrag.
* @vec: page state array.
* @page_num: page number.
*/
int page_in_core(int fd, struct ext4_ext_defrag_data defrag_data,
unsigned char **vec, unsigned long *page_num)

{
	int blocksize;
	int pagesize = getpagesize();
	void *page = NULL;
	loff_t offset, end_offset, length;

	if (vec == NULL || *vec != NULL)
		return RETURN_NG;

	if (ioctl(fd, FIGETBSZ, &blocksize) < 0)
		return RETURN_NG;

	/* In mmap, offset should be a multiple of the page size */
	offset = defrag_data.start_offset * blocksize;
	length = defrag_data.defrag_size * blocksize;
	end_offset = offset + length;
	/* Round the offset down to the nearest multiple of pagesize */
	offset = (offset / pagesize) * pagesize;
	length = end_offset - offset;

	page = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, offset);
	if (page == MAP_FAILED)
		return RETURN_NG;

	*page_num = 0;
	*page_num = (length + pagesize - 1) / pagesize;
	*vec = (unsigned char *)calloc(*page_num, 1);
	if (*vec == NULL)
		return RETURN_NG;

	/* Get information on whether pages are in core */
	if (mincore(page, (size_t)length, *vec) == -1 ||
	    munmap(page, length) == -1) {
		FREE(*vec);
		return RETURN_NG;
	}

	return RETURN_OK;
}

/*
* defrag_fadvise() - Predeclare an access pattern for file data.
*
* @fd: the file's descriptor.
* @defrag_data: data used for defrag.
* @vec: page state array.
* @page_num: page number.
*/
int defrag_fadvise(int fd, struct ext4_ext_defrag_data defrag_data,
unsigned char *vec, unsigned long page_num)

{
	int flag = 1;
	int blocksize;
	int pagesize = getpagesize();
	int fadvise_flag = POSIX_FADV_DONTNEED;
	int sync_flag = SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE|
			SYNC_FILE_RANGE_WAIT_AFTER;
	unsigned long i;
	loff_t offset;

	if (ioctl(fd, FIGETBSZ, &blocksize) < 0)
		return RETURN_NG;

	offset = (loff_t)defrag_data.start_offset * blocksize;
	offset = (offset / pagesize) * pagesize;

	/* Sync file for fadvise process */
	if (sync_file_range(fd, offset,
		(loff_t)pagesize*page_num, sync_flag) != 0)
		return RETURN_NG;

	/* Try to release buffer cache which this process used,
	 * then other process can use the released buffer
	 */
	for (i = 0; i < page_num; i++) {
		if ((vec[i] & 0x1) == 0) {
			offset += pagesize;
			continue;
		}
		if (fadvise(fd, offset, pagesize, fadvise_flag) != 0) {
			if (detail_flag && flag) {
				perror(NGMSG_FADVISE_SYSCALL);
				flag = 0;
			}
		}
		offset += pagesize;
	}

	return RETURN_OK;
}

/*
* check_free_size() - Check if there's enough disk space.
*
* @fd: the file's descriptor.
* @file_name file name.
* @buf: the pointer of the struct stat64.
*/
int check_free_size(int fd, const char *file_name, const struct stat64 *buf)

{
	off64_t	size = 0;
	off64_t	free_size = 0;
	struct statfs	fsbuf;

	/* Target file size */
	size = buf->st_size;

	if (fstatfs(fd, &fsbuf) < 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FS_INFO);
		}
		return RETURN_NG;
	}

	/* Compute free space for root and normal user separately */
	if (getuid() == ROOT_UID)
		free_size = (off64_t)fsbuf.f_bsize * fsbuf.f_bfree;
	else
		free_size = (off64_t)fsbuf.f_bsize * fsbuf.f_bavail;

	if (free_size >= size)
		return RETURN_OK;

	if (detail_flag) {
		PRINT_FILE_NAME(file_name);
		IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_LARGE);
	}
	return RETURN_NG;
}

int file_check(int fd, const struct stat64 *buf, const char *file_name); int force_defrag(int fd, const char *file_name,

		const struct stat64 *buf, int blocksize);

/*
* file_frag_count() - Get file fragment count.
*
* @fd: the file's descriptor.
*/
long long file_frag_count(int fd)

{
	int ret = RETURN_NG;
	struct fiemap file_extent_map;

	/* When fm_extent_count is 0,
	 * ioctl just get file fragment count.
	 */
	memset(&file_extent_map, 0, sizeof(struct fiemap));
	file_extent_map.fm_start = 0;
	file_extent_map.fm_length = FIEMAP_MAX_OFFSET;
	file_extent_map.fm_flags |= FIEMAP_FLAG_SYNC;

	ret = ioctl(fd, FS_IOC_FIEMAP, &file_extent_map);
	if (ret < 0)
		return RETURN_NG;

	return file_extent_map.fm_mapped_extents;
}

/*
* ftw_fn() - Check file attributes and ioctl call to avoid.
* illegal operations.
*
* @file: the file's name.
* @buf: the pointer of the struct stat64.
* @flag: file type.
* @ftwbuf: the pointer of a struct FTW.
*/
int ftw_fn(const char *file, const struct stat64 *buf, int flag,
struct FTW *ftwbuf)

{
	int	fd;
	int	blocksize;
	int	percent = 0;
	int	defrag_fail = 0;
	int	defraged_size = 0;
	int	ret = RETURN_NG;
	long long	file_frags_start, file_frags_end;
	unsigned long	page_num;
	unsigned char	*vec = NULL;
	loff_t	start = 0;
	struct ext4_ext_defrag_data	df_data;

	defraged_file_count++;

	if (detail_flag) {
		printf("[%lu/%lu]", defraged_file_count, total_count);
		fflush(stdout);
	}

	if (lost_found_dir[0] != '\0' &&
	    !memcmp(file, lost_found_dir, strnlen(lost_found_dir, PATH_MAX))) {
		if (detail_flag) {
			PRINT_FILE_NAME(file);
			IN_FTW_PRINT_ERR_MSG(NGMSG_LOST_FOUND);
		}
		return FTW_CONT;
	}

	if (flag != FTW_F) {
		if (detail_flag) {
			PRINT_FILE_NAME(file);
			IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_UNREG);
		}
		return FTW_CONT;
	}

	fd = open64(file, O_RDONLY);
	if (fd < 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_OPEN);
		}
		return FTW_CONT;
	}

	if (file_check(fd, buf, file) == RETURN_NG)
		goto out;

	if (fsync(fd) < 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_SYNC);
		}
		goto out;
	}
	/* Get blocksize */
	if (ioctl(fd, FIGETBSZ, &blocksize) < 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_BLOCKSIZE);
		}
		goto out;
	}
	/* Ioctl call does the actual defragment job */
	df_data.start_offset = 0;
	df_data.goal = goal;
	df_data.ext.len = 0;
	df_data.flag = force_flag;

	/* Count file fragments before defrag */
	if (detail_flag) {
		file_frags_start = file_frag_count(fd);
		if (file_frags_start == RETURN_NG) {
			PRINT_FILE_NAME(file);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_INFO);
			goto out;
		}

		if (file_frags_start != 1)
			frag_files_before_defrag++;

		extents_before_defrag += file_frags_start;
	}

	/* Print process progress */
	percent = (start * 100) / buf->st_size;
	printf("\033[79;0H\033[K[%lu/%lu]%s:\t%3d%%",
		defraged_file_count, total_count, file, min(percent, 100));
	fflush(stdout);

	while (1) {
		df_data.defrag_size = (min((buf->st_size - start),
			DEFRAG_SIZE) + blocksize - 1) / blocksize;
		ret = page_in_core(fd, df_data, &vec, &page_num);
		if (ret == RETURN_NG) {
			if (detail_flag) {
				printf("\n");
				PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_MAP);
			} else {
				printf("\t[ NG ]\n");
			}
			defrag_fail = 1;
			break;
		}

		/* EXT4_IOC_DEFRAG */
		defraged_size = ioctl(fd, EXT4_IOC_DEFRAG, &df_data);

		/* Free pages */
		ret = defrag_fadvise(fd, df_data, vec, page_num);
		if (vec) {
			free(vec);
			vec = NULL;
		}
		if (ret == RETURN_NG) {
			if (detail_flag) {
				printf("\n");
				PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_DROP_BUFFER
					);
			} else {
				printf("\t[ NG ]\n");
			}
			defrag_fail = 1;
			break;
		}

		/* Go into force defrag mode */
		if (defraged_size < 0 && force_flag == DEFRAG_FORCE_TRY
					&& errno == ENOSPC) {
			/* File size is larger than max size of
			 * force defrag mode
			 */
			if (buf->st_size > MAX_FILE_SIZE) {
				if (detail_flag) {
					fprintf(stderr, "\n\t");
					fprintf(stderr, NGMSG_FORCE_DEFRAG_SIZE,
						(MAX_FILE_SIZE) / (1024 * 1024)
						);
					fprintf(stderr, "\t[ NG ]\n");
				} else {
					printf("\t[ NG ]\n");
				}
				defrag_fail = 1;
				break;
			}

			printf("\033[79;0H\033[K");
			defraged_size = force_defrag(fd, file, buf, blocksize);
			if (defraged_size * blocksize >= buf->st_size)
				/* Whole file is enforcedly defraged */
				break;
			else {
				if (!detail_flag) {
					printf("\033[79;0H\033[K[%lu/%lu]%s\t"
						"0%%\t[ NG ]\n",
						defraged_file_count,
						total_count, file);
				}
				defrag_fail = 1;
				break;
			}
		}
		if (defraged_size < 0) {
			if (detail_flag) {
				printf("\n");
				PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_DEFRAG);
			} else {
				printf("\t[ NG ]\n");
			}
			defrag_fail = 1;
			break;
		}
		df_data.start_offset += defraged_size;
		start = (long long)df_data.start_offset * blocksize;

		/* Print process progress */
		percent = (start * 100) / buf->st_size;

		/* Disk space file used is bigger than logical size */
		printf("\033[79;0H\033[K[%lu/%lu]%s:\t%3d%%",
			defraged_file_count, total_count, file,
				min(percent, 100));
		fflush(stdout);

		/* End of file */
		if (start >= buf->st_size)
			break;
	}

	/* Count file fragments after defrag and print extents info */
	if (detail_flag) {
		file_frags_end = file_frag_count(fd);
		if (file_frags_end == RETURN_NG) {
			printf("\n");
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_INFO);
			goto out;
		}

		if (file_frags_end != 1)
			frag_files_after_defrag++;

		extents_after_defrag += file_frags_end;

		if (defrag_fail)
			goto out;

		printf("  extents: %lld -> %lld",
			file_frags_start, file_frags_end);
		fflush(stdout);
	}

	if (defrag_fail)
		goto out;

	printf("\t[ OK ]\n");
	fflush(stdout);
	succeed_cnt++;

out:

	close(fd);
	return FTW_CONT;
}

/*
* file_check() - Check file's attributes.
*
* @fd: the file's descriptor.
* @buf: a pointer of the struct stat64.
* @file_name: the file's name.
*/
int file_check(int fd, const struct stat64 *buf, const char *file_name)

{
	struct flock	lock;

	/* Write-lock check is more reliable */
	lock.l_type = F_WRLCK;
	lock.l_start = 0;
	lock.l_whence = SEEK_SET;
	lock.l_len = 0;

	/* Regular file */
	if (S_ISREG(buf->st_mode) == 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_UNREG);
		}
		return RETURN_NG;
	}

	/* Free space */
	if (check_free_size(fd, file_name, buf) == RETURN_NG)
		return RETURN_NG;

	/* Priority */
	if (getuid() != ROOT_UID &&
		buf->st_uid != getuid()) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_PRIORITY);
		}
		return RETURN_NG;
	}

	/* Lock status */
	if (fcntl(fd, F_GETLK, &lock) < 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_GET_LCKINFO);
		}
		return RETURN_NG;
	} else if (lock.l_type != F_UNLCK) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_LOCK);
		}
		return RETURN_NG;
	}

	/* Empty file */
	if (buf->st_size == 0) {
		if (detail_flag) {
			PRINT_FILE_NAME(file_name);
			IN_FTW_PRINT_ERR_MSG(NGMSG_FILE_BLANK);
		}
		return RETURN_NG;
	}

	return RETURN_OK;
}

/*
* is_ext4() - Whether on an ext4 filesystem.
*
* @filename: the file's name.
*/
int is_ext4(const char *filename)

{
	int 	maxlen, len, ret;
	FILE	*fp = NULL;
	char	*mnt_type = NULL;
	/* Refer to /etc/mtab */
	char	*mtab = MOUNTED;
	char	file_path[PATH_MAX + 1];
	struct mntent	*mnt = NULL;
	struct statfs	buffs;

	/* Get full path */
	if (realpath(filename, file_path) == NULL) {
		perror(NGMSG_REALPATH);
		PRINT_FILE_NAME(filename);
		return RETURN_NG;
	}

	if (statfs(file_path, &buffs) < 0) {
		perror(NGMSG_FS_INFO);
		PRINT_FILE_NAME(filename);
		return RETURN_NG;
	}

	if (buffs.f_type != EXT4_SUPER_MAGIC) {
		PRINT_ERR_MSG(NGMSG_EXT4);
		return RETURN_NG;
	}

	fp = setmntent(mtab, "r");
	if (fp == NULL) {
		perror(NGMSG_MTAB);
		return RETURN_NG;
	}

	maxlen = 0;
	while ((mnt = getmntent(fp)) != NULL) {
		len = strlen(mnt->mnt_dir);
		ret = memcmp(file_path, mnt->mnt_dir, len);
		if (ret != 0)
			continue;

		if (maxlen >= len)
			continue;

		maxlen = len;

		mnt_type = realloc(mnt_type, strlen(mnt->mnt_type) + 1);
		if (!mnt_type) {
			endmntent(fp);
			return RETURN_NG;
		}
		strcpy(mnt_type, mnt->mnt_type);
		strncpy(lost_found_dir, mnt->mnt_dir, PATH_MAX);
	}

	endmntent(fp);
	if (strcmp(mnt_type, FS_EXT4) == 0) {
		FREE(mnt_type);
		return RETURN_OK;
	} else {
		FREE(mnt_type);
		PRINT_ERR_MSG(NGMSG_EXT4);
		return RETURN_NG;
	}
}

/*
* calc_entry_counts - calculate file counts
*
* @file: file name.
* @buf: file info.
* @flag: file type.
* @ftwbuf: the pointer of a struct FTW.
*/
int calc_entry_counts(const char *file, const struct stat64 *buf, int flag,
struct FTW *ftwbuf)
{

	if (flag == FTW_F)
		regular_count++;

	total_count++;

	return FTW_CONT;
}

/*
* get_mount_point() - Get device's mount point.
*
* @devname: the device's name.
* @mount_point: the mount point.
* @dir_path_len: the length of directory.
*/
int get_mount_point(const char *devname, char *mount_point,
int dir_path_len)

{
	/* Refer to /etc/mtab */
	char	*mtab = MOUNTED;
	FILE	*fp = NULL;
	struct mntent	*mnt = NULL;

	fp = setmntent(mtab, "r");
	if (fp == NULL) {
		perror(NGMSG_MTAB);
		return RETURN_NG;
	}

	while ((mnt = getmntent(fp)) != NULL) {
		if (strcmp(devname, mnt->mnt_fsname) != 0)
			continue;

		endmntent(fp);
		if (strcmp(mnt->mnt_type, FS_EXT4) == 0) {
			strncpy(mount_point, mnt->mnt_dir,
				dir_path_len);
			return RETURN_OK;
		}
		PRINT_ERR_MSG(NGMSG_EXT4);
		return RETURN_NG;
	}
	endmntent(fp);
	PRINT_ERR_MSG(NGMSG_UNMOUNT);
	return RETURN_NG;
}

/*
* main() - ext4 online defrag.
*
* @argc: the number of parameter.
* @argv[]: the pointer array of parameter.
*/
int main(int argc, char *argv[])

{
	int	fd;
	int	opt;
	int	i, flags;
	int	arg_type;
	int	detail_tmp;
	int	success_flag;
	char	dir_name[PATH_MAX + 1];
	struct stat64	buf;

	i = 1;
	flags = 0;
	arg_type = -1;
	detail_tmp = -1;
	success_flag = 0;
	/* Do not follow symlink */
	flags |= FTW_PHYS;
	/* Stay within the same filesystem */
	flags |= FTW_MOUNT;

	/* Parse arguments */
	if (argc == 1 || (argc == 2 && argv[1][0] == '-'))
		goto out;

	while ((opt = getopt(argc, argv, "rvf")) != EOF) {
		switch (opt) {
		case 'r':
			regional_flag = 1;
			i = 2;
			break;
		case 'v':
			detail_flag = 1;
			i = 2;
			break;
		case 'f':
			force_flag = DEFRAG_FORCE_TRY;
			i = 2;

			if (argc == 3)
				break;

			if (argc > 4) {
				printf("Illegal argument\n");
				goto out;
			}

			/* Now the agrc must be 4(e4defrag -f file blocknr) */
			int res_strlen;
			res_strlen = strlen(argv[3]);
			for (res_strlen -= 1; res_strlen >= 0; res_strlen--) {
				if (!isdigit(argv[3][res_strlen])) {
					printf("Illegal argument\n");
					goto out;
				}
			}

			fgoal = strtoul(argv[3], NULL, 0);
			if (errno) {
				printf("block num shold be < 4294967295"
					"(max num of unsigned long 32bit)\n");
				exit(1);
			}
			if (!fgoal)
				fgoal = -1;
			break;
		default:
			goto out;
		}
	}

	/* Main process */
	for (; i < argc; i++) {
		succeed_cnt = 0;
		regular_count = 0;
		total_count = 0;
		frag_files_before_defrag = 0;
		frag_files_after_defrag = 0;
		extents_before_defrag = 0;
		extents_after_defrag = 0;
		defraged_file_count = 0;

		memset(dir_name, 0, PATH_MAX + 1);
		memset(lost_found_dir, 0, PATH_MAX + 1);

		if (force_flag && i == 3)
			break;

		#if BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN
			PRINT_ERR_MSG(NGMSG_ENDIAN);
			PRINT_FILE_NAME(argv[i]);
			continue;
		#endif

		if (lstat64(argv[i], &buf) < 0) {
			perror(NGMSG_FILE_INFO);
			PRINT_FILE_NAME(argv[i]);
			continue;
		}

		/* Only regular file is acceptalbe with force defrag mode */
		if (force_flag && !S_ISREG(buf.st_mode)) {
			printf("Inappropriate file type\n");
			goto out;
		}

		if (S_ISBLK(buf.st_mode)) {
			/* Block device */
			if (get_mount_point(argv[i], dir_name, PATH_MAX)
							== RETURN_NG)
				continue;
			arg_type = DEVNAME;
			printf("ext4 defragmentation for device(%s)\n",
				argv[i]);
		} else if (S_ISDIR(buf.st_mode)) {
			/* Directory */
			if (access(argv[i], R_OK) < 0) {
				perror(argv[i]);
				continue;
			}
			arg_type = DIRNAME;
			strcpy(dir_name, argv[i]);
		} else if (S_ISREG(buf.st_mode)) {
			/* Regular file */
			arg_type = FILENAME;
		} else {
			/* Irregular file */
			PRINT_ERR_MSG(NGMSG_FILE_UNREG);
			PRINT_FILE_NAME(argv[i]);
			continue;
		}

		/* For device case,
		 * filesystem type checked in get_mount_point()
		 */
		if (arg_type == FILENAME || arg_type == DIRNAME) {
			if (is_ext4(argv[i]) == RETURN_NG)
				continue;
			if (realpath(argv[i], dir_name) == NULL) {
				perror(NGMSG_REALPATH);
				PRINT_FILE_NAME(argv[i]);
				continue;
			}
		}

		switch (arg_type) {
		case DIRNAME:
			printf("ext4 defragmentation for directory(%s)\n",
				argv[i]);

			int mount_dir_len = 0;
			mount_dir_len = strnlen(lost_found_dir, PATH_MAX);

			strncat(lost_found_dir, "/lost+found",
				PATH_MAX - strnlen(lost_found_dir, PATH_MAX));

			/* Not the case("e4defrag  mount_piont_dir") */
			if (dir_name[mount_dir_len] != '\0') {
				/* "e4defrag mount_piont_dir/lost+found"
				 * or "e4defrag mount_piont_dir/lost+found/"
				 */
				if (strncmp(lost_found_dir, dir_name,
					    strnlen(lost_found_dir,
						    PATH_MAX)) == 0 &&
				    (dir_name[strnlen(lost_found_dir,
						      PATH_MAX)] == '\0' ||
				     dir_name[strnlen(lost_found_dir,
						      PATH_MAX)] == '/')) {
					PRINT_ERR_MSG(NGMSG_LOST_FOUND);
					PRINT_FILE_NAME(argv[i]);
					continue;
				}

				/* "e4defrag mount_piont_dir/else_dir" */
				memset(lost_found_dir, 0, PATH_MAX + 1);
			}
		case DEVNAME:
			if (arg_type == DEVNAME) {
				strncpy(lost_found_dir, dir_name,
					strnlen(dir_name, PATH_MAX));
				strncat(lost_found_dir, "/lost+found/",
					PATH_MAX - strnlen(lost_found_dir,
							   PATH_MAX));
			}

			/* Regional block allocation */
			if (regional_flag) {
				unsigned int bg_num = 0;
				int ret = 0;
				struct ext4_super_block sb;

				printf(MSG_R_OPTION);

				fd = open64(dir_name, O_RDONLY);
				if (fd < 0) {
					if (detail_flag) {
						perror(NGMSG_FILE_OPEN);
						PRINT_FILE_NAME(dir_name);
					}
					continue;
				}

				memset(&sb, 0, sizeof(struct ext4_super_block));

				ret = ioctl(fd, EXT4_IOC_SUPER_INFO, &sb);
				if (ret < 0) {
					perror(NGMSG_FILE_SUPER);
					PRINT_FILE_NAME(dir_name);
					close(fd);
					continue;
				}
				close(fd);

				sb.s_blocks_per_group =
					ext2fs_swab32(sb.s_blocks_per_group);
				sb.s_inodes_per_group =
					ext2fs_swab32(sb.s_inodes_per_group);
				sb.s_first_data_block =
					ext2fs_swab32(sb.s_first_data_block);

				bg_num = (buf.st_ino - 1) /
							sb.s_inodes_per_group;
				goal = bg_num * sb.s_blocks_per_group +
							sb.s_first_data_block;
			}
			nftw64(dir_name, calc_entry_counts, FTW_OPEN_FD, flags);

			/* File tree walk */
			nftw64(dir_name, ftw_fn, FTW_OPEN_FD, flags);
			printf("\n\tSuccess:\t\t\t[ %lu/%lu ]\n", succeed_cnt,
				total_count);
			printf("\tFailure:\t\t\t[ %lu/%lu ]\n",
				total_count - succeed_cnt, total_count);
			if (detail_flag) {
				printf("\tTotal extents:\t\t\t%4d->%d\n",
					extents_before_defrag,
					extents_after_defrag);
				printf("\tFragmented percentage:\t\t"
					"%3llu%%->%llu%%\n",
					!regular_count ? 0 :
					((unsigned long long)
					frag_files_before_defrag * 100) /
					regular_count,
					!regular_count ? 0 :
					((unsigned long long)
					frag_files_after_defrag * 100) /
					regular_count);
			}
			break;
		case FILENAME:
			total_count = 1;
			strncat(lost_found_dir, "/lost+found/",
				PATH_MAX - strnlen(lost_found_dir,
						   PATH_MAX));
			if (strncmp(lost_found_dir, dir_name,
				    strnlen(lost_found_dir,
					    PATH_MAX)) == 0) {
				PRINT_ERR_MSG(NGMSG_LOST_FOUND);
				PRINT_FILE_NAME(argv[i]);
				continue;
			}

			if (regional_flag) {
				fprintf(stderr, NGMSG_TYPE, argv[i]);
				continue;
			}
			printf("ext4 defragmentation for %s\n", argv[i]);
			/* Defrag single file process */
			ftw_fn(argv[i], &buf, FTW_F, NULL);
			if (succeed_cnt != 0)
				printf(" Success:\t\t\t[1/1]\n");
			else
				printf(" Success:\t\t\t[0/1]\n");

			break;
		}

		if (succeed_cnt != 0)
			success_flag = 1;
	}

	if (success_flag)
		return RETURN_OK;

	exit(1);

out:

	printf(MSG_USAGE);
	exit(1);
}

/*
* insert_extent() - Sequentially insert extent by physical block number.
*
* @extlist_head: the head of an extent list.
* @ext: the extent element which will be inserted.
*/
int insert_extent(extent_t **extlist_head, extent_t *ext)

{
	extent_t	*ext_tmp = *extlist_head;

	if (ext == NULL)
		return RETURN_NG;

	/* First element */
	if (*extlist_head == NULL) {
		(*extlist_head) = ext;
		(*extlist_head)->prev = *extlist_head;
		(*extlist_head)->next = *extlist_head;
		return RETURN_OK;
	}

	if (ext->data.start <= ext_tmp->data.start) {
		/* Insert before head */
		if (ext_tmp->data.start < ext->data.start + ext->data.len)
			/* Overlap */
			return RETURN_NG;
		/* Adjust head */
		*extlist_head = ext;
	} else {
		/* Insert into the middle or last of the list */
		do {
			if (ext->data.start < ext_tmp->data.start)
				break;
			ext_tmp = ext_tmp->next;
		} while (ext_tmp != (*extlist_head));
		if (ext->data.start <
		    ext_tmp->prev->data.start + ext_tmp->prev->data.len)
			/* Overlap */
			return RETURN_NG;

		if (ext_tmp != *extlist_head &&
		    ext_tmp->data.start < ext->data.start + ext->data.len)
			/* Overlap */
			return RETURN_NG;
	}
	ext_tmp = ext_tmp->prev;
	/* Insert "ext" after "ext_tmp" */
	insert(ext_tmp, ext);
	return RETURN_OK;
}

/*
* insert_exts_group() - Insert a exts_group in decreasing order
* of length.
*
* @exts_group_list_head: the head of a exts_group list.
* @exts_group: the exts_group element which will be inserted.
*/
int insert_exts_group(exts_group_t **exts_group_list_head,
exts_group_t *exts_group)

{
	exts_group_t	*exts_group_tmp = NULL;

	if (exts_group == NULL)
		return RETURN_NG;

	/* Initialize list */
	if (*exts_group_list_head == NULL) {
		(*exts_group_list_head) = exts_group;
		(*exts_group_list_head)->prev = *exts_group_list_head;
		(*exts_group_list_head)->next = *exts_group_list_head;
		return RETURN_OK;
	}

	if (exts_group->len >= (*exts_group_list_head)->len) {
		/* Insert before exts_group_list_head */
		exts_group_tmp = (*exts_group_list_head)->prev;
		insert(exts_group_tmp, exts_group);
		*exts_group_list_head = exts_group;
		return RETURN_OK;
	}

	/* Find insertion positon */
	for (exts_group_tmp = (*exts_group_list_head)->next;
	     exts_group_tmp != *exts_group_list_head;
	     exts_group_tmp = exts_group_tmp->next) {
		if (exts_group_tmp->len < exts_group->len)
			break;
	}
	exts_group_tmp = exts_group_tmp->prev;
	insert(exts_group_tmp, exts_group);

	return RETURN_OK;
}

/*
* get_exts_group() - Get element from the exts_group list.
*
* @exts_group_list_head: the head of a exts_group list.
* @exts_group: the exts_group element which will be got.
*/
exts_group_t *get_exts_group(exts_group_t **exts_group_list_head,
exts_group_t *exts_group)

{
	if (exts_group == NULL || *exts_group_list_head == NULL)
		return NULL;

	/* Keep "exts_group_list_head" point to the largest extent group */
	if (exts_group == *exts_group_list_head)
		*exts_group_list_head = exts_group->next;

	if (*exts_group_list_head == (*exts_group_list_head)->next &&
	    exts_group == *exts_group_list_head)
		/* Delete the last element in the list */
		*exts_group_list_head = NULL;

	exts_group->prev->next = exts_group->next;
	exts_group->next->prev = exts_group->prev;
	return exts_group;
}

/*
* free_exts_group() - Free the exts_group.
*
* @*exts_group_list_head: the exts_group list head which will be free.
*/
void free_exts_group(exts_group_t *exts_group_list_head)

{
	exts_group_t *exts_group_tmp = NULL;

	if (exts_group_list_head == NULL)
		return;

	while (exts_group_list_head->next != exts_group_list_head) {
		exts_group_tmp = exts_group_list_head;
		exts_group_list_head->prev->next = exts_group_list_head->next;
		exts_group_list_head->next->prev = exts_group_list_head->prev;
		exts_group_list_head = exts_group_list_head->next;
		free(exts_group_tmp);
	}
	free(exts_group_list_head);
}

/*
* free_ext() - Free the extent list.
*
* @extent_list_head: the extent list head of which will be free.
*/
void free_ext(extent_t *extent_list_head)

{
	extent_t *extent_tmp = NULL;

	if (extent_list_head == NULL)
		return;

	while (extent_list_head->next != extent_list_head) {
		extent_tmp = extent_list_head;
		extent_list_head->prev->next = extent_list_head->next;
		extent_list_head->next->prev = extent_list_head->prev;
		extent_list_head = extent_list_head->next;
		free(extent_tmp);
	}
	free(extent_list_head);
}

/*
* do_defrag() - Execute the defrag program in force mode.
*
* @fd: the file's descriptor.
* @exts_group: the exts_group which will be defraged.
* @defrag_data: the data which will be defraged.
*/
int do_defrag(int fd, exts_group_t *exts_group,
struct ext4_ext_defrag_data defrag_data)

{
	int defraged_size = 0;
	int fadvise_ret = 0;
	unsigned long page_num;
	unsigned char *vec = NULL;

	/* Init defrag_data for defrag */
	defrag_data.ext.start = exts_group->start->data.start;
	defrag_data.ext.len = exts_group->len;
	defrag_data.ext.block = 0;
	defrag_data.defrag_size = exts_group->len;
	/* Move victim extents to make sufficient space */
	defrag_data.flag = DEFRAG_FORCE_GATHER;
	defrag_data.goal = exts_group->start->data.start;

	if (page_in_core(fd, defrag_data, &vec, &page_num) == RETURN_NG)
		return RETURN_NG;

	defraged_size = ioctl(fd, EXT4_IOC_DEFRAG, &defrag_data);

	/* Free pages */
	fadvise_ret = defrag_fadvise(fd, defrag_data, vec, page_num);
	FREE(vec);

	if (fadvise_ret == RETURN_NG || defraged_size < 0)
		return RETURN_NG;

	return defraged_size;
}

/*
* get_used_extent() - Get used extent in the block group.
*
* @fd: the file's descriptor.
* @ext_list_head: the head of the extent list.
* @istart: start inode in the block group.
* @iend: end inode in the block group.
* @bstart: start block in the block group.
* @bend: end block in the block group.
*/
int get_used_extent(int fd, extent_t **ext_list_head,
unsigned long long istart, unsigned long long iend, ext4_fsblk_t bstart, ext4_fsblk_t bend)

{
	int	ret = 0;
	int	blocksize;
	int	extents_buffer_size, fiemap_ino_size;
	__u64	pos = 0;
	__u64   group_start, group_end;
	unsigned long long	ino;
	struct	fiemap_extent *extents_buf = NULL;
	struct	fiemap_ino *fiemap_ino_buf = NULL;

	ret = ioctl(fd, FIGETBSZ, &blocksize);
	if (ret < 0) {
		if (detail_flag)
			perror(NGMSG_FILE_BLOCKSIZE);

		return RETURN_NG;
	}

	/* Convert units, in bytes.
	 * Be careful : now, physical block number in extent is 48bit,
	 * and the maximum blocksize for ext4 is 4K(12bit),
	 * so there is no overflow, but in future it may be changed.
	 */
	group_start = bstart * blocksize;
	group_end = bend * blocksize;

	/* Alloc space for fiemap_ino */
	fiemap_ino_size = sizeof(struct fiemap_ino);
	extents_buffer_size = EXTENT_MAX_COUNT * sizeof(struct fiemap_extent);

	fiemap_ino_buf = malloc(fiemap_ino_size + extents_buffer_size);
	if (!fiemap_ino_buf)
		return RETURN_NG;

	extents_buf = fiemap_ino_buf->fiemap.fm_extents;
	memset(fiemap_ino_buf, 0, fiemap_ino_size);
	fiemap_ino_buf->fiemap.fm_length = FIEMAP_MAX_OFFSET;
	fiemap_ino_buf->fiemap.fm_flags |= FIEMAP_FLAG_SYNC;
	fiemap_ino_buf->fiemap.fm_extent_count = EXTENT_MAX_COUNT;

	for (ino = istart; ino <= iend; ino++) {
		fiemap_ino_buf->ino = ino;
		pos = 0;
		do {
			int i;
			fiemap_ino_buf->fiemap.fm_start = pos;
			memset(extents_buf, 0, extents_buffer_size);

			ret = ioctl(fd, EXT4_IOC_FIEMAP_INO, fiemap_ino_buf);
			if (ret < 0) {
				/* Skip non-regular file and unused inode */
				if (errno == EOPNOTSUPP || errno == ESTALE
					|| errno == ENOENT || errno == EACCES)
					continue;
				goto out;
			}

			for (i = 0; i < fiemap_ino_buf->
					fiemap.fm_mapped_extents; i++) {
				extent_t        *extent = NULL;

				/* Is this extent in current block group ? */
				if (extents_buf[i].fe_physical < group_start ||
					extents_buf[i].fe_physical > group_end)
					continue;

				extent = malloc(sizeof(extent_t));
				if (extent == NULL)
					goto out;

				extent->data.start = extents_buf[i].fe_physical
							/ blocksize;
				extent->data.block = extents_buf[i].fe_logical
							/ blocksize;
				extent->data.len = extents_buf[i].fe_length
							/ blocksize;
				extent->ino = ino;
				extent->tag = EXT4_EXT_USE;

				if (insert_extent(ext_list_head, extent) < 0) {
					FREE(extent);
					goto out;
				}
			}

			/* Record file's logical offset this time */
			pos = extents_buf[EXTENT_MAX_COUNT-1].fe_logical +
				extents_buf[EXTENT_MAX_COUNT-1].fe_length;
		/* If fm_extents array has been filled and
		 * there are extents left, continue to cycle.
		 */
		} while (fiemap_ino_buf->fiemap.fm_mapped_extents
						== EXTENT_MAX_COUNT &&
			!(extents_buf[EXTENT_MAX_COUNT-1].fe_flags
						& FIEMAP_EXTENT_LAST));
	}

	FREE(fiemap_ino_buf);
	return RETURN_OK;
out:
	FREE(fiemap_ino_buf);
	return RETURN_NG;
}

/*
* get_free_extent() - Get used extent in the block group.
*
* @fd: the file's descriptor.
* @ino: inode number from struct stat64.
* @blocks_per_group: the block number of each block group.
* @ext_list_head: the head of the extent list.
*/
int get_free_extent(int fd, unsigned long long ino,
int blocks_per_group, extent_t **ext_list_head)

{
	ext4_grpblk_t	pos = 0;
	struct ext4_extents_info	extents_info;

	memset(&extents_info, 0, sizeof(struct ext4_extents_info));
	extents_info.ino = ino;
	extents_info.max_entries = DEFRAG_MAX_ENT;
	while (pos < blocks_per_group) {
		int	i;
		if (ioctl(fd, EXT4_IOC_FREE_BLOCKS_INFO, &extents_info) < 0)
			return RETURN_NG;

		for (i = 0;
		     extents_info.ext[i].len != 0 && i < DEFRAG_MAX_ENT; i++) {
			/* Alloc space for extent */
			extent_t	*extent = NULL;

			extent = malloc(sizeof(extent_t));
			if (extent == NULL)
				return RETURN_NG;
			memset(extent, 0, sizeof(extent_t));
			memcpy(&(extent->data), &(extents_info.ext[i]),
			       sizeof(struct ext4_extent_data));
			/* Free extent */
			extent->tag = EXT4_EXT_FREE;
			if (insert_extent(ext_list_head, extent) < 0) {
				FREE(extent);
				return RETURN_NG;
			}
		}

		/* No free extent after the logical block number "pos".
		 * In other word, offset this time equals to prev recursion.
		 */
		if (pos == extents_info.g_offset)
			break;
		if (i < DEFRAG_MAX_ENT)
			break;
		/* Record the offset of logical block number this time */
		pos = extents_info.g_offset;
		memset(extents_info.ext, 0,
		       sizeof(struct ext4_extent_data) * DEFRAG_MAX_ENT);
	}

	return RETURN_OK;
}

/*
* join_extents() - Find continuous region(exts_group).
*
* @ext_list_head: the head of the extent list.
* @target_exts_group_list_head:the head of the target exts_group list.
* @exts_group_list_head: the head of the original exts_group list.
* @filesize: the file's descriptor.
* @max: the max size of free space.
*/
int join_extents(extent_t *ext_list_head,
exts_group_t **target_exts_group_list_head, exts_group_t **exts_group_list_head, unsigned long filesize, int *max)

{
	int len;
	extent_t *ext_start, *extent_tmp;

	ext_start = ext_list_head;
	extent_tmp = ext_list_head;
	*max = 0;
	len = ext_list_head->data.len;
	extent_tmp = extent_tmp->next;
	do {
		exts_group_t    *exts_group_tmp = NULL;

		if (len >= filesize) {
			/* Hit on the way,
			 * one extent group is enough for defrag, so return.
			 */
			exts_group_tmp = malloc(sizeof(exts_group_t));
			if (!exts_group_tmp)
				return RETURN_NG;

			exts_group_tmp->prev = NULL;
			exts_group_tmp->next = NULL;
			exts_group_tmp->start = ext_start;
			exts_group_tmp->end = extent_tmp->prev;
			exts_group_tmp->len = len;
			if (insert_exts_group(target_exts_group_list_head,
					      exts_group_tmp) < 0) {
				FREE(exts_group_tmp);
				return RETURN_NG;
			}
			return CHECK_FRAG_COUNT;
		}

		/* This extent and previous extent are not continuous,
		 * so, all previous extents are treated as an extent group.
		 */
		if ((extent_tmp->prev->data.start + extent_tmp->prev->data.len)
					    != extent_tmp->data.start) {
			exts_group_tmp = malloc(sizeof(exts_group_t));
			if (exts_group_tmp == NULL)
				return RETURN_NG;

			memset(exts_group_tmp, 0, sizeof(exts_group_t));
			exts_group_tmp->len = len;
			exts_group_tmp->start = ext_start;
			exts_group_tmp->end = extent_tmp->prev;

			if (insert_exts_group(exts_group_list_head,
					      exts_group_tmp) < 0) {
				FREE(exts_group_tmp);
				return RETURN_NG;
			}
			*max += len;
			ext_start = extent_tmp;
			len = extent_tmp->data.len;
			extent_tmp = extent_tmp->next;
			continue;
		}

		/* This extent and previous extent are continuous,
		 * so, they belong to the same extent group, and we check
		 * if the next extent belongs to the same extent group.
		 */
		len += extent_tmp->data.len;
		extent_tmp = extent_tmp->next;
	} while (extent_tmp != ext_list_head->next);

	return RETURN_OK;
}

/*
* find_exts_group() - Find target exts_group.
*
* @ext_count: the number of extents.
* @filesize: the file's size.
* @exts_group_list_head: the head of the original exts_group list
* @target_exts_group_list_head: the head of the target exts_group list.
*/
int find_exts_group(int *ext_count, unsigned long filesize,
exts_group_t **exts_group_list_head, exts_group_t **target_exts_group_list_head)

{
	/* Blocks we found for target file */
	int len = 0;

	if (!(*exts_group_list_head))
		return RETURN_NG;

	while (*exts_group_list_head) {
		exts_group_t	*exts_group_tmp = NULL;

		/* Even add the current largest extent group,
		 * the sapce we found is still not enough for the file,
		 * so just add the current largest extent group,
		 * and do the next loop.
		 */
		if ((*exts_group_list_head)->len + len < filesize) {
			len += (*exts_group_list_head)->len;
			exts_group_tmp = get_exts_group(exts_group_list_head,
				*exts_group_list_head);
			if (insert_exts_group(target_exts_group_list_head,
				      exts_group_tmp) < 0) {
				FREE(exts_group_tmp);
				return RETURN_NG;
			}
			(*ext_count)++;
			continue;
		}

		/* Now, if add the current largest extent group,
		 * the space we found is enough,
		 * So, search from the smallest extent group
		 * to avoid waste of space
		 */
		exts_group_tmp = (*exts_group_list_head)->prev;
		do {
			if (exts_group_tmp->len + len >= filesize) {
				len += exts_group_tmp->len;
				exts_group_tmp =
				get_exts_group(exts_group_list_head,
					       exts_group_tmp);
				if (insert_exts_group
					(target_exts_group_list_head,
					 exts_group_tmp) < 0) {
					FREE(exts_group_tmp);
					return RETURN_NG;
				}
				(*ext_count)++;
				/* The only entry go out normally */
				return RETURN_OK;
			}
			exts_group_tmp = exts_group_tmp->prev;
		} while (exts_group_tmp !=
			 (*exts_group_list_head)->prev);

		/* If we come here, there must be something wrong */
		return RETURN_NG;
	}

	/* If we come here, there must be something wrong(space not enough) */
	return RETURN_NG;
}

/*
* check_frag_count() - Check file frag.
*
* @fd: the file's discriptor.
* @extent_count: the number of extents.
*/
int check_frag_count(int fd, int extent_count)

{
	long long file_extent_count = RETURN_NG;

	file_extent_count = file_frag_count(fd);
	if (file_extent_count == RETURN_NG)
		return RETURN_NG;

	if (extent_count >= file_extent_count) {
		/* No improvment */
		errno = ENOSPC;
		return RETURN_NG;
	}

	return RETURN_OK;
}

/*
* defrag_proc() - Reserve extent group and execute
* the defrag program.
*
* @fd: the file's discriptor.
* @file_name file name.
* @target_exts_group_head: the head of the original exts_group list.
* @ino: inode number from struct stat64.
*/
int defrag_proc(int fd, const char *file_name,
exts_group_t *target_exts_group_head, unsigned long long ino)

{
	int ret = 0;
	int flag = 0;
	int count = 0;
	int percent = 0;
	int blocksize = 0;
	struct stat64	buf;
	exts_group_t 			*exts_group = NULL;
	extent_t			*extent = NULL;
	struct ext4_extents_info	extents_info;
	struct ext4_ext_defrag_data	defrag_data;

	if (!target_exts_group_head)
		return RETURN_NG;

	/* Get file size */
	memset(&buf, 0, sizeof(struct stat64));
	ret = fstat64(fd, &buf);
	if (ret < 0) {
		if (detail_flag) {
			printf("\033[79;0H\033[K[%lu/%lu]%s\n",
					defraged_file_count,
					total_count, file_name);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_INFO);
		}
		return RETURN_NG;
	}

	/* Get block size */
	ret = ioctl(fd, FIGETBSZ, &blocksize);
	if (ret < 0) {
		if (detail_flag) {
			printf("\033[79;0H\033[K[%lu/%lu]%s\n",
					defraged_file_count,
					total_count, file_name);
			PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_BLOCKSIZE);
		}
		return RETURN_NG;
	}

	memset(&extents_info, 0, sizeof(extents_info));
	memset(&defrag_data, 0, sizeof(struct ext4_ext_defrag_data));
	extents_info.ino = 0;
	exts_group = target_exts_group_head;
	extents_info.max_entries = DEFRAG_MAX_ENT;
	extents_info.ino = ino;
	defrag_data.start_offset = 0;

	do {
		/* Move victim extents to make sufficient space */
		extent = exts_group->start;
		int flush_count = 0;
		do {
			if (extent->tag != EXT4_EXT_USE) {
				extent = extent->next;
				continue;
			}
			extents_info.ino = extent->ino;
			extents_info.goal = fgoal;
			memcpy(extents_info.ext, &extent->data,
			       sizeof(struct ext4_extent_data));

			extent = extent->next;
			extents_info.entries = 1;
			ret = ioctl(fd, EXT4_IOC_MOVE_VICTIM, &extents_info);
			if (ret < 0) {
				if (errno == EACCES && detail_flag) {
					printf("\033[79;0H\033[K[%lu/%lu]%s\n",
					defraged_file_count,
					total_count, file_name);
					PRINT_ERR_MSG_WITH_ERRNO
						(NGMSG_VICTIM_UID);
				}
				return ret;
			}
			count++;
			if (detail_flag) {
				if (!flag) {
					printf(" Move victim extents to");
					printf(" the other block group\n");
					flag = 1;
				}
				/* moved extent info */
				if (!flush_count) {
					/* flush the defrag process info */
					printf("\033[79;0H\033[K ext%-3d",
						count);
					printf(" phys:\t%8llu logical:\t%8u "
						"length:\t%8d\n",
						(extents_info.ext[0]).start,
						(extents_info.ext[0]).block,
						(extents_info.ext[0]).len);
					flush_count = 1;
				} else {
					printf(" ext%-3d phys:\t%8llu logical:"
						"\t%8u length:\t%8d\n", count,
						(extents_info.ext[0]).start,
						(extents_info.ext[0]).block,
						(extents_info.ext[0]).len);
				}
			}
		} while (extent != exts_group->end->next);

		if (fsync(fd) < 0) {
			if (detail_flag) {
				printf("\033[79;0H\033[K[%lu/%lu]%s\n",
					defraged_file_count,
					total_count, file_name);
				PRINT_ERR_MSG_WITH_ERRNO(NGMSG_FILE_SYNC);
			}
			return ret;
		}

		/* Init extents_info for ioctl (RESERVE) */
		extents_info.entries = 1;
		extents_info.ext[0].block = exts_group->start->data.block;
		extents_info.ext[0].start = exts_group->start->data.start;
		extents_info.ext[0].len = exts_group->len;

		/* Defrag */
		ret = do_defrag(fd, exts_group, defrag_data);
		if (ret < 0) {
			if (detail_flag) {
				printf("\033[79;0H\033[K[%lu/%lu]%s\n",
					defraged_file_count,
					total_count, file_name);
				printf("\tDEFRAG_ERROR ret = %d\t[ NG ]\n",
					ret);
			}
			return ret;
		}
		/* Defrag success, so update offset */
		defrag_data.start_offset += ret;
		ret = defrag_data.start_offset;

		/* Print process progress */
		{
			percent = ((long long)ret * blocksize * 100) /
				  buf.st_size;
			printf("\033[79;0H\033[K[%lu/%lu]%s:\t%d%%",
				defraged_file_count, total_count,
				file_name, min(percent, 100));
			fflush(stdout);
		}

		exts_group = exts_group->next;
	} while (exts_group != target_exts_group_head);
	return ret;
}

/*
* force_defrag() - Execute the defrag program in force defrag mode.
*
* @fd: the file's descriptor.
* @file_name file name.
* @buf: a pointer of the struct stat64.
* @blocksize: block size in byte.
*/
int force_defrag(int fd, const char *file_name,
const struct stat64 *buf, int blocksize)

{
	int     ret = 0;
	int     exts = 0;
	int     maxlen = 0;
	unsigned int    gnumber;
	unsigned long   filesize;
	unsigned long long	istart, iend;
	ext4_fsblk_t	bstart, bend;
	extent_t	*extlist_head = NULL;
	exts_group_t	*exts_group_list_head, *exts_group_list_target_head;
	struct ext4_super_block sb;

	exts_group_list_head = NULL;
	exts_group_list_target_head = NULL;

	/* Get super block info */
	memset(&sb, 0, sizeof(struct ext4_super_block));

	if (ioctl(fd, EXT4_IOC_SUPER_INFO, &sb) < 0)
		return RETURN_NG;

	sb.s_blocks_per_group = ext2fs_swab32(sb.s_blocks_per_group);
	sb.s_inodes_per_group = ext2fs_swab32(sb.s_inodes_per_group);

	gnumber = (buf->st_ino - 1) / sb.s_inodes_per_group;
	istart = gnumber * sb.s_inodes_per_group;
	iend = istart + sb.s_inodes_per_group - 1;
	bstart = gnumber * sb.s_blocks_per_group;
	bend = bstart + sb.s_blocks_per_group - 1;

	/* Compute filesize in block */
	filesize = (buf->st_size + blocksize - 1) / blocksize;

	/* Get used extents in the block group */
	ret = get_used_extent(fd, &extlist_head, istart, iend, bstart, bend);
	if (ret == RETURN_NG)
		goto freelist;

	/* Get free extents in the group */
	ret = get_free_extent(fd, buf->st_ino,
			     sb.s_blocks_per_group, &extlist_head);
	if (ret == RETURN_NG)
		goto freelist;

	/* All space in this group is used by other groups' inodes */
	if (extlist_head == NULL) {
		ret = RETURN_NG;
		goto freelist;
	}

	/* Get continuous region(extents group) */
	ret = join_extents(extlist_head, &exts_group_list_target_head,
				  &exts_group_list_head, filesize, &maxlen);
	if (ret == RETURN_NG)
		goto freelist;
	if (ret == CHECK_FRAG_COUNT) {
		exts = 1;
		goto frag_check;
	}

	if (maxlen < filesize) {
		/* No enough space */
		errno = ENOSPC;
		ret = RETURN_NG;
		goto freelist;
	}

	if (!exts_group_list_head) {
		ret = RETURN_NG;
		goto freelist;
	}

	/* Find target extents group */
	ret = find_exts_group(&exts, filesize, &exts_group_list_head,
				      &exts_group_list_target_head);
	if (ret == RETURN_NG)
		goto freelist;

frag_check:

	/* Check file extent count */
	ret = check_frag_count(fd, exts);
	if (ret == RETURN_NG)
		goto freelist;

	/* Reserve extent group and execute the defrag program */
	ret = defrag_proc(fd, file_name,
			exts_group_list_target_head, buf->st_ino);

freelist:

	free_exts_group(exts_group_list_target_head);
	free_exts_group(exts_group_list_head);
	free_ext(extlist_head);
	return ret;
}