Skip to content

Commit 46f881b

Browse files
zhangyi089tytso
authored andcommitted
jbd2: fix a race when checking checkpoint buffer busy
Before removing checkpoint buffer from the t_checkpoint_list, we have to check both BH_Dirty and BH_Lock bits together to distinguish buffers have not been or were being written back. But __cp_buffer_busy() checks them separately, it first check lock state and then check dirty, the window between these two checks could be raced by writing back procedure, which locks buffer and clears buffer dirty before I/O completes. So it cannot guarantee checkpointing buffers been written back to disk if some error happens later. Finally, it may clean checkpoint transactions and lead to inconsistent filesystem. jbd2_journal_forget() and __journal_try_to_free_buffer() also have the same problem (journal_unmap_buffer() escape from this issue since it's running under the buffer lock), so fix them through introducing a new helper to try holding the buffer lock and remove really clean buffer. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217490 Cc: stable@vger.kernel.org Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://lore.kernel.org/r/20230606135928.434610-6-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
1 parent e34c8dd commit 46f881b

File tree

3 files changed

+41
-15
lines changed

3 files changed

+41
-15
lines changed

fs/jbd2/checkpoint.c

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,11 +376,15 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,
376376
jh = next_jh;
377377
next_jh = jh->b_cpnext;
378378

379-
if (!destroy && __cp_buffer_busy(jh))
380-
continue;
379+
if (destroy) {
380+
ret = __jbd2_journal_remove_checkpoint(jh);
381+
} else {
382+
ret = jbd2_journal_try_remove_checkpoint(jh);
383+
if (ret < 0)
384+
continue;
385+
}
381386

382387
nr_freed++;
383-
ret = __jbd2_journal_remove_checkpoint(jh);
384388
if (ret) {
385389
*released = true;
386390
break;
@@ -616,6 +620,34 @@ int __jbd2_journal_remove_checkpoint(struct journal_head *jh)
616620
return 1;
617621
}
618622

623+
/*
624+
* Check the checkpoint buffer and try to remove it from the checkpoint
625+
* list if it's clean. Returns -EBUSY if it is not clean, returns 1 if
626+
* it frees the transaction, 0 otherwise.
627+
*
628+
* This function is called with j_list_lock held.
629+
*/
630+
int jbd2_journal_try_remove_checkpoint(struct journal_head *jh)
631+
{
632+
struct buffer_head *bh = jh2bh(jh);
633+
634+
if (!trylock_buffer(bh))
635+
return -EBUSY;
636+
if (buffer_dirty(bh)) {
637+
unlock_buffer(bh);
638+
return -EBUSY;
639+
}
640+
unlock_buffer(bh);
641+
642+
/*
643+
* Buffer is clean and the IO has finished (we held the buffer
644+
* lock) so the checkpoint is done. We can safely remove the
645+
* buffer from this transaction.
646+
*/
647+
JBUFFER_TRACE(jh, "remove from checkpoint list");
648+
return __jbd2_journal_remove_checkpoint(jh);
649+
}
650+
619651
/*
620652
* journal_insert_checkpoint: put a committed buffer onto a checkpoint
621653
* list so that we know when it is safe to clean the transaction out of

fs/jbd2/transaction.c

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,8 +1784,7 @@ int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh)
17841784
* Otherwise, if the buffer has been written to disk,
17851785
* it is safe to remove the checkpoint and drop it.
17861786
*/
1787-
if (!buffer_dirty(bh)) {
1788-
__jbd2_journal_remove_checkpoint(jh);
1787+
if (jbd2_journal_try_remove_checkpoint(jh) >= 0) {
17891788
spin_unlock(&journal->j_list_lock);
17901789
goto drop;
17911790
}
@@ -2112,20 +2111,14 @@ __journal_try_to_free_buffer(journal_t *journal, struct buffer_head *bh)
21122111

21132112
jh = bh2jh(bh);
21142113

2115-
if (buffer_locked(bh) || buffer_dirty(bh))
2116-
goto out;
2117-
21182114
if (jh->b_next_transaction != NULL || jh->b_transaction != NULL)
2119-
goto out;
2115+
return;
21202116

21212117
spin_lock(&journal->j_list_lock);
2122-
if (jh->b_cp_transaction != NULL) {
2123-
/* written-back checkpointed metadata buffer */
2124-
JBUFFER_TRACE(jh, "remove from checkpoint list");
2125-
__jbd2_journal_remove_checkpoint(jh);
2126-
}
2118+
/* Remove written-back checkpointed metadata buffer */
2119+
if (jh->b_cp_transaction != NULL)
2120+
jbd2_journal_try_remove_checkpoint(jh);
21272121
spin_unlock(&journal->j_list_lock);
2128-
out:
21292122
return;
21302123
}
21312124

include/linux/jbd2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,7 @@ extern void jbd2_journal_commit_transaction(journal_t *);
14431443
void __jbd2_journal_clean_checkpoint_list(journal_t *journal, bool destroy);
14441444
unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal, unsigned long *nr_to_scan);
14451445
int __jbd2_journal_remove_checkpoint(struct journal_head *);
1446+
int jbd2_journal_try_remove_checkpoint(struct journal_head *jh);
14461447
void jbd2_journal_destroy_checkpoint(journal_t *journal);
14471448
void __jbd2_journal_insert_checkpoint(struct journal_head *, transaction_t *);
14481449

0 commit comments

Comments
 (0)