Skip to content

Commit 7aab5c8

Browse files
Ye Bintytso
authored andcommitted
ext4: fix fs corruption when tring to remove a non-empty directory with IO error
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]>
1 parent a861fb9 commit 7aab5c8

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
@@ -1783,19 +1783,20 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
17831783
void *inline_pos;
17841784
unsigned int offset;
17851785
struct ext4_dir_entry_2 *de;
1786-
bool ret = true;
1786+
bool ret = false;
17871787

17881788
err = ext4_get_inode_loc(dir, &iloc);
17891789
if (err) {
17901790
EXT4_ERROR_INODE_ERR(dir, -err,
17911791
"error %d getting inode %lu block",
17921792
err, dir->i_ino);
1793-
return true;
1793+
return false;
17941794
}
17951795

17961796
down_read(&EXT4_I(dir)->xattr_sem);
17971797
if (!ext4_has_inline_data(dir)) {
17981798
*has_inline_data = 0;
1799+
ret = true;
17991800
goto out;
18001801
}
18011802

@@ -1804,7 +1805,6 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
18041805
ext4_warning(dir->i_sb,
18051806
"bad inline directory (dir #%lu) - no `..'",
18061807
dir->i_ino);
1807-
ret = true;
18081808
goto out;
18091809
}
18101810

@@ -1823,16 +1823,15 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
18231823
dir->i_ino, le32_to_cpu(de->inode),
18241824
le16_to_cpu(de->rec_len), de->name_len,
18251825
inline_size);
1826-
ret = true;
18271826
goto out;
18281827
}
18291828
if (le32_to_cpu(de->inode)) {
1830-
ret = false;
18311829
goto out;
18321830
}
18331831
offset += ext4_rec_len_from_disk(de->rec_len, inline_size);
18341832
}
18351833

1834+
ret = true;
18361835
out:
18371836
up_read(&EXT4_I(dir)->xattr_sem);
18381837
brelse(iloc.bh);

fs/ext4/namei.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,22 +2997,22 @@ bool ext4_empty_dir(struct inode *inode)
29972997
if (inode->i_size < ext4_dir_rec_len(1, NULL) +
29982998
ext4_dir_rec_len(2, NULL)) {
29992999
EXT4_ERROR_INODE(inode, "invalid size");
3000-
return true;
3000+
return false;
30013001
}
30023002
/* The first directory block must not be a hole,
30033003
* so treat it as DIRENT_HTREE
30043004
*/
30053005
bh = ext4_read_dirblock(inode, 0, DIRENT_HTREE);
30063006
if (IS_ERR(bh))
3007-
return true;
3007+
return false;
30083008

30093009
de = (struct ext4_dir_entry_2 *) bh->b_data;
30103010
if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
30113011
0) ||
30123012
le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) {
30133013
ext4_warning_inode(inode, "directory missing '.'");
30143014
brelse(bh);
3015-
return true;
3015+
return false;
30163016
}
30173017
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
30183018
de = ext4_next_entry(de, sb->s_blocksize);
@@ -3021,7 +3021,7 @@ bool ext4_empty_dir(struct inode *inode)
30213021
le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) {
30223022
ext4_warning_inode(inode, "directory missing '..'");
30233023
brelse(bh);
3024-
return true;
3024+
return false;
30253025
}
30263026
offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
30273027
while (offset < inode->i_size) {
@@ -3035,7 +3035,7 @@ bool ext4_empty_dir(struct inode *inode)
30353035
continue;
30363036
}
30373037
if (IS_ERR(bh))
3038-
return true;
3038+
return false;
30393039
}
30403040
de = (struct ext4_dir_entry_2 *) (bh->b_data +
30413041
(offset & (sb->s_blocksize - 1)));

0 commit comments

Comments
 (0)