[PATCH -next v5 26/32] ext4: submit and wait for disksize-grow I/O in fallocate paths
Zhang Yi <[email protected]>
| Newsgroups | org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-ext4,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Zhang Yi <[email protected]> Collapse range and insert range update i_disksize directly under i_data_sem. If the operation runs while the zeroed EOF block is still awaiting writeback, i_disksize could advance past the zeroed boundary before the zeroed data is persisted, exposing stale data on crash. Deferring i_disksize updates like fallocate and zero_range is not an option here because the shift would move written extents beyond the current i_disksize. So flush and wait for the pending zeroed EOF block before these operations advance i_disksize. Since these operations already perform writeback, the extra flush does not add significant overhead. In addition, for ext4_update_disksize_before_punch(), if the punch discards the pending block, the zeroed data will never be written back before advancing i_disksize, so it is also necessary to sync the pending EOF range there. Finally, for the SYNC variants of zero_range and fallocate, this also guarantees the i_disksize update is persisted on the synchronous return. Signed-off-by: Zhang Yi <[email protected]> --- fs/ext4/ext4.h | 2 ++ fs/ext4/extents.c | 53 ++++++++++++++++++++++++++++++++++++++++------- fs/ext4/inode.c | 34 ++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index b26ce3183bac..504dce9fdc6b 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3226,6 +3226,8 @@ void ext4_iomap_clear_disksize_pending(struct inode *inode); void ext4_iomap_wait_disksize_pending(struct inode *inode); unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode, loff_t *start); +extern int ext4_iomap_sync_zeroed_eof(struct inode *inode, + loff_t offset, loff_t end); extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end); #define EXT4_PARTIAL_ZERO_START 0x1 diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index fc5aa2dbefcf..dda6d50e96e3 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4876,6 +4876,16 @@ static long ext4_zero_range(struct file *file, loff_t offset, return ret; } + /* + * In SYNC mode, sync the pending zeroed EOF block to ensure the + * i_disksize update is persisted. + */ + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && new_size) { + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + } + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); if (IS_ERR(handle)) { ret = PTR_ERR(handle); @@ -4928,10 +4938,20 @@ static long ext4_do_fallocate(struct file *file, loff_t offset, if (ret) goto out; - if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && - EXT4_SB(inode->i_sb)->s_journal) { - ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal, - EXT4_I(inode)->i_sync_tid); + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) { + /* + * Sync the pending zeroed EOF block to ensure the + * i_disksize update is persisted. + */ + if (new_size) { + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + goto out; + } + if (EXT4_SB(inode->i_sb)->s_journal) { + ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal, + EXT4_I(inode)->i_sync_tid); + } } out: trace_ext4_fallocate_exit(inode, offset, @@ -5668,6 +5688,14 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len) if (end >= inode->i_size) return -EINVAL; + /* + * Persist the pending zeroed EOF block to ensure i_disksize + * can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + /* * Write tail of the last page before removed range and data that * will be shifted since they will get removed from the page cache @@ -5715,9 +5743,11 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len) goto out_handle; } + WARN_ON_ONCE(ext4_test_inode_state(inode, + EXT4_STATE_DISKSIZE_GROW_PENDING)); new_size = inode->i_size - len; i_size_write(inode, new_size); - EXT4_I(inode)->i_disksize = new_size; + __ext4_set_i_disksize(inode, new_size); up_write(&EXT4_I(inode)->i_data_sem); ret = ext4_mark_inode_dirty(handle, inode); @@ -5770,6 +5800,14 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len) if (len > inode->i_sb->s_maxbytes - inode->i_size) return -EFBIG; + /* + * Persist the pending zeroed EOF block to ensure i_disksize + * can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + /* * Write out all dirty pages. Need to round down to align start offset * to page size boundary for page size > block size. @@ -5789,8 +5827,9 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len) ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_FALLOC_RANGE, handle); /* Expand file to avoid data loss if there is error while shifting */ - inode->i_size += len; - EXT4_I(inode)->i_disksize += len; + WARN_ON_ONCE(ext4_test_inode_state(inode, + EXT4_STATE_DISKSIZE_GROW_PENDING)); + ext4_update_inode_size(inode, inode->i_size + len); ret = ext4_mark_inode_dirty(handle, inode); if (ret) goto out_handle; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 0fdc31b21be5..056937e27859 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -4823,6 +4823,32 @@ static int ext4_block_zero_range(struct inode *inode, zero_written); } +/* + * Submit and wait for the pending zeroed EOF block range to complete + * if the given range [@offset, @end) fully covers it. Must be called + * outside the context of an active journal handle and hold the i_rwsem. + */ +int ext4_iomap_sync_zeroed_eof(struct inode *inode, loff_t offset, loff_t end) +{ + loff_t pstart, plen; + int ret; + + if (!ext4_inode_buffered_iomap(inode)) + return 0; + + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (!plen || offset > pstart || end < pstart + plen) + return 0; + + ret = filemap_fdatawrite_range(inode->i_mapping, pstart, + pstart + plen - 1); + if (ret) + return ret; + + ext4_iomap_wait_disksize_pending(inode); + return 0; +} + /* * Zero out a mapping from file offset 'from' up to the end of the block * which corresponds to 'from' or to the given 'end' inside this block. @@ -4988,6 +5014,14 @@ int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset, if (offset > size) return 0; + /* + * We are going to punch the pending zeroed EOF block, persist + * it to ensure i_disksize can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, offset, offset + len); + if (ret) + return ret; + if (offset + len < size) size = offset + len; if (EXT4_I(inode)->i_disksize >= size) -- 2.52.0