generic: unaligned direct AIO write test
[xfstests-dev.git] / src / aio-dio-regress / aio-dio-write-verify.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2019 Red Hat, Inc. All Rights reserved.
4  *
5  * This program is used to do AIO write test. Each <-a size=N,off=M> field
6  * specifies an AIO write. More this kind of fields will be combined to a
7  * group of AIO write, then io_submit them together. All AIO write field can
8  * overlap or be sparse.
9  *
10  * After all AIO write operations done, each [size, off] content will be read
11  * back, verify if there's corruption.
12  *
13  * Before doing a series of AIO write, an optional ftruncate operation can be
14  * chosen. To truncate the file i_size to a specified location for testing.
15  *
16  */
17 #include <sys/stat.h>
18 #include <sys/types.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <ctype.h>
25
26 #include <libaio.h>
27
28 #define MAX_EVENT_NUM   128
29 /*
30  * Use same fill pattern each time, so even overlap write won't get
31  * different content
32  */
33 #define IO_PATTERN      0x5a
34
35 void usage(char *progname)
36 {
37         fprintf(stderr, "usage: %s [-t truncsize ] <-a size=N,off=M [-a ...]>  filename\n"
38                 "\t-t truncsize: truncate the file to a special size before AIO wirte\n"
39                 "\t-a: specify once AIO write size and startoff, this option can be specified many times, but less than 128\n"
40                 "\t\tsize=N: AIO write size\n"
41                 "\t\toff=M:  AIO write startoff\n"
42                 "e.g: %s -t 4608 -a size=4096,off=512 -a size=4096,off=4608 filename\n",
43                 progname, progname);
44         exit(1);
45 }
46
47 void dump_buffer(void *buf, off64_t offset, ssize_t len)
48 {
49         int     i, j;
50         char    *p;
51         int     new;
52
53         for (i = 0, p = (char *)buf; i < len; i += 16) {
54                 char    *s = p;
55
56                 if (i && !memcmp(p, p - 16, 16)) {
57                         new = 0;
58                 } else {
59                         if (i)
60                                 printf("*\n");
61                         new = 1;
62                 }
63
64                 if (!new) {
65                         p += 16;
66                         continue;
67                 }
68
69                 printf("%08llx  ", (unsigned long long)offset + i);
70                 for (j = 0; j < 16 && i + j < len; j++, p++)
71                         printf("%02x ", *p);
72                 printf(" ");
73                 for (j = 0; j < 16 && i + j < len; j++, s++) {
74                         if (isalnum((int)*s))
75                                 printf("%c", *s);
76                         else
77                                 printf(".");
78                 }
79                 printf("\n");
80
81         }
82         printf("%08llx\n", (unsigned long long)offset + i);
83 }
84
85 /* Parameters for once AIO write&verify testing */
86 struct io_params {
87         void          *buf;
88         void          *cmp_buf;
89         unsigned long buf_size; /* the size of AIO write */
90         unsigned long offset;   /* AIO write offset*/
91 };
92
93 struct io_params_node {
94         struct iocb iocb;
95         struct io_params *param;
96         struct io_params_node *next;
97 };
98
99 struct io_params_node *iop_head = NULL;
100
101 /* Suboptions of '-a' option */
102 enum {
103         BFS_OPT = 0,
104         OFS_OPT
105 };
106
107 char *const token[] = {
108         [BFS_OPT]       = "size",       /* buffer size of once AIO write */
109         [OFS_OPT]       = "off",        /* start offset */
110         NULL
111 };
112
113 /*
114  * Combine each AIO write operation things to a linked list.
115  *
116  * Note: There won't be link structure free, due to the process will
117  * exit directly when hit a error)
118  */
119 static int add_io_param(unsigned long bs, unsigned long off)
120 {
121         struct io_params_node *io = NULL;
122         struct io_params_node **p = &iop_head;
123
124         io = malloc(sizeof(struct io_params_node));
125         if (!io)
126                 goto err_out;
127
128         io->param = malloc(sizeof(struct io_params));
129         if (!io->param)
130                 goto err_out;
131
132         io->param->buf_size = bs;
133         io->param->offset = off;
134
135         io->next = NULL;
136
137         if (bs > 0) {
138                 if (posix_memalign(&io->param->buf, getpagesize(), bs))
139                         goto err_out;
140                 io->param->cmp_buf = malloc(bs);
141                 if (io->param->cmp_buf == NULL)
142                         goto err_out;
143                 memset(io->param->buf, IO_PATTERN, bs);
144                 memset(io->param->cmp_buf, IO_PATTERN, bs);
145         } else {
146                 return 1;
147         }
148
149         /* find the tail */
150         while(*p != NULL) {
151                 p = &((*p)->next);
152         }
153         *p = io;
154
155         return 0;
156
157  err_out:
158         perror("alloc memory");
159         return 1;
160 }
161
162 static int parse_subopts(char *arg)
163 {
164         char *p = arg;
165         char *value;
166         unsigned long bsize = 0;
167         unsigned long off = 0;
168
169         while (*p != '\0') {
170                 char *endp;
171
172                 switch(getsubopt(&p, token, &value)) {
173                 case BFS_OPT:
174                         bsize = strtoul(value, &endp, 0);
175                         break;
176                 case OFS_OPT:
177                         off = strtoul(value, &endp, 0);
178                         break;
179                 default:
180                         fprintf(stderr, "No match found for subopt %s\n",
181                                 value);
182                         return 1;
183                 }
184         }
185
186         return add_io_param(bsize, off);
187 }
188
189 static int io_write(int fd, int num_events)
190 {
191         int err;
192         struct io_params_node *p = iop_head;
193         struct iocb **iocbs;
194         struct io_event *evs;
195         struct io_context *ctx = NULL;
196         int i;
197
198         err = io_setup(num_events, &ctx);
199         if (err) {
200                 perror("io_setup");
201                 return 1;
202         }
203
204         iocbs = (struct iocb **)malloc(num_events * sizeof(struct iocb *));
205         if (iocbs == NULL) {
206                 perror("malloc");
207                 return 1;
208         }
209
210         evs = malloc(num_events * sizeof(struct io_event));
211         if (evs == NULL) {
212                 perror("malloc");
213                 return 1;
214         }
215
216         i = 0;
217         while (p != NULL) {
218                 iocbs[i] = &(p->iocb);
219                 io_prep_pwrite(&p->iocb, fd, p->param->buf,
220                                p->param->buf_size, p->param->offset);
221                 p = p->next;
222                 i++;
223         }
224
225         err = io_submit(ctx, num_events, iocbs);
226         if (err != num_events) {
227                 fprintf(stderr, "error %s during %s\n",
228                         strerror(err),
229                         "io_submit");
230                 return 1;
231         }
232
233         err = io_getevents(ctx, num_events, num_events, evs, NULL);
234         if (err != num_events) {
235                 fprintf(stderr, "error %s during %s\n",
236                         strerror(err),
237                         "io_getevents");
238                 return 1;
239         }
240
241         /* Try to destroy at here, not necessary, so don't check result */
242         io_destroy(ctx);
243
244         return 0;
245 }
246
247 static int io_verify(int fd)
248 {
249         struct io_params_node *p = iop_head;
250         ssize_t sret;
251         int corrupted = 0;
252
253         while(p != NULL) {
254                 sret = pread(fd, p->param->buf,
255                              p->param->buf_size, p->param->offset);
256                 if (sret == -1) {
257                         perror("pread");
258                         return 1;
259                 } else if (sret != p->param->buf_size) {
260                         fprintf(stderr, "short read %zd was less than %zu\n",
261                                 sret, p->param->buf_size);
262                         return 1;
263                 }
264                 if (memcmp(p->param->buf,
265                            p->param->cmp_buf, p->param->buf_size)) {
266                         printf("Find corruption\n");
267                         dump_buffer(p->param->buf, p->param->offset,
268                                     p->param->buf_size);
269                         corrupted++;
270                 }
271                 p = p->next;
272         }
273
274         return corrupted;
275 }
276
277 int main(int argc, char *argv[])
278 {
279         int fd;
280         int c;
281         char *filename = NULL;
282         int num_events = 0;
283         off_t tsize = 0;
284
285         while ((c = getopt(argc, argv, "a:t:")) != -1) {
286                 char *endp;
287
288                 switch (c) {
289                 case 'a':
290                         if (parse_subopts(optarg) == 0) {
291                                 num_events++;
292                         } else {
293                                 fprintf(stderr, "Bad subopt %s\n", optarg);
294                                 usage(argv[0]);
295                         }
296                         break;
297                 case 't':
298                         tsize = strtoul(optarg, &endp, 0);
299                         break;
300                 default:
301                         usage(argv[0]);
302                 }
303         }
304
305         if (num_events > MAX_EVENT_NUM) {
306                 fprintf(stderr, "Too many AIO events, %d > %d\n",
307                         num_events, MAX_EVENT_NUM);
308                 usage(argv[0]);
309         }
310
311         if (optind == argc - 1)
312                 filename = argv[optind];
313         else
314                 usage(argv[0]);
315
316         fd = open(filename, O_DIRECT | O_CREAT | O_TRUNC | O_RDWR, 0600);
317         if (fd == -1) {
318                 perror("open");
319                 return 1;
320         }
321
322         if (tsize > 0) {
323                 if (ftruncate(fd, tsize)) {
324                         perror("ftruncate");
325                         return 1;
326                 }
327         }
328
329         if (io_write(fd, num_events) != 0) {
330                 fprintf(stderr, "AIO write fails\n");
331                 return 1;
332         }
333
334         if (io_verify(fd) != 0) {
335                 fprintf(stderr, "Data verification fails\n");
336                 return 1;
337         }
338
339         close(fd);
340         return 0;
341 }