blob: a0f216bb472a699a95984ab257b459b49d985fc5 [file] [log] [blame]
/* E4COMPACT
*
* Compact list of files sequentially
*
* Usage example:
* find /etc -type f > etc_list
* fallocate -l100M /etc/.tmp_donor_file
* cat etc_list | ./e4defrag /etc/.tmp_donor_file
* unlink /etc/.tmp_donor_file
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
#include <linux/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef EXT4_IOC_MOVE_EXT
struct move_extent {
__s32 reserved; /* original file descriptor */
__u32 donor_fd; /* donor file descriptor */
__u64 orig_start; /* logical start offset in block for orig */
__u64 donor_start; /* logical start offset in block for donor */
__u64 len; /* block length to be moved */
__u64 moved_len; /* moved block length */
};
#define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent)
#endif
#define EXTENT_MAX_COUNT 512
struct donor_info
{
int fd;
__u64 offset;
__u64 length;
};
static int ignore_error = 0;
static int verbose = 0;
static int do_sparse = 0;
static unsigned blk_per_pg;
static unsigned blk_sz;
static int do_defrag_range(int fd, char *name,__u64 start, __u64 len,
struct donor_info *donor)
{
int ret, retry;
struct move_extent mv_ioc;
__u64 moved = 0;
int i = 0;
assert(donor->length >= len);
/* EXT4_IOC_MOVE_EXT requires both files has same offset inside page */
donor->offset += (blk_per_pg - (donor->offset & (blk_per_pg -1)) +
(start & (blk_per_pg -1))) & (blk_per_pg -1);
mv_ioc.donor_fd = donor->fd;
mv_ioc.orig_start = start;
mv_ioc.donor_start = donor->offset;
mv_ioc.len = len;
if (verbose)
printf("%s %s start:%lld len:%lld donor [%lld, %lld]\n", __func__,
name, (unsigned long long) start,
(unsigned long long) len,
(unsigned long long)donor->offset,
(unsigned long long)donor->length);
retry= 3;
do {
i++;
errno = 0;
mv_ioc.moved_len = 0;
ret = ioctl(fd, EXT4_IOC_MOVE_EXT, &mv_ioc);
if (verbose)
printf("process %s it:%d start:%lld len:%lld donor:%lld,"
"moved:%lld ret:%d errno:%d\n",
name, i,
(unsigned long long) mv_ioc.orig_start,
(unsigned long long) mv_ioc.len,
(unsigned long long)mv_ioc.donor_start,
(unsigned long long)mv_ioc.moved_len,
ret, errno);
if (ret < 0) {
if (verbose)
printf("%s EXT4_IOC_MOVE_EXT failed err:%d\n",
__func__, errno);
if (errno != EBUSY || !retry--)
break;
} else {
retry = 3;
/* Nothing to swap */
if (mv_ioc.moved_len == 0)
break;
}
assert(mv_ioc.len >= mv_ioc.moved_len);
mv_ioc.len -= mv_ioc.moved_len;
mv_ioc.orig_start += mv_ioc.moved_len;
mv_ioc.donor_start += mv_ioc.moved_len;
moved += mv_ioc.moved_len;
} while (mv_ioc.len);
if (ret && ignore_error &&
(errno == EBUSY || errno == ENODATA || errno == EOPNOTSUPP))
ret = 0;
donor->length -= moved;
donor->offset += moved;
return ret;
}
static int do_defrag_sparse(int fd, char *name,__u64 start, __u64 len,
struct donor_info *donor)
{
int i, ret = 0;
struct fiemap *fiemap_buf = NULL;
struct fiemap_extent *ext_buf = NULL;
fiemap_buf = malloc(EXTENT_MAX_COUNT * sizeof(struct fiemap_extent)
+ sizeof(struct fiemap));
if (fiemap_buf == NULL) {
fprintf(stderr, "%s Can not allocate memory\n", __func__);
return -1;
}
ext_buf = fiemap_buf->fm_extents;
memset(fiemap_buf, 0, sizeof(struct fiemap));
fiemap_buf->fm_flags |= FIEMAP_FLAG_SYNC;
fiemap_buf->fm_extent_count = EXTENT_MAX_COUNT;
do {
__u64 next;
fiemap_buf->fm_start = start * blk_sz;
fiemap_buf->fm_length = len * blk_sz;
ret = ioctl(fd, FS_IOC_FIEMAP, fiemap_buf);
if (ret < 0 || fiemap_buf->fm_mapped_extents == 0) {
fprintf(stderr, "%s Can get extent info for %s ret:%d mapped:%d",
__func__, name, ret, fiemap_buf->fm_mapped_extents);
goto out;
}
for (i = 0; i < fiemap_buf->fm_mapped_extents; i++) {
ret = do_defrag_range(fd, name,
ext_buf[i].fe_logical / blk_sz,
ext_buf[i].fe_length / blk_sz,
donor);
if (ret)
goto out;
}
next = (ext_buf[fiemap_buf->fm_mapped_extents -1].fe_logical +
ext_buf[fiemap_buf->fm_mapped_extents -1].fe_length) /
blk_sz;
if (next < start + len) {
len -= next - start;
start = next;
} else
break;
} while (fiemap_buf->fm_mapped_extents == EXTENT_MAX_COUNT &&
!(ext_buf[EXTENT_MAX_COUNT-1].fe_flags & FIEMAP_EXTENT_LAST));
out:
free(fiemap_buf);
return ret;
}
void usage()
{
printf("Usage: -f donor_file [-o donor_offset] [-v] [-i]\n"
"\t\t -v: verbose\n"
"\t\t -s: enable sparse file optimization\n"
"\t\t -i: ignore errors\n");
}
int main(int argc, char **argv)
{
int fd, ret = 0;
char *line = NULL;
size_t len = 0;
ssize_t read;
struct donor_info donor;
struct stat st;
extern char *optarg;
extern int optind;
int c;
char * donor_name = NULL;
__u64 eof_blk;
donor.offset = 0;
while ((c = getopt(argc, argv, "f:o:isv")) != -1) {
switch (c) {
case 'o':
donor.offset = atol(optarg);
break;
case 'i':
ignore_error = 1;
break;
case 'v':
verbose = 1;
break;
case 's':
do_sparse = 1;
break;
case 'f':
donor_name = (optarg);
break;
default:
usage();
exit(1);
}
}
if (!donor_name) {
usage();
exit(1);
}
donor.fd = open(donor_name, O_RDWR);
if (donor.fd < 0) {
perror("can not open donor file");
exit(1);
}
if (fstat(donor.fd, &st)) {
perror("can not stat donor fd");
exit(1);
}
donor.length = st.st_size / st.st_blksize;
if (donor.offset)
donor.offset /= st.st_blksize;
blk_sz = st.st_blksize;
blk_per_pg = sysconf(_SC_PAGESIZE) / blk_sz;
if (verbose)
printf("Init donor %s off:%lld len:%lld bsz:%lu\n",
donor_name, donor.offset, donor.length, st.st_blksize);
while ((read = getline(&line, &len, stdin)) != -1) {
if (line[read -1] == '\n')
line[read -1] = 0;
fd = open(line, O_RDWR);
if (fd < 0) {
if (verbose)
printf("Can not open %s errno:%d\n", line, errno);
if (ignore_error)
continue;
else
break;
}
if(fstat(fd, &st)) {
if (verbose)
perror("Can not stat ");
continue;
if (ignore_error)
continue;
else
break;
}
if (!(st.st_size && st.st_blocks))
continue;
eof_blk = (st.st_size + blk_sz-1) / blk_sz;
if (do_sparse)
ret = do_defrag_sparse(fd, line, 0, eof_blk, &donor);
else
ret = do_defrag_range(fd, line, 0, eof_blk, &donor);
if (ret && !ignore_error)
break;
}
free(line);
return ret;
}