// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2013 Alibaba Group.
 * Copyright (c) 2017 Red Hat Inc.
 * All Rights Reserved.
 *
 * This is a normal case that we do some append dio writes and meanwhile
 * we do some dio reads.  Currently in vfs we don't ensure that i_size
 * is updated properly.  Hence the reader will read some data with '0'.
 * But we expect that the reader should read nothing or data with 'a'.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <libaio.h>
#include <pthread.h>

struct io_data {
	int fd;
	size_t blksize;
	off_t offset;
	char *buf;
	int use_aio;
	ssize_t read_sz;
};

int reader_ready = 0;

static void usage(const char *prog)
{
	fprintf(stderr, "usage: %s [-a] <file>\n", prog);
	fprintf(stderr, "\t-a\tuse aio-dio\n");
	exit(EXIT_FAILURE);
}

static void *reader(void *arg)
{
	struct io_data *data = (struct io_data *)arg;

	memset(data->buf, 'b', data->blksize);
	reader_ready = 1;
	do {
		data->read_sz = pread(data->fd, data->buf, data->blksize,
				      data->offset);
		if (data->read_sz < 0)
			perror("read file");
	} while (data->read_sz <= 0);

	return NULL;
}

#define fail(fmt , args...) do {	\
	fprintf(stderr, fmt , ##args);	\
	exit(1);			\
} while (0)

static void *writer(struct io_data *data)
{
	int ret;

	while (!reader_ready)
		usleep(1);

	if (data->use_aio) {
		struct io_context *ctx = NULL;
		struct io_event evs[1];
		struct iocb iocb;
		struct iocb *iocbs[] = { &iocb };

		ret = io_setup(1, &ctx);
		if (ret)
			fail("error %s during io_setup\n", strerror(ret));
		io_prep_pwrite(&iocb, data->fd, data->buf, data->blksize, data->offset);
		ret = io_submit(ctx, 1, iocbs);
		if (ret != 1)
			fail("error %s during io_submit\n", strerror(ret));
		ret = io_getevents(ctx, 1, 1, evs, NULL);
		if (ret != 1)
			fail("error %s during io_getevents\n", strerror(ret));
		if ((signed)evs[0].res < 0)
			fail("error %s during write\n", strerror(-evs[0].res));
		if ((signed)evs[0].res2 < 0)
			fail("secondary error %s during write\n", strerror(-evs[0].res2));
	} else {
		ret = pwrite(data->fd, data->buf, data->blksize, data->offset);
		if (ret < 0)
			fail("write file failed: %s", strerror(errno));
	}

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t tid;
	struct io_data wdata;
	struct io_data rdata;
	size_t max_blocks = 128;		/* 128 */
	size_t blksize = 1 * 1024 * 1024;	/* 1M */
	char *rbuf = NULL, *wbuf = NULL;
	int rfd = 0, wfd = 0;
	int i, j, c;
	int use_aio = 1;
	int ret = 0;
	int io_align = 4096;
	char *prog;
	char *testfile;


	prog = basename(argv[0]);

	while ((c = getopt(argc, argv, "a:d")) != -1) {
		switch (c) {
		case 'a':
			io_align = strtol(optarg, NULL, 0);
			break;
		case 'd':
			use_aio = 0;
			break;
		default:
			usage(prog);
		}
	}
	if (optind != argc - 1)
		usage(prog);
	testfile = argv[optind];

	wfd = open(testfile, O_CREAT|O_DIRECT|O_WRONLY|O_TRUNC, 0644);
	if (wfd < 0) {
		perror("open for write");
		exit(1);
	}

	rfd = open(testfile, O_DIRECT|O_RDONLY, 0644);
	if (rfd < 0) {
		perror("open for read");
		ret = 1;
		goto err;
	}

	ret = posix_memalign((void **)&wbuf, io_align, blksize);
	if (ret) {
		fail("failed to alloc memory: %s\n", strerror(ret));
		ret = 1;
		goto err;
	}

	ret = posix_memalign((void **)&rbuf, io_align, blksize);
	if (ret) {
		fail("failed to alloc memory: %s\n", strerror(ret));
		ret = 1;
		goto err;
	}

	memset(wbuf, 'a', blksize);
	wdata.fd = wfd;
	wdata.blksize = blksize;
	wdata.buf = wbuf;
	wdata.use_aio = use_aio;
	rdata.fd = rfd;
	rdata.blksize = blksize;
	rdata.buf = rbuf;

	for (i = 0; i < max_blocks; i++) {
		wdata.offset = rdata.offset = i * blksize;

		/* reset reader_ready, it will be set in reader thread */
		reader_ready = 0;
		ret = pthread_create(&tid, NULL, reader, &rdata);
		if (ret) {
			fail("create reader thread failed: %s\n", strerror(ret));
			ret = 1;
			goto err;
		}

		writer(&wdata);

		ret = pthread_join(tid, NULL);
		if (ret) {
			fail("pthread join reader failed: %s\n", strerror(ret));
			ret = 1;
			goto err;
		}

		for (j = 0; j < rdata.read_sz; j++) {
			if (rdata.buf[j] != 'a') {
				fail("encounter an error: "
					"block %d offset %d, content %x\n",
					i, j, rbuf[j]);
				ret = 1;
				goto err;
			}
		}
	}

err:
	if (rfd)
		close(rfd);
	if (wfd)
		close(wfd);
	if (rbuf)
		free(rbuf);
	if (wbuf)
		free(wbuf);

	exit(ret);
}
