ext4: fix readdir error in the case of inline_data+dir_index
Zach reported a problem that if inline data is enabled, we don't tell the difference between the offset of '.' and '..'. And a getdents will fail if the user only want to get '.' and what's worse, if there is a conversion happens when the user calls getdents many times, he/she may get the same entry twice. In theory, a dir block would also fail if it is converted to a hashed-index based dir since f_pos will become a hash value, not the real one, but it doesn't happen. And a deep investigation shows that we uses a hash based solution even for a normal dir if the dir_index feature is enabled. So this patch just adds a new htree_inlinedir_to_tree for inline dir, and if we find that the hash index is supported, we will do like what we do for a dir block. Reported-by: Zach Brown <zab@redhat.com> Signed-off-by: Tao Ma <boyu.mt@taobao.com> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
This commit is contained in:
@@ -46,7 +46,8 @@ static int is_dx_dir(struct inode *inode)
|
||||
if (EXT4_HAS_COMPAT_FEATURE(inode->i_sb,
|
||||
EXT4_FEATURE_COMPAT_DIR_INDEX) &&
|
||||
((ext4_test_inode_flag(inode, EXT4_INODE_INDEX)) ||
|
||||
((inode->i_size >> sb->s_blocksize_bits) == 1)))
|
||||
((inode->i_size >> sb->s_blocksize_bits) == 1) ||
|
||||
ext4_has_inline_data(inode)))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
@@ -115,14 +116,6 @@ static int ext4_readdir(struct file *filp,
|
||||
int ret = 0;
|
||||
int dir_has_error = 0;
|
||||
|
||||
if (ext4_has_inline_data(inode)) {
|
||||
int has_inline_data = 1;
|
||||
ret = ext4_read_inline_dir(filp, dirent, filldir,
|
||||
&has_inline_data);
|
||||
if (has_inline_data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (is_dx_dir(inode)) {
|
||||
err = ext4_dx_readdir(filp, dirent, filldir);
|
||||
if (err != ERR_BAD_DX_DIR) {
|
||||
@@ -136,6 +129,15 @@ static int ext4_readdir(struct file *filp,
|
||||
ext4_clear_inode_flag(file_inode(filp),
|
||||
EXT4_INODE_INDEX);
|
||||
}
|
||||
|
||||
if (ext4_has_inline_data(inode)) {
|
||||
int has_inline_data = 1;
|
||||
ret = ext4_read_inline_dir(filp, dirent, filldir,
|
||||
&has_inline_data);
|
||||
if (has_inline_data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
stored = 0;
|
||||
offset = filp->f_pos & (sb->s_blocksize - 1);
|
||||
|
||||
|
Reference in New Issue
Block a user