aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext3
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2009-08-11 13:06:10 -0400
committerJan Kara <jack@suse.cz>2009-09-16 11:44:11 -0400
commit00171d3c7e3b738ba582c7a9b37408e796f49046 (patch)
tree4c43c59666d78ccb1522a99dd7966252d5878ccf /fs/ext3
parent3adae9da0b35d2ca908039f42a1e90395c335181 (diff)
ext3: Fix possible deadlock between ext3_truncate() and ext3_get_blocks()
During truncate we are sometimes forced to start a new transaction as the amount of blocks to be journaled is both quite large and hard to predict. So far we restarted a transaction while holding truncate_mutex and that violates lock ordering because truncate_mutex ranks below transaction start (and it can lead to a real deadlock with ext3_get_blocks() allocating new blocks from ext3_writepage()). Luckily, the problem is easy to fix: We just drop the truncate_mutex before restarting the transaction and acquire it afterwards. We are safe to do this as by the time ext3_truncate() is called, all the page cache for the truncated part of the file is dropped and so writepage() cannot come and allocate new blocks in the part of the file we are truncating. The rest of writers is stopped by us holding i_mutex. Signed-off-by: Jan Kara <jack@suse.cz>
Diffstat (limited to 'fs/ext3')
-rw-r--r--fs/ext3/inode.c19
1 files changed, 15 insertions, 4 deletions
diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c
index b49908a167ae..eb0b4e0d517b 100644
--- a/fs/ext3/inode.c
+++ b/fs/ext3/inode.c
@@ -172,10 +172,21 @@ static int try_to_extend_transaction(handle_t *handle, struct inode *inode)
172 * so before we call here everything must be consistently dirtied against 172 * so before we call here everything must be consistently dirtied against
173 * this transaction. 173 * this transaction.
174 */ 174 */
175static int ext3_journal_test_restart(handle_t *handle, struct inode *inode) 175static int truncate_restart_transaction(handle_t *handle, struct inode *inode)
176{ 176{
177 int ret;
178
177 jbd_debug(2, "restarting handle %p\n", handle); 179 jbd_debug(2, "restarting handle %p\n", handle);
178 return ext3_journal_restart(handle, blocks_for_truncate(inode)); 180 /*
181 * Drop truncate_mutex to avoid deadlock with ext3_get_blocks_handle
182 * At this moment, get_block can be called only for blocks inside
183 * i_size since page cache has been already dropped and writes are
184 * blocked by i_mutex. So we can safely drop the truncate_mutex.
185 */
186 mutex_unlock(&EXT3_I(inode)->truncate_mutex);
187 ret = ext3_journal_restart(handle, blocks_for_truncate(inode));
188 mutex_lock(&EXT3_I(inode)->truncate_mutex);
189 return ret;
179} 190}
180 191
181/* 192/*
@@ -2072,7 +2083,7 @@ static void ext3_clear_blocks(handle_t *handle, struct inode *inode,
2072 ext3_journal_dirty_metadata(handle, bh); 2083 ext3_journal_dirty_metadata(handle, bh);
2073 } 2084 }
2074 ext3_mark_inode_dirty(handle, inode); 2085 ext3_mark_inode_dirty(handle, inode);
2075 ext3_journal_test_restart(handle, inode); 2086 truncate_restart_transaction(handle, inode);
2076 if (bh) { 2087 if (bh) {
2077 BUFFER_TRACE(bh, "retaking write access"); 2088 BUFFER_TRACE(bh, "retaking write access");
2078 ext3_journal_get_write_access(handle, bh); 2089 ext3_journal_get_write_access(handle, bh);
@@ -2282,7 +2293,7 @@ static void ext3_free_branches(handle_t *handle, struct inode *inode,
2282 return; 2293 return;
2283 if (try_to_extend_transaction(handle, inode)) { 2294 if (try_to_extend_transaction(handle, inode)) {
2284 ext3_mark_inode_dirty(handle, inode); 2295 ext3_mark_inode_dirty(handle, inode);
2285 ext3_journal_test_restart(handle, inode); 2296 truncate_restart_transaction(handle, inode);
2286 } 2297 }
2287 2298
2288 ext3_free_blocks(handle, inode, nr, 1); 2299 ext3_free_blocks(handle, inode, nr, 1);