| From 11517f4e2c0350204f02d970abff784bc139a094 Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Tue, 8 Dec 2009 21:24:33 -0500 |
| Subject: [PATCH 78/85] ext4: Avoid data / filesystem corruption when write fails to copy data |
| |
| (cherry picked from commit b9a4207d5e911b938f73079a83cc2ae10524ec7f) |
| |
| When ext4_write_begin fails after allocating some blocks or |
| generic_perform_write fails to copy data to write, we truncate blocks |
| already instantiated beyond i_size. Although these blocks were never |
| inside i_size, we have to truncate the pagecache of these blocks so |
| that corresponding buffers get unmapped. Otherwise subsequent |
| __block_prepare_write (called because we are retrying the write) will |
| find the buffers mapped, not call ->get_block, and thus the page will |
| be backed by already freed blocks leading to filesystem and data |
| corruption. |
| |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: "Theodore Ts'o" <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| --- |
| fs/ext4/inode.c | 20 +++++++++++++++----- |
| 1 file changed, 15 insertions(+), 5 deletions(-) |
| |
| --- a/fs/ext4/inode.c |
| +++ b/fs/ext4/inode.c |
| @@ -1535,6 +1535,16 @@ static int do_journal_get_write_access(h |
| return ext4_journal_get_write_access(handle, bh); |
| } |
| |
| +/* |
| + * Truncate blocks that were not used by write. We have to truncate the |
| + * pagecache as well so that corresponding buffers get properly unmapped. |
| + */ |
| +static void ext4_truncate_failed_write(struct inode *inode) |
| +{ |
| + truncate_inode_pages(inode->i_mapping, inode->i_size); |
| + ext4_truncate(inode); |
| +} |
| + |
| static int ext4_write_begin(struct file *file, struct address_space *mapping, |
| loff_t pos, unsigned len, unsigned flags, |
| struct page **pagep, void **fsdata) |
| @@ -1600,7 +1610,7 @@ retry: |
| |
| ext4_journal_stop(handle); |
| if (pos + len > inode->i_size) { |
| - ext4_truncate(inode); |
| + ext4_truncate_failed_write(inode); |
| /* |
| * If truncate failed early the inode might |
| * still be on the orphan list; we need to |
| @@ -1710,7 +1720,7 @@ static int ext4_ordered_write_end(struct |
| ret = ret2; |
| |
| if (pos + len > inode->i_size) { |
| - ext4_truncate(inode); |
| + ext4_truncate_failed_write(inode); |
| /* |
| * If truncate failed early the inode might still be |
| * on the orphan list; we need to make sure the inode |
| @@ -1752,7 +1762,7 @@ static int ext4_writeback_write_end(stru |
| ret = ret2; |
| |
| if (pos + len > inode->i_size) { |
| - ext4_truncate(inode); |
| + ext4_truncate_failed_write(inode); |
| /* |
| * If truncate failed early the inode might still be |
| * on the orphan list; we need to make sure the inode |
| @@ -1815,7 +1825,7 @@ static int ext4_journalled_write_end(str |
| if (!ret) |
| ret = ret2; |
| if (pos + len > inode->i_size) { |
| - ext4_truncate(inode); |
| + ext4_truncate_failed_write(inode); |
| /* |
| * If truncate failed early the inode might still be |
| * on the orphan list; we need to make sure the inode |
| @@ -3087,7 +3097,7 @@ retry: |
| * i_size_read because we hold i_mutex. |
| */ |
| if (pos + len > inode->i_size) |
| - ext4_truncate(inode); |
| + ext4_truncate_failed_write(inode); |
| } |
| |
| if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)) |