| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2019 Red Hat, Inc. All Rights reserved. |
| * |
| * This program is used to do AIO write test. Each <-a size=N,off=M> field |
| * specifies an AIO write. More this kind of fields will be combined to a |
| * group of AIO write, then io_submit them together. All AIO write field can |
| * overlap or be sparse. |
| * |
| * After all AIO write operations done, each [size, off] content will be read |
| * back, verify if there's corruption. |
| * |
| * Before doing a series of AIO write, an optional ftruncate operation can be |
| * chosen. To truncate the file i_size to a specified location for testing. |
| * |
| */ |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| |
| #include <libaio.h> |
| |
| #define MAX_EVENT_NUM 128 |
| /* |
| * Use same fill pattern each time, so even overlap write won't get |
| * different content |
| */ |
| #define IO_PATTERN 0x5a |
| |
| void usage(char *progname) |
| { |
| fprintf(stderr, "usage: %s [-t truncsize ] <-a size=N,off=M [-a ...]> filename\n" |
| "\t-t truncsize: truncate the file to a special size before AIO wirte\n" |
| "\t-a: specify once AIO write size and startoff, this option can be specified many times, but less than 128\n" |
| "\t\tsize=N: AIO write size\n" |
| "\t\toff=M: AIO write startoff\n" |
| "e.g: %s -t 4608 -a size=4096,off=512 -a size=4096,off=4608 filename\n", |
| progname, progname); |
| exit(1); |
| } |
| |
| void dump_buffer(void *buf, off64_t offset, ssize_t len) |
| { |
| int i, j; |
| char *p; |
| int new; |
| |
| for (i = 0, p = (char *)buf; i < len; i += 16) { |
| char *s = p; |
| |
| if (i && !memcmp(p, p - 16, 16)) { |
| new = 0; |
| } else { |
| if (i) |
| printf("*\n"); |
| new = 1; |
| } |
| |
| if (!new) { |
| p += 16; |
| continue; |
| } |
| |
| printf("%08llx ", (unsigned long long)offset + i); |
| for (j = 0; j < 16 && i + j < len; j++, p++) |
| printf("%02x ", *p); |
| printf(" "); |
| for (j = 0; j < 16 && i + j < len; j++, s++) { |
| if (isalnum((int)*s)) |
| printf("%c", *s); |
| else |
| printf("."); |
| } |
| printf("\n"); |
| |
| } |
| printf("%08llx\n", (unsigned long long)offset + i); |
| } |
| |
| /* Parameters for once AIO write&verify testing */ |
| struct io_params { |
| void *buf; |
| void *cmp_buf; |
| unsigned long buf_size; /* the size of AIO write */ |
| unsigned long offset; /* AIO write offset*/ |
| }; |
| |
| struct io_params_node { |
| struct iocb iocb; |
| struct io_params *param; |
| struct io_params_node *next; |
| }; |
| |
| struct io_params_node *iop_head = NULL; |
| |
| /* Suboptions of '-a' option */ |
| enum { |
| BFS_OPT = 0, |
| OFS_OPT |
| }; |
| |
| char *const token[] = { |
| [BFS_OPT] = "size", /* buffer size of once AIO write */ |
| [OFS_OPT] = "off", /* start offset */ |
| NULL |
| }; |
| |
| /* |
| * Combine each AIO write operation things to a linked list. |
| * |
| * Note: There won't be link structure free, due to the process will |
| * exit directly when hit a error) |
| */ |
| static int add_io_param(unsigned long bs, unsigned long off) |
| { |
| struct io_params_node *io = NULL; |
| struct io_params_node **p = &iop_head; |
| |
| io = malloc(sizeof(struct io_params_node)); |
| if (!io) |
| goto err_out; |
| |
| io->param = malloc(sizeof(struct io_params)); |
| if (!io->param) |
| goto err_out; |
| |
| io->param->buf_size = bs; |
| io->param->offset = off; |
| |
| io->next = NULL; |
| |
| if (bs > 0) { |
| if (posix_memalign(&io->param->buf, getpagesize(), bs)) |
| goto err_out; |
| io->param->cmp_buf = malloc(bs); |
| if (io->param->cmp_buf == NULL) |
| goto err_out; |
| memset(io->param->buf, IO_PATTERN, bs); |
| memset(io->param->cmp_buf, IO_PATTERN, bs); |
| } else { |
| return 1; |
| } |
| |
| /* find the tail */ |
| while(*p != NULL) { |
| p = &((*p)->next); |
| } |
| *p = io; |
| |
| return 0; |
| |
| err_out: |
| perror("alloc memory"); |
| return 1; |
| } |
| |
| static int parse_subopts(char *arg) |
| { |
| char *p = arg; |
| char *value; |
| unsigned long bsize = 0; |
| unsigned long off = 0; |
| |
| while (*p != '\0') { |
| char *endp; |
| |
| switch(getsubopt(&p, token, &value)) { |
| case BFS_OPT: |
| bsize = strtoul(value, &endp, 0); |
| break; |
| case OFS_OPT: |
| off = strtoul(value, &endp, 0); |
| break; |
| default: |
| fprintf(stderr, "No match found for subopt %s\n", |
| value); |
| return 1; |
| } |
| } |
| |
| return add_io_param(bsize, off); |
| } |
| |
| static int io_write(int fd, int num_events) |
| { |
| int err; |
| struct io_params_node *p = iop_head; |
| struct iocb **iocbs; |
| struct io_event *evs; |
| struct io_context *ctx = NULL; |
| int i; |
| |
| err = io_setup(num_events, &ctx); |
| if (err) { |
| perror("io_setup"); |
| return 1; |
| } |
| |
| iocbs = (struct iocb **)malloc(num_events * sizeof(struct iocb *)); |
| if (iocbs == NULL) { |
| perror("malloc"); |
| return 1; |
| } |
| |
| evs = malloc(num_events * sizeof(struct io_event)); |
| if (evs == NULL) { |
| perror("malloc"); |
| return 1; |
| } |
| |
| i = 0; |
| while (p != NULL) { |
| iocbs[i] = &(p->iocb); |
| io_prep_pwrite(&p->iocb, fd, p->param->buf, |
| p->param->buf_size, p->param->offset); |
| p = p->next; |
| i++; |
| } |
| |
| err = io_submit(ctx, num_events, iocbs); |
| if (err != num_events) { |
| fprintf(stderr, "error %s during %s\n", |
| strerror(err), |
| "io_submit"); |
| return 1; |
| } |
| |
| err = io_getevents(ctx, num_events, num_events, evs, NULL); |
| if (err != num_events) { |
| fprintf(stderr, "error %s during %s\n", |
| strerror(err), |
| "io_getevents"); |
| return 1; |
| } |
| |
| /* Try to destroy at here, not necessary, so don't check result */ |
| io_destroy(ctx); |
| |
| return 0; |
| } |
| |
| static int io_verify(int fd) |
| { |
| struct io_params_node *p = iop_head; |
| ssize_t sret; |
| int corrupted = 0; |
| |
| while(p != NULL) { |
| sret = pread(fd, p->param->buf, |
| p->param->buf_size, p->param->offset); |
| if (sret == -1) { |
| perror("pread"); |
| return 1; |
| } else if (sret != p->param->buf_size) { |
| fprintf(stderr, "short read %zd was less than %zu\n", |
| sret, p->param->buf_size); |
| return 1; |
| } |
| if (memcmp(p->param->buf, |
| p->param->cmp_buf, p->param->buf_size)) { |
| printf("Find corruption\n"); |
| dump_buffer(p->param->buf, p->param->offset, |
| p->param->buf_size); |
| corrupted++; |
| } |
| p = p->next; |
| } |
| |
| return corrupted; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int fd; |
| int c; |
| char *filename = NULL; |
| int num_events = 0; |
| off_t tsize = 0; |
| |
| while ((c = getopt(argc, argv, "a:t:")) != -1) { |
| char *endp; |
| |
| switch (c) { |
| case 'a': |
| if (parse_subopts(optarg) == 0) { |
| num_events++; |
| } else { |
| fprintf(stderr, "Bad subopt %s\n", optarg); |
| usage(argv[0]); |
| } |
| break; |
| case 't': |
| tsize = strtoul(optarg, &endp, 0); |
| break; |
| default: |
| usage(argv[0]); |
| } |
| } |
| |
| if (num_events > MAX_EVENT_NUM) { |
| fprintf(stderr, "Too many AIO events, %d > %d\n", |
| num_events, MAX_EVENT_NUM); |
| usage(argv[0]); |
| } |
| |
| if (optind == argc - 1) |
| filename = argv[optind]; |
| else |
| usage(argv[0]); |
| |
| fd = open(filename, O_DIRECT | O_CREAT | O_TRUNC | O_RDWR, 0600); |
| if (fd == -1) { |
| perror("open"); |
| return 1; |
| } |
| |
| if (tsize > 0) { |
| if (ftruncate(fd, tsize)) { |
| perror("ftruncate"); |
| return 1; |
| } |
| } |
| |
| if (io_write(fd, num_events) != 0) { |
| fprintf(stderr, "AIO write fails\n"); |
| return 1; |
| } |
| |
| if (io_verify(fd) != 0) { |
| fprintf(stderr, "Data verification fails\n"); |
| return 1; |
| } |
| |
| close(fd); |
| return 0; |
| } |