Skip to content

Commit b35eb48

Browse files
Ye Bingregkh
authored andcommitted
ext4: fix fs corruption when tring to remove a non-empty directory with IO error
commit 7aab5c8 upstream. We inject IO error when rmdir non empty direcory, then got issue as follows: step1: mkfs.ext4 -F /dev/sda step2: mount /dev/sda test step3: cd test step4: mkdir -p 1/2 step5: rmdir 1 [ 110.920551] ext4_empty_dir: inject fault [ 110.921926] EXT4-fs warning (device sda): ext4_rmdir:3113: inode #12: comm rmdir: empty directory '1' has too many links (3) step6: cd .. step7: umount test step8: fsck.ext4 -f /dev/sda e2fsck 1.42.9 (28-Dec-2013) Pass 1: Checking inodes, blocks, and sizes Pass 2: Checking directory structure Entry '..' in .../??? (13) has deleted/unused inode 12. Clear<y>? yes Pass 3: Checking directory connectivity Unconnected directory inode 13 (...) Connect to /lost+found<y>? yes Pass 4: Checking reference counts Inode 13 ref count is 3, should be 2. Fix<y>? yes Pass 5: Checking group summary information /dev/sda: ***** FILE SYSTEM WAS MODIFIED ***** /dev/sda: 12/131072 files (0.0% non-contiguous), 26157/524288 blocks ext4_rmdir if (!ext4_empty_dir(inode)) goto end_rmdir; ext4_empty_dir bh = ext4_read_dirblock(inode, 0, DIRENT_HTREE); if (IS_ERR(bh)) return true; Now if read directory block failed, 'ext4_empty_dir' will return true, assume directory is empty. Obviously, it will lead to above issue. To solve this issue, if read directory block failed 'ext4_empty_dir' just return false. To avoid making things worse when file system is already corrupted, 'ext4_empty_dir' also return false. Signed-off-by: Ye Bin <[email protected]> Cc: [email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Theodore Ts'o <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent a1e6884 commit b35eb48

File tree

2 files changed

+9
-10
lines changed

2 files changed

+9
-10
lines changed

fs/ext4/inline.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,19 +1768,20 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
17681768
void *inline_pos;
17691769
unsigned int offset;
17701770
struct ext4_dir_entry_2 *de;
1771-
bool ret = true;
1771+
bool ret = false;
17721772

17731773
err = ext4_get_inode_loc(dir, &iloc);
17741774
if (err) {
17751775
EXT4_ERROR_INODE_ERR(dir, -err,
17761776
"error %d getting inode %lu block",
17771777
err, dir->i_ino);
1778-
return true;
1778+
return false;
17791779
}
17801780

17811781
down_read(&EXT4_I(dir)->xattr_sem);
17821782
if (!ext4_has_inline_data(dir)) {
17831783
*has_inline_data = 0;
1784+
ret = true;
17841785
goto out;
17851786
}
17861787

@@ -1789,7 +1790,6 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
17891790
ext4_warning(dir->i_sb,
17901791
"bad inline directory (dir #%lu) - no `..'",
17911792
dir->i_ino);
1792-
ret = true;
17931793
goto out;
17941794
}
17951795

@@ -1808,16 +1808,15 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
18081808
dir->i_ino, le32_to_cpu(de->inode),
18091809
le16_to_cpu(de->rec_len), de->name_len,
18101810
inline_size);
1811-
ret = true;
18121811
goto out;
18131812
}
18141813
if (le32_to_cpu(de->inode)) {
1815-
ret = false;
18161814
goto out;
18171815
}
18181816
offset += ext4_rec_len_from_disk(de->rec_len, inline_size);
18191817
}
18201818

1819+
ret = true;
18211820
out:
18221821
up_read(&EXT4_I(dir)->xattr_sem);
18231822
brelse(iloc.bh);

fs/ext4/namei.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2868,22 +2868,22 @@ bool ext4_empty_dir(struct inode *inode)
28682868
sb = inode->i_sb;
28692869
if (inode->i_size < EXT4_DIR_REC_LEN(1) + EXT4_DIR_REC_LEN(2)) {
28702870
EXT4_ERROR_INODE(inode, "invalid size");
2871-
return true;
2871+
return false;
28722872
}
28732873
/* The first directory block must not be a hole,
28742874
* so treat it as DIRENT_HTREE
28752875
*/
28762876
bh = ext4_read_dirblock(inode, 0, DIRENT_HTREE);
28772877
if (IS_ERR(bh))
2878-
return true;
2878+
return false;
28792879

28802880
de = (struct ext4_dir_entry_2 *) bh->b_data;
28812881
if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
28822882
0) ||
28832883
le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) {
28842884
ext4_warning_inode(inode, "directory missing '.'");
28852885
brelse(bh);
2886-
return true;
2886+
return false;
28872887
}
28882888
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
28892889
de = ext4_next_entry(de, sb->s_blocksize);
@@ -2892,7 +2892,7 @@ bool ext4_empty_dir(struct inode *inode)
28922892
le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) {
28932893
ext4_warning_inode(inode, "directory missing '..'");
28942894
brelse(bh);
2895-
return true;
2895+
return false;
28962896
}
28972897
offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
28982898
while (offset < inode->i_size) {
@@ -2906,7 +2906,7 @@ bool ext4_empty_dir(struct inode *inode)
29062906
continue;
29072907
}
29082908
if (IS_ERR(bh))
2909-
return true;
2909+
return false;
29102910
}
29112911
de = (struct ext4_dir_entry_2 *) (bh->b_data +
29122912
(offset & (sb->s_blocksize - 1)));

0 commit comments

Comments
 (0)