// SPDX-License-Identifier: GPL-2.0-or-newer
/*
 * Copyright (c) 2019 Oracle.
 * All Rights Reserved.
 *
 * Race appending aio dio and fallocate to make sure we get the correct file
 * size afterwards.
 */
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <libaio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>

static int fd;
static int blocksize;

static void *
falloc_thread(
	void		*p)
{
	int		ret;

	ret = fallocate(fd, 0, 0, blocksize);
	if (ret)
		perror("falloc");

	return NULL;
}

static int
test(
	const char	*fname,
	unsigned int	iteration,
	unsigned int	*passed)
{
	struct stat	sbuf;
	pthread_t	thread;
	io_context_t	ioctx = 0;
	struct iocb	iocb;
	struct iocb	*iocbp = &iocb;
	struct io_event	event;
	char		*buf;
	bool		wait_thread = false;
	int		ret;

	/* Truncate file, allocate resources for doing IO. */
	fd = open(fname, O_DIRECT | O_RDWR | O_TRUNC | O_CREAT, 0644);
	if (fd < 0) {
		perror(fname);
		return -1;
	}

	ret = fstat(fd, &sbuf);
	if (ret) {
		perror(fname);
		goto out;
	}
	blocksize = sbuf.st_blksize;

	ret = posix_memalign((void **)&buf, sysconf(_SC_PAGESIZE), blocksize);
	if (ret) {
		errno = ret;
		perror("buffer");
		goto out;
	}
	memset(buf, 'X', blocksize);
	memset(&event, 0, sizeof(event));

	ret = io_queue_init(1, &ioctx);
	if (ret) {
		errno = -ret;
		perror("io_queue_init");
		goto out_buf;
	}

	/*
	 * Set ourselves up to race fallocate(0..blocksize) with aio dio
	 * pwrite(blocksize..blocksize * 2).  This /should/ give us a file
	 * with length (2 * blocksize).
	 */
	io_prep_pwrite(&iocb, fd, buf, blocksize, blocksize);

	ret = pthread_create(&thread, NULL, falloc_thread, NULL);
	if (ret) {
		errno = ret;
		perror("pthread");
		goto out_io;
	}
	wait_thread = true;

	ret = io_submit(ioctx, 1, &iocbp);
	if (ret != 1) {
		errno = -ret;
		perror("io_submit");
		goto out_join;
	}

	ret = io_getevents(ioctx, 1, 1, &event, NULL);
	if (ret != 1) {
		errno = -ret;
		perror("io_getevents");
		goto out_join;
	}

	if (event.res < 0) {
		errno = -event.res;
		perror("io_event.res");
		goto out_join;
	}

	if (event.res2 < 0) {
		errno = -event.res2;
		perror("io_event.res2");
		goto out_join;
	}

	wait_thread = false;
	ret = pthread_join(thread, NULL);
	if (ret) {
		errno = ret;
		perror("join");
		goto out_io;
	}

	/* Make sure we actually got a file of size (2 * blocksize). */
	ret = fstat(fd, &sbuf);
	if (ret) {
		perror(fname);
		goto out_buf;
	}

	if (sbuf.st_size != 2 * blocksize) {
		fprintf(stderr, "[%u]: sbuf.st_size=%llu, expected %llu.\n",
				iteration,
				(unsigned long long)sbuf.st_size,
				(unsigned long long)2 * blocksize);
	} else {
		printf("[%u]: passed.\n", iteration);
		(*passed)++;
	}

out_join:
	if (wait_thread) {
		ret = pthread_join(thread, NULL);
		if (ret) {
			errno = ret;
			perror("join");
			goto out_io;
		}
	}
out_io:
	ret = io_queue_release(ioctx);
	if (ret) {
		errno = -ret;
		perror("io_queue_release");
	}

out_buf:
	free(buf);
out:
	ret = close(fd);
	fd = -1;
	if (ret) {
		perror("close");
		return -1;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	int		ret;
	long		l;
	unsigned int	i;
	unsigned int	passed = 0;

	if (argc != 3) {
		printf("Usage: %s filename iterations\n", argv[0]);
		return 1;
	}

	errno = 0;
	l = strtol(argv[2], NULL, 0);
	if (errno) {
		perror(argv[2]);
		return 1;
	}
	if (l < 1 || l > UINT_MAX) {
		fprintf(stderr, "%ld: must be between 1 and %u.\n",
				l, UINT_MAX);
		return 1;
	}

	for (i = 0; i < l; i++) {
		ret = test(argv[1], i, &passed);
		if (ret)
			return 1;
	}

	printf("pass rate: %u/%u (%.2f%%)\n", passed, i, 100.0 * passed / i);

	return 0;
}
