1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2018 Intel Corporation.
5 * As of kernel version 4.18-rc6 Linux has an issue with ext4+DAX where DMA
6 * and direct I/O operations aren't synchronized with respect to operations
7 * which can change the block mappings of an inode. This means that we can
8 * schedule an I/O for an inode and have the block mapping for that inode
9 * change before the I/O is actually complete. So, blocks which were once
10 * allocated to a given inode and then freed could still have I/O operations
11 * happening to them. If these blocks have also been reallocated to a
12 * different inode, this interaction can lead to data corruption.
14 * This test exercises four of the paths in ext4 which hit this issue.
25 #include <sys/types.h>
28 #define PAGE(a) ((a)*0x1000)
29 #define FILE_SIZE PAGE(4)
36 #define err_exit(op) \
38 fprintf(stderr, "%s %s: %s\n", __func__, op, strerror(errno)); \
42 #if defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE)
43 void punch_hole_fn(void *ptr)
52 rc = pread(nodax_fd, dax_data + read, FILE_SIZE - read,
58 if (read != FILE_SIZE || rc != 0)
61 rc = fallocate(dax_fd,
62 FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
65 err_exit("fallocate");
67 usleep(rand() % 1000);
71 void punch_hole_fn(void *ptr) { }
74 #if defined(FALLOC_FL_ZERO_RANGE) && defined(FALLOC_FL_KEEP_SIZE)
75 void zero_range_fn(void *ptr)
84 rc = pread(nodax_fd, dax_data + read, FILE_SIZE - read,
90 if (read != FILE_SIZE || rc != 0)
93 rc = fallocate(dax_fd,
94 FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
97 err_exit("fallocate");
99 usleep(rand() % 1000);
103 void zero_range_fn(void *ptr) { }
106 void truncate_down_fn(void *ptr)
114 if (ftruncate(dax_fd, 0) < 0)
115 err_exit("ftruncate");
116 if (fallocate(dax_fd, 0, 0, FILE_SIZE) < 0)
117 err_exit("fallocate");
120 rc = pread(nodax_fd, dax_data + read, FILE_SIZE - read,
127 * For this test we ignore errors from pread(). These errors
128 * can happen if we try and read while the other thread has
129 * made the file size 0.
132 usleep(rand() % 1000);
136 #ifdef FALLOC_FL_COLLAPSE_RANGE
137 void collapse_range_fn(void *ptr)
145 if (fallocate(dax_fd, 0, 0, FILE_SIZE) < 0)
146 err_exit("fallocate 1");
147 if (fallocate(dax_fd, FALLOC_FL_COLLAPSE_RANGE, 0, PAGE(1)) < 0)
148 err_exit("fallocate 2");
149 if (fallocate(dax_fd, 0, 0, FILE_SIZE) < 0)
150 err_exit("fallocate 3");
153 rc = pread(nodax_fd, dax_data + read, FILE_SIZE - read,
159 /* For this test we ignore errors from pread. */
161 usleep(rand() % 1000);
165 void collapse_range_fn(void *ptr) { }
168 void run_test(void (*test_fn)(void *))
170 const int NUM_THREADS = 2;
171 pthread_t worker_thread[NUM_THREADS];
175 for (i = 0; i < NUM_THREADS; i++)
176 pthread_create(&worker_thread[i], NULL, (void*)test_fn, NULL);
181 for (i = 0; i < NUM_THREADS; i++)
182 pthread_join(worker_thread[i], NULL);
185 int main(int argc, char *argv[])
190 printf("Usage: %s <dax file> <non-dax file>\n",
195 dax_fd = open(argv[1], O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
197 err_exit("dax_fd open");
199 nodax_fd = open(argv[2], O_RDWR|O_CREAT|O_DIRECT, S_IRUSR|S_IWUSR);
201 err_exit("nodax_fd open");
203 if (ftruncate(dax_fd, 0) < 0)
204 err_exit("dax_fd ftruncate");
205 if (fallocate(dax_fd, 0, 0, FILE_SIZE) < 0)
206 err_exit("dax_fd fallocate");
208 if (ftruncate(nodax_fd, 0) < 0)
209 err_exit("nodax_fd ftruncate");
210 if (fallocate(nodax_fd, 0, 0, FILE_SIZE) < 0)
211 err_exit("nodax_fd fallocate");
213 dax_data = mmap(NULL, FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED,
215 if (dax_data == MAP_FAILED)
218 run_test(&punch_hole_fn);
219 run_test(&zero_range_fn);
220 run_test(&truncate_down_fn);
221 run_test(&collapse_range_fn);
223 if (munmap(dax_data, FILE_SIZE) != 0)
228 err_exit("dax_fd close");
230 err = close(nodax_fd);
232 err_exit("nodax_fd close");