blob: c872f4e26940092f921b7fb5f9817b3376f26896 [file] [log] [blame]
// 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;
}