1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2004 Stephen Hemminger <shemminger@osdl.org>
4 * Copyright (c) 2004 Daniel McNeil <daniel@osdl.org>
5 * Copyright (c) 2004 Marty Ridgeway <mridge@us.ibm.com>
6 * Copyright (C) 2021 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
7 */
8
9 /*\
10 * [Description]
11 *
12 * Copy file by using an async I/O state machine.
13 *
14 * - Start read request
15 * - When read completes turn it into a write request
16 * - When write completes decrement counter and free up resources
17 */
18
19 #define _GNU_SOURCE
20
21 #include "tst_test.h"
22
23 #ifdef HAVE_LIBAIO
24 #include <libaio.h>
25 #include <string.h>
26 #include <limits.h>
27 #include <sys/stat.h>
28 #include "common.h"
29
30 #include "tst_rand_data.h"
31
32 static const char *srcname = "srcfile.bin";
33 static const char *dstname = "dstfile.bin";
34
35 static char *str_aio_blksize;
36 static char *str_filesize;
37 static char *str_aionum;
38 static char *str_oflag;
39
40 static long long aio_blksize = 64 * 1024;
41 static long long filesize = 1 * 1024 * 1024;
42 static long long alignment;
43 static int aionum = 16;
44 static int srcflags = O_RDONLY;
45 static int dstflags = O_WRONLY;
46
47 static int srcfd;
48 static int dstfd;
49 static long long busy;
50 static long long tocopy;
51 static struct iocb **iocb_free;
52 static int iocb_free_count;
53
54 #ifndef howmany
55 # define howmany(x, y) (((x) + ((y) - 1)) / (y))
56 #endif
57
fill_with_rand_data(int fd,long long size)58 static void fill_with_rand_data(int fd, long long size)
59 {
60 long long i = size;
61
62 while (i > 0) {
63 SAFE_WRITE(1, fd, tst_rand_data,
64 MIN((long long)tst_rand_data_len, i));
65 i -= tst_rand_data_len;
66 }
67 SAFE_FSYNC(fd);
68 }
69
async_init(void)70 static void async_init(void)
71 {
72 int i;
73 char *buff;
74
75 iocb_free = SAFE_MALLOC(aionum * sizeof(struct iocb *));
76 for (i = 0; i < aionum; i++) {
77 iocb_free[i] = SAFE_MALLOC(sizeof(struct iocb));
78 buff = SAFE_MEMALIGN(alignment, aio_blksize);
79
80 io_prep_pread(iocb_free[i], -1, buff, aio_blksize, 0);
81 }
82
83 iocb_free_count = i;
84 }
85
get_iocb(void)86 static struct iocb *get_iocb(void)
87 {
88 if (!iocb_free_count)
89 return 0;
90
91 return iocb_free[--iocb_free_count];
92 }
93
put_iocb(struct iocb * io)94 static void put_iocb(struct iocb *io)
95 {
96 iocb_free[iocb_free_count++] = io;
97 }
98
async_write_done(LTP_ATTRIBUTE_UNUSED io_context_t ctx,struct iocb * iocb,long res,long res2)99 static void async_write_done(LTP_ATTRIBUTE_UNUSED io_context_t ctx, struct iocb *iocb, long res, long res2)
100 {
101 int iosize = iocb->u.c.nbytes;
102
103 if (res != iosize)
104 tst_brk(TBROK, "Write missing bytes expect %d got %ld", iosize, res);
105
106 if (res2 != 0)
107 tst_brk(TBROK, "Write error: %s", tst_strerrno(-res2));
108
109 put_iocb(iocb);
110
111 --busy;
112 --tocopy;
113
114 if (dstflags & O_DIRECT)
115 SAFE_FSYNC(dstfd);
116
117 if (!tst_remaining_runtime())
118 tst_brk(TCONF, "Out of runtime!");
119 }
120
async_copy(io_context_t ctx,struct iocb * iocb,long res,long res2)121 static void async_copy(io_context_t ctx, struct iocb *iocb, long res, long res2)
122 {
123 int iosize = iocb->u.c.nbytes;
124 char *buf = iocb->u.c.buf;
125 off_t offset = iocb->u.c.offset;
126 int w;
127
128 if (res != iosize)
129 tst_brk(TBROK, "Read missing bytes expect %d got %ld", iosize, res);
130
131 if (res2 != 0)
132 tst_brk(TBROK, "Read error: %s", tst_strerrno(-res2));
133
134 io_prep_pwrite(iocb, dstfd, buf, iosize, offset);
135 io_set_callback(iocb, async_write_done);
136
137 w = io_submit(ctx, 1, &iocb);
138 if (w < 0)
139 tst_brk(TBROK, "io_submit error: %s", tst_strerrno(-w));
140 }
141
io_wait_run(io_context_t ctx,struct timespec * to)142 static void io_wait_run(io_context_t ctx, struct timespec *to)
143 {
144 struct io_event events[aionum];
145 struct io_event *ep;
146 int n;
147
148 n = io_getevents(ctx, 1, aionum, events, to);
149 if (n < 0)
150 tst_brk(TBROK, "io_getevents() failed: %s", tst_strerrno(-n));
151
152 for (ep = events; n-- > 0; ep++) {
153 io_callback_t cb = (io_callback_t) ep->data;
154 struct iocb *iocb = ep->obj;
155
156 cb(ctx, iocb, ep->res, ep->res2);
157 }
158 }
159
async_run(io_context_t ctx,int fd,io_callback_t cb)160 static void async_run(io_context_t ctx, int fd, io_callback_t cb)
161 {
162 long long offset = 0;
163 int rc, i, n;
164 int iosize;
165
166 tocopy = filesize / aio_blksize;
167 busy = 0;
168
169 while (tocopy > 0) {
170 n = MIN(aionum - busy, tocopy);
171
172 if (n > 0) {
173 struct iocb *ioq[n];
174
175 for (i = 0; i < n; i++) {
176 struct iocb *io = get_iocb();
177
178 iosize = MIN(filesize - offset, aio_blksize);
179
180 /* If we don't have any byte to write, exit */
181 if (iosize <= 0)
182 break;
183
184 io_prep_pread(io, fd, io->u.c.buf, iosize, offset);
185 io_set_callback(io, cb);
186
187 ioq[i] = io;
188 offset += iosize;
189 }
190
191 rc = io_submit(ctx, i, ioq);
192 if (rc < 0)
193 tst_brk(TBROK, "io_submit write error: %s", tst_strerrno(-rc));
194
195 busy += n;
196 }
197
198 io_wait_run(ctx, 0);
199 }
200 }
201
setup(void)202 static void setup(void)
203 {
204 int maxaio;
205 long long leftover;
206
207 if (tst_parse_int(str_aionum, &aionum, 1, INT_MAX))
208 tst_brk(TBROK, "Invalid number of I/O '%s'", str_aionum);
209
210 SAFE_FILE_SCANF("/proc/sys/fs/aio-max-nr", "%d", &maxaio);
211 tst_res(TINFO, "Maximum AIO blocks: %d", maxaio);
212
213 if (aionum > maxaio)
214 tst_res(TCONF, "Number of async IO blocks passed the maximum (%d)", maxaio);
215
216 if (tst_parse_filesize(str_aio_blksize, &aio_blksize, 1, LLONG_MAX))
217 tst_brk(TBROK, "Invalid write blocks size '%s'", str_aio_blksize);
218
219 if (str_oflag) {
220 if (strncmp(str_oflag, "SYNC", 4) == 0) {
221 dstflags |= O_SYNC;
222 } else if (strncmp(str_oflag, "DIRECT", 6) == 0) {
223 if (tst_fs_type(".") == TST_TMPFS_MAGIC)
224 tst_brk(TCONF, "O_DIRECT not supported on tmpfs");
225
226 srcflags |= O_DIRECT;
227 dstflags |= O_DIRECT;
228 }
229 }
230
231 if (tst_fs_type(".") == TST_TMPFS_MAGIC)
232 alignment = getpagesize();
233 else
234 alignment = tst_dev_block_size(".");
235
236 if (dstflags & O_DIRECT && aio_blksize % alignment)
237 tst_brk(TCONF, "Block size is not multiple of drive block size");
238
239 if (tst_parse_filesize(str_filesize, &filesize, 1, LLONG_MAX))
240 tst_brk(TBROK, "Invalid file size '%s'", str_filesize);
241
242 leftover = filesize % aio_blksize;
243
244 if (leftover > 0) {
245 filesize = filesize - leftover + aio_blksize;
246
247 tst_res(TINFO, "Rounded filesize to the next block size multiple: %llu", filesize);
248 }
249
250 tst_res(TINFO, "Fill %s with random data", srcname);
251
252 srcfd = SAFE_OPEN(srcname, (srcflags & ~O_DIRECT) | O_RDWR | O_CREAT, 0666);
253 fill_with_rand_data(srcfd, filesize);
254 SAFE_CLOSE(srcfd);
255 }
256
cleanup(void)257 static void cleanup(void)
258 {
259 if (srcfd > 0)
260 SAFE_CLOSE(srcfd);
261
262 if (dstfd > 0)
263 SAFE_CLOSE(dstfd);
264 }
265
run(void)266 static void run(void)
267 {
268 const int buffsize = 4096;
269 io_context_t myctx;
270 struct stat st;
271 char srcbuff[buffsize];
272 char dstbuff[buffsize];
273 int reads = 0;
274 int i, r;
275
276 srcfd = SAFE_OPEN(srcname, srcflags | O_RDWR | O_CREAT, 0666);
277 dstfd = SAFE_OPEN(dstname, dstflags | O_WRONLY | O_CREAT, 0666);
278
279 tst_res(TINFO, "Copy %s -> %s", srcname, dstname);
280
281 memset(&myctx, 0, sizeof(myctx));
282 io_queue_init(aionum, &myctx);
283
284 async_init();
285 async_run(myctx, srcfd, async_copy);
286
287 io_destroy(myctx);
288 SAFE_CLOSE(srcfd);
289 SAFE_CLOSE(dstfd);
290
291 tst_res(TINFO, "Comparing %s with %s", srcname, dstname);
292
293 SAFE_STAT(dstname, &st);
294 if (st.st_size != filesize) {
295 tst_res(TFAIL, "Expected destination file size %lld but it's %ld", filesize, st.st_size);
296 /* no need to compare files */
297 return;
298 }
299
300 srcfd = SAFE_OPEN(srcname, srcflags | O_RDONLY, 0666);
301 dstfd = SAFE_OPEN(dstname, srcflags | O_RDONLY, 0666);
302
303 reads = howmany(filesize, buffsize);
304
305 for (i = 0; i < reads; i++) {
306 r = SAFE_READ(0, srcfd, srcbuff, buffsize);
307 SAFE_READ(0, dstfd, dstbuff, buffsize);
308 if (memcmp(srcbuff, dstbuff, r)) {
309 tst_res(TFAIL, "Files are not identical");
310 return;
311 }
312 }
313
314 tst_res(TPASS, "Files are identical");
315
316 SAFE_CLOSE(srcfd);
317 SAFE_CLOSE(dstfd);
318 }
319
320 static struct tst_test test = {
321 .test_all = run,
322 .setup = setup,
323 .cleanup = cleanup,
324 .needs_tmpdir = 1,
325 .max_runtime = 1800,
326 .needs_root = 1,
327 .options = (struct tst_option[]) {
328 {"b:", &str_aio_blksize, "Size of writing blocks (default 1K)"},
329 {"s:", &str_filesize, "Size of file (default 10M)"},
330 {"n:", &str_aionum, "Number of Async IO blocks (default 16)"},
331 {"f:", &str_oflag, "Open flag: SYNC | DIRECT (default O_CREAT only)"},
332 {},
333 },
334 };
335 #else
336 TST_TEST_TCONF("test requires libaio and its development packages");
337 #endif
338