| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2018 Intel Corporation. |
| * |
| * As of kernel version 4.18-rc6 Linux has an issue with ext4+DAX where DMA |
| * and direct I/O operations aren't synchronized with respect to operations |
| * which can change the block mappings of an inode. This means that we can |
| * schedule an I/O for an inode and have the block mapping for that inode |
| * change before the I/O is actually complete. So, blocks which were once |
| * allocated to a given inode and then freed could still have I/O operations |
| * happening to them. If these blocks have also been reallocated to a |
| * different inode, this interaction can lead to data corruption. |
| * |
| * This test exercises four of the paths in ext4 which hit this issue. |
| */ |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| void *dax_data; |
| int nodax_fd; |
| int dax_fd; |
| bool done; |
| static int pagesize; |
| static int file_size; |
| |
| #define err_exit(op) \ |
| { \ |
| fprintf(stderr, "%s %s: %s\n", __func__, op, strerror(errno)); \ |
| exit(1); \ |
| } |
| |
| #if defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE) |
| void punch_hole_fn(void *ptr) |
| { |
| ssize_t read; |
| int rc; |
| |
| while (!done) { |
| read = 0; |
| |
| do { |
| rc = pread(nodax_fd, dax_data + read, file_size - read, |
| read); |
| if (rc > 0) |
| read += rc; |
| } while (rc > 0); |
| |
| if (read != file_size || rc != 0) |
| err_exit("pread"); |
| |
| rc = fallocate(dax_fd, |
| FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, |
| 0, file_size); |
| if (rc < 0) |
| err_exit("fallocate"); |
| |
| usleep(rand() % 1000); |
| } |
| } |
| #else |
| void punch_hole_fn(void *ptr) { } |
| #endif |
| |
| #if defined(FALLOC_FL_ZERO_RANGE) && defined(FALLOC_FL_KEEP_SIZE) |
| void zero_range_fn(void *ptr) |
| { |
| ssize_t read; |
| int rc; |
| |
| while (!done) { |
| read = 0; |
| |
| do { |
| rc = pread(nodax_fd, dax_data + read, file_size - read, |
| read); |
| if (rc > 0) |
| read += rc; |
| } while (rc > 0); |
| |
| if (read != file_size || rc != 0) |
| err_exit("pread"); |
| |
| rc = fallocate(dax_fd, |
| FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE, |
| 0, file_size); |
| if (rc < 0) |
| err_exit("fallocate"); |
| |
| usleep(rand() % 1000); |
| } |
| } |
| #else |
| void zero_range_fn(void *ptr) { } |
| #endif |
| |
| void truncate_down_fn(void *ptr) |
| { |
| ssize_t read; |
| int rc; |
| |
| while (!done) { |
| read = 0; |
| |
| if (ftruncate(dax_fd, 0) < 0) |
| err_exit("ftruncate"); |
| if (fallocate(dax_fd, 0, 0, file_size) < 0) |
| err_exit("fallocate"); |
| |
| do { |
| rc = pread(nodax_fd, dax_data + read, file_size - read, |
| read); |
| if (rc > 0) |
| read += rc; |
| } while (rc > 0); |
| |
| /* |
| * For this test we ignore errors from pread(). These errors |
| * can happen if we try and read while the other thread has |
| * made the file size 0. |
| */ |
| |
| usleep(rand() % 1000); |
| } |
| } |
| |
| #ifdef FALLOC_FL_COLLAPSE_RANGE |
| void collapse_range_fn(void *ptr) |
| { |
| ssize_t read; |
| int rc; |
| |
| while (!done) { |
| read = 0; |
| |
| if (fallocate(dax_fd, 0, 0, file_size) < 0) |
| err_exit("fallocate 1"); |
| if (fallocate(dax_fd, FALLOC_FL_COLLAPSE_RANGE, 0, pagesize) < 0) |
| err_exit("fallocate 2"); |
| if (fallocate(dax_fd, 0, 0, file_size) < 0) |
| err_exit("fallocate 3"); |
| |
| do { |
| rc = pread(nodax_fd, dax_data + read, file_size - read, |
| read); |
| if (rc > 0) |
| read += rc; |
| } while (rc > 0); |
| |
| /* For this test we ignore errors from pread. */ |
| |
| usleep(rand() % 1000); |
| } |
| } |
| #else |
| void collapse_range_fn(void *ptr) { } |
| #endif |
| |
| void run_test(void (*test_fn)(void *)) |
| { |
| const int NUM_THREADS = 2; |
| pthread_t worker_thread[NUM_THREADS]; |
| int i; |
| |
| done = 0; |
| for (i = 0; i < NUM_THREADS; i++) |
| pthread_create(&worker_thread[i], NULL, (void*)test_fn, NULL); |
| |
| sleep(1); |
| done = 1; |
| |
| for (i = 0; i < NUM_THREADS; i++) |
| pthread_join(worker_thread[i], NULL); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int err; |
| |
| if (argc != 3) { |
| printf("Usage: %s <dax file> <non-dax file>\n", |
| basename(argv[0])); |
| exit(0); |
| } |
| |
| pagesize = getpagesize(); |
| file_size = 4 * pagesize; |
| |
| dax_fd = open(argv[1], O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); |
| if (dax_fd < 0) |
| err_exit("dax_fd open"); |
| |
| nodax_fd = open(argv[2], O_RDWR|O_CREAT|O_DIRECT, S_IRUSR|S_IWUSR); |
| if (nodax_fd < 0) |
| err_exit("nodax_fd open"); |
| |
| if (ftruncate(dax_fd, 0) < 0) |
| err_exit("dax_fd ftruncate"); |
| if (fallocate(dax_fd, 0, 0, file_size) < 0) |
| err_exit("dax_fd fallocate"); |
| |
| if (ftruncate(nodax_fd, 0) < 0) |
| err_exit("nodax_fd ftruncate"); |
| if (fallocate(nodax_fd, 0, 0, file_size) < 0) |
| err_exit("nodax_fd fallocate"); |
| |
| dax_data = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_SHARED, |
| dax_fd, 0); |
| if (dax_data == MAP_FAILED) |
| err_exit("mmap"); |
| |
| run_test(&punch_hole_fn); |
| run_test(&zero_range_fn); |
| run_test(&truncate_down_fn); |
| run_test(&collapse_range_fn); |
| |
| if (munmap(dax_data, file_size) != 0) |
| err_exit("munmap"); |
| |
| err = close(dax_fd); |
| if (err < 0) |
| err_exit("dax_fd close"); |
| |
| err = close(nodax_fd); |
| if (err < 0) |
| err_exit("nodax_fd close"); |
| |
| return 0; |
| } |