| /* 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; | |
| } |