From: Jeffle Xu jefflexu@linux.alibaba.com
stable inclusion from stable-5.10.71 commit 9ccf35492b084ecbc916761a5c6f42599450f013 bugzilla: 182981 https://gitee.com/openeuler/kernel/issues/I4I3KD
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=...
--------------------------------
commit 6fed83957f21eff11c8496e9f24253b03d2bc1dc upstream.
When ext4_insert_delayed block receives and recovers from an error from ext4_es_insert_delayed_block(), e.g., ENOMEM, it does not release the space it has reserved for that block insertion as it should. One effect of this bug is that s_dirtyclusters_counter is not decremented and remains incorrectly elevated until the file system has been unmounted. This can result in premature ENOSPC returns and apparent loss of free space.
Another effect of this bug is that /sys/fs/ext4/<dev>/delayed_allocation_blocks can remain non-zero even after syncfs has been executed on the filesystem.
Besides, add check for s_dirtyclusters_counter when inode is going to be evicted and freed. s_dirtyclusters_counter can still keep non-zero until inode is written back in .evict_inode(), and thus the check is delayed to .destroy_inode().
Fixes: 51865fda28e5 ("ext4: let ext4 maintain extent status tree") Cc: stable@kernel.org Suggested-by: Gao Xiang hsiangkao@linux.alibaba.com Signed-off-by: Jeffle Xu jefflexu@linux.alibaba.com Reviewed-by: Eric Whitney enwlinux@gmail.com Signed-off-by: Theodore Ts'o tytso@mit.edu Link: https://lore.kernel.org/r/20210823061358.84473-1-jefflexu@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman gregkh@linuxfoundation.org Signed-off-by: Chen Jun chenjun102@huawei.com Acked-by: Weilong Chen chenweilong@huawei.com
Signed-off-by: Chen Jun chenjun102@huawei.com --- fs/ext4/inode.c | 5 +++++ fs/ext4/super.c | 6 ++++++ 2 files changed, 11 insertions(+)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 94c9a4d01102..0415548afc71 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -1628,6 +1628,7 @@ static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk) struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb); int ret; bool allocated = false; + bool reserved = false;
/* * If the cluster containing lblk is shared with a delayed, @@ -1644,6 +1645,7 @@ static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk) ret = ext4_da_reserve_space(inode); if (ret != 0) /* ENOSPC */ goto errout; + reserved = true; } else { /* bigalloc */ if (!ext4_es_scan_clu(inode, &ext4_es_is_delonly, lblk)) { if (!ext4_es_scan_clu(inode, @@ -1656,6 +1658,7 @@ static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk) ret = ext4_da_reserve_space(inode); if (ret != 0) /* ENOSPC */ goto errout; + reserved = true; } else { allocated = true; } @@ -1666,6 +1669,8 @@ static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk) }
ret = ext4_es_insert_delayed_block(inode, lblk, allocated); + if (ret && reserved) + ext4_da_release_space(inode, 1);
errout: return ret; diff --git a/fs/ext4/super.c b/fs/ext4/super.c index bfa2f84dc7b1..16e228285967 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1348,6 +1348,12 @@ static void ext4_destroy_inode(struct inode *inode) true); dump_stack(); } + + if (EXT4_I(inode)->i_reserved_data_blocks) + ext4_msg(inode->i_sb, KERN_ERR, + "Inode %lu (%p): i_reserved_data_blocks (%u) not cleared!", + inode->i_ino, EXT4_I(inode), + EXT4_I(inode)->i_reserved_data_blocks); }
static void init_once(void *foo)