--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Red Hat, Inc. All Rights Reserved.
+ * Written by Andreas Gruenbacher (agruenba@redhat.com)
+ */
+
+/* Trigger page faults in the same file during read and write. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* to get definition of O_DIRECT flag. */
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+
+char *filename;
+unsigned int page_size;
+void *page;
+char *addr;
+int fd;
+ssize_t ret;
+
+/*
+ * Leave a hole at the beginning of the test file and initialize a block of
+ * @page_size bytes at offset @page_size to @c. Then, reopen the file and
+ * mmap the first two pages.
+ */
+void init(char c, int flags)
+{
+ fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_DIRECT, 0666);
+ if (fd == -1)
+ goto fail;
+ memset(page, c, page_size);
+ ret = pwrite(fd, page, page_size, page_size);
+ if (ret != page_size)
+ goto fail;
+ if (close(fd))
+ goto fail;
+
+ fd = open(filename, flags);
+ if (fd == -1)
+ goto fail;
+ addr = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED)
+ err(1, NULL);
+ return;
+
+fail:
+ err(1, "%s", filename);
+}
+
+void done(void)
+{
+ if (fsync(fd))
+ goto fail;
+ if (close(fd))
+ goto fail;
+ return;
+
+fail:
+ err(1, "%s", filename);
+}
+
+static ssize_t do_read(int fd, void *buf, size_t count, off_t offset)
+{
+ ssize_t count2 = 0, ret;
+
+ do {
+ ret = pread(fd, buf, count, offset);
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (ret == 0)
+ break;
+ count2 += ret;
+ buf += ret;
+ count -= ret;
+ } while (count);
+ return count2;
+}
+
+static ssize_t do_write(int fd, const void *buf, size_t count, off_t offset)
+{
+ ssize_t count2 = 0, ret;
+
+ do {
+ ret = pwrite(fd, buf, count, offset);
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ if (ret == 0)
+ break;
+ count2 += ret;
+ buf += ret;
+ count -= ret;
+ } while (count);
+ return count2;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ errx(1, "no test filename argument given");
+ filename = argv[1];
+
+ page_size = ret = sysconf(_SC_PAGE_SIZE);
+ if (ret == -1)
+ err(1, NULL);
+
+ ret = posix_memalign(&page, page_size, page_size);
+ if (ret) {
+ errno = ENOMEM;
+ err(1, NULL);
+ }
+
+ /*
+ * Make sure page faults during pread are handled correctly:
+ * read from an allocated area on disk into page 0.
+ */
+ init('a', O_RDWR);
+ ret = do_read(fd, addr, page_size, page_size);
+ if (ret != page_size)
+ err(1, "pread %s: %ld != %u", filename, ret, page_size);
+ if (memcmp(addr, page, page_size))
+ errx(1, "pread is broken");
+ done();
+
+ init('b', O_RDWR | O_DIRECT);
+ ret = do_read(fd, addr, page_size, page_size);
+ if (ret != page_size)
+ err(1, "pread %s (O_DIRECT): %ld != %u", filename, ret, page_size);
+ if (memcmp(addr, page, page_size))
+ errx(1, "pread (D_DIRECT) is broken");
+ done();
+
+ /*
+ * Make sure page faults during pwrite are handled correctly:
+ * write from an allocated area on disk into page 0.
+ */
+ init('c', O_RDWR);
+ ret = do_write(fd, addr + page_size, page_size, 0);
+ if (ret != page_size)
+ err(1, "pwrite %s: %ld != %u", filename, ret, page_size);
+ if (memcmp(addr, page, page_size))
+ errx(1, "pwrite is broken");
+ done();
+
+ init('d', O_RDWR | O_DIRECT);
+ ret = do_write(fd, addr + page_size, page_size, 0);
+ if (ret != page_size)
+ err(1, "pwrite %s (O_DIRECT): %ld != %u", filename, ret, page_size);
+ if (memcmp(addr, page, page_size))
+ errx(1, "pwrite (O_DIRECT) is broken");
+ done();
+
+ /*
+ * Reading from a hole under O_DIRECT takes a different code path in
+ * the kernel. Read from a hole into page 0 to test that. (It
+ * shouldn't matter that the hole and page 0 coincide.)
+ */
+ init('e', O_RDWR | O_DIRECT);
+ ret = do_read(fd, addr, page_size, 0);
+ if (ret != page_size)
+ err(1, "pread %s (O_DIRECT) from hole: %ld != %u", filename, ret, page_size);
+ memset(page, 0, page_size);
+ if (memcmp(addr, page, page_size))
+ errx(1, "pread (D_DIRECT) from hole is broken");
+ done();
+
+ if (unlink(filename))
+ err(1, "unlink %s", filename);
+
+ return 0;
+}