// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2014 Dmitry Monakhov.  All Rights Reserved.
 *
 * Perform aio writes to file and toggle O_DIRECT flag concurrently
 * this may trigger race between file->f_flags read and modification
 * unuligned aio allow to makes race window wider.
 * Regression test for https://lkml.org/lkml/2014/10/8/545 CVE-2014-8086
 * Patch proposed: http://www.spinics.net/lists/linux-ext4/msg45683.html
 */
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <libaio.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#define BUF_SIZE	512
#define LOOP_SECONDS 10


static int do_aio_loop(int fd, void *buf)
{
	int err, ret;
	struct io_context *ctx = NULL;
	struct io_event ev;
	struct iocb iocb, *iocbs[] = { &iocb };
	struct timeval start, now, delta = { 0, 0 };

	ret = 0;
	err = io_setup(1, &ctx);
	if (err) {
		fprintf(stderr, "error %s during %s\n",
			strerror(-err), "io_setup" );
		return 1;
	}
	gettimeofday(&start, NULL);
	while (1) {
		io_prep_pwrite(&iocb, fd, buf, BUF_SIZE, BUF_SIZE);
		err = io_submit(ctx, 1, iocbs);
		if (err != 1) {
			fprintf(stderr, "error %s during %s\n",
				strerror(-err),
				"io_submit");
			ret = 1;
			break;
		}
		err = io_getevents(ctx, 1, 1, &ev, NULL);
		if (err != 1) {
			fprintf(stderr, "error %s during %s\n",
				strerror(-err),
				"io_getevents");
			ret = 1;
			break;
		}
		gettimeofday(&now, NULL);
		timersub(&now, &start, &delta);
		if (delta.tv_sec >= LOOP_SECONDS)
			break;
	}
	io_destroy(ctx);
	return ret;
}

int main(int argc, char **argv)
{
	int flags, fd;
	int pid1, pid2 = 0;
	int ret1, ret = 0;

	if (argc != 2){
		printf("Usage %s fname\n", argv[0]);
		return 1;
	}
	fd = open(argv[1], O_CREAT | O_TRUNC | O_RDWR, 0600);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	pid1 = fork();
	if (pid1 < 0) {
		perror("fork");
		return 1;
	}

	if (pid1 == 0) {
		struct timeval start, now, delta = { 0, 0 };

		gettimeofday(&start, NULL);

		/* child: toggle O_DIRECT*/
		flags = fcntl(fd, F_GETFL);
		while (1) {
			ret = fcntl(fd, F_SETFL, flags | O_DIRECT);
			if (ret) {
				perror("fcntl O_DIRECT");
				return 1;
			}
			ret = fcntl(fd, F_SETFL, flags);
			if (ret) {
				perror("fcntl");
				return 1;
			}
			gettimeofday(&now, NULL);
			timersub(&now, &start, &delta);
			if (delta.tv_sec >= LOOP_SECONDS)
				break;
		}
	} else {
		/* parent: AIO */
		void *buf = NULL;
		int err;

		err = posix_memalign(&buf, BUF_SIZE, BUF_SIZE);
		if (err || buf == NULL) {
			fprintf(stderr, "posix_memalign failed: %s\n",
				strerror(err));
			exit(1);
		}
		/* Two tasks which performs unaligned aio will be serialized
		   which maks race window wider */
		pid2 = fork();
		if (pid2 < 0)
			goto out;
		else if (pid2 > 0)
			printf("All tasks are spawned\n");

		ret = do_aio_loop(fd, buf);
	}
out:
	/* Parent wait for all others */
	if (pid2 > 0){
		waitpid(pid1, &ret1, 0);
		if (!ret)
			ret = ret1;
		waitpid(pid2, &ret1, 0);
	} else {
		waitpid(pid1, &ret1, 0);
	}
	if (!ret)
		ret = ret1;

	return ret;
}
