jbd2: fix data missing when reusing bh which is ready to be checkpointed
Following process will make data lost and could lead to a filesystem corrupted problem: 1. jh(bh) is inserted into T1->t_checkpoint_list, bh is dirty, and jh->b_transaction = NULL 2. T1 is added into journal->j_checkpoint_transactions. 3. Get bh prepare to write while doing checkpoing: PA PB do_get_write_access jbd2_log_do_checkpoint spin_lock(&jh->b_state_lock) if (buffer_dirty(bh)) clear_buffer_dirty(bh) // clear buffer dirty set_buffer_jbddirty(bh) transaction = journal->j_checkpoint_transactions jh = transaction->t_checkpoint_list if (!buffer_dirty(bh)) __jbd2_journal_remove_checkpoint(jh) // bh won't be flushed jbd2_cleanup_journal_tail __jbd2_journal_file_buffer(jh, transaction, BJ_Reserved) 4. Aborting journal/Power-cut before writing latest bh on journal area. In this way we get a corrupted filesystem with bh's data lost. Fix it by moving the clearing of buffer_dirty bit just before the call to __jbd2_journal_file_buffer(), both bit clearing and jh->b_transaction assignment are under journal->j_list_lock locked, so that jbd2_log_do_checkpoint() will wait until jh's new transaction fininshed even bh is currently not dirty. And journal_shrink_one_cp_list() won't remove jh from checkpoint list if the buffer head is reused in do_get_write_access(). Fetch a reproducer in [Link]. Link: https://bugzilla.kernel.org/show_bug.cgi?id=216898 Cc: <stable@kernel.org> Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com> Signed-off-by: zhanchengbin <zhanchengbin1@huawei.com> Suggested-by: Jan Kara <jack@suse.cz> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://lore.kernel.org/r/20230110015327.1181863-1-chengzhihao1@huawei.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
3039d8b869
commit
e6b9bd7290
@ -1010,36 +1010,28 @@ repeat:
|
||||
* ie. locked but not dirty) or tune2fs (which may actually have
|
||||
* the buffer dirtied, ugh.) */
|
||||
|
||||
if (buffer_dirty(bh)) {
|
||||
if (buffer_dirty(bh) && jh->b_transaction) {
|
||||
warn_dirty_buffer(bh);
|
||||
/*
|
||||
* First question: is this buffer already part of the current
|
||||
* transaction or the existing committing transaction?
|
||||
*/
|
||||
if (jh->b_transaction) {
|
||||
J_ASSERT_JH(jh,
|
||||
jh->b_transaction == transaction ||
|
||||
jh->b_transaction ==
|
||||
journal->j_committing_transaction);
|
||||
if (jh->b_next_transaction)
|
||||
J_ASSERT_JH(jh, jh->b_next_transaction ==
|
||||
transaction);
|
||||
warn_dirty_buffer(bh);
|
||||
}
|
||||
/*
|
||||
* In any case we need to clean the dirty flag and we must
|
||||
* do it under the buffer lock to be sure we don't race
|
||||
* with running write-out.
|
||||
* We need to clean the dirty flag and we must do it under the
|
||||
* buffer lock to be sure we don't race with running write-out.
|
||||
*/
|
||||
JBUFFER_TRACE(jh, "Journalling dirty buffer");
|
||||
clear_buffer_dirty(bh);
|
||||
/*
|
||||
* The buffer is going to be added to BJ_Reserved list now and
|
||||
* nothing guarantees jbd2_journal_dirty_metadata() will be
|
||||
* ever called for it. So we need to set jbddirty bit here to
|
||||
* make sure the buffer is dirtied and written out when the
|
||||
* journaling machinery is done with it.
|
||||
*/
|
||||
set_buffer_jbddirty(bh);
|
||||
}
|
||||
|
||||
unlock_buffer(bh);
|
||||
|
||||
error = -EROFS;
|
||||
if (is_handle_aborted(handle)) {
|
||||
spin_unlock(&jh->b_state_lock);
|
||||
unlock_buffer(bh);
|
||||
goto out;
|
||||
}
|
||||
error = 0;
|
||||
@ -1049,8 +1041,10 @@ repeat:
|
||||
* b_next_transaction points to it
|
||||
*/
|
||||
if (jh->b_transaction == transaction ||
|
||||
jh->b_next_transaction == transaction)
|
||||
jh->b_next_transaction == transaction) {
|
||||
unlock_buffer(bh);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* this is the first time this transaction is touching this buffer,
|
||||
@ -1074,10 +1068,24 @@ repeat:
|
||||
*/
|
||||
smp_wmb();
|
||||
spin_lock(&journal->j_list_lock);
|
||||
if (test_clear_buffer_dirty(bh)) {
|
||||
/*
|
||||
* Execute buffer dirty clearing and jh->b_transaction
|
||||
* assignment under journal->j_list_lock locked to
|
||||
* prevent bh being removed from checkpoint list if
|
||||
* the buffer is in an intermediate state (not dirty
|
||||
* and jh->b_transaction is NULL).
|
||||
*/
|
||||
JBUFFER_TRACE(jh, "Journalling dirty buffer");
|
||||
set_buffer_jbddirty(bh);
|
||||
}
|
||||
__jbd2_journal_file_buffer(jh, transaction, BJ_Reserved);
|
||||
spin_unlock(&journal->j_list_lock);
|
||||
unlock_buffer(bh);
|
||||
goto done;
|
||||
}
|
||||
unlock_buffer(bh);
|
||||
|
||||
/*
|
||||
* If there is already a copy-out version of this buffer, then we don't
|
||||
* need to make another one
|
||||
|
Loading…
Reference in New Issue
Block a user