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: Jan Kara <jack@suse.cz> Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
This commit is contained in:
Родитель
b7ca1e8ec5
Коммит
2d859db3e4
|
@ -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,
|
||||||
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,
|
||||||
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;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче