Skip to content

Commit

Permalink
ext3: Improve truncate error handling
Browse files Browse the repository at this point in the history
New truncate calling convention allows us to handle errors from
ext3_block_truncate_page(). So reorganize the code so that
ext3_block_truncate_page() is called before we change inode size.

This also removes unnecessary block zeroing from error recovery after failed
buffered writes (zeroing isn't needed because we could have never written
non-zero data to disk). We have to be careful and keep zeroing in direct IO
write error recovery because there we might have already overwritten end of the
last file block.

Signed-off-by: Jan Kara <[email protected]>
  • Loading branch information
jankara committed Jun 25, 2011
1 parent 9008593 commit ee3e77f
Showing 1 changed file with 63 additions and 38 deletions.
101 changes: 63 additions & 38 deletions fs/ext3/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "acl.h"

static int ext3_writepage_trans_blocks(struct inode *inode);
static int ext3_block_truncate_page(struct inode *inode, loff_t from);

/*
* Test whether an inode is a fast symlink.
Expand Down Expand Up @@ -1207,6 +1208,16 @@ static void ext3_truncate_failed_write(struct inode *inode)
ext3_truncate(inode);
}

/*
* Truncate blocks that were not used by direct IO write. We have to zero out
* the last file block as well because direct IO might have written to it.
*/
static void ext3_truncate_failed_direct_write(struct inode *inode)
{
ext3_block_truncate_page(inode, inode->i_size);
ext3_truncate(inode);
}

static int ext3_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
Expand Down Expand Up @@ -1847,7 +1858,7 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
loff_t end = offset + iov_length(iov, nr_segs);

if (end > isize)
ext3_truncate_failed_write(inode);
ext3_truncate_failed_direct_write(inode);
}
if (ret == -ENOSPC && ext3_should_retry_alloc(inode->i_sb, &retries))
goto retry;
Expand All @@ -1861,7 +1872,7 @@ static ssize_t ext3_direct_IO(int rw, struct kiocb *iocb,
/* This is really bad luck. We've written the data
* but cannot extend i_size. Truncate allocated blocks
* and pretend the write failed... */
ext3_truncate_failed_write(inode);
ext3_truncate_failed_direct_write(inode);
ret = PTR_ERR(handle);
goto out;
}
Expand Down Expand Up @@ -1971,17 +1982,24 @@ void ext3_set_aops(struct inode *inode)
* This required during truncate. We need to physically zero the tail end
* of that block so it doesn't yield old data if the file is later grown.
*/
static int ext3_block_truncate_page(handle_t *handle, struct page *page,
struct address_space *mapping, loff_t from)
static int ext3_block_truncate_page(struct inode *inode, loff_t from)
{
ext3_fsblk_t index = from >> PAGE_CACHE_SHIFT;
unsigned offset = from & (PAGE_CACHE_SIZE-1);
unsigned offset = from & (PAGE_CACHE_SIZE - 1);
unsigned blocksize, iblock, length, pos;
struct inode *inode = mapping->host;
struct page *page;
handle_t *handle = NULL;
struct buffer_head *bh;
int err = 0;

/* Truncated on block boundary - nothing to do */
blocksize = inode->i_sb->s_blocksize;
if ((from & (blocksize - 1)) == 0)
return 0;

page = grab_cache_page(inode->i_mapping, index);
if (!page)
return -ENOMEM;
length = blocksize - (offset & (blocksize - 1));
iblock = index << (PAGE_CACHE_SHIFT - inode->i_sb->s_blocksize_bits);

Expand Down Expand Up @@ -2026,11 +2044,23 @@ static int ext3_block_truncate_page(handle_t *handle, struct page *page,
goto unlock;
}

/* data=writeback mode doesn't need transaction to zero-out data */
if (!ext3_should_writeback_data(inode)) {
/* We journal at most one block */
handle = ext3_journal_start(inode, 1);
if (IS_ERR(handle)) {
clear_highpage(page);
flush_dcache_page(page);
err = PTR_ERR(handle);
goto unlock;
}
}

if (ext3_should_journal_data(inode)) {
BUFFER_TRACE(bh, "get write access");
err = ext3_journal_get_write_access(handle, bh);
if (err)
goto unlock;
goto stop;
}

zero_user(page, offset, length);
Expand All @@ -2044,6 +2074,9 @@ static int ext3_block_truncate_page(handle_t *handle, struct page *page,
err = ext3_journal_dirty_data(handle, bh);
mark_buffer_dirty(bh);
}
stop:
if (handle)
ext3_journal_stop(handle);

unlock:
unlock_page(page);
Expand Down Expand Up @@ -2455,15 +2488,13 @@ void ext3_truncate(struct inode *inode)
struct ext3_inode_info *ei = EXT3_I(inode);
__le32 *i_data = ei->i_data;
int addr_per_block = EXT3_ADDR_PER_BLOCK(inode->i_sb);
struct address_space *mapping = inode->i_mapping;
int offsets[4];
Indirect chain[4];
Indirect *partial;
__le32 nr = 0;
int n;
long last_block;
unsigned blocksize = inode->i_sb->s_blocksize;
struct page *page;

trace_ext3_truncate_enter(inode);

Expand All @@ -2473,37 +2504,12 @@ void ext3_truncate(struct inode *inode)
if (inode->i_size == 0 && ext3_should_writeback_data(inode))
ext3_set_inode_state(inode, EXT3_STATE_FLUSH_ON_CLOSE);

/*
* We have to lock the EOF page here, because lock_page() nests
* outside journal_start().
*/
if ((inode->i_size & (blocksize - 1)) == 0) {
/* Block boundary? Nothing to do */
page = NULL;
} else {
page = grab_cache_page(mapping,
inode->i_size >> PAGE_CACHE_SHIFT);
if (!page)
goto out_notrans;
}

handle = start_transaction(inode);
if (IS_ERR(handle)) {
if (page) {
clear_highpage(page);
flush_dcache_page(page);
unlock_page(page);
page_cache_release(page);
}
if (IS_ERR(handle))
goto out_notrans;
}

last_block = (inode->i_size + blocksize-1)
>> EXT3_BLOCK_SIZE_BITS(inode->i_sb);

if (page)
ext3_block_truncate_page(handle, page, mapping, inode->i_size);

n = ext3_block_to_path(inode, last_block, offsets, NULL);
if (n == 0)
goto out_stop; /* error */
Expand Down Expand Up @@ -3251,11 +3257,30 @@ int ext3_setattr(struct dentry *dentry, struct iattr *attr)
}

error = ext3_orphan_add(handle, inode);
if (error) {
ext3_journal_stop(handle);
goto err_out;
}
EXT3_I(inode)->i_disksize = attr->ia_size;
rc = ext3_mark_inode_dirty(handle, inode);
if (!error)
error = rc;
error = ext3_mark_inode_dirty(handle, inode);
ext3_journal_stop(handle);
if (error) {
/* Some hard fs error must have happened. Bail out. */
ext3_orphan_del(NULL, inode);
goto err_out;
}
rc = ext3_block_truncate_page(inode, attr->ia_size);
if (rc) {
/* Cleanup orphan list and exit */
handle = ext3_journal_start(inode, 3);
if (IS_ERR(handle)) {
ext3_orphan_del(NULL, inode);
goto err_out;
}
ext3_orphan_del(handle, inode);
ext3_journal_stop(handle);
goto err_out;
}
}

if ((attr->ia_valid & ATTR_SIZE) &&
Expand Down

0 comments on commit ee3e77f

Please sign in to comment.