| /* |
| * ioe_e4defrag: ioengine for git://git.kernel.dk/fio.git |
| * |
| * IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate |
| * defragment activity |
| * |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/uio.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| |
| #include "../fio.h" |
| #include "../optgroup.h" |
| |
| #ifndef EXT4_IOC_MOVE_EXT |
| #define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent) |
| struct move_extent { |
| __u32 reserved; /* should be zero */ |
| __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 */ |
| }; |
| #endif |
| |
| struct e4defrag_data { |
| int donor_fd; |
| int bsz; |
| }; |
| |
| struct e4defrag_options { |
| void *pad; |
| unsigned int inplace; |
| char * donor_name; |
| }; |
| |
| static struct fio_option options[] = { |
| { |
| .name = "donorname", |
| .lname = "Donor Name", |
| .type = FIO_OPT_STR_STORE, |
| .off1 = offsetof(struct e4defrag_options, donor_name), |
| .help = "File used as a block donor", |
| .category = FIO_OPT_C_ENGINE, |
| .group = FIO_OPT_G_E4DEFRAG, |
| }, |
| { |
| .name = "inplace", |
| .lname = "In Place", |
| .type = FIO_OPT_INT, |
| .off1 = offsetof(struct e4defrag_options, inplace), |
| .minval = 0, |
| .maxval = 1, |
| .help = "Alloc and free space inside defrag event", |
| .category = FIO_OPT_C_ENGINE, |
| .group = FIO_OPT_G_E4DEFRAG, |
| }, |
| { |
| .name = NULL, |
| }, |
| }; |
| |
| static int fio_e4defrag_init(struct thread_data *td) |
| { |
| int r, len = 0; |
| struct e4defrag_options *o = td->eo; |
| struct e4defrag_data *ed; |
| struct stat stub; |
| char donor_name[PATH_MAX]; |
| |
| if (!strlen(o->donor_name)) { |
| log_err("'donorname' options required\n"); |
| return 1; |
| } |
| |
| ed = malloc(sizeof(*ed)); |
| if (!ed) { |
| td_verror(td, ENOMEM, "io_queue_init"); |
| return 1; |
| } |
| memset(ed, 0 ,sizeof(*ed)); |
| |
| if (td->o.directory) |
| len = sprintf(donor_name, "%s/", td->o.directory); |
| sprintf(donor_name + len, "%s", o->donor_name); |
| |
| ed->donor_fd = open(donor_name, O_CREAT|O_WRONLY, 0644); |
| if (ed->donor_fd < 0) { |
| td_verror(td, errno, "io_queue_init"); |
| log_err("Can't open donor file %s err:%d\n", donor_name, ed->donor_fd); |
| free(ed); |
| return 1; |
| } |
| |
| if (!o->inplace) { |
| long long __len = td->o.file_size_high - td->o.start_offset; |
| r = fallocate(ed->donor_fd, 0, td->o.start_offset, __len); |
| if (r) |
| goto err; |
| } |
| r = fstat(ed->donor_fd, &stub); |
| if (r) |
| goto err; |
| |
| ed->bsz = stub.st_blksize; |
| td->io_ops_data = ed; |
| return 0; |
| err: |
| td_verror(td, errno, "io_queue_init"); |
| close(ed->donor_fd); |
| free(ed); |
| return 1; |
| } |
| |
| static void fio_e4defrag_cleanup(struct thread_data *td) |
| { |
| struct e4defrag_data *ed = td->io_ops_data; |
| if (ed) { |
| if (ed->donor_fd >= 0) |
| close(ed->donor_fd); |
| free(ed); |
| } |
| } |
| |
| |
| static int fio_e4defrag_queue(struct thread_data *td, struct io_u *io_u) |
| { |
| |
| int ret; |
| unsigned long long len; |
| struct move_extent me; |
| struct fio_file *f = io_u->file; |
| struct e4defrag_data *ed = td->io_ops_data; |
| struct e4defrag_options *o = td->eo; |
| |
| fio_ro_check(td, io_u); |
| |
| /* Theoretically defragmentation should not change data, but it |
| * changes data layout. So this function handle only DDIR_WRITE |
| * in order to satisfy strict read only access pattern |
| */ |
| if (io_u->ddir != DDIR_WRITE) { |
| io_u->error = EINVAL; |
| return FIO_Q_COMPLETED; |
| } |
| |
| if (o->inplace) { |
| ret = fallocate(ed->donor_fd, 0, io_u->offset, io_u->xfer_buflen); |
| if (ret) |
| goto out; |
| } |
| |
| memset(&me, 0, sizeof(me)); |
| me.donor_fd = ed->donor_fd; |
| me.orig_start = io_u->offset / ed->bsz; |
| me.donor_start = me.orig_start; |
| len = (io_u->offset + io_u->xfer_buflen + ed->bsz -1); |
| me.len = len / ed->bsz - me.orig_start; |
| |
| ret = ioctl(f->fd, EXT4_IOC_MOVE_EXT, &me); |
| len = me.moved_len * ed->bsz; |
| |
| if (len > io_u->xfer_buflen) |
| len = io_u->xfer_buflen; |
| |
| if (len != io_u->xfer_buflen) { |
| if (len) { |
| io_u->resid = io_u->xfer_buflen - len; |
| io_u->error = 0; |
| } else { |
| /* access beyond i_size */ |
| io_u->error = EINVAL; |
| } |
| } |
| if (ret) |
| io_u->error = errno; |
| |
| if (o->inplace) |
| ret = ftruncate(ed->donor_fd, 0); |
| out: |
| if (ret && !io_u->error) |
| io_u->error = errno; |
| |
| return FIO_Q_COMPLETED; |
| } |
| |
| static struct ioengine_ops ioengine = { |
| .name = "e4defrag", |
| .version = FIO_IOOPS_VERSION, |
| .init = fio_e4defrag_init, |
| .queue = fio_e4defrag_queue, |
| .open_file = generic_open_file, |
| .close_file = generic_close_file, |
| .get_file_size = generic_get_file_size, |
| .flags = FIO_SYNCIO, |
| .cleanup = fio_e4defrag_cleanup, |
| .options = options, |
| .option_struct_size = sizeof(struct e4defrag_options), |
| |
| }; |
| |
| static void fio_init fio_syncio_register(void) |
| { |
| register_ioengine(&ioengine); |
| } |
| |
| static void fio_exit fio_syncio_unregister(void) |
| { |
| unregister_ioengine(&ioengine); |
| } |