【运维案例】创建文件失败,系统侧有inode的报错

产品:openEuler

版本:20.03-LTS-SP3

分类:内核 / 文件系统

来源:现网

[背景及现象描述]

TS200-2280K服务器 ,系统侧有inode的报错
报错信息如下:
[Thu Jul 27 01:32:25 2023] EXT4-fs error (device dm-5): ext4_dx_csum_verify:463: inode #686223488: comm universe: dir seems corrupt? Run e2fsck -D.
[Thu Jul 27 01:32:25 2023] EXT4-fs error (device dm-5): dx_probe:856: inode #686223488: block 8188: comm universe: Directory index failed checksum
[Thu Jul 27 01:42:40 2023] EXT4-fs error (device dm-5): ext4_dx_csum_verify:463: inode #686223488: comm universe: dir seems corrupt? Run e2fsck -D.
[Thu Jul 27 01:42:40 2023] EXT4-fs error (device dm-5): dx_probe:856: inode #686223488: block 8188: comm universe: Directory index failed
问题复现步骤:创建一个ext4文件系统,挂载到目录下后写循序在这个目录里创建2000万个文件
出现概率:必现

[原因分析]

分析代码:
ext4_add_entry
ext4_dx_add_entry
// 当前 dx_node 已满,没有空闲空间了
ext4_append // 添加新的块
fake.rec_len = ext4_rec_len_to_disk(sb->s_blocksize, sb->s_blocksize);
// 新的 rec_len, 块大小为 65536,blocksize 和 len 也是 65536, 此时 rec_len 会被直接指定为 65535
ext4_handle_dirty_dx_node // 置脏新的 dx_node 块
ext4_dx_csum_set
get_dx_countlimit
if (le16_to_cpu(dirent->rec_len) == EXT4_BLOCK_SIZE(inode->i_sb)) // dx_node rec_len为块大小
count_offset = 8;
else if (le16_to_cpu(dirent->rec_len) == 12) // dx_root “.” dentry
count_offset = 32;
else // 两者均不满足有问题,不设置 checksum
return NULL;
当 dir_index tree 其中一个 node 满了而无法再存放更多的 dir block 时,会新增 dx_node block。
在 arm64 平台,开启 64k 页之后,ext4 也支持挂载 64k 块大小的文件。
在新增块时,会将 dx_node 对应的 fake dentry 的 rec_len 设置为块大小
但是 rec_len 数据类型为 uint16 最大支持 65535,为了避免溢出截断
在 ext4_rec_len_to_disk 中当当前块大小为 65536 时,设置 rec_len 为最大值 65535, 而不是 65536
但是在 get_dx_countlimit 中计算 count 和 limit 的偏移时,因为 65535 != 65536,
所以返回了 NULL, 导致 dx_node block 没有设置 checksum, 后续访问该 node 都会报错。

[解决方法]

正确的做法应该是使用 ext4_rec_len_from_disk() 转换 rec_len 为 65536 之后再进行比较
已发送补丁到上游社区:
V1: https://lore.kernel.org/lkml/20230729061357.1702891-1-zhangshida@kylinos.cn/T/
V2: https://lore.kernel.org/lkml/20230730024409.1738559-1-zhangshida@kylinos.cn/t/

2 个赞