All of lore.kernel.org
 help / color / mirror / Atom feed
From: sho@tnes.nec.co.jp
To: linux-ext4@vger.kernel.org, linux-fsdevel@vger.kernel.org
Subject: [RFC][PATCH 3/3] Online defrag command
Date: Tue, 16 Jan 2007 21:06:04 +0900	[thread overview]
Message-ID: <20070116210604sho@rifu.tnes.nec.co.jp> (raw)

The defrag command.  Usage is as follows:
o Put the multiple files closer together.
  # e4defrag -r directory-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 <sho@tnes.nec.co.jp>
---
/*
 * e4defrag, 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>


#define EXT4_SUPER_MAGIC	0xEF53 /* magic number for ext4 */
#define DEFRAG_PAGES    	128 /* the number of pages to defrag at one time */
#define MAX_BLOCKS_LEN  	16384 /* Maximum length of contiguous blocks */

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

/* ioctl command */
#define EXT4_IOC_GET_DATA_BLOCK	_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 [-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_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."

struct ext4_ext_defrag_data {
	loff_t start_offset; /* start offset to defrag in bytes */
	loff_t defrag_size;  /* size of defrag in bytes */
	ext4_fsblk_t goal;   /* block offset for allocation */
};

int		detail_flag = 0;
int		regional_flag = 0;
int		amount_cnt = 0;
int		succeed_cnt = 0;
ext4_fsblk_t	goal = 0;

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

	if (-1 == fstatfs(fd, &fsbuf)) {
		if (detail_flag) {
			perror(NGMSG_FS_INFO);
		}
		return RETURN_NG;
	}

	/* compute free space for root and normal user separately */
	if (ROOT_UID == getuid())
		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;
}

/*
 * file tree walk callback function
 * check file attributes before ioctl call to avoid illegal operations
 */
int
ftw_fn(
	const char*		file,
	const struct stat64	*sb,
	int			flag,
	struct FTW*		ftwbuf
)
{
	int				fd;
	int				defraged_size;
	struct ext4_ext_defrag_data	df_data;
	if (FTW_F == flag) {
		amount_cnt++;
		if (-1 == (fd = open64(file, O_RDONLY))) {
			if (detail_flag) {
				perror(NGMSG_FILE_OPEN);
				PRINT_FILE_NAME(file);
			}
			return FTW_CONT;
		}

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

		if (-1 == fsync(fd)) {
			if (detail_flag) {
				perror(NGMSG_FILE_SYNC);
				PRINT_FILE_NAME(file);
			}
			close(fd);
			return FTW_CONT;
		}

		/* ioctl call does the actual defragment job. */
		df_data.start_offset = 0;
		df_data.goal = goal;
		while (1) {
			df_data.defrag_size =
				min((sb->st_size - df_data.start_offset),
					DEFRAG_SIZE);
			/* EXT4_IOC_DEFRAG */
			defraged_size = ioctl(fd, EXT4_IOC_DEFRAG, &df_data);
			if (defraged_size == -1) {
				if (detail_flag) {
					perror(NGMSG_FILE_DEFRAG);
					PRINT_FILE_NAME(file);
				}
				close(fd);
				return FTW_CONT;
			}
			df_data.start_offset += defraged_size;

			/* end of file */
			if (df_data.start_offset >= 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 (0 == S_ISREG(buf->st_mode)) {
		if (detail_flag) {
			PRINT_ERR_MSG(NGMSG_FILE_UNREG);
			PRINT_FILE_NAME(file_name);
		}
		return FILE_CHK_NG;
	}

	/* available space */
	if (RETURN_NG == check_free_size(fd, DEFRAG_SIZE)) {
		if (detail_flag) {
			PRINT_ERR_MSG(NGMSG_FILE_LARGE);
			PRINT_FILE_NAME(file_name);
		}
		return FILE_CHK_NG;
	}

	/* priority */
	if (ROOT_UID != getuid() &&
		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 (-1 == fcntl(fd, F_GETLK, &lock)) {
		if (detail_flag) {
			perror(NGMSG_GET_LCKINFO);
			PRINT_FILE_NAME(file_name);
		}
		return FILE_CHK_NG;
	} else if (F_UNLCK != lock.l_type) {
		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;

	if (-1 == statfs(filename, &buffs)) {
		perror(NGMSG_FS_INFO);
		PRINT_FILE_NAME(filename);
		return RETURN_NG;
	} else if (EXT4_SUPER_MAGIC == buffs.f_type) {
		return RETURN_OK;
	} else {
		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 (NULL == (fp = setmntent(mtab, "r"))) {
		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;
	struct ext4_ext_defrag_data     df_data;

	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 */
	while ((c = getopt(argc, argv, "rv")) != EOF) {
		switch (c) {
		case 'r':
			regional_flag = 1;
			i = 2;
			break;
		case 'v':
			detail_flag = 1;
			i = 2;
			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 (-1 == stat64(argv[i], &buf)) {
			perror(NGMSG_FILE_INFO);
			PRINT_FILE_NAME(argv[i]);
			continue;
		}

		/* block device */
		if (S_ISBLK(buf.st_mode)) {
			arg_type = DEVNAME;
			if (RETURN_NG ==
				get_mount_point(argv[i], dir_name, PATH_MAX)) {
				continue;
			}
			printf("Start defragment for device(%s)\n", argv[i]);
		} else if (S_ISDIR(buf.st_mode)) {
			/* directory */
			arg_type = DIRNAME;
			if (-1 == access(argv[i], R_OK)) {
				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 (RETURN_NG == is_ext4(argv[i])) {
				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 (-1 == (fd = open64(dir_name,
								O_RDONLY))) {
                                        	if (detail_flag) {
                                                	perror(NGMSG_FILE_OPEN);
                                                	PRINT_FILE_NAME(dir_name);
                                        	}
                                        	return RETURN_NG;
                               		}

	                                if (0 < (ret = ioctl(fd,
						 EXT4_IOC_GET_DATA_BLOCK,
								&goal))) {
                                        	perror(NGMSG_FILE_DEFRAG);
						PRINT_FILE_NAME(dir_name);
						close(fd);
                                        	return ret;
					}
					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) {
					printf(MSG_USAGE);
					return 1;
				}
				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;
}

             reply	other threads:[~2007-01-16 12:10 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-01-16 12:06 sho [this message]
  -- strict thread matches above, loose matches on Subject: below --
2007-02-08  9:02 [RFC][PATCH 3/3] Online defrag command Takashi Sato
2007-02-08 13:56 ` Jens Axboe
2007-02-09  8:08   ` Takashi Sato
2006-12-22 10:30 sho
2006-11-09 11:11 sho

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20070116210604sho@rifu.tnes.nec.co.jp \
    --to=sho@tnes.nec.co.jp \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.