From: Lukas Czerner <lukas@czerner.cz>
To: linux-ext4@vger.kernel.org
Cc: Theodore Ts'o <tytso@mit.edu>, Lukas Czerner <lukas@czerner.cz>
Subject: [PATCH 1/2] ext4: fix failed migrate freeing extent tree blocks
Date: Sun, 25 Oct 2026 21:18:02 +0100 [thread overview]
Message-ID: <20261025201803.3043712-2-lukas@czerner.cz> (raw)
In-Reply-To: <20261025201803.3043712-1-lukas@czerner.cz>
In case ext4_ext_swap_inode_data() fails it will clean up a failed
migration by freeing the extent tree in the tmp_inode. However, in
case we already copied the temporary inode's extent tree root into
the original inode, the process has been functionally finished.
The error case clean up calls free_ext_block() which will free those
extent tree blocks while the original inode already points at them and
the filesystem ends up with multiply claimed blocks.
Fix it by resetting the temporary inode's root to an empty tree right
after copying i_data to the original inode, so free_ext_block() won't
free the original inode extent tree blocks. Move temporary inode's
i_blocks as well so eviction won't try to truncate metadata blocks the
tmp_inode no longer owns.
Signed-off-by: Lukas Czerner <lukas@czerner.cz>
---
fs/ext4/ext4.h | 1 +
fs/ext4/extents.c | 11 ++++++++++-
fs/ext4/migrate.c | 13 ++++++++++++-
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 1ee5733e2b31..915476d4d089 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3903,6 +3903,7 @@ struct ext4_extent;
*/
#define EXT_MAX_BLOCKS 0xffffffff
+extern void ext4_ext_init_root(struct inode *inode);
extern void ext4_ext_tree_init(handle_t *handle, struct inode *inode);
extern int ext4_ext_index_trans_blocks(struct inode *inode, int extents);
extern int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 3bf0a6cdbec2..ada059f83295 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -869,7 +869,11 @@ ext4_ext_binsearch(struct inode *inode,
}
-void ext4_ext_tree_init(handle_t *handle, struct inode *inode)
+/*
+ * Initialize the extent tree of inode. This only changes the in-memory
+ * inode, the caller has to mark the inode dirty.
+ */
+void ext4_ext_init_root(struct inode *inode)
{
struct ext4_extent_header *eh;
@@ -879,6 +883,11 @@ void ext4_ext_tree_init(handle_t *handle, struct inode *inode)
eh->eh_magic = EXT4_EXT_MAGIC;
eh->eh_max = cpu_to_le16(ext4_ext_space_root(inode, 0));
eh->eh_generation = 0;
+}
+
+void ext4_ext_tree_init(handle_t *handle, struct inode *inode)
+{
+ ext4_ext_init_root(inode);
ext4_mark_inode_dirty(handle, inode);
}
diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c
index 5d60ef10fe11..0d32cfa55785 100644
--- a/fs/ext4/migrate.c
+++ b/fs/ext4/migrate.c
@@ -322,6 +322,12 @@ static int ext4_ext_swap_inode_data(handle_t *handle, struct inode *inode,
*/
ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
memcpy(ei->i_data, tmp_ei->i_data, sizeof(ei->i_data));
+ /*
+ * The extent tree belongs to the inode now. Make tmp_inode's extent
+ * tree empty, so that cleaning up tmp_inode can't free the tree
+ * blocks, if we fail to finish the migration.
+ */
+ ext4_ext_init_root(tmp_inode);
/*
* Update i_blocks with the new blocks that got
@@ -331,10 +337,13 @@ static int ext4_ext_swap_inode_data(handle_t *handle, struct inode *inode,
* While converting to extents we need not
* update the original inode i_blocks for extent blocks
* via quota APIs. The quota update happened via tmp_inode already.
+ * Clear tmp_inode i_blocks to avoid truncating original data blocks
+ * in case we fail to finish the migration.
*/
spin_lock(&inode->i_lock);
inode->i_blocks += tmp_inode->i_blocks;
spin_unlock(&inode->i_lock);
+ tmp_inode->i_blocks = 0;
up_write(&EXT4_I(inode)->i_data_sem);
/*
@@ -561,7 +570,9 @@ int ext4_ext_migrate(struct inode *inode)
if (retval)
/*
* if we fail to swap inode data free the extent
- * details of the tmp inode
+ * details of the tmp inode. If the tree was already
+ * moved to the original inode, the tmp_inode has an
+ * empty tree and there is nothing to free.
*/
free_ext_block(handle, tmp_inode);
}
--
2.56.0
next prev parent reply other threads:[~2026-10-25 20:18 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-25 20:18 [PATCH 0/2] ext4: fix error handling of failed migration Lukas Czerner
2026-10-25 20:18 ` Lukas Czerner [this message]
2026-10-25 20:45 ` [PATCH 1/2] ext4: fix failed migrate freeing extent tree blocks sashiko-bot
2026-10-25 20:18 ` [PATCH 2/2] ext4: don't lose the error of a failed migration Lukas Czerner
2026-10-25 20:34 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261025201803.3043712-2-lukas@czerner.cz \
--to=lukas@czerner.cz \
--cc=linux-ext4@vger.kernel.org \
--cc=tytso@mit.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox