Re: [PATCH] btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr()
Filipe Manana <[email protected]>
| Newsgroups | org.kernel.vger.linux-btrfs |
|---|---|
| Message-ID | <CAL3q7H6kcDvYfi5T7N1Rf9=_aCdT8oDafW2w_QS2U3nmhMRZPw@mail.gmail.com> |
On Fri, Jun 26, 2026 at 7:52 AM Dave Chen <[email protected]> wrote: > > btrfs_getattr() unconditionally reads BTRFS_I(inode)->new_delalloc_bytes > and adds it (sector-aligned) to stat->blocks for every inode type. > However, new_delalloc_bytes lives in a union with last_dir_index_offset: > > union { > u64 new_delalloc_bytes; /* files only */ > u64 last_dir_index_offset; /* directories only */ > }; > > For a directory inode this memory holds last_dir_index_offset, which is > set during directory logging (e.g. flush_dir_items_batch()) to the > offset of the last logged BTRFS_DIR_INDEX_KEY. That offset grows with > the number of entries ever created in the directory (dir indexes are > monotonic and never reused), so it can be arbitrarily large. > > As a result, after a directory has been logged (e.g. via an fsync that > triggers directory logging), btrfs_getattr() reports inflated st_blocks > for that directory. The inflation is purely in-core and disappears > after the inode is evicted and reloaded (btrfs_alloc_inode() zeroes the > union), e.g. after a remount. > > Reproducer (on a btrfs filesystem): > > D=/mnt/btrfs/d > mkdir -p $D > for i in $(seq 1 20000); do touch $D/f$i; done > sync # commit, push dir index high > touch $D/trigger # dirty the dir in a new transaction > xfs_io -c fsync $D # log the directory -> sets last_dir_index_offset > stat -c '%b' $D # st_blocks is now inflated (e.g. 40) > # umount + mount -> st_blocks drops back to the correct value > > The evict path already knows this union is type-dependent and guards the > corresponding WARN_ON with !S_ISDIR() in btrfs_destroy_inode(); only > btrfs_getattr() was missing the equivalent check. > > Only read new_delalloc_bytes for regular files, which are the only > inodes that ever set it. > > Signed-off-by: Dave Chen <[email protected]> Reviewed-by: Filipe Manana <[email protected]> Will add it soon to github's for-next branch, thanks. > --- > fs/btrfs/inode.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c > index 272598f6ae77..b446c3014b24 100644 > --- a/fs/btrfs/inode.c > +++ b/fs/btrfs/inode.c > @@ -8068,7 +8068,8 @@ static int btrfs_getattr(struct mnt_idmap *idmap, > stat->result_mask |= STATX_SUBVOL; > > spin_lock(&BTRFS_I(inode)->lock); > - delalloc_bytes = BTRFS_I(inode)->new_delalloc_bytes; > + delalloc_bytes = S_ISREG(inode->i_mode) ? > + BTRFS_I(inode)->new_delalloc_bytes : 0; > inode_bytes = inode_get_bytes(inode); > spin_unlock(&BTRFS_I(inode)->lock); > stat->blocks = (ALIGN(inode_bytes, blocksize) + > -- > 2.43.0 > > > Disclaimer: The contents of this e-mail message and any attachments are confidential and are intended solely for addressee. The information may also be legally privileged. This transmission is sent in trust, for the sole purpose of delivery to the intended recipient. If you have received this transmission in error, any use, reproduction or dissemination of this transmission is strictly prohibited. If you are not the intended recipient, please immediately notify the sender by reply e-mail or phone and delete this message and its attachments, if any. >