// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2007 Zach Brown
 *
 * Test sync DIO invalidation of readahead
 */
#define _XOPEN_SOURCE 500 /* pwrite */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libaio.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <malloc.h>

/*
 * sync DIO invalidates the read cache after it finishes the write.  This
 * is to invalidate cached pages which might have been brought in during
 * the write.
 *
 * In http://lkml.org/lkml/2007/10/26/478 a user reported this failing
 * for his case of readers and writers racing.  It turned out that his
 * reader wasn't actually racing with the writer, but read-ahead from
 * the reader pushed reads up into the region that the writer was working
 * on.
 *
 * This test reproduces his case.  We have a writing thread tell
 * a reading thread how far into the file it will find new data.
 * The reader reads behind the writer, checking for stale data.
 * If the kernel fails to invalidate the read-ahead after the
 * write then the reader will see stale data.
 */
#ifndef O_DIRECT
#define O_DIRECT         040000 /* direct disk access hint */
#endif

#define FILE_SIZE (8 * 1024 * 1024)

/* this test always failed before 10 seconds on a single spindle */
#define SECONDS 30

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

int page_size;

pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
loff_t write_pos = 0;
loff_t read_pos = 0;
unsigned char byte = 0;

static void *writer(void *arg)
{
	char *path = arg;
	loff_t off;
	void *buf;
	int ret;
	int fd;
	time_t start = time(NULL);

	buf = memalign(page_size, page_size);
	if (buf == NULL)
		fail("failed to allocate an aligned page");

	fd = open(path, O_DIRECT|O_CREAT|O_RDWR|O_TRUNC, 0644);
	if (fd < 0)
		fail("dio open failed: %d\n", errno);

	while (1) {
		if ((time(NULL) - start) > SECONDS) {
			printf("test ran for %u seconds without error\n",
			       SECONDS);
			exit(0);
		}

		pthread_mutex_lock(&mut);
		while (read_pos != write_pos)
			pthread_cond_wait(&cond, &mut);
		byte++;
		write_pos = 0;
		pthread_mutex_unlock(&mut);

		memset(buf, byte, page_size);

		for (off = 0; off < FILE_SIZE; off += page_size) {

			ret = pwrite(fd, buf, page_size, off);
			if (ret != page_size)
				fail("write returned %d", ret);

			if ((rand() % 4) == 0) {
				pthread_mutex_lock(&mut);
				write_pos = off;
				pthread_cond_signal(&cond);
				pthread_mutex_unlock(&mut);
			};
		}
	}
}

static void *reader(void *arg)
{
	char *path = arg;
	unsigned char old;
	loff_t read_to = 0;
	void *found;
	int fd;
	int ret;
	void *buf;
	loff_t off;

	setvbuf(stdout, NULL, _IONBF, 0);

	buf = memalign(page_size, page_size);
	if (buf == NULL)
		fail("failed to allocate an aligned page");

	fd = open(path, O_CREAT|O_RDONLY, 0644);
	if (fd < 0)
		fail("buffered open failed: %d\n", errno);

	while (1) {
		pthread_mutex_lock(&mut);
		read_pos = read_to;
		pthread_cond_signal(&cond);
		while (read_pos == write_pos)
			pthread_cond_wait(&cond, &mut);
		read_to = write_pos;
		off = read_pos;
		old = byte - 1;
		pthread_mutex_unlock(&mut);

		for (; off < read_to; off += page_size) {

			ret = pread(fd, buf, page_size, off);
			if (ret != page_size)
				fail("write returned %d", ret);

			found = memchr(buf, old, page_size);
			if (found)
				fail("reader found old byte at pos %lu",
				     (unsigned long)off +
				     (unsigned long)found -
				     (unsigned long)buf);
		}
	}
}

int main(int argc, char **argv)
{
	pthread_t reader_thread;
	pthread_t writer_thread;
	int ret;

	page_size = getpagesize();

	if (argc != 2)
		fail("only arg should be file name");

	ret = pthread_create(&writer_thread, NULL, writer, argv[1]);
	if (ret == 0)
		ret = pthread_create(&reader_thread, NULL, reader, argv[1]);
	if (ret)
		fail("failed to start reader and writer threads: %d", ret);

	pthread_join(writer_thread, NULL);
	pthread_join(reader_thread, NULL);
	exit(0);
}
