|  | // 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> | 
|  | #include <getopt.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; | 
|  | } | 
|  |  | 
|  | static void usage(const char *argv0) | 
|  | { | 
|  | fprintf(stderr, "Usage: %s [-2] {filename}\n", argv0); | 
|  | exit(2); | 
|  | } | 
|  |  | 
|  | int main(int argc, char *argv[]) | 
|  | { | 
|  | int opt, opt_2 = 0; | 
|  |  | 
|  | while ((opt = getopt(argc, argv, "2")) != -1) { | 
|  | switch(opt) { | 
|  | case '2': | 
|  | opt_2 = 1; | 
|  | break; | 
|  | default: | 
|  | usage(argv[0]); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (optind + 1 != argc) | 
|  | usage(argv[0]); | 
|  | filename = argv[optind]; | 
|  |  | 
|  | 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 (opt_2) { | 
|  | init('f', O_RDWR | O_DIRECT); | 
|  | ret = do_write(fd, addr + page_size, page_size, page_size); | 
|  | if (ret != page_size) | 
|  | err(1, "pwrite %s (O_DIRECT): %ld != %u", filename, ret, page_size); | 
|  | done(); | 
|  | } | 
|  |  | 
|  | if (unlink(filename)) | 
|  | err(1, "unlink %s", filename); | 
|  |  | 
|  | return 0; | 
|  | } |