blob: 82f9d9781e06396cc10462a95ab989d8c775d86c [file] [log] [blame]
// 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;
}