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