Commit 2d859db3 authored by Jan Kara's avatar Jan Kara Committed by Theodore Ts'o

ext4: fix data corruption in inodes with journalled data

When journalling data for an inode (either because it is a symlink or
because the filesystem is mounted in data=journal mode), ext4_evict_inode()
can discard unwritten data by calling truncate_inode_pages(). This is
because we don't mark the buffer / page dirty when journalling data but only
add the buffer to the running transaction and thus mm does not know there
are still unwritten data.

Fix the problem by carefully tracking transaction containing inode's data,
committing this transaction, and writing uncheckpointed buffers when inode
should be reaped.
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent b7ca1e8e
...@@ -121,6 +121,33 @@ void ext4_evict_inode(struct inode *inode) ...@@ -121,6 +121,33 @@ void ext4_evict_inode(struct inode *inode)
trace_ext4_evict_inode(inode); trace_ext4_evict_inode(inode);
if (inode->i_nlink) { if (inode->i_nlink) {
/*
* When journalling data dirty buffers are tracked only in the
* journal. So although mm thinks everything is clean and
* ready for reaping the inode might still have some pages to
* write in the running transaction or waiting to be
* checkpointed. Thus calling jbd2_journal_invalidatepage()
* (via truncate_inode_pages()) to discard these buffers can
* cause data loss. Also even if we did not discard these
* buffers, we would have no way to find them after the inode
* is reaped and thus user could see stale data if he tries to
* read them before the transaction is checkpointed. So be
* careful and force everything to disk here... We use
* ei->i_datasync_tid to store the newest transaction
* containing inode's data.
*
* Note that directories do not have this problem because they
* don't use page cache.
*/
if (ext4_should_journal_data(inode) &&
(S_ISLNK(inode->i_mode) || S_ISREG(inode->i_mode))) {
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
tid_t commit_tid = EXT4_I(inode)->i_datasync_tid;
jbd2_log_start_commit(journal, commit_tid);
jbd2_log_wait_commit(journal, commit_tid);
filemap_write_and_wait(&inode->i_data);
}
truncate_inode_pages(&inode->i_data, 0); truncate_inode_pages(&inode->i_data, 0);
goto no_delete; goto no_delete;
} }
...@@ -970,6 +997,7 @@ static int ext4_journalled_write_end(struct file *file, ...@@ -970,6 +997,7 @@ static int ext4_journalled_write_end(struct file *file,
if (new_i_size > inode->i_size) if (new_i_size > inode->i_size)
i_size_write(inode, pos+copied); i_size_write(inode, pos+copied);
ext4_set_inode_state(inode, EXT4_STATE_JDATA); ext4_set_inode_state(inode, EXT4_STATE_JDATA);
EXT4_I(inode)->i_datasync_tid = handle->h_transaction->t_tid;
if (new_i_size > EXT4_I(inode)->i_disksize) { if (new_i_size > EXT4_I(inode)->i_disksize) {
ext4_update_i_disksize(inode, new_i_size); ext4_update_i_disksize(inode, new_i_size);
ret2 = ext4_mark_inode_dirty(handle, inode); ret2 = ext4_mark_inode_dirty(handle, inode);
...@@ -1678,6 +1706,7 @@ static int __ext4_journalled_writepage(struct page *page, ...@@ -1678,6 +1706,7 @@ static int __ext4_journalled_writepage(struct page *page,
write_end_fn); write_end_fn);
if (ret == 0) if (ret == 0)
ret = err; ret = err;
EXT4_I(inode)->i_datasync_tid = handle->h_transaction->t_tid;
err = ext4_journal_stop(handle); err = ext4_journal_stop(handle);
if (!ret) if (!ret)
ret = err; ret = err;
......
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