Skip to content

Commit

Permalink
[PATCH] retries in ext4_prepare_write() violate ordering requirements
Browse files Browse the repository at this point in the history
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 <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Ken Chen <[email protected]>
Cc: <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Andrey Savochkin authored and Linus Torvalds committed Dec 7, 2006
1 parent e92a4d5 commit b46be05
Showing 1 changed file with 75 additions and 10 deletions.
85 changes: 75 additions & 10 deletions fs/ext4/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1147,37 +1147,102 @@ static int do_journal_get_write_access(handle_t *handle,
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,
unsigned from, unsigned to)
{
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;
int retries = 0;

retry:
handle = ext4_journal_start(inode, needed_blocks);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto out;
}
if (IS_ERR(handle))
return PTR_ERR(handle);
if (test_opt(inode->i_sb, NOBH) && ext4_should_writeback_data(inode))
ret = nobh_prepare_write(page, from, to, ext4_get_block);
else
ret = block_prepare_write(page, from, to, ext4_get_block);
if (ret)
goto prepare_write_failed;
goto failure;

if (ext4_should_journal_data(inode)) {
ret = walk_page_buffers(handle, page_buffers(page),
from, to, NULL, do_journal_get_write_access);
if (ret)
/* fatal error, just put the handle and return */
journal_stop(handle);
}
prepare_write_failed:
if (ret)
ext4_journal_stop(handle);
return ret;

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))
goto retry;
out:
/* retry number exceeded, or other error like -EDQUOT */
return ret;
}

Expand Down

0 comments on commit b46be05

Please sign in to comment.