| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * dio-writeback-race -- test direct IO writes with the contents of | |
| * the buffer changed during writeback. | |
| * | |
| * Copyright (C) 2025 SUSE Linux Products GmbH. | |
| */ | |
| /* | |
| * dio-writeback-race | |
| * | |
| * Compile: | |
| * | |
| * gcc -Wall -pthread -o dio-writeback-race dio-writeback-race.c | |
| * | |
| * Make sure filesystems with explicit data checksum can handle direct IO | |
| * writes correctly, so that even the contents of the direct IO buffer changes | |
| * during writeback, the fs should still work fine without data checksum mismatch. | |
| * (Normally by falling back to buffer IO to keep the checksum and contents | |
| * consistent) | |
| * | |
| * Usage: | |
| * | |
| * dio-writeback-race [-b <buffersize>] [-s <filesize>] <filename> | |
| * | |
| * Where: | |
| * | |
| * <filename> is the name of the test file to create, if the file exists | |
| * it will be overwritten. | |
| * | |
| * <buffersize> is the buffer size for direct IO. Users are responsible to | |
| * probe the correct direct IO size and alignment requirement. | |
| * If not specified, the default value will be 4096. | |
| * | |
| * <filesize> is the total size of the test file. If not aligned to <blocksize> | |
| * the result file size will be rounded up to <blocksize>. | |
| * If not specified, the default value will be 64MiB. | |
| */ | |
| #include <fcntl.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <pthread.h> | |
| #include <unistd.h> | |
| #include <getopt.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <sys/stat.h> | |
| static int fd = -1; | |
| static void *buf = NULL; | |
| static unsigned long long filesize = 64 * 1024 * 1024; | |
| static int blocksize = 4096; | |
| const int orig_pattern = 0xff; | |
| const int modify_pattern = 0x00; | |
| static void *do_io(void *arg) | |
| { | |
| ssize_t ret; | |
| ret = write(fd, buf, blocksize); | |
| pthread_exit((void *)ret); | |
| } | |
| static void *do_modify(void *arg) | |
| { | |
| memset(buf, modify_pattern, blocksize); | |
| pthread_exit(NULL); | |
| } | |
| int main (int argc, char *argv[]) | |
| { | |
| pthread_t io_thread; | |
| pthread_t modify_thread; | |
| unsigned long long filepos; | |
| int opt; | |
| int ret = -EINVAL; | |
| while ((opt = getopt(argc, argv, "b:s:")) > 0) { | |
| switch (opt) { | |
| case 'b': | |
| blocksize = atoi(optarg); | |
| if (blocksize == 0) { | |
| fprintf(stderr, "invalid blocksize '%s'\n", optarg); | |
| goto error; | |
| } | |
| break; | |
| case 's': | |
| filesize = atoll(optarg); | |
| if (filesize == 0) { | |
| fprintf(stderr, "invalid filesize '%s'\n", optarg); | |
| goto error; | |
| } | |
| break; | |
| default: | |
| fprintf(stderr, "unknown option '%c'\n", opt); | |
| goto error; | |
| } | |
| } | |
| if (optind >= argc) { | |
| fprintf(stderr, "missing argument\n"); | |
| goto error; | |
| } | |
| ret = posix_memalign(&buf, sysconf(_SC_PAGESIZE), blocksize); | |
| if (!buf) { | |
| fprintf(stderr, "failed to allocate aligned memory\n"); | |
| exit(EXIT_FAILURE); | |
| } | |
| fd = open(argv[optind], O_DIRECT | O_WRONLY | O_CREAT, 0600); | |
| if (fd < 0) { | |
| fprintf(stderr, "failed to open file '%s': %m\n", argv[optind]); | |
| goto error; | |
| } | |
| for (filepos = 0; filepos < filesize; filepos += blocksize) { | |
| void *retval = NULL; | |
| memset(buf, orig_pattern, blocksize); | |
| pthread_create(&io_thread, NULL, do_io, NULL); | |
| pthread_create(&modify_thread, NULL, do_modify, NULL); | |
| ret = pthread_join(io_thread, &retval); | |
| if (ret) { | |
| errno = ret; | |
| ret = -ret; | |
| fprintf(stderr, "failed to join io thread: %m\n"); | |
| goto error; | |
| } | |
| if ((ssize_t )retval < blocksize) { | |
| ret = -EIO; | |
| fprintf(stderr, "io thread failed\n"); | |
| goto error; | |
| } | |
| ret = pthread_join(modify_thread, NULL); | |
| if (ret) { | |
| errno = ret; | |
| ret = -ret; | |
| fprintf(stderr, "failed to join modify thread: %m\n"); | |
| goto error; | |
| } | |
| } | |
| error: | |
| close(fd); | |
| free(buf); | |
| if (ret < 0) | |
| return EXIT_FAILURE; | |
| return EXIT_SUCCESS; | |
| } |