blob: 302b8fe4a09605cc7c80d8538352a3bdbaa8fd12 [file] [log] [blame]
// 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;
}