summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorEric Whitney <enwlinux@gmail.com>2014-04-01 19:49:30 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-04-26 17:19:06 -0700
commitc80e9ae4c5ac98314d8b3d1d5702bb9ceae4d346 (patch)
treef87ce31e682cea2ca1034f84e6adcf284ea90b6c /fs
parent5ffaffe424119ade4c03bdbe08387a952e318db2 (diff)
downloadlwn-c80e9ae4c5ac98314d8b3d1d5702bb9ceae4d346.tar.gz
lwn-c80e9ae4c5ac98314d8b3d1d5702bb9ceae4d346.zip
ext4: fix premature freeing of partial clusters split across leaf blocks
commit ad6599ab3ac98a4474544086e048ce86ec15a4d1 upstream. Xfstests generic/311 and shared/298 fail when run on a bigalloc file system. Kernel error messages produced during the tests report that blocks to be freed are already on the to-be-freed list. When e2fsck is run at the end of the tests, it typically reports bad i_blocks and bad free blocks counts. The bug that causes these failures is located in ext4_ext_rm_leaf(). Code at the end of the function frees a partial cluster if it's not shared with an extent remaining in the leaf. However, if all the extents in the leaf have been removed, the code dereferences an invalid extent pointer (off the front of the leaf) when the check for sharing is made. This generally has the effect of unconditionally freeing the partial cluster, which leads to the observed failures when the partial cluster is shared with the last extent in the next leaf. Fix this by attempting to free the cluster only if extents remain in the leaf. Any remaining partial cluster will be freed if possible when the next leaf is processed or when leaf removal is complete. Signed-off-by: Eric Whitney <enwlinux@gmail.com> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/ext4/extents.c11
1 files changed, 8 insertions, 3 deletions
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index fa8b4e2b72e7..47188916dd8d 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -2731,10 +2731,15 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
err = ext4_ext_correct_indexes(handle, inode, path);
/*
- * Free the partial cluster only if the current extent does not
- * reference it. Otherwise we might free used cluster.
+ * If there's a partial cluster and at least one extent remains in
+ * the leaf, free the partial cluster if it isn't shared with the
+ * current extent. If there's a partial cluster and no extents
+ * remain in the leaf, it can't be freed here. It can only be
+ * freed when it's possible to determine if it's not shared with
+ * any other extent - when the next leaf is processed or when space
+ * removal is complete.
*/
- if (*partial_cluster > 0 &&
+ if (*partial_cluster > 0 && eh->eh_entries &&
(EXT4_B2C(sbi, ext4_ext_pblock(ex) + ex_ee_len - 1) !=
*partial_cluster)) {
int flags = get_default_free_blocks_flags(inode);