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