Commit 1dfc3220 authored by Josef Bacik's avatar Josef Bacik Committed by Theodore Ts'o

jbd2: fix possible journal overflow issues

There are several cases where the running transaction can get buffers
added to its BJ_Metadata list which it never dirtied, which makes its
t_nr_buffers counter end up larger than its t_outstanding_credits
counter.

This will cause issues when starting new transactions as while we are
logging buffers we decrement t_outstanding_buffers, so when
t_outstanding_buffers goes negative, we will report that we need less
space in the journal than we actually need, so transactions will be
started even though there may not be enough room for them.  In the worst
case scenario (which admittedly is almost impossible to reproduce) this
will result in the journal running out of space.

The fix is to only refile buffers from the committing transaction to the
running transactions BJ_Modified list when b_modified is set on that
journal, which is the only way to be sure if the running transaction has
modified that buffer.

This patch also fixes an accounting error in journal_forget, it is
possible that we can call journal_forget on a buffer without having
modified it, only gotten write access to it, so instead of freeing a
credit, we only do so if the buffer was modified.  The assert will help
catch if this problem occurs.  Without these two patches I could hit
this assert within minutes of running postmark, with them this issue no
longer arises.

Cc: <linux-ext4@vger.kernel.org>
Cc: Jan Kara <jack@ucw.cz>
Signed-off-by: default avatarJosef Bacik <jbacik@redhat.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 9fc7c63a
...@@ -568,6 +568,9 @@ void jbd2_journal_commit_transaction(journal_t *journal) ...@@ -568,6 +568,9 @@ void jbd2_journal_commit_transaction(journal_t *journal)
stats.u.run.rs_blocks = commit_transaction->t_outstanding_credits; stats.u.run.rs_blocks = commit_transaction->t_outstanding_credits;
stats.u.run.rs_blocks_logged = 0; stats.u.run.rs_blocks_logged = 0;
J_ASSERT(commit_transaction->t_nr_buffers <=
commit_transaction->t_outstanding_credits);
descriptor = NULL; descriptor = NULL;
bufs = 0; bufs = 0;
while (commit_transaction->t_buffers) { while (commit_transaction->t_buffers) {
......
...@@ -1243,6 +1243,7 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh) ...@@ -1243,6 +1243,7 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh)
struct journal_head *jh; struct journal_head *jh;
int drop_reserve = 0; int drop_reserve = 0;
int err = 0; int err = 0;
int was_modified = 0;
BUFFER_TRACE(bh, "entry"); BUFFER_TRACE(bh, "entry");
...@@ -1261,6 +1262,9 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh) ...@@ -1261,6 +1262,9 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh)
goto not_jbd; goto not_jbd;
} }
/* keep track of wether or not this transaction modified us */
was_modified = jh->b_modified;
/* /*
* The buffer's going from the transaction, we must drop * The buffer's going from the transaction, we must drop
* all references -bzzz * all references -bzzz
...@@ -1278,7 +1282,12 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh) ...@@ -1278,7 +1282,12 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh)
JBUFFER_TRACE(jh, "belongs to current transaction: unfile"); JBUFFER_TRACE(jh, "belongs to current transaction: unfile");
drop_reserve = 1; /*
* we only want to drop a reference if this transaction
* modified the buffer
*/
if (was_modified)
drop_reserve = 1;
/* /*
* We are no longer going to journal this buffer. * We are no longer going to journal this buffer.
...@@ -1318,7 +1327,13 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh) ...@@ -1318,7 +1327,13 @@ int jbd2_journal_forget (handle_t *handle, struct buffer_head *bh)
if (jh->b_next_transaction) { if (jh->b_next_transaction) {
J_ASSERT(jh->b_next_transaction == transaction); J_ASSERT(jh->b_next_transaction == transaction);
jh->b_next_transaction = NULL; jh->b_next_transaction = NULL;
drop_reserve = 1;
/*
* only drop a reference if this transaction modified
* the buffer
*/
if (was_modified)
drop_reserve = 1;
} }
} }
...@@ -2090,7 +2105,7 @@ void __jbd2_journal_refile_buffer(struct journal_head *jh) ...@@ -2090,7 +2105,7 @@ void __jbd2_journal_refile_buffer(struct journal_head *jh)
jh->b_transaction = jh->b_next_transaction; jh->b_transaction = jh->b_next_transaction;
jh->b_next_transaction = NULL; jh->b_next_transaction = NULL;
__jbd2_journal_file_buffer(jh, jh->b_transaction, __jbd2_journal_file_buffer(jh, jh->b_transaction,
was_dirty ? BJ_Metadata : BJ_Reserved); jh->b_modified ? BJ_Metadata : BJ_Reserved);
J_ASSERT_JH(jh, jh->b_transaction->t_state == T_RUNNING); J_ASSERT_JH(jh, jh->b_transaction->t_state == T_RUNNING);
if (was_dirty) if (was_dirty)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment