Repository navigation
Description
System information
| Type | Version/Name |
|---|---|
| Distribution Name | NixOS |
| Distribution Version | NixOS 25.11 (Xantusia) |
| Kernel Version | 6.18.19 |
| Architecture | x86_64 |
| OpenZFS Version | 2.4.1 |
Describe the problem you're observing
I have a program which reads a file after a copy (copy at https://github.com/rust-lang/rust/blob/ad4b9354009cb6bd5a9ff1b5f5a63a13ec98ebc9/compiler/rustc_codegen_ssa/src/back/write.rs#L524, read at https://github.com/Rust-for-Linux/klint/blob/62f35fa7fc978aee288a8cc859e0cc40d9c32e56/src/binary_analysis/mod.rs#L22, but the read in theory could also be linker or another dependent compilation).
This works fine for unit testing the program, however when I build kernel with -j64 this causes issue. The file read can sometimes read back corrupted file. I've investigated the corrupted read, and I found that the first 128KiB are all zero, and this matches my recordsize, so I suspect ZFS is causing the bug.
Clean build never triggers the issue, and the issue only triggers when source is modified and rebuilt with target .o file already present. Also interestingly, the file itself is not corrupted, if you read the content afterwards. Only the read is.
Describe how to reproduce the problem
I've minimized the reproduction, and this has fairly reliably reproduce the bug on my machine:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#define FILE_SIZE (1024 * 1024) // 1MB
#define RECORD_SIZE (128 * 1024) // 128KiB ZFS recordsize
#define NUM_THREADS 64
void *run_test(void *arg)
{
int thread_id = (int)(long)arg;
char src_path[64], dst_path[64];
sprintf(src_path, "src-%d.bin", thread_id);
sprintf(dst_path, "dst-%d.bin", thread_id);
unsigned char *write_buf = malloc(FILE_SIZE);
unsigned char *read_buf = malloc(FILE_SIZE);
// Write out expected data.
memset(write_buf, 0xAA, FILE_SIZE);
int src = open(src_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (write(src, write_buf, FILE_SIZE) != FILE_SIZE)
perror("write");
close(src);
// Create destination file so we exercise O_TRUNC.
int dst = open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (write(dst, write_buf, FILE_SIZE) != FILE_SIZE)
perror("write");
fsync(dst);
close(dst);
// Open file with O_TRUNC and perform copy.
src = open(src_path, O_RDONLY);
dst = open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
loff_t off_in = 0, off_out = 0;
ssize_t ret =
copy_file_range(src, &off_in, dst, &off_out, FILE_SIZE, 0);
if (ret != FILE_SIZE)
perror("copy_file_range");
close(src);
close(dst);
// Read back
dst = open(dst_path, O_RDONLY);
if (read(dst, read_buf, FILE_SIZE) != FILE_SIZE)
perror("read");
close(dst);
// Bug check
if (memcmp(write_buf, read_buf, FILE_SIZE) != 0) {
fprintf(stderr, "[%d]: FAIL\n", thread_id);
int all_zeros = 1;
for (int i = 0; i < RECORD_SIZE; i++) {
if (read_buf[i] != 0) {
all_zeros = 0;
break;
}
}
if (all_zeros) {
fprintf(stderr, "[%d]: ALL ZERO\n", thread_id);
}
goto keep_file;
}
unlink(src_path);
unlink(dst_path);
keep_file:
free(write_buf);
free(read_buf);
return NULL;
}
int main()
{
pthread_t threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, run_test, (void *)(long)i);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}An example failing run gives:
[20]: FAIL
[4]: FAIL
[20]: ALL ZERO
[4]: ALL ZERO
[14]: FAIL
[15]: FAIL
[14]: ALL ZERO
[22]: FAIL
[22]: ALL ZERO
[15]: ALL ZERO
[16]: FAIL
[21]: FAIL
[16]: ALL ZERO
[21]: ALL ZERO
On other file system, this works cleanly without the issue.
Activity
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate
Fix read corruption after block clone after truncate