我让 Codex 帮我写了一个可以稳定触发这个 bug 的程序:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <sys/mman.h>
#include <unistd.h>
#define READ_SIZE 4096UL
#define TEST_FILE_SIZE (1UL * 1024 * 1024)
#define MAX_TRIES 100UL
/*
* Reproduce the Btrfs direct-IO checksum false failure:
* one thread overwrites the O_DIRECT read buffer while read() is in flight.
*/
static atomic_int stop_scribble;
static void fill_buffer(char *buf, size_t len, uint64_t *seed)
{
size_t i;
for (i = 0; i < len; i++) {
*seed ^= *seed << 13;
*seed ^= *seed >> 7;
*seed ^= *seed << 17;
buf[i] = (char)*seed;
}
}
static int create_test_file(void)
{
char path[] = "./btrfs-read-repro.XXXXXX";
char buf[READ_SIZE];
uint64_t seed = 0x123456789abcdef0ULL;
size_t done;
int fd;
int direct_fd;
fd = mkstemp(path);
if (fd < 0) {
perror("mkstemp");
return -1;
}
for (done = 0; done < TEST_FILE_SIZE; done += sizeof(buf)) {
ssize_t ret;
fill_buffer(buf, sizeof(buf), &seed);
ret = write(fd, buf, sizeof(buf));
if (ret != (ssize_t)sizeof(buf)) {
if (ret < 0) {
perror("write");
} else {
fprintf(stderr, "short write: %zd\n", ret);
}
goto fail;
}
}
if (fsync(fd) < 0) {
perror("fsync");
goto fail;
}
(void)posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
close(fd);
direct_fd = open(path, O_RDONLY | O_DIRECT | O_CLOEXEC);
if (direct_fd < 0) {
perror(path);
unlink(path);
return -1;
}
unlink(path);
return direct_fd;
fail:
close(fd);
unlink(path);
return -1;
}
static void *scribble_thread(void *opaque)
{
char *buf = opaque;
uint64_t v = 0x5a5a5a5a5a5a5a5aULL;
while (!atomic_load_explicit(&stop_scribble, memory_order_relaxed)) {
size_t off;
for (off = 0; off < READ_SIZE; off += sizeof(v)) {
*(volatile uint64_t *)(buf + off) = v;
}
v += 0x0101010101010101ULL;
}
return NULL;
}
int main(void)
{
pthread_t scribbler;
char *buf;
int fd;
int reproduced = 0;
fd = create_test_file();
if (fd < 0) {
return 1;
}
buf = mmap(NULL, READ_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
if (pthread_create(&scribbler, NULL, scribble_thread, buf) != 0) {
perror("pthread_create");
munmap(buf, READ_SIZE);
close(fd);
return 1;
}
for (unsigned long n = 0; n < MAX_TRIES; n++) {
ssize_t ret;
if (lseek(fd, 0, SEEK_SET) < 0) {
perror("lseek");
break;
}
ret = read(fd, buf, READ_SIZE);
if (ret < 0 && errno == EIO) {
fprintf(stderr, "reproduced: read returned EIO at try %lu\n", n);
reproduced = 1;
break;
}
if (ret != (ssize_t)READ_SIZE) {
fprintf(stderr, "unexpected read result: %zd errno=%d\n",
ret, errno);
break;
}
}
atomic_store_explicit(&stop_scribble, 1, memory_order_relaxed);
pthread_join(scribbler, NULL);
munmap(buf, READ_SIZE);
close(fd);
if (!reproduced) {
fprintf(stderr, "not reproduced after %lu tries\n", MAX_TRIES);
return 1;
}
return 0;
}
只需要当前目录是 btrfs ,运行这个程序就能直接触发下面这个报错:
root@develop:~/test# gcc btrfs_read_repro.c -pthread -o btrfs_read_repro
root@develop:~/test#
root@develop:~/test# ./btrfs_read_repro
reproduced: read returned EIO at try 1
root@develop:~/test#
root@develop:~/test# dmesg
[12297.261256] BTRFS warning (device nvme1n1p1): csum failed root 336 ino 1110132 off 0 csum 0x588c3802 expected csum 0x84efe040 mirror 1
[12297.261263] BTRFS error (device nvme1n1p1): bdev /dev/nvme1n1p1 errs: wr 0, rd 0, flush 0, corrupt 36926, gen 0
[12297.261267] BTRFS warning (device nvme1n1p1): direct IO failed ino 1110132 op 0x8800 offset 0x0 len 4096 err no 10
root@develop:~/test#
我用最新的 7.3-rc1 内核测试仍然有这个报错,不知道上游是否知道有这个 bug ,这个 bug 用 5.10 内核都能复现,如果上游知道这个 bug 的存在为啥到现在还都没有修复。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.