Commit 81eb6906 authored by Andrew Morton's avatar Andrew Morton Committed by Jens Axboe

[PATCH] fix ext3 BUG due to race with truncate

When ext3_writepage races with truncate, block_write_full_page() will see
that the page is outside i_size and will bale out with -EIO.  But
ext3_writepage() will ignore this and will proceed to add the buffers to the
transaction.

Later, kjournald tries to write them out and goes BUG() because those buffers
are not mapped to disk.

The fix is to not attach the buffers to the transaction in ext3_writepage()
if block_write_full_page() failed.

So far so good, but that page now has dirty, unmapped buffers (the buffers
were attached in a dirty state by ext3_writepage()).  So teach
block_write_full_page() to clean the buffers against the page if it is wholly
outside i_size.

(A simpler fix to all of this mught be to just bale out of ext3_writepage()
if the page is outside i_size.  But that is racy against
block_write_full_page()'s subsequent execution of the same comparison).
parent 7ac1de5d
...@@ -2484,6 +2484,12 @@ int block_write_full_page(struct page *page, get_block_t *get_block, ...@@ -2484,6 +2484,12 @@ int block_write_full_page(struct page *page, get_block_t *get_block,
/* Is the page fully outside i_size? (truncate in progress) */ /* Is the page fully outside i_size? (truncate in progress) */
offset = inode->i_size & (PAGE_CACHE_SIZE-1); offset = inode->i_size & (PAGE_CACHE_SIZE-1);
if (page->index >= end_index+1 || !offset) { if (page->index >= end_index+1 || !offset) {
/*
* The page may have dirty, unmapped buffers. For example,
* they may have been added in ext3_writepage(). Make them
* freeable here, so the page does not leak.
*/
block_invalidatepage(page, 0);
unlock_page(page); unlock_page(page);
return -EIO; return -EIO;
} }
......
...@@ -1357,14 +1357,21 @@ static int ext3_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1357,14 +1357,21 @@ static int ext3_writepage(struct page *page, struct writeback_control *wbc)
handle = ext3_journal_current_handle(); handle = ext3_journal_current_handle();
lock_kernel(); lock_kernel();
/* And attach them to the current transaction */ /*
* And attach them to the current transaction. But only if
* block_write_full_page() succeeded. Otherwise they are unmapped,
* and generally junk.
*/
if (order_data) { if (order_data) {
err = walk_page_buffers(handle, page_bufs, if (ret == 0) {
0, PAGE_CACHE_SIZE, NULL, ext3_journal_dirty_data); err = walk_page_buffers(handle, page_bufs,
0, PAGE_CACHE_SIZE, NULL,
ext3_journal_dirty_data);
if (!ret)
ret = err;
}
walk_page_buffers(handle, page_bufs, 0, walk_page_buffers(handle, page_bufs, 0,
PAGE_CACHE_SIZE, NULL, bput_one); PAGE_CACHE_SIZE, NULL, bput_one);
if (!ret)
ret = err;
} }
err = ext3_journal_stop(handle, inode); err = ext3_journal_stop(handle, inode);
......
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