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>
35 #define err_exit(op) \
37 fprintf(stderr, "%s %s: %s\n", __func__, op, strerror(errno)); \
41 #if defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE)
42 void punch_hole_fn(void *ptr)
51 rc = pread(nodax_fd, dax_data + read, file_size - read,
57 if (read != file_size || rc != 0)
60 rc = fallocate(dax_fd,
61 FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
64 err_exit("fallocate");
66 usleep(rand() % 1000);
70 void punch_hole_fn(void *ptr) { }
73 #if defined(FALLOC_FL_ZERO_RANGE) && defined(FALLOC_FL_KEEP_SIZE)
74 void zero_range_fn(void *ptr)
83 rc = pread(nodax_fd, dax_data + read, file_size - read,
89 if (read != file_size || rc != 0)
92 rc = fallocate(dax_fd,
93 FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE,
96 err_exit("fallocate");
98 usleep(rand() % 1000);
102 void zero_range_fn(void *ptr) { }
105 void truncate_down_fn(void *ptr)
113 if (ftruncate(dax_fd, 0) < 0)
114 err_exit("ftruncate");
115 if (fallocate(dax_fd, 0, 0, file_size) < 0)
116 err_exit("fallocate");
119 rc = pread(nodax_fd, dax_data + read, file_size - read,
126 * For this test we ignore errors from pread(). These errors
127 * can happen if we try and read while the other thread has
128 * made the file size 0.
131 usleep(rand() % 1000);
135 #ifdef FALLOC_FL_COLLAPSE_RANGE
136 void collapse_range_fn(void *ptr)
144 if (fallocate(dax_fd, 0, 0, file_size) < 0)
145 err_exit("fallocate 1");
146 if (fallocate(dax_fd, FALLOC_FL_COLLAPSE_RANGE, 0, pagesize) < 0)
147 err_exit("fallocate 2");
148 if (fallocate(dax_fd, 0, 0, file_size) < 0)
149 err_exit("fallocate 3");
152 rc = pread(nodax_fd, dax_data + read, file_size - read,
158 /* For this test we ignore errors from pread. */
160 usleep(rand() % 1000);
164 void collapse_range_fn(void *ptr) { }
167 void run_test(void (*test_fn)(void *))
169 const int NUM_THREADS = 2;
170 pthread_t worker_thread[NUM_THREADS];
174 for (i = 0; i < NUM_THREADS; i++)
175 pthread_create(&worker_thread[i], NULL, (void*)test_fn, NULL);
180 for (i = 0; i < NUM_THREADS; i++)
181 pthread_join(worker_thread[i], NULL);
184 int main(int argc, char *argv[])
189 printf("Usage: %s <dax file> <non-dax file>\n",
194 pagesize = getpagesize();
195 file_size = 4 * pagesize;
197 dax_fd = open(argv[1], O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
199 err_exit("dax_fd open");
201 nodax_fd = open(argv[2], O_RDWR|O_CREAT|O_DIRECT, S_IRUSR|S_IWUSR);
203 err_exit("nodax_fd open");
205 if (ftruncate(dax_fd, 0) < 0)
206 err_exit("dax_fd ftruncate");
207 if (fallocate(dax_fd, 0, 0, file_size) < 0)
208 err_exit("dax_fd fallocate");
210 if (ftruncate(nodax_fd, 0) < 0)
211 err_exit("nodax_fd ftruncate");
212 if (fallocate(nodax_fd, 0, 0, file_size) < 0)
213 err_exit("nodax_fd fallocate");
215 dax_data = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_SHARED,
217 if (dax_data == MAP_FAILED)
220 run_test(&punch_hole_fn);
221 run_test(&zero_range_fn);
222 run_test(&truncate_down_fn);
223 run_test(&collapse_range_fn);
225 if (munmap(dax_data, file_size) != 0)
230 err_exit("dax_fd close");
232 err = close(nodax_fd);
234 err_exit("nodax_fd close");