mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Chao Yu <chao@kernel.org>
To: Daeho Jeong <daeho43@gmail.com>
Cc: chao@kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net, kernel-team@android.com,
	Daeho Jeong <daehojeong@google.com>
Subject: Re: [f2fs-dev] [PATCH 08/20] f2fs: adapt range operations and mmap for subpage blocks
Date: Tue, 10 Nov 2026 19:35:20 +0800	[thread overview]
Message-ID: <5b96e661-8dd5-45d8-b259-6dd5c479f4ac@kernel.org> (raw)
In-Reply-To: <CACOAw_zfDubfF=U0ALSuNBkpU-tzUhQE_iyOq4CqarzFYYqQyg@mail.gmail.com>

On 11/8/26 07:24, Daeho Jeong wrote:
> On Thu, Nov 5, 2026 at 7:54 PM Chao Yu <chao@kernel.org> wrote:
>>
>> On 11/3/26 03:39, Daeho Jeong wrote:
>>> From: Daeho Jeong <daehojeong@google.com>
>>>
>>> - A writable PTE covers the whole folio, so page_mkwrite prepares
>>>    every block of the folio up to EOF and dirties only those blocks.
>>> - Align zero range, punch hole, preallocation and truncation to the
>>>    block size instead of PAGE_SIZE. When i_size grows, write-protect the
>>>    folio straddling the old EOF so that stores to its new blocks fault
>>>    into page_mkwrite.
>>> - Unmap every base page overlapping a punched or zeroed range, and
>>>    truncate the page cache without truncate_pagecache_range(), whose
>>>    unmap underflows for a range ending in the first page.
>>> - For SEEK_DATA, treat a NEW_ADDR block as data only if the block
>>>    itself is dirty.
>>> - When truncation zeroes the tail of a page, mark only the block
>>>    holding the new EOF dirty, not the blocks after it.
>>
>> IIUC, we will call pagecache_isize_extended() after i_size updates.
>>
>> Will f2fs_zero_post_eof_page() cover this case? if it missed anything after we
>> enable subpage block feature, how about fixing inside f2fs_zero_post_eof_page()?
> 
> Hi Chao,
> 
> They do different things. f2fs_zero_post_eof_page() runs before i_size
> grows and zeroes the data past the old EOF. pagecache_isize_extended()
> write-protects the folio straddling the old EOF, which may still be
> mapped writable, so that mmap stores to the blocks between the old EOF
> and the new size fault into ->page_mkwrite(), which prepares and dirties
> them. It has to run after i_size is updated, otherwise a fault in
> between could map the folio writable again with the old size, so it
> cannot be done in f2fs_zero_post_eof_page(). It returns early when the
> block size is not smaller than PAGE_SIZE, and this is how ext4 and xfs
> use it as well; setattr already gets it through truncate_setsize().

Daeho,

I see, thanks for the explanation.

Thanks,

> 
>>
>> Minor cleanup comments below.
> 
> Will change them in v2.
> 
> Thanks,
> 
>>
>>>
>>> The SEEK_DATA change also applies to large folios when the block size
>>> equals PAGE_SIZE.
>>>
>>> Signed-off-by: Daeho Jeong <daehojeong@google.com>
>>> ---
>>>   fs/f2fs/data.c |  58 +++++++++--
>>>   fs/f2fs/f2fs.h |   4 +-
>>>   fs/f2fs/file.c | 263 ++++++++++++++++++++++++++++++++-----------------
>>>   3 files changed, 228 insertions(+), 97 deletions(-)
>>>
>>> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
>>> index 41246f83b31d..77915a2eb190 100644
>>> --- a/fs/f2fs/data.c
>>> +++ b/fs/f2fs/data.c
>>> @@ -3198,7 +3198,7 @@ bool f2fs_ffs_dirty_blk_range(struct folio *folio, unsigned int from,
>>>
>>>   static bool find_next_valid_block(const struct folio *folio,
>>>                                 size_t orig_off, size_t *need_off,
>>> -                               size_t len)
>>> +                               size_t len, bool read_full)
>>>   {
>>>       struct f2fs_sb_info *sbi = F2FS_M_SB(folio->mapping);
>>>       size_t start = orig_off;
>>> @@ -3206,6 +3206,20 @@ static bool find_next_valid_block(const struct folio *folio,
>>>       size_t head, tail;
>>>       pgoff_t index;
>>>
>>> +     /* Find every block that is not uptodate, not only the partial ones. */
>>> +     if (read_full) {
>>> +             for (head = round_down(*need_off, F2FS_BLKSIZE(sbi)); head < end;
>>> +                  head += F2FS_BLKSIZE(sbi)) {
>>> +                     index = f2fs_folio_lblk(sbi, folio) +
>>> +                             F2FS_BYTES_TO_BLK(sbi, head);
>>> +                     if (!f2fs_ffs_test_blk_uptodate(folio, index)) {
>>> +                             *need_off = head;
>>> +                             return true;
>>> +                     }
>>> +             }
>>> +             return false;
>>> +     }
>>> +
>>>       if (start & (F2FS_BLKSIZE(sbi) - 1)) {
>>
>> F2FS_BLKSIZE_MASK(sbi)
>>
>>>               head = round_down(start, F2FS_BLKSIZE(sbi));
>>>               index = f2fs_folio_lblk(sbi, folio) +
>>> @@ -5222,7 +5236,8 @@ static bool f2fs_balance_fs_folio_locked(struct folio *folio,
>>>
>>>   static int prepare_large_folio_write_begin(struct inode *inode,
>>>                                         struct folio *folio, loff_t pos,
>>> -                                       unsigned int len, bool *need_balance)
>>> +                                       unsigned int len, bool read_full,
>>> +                                       bool preallocated, bool *need_balance)
>>>   {
>>>       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>>       struct f2fs_folio_state *ffs;
>>> @@ -5238,8 +5253,7 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>>>        * Reserve blocks without f2fs_balance_fs() since the folio is locked;
>>>        * the caller balances after unlocking it if *need_balance is set.
>>>        */
>>> -     if (!is_inode_flag_set(inode, FI_PREALLOCATED_ALL) &&
>>> -         !IS_DEVICE_ALIASING(inode)) {
>>> +     if (!preallocated && !IS_DEVICE_ALIASING(inode)) {
>>>               pgoff_t first = F2FS_BYTES_TO_BLK(sbi, pos);
>>>               pgoff_t last = F2FS_BLK_ALIGN(sbi, pos + len);
>>>               pgoff_t idx;
>>> @@ -5261,14 +5275,15 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>>>       /* Skip read if the folio is already fully uptodate or the write
>>>        * covers the entire folio.
>>>        */
>>> -     if (folio_test_uptodate(folio) || len == folio_size(folio))
>>> +     if (folio_test_uptodate(folio) ||
>>> +         (!read_full && len == folio_size(folio)))
>>>               return 0;
>>>
>>>       /*
>>>        * Skip read if the write starts at a block boundary and reaches EOF,
>>>        * and zero the rest of the folio instead.
>>>        */
>>> -     if (!f2fs_verity_in_progress(inode) &&
>>> +     if (!read_full && !f2fs_verity_in_progress(inode) &&
>>>           !(pos & (F2FS_BLKSIZE(sbi) - 1)) && (pos + len) >= i_size_read(inode)) {
>>
>> F2FS_BLKSIZE_MASK(sbi)
>>
>>>               size_t zoff = ori_off + len;
>>>
>>> @@ -5281,7 +5296,7 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>>>       f2fs_bug_on(sbi, f2fs_has_inline_data(inode));
>>>
>>>       while (find_next_valid_block(folio, ori_off,
>>> -                                  &need_off, len)) {
>>> +                                  &need_off, len, read_full)) {
>>>               struct dnode_of_data dn;
>>>               pgoff_t index = f2fs_folio_lblk(sbi, folio) +
>>>                               F2FS_BYTES_TO_BLK(sbi, need_off);
>>> @@ -5382,6 +5397,20 @@ static int prepare_large_folio_atomic_write_begin(struct inode *inode,
>>>       return 0;
>>>   }
>>>
>>> +int f2fs_prepare_subpage_mkwrite(struct inode *inode, struct folio *folio,
>>> +                              loff_t pos, unsigned int len)
>>> +{
>>> +     bool need_balance = false;
>>> +
>>> +     /*
>>> +      * mkwrite runs without inode_lock, so FI_PREALLOCATED_ALL may have
>>> +      * been set by a concurrent write() for a different range. It calls
>>> +      * f2fs_balance_fs() before locking the folio, so ignore need_balance.
>>> +      */
>>> +     return prepare_large_folio_write_begin(inode, folio, pos, len, true,
>>> +                                            false, &need_balance);
>>> +}
>>> +
>>>   static int f2fs_write_begin(const struct kiocb *iocb,
>>>                           struct address_space *mapping,
>>>                           loff_t pos, unsigned len, struct folio **foliop,
>>> @@ -5490,7 +5519,9 @@ static int f2fs_write_begin(const struct kiocb *iocb,
>>>                                       mapping, folio, pos, len, retried);
>>>       } else {
>>>               err = prepare_large_folio_write_begin(inode, folio, pos, blen,
>>> -                                                   &need_balance);
>>> +                             false,
>>> +                             is_inode_flag_set(inode, FI_PREALLOCATED_ALL),
>>> +                             &need_balance);
>>>       }
>>>       if (err) {
>>>               if (err == -EAGAIN) {
>>> @@ -5566,6 +5597,7 @@ static int f2fs_write_end(const struct kiocb *iocb,
>>>                       struct folio *folio, void *fsdata)
>>>   {
>>>       struct inode *inode = folio->mapping->host;
>>> +     loff_t old_size = i_size_read(inode);
>>>
>>>       trace_f2fs_write_end(inode, pos, len, copied);
>>>
>>> @@ -5595,6 +5627,8 @@ static int f2fs_write_end(const struct kiocb *iocb,
>>>               if (pos + copied > i_size_read(inode) &&
>>>                               !f2fs_verity_in_progress(inode))
>>>                       f2fs_i_size_write(inode, pos + copied);
>>> +             if (i_size_read(inode) > old_size)
>>> +                     pagecache_isize_extended(inode, old_size, pos);
>>>               return copied;
>>>       }
>>>   #endif
>>> @@ -5620,6 +5654,14 @@ static int f2fs_write_end(const struct kiocb *iocb,
>>>   unlock_out:
>>>       f2fs_folio_put(folio, true);
>>>       f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
>>> +
>>> +     /*
>>> +      * The folio straddling the old EOF may still be mapped writable, and
>>> +      * the blocks between the old EOF and pos in it are not prepared.
>>> +      * Write-protect it so that mmap stores to them fault.
>>> +      */
>>> +     if (i_size_read(inode) > old_size)
>>> +             pagecache_isize_extended(inode, old_size, pos);
>>>       return copied;
>>>   }
>>>
>>> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
>>> index aab1095ad5ff..8e3224eacdb5 100644
>>> --- a/fs/f2fs/f2fs.h
>>> +++ b/fs/f2fs/f2fs.h
>>> @@ -4094,7 +4094,7 @@ int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path,
>>>                struct kstat *stat, u32 request_mask, unsigned int flags);
>>>   int f2fs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
>>>                struct iattr *attr);
>>> -int f2fs_truncate_hole(struct inode *inode, pgoff_t pg_start, pgoff_t pg_end);
>>> +int f2fs_truncate_hole(struct inode *inode, pgoff_t blk_start, pgoff_t blk_end);
>>>   void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count);
>>>   int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag,
>>>                                               bool readonly, bool need_lock);
>>> @@ -4539,6 +4539,8 @@ void f2fs_ffs_clear_subrange_uptodate(struct folio *folio, size_t offset,
>>>                                     size_t len);
>>>   bool f2fs_ffs_dirty_blk_range(struct folio *folio, unsigned int from,
>>>                             unsigned int to, unsigned int *start, unsigned int *count);
>>> +int f2fs_prepare_subpage_mkwrite(struct inode *inode, struct folio *folio,
>>> +                              loff_t pos, unsigned int len);
>>>   void f2fs_write_failed(struct inode *inode, loff_t to);
>>>   void f2fs_invalidate_folio(struct folio *folio, size_t offset, size_t length);
>>>   bool f2fs_release_folio(struct folio *folio, gfp_t wait);
>>> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
>>> index be548f39e33e..e68553cef777 100644
>>> --- a/fs/f2fs/file.c
>>> +++ b/fs/f2fs/file.c
>>> @@ -41,18 +41,19 @@ static int fill_zero(struct inode *inode, pgoff_t index,
>>>
>>>   static int do_zero_post_eof_page(struct inode *inode, loff_t new_size)
>>>   {
>>> +     struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>>       loff_t old_size = i_size_read(inode);
>>>       unsigned int offset, len;
>>> -     pgoff_t index;
>>> +     pgoff_t bidx;
>>>       int err;
>>>
>>> -     offset = old_size & (PAGE_SIZE - 1);
>>> +     offset = old_size & (F2FS_BLKSIZE(sbi) - 1);
>>
>> F2FS_BLKSIZE_MASK(sbi)
>>
>>>
>>>       if (!offset)
>>>               return 0;
>>>
>>> -     len = min_t(loff_t, PAGE_SIZE - offset, new_size - old_size);
>>> -     index = old_size >> PAGE_SHIFT;
>>> +     len = min_t(loff_t, F2FS_BLKSIZE(sbi) - offset, new_size - old_size);
>>> +     bidx = F2FS_BYTES_TO_BLK(sbi, old_size);
>>>
>>>       if (f2fs_has_inline_data(inode)) {
>>>               /* data post eof should be always zero */
>>> @@ -63,7 +64,7 @@ static int do_zero_post_eof_page(struct inode *inode, loff_t new_size)
>>>                       return err;
>>>       }
>>>
>>> -     err = fill_zero(inode, index, offset, len);
>>> +     err = fill_zero(inode, bidx, offset, len);
>>>       if (err)
>>>               return err;
>>>       return filemap_write_and_wait_range(inode->i_mapping,
>>> @@ -144,11 +145,11 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
>>>       bool need_alloc = !f2fs_is_pinned_file(inode);
>>>       pgoff_t pidx = folio->index + folio_page_idx(folio, vmf->page);
>>>       loff_t pos = (loff_t)pidx << PAGE_SHIFT;
>>> +     pgoff_t bidx = F2FS_BYTES_TO_BLK(sbi, pos + offset_in_page(vmf->address));
>>>       loff_t isize;
>>>       loff_t folio_start;
>>>       loff_t valid_end;
>>>       size_t dirty_len;
>>> -     size_t subpage_off;
>>>       pgoff_t i, nr;
>>>       int err = 0;
>>>       vm_fault_t ret;
>>> @@ -212,14 +213,12 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
>>>       folio_lock(folio);
>>>       isize = i_size_read(inode);
>>>       folio_start = folio_pos(folio);
>>> -     subpage_off = offset_in_folio(folio, pos);
>>>       valid_end = min_t(loff_t, folio_start + folio_size(folio), isize);
>>>       dirty_len = valid_end - folio_start;
>>>
>>>       if (unlikely(folio->mapping != inode->i_mapping ||
>>>                       pos >= isize ||
>>> -                     !f2fs_ffs_test_blk_uptodate(folio,
>>> -                     folio->index + (subpage_off >> PAGE_SHIFT)))) {
>>> +                     !f2fs_ffs_test_blk_uptodate(folio, bidx))) {
>>>               folio_unlock(folio);
>>>               err = -EFAULT;
>>>               goto out_sem;
>>> @@ -240,6 +239,24 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
>>>
>>>       nr = DIV_ROUND_UP(dirty_len, PAGE_SIZE);
>>>
>>> +     if (f2fs_has_subpage_blocks(sbi)) {
>>> +             /*
>>> +              * A writable PTE covers the whole base folio.  Like ext4's
>>> +              * block_page_mkwrite(), prepare every filesystem block through EOF
>>> +              * before returning it writable, but dirty only those blocks.  This
>>> +              * prevents a later store in a sibling 4K block from bypassing block
>>> +              * allocation while also keeping blocks beyond i_size clean.
>>> +              */
>>> +             f2fs_folio_wait_writeback(folio, false, true);
>>> +             err = f2fs_prepare_subpage_mkwrite(inode, folio,
>>> +                                                folio_pos(folio), dirty_len);
>>> +             if (err) {
>>> +                     folio_unlock(folio);
>>> +                     goto out_sem;
>>> +             }
>>> +             goto zero_tail;
>>> +     }
>>> +
>>>       for (i = 0; i < nr; i++) {
>>>               err = f2fs_get_block_mkwrite(inode, folio, folio->index + i,
>>>                                            need_alloc);
>>> @@ -249,6 +266,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
>>>               }
>>>       }
>>>
>>> +zero_tail:
>>>       /* page is wholly or partially inside EOF */
>>>       if (folio_start + folio_size(folio) > isize) {
>>>               size_t offset = offset_in_folio(folio, isize);
>>> @@ -256,8 +274,7 @@ static vm_fault_t f2fs_vm_page_mkwrite(struct vm_fault *vmf)
>>>               folio_zero_segment(folio, offset, folio_size(folio));
>>>       }
>>>
>>> -     if (folio_test_large(folio))
>>> -             f2fs_ffs_mark_subrange_dirty(folio, 0, dirty_len);
>>> +     f2fs_ffs_mark_subrange_dirty(folio, 0, dirty_len);
>>>       folio_mark_dirty(folio);
>>>
>>>       f2fs_update_iostat(sbi, inode, APP_MAPPED_IO, dirty_len);
>>> @@ -503,7 +520,9 @@ static bool __found_offset(struct address_space *mapping,
>>>   {
>>>       block_t blkaddr = f2fs_data_blkaddr(dn);
>>>       struct inode *inode = mapping->host;
>>> +     struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>>       bool compressed_cluster = false;
>>> +     bool dirty = false;
>>>
>>>       if (f2fs_compressed_file(inode)) {
>>>               block_t first_blkaddr = data_blkaddr(dn->inode, dn->node_entry,
>>> @@ -512,12 +531,37 @@ static bool __found_offset(struct address_space *mapping,
>>>               compressed_cluster = first_blkaddr == COMPRESS_ADDR;
>>>       }
>>>
>>> +     if (blkaddr == NEW_ADDR) {
>>> +             pgoff_t f_index = f2fs_lblk_to_folio_index(sbi, index);
>>> +
>>> +             if (xa_get_mark(&mapping->i_pages, f_index, PAGECACHE_TAG_DIRTY)) {
>>> +                     struct folio *folio = filemap_get_folio(mapping, f_index);
>>> +
>>> +                     if (!IS_ERR(folio)) {
>>> +                             /*
>>> +                              * ffs is freed under the folio lock. We hold the
>>> +                              * dnode, so only trylock the folio, and take the
>>> +                              * block as dirty, as the dirty tag says, if the
>>> +                              * folio is busy.
>>> +                              */
>>> +                             if (folio_trylock(folio)) {
>>> +                                     if (folio->mapping == mapping)
>>> +                                             dirty = f2fs_ffs_test_blk_dirty(folio,
>>> +                                                                     index);
>>> +                                     folio_unlock(folio);
>>> +                             } else {
>>> +                                     dirty = true;
>>> +                             }
>>> +                             f2fs_folio_put(folio, false);
>>> +                     }
>>> +             }
>>> +     }
>>> +
>>>       switch (whence) {
>>>       case SEEK_DATA:
>>>               if (__is_valid_data_blkaddr(blkaddr))
>>>                       return true;
>>> -             if (blkaddr == NEW_ADDR &&
>>> -                 xa_get_mark(&mapping->i_pages, index, PAGECACHE_TAG_DIRTY))
>>> +             if (dirty)
>>>                       return true;
>>>               if (compressed_cluster)
>>>                       return true;
>>> @@ -815,20 +859,23 @@ void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count)
>>>                                        dn->ofs_in_node, nr_free);
>>>   }
>>>
>>> -static int truncate_partial_data_page(struct inode *inode, u64 from,
>>> +static int truncate_partial_data_block(struct inode *inode, u64 from,
>>>                                                               bool cache_only)
>>>   {
>>> -     pgoff_t index = from >> PAGE_SHIFT;
>>> +     struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>> +     pgoff_t bidx = F2FS_BYTES_TO_BLK(sbi, from);
>>> +     pgoff_t f_index = f2fs_has_subpage_blocks(sbi) ?
>>> +             f2fs_lblk_to_folio_index(sbi, bidx) : (from >> PAGE_SHIFT);
>>>       struct address_space *mapping = inode->i_mapping;
>>>       struct folio *folio;
>>>       size_t folio_off;
>>>       size_t blk_end;
>>>
>>> -     if (!(from & (PAGE_SIZE - 1)) && !cache_only)
>>> +     if (!(from & (F2FS_BLKSIZE(sbi) - 1)) && !cache_only)
>>
>> F2FS_BLKSIZE_MASK(sbi)
>>
>>>               return 0;
>>>
>>>       if (cache_only) {
>>> -             folio = filemap_lock_folio(mapping, index);
>>> +             folio = filemap_lock_folio(mapping, f_index);
>>>               if (IS_ERR(folio))
>>>                      return 0;
>>>               if (folio_test_uptodate(folio))
>>> @@ -837,7 +884,7 @@ static int truncate_partial_data_page(struct inode *inode, u64 from,
>>>               return 0;
>>>       }
>>>
>>> -     folio = f2fs_get_lock_data_folio(inode, index, true);
>>> +     folio = f2fs_get_lock_data_folio(inode, bidx, true);
>>>       if (IS_ERR(folio))
>>>               return PTR_ERR(folio) == -ENOENT ? 0 : PTR_ERR(folio);
>>>   truncate_out:
>>> @@ -848,14 +895,15 @@ static int truncate_partial_data_page(struct inode *inode, u64 from,
>>>       folio_zero_segment(folio, folio_off, blk_end);
>>>
>>>       /* An encrypted inode should have a key and truncate the last page. */
>>> -     f2fs_bug_on(F2FS_I_SB(inode), cache_only && IS_ENCRYPTED(inode));
>>> +     f2fs_bug_on(sbi, cache_only && IS_ENCRYPTED(inode));
>>>       if (!cache_only) {
>>>               /*
>>>                * The folio was read back above, so the subpage is
>>> -              * already uptodate and dirty data stays valid.
>>> +              * already uptodate and dirty data stays valid. Mark only
>>> +              * the block holding the new EOF dirty.
>>>                */
>>>               f2fs_ffs_mark_subrange_dirty(folio, folio_off,
>>> -                             blk_end - folio_off);
>>> +                     round_up(folio_off, F2FS_BLKSIZE(sbi)) - folio_off);
>>>               folio_mark_dirty(folio);
>>>       }
>>>       f2fs_folio_put(folio, true);
>>> @@ -944,9 +992,9 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock)
>>>       if (lock)
>>>               f2fs_unlock_op(sbi, &lc);
>>>   free_partial:
>>> -     /* lastly zero out the first data page */
>>> +     /* lastly zero out the first data block */
>>>       if (!err)
>>> -             err = truncate_partial_data_page(inode, from, truncate_page);
>>> +             err = truncate_partial_data_block(inode, from, truncate_page);
>>
>> s/truncate_page/truncate_block ?
>>
>> Thanks,
>>
>>>   out_err:
>>>       trace_f2fs_truncate_blocks_exit(inode, err);
>>>       return err;
>>> @@ -1310,7 +1358,7 @@ const struct inode_operations f2fs_file_inode_operations = {
>>>       .fileattr_set   = f2fs_fileattr_set,
>>>   };
>>>
>>> -static int fill_zero(struct inode *inode, pgoff_t index,
>>> +static int fill_zero(struct inode *inode, pgoff_t bidx,
>>>                                       loff_t start, loff_t len)
>>>   {
>>>       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>> @@ -1324,15 +1372,14 @@ static int fill_zero(struct inode *inode, pgoff_t index,
>>>       f2fs_balance_fs(sbi, true);
>>>
>>>       f2fs_lock_op(sbi, &lc);
>>> -     folio = f2fs_get_new_data_folio(inode, NULL, index, false);
>>> +     folio = f2fs_get_new_data_folio(inode, NULL, bidx, false);
>>>       f2fs_unlock_op(sbi, &lc);
>>>
>>>       if (IS_ERR(folio))
>>>               return PTR_ERR(folio);
>>>
>>>       f2fs_folio_wait_writeback(folio, true, true);
>>> -     folio_off = offset_in_folio(folio,
>>> -                                (loff_t)index << PAGE_SHIFT) + start;
>>> +     folio_off = f2fs_folio_byte_offset(sbi, folio, bidx) + start;
>>>       folio_zero_range(folio, folio_off, len);
>>>       /*
>>>        * This subpage must be uptodate here, which is guaranteed by
>>> @@ -1344,41 +1391,68 @@ static int fill_zero(struct inode *inode, pgoff_t index,
>>>       return 0;
>>>   }
>>>
>>> -int f2fs_truncate_hole(struct inode *inode, pgoff_t pg_start, pgoff_t pg_end)
>>> +int f2fs_truncate_hole(struct inode *inode, pgoff_t blk_start, pgoff_t blk_end)
>>>   {
>>>       int err;
>>>
>>> -     while (pg_start < pg_end) {
>>> +     while (blk_start < blk_end) {
>>>               struct dnode_of_data dn;
>>>               pgoff_t end_offset, count;
>>>
>>>               set_new_dnode(&dn, inode, NULL, NULL, 0);
>>> -             err = f2fs_get_dnode_of_data(&dn, pg_start, LOOKUP_NODE);
>>> +             err = f2fs_get_dnode_of_data(&dn, blk_start, LOOKUP_NODE);
>>>               if (err) {
>>>                       if (err == -ENOENT) {
>>> -                             pg_start = f2fs_get_next_block_offset(&dn,
>>> -                                                             pg_start);
>>> +                             blk_start = f2fs_get_next_block_offset(&dn,
>>> +                                                             blk_start);
>>>                               continue;
>>>                       }
>>>                       return err;
>>>               }
>>>
>>>               end_offset = ADDRS_PER_PAGE(dn.node_entry, inode);
>>> -             count = min(end_offset - dn.ofs_in_node, pg_end - pg_start);
>>> +             count = min(end_offset - dn.ofs_in_node, blk_end - blk_start);
>>>
>>>               f2fs_bug_on(F2FS_I_SB(inode), count == 0 || count > end_offset);
>>>
>>>               f2fs_truncate_data_blocks_range(&dn, count);
>>>               f2fs_put_dnode(&dn);
>>>
>>> -             pg_start += count;
>>> +             blk_start += count;
>>>       }
>>>       return 0;
>>>   }
>>>
>>> +/*
>>> + * unmap_mapping_range() computes page count solely from holelen, which causes
>>> + * under-unmapping when start is not PAGE_SIZE aligned. Outward-align to PAGE_SIZE
>>> + * to guarantee all overlapping base pages are unmapped.
>>> + *
>>> + * Then truncate the page cache without truncate_pagecache_range(), whose own
>>> + * unmap underflows when the range ends in the first page and unmaps the rest
>>> + * of the file.
>>> + */
>>> +static void f2fs_truncate_pagecache_range(struct inode *inode,
>>> +                                       loff_t start, loff_t end)
>>> +{
>>> +     struct address_space *mapping = inode->i_mapping;
>>> +     loff_t unmap_start, unmap_len;
>>> +
>>> +     if (!f2fs_has_subpage_blocks(F2FS_I_SB(inode))) {
>>> +             truncate_pagecache_range(inode, start, end - 1);
>>> +             return;
>>> +     }
>>> +
>>> +     unmap_start = round_down(start, PAGE_SIZE);
>>> +     unmap_len = round_up(end, PAGE_SIZE) - unmap_start;
>>> +     unmap_mapping_range(mapping, unmap_start, unmap_len, 0);
>>> +     truncate_inode_pages_range(mapping, start, end - 1);
>>> +}
>>> +
>>>   static int f2fs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
>>>   {
>>> -     pgoff_t pg_start, pg_end;
>>> +     struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>> +     pgoff_t blk_start, blk_end;
>>>       loff_t off_start, off_end;
>>>       int ret;
>>>
>>> @@ -1390,47 +1464,46 @@ static int f2fs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
>>>       if (ret)
>>>               return ret;
>>>
>>> -     pg_start = ((unsigned long long) offset) >> PAGE_SHIFT;
>>> -     pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT;
>>> +     blk_start = F2FS_BYTES_TO_BLK(sbi, offset);
>>> +     blk_end = F2FS_BYTES_TO_BLK(sbi, offset + len);
>>>
>>> -     off_start = offset & (PAGE_SIZE - 1);
>>> -     off_end = (offset + len) & (PAGE_SIZE - 1);
>>> +     off_start = offset & F2FS_BLKSIZE_MASK(sbi);
>>> +     off_end = (offset + len) & F2FS_BLKSIZE_MASK(sbi);
>>>
>>> -     if (pg_start == pg_end) {
>>> -             ret = fill_zero(inode, pg_start, off_start,
>>> +     if (blk_start == blk_end) {
>>> +             ret = fill_zero(inode, blk_start, off_start,
>>>                                               off_end - off_start);
>>>               if (ret)
>>>                       return ret;
>>>       } else {
>>>               if (off_start) {
>>> -                     ret = fill_zero(inode, pg_start++, off_start,
>>> -                                             PAGE_SIZE - off_start);
>>> +                     ret = fill_zero(inode, blk_start++, off_start,
>>> +                                             F2FS_BLKSIZE(sbi) - off_start);
>>>                       if (ret)
>>>                               return ret;
>>>               }
>>>               if (off_end) {
>>> -                     ret = fill_zero(inode, pg_end, 0, off_end);
>>> +                     ret = fill_zero(inode, blk_end, 0, off_end);
>>>                       if (ret)
>>>                               return ret;
>>>               }
>>>
>>> -             if (pg_start < pg_end) {
>>> -                     loff_t blk_start, blk_end;
>>> -                     struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>> +             if (blk_start < blk_end) {
>>> +                     loff_t byte_start, byte_end;
>>>                       struct f2fs_lock_context lc;
>>>
>>>                       f2fs_balance_fs(sbi, true);
>>>
>>> -                     blk_start = (loff_t)pg_start << PAGE_SHIFT;
>>> -                     blk_end = (loff_t)pg_end << PAGE_SHIFT;
>>> +                     byte_start = F2FS_BLK_TO_BYTES(sbi, blk_start);
>>> +                     byte_end = F2FS_BLK_TO_BYTES(sbi, blk_end);
>>>
>>>                       f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
>>>                       filemap_invalidate_lock(inode->i_mapping);
>>>
>>> -                     truncate_pagecache_range(inode, blk_start, blk_end - 1);
>>> +                     f2fs_truncate_pagecache_range(inode, byte_start, byte_end);
>>>
>>>                       f2fs_lock_op(sbi, &lc);
>>> -                     ret = f2fs_truncate_hole(inode, pg_start, pg_end);
>>> +                     ret = f2fs_truncate_hole(inode, blk_start, blk_end);
>>>                       f2fs_unlock_op(sbi, &lc);
>>>
>>>                       filemap_invalidate_unlock(inode->i_mapping);
>>> @@ -1621,7 +1694,7 @@ static int __clone_blkaddrs(struct inode *src_inode, struct inode *dst_inode,
>>>                       f2fs_folio_wait_writeback(fdst, true, true);
>>>                       memcpy_folio(fdst, dst_off, fsrc, src_off,
>>>                                   F2FS_BLKSIZE(sbi));
>>> -                     if (folio_test_large(fdst)) {
>>> +                     if (f2fs_is_multiblock_folio(sbi, fdst)) {
>>>                               f2fs_ffs_mark_subrange_uptodate(fdst,
>>>                                               dst_off, F2FS_BLKSIZE(sbi));
>>>                               f2fs_ffs_mark_subrange_dirty(fdst, dst_off,
>>> @@ -1830,7 +1903,7 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>   {
>>>       struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>>>       struct address_space *mapping = inode->i_mapping;
>>> -     pgoff_t index, pg_start, pg_end;
>>> +     pgoff_t blk_start, blk_end;
>>>       loff_t new_size = i_size_read(inode);
>>>       loff_t off_start, off_end;
>>>       int ret = 0;
>>> @@ -1851,31 +1924,33 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>       if (ret)
>>>               return ret;
>>>
>>> -     pg_start = ((unsigned long long) offset) >> PAGE_SHIFT;
>>> -     pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT;
>>> +     blk_start = F2FS_BYTES_TO_BLK(sbi, offset);
>>> +     blk_end = F2FS_BYTES_TO_BLK(sbi, offset + len);
>>>
>>> -     off_start = offset & (PAGE_SIZE - 1);
>>> -     off_end = (offset + len) & (PAGE_SIZE - 1);
>>> +     off_start = offset & F2FS_BLKSIZE_MASK(sbi);
>>> +     off_end = (offset + len) & F2FS_BLKSIZE_MASK(sbi);
>>>
>>> -     if (pg_start == pg_end) {
>>> -             ret = fill_zero(inode, pg_start, off_start,
>>> +     if (blk_start == blk_end) {
>>> +             ret = fill_zero(inode, blk_start, off_start,
>>>                                               off_end - off_start);
>>>               if (ret)
>>>                       return ret;
>>>
>>>               new_size = max_t(loff_t, new_size, offset + len);
>>>       } else {
>>> +             pgoff_t index;
>>> +
>>>               if (off_start) {
>>> -                     ret = fill_zero(inode, pg_start++, off_start,
>>> -                                             PAGE_SIZE - off_start);
>>> +                     ret = fill_zero(inode, blk_start++, off_start,
>>> +                                             F2FS_BLKSIZE(sbi) - off_start);
>>>                       if (ret)
>>>                               return ret;
>>>
>>>                       new_size = max_t(loff_t, new_size,
>>> -                                     (loff_t)pg_start << PAGE_SHIFT);
>>> +                                     F2FS_BLK_TO_BYTES(sbi, blk_start));
>>>               }
>>>
>>> -             for (index = pg_start; index < pg_end;) {
>>> +             for (index = blk_start; index < blk_end;) {
>>>                       struct dnode_of_data dn;
>>>                       struct f2fs_lock_context lc;
>>>                       unsigned int end_offset;
>>> @@ -1884,9 +1959,9 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>                       f2fs_down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
>>>                       filemap_invalidate_lock(mapping);
>>>
>>> -                     truncate_pagecache_range(inode,
>>> -                             (loff_t)index << PAGE_SHIFT,
>>> -                             ((loff_t)pg_end << PAGE_SHIFT) - 1);
>>> +                     f2fs_truncate_pagecache_range(inode,
>>> +                             F2FS_BLK_TO_BYTES(sbi, index),
>>> +                             F2FS_BLK_TO_BYTES(sbi, blk_end));
>>>
>>>                       f2fs_lock_op(sbi, &lc);
>>>
>>> @@ -1900,7 +1975,7 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>                       }
>>>
>>>                       end_offset = ADDRS_PER_PAGE(dn.node_entry, inode);
>>> -                     end = min(pg_end, end_offset - dn.ofs_in_node + index);
>>> +                     end = min(blk_end, end_offset - dn.ofs_in_node + index);
>>>
>>>                       ret = f2fs_do_zero_range(&dn, index, end);
>>>                       f2fs_put_dnode(&dn);
>>> @@ -1916,11 +1991,11 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>
>>>                       index = end;
>>>                       new_size = max_t(loff_t, new_size,
>>> -                                     (loff_t)index << PAGE_SHIFT);
>>> +                                     F2FS_BLK_TO_BYTES(sbi, index));
>>>               }
>>>
>>>               if (off_end) {
>>> -                     ret = fill_zero(inode, pg_end, 0, off_end);
>>> +                     ret = fill_zero(inode, blk_end, 0, off_end);
>>>                       if (ret)
>>>                               goto out;
>>>
>>> @@ -1930,10 +2005,14 @@ static int f2fs_zero_range(struct inode *inode, loff_t offset, loff_t len,
>>>
>>>   out:
>>>       if (new_size > i_size_read(inode)) {
>>> -             if (mode & FALLOC_FL_KEEP_SIZE)
>>> +             if (mode & FALLOC_FL_KEEP_SIZE) {
>>>                       file_set_keep_isize(inode);
>>> -             else
>>> +             } else {
>>> +                     loff_t old_size = i_size_read(inode);
>>> +
>>>                       f2fs_i_size_write(inode, new_size);
>>> +                     pagecache_isize_extended(inode, old_size, new_size);
>>> +             }
>>>       }
>>>       return ret;
>>>   }
>>> @@ -2034,7 +2113,7 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
>>>                       .should_migrate_blocks = false,
>>>                       .err_gc_skipped = true,
>>>                       .nr_free_secs = 0 };
>>> -     pgoff_t pg_start, pg_end;
>>> +     pgoff_t blk_start, blk_end;
>>>       loff_t new_size;
>>>       loff_t off_end;
>>>       block_t expanded = 0;
>>> @@ -2054,12 +2133,12 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
>>>
>>>       f2fs_balance_fs(sbi, true);
>>>
>>> -     pg_start = ((unsigned long long)offset) >> PAGE_SHIFT;
>>> -     pg_end = ((unsigned long long)offset + len) >> PAGE_SHIFT;
>>> -     off_end = (offset + len) & (PAGE_SIZE - 1);
>>> +     blk_start = F2FS_BYTES_TO_BLK(sbi, offset);
>>> +     blk_end = F2FS_BYTES_TO_BLK(sbi, offset + len);
>>> +     off_end = (offset + len) & F2FS_BLKSIZE_MASK(sbi);
>>>
>>> -     map.m_lblk = pg_start;
>>> -     map.m_len = pg_end - pg_start;
>>> +     map.m_lblk = blk_start;
>>> +     map.m_len = blk_end - blk_start;
>>>       if (off_end)
>>>               map.m_len++;
>>>
>>> @@ -2071,9 +2150,9 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
>>>               block_t sec_len;
>>>
>>>               if (map.m_lblk % sec_blks) {
>>> -                     pg_start = rounddown(map.m_lblk, sec_blks);
>>> -                     map.m_lblk = pg_start;
>>> -                     map.m_len = pg_end - pg_start;
>>> +                     blk_start = rounddown(map.m_lblk, sec_blks);
>>> +                     map.m_lblk = blk_start;
>>> +                     map.m_len = blk_end - blk_start;
>>>                       if (off_end)
>>>                               map.m_len++;
>>>               }
>>> @@ -2089,7 +2168,7 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
>>>                               f2fs_warn_ratelimited(sbi,
>>>                                       "ino:%llu, start:%lu, end:%lu, need to trigger GC to "
>>>                                       "reclaim enough free segment when checkpoint is enabled",
>>> -                                     inode->i_ino, pg_start, pg_end);
>>> +                                     inode->i_ino, blk_start, blk_end);
>>>                               goto out_err;
>>>                       }
>>>               }
>>> @@ -2135,20 +2214,24 @@ static int f2fs_expand_inode_data(struct inode *inode, loff_t offset,
>>>               if (!expanded)
>>>                       return err;
>>>
>>> -             last_off = pg_start + expanded - 1;
>>> +             last_off = blk_start + expanded - 1;
>>>
>>>               /* update new size to the failed position */
>>> -             new_size = (last_off == pg_end) ? offset + len :
>>> -                                     (loff_t)(last_off + 1) << PAGE_SHIFT;
>>> +             new_size = (last_off == blk_end) ? offset + len :
>>> +                                     F2FS_BLK_TO_BYTES(sbi, last_off + 1);
>>>       } else {
>>> -             new_size = ((loff_t)pg_end << PAGE_SHIFT) + off_end;
>>> +             new_size = F2FS_BLK_TO_BYTES(sbi, blk_end) + off_end;
>>>       }
>>>
>>>       if (new_size > i_size_read(inode)) {
>>> -             if (mode & FALLOC_FL_KEEP_SIZE)
>>> +             if (mode & FALLOC_FL_KEEP_SIZE) {
>>>                       file_set_keep_isize(inode);
>>> -             else
>>> +             } else {
>>> +                     loff_t old_size = i_size_read(inode);
>>> +
>>>                       f2fs_i_size_write(inode, new_size);
>>> +                     pagecache_isize_extended(inode, old_size, new_size);
>>> +             }
>>>       }
>>>
>>>       return err;
>>> @@ -5758,8 +5841,12 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
>>>
>>>       if (ret < 0)
>>>               goto out;
>>> -     if (pos + ret > inode->i_size)
>>> +     if (pos + ret > inode->i_size) {
>>> +             loff_t old_size = inode->i_size;
>>> +
>>>               f2fs_i_size_write(inode, pos + ret);
>>> +             pagecache_isize_extended(inode, old_size, pos + ret);
>>> +     }
>>>       if (!do_opu)
>>>               set_inode_flag(inode, FI_UPDATE_WRITE);
>>>
>>


  reply	other threads:[~2026-11-10 11:35 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 19:39 [PATCH 00/20] f2fs: support block size smaller than PAGE_SIZE Daeho Jeong
2026-11-02 19:39 ` [PATCH 01/20] f2fs: use block-based index and size calculations in remaining paths Daeho Jeong
2026-11-03  7:04   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 02/20] f2fs: support subpage block size superblock and mount Daeho Jeong
2026-11-03  9:26   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 03/20] f2fs: extend f2fs_folio_state for subpage blocks Daeho Jeong
2026-11-04  9:19   ` [f2fs-dev] " Chao Yu
2026-11-06 20:53     ` Daeho Jeong
2026-11-02 19:39 ` [PATCH 04/20] f2fs: support subpage blocks in directory operations Daeho Jeong
2026-11-04 11:13   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 05/20] f2fs: convert inode dirty page counters to dirty block counters Daeho Jeong
2026-11-04 11:24   ` [f2fs-dev] " Chao Yu
2026-11-06 21:29     ` Daeho Jeong
2026-11-02 19:39 ` [PATCH 06/20] f2fs: support subpage blocks in data read path and GC Daeho Jeong
2026-11-05  7:55   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 07/20] f2fs: support subpage blocks in data write path and writeback Daeho Jeong
2026-11-06  2:52   ` [f2fs-dev] " Chao Yu
2026-11-07 23:14     ` Daeho Jeong
2026-11-10 11:19       ` Chao Yu
2026-11-02 19:39 ` [PATCH 08/20] f2fs: adapt range operations and mmap for subpage blocks Daeho Jeong
2026-11-06  3:54   ` [f2fs-dev] " Chao Yu
2026-11-07 23:24     ` Daeho Jeong
2026-11-10 11:35       ` Chao Yu [this message]
2026-11-02 19:39 ` [PATCH 09/20] f2fs: support subpage blocks in pinned files Daeho Jeong
2026-11-02 19:39 ` [PATCH 10/20] f2fs: support subpage blocks in collapse and insert range Daeho Jeong
2026-11-02 19:39 ` [PATCH 11/20] f2fs: support subpage blocks in move range Daeho Jeong
2026-11-02 19:39 ` [PATCH 12/20] f2fs: support subpage blocks in defragmentation Daeho Jeong
2026-11-02 19:39 ` [PATCH 13/20] f2fs: support subpage blocks in swapfile activation Daeho Jeong
2026-11-02 19:39 ` [PATCH 14/20] f2fs: support subpage blocks in fs-verity enablement Daeho Jeong
2026-11-02 19:39 ` [PATCH 15/20] f2fs: support subpage blocks in atomic writes Daeho Jeong
2026-11-09  2:56   ` [f2fs-dev] " Chao Yu
2026-11-10 11:49   ` Chao Yu
2026-11-02 19:39 ` [PATCH 16/20] f2fs: preserve inline data in subpage block mode Daeho Jeong
2026-11-09  3:18   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 17/20] f2fs: support subpage blocks in online resize Daeho Jeong
2026-11-02 19:39 ` [PATCH 18/20] f2fs: support subpage blocks in compression Daeho Jeong
2026-11-10  3:04   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 19/20] f2fs: count compress cache entries in pages for its memory limit Daeho Jeong
2026-11-02 19:39 ` [PATCH 20/20] f2fs: enable subpage block size support Daeho Jeong

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=5b96e661-8dd5-45d8-b259-6dd5c479f4ac@kernel.org \
    --to=chao@kernel.org \
    --cc=daeho43@gmail.com \
    --cc=daehojeong@google.com \
    --cc=kernel-team@android.com \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    /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®