Skip to content

Commit 3c652c3

Browse files
zhangyi089tytso
authored andcommitted
jbd2: ensure that all ongoing I/O complete before freeing blocks
When releasing file system metadata blocks in jbd2_journal_forget(), if this buffer has not yet been checkpointed, it may have already been written back, currently be in the process of being written back, or has not yet written back. jbd2_journal_forget() calls jbd2_journal_try_remove_checkpoint() to check the buffer's status and add it to the current transaction if it has not been written back. This buffer can only be reallocated after the transaction is committed. jbd2_journal_try_remove_checkpoint() attempts to lock the buffer and check its dirty status while holding the buffer lock. If the buffer has already been written back, everything proceeds normally. However, there are two issues. First, the function returns immediately if the buffer is locked by the write-back process. It does not wait for the write-back to complete. Consequently, until the current transaction is committed and the block is reallocated, there is no guarantee that the I/O will complete. This means that ongoing I/O could write stale metadata to the newly allocated block, potentially corrupting data. Second, the function unlocks the buffer as soon as it detects that the buffer is still dirty. If a concurrent write-back occurs immediately after this unlocking and before clear_buffer_dirty() is called in jbd2_journal_forget(), data corruption can theoretically still occur. Although these two issues are unlikely to occur in practice since the undergoing metadata writeback I/O does not take this long to complete, it's better to explicitly ensure that all ongoing I/O operations are completed. Fixes: 5975992 ("jbd2: discard dirty data when forgetting an un-journalled buffer") Cc: [email protected] Suggested-by: Jan Kara <[email protected]> Signed-off-by: Zhang Yi <[email protected]> Reviewed-by: Jan Kara <[email protected]> Message-ID: <[email protected]> Signed-off-by: Theodore Ts'o <[email protected]>
1 parent acf943e commit 3c652c3

File tree

1 file changed

+9
-4
lines changed

1 file changed

+9
-4
lines changed

fs/jbd2/transaction.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,7 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh)
16591659
int drop_reserve = 0;
16601660
int err = 0;
16611661
int was_modified = 0;
1662+
int wait_for_writeback = 0;
16621663

16631664
if (is_handle_aborted(handle))
16641665
return -EROFS;
@@ -1782,18 +1783,22 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh)
17821783
}
17831784

17841785
/*
1785-
* The buffer is still not written to disk, we should
1786-
* attach this buffer to current transaction so that the
1787-
* buffer can be checkpointed only after the current
1788-
* transaction commits.
1786+
* The buffer has not yet been written to disk. We should
1787+
* either clear the buffer or ensure that the ongoing I/O
1788+
* is completed, and attach this buffer to current
1789+
* transaction so that the buffer can be checkpointed only
1790+
* after the current transaction commits.
17891791
*/
17901792
clear_buffer_dirty(bh);
1793+
wait_for_writeback = 1;
17911794
__jbd2_journal_file_buffer(jh, transaction, BJ_Forget);
17921795
spin_unlock(&journal->j_list_lock);
17931796
}
17941797
drop:
17951798
__brelse(bh);
17961799
spin_unlock(&jh->b_state_lock);
1800+
if (wait_for_writeback)
1801+
wait_on_buffer(bh);
17971802
jbd2_journal_put_journal_head(jh);
17981803
if (drop_reserve) {
17991804
/* no need to reserve log space for this block -bzzz */

0 commit comments

Comments
 (0)