mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Zhang Yi <yizhang089@gmail.com>
To: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Cc: Zhang Yi <yi.zhang@huaweicloud.com>,
	linux-ext4@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, tytso@mit.edu,
	adilger.kernel@dilger.ca, libaokun@linux.alibaba.com,
	jack@suse.cz, ritesh.list@gmail.com, djwong@kernel.org,
	hch@infradead.org, yi.zhang@huawei.com, chengzhihao1@huawei.com,
	yangerkun@huawei.com, wangkefeng.wang@huawei.com,
	yukuai@fnnas.com
Subject: Re: [PATCH v7 27/31] ext4: set DISKSIZE_GROW_PENDING after zeroing unaligned EOF block
Date: Sat, 10 Oct 2026 23:54:57 +0800	[thread overview]
Message-ID: <bf9a1b23-0473-4623-952b-b3a407e9b7a3@gmail.com> (raw)
In-Reply-To: <aspCa7EpqXoEhuN6@li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com>

On 10/10/2026 9:49 PM, Ojaswin Mujoo wrote:
> On Sat, Oct 10, 2026 at 05:50:24PM +0800, Zhang Yi wrote:
>> On 10/10/2026 5:00 PM, Ojaswin Mujoo wrote:
>>> On Fri, Oct 09, 2026 at 06:30:28PM +0800, Zhang Yi wrote:
>>>> From: Zhang Yi <yi.zhang@huawei.com>
>>>>
>>>> In the iomap buffered I/O path, data=ordered mode is not used, so the
>>>> zeroed EOF block has no implicit ordering with later i_disksize updates.
>>>> Without the pending state being set, i_disksize can be advanced past the
>>>> zeroed block before writeback completes, exposing stale data after a
>>>> crash.
>>>>
>>>> Previous patches added the consumer side of the
>>>> disksize-grow-pending mechanism: the state bit, clear and wait helpers,
>>>> and ioend tagging. Now add ext4_iomap_mark_disksize_pending() and call
>>>> it from ext4_block_zero_eof() after zeroing the tail of the block that
>>>> straddles i_disksize.
>>>>
>>>> The helper locks the folio, waits for any in-flight writeback on it to
>>>> complete, then sets EXT4_STATE_DISKSIZE_GROW_PENDING only if the folio
>>>> is still dirty. Waiting for writeback prevents folio_test_dirty() from
>>>> returning false mid-writeback, which would cause us to skip the pending
>>>> state while zeroed data is still in flight. The dirty check then avoids
>>>> setting the bit when the data has already been written back.
>>>>
>>>> Suggested-by: Jan Kara <jack@suse.cz>
>>>> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
>>>> ---
>>>>    fs/ext4/inode.c | 106 ++++++++++++++++++++++++++++++++++++++++++------
>>>>    1 file changed, 94 insertions(+), 12 deletions(-)
>>>>
>>>> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
>>>> index 9b6916609000c..3bdb5f3b119ab 100644
>>>> --- a/fs/ext4/inode.c
>>>> +++ b/fs/ext4/inode.c
>>>> @@ -4847,6 +4847,89 @@ static int ext4_block_zero_range(struct inode *inode,
>>>>    					zero_written);
>>>>    }
>>>> +/*
>>>> + * Inodes using the iomap buffered I/O path do not use data=ordered mode.
>>>> + * Therefore, we mark the inode as disksize-grow-pending after zeroing the
>>>> + * EOF block. The zeroed block will be submitted before any subsequent
>>>> + * data.
>>>> + *
>>>> + * In the I/O completion path, ext4_iomap_wb_disksize_pending_wait() will
>>>> + * wait for I/O completion before advancing i_disksize if the write
>>>> + * extends beyond the zeroed boundary.
>>>> + *
>>>> + * When zeroed I/O is in progress, operations that extend i_disksize are
>>>> + * handled as follows:
>>>> + *
>>>> + *  - Truncate up, append fallocate and zero_range:
>>>> + *    Defer the update. The file size will be updated to i_size by the
>>>> + *    end_io handler once the ongoing pending I/O completes.
>>>> + *
>>>> + *  - Insert range and collapse range operations:
>>>> + *    Wait synchronously for the relevant I/O to complete before updating
>>>> + *    i_disksize.
>>>> + */
>>>> +static int ext4_iomap_mark_disksize_pending(struct inode *inode, loff_t from)
>>>> +{
>>>> +	struct folio *folio;
>>>> +
>>>> +	folio = filemap_lock_folio(inode->i_mapping, from >> PAGE_SHIFT);
>>>> +	if (IS_ERR(folio))
>>>> +		/* Already in writeback and cleared? */
>>>> +		return PTR_ERR(folio) == -ENOENT ? 0 : PTR_ERR(folio);
>>>> +
>>>> +	/*
>>>> +	 * Ensure that in-flight writeback, possibly started after
>>>> +	 * iomap_zero_range() unlocked the folio, has completed. Without
>>>> +	 * this wait folio_test_dirty() below may miss the zeroed data
>>>> +	 * (writeback clears PG_dirty), causing us to skip the
>>>> +	 * disksize-grow-pending tracking and potentially expose stale
>>>> +	 * on-disk data.
>>>> +	 */
>>>> +	folio_wait_writeback(folio);
>>>> +	WARN_ON_ONCE(folio_test_writeback(folio));
>>>> +
>>>> +	/*
>>>> +	 * If the zeroed range does not overlap the existing on-disk tail
>>>> +	 * block, the zeroed data lies beyond the currently on-disk data
>>>> +	 * and will be written back before i_disksize is advanced past it,
>>>> +	 * so no stale data can be exposed.
>>>> +	 *
>>>> +	 * Checking i_disksize here (after folio_wait_writeback()) is
>>>> +	 * necessary and safe. If a delalloc writeback of this folio was
>>>> +	 * in-flight, it could be raced by a concurrent mmap write which
>>>> +	 * corrupts the tail block beyond i_size but the i_disksize is not
>>>> +	 * advanced. folio_wait_writeback() has waited for its completion
>>>> +	 * and the ioend has advanced i_disksize accordingly. If writeback
>>>> +	 * had not started, we don't need to mark any pending state because
>>>> +	 * any future writeback will carry the pagecache content that now
>>>> +	 * includes the zeroed data, so no stale data can appear on disk
>>>> +	 * even without the pending tracking.
>>>> +	 */
>>>
>>> Hey Zhang,
>>>
>>> So in continuation for our discussion at [1], thanks for the info and
>>> yes I think the race mentioned there cannot happen. But I was still
>>> trying to look at this path for my own understanding and there's another
>>> sequence of events I'd like to discuss with you. Mostly the same from
>>> last but what if the writeback moves after
>>> ext4_iomap_makr_zero_pending():
>>>
>>>       Initial state: i_size = i_disksize = 2k
>>>
>>>       1. pwrite(4k,6k)
>>>         - ext4_block_zero_eof(2k,4k)
>>>             set inode state DISK_SIZE_GROW_PENDING
>>>         - i_size=6k, i_disksize=2k
>>>       2. writeback(2k,4k)
>>>         - submits a GROW_IO ioend for 0,4k
>>>         - i_size=6k, i_disksize=2k (unchanged)
>>>       3. pwrite(8k,10k) - part 1
>>>         - ext4_block_zero_eof(6k,8k)
>>>         - zeroes 6k,8k
>>>         - ext4_iomap_mark_zero_pending(from=6k)
>>>           - folio is unlocked and not under writeback yet
>>>           - keeps DISK_SIZE_GROW_PENDING set
>>>         - iomap_write_iter not called yet...
>>>       4. mmap write at (6k,8k)
>>>         - I dont see anything stopping this?
>>>         - i_size is still 6k so this is eof write.
>>>       5. writeback(4k,6k)
>>>         - submits IO for 4k,8k
>>>         - i_size=6k, i_disksize=2k (unchanged)
>>>       6. ioend for 2. (2k,4k)
>>>         - clears DISKSIZE_GROW_PENDING
>>>         - GROW_IO so i_disksize updated
>>>         - i_size=6k, i_disksize=6k
>>>       7. ioend for 5. (4k,8k)
>>>         - DISKSIZE_GROW_PENDING is cleared
>>>         - i_disksize=6k,i_size=6k - no updates
>>>         - 4k,8k converted written - data beyond EOF from 4.
>>>       8. 3. continues
>>>         - enter iomap_write_iter
>>>         - pagecache_i_size_extended()
>>>           - page_mkwrite(6k folio) and mark dirty
>>>           - zero 6k,8k
>>>         - i_size=10k, i_disksize=6k
>>>       9. writeback(8k,10k)
>>>         - DISKSIZE_GROW_PENDING cleared so just submit (8k,10k)
>>>       10. ioend for 8.
>>>         - DISKSIZE_GROW_PENDING cleared so no waiting
>>>         - i_size=10k, i_disksize=10k - updated
>>>         - if we crash now, 6k,8k non-zero data exposed
>>>
>>> I looked at the code surrounding ext4_iomap_mkwrite() and
>>> ext4_buffered_write_iter() and I don't think there's any sort of
>>> serialization against this sequence right? Or am I missing something
>>> again :)
>>>
>>
>> Hi Ojaswin,
>>
>> If I understand correctly, your scenario is essentially a concurrent
>> mmap write and append write, that is, a post-EOF mmap write is performed
>> after the append write does the EOF zeroing but before i_size is
>> advanced, right?
>>
>> The scenario can be simplified as follows:
>>
>>    Initial state: i_size = i_disksize = 2k
>>
>>    1. pwrite (4k,6k)
>>      - ext4_block_zero_eof(2k,4k), zero 2k,4k and dirty folio
>>      - iomap_write_iter not called yet... so i_size is still 2k
>>    2. mmap write at (2k,4k)
>>      - i_size is still 2k (so this is still an EOF write?)
>>    3. writeback (0,4k) since it's dirty
>>      - submits I/O for (0,4k), data is written to disk
>>    4. 1 continue and then writeback
>>      - i_size=6k
>>      - submits I/O for (4,8k)
>>      - i_disksize=6k, 2k,4k non-zero data exposed
> 
> Yep, looks right.
>>
>> Actually, I think we don't need to consider this kind of concurrent
>> scenario, since it is undefined behavior in itself. Even if we
>> don't write this part of the mmap data back to disk, we can still
> 
> It will be zeroed again when i_size grows in iomap_write_iter()
> -> pagecache_isize_extended() but yeah a mapped reader might be able to
> read it before that happens.

In fact, for our current scenario, pagecache_isize_extended() is a
no-op, since blocksize equals PAGE_SIZE, so it will no longer zero out
the post-EOF data written by mmap. What pagecache_isize_extended()
zeroes is the data in the folio that doesn't belong to the block
containing the current EOF when blocksize is smaller than folio size,
whereas ext4_block_zero_eof() zeroes the post-EOF part inside the tail
block. Their responsibilities are not the same.

So in the scenario you describe, the data written by mmap will remain in
memory for a long time, and this is not a small window. Since we don't
care about this concurrent scenario, puting ext4_block_zero_eof() before
the write is safe.

Cheers,
Yi.

> 
>> read it from memory, right? What ext4_block_zero_eof() needs to do
>> is zero out the post-EOF stale data written by the mmap before the
>> append write.
> 
> Okay got it so we care more about not exposing data that was
> previously on disk rather than a race with mmap. We do have some
> xfstests that seem to care about the later as well (like generic/363)
> but I get that the race is a very small window here and needs us to
> crash at a specific time. Plus its a parallel mmap written data rather
> than stale data so hopefully it's harmless even if a bit of it is
> exposed after crash.
> 
> Thanks,
> ojaswin
> 
>>
>> Thanks,
>> Yi
>>
>>
>>> Regards,
>>> ojaswin
>>>
>>> [1] https://lore.kernel.org/linux-ext4/179153611724.860602.5266334742364847927.b4-ty@b4/T/#m72e1710d172cf7699b6f05e29a270e45951800b0
>>>> +	if (from >= round_up(READ_ONCE(EXT4_I(inode)->i_disksize),
>>>> +			     i_blocksize(inode)))
>>>> +		goto out;
>>>> +
>>>> +	/*
>>>> +	 * Mark the inode as disksize-grow-pending. The zeroed block will
>>>> +	 * be written out by the generic writepages cycle or any other
>>>> +	 * syncing operation.
>>>> +	 *
>>>> +	 * Multiple overlapping unaligned EOF writes should not happen,
>>>> +	 * because we only mark the pending state after zeroing the on-disk
>>>> +	 * EOF block, and i_disksize can only be updated after the previous
>>>> +	 * zeroed pending block has been written back or the dirty folio
>>>> +	 * has been discarded.
>>>> +	 */
>>>> +	if (likely(folio_test_dirty(folio) &&
>>>> +		   !ext4_test_inode_state(inode,
>>>> +					  EXT4_STATE_DISKSIZE_GROW_PENDING)))
>>>> +		ext4_set_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING);
>>>> +out:
>>>> +	folio_unlock(folio);
>>>> +	folio_put(folio);
>>>> +	return 0;
>>>> +}
>>>> +
>>>>    /*
>>>>     * Submit and wait for the pending zeroed EOF block range to complete
>>>>     * if the given range [@offset, @end) fully covers it.  Must be called
>>>> @@ -4923,21 +5006,20 @@ int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end)
>>>>    	 * truncating up or performing an append write, because there might be
>>>>    	 * exposing stale on-disk data which may caused by concurrent post-EOF
>>>>    	 * mmap write during folio writeback.
>>>> -	 *
>>>> -	 * TODO: In the iomap path, handle this by tracking the ordered range
>>>> -	 * and updating i_disksize to i_size after the zeroed data has been
>>>> -	 * written back.
>>>>    	 */
>>>> -	if (ext4_should_order_data(inode) &&
>>>> -	    did_zero && zero_written && !IS_DAX(inode)) {
>>>> -		handle_t *handle;
>>>> +	if (did_zero && zero_written && !IS_DAX(inode)) {
>>>> +		if (ext4_should_order_data(inode)) {
>>>> +			handle_t *handle;
>>>> -		handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
>>>> -		if (IS_ERR(handle))
>>>> -			return PTR_ERR(handle);
>>>> +			handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
>>>> +			if (IS_ERR(handle))
>>>> +				return PTR_ERR(handle);
>>>> -		err = ext4_jbd2_inode_add_write(handle, inode, from, length);
>>>> -		ext4_journal_stop(handle);
>>>> +			err = ext4_jbd2_inode_add_write(handle, inode, from,
>>>> +							length);
>>>> +			ext4_journal_stop(handle);
>>>> +		} else if (ext4_inode_buffered_iomap(inode))
>>>> +			err = ext4_iomap_mark_disksize_pending(inode, from);
>>>>    		if (err)
>>>>    			return err;
>>>>    	}
>>>> -- 
>>>> 2.52.0
>>>>
>>


  reply	other threads:[~2026-10-10 15:55 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09 10:30 [PATCH v7 00/31] ext4: use iomap for regular file's buffered I/O path Zhang Yi
2026-10-09 10:30 ` [PATCH v7 01/31] ext4: simplify size updating in ext4_setattr() Zhang Yi
2026-10-09 10:30 ` [PATCH v7 02/31] ext4: factor out ext4_truncate_[up|down]() Zhang Yi
2026-10-09 10:30 ` [PATCH v7 03/31] ext4: set EXT4_MAP_NEW flag for delayed allocated blocks Zhang Yi
2026-10-09 10:30 ` [PATCH v7 04/31] ext4: recheck extent status tree before block allocation Zhang Yi
2026-10-09 10:30 ` [PATCH v7 05/31] ext4: fix orig_mlen initialization in ext4_map_blocks() Zhang Yi
2026-10-09 10:30 ` [PATCH v7 06/31] ext4: allow ext4_map_blocks() to start its own transaction handle Zhang Yi
2026-10-09 10:30 ` [PATCH v7 07/31] ext4: avoid unnecessary transaction in ext4_map_blocks() for unwritten extents Zhang Yi
2026-10-09 10:30 ` [PATCH v7 08/31] ext4: skip block allocation for holes in the data submission path Zhang Yi
2026-10-09 10:30 ` [PATCH v7 09/31] ext4: add iomap address space operations for buffered I/O Zhang Yi
2026-10-09 10:30 ` [PATCH v7 10/31] ext4: implement buffered read path using iomap Zhang Yi
2026-10-09 10:30 ` [PATCH v7 11/31] ext4: pass out extent seq counter when mapping da blocks Zhang Yi
2026-10-09 10:30 ` [PATCH v7 12/31] ext4: do not use data=ordered mode for inodes using buffered iomap path Zhang Yi
2026-10-09 10:30 ` [PATCH v7 13/31] ext4: implement buffered write path using iomap Zhang Yi
2026-10-09 10:30 ` [PATCH v7 14/31] ext4: rework handle credit accounting for unwritten extent conversion Zhang Yi
2026-10-09 10:30 ` [PATCH v7 15/31] ext4: implement writeback path using iomap Zhang Yi
2026-10-09 10:30 ` [PATCH v7 16/31] ext4: implement mmap " Zhang Yi
2026-10-09 10:30 ` [PATCH v7 17/31] ext4: implement partial block zero range " Zhang Yi
2026-10-09 10:30 ` [PATCH v7 18/31] ext4: drain writeback before removing extents on the iomap path Zhang Yi
2026-10-09 10:30 ` [PATCH v7 19/31] ext4: add block mapping tracepoints for iomap buffered I/O path Zhang Yi
2026-10-09 10:30 ` [PATCH v7 20/31] ext4: disable online defrag when inode using " Zhang Yi
2026-10-09 10:30 ` [PATCH v7 21/31] ext4: add EXT4_STATE_DISKSIZE_GROW_PENDING state bit and helpers Zhang Yi
2026-10-09 10:30 ` [PATCH v7 22/31] ext4: submit and wait for pending disksize-grow I/O on writeback Zhang Yi
2026-10-09 10:30 ` [PATCH v7 23/31] ext4: advance i_disksize to i_size upon disksize-grow I/O completion Zhang Yi
2026-10-09 10:30 ` [PATCH v7 24/31] ext4: defer i_disksize update while DISKSIZE_GROW_PENDING is set Zhang Yi
2026-10-09 10:30 ` [PATCH v7 25/31] ext4: submit and wait for disksize-grow I/O in fallocate paths Zhang Yi
2026-10-09 10:30 ` [PATCH v7 26/31] ext4: clear DISKSIZE_GROW_PENDING on truncate or error Zhang Yi
2026-10-09 10:30 ` [PATCH v7 27/31] ext4: set DISKSIZE_GROW_PENDING after zeroing unaligned EOF block Zhang Yi
2026-10-10  9:00   ` Ojaswin Mujoo
2026-10-10  9:50     ` Zhang Yi
2026-10-10 13:49       ` Ojaswin Mujoo
2026-10-10 15:54         ` Zhang Yi [this message]
2026-10-09 10:30 ` [PATCH v7 28/31] ext4: add tracepoints for DISKSIZE_GROW_PENDING set, clear, and wait Zhang Yi
2026-10-09 10:38 ` [PATCH v7 29/31] ext4: add tracepoints for EOF block zeroing and disksize-grow I/O Zhang Yi
2026-10-09 10:38 ` [PATCH v7 30/31] ext4: partially enable iomap for the buffered I/O path of regular files Zhang Yi
2026-10-09 10:38 ` [PATCH v7 31/31] ext4: introduce a mount option for iomap buffered I/O path Zhang Yi
2026-10-09 17:54 ` [syzbot ci] Re: ext4: use iomap for regular file's " syzbot ci
2026-10-10  8:44   ` Zhang Yi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bf9a1b23-0473-4623-952b-b3a407e9b7a3@gmail.com \
    --to=yizhang089@gmail.com \
    --cc=adilger.kernel@dilger.ca \
    --cc=chengzhihao1@huawei.com \
    --cc=djwong@kernel.org \
    --cc=hch@infradead.org \
    --cc=jack@suse.cz \
    --cc=libaokun@linux.alibaba.com \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ojaswin@linux.ibm.com \
    --cc=ritesh.list@gmail.com \
    --cc=tytso@mit.edu \
    --cc=wangkefeng.wang@huawei.com \
    --cc=yangerkun@huawei.com \
    --cc=yi.zhang@huawei.com \
    --cc=yi.zhang@huaweicloud.com \
    --cc=yukuai@fnnas.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®