ext3: Avoid filesystem corruption after a crash under heavy delete load
authorJan Kara <jack@suse.cz>
Mon, 12 Jul 2010 19:04:31 +0000 (21:04 +0200)
committerJan Kara <jack@suse.cz>
Wed, 21 Jul 2010 14:04:26 +0000 (16:04 +0200)
It can happen that ext3_free_branches calls ext3_forget() for an indirect block
in an earlier transaction than a transaction in which we clear pointer to this
indirect block. Thus if we crash before a transaction clearing the block
pointer is committed, we will see indirect block pointing to already freed
blocks and complain during orphan list cleanup.

The fix is simple: Make sure ext3_forget() is called in the transaction
doing block pointer clearing.

This is a backport of an ext4 fix by Amir G. <amir73il@users.sourceforge.net>

Signed-off-by: Jan Kara <jack@suse.cz>
fs/ext3/inode.c

index a786db403efc5d0e9d6036ea7f65f5fd65f2d46c..436e5bbccbc2606922b87fe83edef3e8acb1edfd 100644 (file)
@@ -2269,27 +2269,6 @@ static void ext3_free_branches(handle_t *handle, struct inode *inode,
                                           (__le32*)bh->b_data + addr_per_block,
                                           depth);
 
-                       /*
-                        * We've probably journalled the indirect block several
-                        * times during the truncate.  But it's no longer
-                        * needed and we now drop it from the transaction via
-                        * journal_revoke().
-                        *
-                        * That's easy if it's exclusively part of this
-                        * transaction.  But if it's part of the committing
-                        * transaction then journal_forget() will simply
-                        * brelse() it.  That means that if the underlying
-                        * block is reallocated in ext3_get_block(),
-                        * unmap_underlying_metadata() will find this block
-                        * and will try to get rid of it.  damn, damn.
-                        *
-                        * If this block has already been committed to the
-                        * journal, a revoke record will be written.  And
-                        * revoke records must be emitted *before* clearing
-                        * this block's bit in the bitmaps.
-                        */
-                       ext3_forget(handle, 1, inode, bh, bh->b_blocknr);
-
                        /*
                         * Everything below this this pointer has been
                         * released.  Now let this top-of-subtree go.
@@ -2313,6 +2292,31 @@ static void ext3_free_branches(handle_t *handle, struct inode *inode,
                                truncate_restart_transaction(handle, inode);
                        }
 
+                       /*
+                        * We've probably journalled the indirect block several
+                        * times during the truncate.  But it's no longer
+                        * needed and we now drop it from the transaction via
+                        * journal_revoke().
+                        *
+                        * That's easy if it's exclusively part of this
+                        * transaction.  But if it's part of the committing
+                        * transaction then journal_forget() will simply
+                        * brelse() it.  That means that if the underlying
+                        * block is reallocated in ext3_get_block(),
+                        * unmap_underlying_metadata() will find this block
+                        * and will try to get rid of it.  damn, damn. Thus
+                        * we don't allow a block to be reallocated until
+                        * a transaction freeing it has fully committed.
+                        *
+                        * We also have to make sure journal replay after a
+                        * crash does not overwrite non-journaled data blocks
+                        * with old metadata when the block got reallocated for
+                        * data.  Thus we have to store a revoke record for a
+                        * block in the same transaction in which we free the
+                        * block.
+                        */
+                       ext3_forget(handle, 1, inode, bh, bh->b_blocknr);
+
                        ext3_free_blocks(handle, inode, nr, 1);
 
                        if (parent_bh) {