summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhihao Cheng <chengzhihao1@huawei.com>2023-06-06 16:59:26 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2023-08-03 11:23:47 +0300
commit0935bbbf6e5aa55b7ebb3bde7ae1df44905121df (patch)
treee39f6c4bbacefa641146b016f0bad7209c9aefb9
parent6e385845eea187c573949e31e21d6934af1f3415 (diff)
downloadlinux-0935bbbf6e5aa55b7ebb3bde7ae1df44905121df.tar.xz
jbd2: Fix wrongly judgement for buffer head removing while doing checkpoint
[ Upstream commit e34c8dd238d0c9368b746480f313055f5bab5040 ] Following process, jbd2_journal_commit_transaction // there are several dirty buffer heads in transaction->t_checkpoint_list P1 wb_workfn jbd2_log_do_checkpoint if (buffer_locked(bh)) // false __block_write_full_page trylock_buffer(bh) test_clear_buffer_dirty(bh) if (!buffer_dirty(bh)) __jbd2_journal_remove_checkpoint(jh) if (buffer_write_io_error(bh)) // false >> bh IO error occurs << jbd2_cleanup_journal_tail __jbd2_update_log_tail jbd2_write_superblock // The bh won't be replayed in next mount. , which could corrupt the ext4 image, fetch a reproducer in [Link]. Since writeback process clears buffer dirty after locking buffer head, we can fix it by try locking buffer and check dirtiness while buffer is locked, the buffer head can be removed if it is neither dirty nor locked. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217490 Fixes: 470decc613ab ("[PATCH] jbd2: initial copy of files from jbd") Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://lore.kernel.org/r/20230606135928.434610-5-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu> Signed-off-by: Sasha Levin <sashal@kernel.org>
-rw-r--r--fs/jbd2/checkpoint.c32
1 files changed, 17 insertions, 15 deletions
diff --git a/fs/jbd2/checkpoint.c b/fs/jbd2/checkpoint.c
index 25e3c20eb19f..c4e0da6db719 100644
--- a/fs/jbd2/checkpoint.c
+++ b/fs/jbd2/checkpoint.c
@@ -221,20 +221,6 @@ restart:
jh = transaction->t_checkpoint_list;
bh = jh2bh(jh);
- /*
- * The buffer may be writing back, or flushing out in the
- * last couple of cycles, or re-adding into a new transaction,
- * need to check it again until it's unlocked.
- */
- if (buffer_locked(bh)) {
- get_bh(bh);
- spin_unlock(&journal->j_list_lock);
- wait_on_buffer(bh);
- /* the journal_head may have gone by now */
- BUFFER_TRACE(bh, "brelse");
- __brelse(bh);
- goto retry;
- }
if (jh->b_transaction != NULL) {
transaction_t *t = jh->b_transaction;
tid_t tid = t->t_tid;
@@ -269,7 +255,22 @@ restart:
spin_lock(&journal->j_list_lock);
goto restart;
}
- if (!buffer_dirty(bh)) {
+ if (!trylock_buffer(bh)) {
+ /*
+ * The buffer is locked, it may be writing back, or
+ * flushing out in the last couple of cycles, or
+ * re-adding into a new transaction, need to check
+ * it again until it's unlocked.
+ */
+ get_bh(bh);
+ spin_unlock(&journal->j_list_lock);
+ wait_on_buffer(bh);
+ /* the journal_head may have gone by now */
+ BUFFER_TRACE(bh, "brelse");
+ __brelse(bh);
+ goto retry;
+ } else if (!buffer_dirty(bh)) {
+ unlock_buffer(bh);
BUFFER_TRACE(bh, "remove from checkpoint");
/*
* If the transaction was released or the checkpoint
@@ -279,6 +280,7 @@ restart:
!transaction->t_checkpoint_list)
goto out;
} else {
+ unlock_buffer(bh);
/*
* We are about to write the buffer, it could be
* raced by some other transaction shrink or buffer