text: /*
   * 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"
  
  #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 {
  	struct thread_data *td;
  	unsigned int inplace;
  	char * donor_name;
  };
  
  static struct fio_option options[] = {
  	{
  		.name	= "donorname",
  		.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",
  		.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", 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) {
  		io_u->resid = io_u->xfer_buflen - len;
  		io_u->error = 0;
  	}
  	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);
  }
  