[RFC][PATCH 10/10] Online defrag command

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



- The defrag command.  Usage is as follows:
  o Put the multiple files closer together.
    # e4defrag -r directory-name
  o Defrag for free space fragmentation.
    # e4defrag -f file-name
  o Defrag for a single file.
    # e4defrag file-name
  o Defrag for all files on ext4.
    # e4defrag device-name

Signed-off-by: Takashi Sato <t-sato@xxxxxxxxxxxxx>
Signed-off-by: Akira Fujita <a-fujita@xxxxxxxxxxxxx>
---
/*
 * e4defrag.c - ext4 filesystem defragmenter
 */

#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
#endif

#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE
#endif

#define _XOPEN_SOURCE	500
#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>

#define EXT4_IOC_DEFRAG		_IOW('f', 10, struct ext4_ext_defrag_data)
#define EXT4_IOC_GROUP_INFO	_IOW('f', 11, struct ext4_group_data_info)
#define EXT4_IOC_FREE_BLOCKS_INFO _IOW('f', 12, struct ext4_extents_info)
#define EXT4_IOC_EXTENTS_INFO	_IOW('f', 13, struct ext4_extents_info)
#define EXT4_IOC_RESERVE_BLOCK	_IOW('f', 14, struct ext4_extents_info)
#define EXT4_IOC_MOVE_VICTIM	_IOW('f', 15, struct ext4_extents_info)
#define EXT4_IOC_BLOCK_RELEASE	_IO('f', 16)


#define _FILE_OFFSET_BITS 64
#define ext4_fsblk_t	unsigned long long
#define DEFRAG_MAX_ENT	32

/* Extent status which are used in ext_in_group */
#define EXT4_EXT_USE		0
#define EXT4_EXT_FREE		1
#define EXT4_EXT_RESERVE	2

/* Insert list2 after list1 */
#define insert(list1,list2) { list2 ->next = list1->next;\
						list1->next->prev = list2;\
						list2->prev = list1;\
						list1->next = list2;\
}

#define DEFRAG_RESERVE_BLOCK_SECOND	2

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

/* The number of pages to defrag at one time */
#define DEFRAG_PAGES    	128

/* Maximum length of contiguous blocks */
#define MAX_BLOCKS_LEN  	16384

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

/* Force defrag mode: Max filesystem relative offset (48bit) */
#define MAX_FS_OFFSET_BIT	48

/* Data type for filesystem-wide blocks number */
#define  ext4_fsblk_t unsigned long long

/* Ioctl command */
#define EXT4_IOC_FIBMAP		_IOW('f', 9, ext4_fsblk_t)
#define EXT4_IOC_DEFRAG		_IOW('f', 10, struct ext4_ext_defrag_data)

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

#define RETURN_OK		0
#define RETURN_NG		-1
#define FTW_CONT		0
#define FTW_STOP		-1
#define FTW_OPEN_FD		2000
#define FILE_CHK_OK		0
#define FILE_CHK_NG		-1
#define FS_EXT4			"ext4dev"
#define ROOT_UID		0

/* Defrag block size, in bytes */
#define DEFRAG_SIZE		67108864

#define min(x,y) (((x) > (y)) ? (y) : (x))

#define PRINT_ERR_MSG(msg)	fprintf(stderr, "%s\n", (msg));
#define PRINT_FILE_NAME(file)	fprintf(stderr, "\t\t    \"%s\"\n", (file));

#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		"\te4defrag  : Can not access /etc/mtab."
#define NGMSG_UNMOUNT		"\te4defrag  : FS is not mounted."
#define NGMSG_EXT4		"\te4defrag  : FS is not ext4 File System."
#define NGMSG_FS_INFO		"\te4defrag  : get FSInfo fail."
#define NGMSG_FILE_INFO		"\te4defrag  : get FileInfo fail."
#define NGMSG_FILE_OPEN		"\te4defrag  : open fail."
#define NGMSG_FILE_SYNC		"\te4defrag  : sync(fsync) fail."
#define NGMSG_FILE_DEFRAG	"\te4defrag  : defrag fail."
#define NGMSG_FILE_BLOCKSIZE	"\te4defrag  : can't get blocksize."
#define NGMSG_FILE_FIBMAP	"\te4defrag  : can't get block number."
#define NGMSG_FILE_UNREG	"\te4defrag  : File is not regular file."

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

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

#define NGMSG_FILE_LOCK		"\te4defrag  : File is locked."
#define NGMSG_FILE_BLANK	"\te4defrag  : File size is 0."
#define NGMSG_GET_LCKINFO	"\te4defrag  : get LockInfo fail."
#define NGMSG_TYPE		\
	"e4defrag  : Can not process %s in regional mode\n."
#define NGMSG_REALPATH		"\te4defrag  : Can not get full path."

struct ext4_extent_data {
	unsigned long long 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_fsblk_t start_offset;	/* start offset to defrag in blocks */
	ext4_fsblk_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;
};

struct ext4_group_data_info {
	int s_blocks_per_group;		/* blocks per group */
	int s_inodes_per_group;		/* inodes per group */
};

struct ext4_extents_info {
	unsigned long long ino;		/* inode number */
	int max_entries;		/* maximum extents count */
	int entries; 	 		/* extent number/count */
	unsigned long offset;
	ext4_fsblk_t goal;
	struct ext4_extent_data ext[DEFRAG_MAX_ENT];
};

typedef struct ext_in_group {
	struct ext_in_group *prev;
	unsigned long tag;		/* Extent status */
	unsigned long ino;		/* File`s inode number */
	struct ext4_extent_data data;	/* Extent belong to file */
	struct ext_in_group *next;
}extlist;

typedef struct big_extent {
	struct big_extent *prev;
	extlist *start;			/* Start ext */
	extlist *end;			/* End ext */
	int big_len;			/* Length of this continuous region */
	struct big_extent *next;
}bigext;

typedef struct ext_group_wrap {
	struct ext_group_wrap *prev, *next;
	struct ext_in_group *group_ext;
}ext_wrap;

int	force_flag = 0;
int	detail_flag = 0;
int	regional_flag = 0;
int	amount_cnt = 0;
int	succeed_cnt = 0;
ext4_fsblk_t	goal = 0;
ext4_fsblk_t	fgoal = -1;

/*
 * Check if there's enough disk space
 */
int check_free_size(int fd, const struct stat64 *sb)
{
	struct statfs	fsbuf;
	off64_t	file_asize = 0;
	off64_t	fsize = 0;

	/* target file size */
	fsize = sb->st_size;

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

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

	if (file_asize >= fsize) {
		return RETURN_OK;
	}

	return RETURN_NG;
}

int file_check(int fd, const struct stat64 * buf, const char * file_name);
int force_defrag(int fd, const struct stat64 *sb, int blocksize);
/*
 * Check file attributes and ioctl call to avoid illegal operations
 */
int
ftw_fn(const char *file, const struct stat64 *sb, int flag, struct FTW *ftwbuf)
{
	struct ext4_ext_defrag_data	df_data;
	loff_t	start = 0;
	int	defraged_size = 0;
	int	blocksize;
	int	fd;

	if (flag == FTW_F) {
		amount_cnt++;
		if ((fd = open64(file, O_RDONLY)) < 0) {
			if (detail_flag) {
				perror(NGMSG_FILE_OPEN);
				PRINT_FILE_NAME(file);
			}
			return FTW_CONT;
		}

		if (file_check(fd, sb, file) == FILE_CHK_NG) {
			close(fd);
			return FTW_CONT;
		}

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

		if (force_flag)
			df_data.flag = 1;
		while (1) {
			df_data.defrag_size =
					(min((sb->st_size - start),
					     DEFRAG_SIZE) + blocksize - 1) /
					blocksize;
			/* EXT4_IOC_DEFRAG */
			defraged_size = ioctl(fd, EXT4_IOC_DEFRAG, &df_data);
			if ((defraged_size < 0) && (force_flag == 1) &&
			 (errno == ENOSPC) && sb->st_size <= MAX_FILE_SIZE) {
				defraged_size = force_defrag(fd, sb, blocksize);
				if (defraged_size *blocksize >= sb->st_size) {
					/* Whole file is enforcedly defraged */
					break;
				} else {
					defraged_size = RETURN_NG;
				}
			}
			if (defraged_size < 0) {
				if (detail_flag) {
					perror(NGMSG_FILE_DEFRAG);
					PRINT_FILE_NAME(file);
				}
				close(fd);
				return FTW_CONT;
			}
			df_data.start_offset += defraged_size;
			start = df_data.start_offset * blocksize;
			/* End of file */
			if (start >= sb->st_size) {
				break;
			}
		}
		close(fd);
		succeed_cnt++;
	} else {
		if (detail_flag) {
			PRINT_ERR_MSG(NGMSG_FILE_UNREG);
			PRINT_FILE_NAME(file);
		}
	}

	return FTW_CONT;
}

/*
 * Check file's attributes
 */
int file_check(int fd, const struct stat64 *buf, const char *file_name)
{
	struct flock	lock;

	lock.l_type = F_WRLCK; /* Write-lock check is more reliable. */
	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_ERR_MSG(NGMSG_FILE_UNREG);
			PRINT_FILE_NAME(file_name);
		}
		return FILE_CHK_NG;
	}

	/* Available space */
	if (check_free_size(fd, buf) == RETURN_NG) {

		if (detail_flag) {
			PRINT_ERR_MSG(NGMSG_FILE_LARGE);
			PRINT_FILE_NAME(file_name);
		}
		return FILE_CHK_NG;
	}

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

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

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

	return FILE_CHK_OK;
}

/*
 * Whether on an ext4 filesystem
 */
int is_ext4(const char *filename)
{
	struct statfs	buffs;
	char	*mtab = MOUNTED;	/* Refer to /etc/mtab */
	struct mntent	*mnt = NULL;
	FILE	*fp = NULL;
	int maxlen, len;
	char maxtype[10];
	char	fullpath[PATH_MAX];

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

	if (statfs(fullpath, &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;
	}

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

	maxlen = 0;
	while ((mnt = getmntent(fp)) != NULL) {
		len = strlen(mnt->mnt_dir);
		if (memcmp(fullpath, mnt->mnt_dir, len) == 0) {
			if (maxlen < len) {
				maxlen = len;
				strcpy(maxtype, mnt->mnt_type);
			}
		}
	}

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

/*
 * Get device's mount point
 */
int get_mount_point(const char *devname, char *mount_point, int buf_len)
{
	char	*mtab = MOUNTED;	/* Refer to /etc/mtab */
	struct mntent	*mnt = NULL;
	FILE	*fp = NULL;

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

	while ((mnt = getmntent(fp)) != NULL) {
		if (strcmp(devname, mnt->mnt_fsname) == 0) {
			endmntent(fp);
			if (strcmp(mnt->mnt_type, FS_EXT4) == 0) {
				strncpy(mount_point, mnt->mnt_dir, buf_len);
				return RETURN_OK;
			}
			PRINT_ERR_MSG(NGMSG_EXT4);
			return RETURN_NG;
		}
	}
	endmntent(fp);
	PRINT_ERR_MSG(NGMSG_UNMOUNT);
	return RETURN_NG;
}

int main(int argc, char *argv[])
{
	int	i, flags;
	int	arg_type;
	int	success_flag;
	int	orig_detail;
	char	dir_name[PATH_MAX];
	int	fd;
	int	ret;
	int	c;
	struct stat64	buf;

	i = 1;
	arg_type = -1;
	success_flag = 0;
	orig_detail = -1;
	flags = 0;
	flags |= FTW_PHYS;	/* Do not follow symlink */
	flags |= FTW_MOUNT;	/* Stay within the same filesystem */
	/* Parse arguments */
	if (argc == 1 || (argc == 2 && argv[1][0] == '-')) {
		printf(MSG_USAGE);
		return 1;
	}

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

			if (argc == 4) {
				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\n");
						printf(MSG_USAGE);
						exit(1);
					}
				}

				fgoal = strtoul(argv[3], NULL, 0);
				if (errno) {
					printf ("block num shold be < 32bit\n");
					exit(1);
				}
			}
			if (!fgoal)
				fgoal = -1;
			break;
		default:
			printf(MSG_USAGE);
			return 1;
		}
	}

	/* Main process */
	for (; i < argc; i++) {
		amount_cnt = 0;
		succeed_cnt = 0;
		memset(dir_name, 0, PATH_MAX);

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

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

		/* Regular file is acceptalbe with force mode */
		if (force_flag && !S_ISREG(buf.st_mode)) {
			printf ("Inappropriate file type \n\n");
			printf(MSG_USAGE);
			exit(1);
		}

		/* Block device */
		if (S_ISBLK(buf.st_mode)) {
			arg_type = DEVNAME;
			if (get_mount_point(argv[i], dir_name, PATH_MAX) ==
				RETURN_NG) {
				continue;
			}
			printf("Start defragment for device(%s)\n", argv[i]);
		} else if (S_ISDIR(buf.st_mode)) {
			/* Directory */
			arg_type = DIRNAME;
			if (access(argv[i], R_OK) < 0) {
				perror(argv[i]);
				continue;
			}
			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;
		}

		/* Device's ext4 check is in get_mount_point() */
		if (arg_type == FILENAME || arg_type == DIRNAME) {
			if (is_ext4(argv[i]) == RETURN_NG) {
				continue;
			}
		}

		switch (arg_type) {
			case DIRNAME:
				printf("Start defragment for directory(%s)\n",
					argv[i]);
			case DEVNAME:
				/* Regional block allocation */
				if (regional_flag) {
					printf(MSG_R_OPTION);

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

					goal = 0;
					if ((ret = ioctl(fd, EXT4_IOC_FIBMAP,
							 &goal)) > 0) {
						perror(NGMSG_FILE_FIBMAP);
						PRINT_FILE_NAME(dir_name);
						close(fd);
						continue;
					}
					close(fd);
				}

				/* File tree walk */
				nftw64(dir_name, ftw_fn, FTW_OPEN_FD, flags);
				printf("\tTotal:\t\t%12d\n", amount_cnt);
				printf("\tSuccess:\t%12d\n", succeed_cnt);
				printf("\tFailure:\t%12d\n",
					amount_cnt - succeed_cnt);
				break;
			case FILENAME:
				if (regional_flag) {
					fprintf(stderr, NGMSG_TYPE, argv[i]);
					continue;
				}
				orig_detail = detail_flag;
				detail_flag = 1;
				printf("Start defragment for %s\n", argv[i]);
				/* Single file process */
				ftw_fn(argv[i], &buf, FTW_F, NULL);
				if (succeed_cnt != 0) {
					printf(
					"\tSUCCESS\t:file defrag success.\n"
					);
				}
				detail_flag = orig_detail;
				break;
		}

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

	if (success_flag)
		return 0;

	return 1;
}

/* Sequentially insert extent by physical block number */
int insertextent(extlist **head, extlist *ext)
{
	extlist	*p = *head;
	if (ext == NULL) {
		return RETURN_NG;
	}
	/* First element */
	if (*head == NULL) {
		(*head) = ext;
		(*head)->prev = *head;
		(*head)->next = *head;
		return 0;
	}

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

/* Insert big extent in decreasing order of length */
int insertbig(bigext **bighead, bigext *ext)
{
	bigext	*p = NULL;
	if (ext == NULL) {
		return RETURN_NG;
	}

	/* Initialize list */
	if (*bighead == NULL) {
		(*bighead) = ext;
		(*bighead)->prev = *bighead;
		(*bighead)->next = *bighead;
		return 0;
	}

	if (ext->big_len >= (*bighead)->big_len) {
		/* Insert before bighead */
		p = (*bighead)->prev;
		insert(p, ext);
		*bighead = ext;
		return 0;
	}

	/* Find insertion positon */
	for (p = (*bighead)->next; p != *bighead; p = p->next) {
		if (p->big_len < ext->big_len) {
			break;
		}
	}
	p = p->prev;
	insert(p, ext);

	return 0;
}

/* Delete and get element from the big list */
bigext * del_get_big(bigext **bighead, bigext *ext)
{
	if (ext == NULL || *bighead == NULL) {
		return NULL;
	}
	/* Keep "bighead" point to the largest enxtent */
	if (ext == *bighead) {
		*bighead = ext->next;
	}
	if (*bighead == (*bighead)->next && ext == *bighead) {
		/* Delete the last element in the list */
		*bighead = NULL;
	}
	ext->prev->next = ext->next;
	ext->next->prev = ext->prev;
	return ext;
}

 void freebig(bigext *big)
{
	bigext *tmpbig = NULL;
	if (big == NULL) {
		return;
	}
	while (big->next != big) {
		tmpbig = big;
		big->prev->next = big->next;
		big->next->prev = big->prev;
		big = big->next;
		free(tmpbig);
	}
	free(big);
}

void free_ext(extlist *head)
{
	extlist *tmp = NULL;
	if (head == NULL) {
		return;
	}
	while (head->next != head) {
		tmp = head;
		head->prev->next = head->next;
		head->next->prev = head->prev;
		head = head->next;
		free(tmp);
	}
	free(head);
}

int move_wrap_tail(ext_wrap **from, ext_wrap **to, ext_wrap *entry)
{
	if (!to || !entry) {
		return RETURN_NG;
	}
	if (from && *from == entry) {
		if ((*from)->next == *from) {
			*from = NULL;
		} else {
			*from = (*from)->next;
		}
	}
	entry->next->prev = entry->prev;
	entry->prev->next = entry->next;
	if (!(*to)) {
		*to = entry;
		(*to)->prev = (*to)->next = *to;
	} else {
		entry->next = *to;
		entry->prev = (*to)->prev;
		(*to)->prev->next = entry;
		(*to)->prev = entry;
	}
	return 0;
}

void mark_wrap(ext_wrap *list)
{
	ext_wrap *wrap = list;

	if (!list) {
		return;
	}
	do {
		wrap->group_ext->tag |= EXT4_EXT_RESERVE;
		wrap = wrap->next;
	} while (wrap != list);
}

void free_wrap_list(ext_wrap **head)
{
	ext_wrap *wrap, *tmp;

	if (!head || !(*head)) {
		return;
	}
	wrap = *head;
	do {
		tmp = wrap;
		wrap = wrap->next;
		free(tmp);
	} while (wrap != *head);
	*head = NULL;
}

int list_size(ext_wrap *list)
{
	ext_wrap	*entry = list;
	int	ret = 0;

	if (!list) {
		return 0;
	}
	do {
		ret++;
		entry = entry->next;
	} while (entry != list);

	return ret;
}

static inline
int do_defrag(int fd, bigext *bigptr, extlist *ptr0, struct ext4_ext_defrag_data defrag_data)
{
	int defraged_size = 0;
	int ret = 0;

	/* Defrag */
	defrag_data.ext.start = bigptr->start->data.start;
	defrag_data.ext.len = bigptr->big_len;
	defrag_data.ext.block = 0;
	defrag_data.defrag_size = bigptr->big_len;
	defrag_data.flag = DEFRAG_RESERVE_BLOCK_SECOND;
	defrag_data.goal = bigptr->start->data.start;

	defraged_size = ioctl(fd, EXT4_IOC_DEFRAG, &defrag_data);
	if (defraged_size < 0) {
		ret = RETURN_NG;
	}
	/* Release reserved sign */
	ptr0 = bigptr->start;
	do {
		ptr0->tag &= ~EXT4_EXT_RESERVE;
		ptr0 = ptr0->next;
	} while (ptr0 != bigptr->end->next);

	ret += defraged_size;

	if(defraged_size > 0)
		defrag_data.start_offset += defraged_size;

	return ret;
}

int force_defrag(int fd, const struct stat64 *sb, int blocksize)
{
	struct ext4_group_data_info	ext4_group_data;
	struct ext4_extents_info	tmpext;
	struct ext4_extent_data	ext;
	struct ext4_ext_defrag_data	defrag_data;
	bigext	*bighead, *target_bighead, *bigptr;
	extlist	*head, *startptr, *ptr0;
	ext_wrap	*target_head, *wrap, *tmp_wrap_list;
	ext4_fsblk_t	bstart, bend;
	unsigned long	filesize;
	unsigned long long	ino, istart, iend;
	unsigned int	gn;
	int	len, maxlen;
	int	pos = 0;
	int	ret = 0;
	int	exts, file_exts;

	memset(&ext4_group_data, 0, sizeof(struct ext4_group_data_info));
	memset(&tmpext, 0, sizeof(struct ext4_extents_info));
	memset(&defrag_data, 0, sizeof(struct ext4_ext_defrag_data));
	memset(&ext, 0, sizeof(struct ext4_extent_data));

	bigptr = target_bighead = bighead = NULL;
	head = startptr = ptr0 =  NULL;
	target_head = wrap = tmp_wrap_list = NULL;

	if (ioctl(fd, EXT4_IOC_GROUP_INFO, &ext4_group_data) < 0) {
		return RETURN_NG;
	}

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

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

	/* Get used extent in the block group */
	tmpext.max_entries = DEFRAG_MAX_ENT;
	for (ino = istart; ino <= iend; ino++) {
		tmpext.ino = ino;
		tmpext.entries = 0;
		pos = 0;
		do {
			/* Get extents info */
			int j;
			tmpext.entries += pos;/* Offset */
			pos = tmpext.entries;
			memset(tmpext.ext, 0,
			     sizeof(struct ext4_extent_data) * DEFRAG_MAX_ENT);
			ret = ioctl(fd, EXT4_IOC_EXTENTS_INFO, &tmpext);
			if (ret < 0) {
				if (errno == ENOENT) {
					continue;
				} else {
					/* without ENOENT case*/
					ret = RETURN_NG;
					goto freelist;
				}
			}

			for (j = 0; j < tmpext.entries; j++) {
				extlist	*listptr = NULL;
				/* Is this extent in current block group? */
				if (tmpext.ext[j].start < bstart ||
				    tmpext.ext[j].start > bend) {
					continue;
				}
				listptr = malloc(sizeof(extlist));
				if (listptr == NULL) {
					ret = RETURN_NG;
					goto freelist;
				}
				memset(listptr, 0, sizeof(extlist));
				memcpy(&(listptr->data), &tmpext.ext[j],
				       sizeof(struct ext4_extent_data));
				listptr->ino = ino;
				if (insertextent(&head, listptr) < 0) {
					ret = RETURN_NG;
					goto freelist;
				}
			}
		} while (tmpext.entries == DEFRAG_MAX_ENT && ret == 0);
	}
	/* Get free extents in the group */
	memset(&tmpext, 0, sizeof(struct ext4_extents_info));
	tmpext.ino = sb->st_ino;
	tmpext.max_entries = DEFRAG_MAX_ENT;
	for (pos = 0; pos < ext4_group_data.s_blocks_per_group;)
	{
		int	j;
		if (ioctl(fd, EXT4_IOC_FREE_BLOCKS_INFO, &tmpext) < 0) {
			ret = RETURN_NG;
			goto freelist;
		}
		/*
		 * No free extent after the logical block number "pos".
		 * In other word, offset this time equals to prev recursion.
		 */
		for (j = 0; tmpext.ext[j].len != 0 && j < DEFRAG_MAX_ENT; j++) {
			/* Alloc list node store extent */
			extlist	*listptr = NULL;
			listptr = malloc(sizeof(extlist));
			if (listptr == NULL) {
				ret = RETURN_NG;
				goto freelist;
			}
			memset(listptr, 0, sizeof(extlist));
			memcpy(&(listptr->data), &(tmpext.ext[j]),
			       sizeof(struct ext4_extent_data));
			listptr->tag = EXT4_EXT_FREE;/* Free extent */
			if (insertextent(&head, listptr) < 0) {
				ret = RETURN_NG;
				goto freelist;
			}
		}
		/*
		 * No free extent after the logical block number "pos".
		 * In other word, offset this time equals to prev recursion.
		 */
		if (pos == tmpext.offset) {
			break;
		}
		if (j < DEFRAG_MAX_ENT) {
			break;
		}
		/* Record the offset of logical block number this time */
		pos = tmpext.offset;
		memset(tmpext.ext, 0,
		       sizeof(struct ext4_extent_data) * DEFRAG_MAX_ENT);
	}

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

	/* Find continuous region(big extent) */
	startptr = ptr0 = head;
	maxlen = 0;
	len = head->data.len;
	ptr0 = ptr0->next;
	do {
		if (len >= filesize) {
			/* Hit on the way */
			bigext	*bigtmp = malloc(sizeof(bigext));
			if (!bigtmp) {
				ret = RETURN_NG;
				goto freelist;
			}
			bigtmp->prev = bigtmp->next = NULL;
			bigtmp->start = startptr;
			bigtmp->end = ptr0->prev;
			bigtmp->big_len = len;
			if (insertbig(&target_bighead, bigtmp) < 0) {
				ret = RETURN_NG;
				goto freelist;
			}
			maxlen += len;
			exts = 1;
			goto frag_check;
		}
		if ((ptr0->prev->data.start + ptr0->prev->data.len) !=
		    ptr0->data.start) {
			bigext	*bigtmp = NULL;
			bigtmp = malloc(sizeof(bigext));
			if (bigtmp == NULL) {
				ret = RETURN_NG;
				goto freelist;
			}
			memset(bigtmp, 0, sizeof(bigext));
			bigtmp->big_len = len;
			bigtmp->start = startptr;
			bigtmp->end = ptr0->prev;

			if (insertbig(&bighead, bigtmp) < 0) {
				ret = RETURN_NG;
				goto freelist;
			}
			maxlen += len;
			startptr = ptr0;
			len = ptr0->data.len;
			ptr0 = ptr0->next;
			continue;
		}
		len += ptr0->data.len;
		ptr0 = ptr0->next;
	} while (ptr0 != head->next);

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

	if (!bighead) {
		ret = RETURN_NG;
		goto freelist;
	}
	len = 0;/* Blocks we found for target file */
	exts = 0;/* Extents we found for target file */
	while (bighead)
	{
		bigext	*bigtmp;
		if (bighead->big_len + len >= filesize) {
			/*
			 * Search from the smallest big-extent
			 * to avoid waste of space
			 */
			bigtmp = bighead->prev;
			do {
				if (bigtmp->big_len + len >= filesize) {
					len += bigtmp->big_len;
					bigtmp = del_get_big(&bighead, bigtmp);
					if (insertbig(&target_bighead, bigtmp) <
					    0) {
						ret = RETURN_NG;
						goto freelist;
					}
					exts++;
					goto frag_check;
				}
				bigtmp = bigtmp->prev;
			} while (bigtmp != bighead->prev);
		}
		len += bighead->big_len;
		bigtmp = del_get_big(&bighead, bighead);
		if (insertbig(&target_bighead, bigtmp) < 0) {
			ret = RETURN_NG;
			goto freelist;
		}
		exts++;
	}
	if (!bighead) {
		ret = RETURN_NG;
		goto freelist;
	}

frag_check:
	/* Count file exts */
	memset(&tmpext, 0, sizeof(struct ext4_extents_info));
	file_exts = 0;/* Extents count of file */
	tmpext.ino = sb->st_ino;
	tmpext.max_entries = DEFRAG_MAX_ENT;
	tmpext.entries = 0;
	pos = 0;
	do {
		tmpext.entries += pos;
		pos = tmpext.entries;
		ret = ioctl(fd, EXT4_IOC_EXTENTS_INFO, &tmpext);
		if (ret < 0) {
			ret = RETURN_NG;
			goto freelist;
		}
		file_exts += tmpext.entries;
	} while (tmpext.entries == DEFRAG_MAX_ENT && ret == 0);

	if (exts >= file_exts) {
		/* No improvment */
		ret = RETURN_NG;
		goto freelist;
	}

	/* Reserve free extents */
	if (!target_bighead) {
		/* Fault */
		ret = RETURN_NG;
		goto freelist;
	}
	bigptr = target_bighead;
	memset(&tmpext, 0, sizeof(tmpext));
	tmpext.ino = 0;
	tmpext.max_entries = DEFRAG_MAX_ENT;
	tmpext.ino = sb->st_ino;
	tmp_wrap_list = NULL;
	ext4_fsblk_t data_block = 0;
	ext4_fsblk_t data_start = 0;
	int data_len = 0;
	defrag_data.start_offset = 0;

	do {
		ext_wrap	*wrap;
		ptr0 = bigptr->start;
		data_len = 0;
		data_start = ptr0->data.start;
		data_block = ptr0->data.block;
		do {
			data_len += ptr0->data.len;
			if (ptr0->tag != EXT4_EXT_USE) {
				ptr0->tag = EXT4_EXT_RESERVE;
				ptr0 = ptr0->next;
				continue;
			}
			tmpext.ino = ptr0->ino;
			tmpext.goal = fgoal;
			memcpy(tmpext.ext, &ptr0->data,
			       sizeof(struct ext4_extent_data));
			wrap = malloc(sizeof(ext_wrap));
			if (!wrap) {
				ret = RETURN_NG;
				goto release_blocks;
			}
			wrap->group_ext = ptr0;
			wrap->next = wrap->prev = wrap;
			if (move_wrap_tail(NULL, &tmp_wrap_list, wrap) < 0) {
				ret = RETURN_NG;
				goto release_blocks;
			}
			ptr0 = ptr0->next;
			tmpext.entries = 1;
			ret = ioctl(fd, EXT4_IOC_MOVE_VICTIM, &tmpext);
			if (ret < 0) {
				ret = RETURN_NG;
				goto release_blocks;
			}
			mark_wrap(tmp_wrap_list);
			free_wrap_list(&tmp_wrap_list);
		} while (ptr0 != bigptr->end->next);

		if (fsync(fd) < 0) {
			if (detail_flag) {
				perror(NGMSG_FILE_SYNC);
			}
			close(fd);
			goto freelist;
		}

		tmpext.entries = 1;
		tmpext.ext[0].block = data_block;
		tmpext.ext[0].start = data_start;
		tmpext.ext[0].len = bigptr->big_len;
		ret = ioctl(fd, EXT4_IOC_RESERVE_BLOCK, &tmpext);
		if (ret < 0) {
			printf("RESERVE_ERROR ret = %d\n", ret);
			goto release_blocks;
		}
		ret = do_defrag(fd, bigptr, ptr0, defrag_data);
		if (ret < 0) {
			printf("DEFRAG_ERROR ret = %d\n", ret);
			goto release_blocks;
		}
		defrag_data.start_offset += ret;
		ret = defrag_data.start_offset;

		bigptr = bigptr->next;
	} while (bigptr != target_bighead);
	goto freelist;

release_blocks:
	ret = ioctl(fd, EXT4_IOC_BLOCK_RELEASE);
	if (ret < 0) {
		ret = RETURN_NG;
		goto freelist;
	}

freelist:
	free_wrap_list(&target_head);
	free_wrap_list(&tmp_wrap_list);
	freebig(target_bighead);
	freebig(bighead);
	free_ext(head);
	return ret;
}
-
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Reiser Filesystem Development]     [Ceph FS]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite National Park]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Device Mapper]     [Linux Media]

  Powered by Linux