mirror of
https://github.com/adulau/aha.git
synced 2024-12-29 04:06:22 +00:00
[PATCH] retries in ext4_prepare_write() violate ordering requirements
In journal=ordered or journal=data mode retry in ext4_prepare_write() breaks the requirements of journaling of data with respect to metadata. The fix is to call commit_write to commit allocated zero blocks before retry. Signed-off-by: Kirill Korotaev <dev@openvz.org> Cc: Ingo Molnar <mingo@elte.hu> Cc: Ken Chen <kenneth.w.chen@intel.com> Cc: <linux-ext4@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
e92a4d595b
commit
b46be05004
1 changed files with 75 additions and 10 deletions
|
@ -1147,37 +1147,102 @@ static int do_journal_get_write_access(handle_t *handle,
|
||||||
return ext4_journal_get_write_access(handle, bh);
|
return ext4_journal_get_write_access(handle, bh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The idea of this helper function is following:
|
||||||
|
* if prepare_write has allocated some blocks, but not all of them, the
|
||||||
|
* transaction must include the content of the newly allocated blocks.
|
||||||
|
* This content is expected to be set to zeroes by block_prepare_write().
|
||||||
|
* 2006/10/14 SAW
|
||||||
|
*/
|
||||||
|
static int ext4_prepare_failure(struct file *file, struct page *page,
|
||||||
|
unsigned from, unsigned to)
|
||||||
|
{
|
||||||
|
struct address_space *mapping;
|
||||||
|
struct buffer_head *bh, *head, *next;
|
||||||
|
unsigned block_start, block_end;
|
||||||
|
unsigned blocksize;
|
||||||
|
int ret;
|
||||||
|
handle_t *handle = ext4_journal_current_handle();
|
||||||
|
|
||||||
|
mapping = page->mapping;
|
||||||
|
if (ext4_should_writeback_data(mapping->host)) {
|
||||||
|
/* optimization: no constraints about data */
|
||||||
|
skip:
|
||||||
|
return ext4_journal_stop(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
head = page_buffers(page);
|
||||||
|
blocksize = head->b_size;
|
||||||
|
for ( bh = head, block_start = 0;
|
||||||
|
bh != head || !block_start;
|
||||||
|
block_start = block_end, bh = next)
|
||||||
|
{
|
||||||
|
next = bh->b_this_page;
|
||||||
|
block_end = block_start + blocksize;
|
||||||
|
if (block_end <= from)
|
||||||
|
continue;
|
||||||
|
if (block_start >= to) {
|
||||||
|
block_start = to;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!buffer_mapped(bh))
|
||||||
|
/* prepare_write failed on this bh */
|
||||||
|
break;
|
||||||
|
if (ext4_should_journal_data(mapping->host)) {
|
||||||
|
ret = do_journal_get_write_access(handle, bh);
|
||||||
|
if (ret) {
|
||||||
|
ext4_journal_stop(handle);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* block_start here becomes the first block where the current iteration
|
||||||
|
* of prepare_write failed.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (block_start <= from)
|
||||||
|
goto skip;
|
||||||
|
|
||||||
|
/* commit allocated and zeroed buffers */
|
||||||
|
return mapping->a_ops->commit_write(file, page, from, block_start);
|
||||||
|
}
|
||||||
|
|
||||||
static int ext4_prepare_write(struct file *file, struct page *page,
|
static int ext4_prepare_write(struct file *file, struct page *page,
|
||||||
unsigned from, unsigned to)
|
unsigned from, unsigned to)
|
||||||
{
|
{
|
||||||
struct inode *inode = page->mapping->host;
|
struct inode *inode = page->mapping->host;
|
||||||
int ret, needed_blocks = ext4_writepage_trans_blocks(inode);
|
int ret, ret2;
|
||||||
|
int needed_blocks = ext4_writepage_trans_blocks(inode);
|
||||||
handle_t *handle;
|
handle_t *handle;
|
||||||
int retries = 0;
|
int retries = 0;
|
||||||
|
|
||||||
retry:
|
retry:
|
||||||
handle = ext4_journal_start(inode, needed_blocks);
|
handle = ext4_journal_start(inode, needed_blocks);
|
||||||
if (IS_ERR(handle)) {
|
if (IS_ERR(handle))
|
||||||
ret = PTR_ERR(handle);
|
return PTR_ERR(handle);
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (test_opt(inode->i_sb, NOBH) && ext4_should_writeback_data(inode))
|
if (test_opt(inode->i_sb, NOBH) && ext4_should_writeback_data(inode))
|
||||||
ret = nobh_prepare_write(page, from, to, ext4_get_block);
|
ret = nobh_prepare_write(page, from, to, ext4_get_block);
|
||||||
else
|
else
|
||||||
ret = block_prepare_write(page, from, to, ext4_get_block);
|
ret = block_prepare_write(page, from, to, ext4_get_block);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto prepare_write_failed;
|
goto failure;
|
||||||
|
|
||||||
if (ext4_should_journal_data(inode)) {
|
if (ext4_should_journal_data(inode)) {
|
||||||
ret = walk_page_buffers(handle, page_buffers(page),
|
ret = walk_page_buffers(handle, page_buffers(page),
|
||||||
from, to, NULL, do_journal_get_write_access);
|
from, to, NULL, do_journal_get_write_access);
|
||||||
|
if (ret)
|
||||||
|
/* fatal error, just put the handle and return */
|
||||||
|
journal_stop(handle);
|
||||||
}
|
}
|
||||||
prepare_write_failed:
|
return ret;
|
||||||
if (ret)
|
|
||||||
ext4_journal_stop(handle);
|
failure:
|
||||||
|
ret2 = ext4_prepare_failure(file, page, from, to);
|
||||||
|
if (ret2 < 0)
|
||||||
|
return ret2;
|
||||||
if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
|
if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
|
||||||
goto retry;
|
goto retry;
|
||||||
out:
|
/* retry number exceeded, or other error like -EDQUOT */
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue