1/*
2 * ioe_e4defrag:  ioengine for git://git.kernel.dk/fio.git
3 *
4 * IO engine that does regular EXT4_IOC_MOVE_EXT ioctls to simulate
5 * defragment activity
6 *
7 */
8
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <unistd.h>
14#include <sys/uio.h>
15#include <errno.h>
16#include <assert.h>
17#include <fcntl.h>
18
19#include "../fio.h"
20#include "../optgroup.h"
21
22#ifndef EXT4_IOC_MOVE_EXT
23#define EXT4_IOC_MOVE_EXT               _IOWR('f', 15, struct move_extent)
24struct move_extent {
25	__u32 reserved;         /* should be zero */
26	__u32 donor_fd;         /* donor file descriptor */
27	__u64 orig_start;       /* logical start offset in block for orig */
28	__u64 donor_start;      /* logical start offset in block for donor */
29	__u64 len;              /* block length to be moved */
30	__u64 moved_len;        /* moved block length */
31};
32#endif
33
34struct e4defrag_data {
35	int donor_fd;
36	int bsz;
37};
38
39struct e4defrag_options {
40	void *pad;
41	unsigned int inplace;
42	char * donor_name;
43};
44
45static struct fio_option options[] = {
46	{
47		.name	= "donorname",
48		.lname	= "Donor Name",
49		.type	= FIO_OPT_STR_STORE,
50		.off1	= offsetof(struct e4defrag_options, donor_name),
51		.help	= "File used as a block donor",
52		.category = FIO_OPT_C_ENGINE,
53		.group	= FIO_OPT_G_E4DEFRAG,
54	},
55	{
56		.name	= "inplace",
57		.lname	= "In Place",
58		.type	= FIO_OPT_INT,
59		.off1	= offsetof(struct e4defrag_options, inplace),
60		.minval	= 0,
61		.maxval	= 1,
62		.help	= "Alloc and free space inside defrag event",
63		.category = FIO_OPT_C_ENGINE,
64		.group	= FIO_OPT_G_E4DEFRAG,
65	},
66	{
67		.name	= NULL,
68	},
69};
70
71static int fio_e4defrag_init(struct thread_data *td)
72{
73	int r, len = 0;
74	struct e4defrag_options *o = td->eo;
75	struct e4defrag_data *ed;
76	struct stat stub;
77	char donor_name[PATH_MAX];
78
79	if (!strlen(o->donor_name)) {
80		log_err("'donorname' options required\n");
81		return 1;
82	}
83
84	ed = malloc(sizeof(*ed));
85	if (!ed) {
86		td_verror(td, ENOMEM, "io_queue_init");
87		return 1;
88	}
89	memset(ed, 0 ,sizeof(*ed));
90
91	if (td->o.directory)
92		len = sprintf(donor_name, "%s/", td->o.directory);
93	sprintf(donor_name + len, "%s", o->donor_name);
94
95	ed->donor_fd = open(donor_name, O_CREAT|O_WRONLY, 0644);
96	if (ed->donor_fd < 0) {
97		td_verror(td, errno, "io_queue_init");
98		log_err("Can't open donor file %s err:%d\n", donor_name, ed->donor_fd);
99		free(ed);
100		return 1;
101	}
102
103	if (!o->inplace) {
104		long long __len = td->o.file_size_high - td->o.start_offset;
105		r = fallocate(ed->donor_fd, 0, td->o.start_offset, __len);
106		if (r)
107			goto err;
108	}
109	r = fstat(ed->donor_fd, &stub);
110	if (r)
111		goto err;
112
113	ed->bsz = stub.st_blksize;
114	td->io_ops_data = ed;
115	return 0;
116err:
117	td_verror(td, errno, "io_queue_init");
118	close(ed->donor_fd);
119	free(ed);
120	return 1;
121}
122
123static void fio_e4defrag_cleanup(struct thread_data *td)
124{
125	struct e4defrag_data *ed = td->io_ops_data;
126	if (ed) {
127		if (ed->donor_fd >= 0)
128			close(ed->donor_fd);
129		free(ed);
130	}
131}
132
133
134static int fio_e4defrag_queue(struct thread_data *td, struct io_u *io_u)
135{
136
137	int ret;
138	unsigned long long len;
139	struct move_extent me;
140	struct fio_file *f = io_u->file;
141	struct e4defrag_data *ed = td->io_ops_data;
142	struct e4defrag_options *o = td->eo;
143
144	fio_ro_check(td, io_u);
145
146	/* Theoretically defragmentation should not change data, but it
147	 * changes data layout. So this function handle only DDIR_WRITE
148	 * in order to satisfy strict read only access pattern
149	 */
150	if (io_u->ddir != DDIR_WRITE) {
151		io_u->error = EINVAL;
152		return FIO_Q_COMPLETED;
153	}
154
155	if (o->inplace) {
156		ret = fallocate(ed->donor_fd, 0, io_u->offset, io_u->xfer_buflen);
157		if (ret)
158			goto out;
159	}
160
161	memset(&me, 0, sizeof(me));
162	me.donor_fd = ed->donor_fd;
163	me.orig_start = io_u->offset / ed->bsz;
164	me.donor_start = me.orig_start;
165	len = (io_u->offset + io_u->xfer_buflen + ed->bsz -1);
166	me.len = len / ed->bsz - me.orig_start;
167
168	ret = ioctl(f->fd, EXT4_IOC_MOVE_EXT, &me);
169	len = me.moved_len * ed->bsz;
170
171	if (len > io_u->xfer_buflen)
172		len = io_u->xfer_buflen;
173
174	if (len != io_u->xfer_buflen) {
175		if (len) {
176			io_u->resid = io_u->xfer_buflen - len;
177			io_u->error = 0;
178		} else {
179			/* access beyond i_size */
180			io_u->error = EINVAL;
181		}
182	}
183	if (ret)
184		io_u->error = errno;
185
186	if (o->inplace)
187		ret = ftruncate(ed->donor_fd, 0);
188out:
189	if (ret && !io_u->error)
190		io_u->error = errno;
191
192	return FIO_Q_COMPLETED;
193}
194
195static struct ioengine_ops ioengine = {
196	.name			= "e4defrag",
197	.version		= FIO_IOOPS_VERSION,
198	.init			= fio_e4defrag_init,
199	.queue			= fio_e4defrag_queue,
200	.open_file		= generic_open_file,
201	.close_file		= generic_close_file,
202	.get_file_size		= generic_get_file_size,
203	.flags			= FIO_SYNCIO,
204	.cleanup		= fio_e4defrag_cleanup,
205	.options		= options,
206	.option_struct_size	= sizeof(struct e4defrag_options),
207
208};
209
210static void fio_init fio_syncio_register(void)
211{
212	register_ioengine(&ioengine);
213}
214
215static void fio_exit fio_syncio_unregister(void)
216{
217	unregister_ioengine(&ioengine);
218}
219