mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 11:03:07 -04:00
ext4: Fix data integrity writeout issues in nojournal mode
Several racing fsyncs on ext4 in nojournal mode could result in some fsync returning earlier than all metadata buffers were properly persisted. Also ext4_fsync() in nojournal mode was somewhat inefficient because it was always writing out the inode regardless whether it was dirty or not. Fix these issues by using new .sync_inode_metadata method which makes sure all inode related metadata is written to disk during any WB_SYNC_ALL writeback in nojournal mode. This also somewhat simplifies the nojournal mode fsync handling. Signed-off-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260727104923.3828017-37-jack@suse.cz Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
committed by
Christian Brauner
parent
84af7c3b34
commit
c26339e1df
@@ -3166,6 +3166,7 @@ extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
|
||||
__ext4_iget((sb), (ino), (flags), __func__, __LINE__)
|
||||
|
||||
extern int ext4_write_inode(struct inode *, struct writeback_control *);
|
||||
extern int ext4_sync_inode_metadata(struct inode *, struct writeback_control *);
|
||||
extern int ext4_setattr(struct mnt_idmap *, struct dentry *,
|
||||
struct iattr *);
|
||||
extern u32 ext4_dio_alignment(struct inode *inode);
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
static int ext4_sync_parent(struct inode *inode)
|
||||
{
|
||||
struct dentry *dentry, *next;
|
||||
struct mapping_metadata_bhs *mmb;
|
||||
int ret = 0;
|
||||
|
||||
if (!ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY))
|
||||
@@ -69,12 +68,6 @@ static int ext4_sync_parent(struct inode *inode)
|
||||
* through ext4_evict_inode()) and so we are safe to flush
|
||||
* metadata blocks and the inode.
|
||||
*/
|
||||
mmb = ext4_i_metadata_bhs(inode);
|
||||
if (mmb) {
|
||||
ret = mmb_sync(mmb);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
ret = sync_inode_metadata(inode, 1);
|
||||
if (ret)
|
||||
break;
|
||||
@@ -87,22 +80,11 @@ static int ext4_fsync_nojournal(struct file *file, loff_t start, loff_t end,
|
||||
int datasync, bool *needs_barrier)
|
||||
{
|
||||
struct inode *inode = file->f_inode;
|
||||
struct writeback_control wbc = {
|
||||
.sync_mode = WB_SYNC_ALL,
|
||||
.nr_to_write = 0,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = mmb_fsync_noflush(file, ext4_i_metadata_bhs(inode),
|
||||
start, end, datasync);
|
||||
ret = sync_inode_metadata(inode, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Force writeout of inode table buffer to disk */
|
||||
ret = ext4_write_inode(inode, &wbc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ext4_sync_parent(inode);
|
||||
|
||||
if (test_opt(inode->i_sb, BARRIER))
|
||||
@@ -160,6 +142,10 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
if (sb_rdonly(inode->i_sb))
|
||||
goto out;
|
||||
|
||||
ret = file_write_and_wait_range(file, start, end);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (!EXT4_SB(inode->i_sb)->s_journal) {
|
||||
ret = ext4_fsync_nojournal(file, start, end, datasync,
|
||||
&needs_barrier);
|
||||
@@ -168,10 +154,6 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = file_write_and_wait_range(file, start, end);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* The caller's filemap_fdatawrite()/wait will sync the data.
|
||||
* Metadata is in the journal, we wait for proper transaction to
|
||||
|
||||
@@ -5799,6 +5799,10 @@ static int ext4_do_update_inode(handle_t *handle,
|
||||
* ext4_mark_inode_dirty(). This is a correctness thing for WB_SYNC_ALL
|
||||
* writeback.
|
||||
*
|
||||
* For nojournal mode all the work is done in ext4_sync_inode_metadata()
|
||||
* because inode content is already copied into raw inode buffer and inode
|
||||
* is marked with I_METADATA_WRITEBACK.
|
||||
*
|
||||
* Note that we are absolutely dependent upon all inode dirtiers doing the
|
||||
* right thing: they *must* call mark_inode_dirty() after dirtying info in
|
||||
* which we are interested.
|
||||
@@ -5824,42 +5828,54 @@ int ext4_write_inode(struct inode *inode, struct writeback_control *wbc)
|
||||
if (unlikely(err))
|
||||
return err;
|
||||
|
||||
if (EXT4_SB(inode->i_sb)->s_journal) {
|
||||
if (ext4_journal_current_handle()) {
|
||||
ext4_debug("called recursively, non-PF_MEMALLOC!\n");
|
||||
dump_stack();
|
||||
return -EIO;
|
||||
}
|
||||
if (!EXT4_SB(inode->i_sb)->s_journal)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* No need to force transaction in WB_SYNC_NONE mode. Also
|
||||
* ext4_sync_fs() will force the commit after everything is
|
||||
* written.
|
||||
*/
|
||||
if (wbc->sync_mode != WB_SYNC_ALL || wbc->for_sync)
|
||||
return 0;
|
||||
|
||||
err = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
|
||||
EXT4_I(inode)->i_sync_tid);
|
||||
} else {
|
||||
struct ext4_iloc iloc;
|
||||
|
||||
err = __ext4_get_inode_loc_noinmem(inode, &iloc);
|
||||
if (err)
|
||||
return err;
|
||||
/*
|
||||
* sync(2) will flush the whole buffer cache. No need to do
|
||||
* it here separately for each inode.
|
||||
*/
|
||||
if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync)
|
||||
sync_dirty_buffer(iloc.bh);
|
||||
if (buffer_req(iloc.bh) && !buffer_uptodate(iloc.bh)) {
|
||||
ext4_error_inode_block(inode, iloc.bh->b_blocknr, EIO,
|
||||
"IO error syncing inode");
|
||||
err = -EIO;
|
||||
}
|
||||
brelse(iloc.bh);
|
||||
if (ext4_journal_current_handle()) {
|
||||
ext4_debug("called recursively, non-PF_MEMALLOC!\n");
|
||||
dump_stack();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* No need to force transaction in WB_SYNC_NONE mode. Also
|
||||
* ext4_sync_fs() will force the commit after everything is
|
||||
* written.
|
||||
*/
|
||||
if (wbc->sync_mode != WB_SYNC_ALL || wbc->for_sync)
|
||||
return 0;
|
||||
|
||||
return ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal,
|
||||
EXT4_I(inode)->i_sync_tid);
|
||||
}
|
||||
|
||||
int ext4_sync_inode_metadata(struct inode *inode, struct writeback_control *wbc)
|
||||
{
|
||||
struct ext4_iloc iloc;
|
||||
struct mapping_metadata_bhs *mmb;
|
||||
int err;
|
||||
|
||||
/* We should only get here in nojournal mode */
|
||||
if (WARN_ON_ONCE(EXT4_SB(inode->i_sb)->s_journal))
|
||||
return -EFSCORRUPTED;
|
||||
|
||||
err = __ext4_get_inode_loc_noinmem(inode, &iloc);
|
||||
if (err)
|
||||
return err;
|
||||
mmb = READ_ONCE(EXT4_I(inode)->i_metadata_bhs);
|
||||
if (mmb) {
|
||||
err = mmb_sync(mmb);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
sync_dirty_buffer(iloc.bh);
|
||||
if (buffer_write_io_error(iloc.bh)) {
|
||||
ext4_error_inode_block(inode, iloc.bh->b_blocknr, EIO,
|
||||
"IO error syncing inode");
|
||||
err = -EIO;
|
||||
}
|
||||
out:
|
||||
brelse(iloc.bh);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -6407,6 +6423,20 @@ int ext4_mark_iloc_dirty(handle_t *handle,
|
||||
/* ext4_do_update_inode() does jbd2_journal_dirty_metadata */
|
||||
err = ext4_do_update_inode(handle, inode, iloc);
|
||||
put_bh(iloc->bh);
|
||||
/*
|
||||
* Mark that there's metadata writeout pending for the inode so that it
|
||||
* gets properly flushed on fsync(2) and similar.
|
||||
*/
|
||||
if (!EXT4_SB(inode->i_sb)->s_journal) {
|
||||
/*
|
||||
* Inode didn't need to go through dirtying, make sure it is
|
||||
* attached to wb so that writeback can handle it.
|
||||
*/
|
||||
spin_lock(&inode->i_lock);
|
||||
inode_attach_wb(inode, NULL);
|
||||
spin_unlock(&inode->i_lock);
|
||||
set_inode_metadata_writeback(inode);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -1608,9 +1608,13 @@ static int ext4_nfs_commit_metadata(struct inode *inode)
|
||||
struct writeback_control wbc = {
|
||||
.sync_mode = WB_SYNC_ALL
|
||||
};
|
||||
int ret;
|
||||
|
||||
trace_ext4_nfs_commit_metadata(inode);
|
||||
return ext4_write_inode(inode, &wbc);
|
||||
ret = ext4_write_inode(inode, &wbc);
|
||||
if (!ret && inode_state_read_once(inode) & I_METADATA_WRITEBACK)
|
||||
ret = ext4_sync_inode_metadata(inode, &wbc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_QUOTA
|
||||
@@ -1667,6 +1671,7 @@ static const struct super_operations ext4_sops = {
|
||||
.free_inode = ext4_free_in_core_inode,
|
||||
.destroy_inode = ext4_destroy_inode,
|
||||
.write_inode = ext4_write_inode,
|
||||
.sync_inode_metadata = ext4_sync_inode_metadata,
|
||||
.dirty_inode = ext4_dirty_inode,
|
||||
.drop_inode = ext4_drop_inode,
|
||||
.evict_inode = ext4_evict_inode,
|
||||
|
||||
Reference in New Issue
Block a user