| From 6fed83957f21eff11c8496e9f24253b03d2bc1dc Mon Sep 17 00:00:00 2001 |
| From: Jeffle Xu <jefflexu@linux.alibaba.com> |
| Date: Mon, 23 Aug 2021 14:13:58 +0800 |
| Subject: ext4: fix reserved space counter leakage |
| |
| From: Jeffle Xu <jefflexu@linux.alibaba.com> |
| |
| 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> |
| --- |
| fs/ext4/inode.c | 5 +++++ |
| fs/ext4/super.c | 6 ++++++ |
| 2 files changed, 11 insertions(+) |
| |
| --- a/fs/ext4/inode.c |
| +++ b/fs/ext4/inode.c |
| @@ -1641,6 +1641,7 @@ static int ext4_insert_delayed_block(str |
| 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, |
| @@ -1657,6 +1658,7 @@ static int ext4_insert_delayed_block(str |
| 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, |
| @@ -1669,6 +1671,7 @@ static int ext4_insert_delayed_block(str |
| ret = ext4_da_reserve_space(inode); |
| if (ret != 0) /* ENOSPC */ |
| goto errout; |
| + reserved = true; |
| } else { |
| allocated = true; |
| } |
| @@ -1679,6 +1682,8 @@ static int ext4_insert_delayed_block(str |
| } |
| |
| ret = ext4_es_insert_delayed_block(inode, lblk, allocated); |
| + if (ret && reserved) |
| + ext4_da_release_space(inode, 1); |
| |
| errout: |
| return ret; |
| --- a/fs/ext4/super.c |
| +++ b/fs/ext4/super.c |
| @@ -1356,6 +1356,12 @@ static void ext4_destroy_inode(struct in |
| 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) |