From 86e332447d726127e042b28f7c65ca3784443794 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Mon, 6 Jul 2026 10:41:16 +0200 Subject: [PATCH 01/23] xfs: add an allocation mode to xfs_alloc_file_space() xfs_alloc_file_space() hardcodes XFS_BMAPI_PREALLOC to preallocate unwritten extents across a range. In preparation for FALLOC_FL_WRITE_ZEROES, add an explicit allocation mode argument, enum xfs_alloc_file_space_mode, and derive the xfs_bmapi flags from it. The only mode for now is XFS_ALLOC_FILE_SPACE_PREALLOC, which preallocates unwritten extents and marks the inode as preallocated exactly as before, so there is no functional change. Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Pankaj Raghav Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_bmap_util.c | 25 +++++++++++++++++++++---- fs/xfs/xfs_bmap_util.h | 6 +++++- fs/xfs/xfs_file.c | 9 ++++++--- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index c88b9ade7389..48db310b118c 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -642,11 +642,19 @@ xfs_free_eofblocks( return error; } +/* + * Allocate space for a file according to @mode: + * + * XFS_ALLOC_FILE_SPACE_PREALLOC: + * Preallocate unwritten extents over holes across the range and mark the inode + * as preallocated. + */ int xfs_alloc_file_space( struct xfs_inode *ip, xfs_off_t offset, - xfs_off_t len) + xfs_off_t len, + enum xfs_alloc_file_space_mode mode) { xfs_mount_t *mp = ip->i_mount; xfs_off_t count; @@ -657,6 +665,7 @@ xfs_alloc_file_space( int rt; xfs_trans_t *tp; xfs_bmbt_irec_t imaps[1], *imapp; + uint32_t bmapi_flags, nr_exts; int error; if (xfs_is_always_cow_inode(ip)) @@ -674,6 +683,15 @@ xfs_alloc_file_space( if (len <= 0) return -EINVAL; + switch (mode) { + case XFS_ALLOC_FILE_SPACE_PREALLOC: + bmapi_flags = XFS_BMAPI_PREALLOC; + nr_exts = XFS_IEXT_ADD_NOSPLIT_CNT; + break; + default: + return -EINVAL; + } + rt = XFS_IS_REALTIME_INODE(ip); extsz = xfs_get_extsz_hint(ip); @@ -733,8 +751,7 @@ xfs_alloc_file_space( if (error) break; - error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK, - XFS_IEXT_ADD_NOSPLIT_CNT); + error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK, nr_exts); if (error) goto error; @@ -748,7 +765,7 @@ xfs_alloc_file_space( * will eventually reach the requested range. */ error = xfs_bmapi_write(tp, ip, startoffset_fsb, - allocatesize_fsb, XFS_BMAPI_PREALLOC, 0, imapp, + allocatesize_fsb, bmapi_flags, 0, imapp, &nimaps); if (error) { if (error != -ENOSR) diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index eaaf094154b9..929b39d9d88a 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -55,8 +55,12 @@ int xfs_bmap_last_extent(struct xfs_trans *tp, struct xfs_inode *ip, int *is_empty); /* preallocation and hole punch interface */ +enum xfs_alloc_file_space_mode { + XFS_ALLOC_FILE_SPACE_PREALLOC, +}; + int xfs_alloc_file_space(struct xfs_inode *ip, xfs_off_t offset, - xfs_off_t len); + xfs_off_t len, enum xfs_alloc_file_space_mode mode); int xfs_free_file_space(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t len, struct xfs_zone_alloc_ctx *ac); int xfs_collapse_file_space(struct xfs_inode *, xfs_off_t offset, diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 845a97c9b063..e90ea6ebdc8e 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1406,7 +1406,8 @@ xfs_falloc_zero_range( len = round_up(offset + len, blksize) - round_down(offset, blksize); offset = round_down(offset, blksize); - error = xfs_alloc_file_space(ip, offset, len); + error = xfs_alloc_file_space(ip, offset, len, + XFS_ALLOC_FILE_SPACE_PREALLOC); } if (error) return error; @@ -1432,7 +1433,8 @@ xfs_falloc_unshare_range( if (error) return error; - error = xfs_alloc_file_space(XFS_I(inode), offset, len); + error = xfs_alloc_file_space(XFS_I(inode), offset, len, + XFS_ALLOC_FILE_SPACE_PREALLOC); if (error) return error; return xfs_falloc_setsize(file, new_size); @@ -1460,7 +1462,8 @@ xfs_falloc_allocate_range( if (error) return error; - error = xfs_alloc_file_space(XFS_I(inode), offset, len); + error = xfs_alloc_file_space(XFS_I(inode), offset, len, + XFS_ALLOC_FILE_SPACE_PREALLOC); if (error) return error; return xfs_falloc_setsize(file, new_size); From b16b63a47902997cb062ab6d94fa43645e00071b Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Mon, 6 Jul 2026 10:41:17 +0200 Subject: [PATCH 02/23] xfs: add support for FALLOC_FL_WRITE_ZEROES If the underlying block device supports the unmap write zeroes operation, this flag allows users to quickly preallocate a file with written extents that contain zeroes. This is beneficial for subsequent overwrites as it prevents the need for unwritten-to-written extent conversions, thereby significantly reducing metadata updates and journal I/O overhead, improving overwrite performance. Punch the range first so it becomes a hole, update the size via xfs_falloc_setsize() while it is still a hole (so its xfs_zero_range() skips it and avoids rezeroing), then convert it to written zeroed extents. A crash between the size update and the conversion is safe, as a hole within i_size reads back as zeroes. Co-developed-by: Lukas Herbolt Signed-off-by: Lukas Herbolt Signed-off-by: Pankaj Raghav Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_bmap_util.c | 19 ++++++++-- fs/xfs/xfs_bmap_util.h | 1 + fs/xfs/xfs_file.c | 83 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 48db310b118c..268d159339d0 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -643,11 +643,18 @@ xfs_free_eofblocks( } /* - * Allocate space for a file according to @mode: + * Allocate space or convert extents for a file according to @mode: * * XFS_ALLOC_FILE_SPACE_PREALLOC: * Preallocate unwritten extents over holes across the range and mark the inode * as preallocated. + * + * XFS_ALLOC_FILE_SPACE_WRITE_ZEROES: + * Allocate written extents over holes and convert unwritten extents in the + * range to written extents, initialising both to contain zeroes. + * + * This function does not update the file size; callers that extend the file + * are responsible for updating it once the extents are allocated. */ int xfs_alloc_file_space( @@ -688,6 +695,10 @@ xfs_alloc_file_space( bmapi_flags = XFS_BMAPI_PREALLOC; nr_exts = XFS_IEXT_ADD_NOSPLIT_CNT; break; + case XFS_ALLOC_FILE_SPACE_WRITE_ZEROES: + bmapi_flags = XFS_BMAPI_CONVERT | XFS_BMAPI_ZERO; + nr_exts = XFS_IEXT_WRITE_UNWRITTEN_CNT; + break; default: return -EINVAL; } @@ -776,8 +787,10 @@ xfs_alloc_file_space( allocatesize_fsb -= imapp->br_blockcount; } - ip->i_diflags |= XFS_DIFLAG_PREALLOC; - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + if (mode == XFS_ALLOC_FILE_SPACE_PREALLOC) { + ip->i_diflags |= XFS_DIFLAG_PREALLOC; + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + } error = xfs_trans_commit(tp); xfs_iunlock(ip, XFS_ILOCK_EXCL); diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index 929b39d9d88a..c7b48b2602f2 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -57,6 +57,7 @@ int xfs_bmap_last_extent(struct xfs_trans *tp, struct xfs_inode *ip, /* preallocation and hole punch interface */ enum xfs_alloc_file_space_mode { XFS_ALLOC_FILE_SPACE_PREALLOC, + XFS_ALLOC_FILE_SPACE_WRITE_ZEROES, }; int xfs_alloc_file_space(struct xfs_inode *ip, xfs_off_t offset, diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index e90ea6ebdc8e..0ade13b31335 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1368,6 +1368,84 @@ xfs_falloc_force_zero( return XFS_TEST_ERROR(ip->i_mount, XFS_ERRTAG_FORCE_ZERO_RANGE); } +static int +xfs_falloc_write_zeroes( + struct file *file, + int mode, + loff_t offset, + loff_t len, + struct xfs_zone_alloc_ctx *ac) +{ + struct inode *inode = file_inode(file); + struct xfs_inode *ip = XFS_I(inode); + loff_t new_size = 0; + int error; + + /* + * XXX: There is an issue with bigrtalloc inodes where there can be blocks + * that are written after the EOF block. This breaks the promise of no + * written blocks past EOF. Return EOPNOTSUPP until it is fixed. + */ + if (xfs_is_always_cow_inode(ip) || xfs_inode_has_bigrtalloc(ip) || + !bdev_write_zeroes_unmap_sectors(xfs_inode_buftarg(ip)->bt_bdev)) + return -EOPNOTSUPP; + + error = xfs_falloc_newsize(file, mode, offset, len, &new_size); + if (error) + return error; + + /* + * + * |----------|----------|----------|----------|----------| + * ^ ^ ^ ^ ^ ^ + * | | | | | | + * | offset | | end | + * | | | | + * offset_rd offset_ru end_rd end_ru + * + * xfs_free_file_space() punches the aligned interior offset_ru -> end_rd + * to holes and byte-zeroes the in-range parts of the partial edge blocks, + * offset -> offset_ru and end_rd -> end. xfs_zero_range() only touches + * already-written blocks here; it skips holes and unwritten extents, so + * unallocated/unwritten edge blocks are left for the allocation below. + */ + error = xfs_free_file_space(ip, offset, len, ac); + if (error) + return error; + + /* + * Publish the new size while the punched range is still a hole, then + * fill it with written zeroes. Like the other fallocate modes we use + * xfs_falloc_setsize(), but it must run *before* we convert the range + * to written extents: xfs_setattr_size() zeroes [old EOF, new size) via + * xfs_zero_range(), which skips holes, so there is nothing to re-zero. + * It will also writeback partial EOF block before the on-disk size is + * logged. + * Note: extending the size before allocating means a failure below + * leaves the file larger with unallocated holes in the new range. + * That is safe as holes within i_size read back as zeroes and expose + * no stale data while the error is propagated to the caller. + */ + error = xfs_falloc_setsize(file, new_size); + if (error) + return error; + + /* + * Allocate written, zeroed extents across the range. xfs_alloc_file_space() + * rounds outward to block granularity: + * - holes (the punched interior and any unallocated edge block) are + * allocated and zeroed; + * - unwritten extents (including unwritten edge blocks) are converted to + * written and zeroed; + * - Already written edge blocks are skipped. The out-of-range bytes of + * a written edge block keep their data (offset_rd -> offset and + * end -> end_rd); their in-range bytes (offset -> offset_ru and + * end_ru -> end were already zeroed by xfs_free_file_space(). + */ + return xfs_alloc_file_space(ip, offset, len, + XFS_ALLOC_FILE_SPACE_WRITE_ZEROES); +} + /* * Punch a hole and prealloc the range. We use a hole punch rather than * unwritten extent conversion for two reasons: @@ -1473,7 +1551,7 @@ xfs_falloc_allocate_range( (FALLOC_FL_ALLOCATE_RANGE | FALLOC_FL_KEEP_SIZE | \ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE | \ FALLOC_FL_ZERO_RANGE | FALLOC_FL_INSERT_RANGE | \ - FALLOC_FL_UNSHARE_RANGE) + FALLOC_FL_UNSHARE_RANGE | FALLOC_FL_WRITE_ZEROES) STATIC long __xfs_file_fallocate( @@ -1525,6 +1603,9 @@ __xfs_file_fallocate( case FALLOC_FL_ALLOCATE_RANGE: error = xfs_falloc_allocate_range(file, mode, offset, len); break; + case FALLOC_FL_WRITE_ZEROES: + error = xfs_falloc_write_zeroes(file, mode, offset, len, ac); + break; default: error = -EOPNOTSUPP; break; From daf43402da0d3a66eda26fefe3473799165bd7b2 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 13 Jul 2026 14:42:47 +0200 Subject: [PATCH 03/23] xfs: add xfs_metadir_create_file helper Factor the metadata inode create/commit/cleanup lifecycle out of xfs_metadir_mkdir into a reusable helper that takes an optional callback to initialize the new inode, and convert xfs_metadir_mkdir to it. Signed-off-by: Johannes Thumshirn Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metadir.c | 78 ++++++++++++++++++++++--------------- fs/xfs/libxfs/xfs_metadir.h | 6 +++ 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metadir.c b/fs/xfs/libxfs/xfs_metadir.c index 74c4596ee4cf..0d6a153bc9e9 100644 --- a/fs/xfs/libxfs/xfs_metadir.c +++ b/fs/xfs/libxfs/xfs_metadir.c @@ -438,6 +438,52 @@ xfs_metadir_cancel( xfs_metadir_teardown(upd, error); } +int +xfs_metadir_create_file( + struct xfs_metadir_update *upd, + umode_t mode, + xfs_metadir_createfn create, + void *priv, + struct xfs_inode **ipp) +{ + int error; + + if (xfs_is_shutdown(upd->dp->i_mount)) + return -EIO; + + error = xfs_metadir_start_create(upd); + if (error) + return error; + + error = xfs_metadir_create(upd, mode); + if (error) + goto out_cancel; + + if (create) { + error = create(upd, priv); + if (error) + goto out_cancel; + } + + error = xfs_metadir_commit(upd); + if (error) + goto out_irele; + + xfs_finish_inode_setup(upd->ip); + *ipp = upd->ip; + return 0; + +out_cancel: + xfs_metadir_cancel(upd, error); +out_irele: + /* Have to finish setting up the inode to ensure it's deleted. */ + if (upd->ip) { + xfs_finish_inode_setup(upd->ip); + xfs_irele(upd->ip); + } + return error; +} + /* Create a metadata for the last component of the path. */ int xfs_metadir_mkdir( @@ -450,36 +496,6 @@ xfs_metadir_mkdir( .path = path, .metafile_type = XFS_METAFILE_DIR, }; - int error; - if (xfs_is_shutdown(dp->i_mount)) - return -EIO; - - /* Allocate a transaction to create the last directory. */ - error = xfs_metadir_start_create(&upd); - if (error) - return error; - - /* Create the subdirectory and take our reference. */ - error = xfs_metadir_create(&upd, S_IFDIR); - if (error) - goto out_cancel; - - error = xfs_metadir_commit(&upd); - if (error) - goto out_irele; - - xfs_finish_inode_setup(upd.ip); - *ipp = upd.ip; - return 0; - -out_cancel: - xfs_metadir_cancel(&upd, error); -out_irele: - /* Have to finish setting up the inode to ensure it's deleted. */ - if (upd.ip) { - xfs_finish_inode_setup(upd.ip); - xfs_irele(upd.ip); - } - return error; + return xfs_metadir_create_file(&upd, S_IFDIR, NULL, NULL, ipp); } diff --git a/fs/xfs/libxfs/xfs_metadir.h b/fs/xfs/libxfs/xfs_metadir.h index bfecac7d3d14..a795a2d0e3fe 100644 --- a/fs/xfs/libxfs/xfs_metadir.h +++ b/fs/xfs/libxfs/xfs_metadir.h @@ -35,6 +35,12 @@ int xfs_metadir_load(struct xfs_trans *tp, struct xfs_inode *dp, int xfs_metadir_start_create(struct xfs_metadir_update *upd); int xfs_metadir_create(struct xfs_metadir_update *upd, umode_t mode); +typedef int (*xfs_metadir_createfn)(struct xfs_metadir_update *upd, void *priv); + +int xfs_metadir_create_file(struct xfs_metadir_update *upd, umode_t mode, + xfs_metadir_createfn create, void *priv, + struct xfs_inode **ipp); + int xfs_metadir_start_link(struct xfs_metadir_update *upd); int xfs_metadir_link(struct xfs_metadir_update *upd); From e6ecb1a98d14b1e9ef0cd8f3340d30b5f491d2bc Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 13 Jul 2026 14:42:48 +0200 Subject: [PATCH 04/23] xfs: create quota metadir inodes using xfs_metadir_create_file Now that we have xfs_metadir_create_file() use it in xfs_dqinode_metadir_create(). Reviewed-by: Christoph Hellwig Signed-off-by: Johannes Thumshirn Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_dquot_buf.c | 39 ++++++++++------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c index bbada0d3cc08..f960474bed3d 100644 --- a/fs/xfs/libxfs/xfs_dquot_buf.c +++ b/fs/xfs/libxfs/xfs_dquot_buf.c @@ -416,6 +416,15 @@ xfs_dqinode_load( return 0; } +static int +xfs_dqinode_init( + struct xfs_metadir_update *upd, + void *priv) +{ + xfs_trans_log_inode(upd->tp, upd->ip, XFS_ILOG_CORE); + return 0; +} + /* Create a metadata directory quota inode. */ int xfs_dqinode_metadir_create( @@ -428,35 +437,9 @@ xfs_dqinode_metadir_create( .metafile_type = xfs_dqinode_metafile_type(type), .path = xfs_dqinode_path(type), }; - int error; - error = xfs_metadir_start_create(&upd); - if (error) - return error; - - error = xfs_metadir_create(&upd, S_IFREG); - if (error) - goto out_cancel; - - xfs_trans_log_inode(upd.tp, upd.ip, XFS_ILOG_CORE); - - error = xfs_metadir_commit(&upd); - if (error) - goto out_irele; - - xfs_finish_inode_setup(upd.ip); - *ipp = upd.ip; - return 0; - -out_cancel: - xfs_metadir_cancel(&upd, error); -out_irele: - /* Have to finish setting up the inode to ensure it's deleted. */ - if (upd.ip) { - xfs_finish_inode_setup(upd.ip); - xfs_irele(upd.ip); - } - return error; + return xfs_metadir_create_file(&upd, S_IFREG, xfs_dqinode_init, NULL, + ipp); } #ifndef __KERNEL__ From de64b150a70cbf4e9499921d6cb7dc52fbeb55f7 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 13 Jul 2026 14:42:49 +0200 Subject: [PATCH 05/23] xfs: create rtgroup metadir inodes using xfs_metadir_create_file Now that we have xfs_metadir_create_file() use it in xfs_rtginode_create(). Reviewed-by: Christoph Hellwig Signed-off-by: Johannes Thumshirn Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtgroup.c | 58 +++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtgroup.c b/fs/xfs/libxfs/xfs_rtgroup.c index c85d50953218..fe7222bbe449 100644 --- a/fs/xfs/libxfs/xfs_rtgroup.c +++ b/fs/xfs/libxfs/xfs_rtgroup.c @@ -517,6 +517,25 @@ xfs_rtginode_irele( *ipp = NULL; } +struct xfs_rtginode_create { + struct xfs_rtgroup *rtg; + enum xfs_rtg_inodes type; + bool init; +}; + +static int +xfs_rtginode_init( + struct xfs_metadir_update *upd, + void *priv) +{ + struct xfs_rtginode_create *rc = priv; + const struct xfs_rtginode_ops *ops = &xfs_rtginode_ops[rc->type]; + + xfs_rtginode_lockdep_setup(upd->ip, rtg_rgno(rc->rtg), rc->type); + upd->ip->i_projid = rtg_rgno(rc->rtg); + return ops->create(rc->rtg, upd->ip, upd->tp, rc->init); +} + /* Add a metadata inode for a realtime rmap btree. */ int xfs_rtginode_create( @@ -526,6 +545,11 @@ xfs_rtginode_create( { const struct xfs_rtginode_ops *ops = &xfs_rtginode_ops[type]; struct xfs_mount *mp = rtg_mount(rtg); + struct xfs_rtginode_create rc = { + .rtg = rtg, + .type = type, + .init = init, + }; struct xfs_metadir_update upd = { .dp = mp->m_rtdirip, .metafile_type = ops->metafile_type, @@ -544,38 +568,8 @@ xfs_rtginode_create( if (!upd.path) return -ENOMEM; - error = xfs_metadir_start_create(&upd); - if (error) - goto out_path; - - error = xfs_metadir_create(&upd, S_IFREG); - if (error) - goto out_cancel; - - xfs_rtginode_lockdep_setup(upd.ip, rtg_rgno(rtg), type); - - upd.ip->i_projid = rtg_rgno(rtg); - error = ops->create(rtg, upd.ip, upd.tp, init); - if (error) - goto out_cancel; - - error = xfs_metadir_commit(&upd); - if (error) - goto out_path; - - kfree(upd.path); - xfs_finish_inode_setup(upd.ip); - rtg->rtg_inodes[type] = upd.ip; - return 0; - -out_cancel: - xfs_metadir_cancel(&upd, error); - /* Have to finish setting up the inode to ensure it's deleted. */ - if (upd.ip) { - xfs_finish_inode_setup(upd.ip); - xfs_irele(upd.ip); - } -out_path: + error = xfs_metadir_create_file(&upd, S_IFREG, xfs_rtginode_init, &rc, + &rtg->rtg_inodes[type]); kfree(upd.path); return error; } From c6c54d05b129c4e29f2fdc6d356b5be10765231d Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Mon, 13 Jul 2026 14:42:50 +0200 Subject: [PATCH 06/23] xfs: mark internal metadir file creation helpers static Now that there is xfs_metadir_create_file() mark xfs_metadir_start_create(), xfs_metadir_create() and xfs_metadir_cancel() as static and remove them from xfs_metadir.h. Signed-off-by: Johannes Thumshirn Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metadir.c | 6 +++--- fs/xfs/libxfs/xfs_metadir.h | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metadir.c b/fs/xfs/libxfs/xfs_metadir.c index 0d6a153bc9e9..7c6b086b73db 100644 --- a/fs/xfs/libxfs/xfs_metadir.c +++ b/fs/xfs/libxfs/xfs_metadir.c @@ -182,7 +182,7 @@ xfs_metadir_teardown( * Begin the process of creating a metadata file by allocating transactions * and taking whatever resources we're going to need. */ -int +static int xfs_metadir_start_create( struct xfs_metadir_update *upd) { @@ -236,7 +236,7 @@ xfs_metadir_start_create( * a negative error code. If an inode is passed back, the caller must finish * setting up the inode before releasing it. */ -int +static int xfs_metadir_create( struct xfs_metadir_update *upd, umode_t mode) @@ -425,7 +425,7 @@ xfs_metadir_commit( } /* Cancel a metadir update and unlock/drop all resources. */ -void +static void xfs_metadir_cancel( struct xfs_metadir_update *upd, int error) diff --git a/fs/xfs/libxfs/xfs_metadir.h b/fs/xfs/libxfs/xfs_metadir.h index a795a2d0e3fe..e434b9d1c932 100644 --- a/fs/xfs/libxfs/xfs_metadir.h +++ b/fs/xfs/libxfs/xfs_metadir.h @@ -32,9 +32,6 @@ int xfs_metadir_load(struct xfs_trans *tp, struct xfs_inode *dp, const char *path, enum xfs_metafile_type metafile_type, struct xfs_inode **ipp); -int xfs_metadir_start_create(struct xfs_metadir_update *upd); -int xfs_metadir_create(struct xfs_metadir_update *upd, umode_t mode); - typedef int (*xfs_metadir_createfn)(struct xfs_metadir_update *upd, void *priv); int xfs_metadir_create_file(struct xfs_metadir_update *upd, umode_t mode, @@ -45,7 +42,6 @@ int xfs_metadir_start_link(struct xfs_metadir_update *upd); int xfs_metadir_link(struct xfs_metadir_update *upd); int xfs_metadir_commit(struct xfs_metadir_update *upd); -void xfs_metadir_cancel(struct xfs_metadir_update *upd, int error); int xfs_metadir_mkdir(struct xfs_inode *dp, const char *path, struct xfs_inode **ipp); From 56aa9ef3c413cb13a373226f91a19358cd9f1266 Mon Sep 17 00:00:00 2001 From: Cihan Karadag Date: Thu, 16 Jul 2026 15:48:12 -0600 Subject: [PATCH 07/23] xfs: use kmalloc_objs() instead of kmalloc() in xfs_da_grow_inode_int Convert open-coded kmalloc() multiplication to the modern kmalloc_objs() interface to improve type safety and prevent potential integer overflows. No functional changes are intended. Signed-off-by: Cihan Karadag Reviewed-by: Carlos Maiolino Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 9debb95d86fa..f190c088591b 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -2354,8 +2354,7 @@ xfs_da_grow_inode_int( * If we didn't get it and the block might work if fragmented, * try without the CONTIG flag. Loop until we get it all. */ - mapp = kmalloc(sizeof(*mapp) * count, - GFP_KERNEL | __GFP_NOFAIL); + mapp = kmalloc_objs(*mapp, count, GFP_KERNEL | __GFP_NOFAIL); for (b = *bno, mapi = 0; b < *bno + count; ) { c = (int)(*bno + count - b); nmap = min(XFS_BMAP_MAX_NMAP, c); From 45fd506c334ceb79ea3b40986b2c5ebb7802b385 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:54 +0200 Subject: [PATCH 08/23] xfs: don't get a pag reference in xfs_buf_get_map As of commit 497560b9ef42 ("xfs: switch (back) to a per-buftarg buffer hash"), buffer lookups don't require the perag structure. Stop looking it up in xfs_buf_get_map, and instead only find it when allocating a new buffer. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 44 ++++++++++---------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 48d7dfd3e15f..74db8a10fef1 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -469,7 +469,6 @@ xfs_buf_lookup( static int xfs_buf_find_insert( struct xfs_buftarg *btp, - struct xfs_perag *pag, struct xfs_buf_map *cmap, struct xfs_buf_map *map, int nmaps, @@ -482,10 +481,13 @@ xfs_buf_find_insert( error = xfs_buf_alloc(btp, map, nmaps, flags, &new_bp); if (error) - goto out_drop_pag; + return error; /* The new buffer keeps the perag reference until it is freed. */ - new_bp->b_pag = pag; + if (!xfs_buftarg_is_mem(btp)) { + new_bp->b_pag = xfs_perag_get(btp->bt_mount, + xfs_daddr_to_agno(btp->bt_mount, cmap->bm_bn)); + } retry: rcu_read_lock(); @@ -520,25 +522,12 @@ xfs_buf_find_insert( return 0; out_free_buf: + if (new_bp->b_pag) + xfs_perag_put(new_bp->b_pag); xfs_buf_free(new_bp); -out_drop_pag: - if (pag) - xfs_perag_put(pag); return error; } -static inline struct xfs_perag * -xfs_buftarg_get_pag( - struct xfs_buftarg *btp, - const struct xfs_buf_map *map) -{ - struct xfs_mount *mp = btp->bt_mount; - - if (xfs_buftarg_is_mem(btp)) - return NULL; - return xfs_perag_get(mp, xfs_daddr_to_agno(mp, map->bm_bn)); -} - /* * Assembles a buffer covering the specified range. The code is optimised for * cache hits, as metadata intensive workloads will see 3 orders of magnitude @@ -552,7 +541,6 @@ xfs_buf_get_map( xfs_buf_flags_t flags, struct xfs_buf **bpp) { - struct xfs_perag *pag; struct xfs_buf *bp = NULL; struct xfs_buf_map cmap = { .bm_bn = map[0].bm_bn }; int error; @@ -567,28 +555,21 @@ xfs_buf_get_map( if (error) return error; - pag = xfs_buftarg_get_pag(btp, &cmap); - error = xfs_buf_lookup(btp, &cmap, flags, &bp); if (error && error != -ENOENT) - goto out_put_perag; + return error; /* cache hits always outnumber misses by at least 10:1 */ if (unlikely(!bp)) { XFS_STATS_INC(btp->bt_mount, xb_miss_locked); if (flags & XBF_INCORE) - goto out_put_perag; - - /* xfs_buf_find_insert() consumes the perag reference. */ - error = xfs_buf_find_insert(btp, pag, &cmap, map, nmaps, - flags, &bp); + return 0; + error = xfs_buf_find_insert(btp, &cmap, map, nmaps, flags, &bp); if (error) return error; } else { XFS_STATS_INC(btp->bt_mount, xb_get_locked); - if (pag) - xfs_perag_put(pag); } /* @@ -602,11 +583,6 @@ xfs_buf_get_map( trace_xfs_buf_get(bp, flags, _RET_IP_); *bpp = bp; return 0; - -out_put_perag: - if (pag) - xfs_perag_put(pag); - return error; } int From 568c8798a3d0afabc133d7b34d37155daa95d1f2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:55 +0200 Subject: [PATCH 09/23] xfs: consolidate buffer locking in xfs_buf_get_map Consolidate the code to lock the buffer based on the passed in flags into xfs_buf_get_map instead of having two different sites for buffer lookup vs insertation. This requires initializing b_lock to unlocked on allocation and doing an atomic for locking it for newly allocated buffers, but greatly simplifies the logic. Signed-off-by: Christoph Hellwig Reviewed-by: Brian Foster Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 62 ++++++++++++++++------------------------------ fs/xfs/xfs_trace.h | 2 +- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 74db8a10fef1..8d252b21579e 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -282,15 +282,8 @@ xfs_buf_alloc( * specifically set by later operations on the buffer. */ flags &= ~(XBF_TRYLOCK | XBF_ASYNC | XBF_READ_AHEAD); - - /* - * A new buffer is held and locked by the owner. This ensures that the - * buffer is owned by the caller and racing RCU lookups right after - * inserting into the hash table are safe (and will have to wait for - * the unlock to do anything non-trivial). - */ lockref_init(&bp->b_lockref); - sema_init(&bp->b_sema, 0); /* held, no waiters */ + sema_init(&bp->b_sema, 1); /* unlocked */ atomic_set(&bp->b_lru_ref, 1); init_completion(&bp->b_iowait); INIT_LIST_HEAD(&bp->b_lru); @@ -433,33 +426,25 @@ xfs_buf_find_lock( return 0; } -static inline int +static inline struct xfs_buf * xfs_buf_lookup( struct xfs_buftarg *btp, - struct xfs_buf_map *map, - xfs_buf_flags_t flags, - struct xfs_buf **bpp) + struct xfs_buf_map *map) { struct xfs_buf *bp; - int error; rcu_read_lock(); bp = rhashtable_lookup(&btp->bt_hash, map, xfs_buf_hash_params); if (!bp || !lockref_get_not_dead(&bp->b_lockref)) { rcu_read_unlock(); - return -ENOENT; + XFS_STATS_INC(btp->bt_mount, xb_miss_locked); + return NULL; } rcu_read_unlock(); - error = xfs_buf_find_lock(bp, flags); - if (error) { - xfs_buf_rele(bp); - return error; - } - - trace_xfs_buf_find(bp, flags, _RET_IP_); - *bpp = bp; - return 0; + trace_xfs_buf_find(bp, _RET_IP_); + XFS_STATS_INC(btp->bt_mount, xb_get_locked); + return bp; } /* @@ -509,11 +494,7 @@ xfs_buf_find_insert( goto retry; } rcu_read_unlock(); - error = xfs_buf_find_lock(bp, flags); - if (error) - xfs_buf_rele(bp); - else - *bpp = bp; + *bpp = bp; goto out_free_buf; } rcu_read_unlock(); @@ -555,21 +536,20 @@ xfs_buf_get_map( if (error) return error; - error = xfs_buf_lookup(btp, &cmap, flags, &bp); - if (error && error != -ENOENT) - return error; - /* cache hits always outnumber misses by at least 10:1 */ + bp = xfs_buf_lookup(btp, &cmap); if (unlikely(!bp)) { - XFS_STATS_INC(btp->bt_mount, xb_miss_locked); - if (flags & XBF_INCORE) - return 0; + return -ENOENT; error = xfs_buf_find_insert(btp, &cmap, map, nmaps, flags, &bp); if (error) return error; - } else { - XFS_STATS_INC(btp->bt_mount, xb_get_locked); + } + + error = xfs_buf_find_lock(bp, flags); + if (error) { + xfs_buf_rele(bp); + return error; } /* @@ -794,9 +774,11 @@ xfs_buf_get_uncached( DEFINE_SINGLE_BUF_MAP(map, XFS_BUF_DADDR_NULL, numblks); error = xfs_buf_alloc(target, &map, 1, 0, bpp); - if (!error) - trace_xfs_buf_get_uncached(*bpp, _RET_IP_); - return error; + if (error) + return error; + xfs_buf_lock(*bpp); + trace_xfs_buf_get_uncached(*bpp, _RET_IP_); + return 0; } /* diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index aeb89ac53bf1..f333c938fbd9 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -792,6 +792,7 @@ DEFINE_BUF_EVENT(xfs_buf_backing_folio); DEFINE_BUF_EVENT(xfs_buf_backing_kmem); DEFINE_BUF_EVENT(xfs_buf_backing_vmalloc); DEFINE_BUF_EVENT(xfs_buf_backing_fallback); +DEFINE_BUF_EVENT(xfs_buf_find); /* not really buffer traces, but the buf provides useful information */ DEFINE_BUF_EVENT(xfs_btree_corrupt); @@ -837,7 +838,6 @@ DECLARE_EVENT_CLASS(xfs_buf_flags_class, DEFINE_EVENT(xfs_buf_flags_class, name, \ TP_PROTO(struct xfs_buf *bp, unsigned flags, unsigned long caller_ip), \ TP_ARGS(bp, flags, caller_ip)) -DEFINE_BUF_FLAGS_EVENT(xfs_buf_find); DEFINE_BUF_FLAGS_EVENT(xfs_buf_get); DEFINE_BUF_FLAGS_EVENT(xfs_buf_read); DEFINE_BUF_FLAGS_EVENT(xfs_buf_readahead); From b6ba780af010f0d1c5bfb970fe692a02559de473 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:56 +0200 Subject: [PATCH 10/23] xfs: split out a lower-level xfs_buf_get_map helper from xfs_find_get_buf xfs_buf_get_map is currently reused to implement xfs_buf_read_map and xfs_buf_readahead_map. This causes double accounting of buf_get stat and leads to some ugly overload of the flags. Split out a slightly lower-level xfs_find_get_buf helper and use that to implement xfs_buf_get_map, xfs_buf_read_map and xfs_buf_readahead_map. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 8d252b21579e..f56bd8b0a998 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -514,8 +514,8 @@ xfs_buf_find_insert( * cache hits, as metadata intensive workloads will see 3 orders of magnitude * more hits than misses. */ -int -xfs_buf_get_map( +static int +xfs_find_get_buf( struct xfs_buftarg *btp, struct xfs_buf_map *map, int nmaps, @@ -552,19 +552,36 @@ xfs_buf_get_map( return error; } - /* - * Clear b_error if this is a lookup from a caller that doesn't expect - * valid data to be found in the buffer. - */ - if (!(flags & XBF_READ)) - xfs_buf_ioerror(bp, 0); - - XFS_STATS_INC(btp->bt_mount, xb_get); - trace_xfs_buf_get(bp, flags, _RET_IP_); *bpp = bp; return 0; } +int +xfs_buf_get_map( + struct xfs_buftarg *btp, + struct xfs_buf_map *map, + int nmaps, + xfs_buf_flags_t flags, + struct xfs_buf **bpp) +{ + int error; + + ASSERT(!(flags & ~(XBF_TRYLOCK | XBF_INCORE | XBF_LIVESCAN))); + ASSERT(!(flags & XBF_LIVESCAN) || (flags & XBF_INCORE)); + + /* + * Zero the buffer and clear b_error as xfs_buf_get_map callers don't + * expect valid data to be found in the buffer. + */ + error = xfs_find_get_buf(btp, map, nmaps, flags, bpp); + if (error) + return error; + XFS_STATS_INC(btp->bt_mount, xb_get); + trace_xfs_buf_get(*bpp, flags, _RET_IP_); + xfs_buf_ioerror(*bpp, 0); + return 0; +} + int _xfs_buf_read( struct xfs_buf *bp) @@ -625,12 +642,12 @@ xfs_buf_read_map( struct xfs_buf *bp; int error; - ASSERT(!(flags & (XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD))); + ASSERT(!(flags & ~XBF_TRYLOCK)); flags |= XBF_READ; *bpp = NULL; - error = xfs_buf_get_map(target, map, nmaps, flags, &bp); + error = xfs_find_get_buf(target, map, nmaps, flags, &bp); if (error) return error; @@ -706,7 +723,7 @@ xfs_buf_readahead_map( if (xfs_buftarg_is_mem(target)) return; - if (xfs_buf_get_map(target, map, nmaps, flags | XBF_TRYLOCK, &bp)) + if (xfs_find_get_buf(target, map, nmaps, flags | XBF_TRYLOCK, &bp)) return; trace_xfs_buf_readahead(bp, 0, _RET_IP_); From 7a4eae80b5b3db8d68961af3707fd56f2220cc29 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:57 +0200 Subject: [PATCH 11/23] xfs: remove spurious XBF_DONE clearing on readahead validation failure Both callers of ->verify_read already do this, so don't duplicate the flag manipulation. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_dquot_buf.c | 8 +++----- fs/xfs/libxfs/xfs_inode_buf.c | 15 ++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c index f960474bed3d..77954d1d924c 100644 --- a/fs/xfs/libxfs/xfs_dquot_buf.c +++ b/fs/xfs/libxfs/xfs_dquot_buf.c @@ -252,8 +252,8 @@ xfs_dquot_buf_read_verify( /* * readahead errors are silent and simply leave the buffer as !done so a real * read will then be run with the xfs_dquot_buf_ops verifier. See - * xfs_inode_buf_verify() for why we use EIO and ~XBF_DONE here rather than - * reporting the failure. + * xfs_inode_buf_verify() for why we use EIO here rather than reporting the + * failure. */ static void xfs_dquot_buf_readahead_verify( @@ -262,10 +262,8 @@ xfs_dquot_buf_readahead_verify( struct xfs_mount *mp = bp->b_mount; if (!xfs_dquot_buf_verify_crc(mp, bp, true) || - xfs_dquot_buf_verify(mp, bp, true) != NULL) { + xfs_dquot_buf_verify(mp, bp, true) != NULL) xfs_buf_ioerror(bp, -EIO); - bp->b_flags &= ~XBF_DONE; - } } /* diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index 336ef843f2fe..e4c3f7b24e95 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -29,12 +29,14 @@ * has not had the inode cores stamped into it. Hence for readahead, the buffer * may be potentially invalid. * - * If the readahead buffer is invalid, we need to mark it with an error and - * clear the DONE status of the buffer so that a followup read will re-read it - * from disk. We don't report the error otherwise to avoid warnings during log - * recovery and we don't get unnecessary panics on debug kernels. We use EIO here - * because all we want to do is say readahead failed; there is no-one to report - * the error to, so this will distinguish it from a non-ra verifier failure. + * If the readahead buffer is invalid, we need to mark it with an error so that a + * followup read will re-read it from disk. + * + * We don't report the error otherwise to avoid warnings during log recovery and + * we don't get unnecessary panics on debug kernels. Use EIO here because all + * we want to do is say readahead failed; there is no-one to report the error + * to, so this will distinguish it from a non-ra verifier failure. + * * Changes to this readahead error behaviour also need to be reflected in * xfs_dquot_buf_readahead_verify(). */ @@ -64,7 +66,6 @@ xfs_inode_buf_verify( if (unlikely(!di_ok || XFS_TEST_ERROR(mp, XFS_ERRTAG_ITOBP_INOTOBP))) { if (readahead) { - bp->b_flags &= ~XBF_DONE; xfs_buf_ioerror(bp, -EIO); return; } From d80fe85bb2fc551e56758606f764e57516b7f9a3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:58 +0200 Subject: [PATCH 12/23] xfs: remove _XBF_LOGRECOVERY Adding _XBF_LOGRECOVERY to every buffer write from log recovery is error prone. Instead key off the behavior on log recovery being active with indirecting that through a flag. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 5 ++--- fs/xfs/xfs_buf.h | 4 ---- fs/xfs/xfs_buf_item.c | 10 ++++++---- fs/xfs/xfs_buf_item_recover.c | 3 --- fs/xfs/xfs_dquot_item_recover.c | 1 - fs/xfs/xfs_inode_item_recover.c | 1 - fs/xfs/xfs_log_recover.c | 5 ++--- 7 files changed, 10 insertions(+), 19 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index f56bd8b0a998..f922f1081ce3 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -1017,7 +1017,7 @@ xfs_buf_ioend_handle_error( * We're not going to bother about retrying this during recovery. * One strike! */ - if (bp->b_flags & _XBF_LOGRECOVERY) { + if (mp->m_log && xlog_in_recovery(mp->m_log)) { xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); return false; } @@ -1124,8 +1124,7 @@ xfs_buf_ioend( bp->b_iodone(bp); } - bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD | - _XBF_LOGRECOVERY); + bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD); if (async) xfs_buf_relse(bp); } diff --git a/fs/xfs/xfs_buf.h b/fs/xfs/xfs_buf.h index 79cc9c3f0254..e440f97cf3e1 100644 --- a/fs/xfs/xfs_buf.h +++ b/fs/xfs/xfs_buf.h @@ -34,9 +34,6 @@ struct xfs_buf; #define XBF_STALE (1u << 6) /* buffer has been staled, do not find it */ #define XBF_WRITE_FAIL (1u << 7) /* async writes have failed on this buffer */ -/* buffer type flags for write callbacks */ -#define _XBF_LOGRECOVERY (1u << 18)/* log recovery buffer */ - /* flags used only internally */ #define _XBF_KMEM (1u << 21)/* backed by heap memory */ #define _XBF_DELWRI_Q (1u << 22)/* buffer on a delwri queue */ @@ -61,7 +58,6 @@ typedef unsigned int xfs_buf_flags_t; { XBF_DONE, "DONE" }, \ { XBF_STALE, "STALE" }, \ { XBF_WRITE_FAIL, "WRITE_FAIL" }, \ - { _XBF_LOGRECOVERY, "LOG_RECOVERY" }, \ { _XBF_KMEM, "KMEM" }, \ { _XBF_DELWRI_Q, "DELWRI_Q" }, \ /* The following interface flags should never be set */ \ diff --git a/fs/xfs/xfs_buf_item.c b/fs/xfs/xfs_buf_item.c index 1f055cd6732e..1a4ef34af8d5 100644 --- a/fs/xfs/xfs_buf_item.c +++ b/fs/xfs/xfs_buf_item.c @@ -1066,6 +1066,8 @@ void xfs_buf_item_done( struct xfs_buf *bp) { + struct xfs_buf_log_item *bip = bp->b_log_item; + /* * If we are forcibly shutting down, this may well be off the AIL * already. That's because we simulate the log-committed callbacks to @@ -1078,8 +1080,8 @@ xfs_buf_item_done( * Note that log recovery writes might have buffer items that are not on * the AIL even when the file system is not shut down. */ - xfs_trans_ail_delete(&bp->b_log_item->bli_item, - (bp->b_flags & _XBF_LOGRECOVERY) ? 0 : - SHUTDOWN_CORRUPT_INCORE); - xfs_buf_item_relse(bp->b_log_item); + xfs_trans_ail_delete(&bip->bli_item, + xlog_in_recovery(bip->bli_item.li_log) ? + 0 : SHUTDOWN_CORRUPT_INCORE); + xfs_buf_item_relse(bip); } diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c index 240deb3f7827..57929f115055 100644 --- a/fs/xfs/xfs_buf_item_recover.c +++ b/fs/xfs/xfs_buf_item_recover.c @@ -448,7 +448,6 @@ xlog_recover_validate_buf_type( if (bp->b_ops) { struct xfs_buf_log_item *bip; - bp->b_flags |= _XBF_LOGRECOVERY; xfs_buf_item_init(bp, mp); bip = bp->b_log_item; bip->bli_item.li_lsn = current_lsn; @@ -1122,7 +1121,6 @@ xlog_recover_buf_commit_pass2( xfs_buf_lock(rtsb_bp); xfs_buf_hold(rtsb_bp); xfs_update_rtsb(rtsb_bp, bp); - rtsb_bp->b_flags |= _XBF_LOGRECOVERY; xfs_buf_delwri_queue(rtsb_bp, buffer_list); xfs_buf_relse(rtsb_bp); } @@ -1164,7 +1162,6 @@ xlog_recover_buf_commit_pass2( error = xfs_bwrite(bp); } else { ASSERT(bp->b_mount == mp); - bp->b_flags |= _XBF_LOGRECOVERY; xfs_buf_delwri_queue(bp, buffer_list); } diff --git a/fs/xfs/xfs_dquot_item_recover.c b/fs/xfs/xfs_dquot_item_recover.c index fe419b28de22..d4dfe1885666 100644 --- a/fs/xfs/xfs_dquot_item_recover.c +++ b/fs/xfs/xfs_dquot_item_recover.c @@ -168,7 +168,6 @@ xlog_recover_dquot_commit_pass2( ASSERT(dq_f->qlf_size == 2); ASSERT(bp->b_mount == mp); - bp->b_flags |= _XBF_LOGRECOVERY; xfs_buf_delwri_queue(bp, buffer_list); out_release: diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recover.c index 169a8fe3bf0a..1d2319ad15c5 100644 --- a/fs/xfs/xfs_inode_item_recover.c +++ b/fs/xfs/xfs_inode_item_recover.c @@ -586,7 +586,6 @@ xlog_recover_inode_commit_pass2( } ASSERT(bp->b_mount == mp); - bp->b_flags |= _XBF_LOGRECOVERY; xfs_buf_delwri_queue(bp, buffer_list); out_release: diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index fdb011e6ef60..e7e49529658b 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -3279,9 +3279,8 @@ xlog_do_recovery_pass( * checkpoints at this start LSN. * * Note: Shutting down the filesystem will result in the - * delwri submission marking all the buffers stale, - * completing them and cleaning up _XBF_LOGRECOVERY - * state without doing any IO. + * delwri submission marking all the buffers stale and + * completing them without doing any IO. */ xlog_force_shutdown(log, SHUTDOWN_LOG_IO_ERROR); } From 9f224de410d7efafc44ce1880aabd0101aa8688f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:50:59 +0200 Subject: [PATCH 13/23] xfs: hide b_flags manipulation from code outside of xfs_buf.c Add helpers for the remaining buffer flags manipulation not done in the core buffer cache code. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_staging.c | 7 +++---- fs/xfs/libxfs/xfs_ialloc.c | 2 +- fs/xfs/xfs_buf.c | 16 ++++++++++++++++ fs/xfs/xfs_buf.h | 4 +++- fs/xfs/xfs_fsops.c | 2 +- fs/xfs/xfs_inode.c | 4 ++-- fs/xfs/xfs_trans_buf.c | 7 +++---- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/fs/xfs/libxfs/xfs_btree_staging.c b/fs/xfs/libxfs/xfs_btree_staging.c index c3c7ea54895a..7314dab4bcfb 100644 --- a/fs/xfs/libxfs/xfs_btree_staging.c +++ b/fs/xfs/libxfs/xfs_btree_staging.c @@ -248,11 +248,10 @@ xfs_btree_bload_drop_buf( return 0; /* - * Mark this buffer XBF_DONE (i.e. uptodate) so that a subsequent - * xfs_buf_read will not pointlessly reread the contents from the disk. + * Mark this buffer uptodate so that a subsequent xfs_buf_read will + * not pointlessly reread the contents from the disk. */ - bp->b_flags |= XBF_DONE; - + xfs_buf_set_uptodate(bp); xfs_buf_delwri_queue_here(bp, buffers_list); xfs_buf_relse(bp); *bpp = NULL; diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index ffcdd1f691fd..58dac4d505ba 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -413,7 +413,7 @@ xfs_ialloc_inode_init( xfs_trans_ordered_buf(tp, fbuf); } } else { - fbuf->b_flags |= XBF_DONE; + xfs_buf_set_uptodate(fbuf); xfs_buf_delwri_queue(fbuf, buffer_list); xfs_buf_relse(fbuf); } diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index f922f1081ce3..d1b426c657f5 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -55,6 +55,13 @@ static inline bool xfs_buf_is_uncached(struct xfs_buf *bp) return bp->b_rhash_key == XFS_BUF_DADDR_NULL; } +void +xfs_buf_set_uptodate( + struct xfs_buf *bp) +{ + bp->b_flags |= XBF_DONE; +} + /* * When we mark a buffer stale, we remove the buffer from the LRU and clear the * b_lru_ref count so that the buffer is freed immediately when the buffer @@ -85,6 +92,15 @@ xfs_buf_stale( spin_unlock(&bp->b_lockref.lock); } +void +xfs_buf_clear_stale( + struct xfs_buf *bp) +{ + ASSERT(bp->b_flags & XBF_STALE); + + bp->b_flags &= ~XBF_STALE; +} + static void xfs_buf_free_callback( struct callback_head *cb) diff --git a/fs/xfs/xfs_buf.h b/fs/xfs/xfs_buf.h index e440f97cf3e1..a4729253b56f 100644 --- a/fs/xfs/xfs_buf.h +++ b/fs/xfs/xfs_buf.h @@ -301,7 +301,9 @@ static inline void xfs_buf_zero(struct xfs_buf *bp, size_t boff, size_t bsize) memset(bp->b_addr + boff, 0, bsize); } -extern void xfs_buf_stale(struct xfs_buf *bp); +void xfs_buf_set_uptodate(struct xfs_buf *bp); +void xfs_buf_stale(struct xfs_buf *bp); +void xfs_buf_clear_stale(struct xfs_buf *bp); /* Delayed Write Buffer Routines */ extern void xfs_buf_delwri_cancel(struct list_head *); diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c index 67624a804a7f..21114bb6d4ff 100644 --- a/fs/xfs/xfs_fsops.c +++ b/fs/xfs/xfs_fsops.c @@ -501,7 +501,7 @@ xfs_do_force_shutdown( return; } if (mp->m_sb_bp) - mp->m_sb_bp->b_flags |= XBF_DONE; + xfs_buf_set_uptodate(mp->m_sb_bp); if (flags & SHUTDOWN_FORCE_UMOUNT) xfs_alert(mp, "User initiated shutdown received."); diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 15279d22a894..030a7c8f2c12 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1753,7 +1753,7 @@ xfs_ifree_cluster( * attachment may occur in xfs_inode_item_precommit() after we * have marked this buffer stale. If this buffer was not in * memory before xfs_ifree_cluster() started, it will not be - * marked XBF_DONE and this will cause problems later in + * marked uptodate and this will cause problems later in * xfs_inode_item_precommit() when we trip over a (stale, !done) * buffer to attached to the transaction. * @@ -1766,7 +1766,7 @@ xfs_ifree_cluster( * fail. We can acheive this by adding a write verifier to the * buffer. */ - bp->b_flags |= XBF_DONE; + xfs_buf_set_uptodate(bp); bp->b_ops = &xfs_inode_buf_ops; /* diff --git a/fs/xfs/xfs_trans_buf.c b/fs/xfs/xfs_trans_buf.c index 7e17b93fe9ad..1e025848811a 100644 --- a/fs/xfs/xfs_trans_buf.c +++ b/fs/xfs/xfs_trans_buf.c @@ -140,7 +140,7 @@ xfs_trans_get_buf_map( ASSERT(xfs_buf_islocked(bp)); if (xfs_is_shutdown(tp->t_mountp)) { xfs_buf_stale(bp); - bp->b_flags |= XBF_DONE; + xfs_buf_set_uptodate(bp); } ASSERT(bp->b_transp == tp); @@ -482,7 +482,7 @@ xfs_trans_dirty_buf( * item from the AIL and free it when the buffer is flushed * to disk. */ - bp->b_flags |= XBF_DONE; + xfs_buf_set_uptodate(bp); ASSERT(atomic_read(&bip->bli_refcount) > 0); @@ -494,8 +494,7 @@ xfs_trans_dirty_buf( */ if (bip->bli_flags & XFS_BLI_STALE) { bip->bli_flags &= ~XFS_BLI_STALE; - ASSERT(bp->b_flags & XBF_STALE); - bp->b_flags &= ~XBF_STALE; + xfs_buf_clear_stale(bp); bip->__bli_format.blf_flags &= ~XFS_BLF_CANCEL; } bip->bli_flags |= XFS_BLI_DIRTY | XFS_BLI_LOGGED; From b15f6520ab6481b89688d4b6362d1fa5d9f9df6b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:00 +0200 Subject: [PATCH 14/23] xfs: use WRITE_ONCE to update b_flags Prepare for limited lockless reading of flags by using WRITE_ONCE to prevent the compiler from doing non-standard read-modify-write operations. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 86 ++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index d1b426c657f5..88ca50b19064 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -55,11 +55,27 @@ static inline bool xfs_buf_is_uncached(struct xfs_buf *bp) return bp->b_rhash_key == XFS_BUF_DADDR_NULL; } +static inline void +xfs_buf_set_flags( + struct xfs_buf *bp, + unsigned int flags) +{ + WRITE_ONCE(bp->b_flags, bp->b_flags | flags); +} + +static inline void +xfs_buf_clear_flags( + struct xfs_buf *bp, + unsigned int flags) +{ + WRITE_ONCE(bp->b_flags, bp->b_flags & ~flags); +} + void xfs_buf_set_uptodate( struct xfs_buf *bp) { - bp->b_flags |= XBF_DONE; + xfs_buf_set_flags(bp, XBF_DONE); } /* @@ -76,14 +92,14 @@ xfs_buf_stale( { ASSERT(xfs_buf_islocked(bp)); - bp->b_flags |= XBF_STALE; + xfs_buf_set_flags(bp, XBF_STALE); /* * Clear the delwri status so that a delwri queue walker will not * flush this buffer to disk now that it is stale. The delwri queue has * a reference to the buffer, so this is safe to do. */ - bp->b_flags &= ~_XBF_DELWRI_Q; + xfs_buf_clear_flags(bp, _XBF_DELWRI_Q); spin_lock(&bp->b_lockref.lock); atomic_set(&bp->b_lru_ref, 0); @@ -97,8 +113,7 @@ xfs_buf_clear_stale( struct xfs_buf *bp) { ASSERT(bp->b_flags & XBF_STALE); - - bp->b_flags &= ~XBF_STALE; + xfs_buf_clear_flags(bp, XBF_STALE); } static void @@ -175,7 +190,7 @@ xfs_buf_alloc_kmem( bp->b_addr = NULL; return -ENOMEM; } - bp->b_flags |= _XBF_KMEM; + xfs_buf_set_flags(bp, _XBF_KMEM); trace_xfs_buf_backing_kmem(bp, _RET_IP_); return 0; } @@ -307,7 +322,7 @@ xfs_buf_alloc( INIT_LIST_HEAD(&bp->b_li_list); bp->b_target = target; bp->b_mount = target->bt_mount; - bp->b_flags = flags; + WRITE_ONCE(bp->b_flags, flags); bp->b_rhash_key = map[0].bm_bn; bp->b_length = 0; bp->b_map_count = nmaps; @@ -436,7 +451,7 @@ xfs_buf_find_lock( return -ENOENT; } ASSERT((bp->b_flags & _XBF_DELWRI_Q) == 0); - bp->b_flags &= _XBF_KMEM; + xfs_buf_clear_flags(bp, ~_XBF_KMEM); bp->b_ops = NULL; } return 0; @@ -604,8 +619,9 @@ _xfs_buf_read( { ASSERT(bp->b_maps[0].bm_bn != XFS_BUF_DADDR_NULL); - bp->b_flags &= ~(XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD | XBF_DONE); - bp->b_flags |= XBF_READ; + xfs_buf_clear_flags(bp, XBF_WRITE | XBF_ASYNC | XBF_READ_AHEAD | + XBF_DONE); + xfs_buf_set_flags(bp, XBF_READ); xfs_buf_submit(bp); return xfs_buf_iowait(bp); } @@ -641,7 +657,7 @@ xfs_buf_reverify( bp->b_ops = ops; bp->b_ops->verify_read(bp); if (bp->b_error) - bp->b_flags &= ~XBF_DONE; + xfs_buf_clear_flags(bp, XBF_DONE); return bp->b_error; } @@ -679,7 +695,7 @@ xfs_buf_read_map( error = xfs_buf_reverify(bp, ops); /* We do not want read in the flags */ - bp->b_flags &= ~XBF_READ; + xfs_buf_clear_flags(bp, XBF_READ); ASSERT(bp->b_ops != NULL || ops == NULL); } @@ -704,7 +720,7 @@ xfs_buf_read_map( if (!xlog_is_shutdown(target->bt_mount->m_log)) xfs_buf_ioerror_alert(bp, fa); - bp->b_flags &= ~XBF_DONE; + xfs_buf_clear_flags(bp, XBF_DONE); xfs_buf_stale(bp); xfs_buf_relse(bp); @@ -750,8 +766,8 @@ xfs_buf_readahead_map( } XFS_STATS_INC(target->bt_mount, xb_get_read); bp->b_ops = ops; - bp->b_flags &= ~(XBF_WRITE | XBF_DONE); - bp->b_flags |= flags; + xfs_buf_clear_flags(bp, XBF_WRITE | XBF_DONE); + xfs_buf_set_flags(bp, flags); percpu_counter_inc(&target->bt_readahead_count); xfs_buf_submit(bp); } @@ -783,7 +799,7 @@ xfs_buf_read_uncached( ASSERT(bp->b_map_count == 1); bp->b_rhash_key = XFS_BUF_DADDR_NULL; bp->b_maps[0].bm_bn = daddr; - bp->b_flags |= XBF_READ; + xfs_buf_set_flags(bp, XBF_READ); bp->b_ops = ops; xfs_buf_submit(bp); @@ -1077,14 +1093,14 @@ xfs_buf_ioend_handle_error( resubmit: xfs_buf_ioerror(bp, 0); - bp->b_flags |= (XBF_DONE | XBF_WRITE_FAIL); + xfs_buf_set_flags(bp, XBF_DONE | XBF_WRITE_FAIL); reinit_completion(&bp->b_iowait); xfs_buf_submit(bp); return true; out_stale: xfs_buf_stale(bp); - bp->b_flags |= XBF_DONE; - bp->b_flags &= ~XBF_WRITE; + xfs_buf_set_flags(bp, XBF_DONE); + xfs_buf_clear_flags(bp, XBF_WRITE); trace_xfs_buf_error_relse(bp, _RET_IP_); return false; } @@ -1109,7 +1125,7 @@ xfs_buf_ioend( if (!bp->b_error && bp->b_ops) bp->b_ops->verify_read(bp); if (!bp->b_error) - bp->b_flags |= XBF_DONE; + xfs_buf_set_flags(bp, XBF_DONE); if (bp->b_flags & XBF_READ_AHEAD) percpu_counter_dec(&bp->b_target->bt_readahead_count); } else { @@ -1119,8 +1135,8 @@ xfs_buf_ioend( return; } } else { - bp->b_flags &= ~XBF_WRITE_FAIL; - bp->b_flags |= XBF_DONE; + xfs_buf_clear_flags(bp, XBF_WRITE_FAIL); + xfs_buf_set_flags(bp, XBF_DONE); } /* clear the retry state */ @@ -1140,7 +1156,7 @@ xfs_buf_ioend( bp->b_iodone(bp); } - bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD); + xfs_buf_clear_flags(bp, XBF_READ | XBF_WRITE | XBF_READ_AHEAD); if (async) xfs_buf_relse(bp); } @@ -1186,8 +1202,8 @@ xfs_buf_fail( { ASSERT(xfs_buf_islocked(bp)); - bp->b_flags |= XBF_ASYNC; - bp->b_flags &= ~XBF_DONE; + xfs_buf_set_flags(bp, XBF_ASYNC); + xfs_buf_clear_flags(bp, XBF_DONE); xfs_buf_stale(bp); xfs_buf_ioerror(bp, -EIO); xfs_buf_ioend(bp); @@ -1201,9 +1217,9 @@ xfs_bwrite( ASSERT(xfs_buf_islocked(bp)); - bp->b_flags |= XBF_WRITE; - bp->b_flags &= ~(XBF_ASYNC | XBF_READ | _XBF_DELWRI_Q | - XBF_DONE); + xfs_buf_set_flags(bp, XBF_WRITE); + xfs_buf_clear_flags(bp, XBF_ASYNC | XBF_READ | _XBF_DELWRI_Q | + XBF_DONE); xfs_buf_submit(bp); error = xfs_buf_iowait(bp); @@ -1394,7 +1410,7 @@ xfs_buf_submit( return; ioerror: - bp->b_flags &= ~XBF_DONE; + xfs_buf_clear_flags(bp, XBF_DONE); xfs_buf_stale(bp); end_io: if (bp->b_flags & XBF_ASYNC) @@ -1805,7 +1821,7 @@ xfs_buf_delwri_cancel( bp = list_first_entry(list, struct xfs_buf, b_list); xfs_buf_lock(bp); - bp->b_flags &= ~_XBF_DELWRI_Q; + xfs_buf_clear_flags(bp, _XBF_DELWRI_Q); xfs_buf_list_del(bp); xfs_buf_relse(bp); } @@ -1850,7 +1866,7 @@ xfs_buf_delwri_queue( * might get readded to a delwri list after the synchronous writeout, in * which case we need just need to re-add the flag here. */ - bp->b_flags |= _XBF_DELWRI_Q; + xfs_buf_set_flags(bp, _XBF_DELWRI_Q); if (list_empty(&bp->b_list)) { xfs_buf_hold(bp); list_add_tail(&bp->b_list, list); @@ -1927,8 +1943,8 @@ xfs_buf_delwri_submit_prep( } trace_xfs_buf_delwri_split(bp, _RET_IP_); - bp->b_flags &= ~_XBF_DELWRI_Q; - bp->b_flags |= XBF_WRITE; + xfs_buf_clear_flags(bp, _XBF_DELWRI_Q); + xfs_buf_set_flags(bp, XBF_WRITE); return true; } @@ -1969,7 +1985,7 @@ xfs_buf_delwri_submit_nowait( } if (!xfs_buf_delwri_submit_prep(bp)) continue; - bp->b_flags |= XBF_ASYNC; + xfs_buf_set_flags(bp, XBF_ASYNC); xfs_buf_list_del(bp); xfs_buf_submit(bp); } @@ -2002,7 +2018,7 @@ xfs_buf_delwri_submit( xfs_buf_lock(bp); if (!xfs_buf_delwri_submit_prep(bp)) continue; - bp->b_flags &= ~XBF_ASYNC; + xfs_buf_clear_flags(bp, XBF_ASYNC); list_move_tail(&bp->b_list, &wait_list); xfs_buf_submit(bp); } From 3d102727432b5c5cff9afa80a430ca8f34b59666 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:01 +0200 Subject: [PATCH 15/23] xfs: don't reverify buffers in xfs_buf_readahead_map xfs_buf_read_map calls xfs_buf_reverify to ensure the verifier has run for a buffer before the data can be used when an earlier readahead read the data before the buf_ops were assigned. There is no point in doing this in xfs_buf_readahead_map for a buffer already in memory as a later xfs_buf_read will do the same and can actually propagate the error to the caller. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 88ca50b19064..1d0a417139a6 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -760,7 +760,6 @@ xfs_buf_readahead_map( trace_xfs_buf_readahead(bp, 0, _RET_IP_); if (bp->b_flags & XBF_DONE) { - xfs_buf_reverify(bp, ops); xfs_buf_relse(bp); return; } From 819f1cc98aaf09733b30700608a02256f47d7908 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:02 +0200 Subject: [PATCH 16/23] xfs: use goto based error unwinding in xfs_buf_read_map This keeps the I/O error handling contained at the end of the function and removes the indentation for it. It also allows to reorder the comments so that they are closer to the logic that they describe. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 1d0a417139a6..6c0c00107b35 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -699,6 +699,23 @@ xfs_buf_read_map( ASSERT(bp->b_ops != NULL || ops == NULL); } + if (error) + goto out_ioerror; + + *bpp = bp; + return 0; + +out_ioerror: + /* + * Check against log shutdown for error reporting because metadata + * writeback may require a read first and we need to report errors in + * metadata writeback until the log is shut down. High level + * transaction read functions already check against mount shutdown, so + * we only need to be concerned about low level/ IO interactions here. + */ + if (!xlog_is_shutdown(target->bt_mount->m_log)) + xfs_buf_ioerror_alert(bp, fa); + /* * If we've had a read error, then the contents of the buffer are * invalid and should not be used. To ensure that a followup read tries @@ -708,30 +725,14 @@ xfs_buf_read_map( * future cache lookups will also treat it as an empty, uninitialised * buffer. */ - if (error) { - /* - * Check against log shutdown for error reporting because - * metadata writeback may require a read first and we need to - * report errors in metadata writeback until the log is shut - * down. High level transaction read functions already check - * against mount shutdown, anyway, so we only need to be - * concerned about low level IO interactions here. - */ - if (!xlog_is_shutdown(target->bt_mount->m_log)) - xfs_buf_ioerror_alert(bp, fa); + xfs_buf_clear_flags(bp, XBF_DONE); + xfs_buf_stale(bp); + xfs_buf_relse(bp); - xfs_buf_clear_flags(bp, XBF_DONE); - xfs_buf_stale(bp); - xfs_buf_relse(bp); - - /* bad CRC means corrupted metadata */ - if (error == -EFSBADCRC) - error = -EFSCORRUPTED; - return error; - } - - *bpp = bp; - return 0; + /* bad CRC means corrupted metadata */ + if (error == -EFSBADCRC) + return -EFSCORRUPTED; + return error; } /* From 21cc7775ee5ca2d5cd4be0de0d55fd28b14fd4e1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:03 +0200 Subject: [PATCH 17/23] xfs: merge xfs_buf_reverify into xfs_buf_read_map xfs_buf_read_map is the only caller of xfs_buf_reverify that is left. Merge it into that so that the comments can be moved closer to the logic, and redundant asserts can be removed. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 79 ++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 6c0c00107b35..22e6541b9ffa 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -626,41 +626,6 @@ _xfs_buf_read( return xfs_buf_iowait(bp); } -/* - * Reverify a buffer found in cache without an attached ->b_ops. - * - * If the caller passed an ops structure and the buffer doesn't have ops - * assigned, set the ops and use it to verify the contents. If verification - * fails, clear XBF_DONE. We assume the buffer has no recorded errors and is - * already in XBF_DONE state on entry. - * - * Under normal operations, every in-core buffer is verified on read I/O - * completion. There are two scenarios that can lead to in-core buffers without - * an assigned ->b_ops. The first is during log recovery of buffers on a V4 - * filesystem, though these buffers are purged at the end of recovery. The - * other is online repair, which intentionally reads with a NULL buffer ops to - * run several verifiers across an in-core buffer in order to establish buffer - * type. If repair can't establish that, the buffer will be left in memory - * with NULL buffer ops. - */ -static int -xfs_buf_reverify( - struct xfs_buf *bp, - const struct xfs_buf_ops *ops) -{ - ASSERT(bp->b_flags & XBF_DONE); - ASSERT(bp->b_error == 0); - - if (!ops || bp->b_ops) - return 0; - - bp->b_ops = ops; - bp->b_ops->verify_read(bp); - if (bp->b_error) - xfs_buf_clear_flags(bp, XBF_DONE); - return bp->b_error; -} - int xfs_buf_read_map( struct xfs_buftarg *target, @@ -685,18 +650,46 @@ xfs_buf_read_map( trace_xfs_buf_read(bp, flags, _RET_IP_); - if (!(bp->b_flags & XBF_DONE)) { + if (bp->b_flags & XBF_DONE) { + ASSERT(bp->b_error == 0); + + /* + * If the caller passed an ops structure and the buffer doesn't + * have ops assigned yet, set the ops and use them to verify the + * buffer contents. + * + * Under normal operations, every in-core buffer is verified on + * read I/O completion, but there are two scenarios that can + * lead to in-core buffers without an assigned ->b_ops: + * + * 1) During log recovery of buffers on a V4 filesystem. + * These buffers are purged at the end of recovery, though. + * 2) Oonline repair intentionally reads with a NULL buffer + * ops to run several verifiers across an in-core buffer in + * order to establish buffer type. If repair can't + * establish that, the buffer will be left in memory with + * NULL buffer ops. + */ + if (ops && !bp->b_ops) { + bp->b_ops = ops; + bp->b_ops->verify_read(bp); + /* + * If verification failed, clear XBF_DONE as we assume + * that buffers have no recorded errors when in XBF_DONE + * state. + */ + error = bp->b_error; + if (error) + xfs_buf_clear_flags(bp, XBF_DONE); + } + + /* We do not want read in the flags */ + xfs_buf_clear_flags(bp, XBF_READ); + } else { /* Initiate the buffer read and wait. */ XFS_STATS_INC(target->bt_mount, xb_get_read); bp->b_ops = ops; error = _xfs_buf_read(bp); - } else { - /* Buffer already read; all we need to do is check it. */ - error = xfs_buf_reverify(bp, ops); - - /* We do not want read in the flags */ - xfs_buf_clear_flags(bp, XBF_READ); - ASSERT(bp->b_ops != NULL || ops == NULL); } if (error) From 2aedf844be1d578c4c2746177aaaf096a053d650 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:04 +0200 Subject: [PATCH 18/23] xfs: move buffer locking out of xfs_find_get_buf To prepare for buffer loookups that don't lock the buffer, move the call to xfs_buf_find_lock from xfs_find_get_buf to its callers. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index 22e6541b9ffa..bad4e1c0a647 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -577,12 +577,6 @@ xfs_find_get_buf( return error; } - error = xfs_buf_find_lock(bp, flags); - if (error) { - xfs_buf_rele(bp); - return error; - } - *bpp = bp; return 0; } @@ -607,6 +601,12 @@ xfs_buf_get_map( error = xfs_find_get_buf(btp, map, nmaps, flags, bpp); if (error) return error; + + error = xfs_buf_find_lock(*bpp, flags); + if (error) { + xfs_buf_rele(*bpp); + return error; + } XFS_STATS_INC(btp->bt_mount, xb_get); trace_xfs_buf_get(*bpp, flags, _RET_IP_); xfs_buf_ioerror(*bpp, 0); @@ -647,6 +647,11 @@ xfs_buf_read_map( error = xfs_find_get_buf(target, map, nmaps, flags, &bp); if (error) return error; + error = xfs_buf_find_lock(bp, flags); + if (error) { + xfs_buf_rele(bp); + return error; + } trace_xfs_buf_read(bp, flags, _RET_IP_); @@ -749,20 +754,26 @@ xfs_buf_readahead_map( if (xfs_buftarg_is_mem(target)) return; - if (xfs_find_get_buf(target, map, nmaps, flags | XBF_TRYLOCK, &bp)) + if (xfs_find_get_buf(target, map, nmaps, flags, &bp)) return; - trace_xfs_buf_readahead(bp, 0, _RET_IP_); + if (xfs_buf_find_lock(bp, XBF_TRYLOCK)) + goto out_rele; + + trace_xfs_buf_readahead(bp, 0, _RET_IP_); + if (bp->b_flags & XBF_DONE) + goto out_unlock; - if (bp->b_flags & XBF_DONE) { - xfs_buf_relse(bp); - return; - } XFS_STATS_INC(target->bt_mount, xb_get_read); bp->b_ops = ops; xfs_buf_clear_flags(bp, XBF_WRITE | XBF_DONE); xfs_buf_set_flags(bp, flags); percpu_counter_inc(&target->bt_readahead_count); xfs_buf_submit(bp); + return; +out_unlock: + xfs_buf_unlock(bp); +out_rele: + xfs_buf_rele(bp); } /* From 00f40876f417463ef759dc3b0c4cfe15da48b092 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 15 Jul 2026 16:51:05 +0200 Subject: [PATCH 19/23] xfs: add lockless xfs_buf_readahead_map fast path Readahead currently always locks the buffer, which can cause contention with actual users of the buffer. Add a fast path without taking any locks if the buffer is uptodate and not stale. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index bad4e1c0a647..a700ebd8ea4e 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -756,13 +756,23 @@ xfs_buf_readahead_map( if (xfs_find_get_buf(target, map, nmaps, flags, &bp)) return; - if (xfs_buf_find_lock(bp, XBF_TRYLOCK)) + + /* + * Do a lockless fast path check for a valid uptodate buffer and avoid + * locking entirely in this case. + */ + if ((READ_ONCE(bp->b_flags) & (XBF_DONE | XBF_STALE)) == XBF_DONE) goto out_rele; - trace_xfs_buf_readahead(bp, 0, _RET_IP_); - if (bp->b_flags & XBF_DONE) + /* Otherwise lock the buffer to stabilize the state */ + if (!xfs_buf_trylock(bp)) + goto out_rele; + + /* Let the actual reader deal with stale buffers. */ + if (bp->b_flags & (XBF_STALE | XBF_DONE)) goto out_unlock; + trace_xfs_buf_readahead(bp, 0, _RET_IP_); XFS_STATS_INC(target->bt_mount, xb_get_read); bp->b_ops = ops; xfs_buf_clear_flags(bp, XBF_WRITE | XBF_DONE); From 0241ea5fb0fe86d2a673163b2f5815111aadc7f7 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 30 Jul 2026 17:30:02 +0800 Subject: [PATCH 20/23] xfs: restore nofs context unconditionally in xfs_trans_roll When __xfs_trans_commit() fails in xfs_trans_roll(), the NOFS context is cleared but only restored in the success path. This leaves the error path without nofs protection, causing a circular lock dependency between xfs_nondir_ilock_class and fs_reclaim: CPU0 CPU1 ---- ---- lock(&xfs_nondir_ilock_class); lock(fs_reclaim); lock(&xfs_nondir_ilock_class); lock(fs_reclaim); Fix this by moving xfs_trans_set_context() before the error check so that nofs context is always restored on the new transaction. Reported-by: syzbot+59178abfeb0ea3f0ab20@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=59178abfeb0ea3f0ab20 Fixes: a1ca658d649a ("xfs: fix incorrect context handling in xfs_trans_roll") Cc: stable@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Yun Zhou Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 7bfbd9f6f0df..1b36cf12d4e3 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -1029,6 +1029,15 @@ xfs_trans_roll( * duplicate transaction that gets returned. */ error = __xfs_trans_commit(tp, true); + + tp = *tpp; + /* + * __xfs_trans_commit cleared the NOFS flag by calling into + * xfs_trans_free. Set it again here before doing memory + * allocations. + */ + xfs_trans_set_context(tp); + if (error) return error; @@ -1040,13 +1049,6 @@ xfs_trans_roll( * either nothing be locked across this call, or that anything that is * locked be logged in the prior and the next transactions. */ - tp = *tpp; - /* - * __xfs_trans_commit cleared the NOFS flag by calling into - * xfs_trans_free. Set it again here before doing memory - * allocations. - */ - xfs_trans_set_context(tp); error = xfs_log_regrant(tp->t_mountp, tp->t_ticket); if (error) return error; From a94e648f03cf4e0def8ab3c74a1f9fe140720dd4 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 30 Jul 2026 16:42:36 +0800 Subject: [PATCH 21/23] xfs: use file target for post-log fsync fallback flush xfs_file_fsync() has a fallback flush for the case where the log force was a no-op, for example fdatasync/O_DSYNC writes that do not require metadata updates. The current fallback path is expressed in terms of the main data device and explicitly excludes realtime inodes. Realtime files with a separate realtime device are flushed before the log force, because their data must reach stable storage before the log commit. For the internal realtime device used by the zoned allocator, writes are out-of-place and update inode and bmap metadata from I/O completion, so the overwrite-without-metadata-update case does not apply in the same way. Even so, the current fallback condition is inconsistent because it is expressed as "non-realtime inode on the main data device" rather than in terms of the inode's actual file data target. Use xfs_inode_buftarg() to obtain the target that stores this file's data, and issue the fallback flush when the log force did not flush anything and the log target is the same as that file target. This preserves existing behavior for regular files while making the fallback logic consistent for files whose data target is selected by the inode. Fixes: bdc03eb5f98f ("xfs: allow internal RT devices for zoned mode") Signed-off-by: Hongling Zeng Suggested-by: Christoph Hellwig Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 0ade13b31335..4745f047193c 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -166,17 +166,23 @@ xfs_file_fsync( } /* - * If we only have a single device, and the log force about was - * a no-op we might have to flush the data device cache here. - * This can only happen for fdatasync/O_DSYNC if we were overwriting - * an already allocated file and thus do not have any metadata to - * commit. + * If the log force was a no-op, we may still need to flush the + * file data target cache here. This can happen for fdatasync/O_DSYNC + * when no metadata needed to be committed. + * + * Use the inode's actual file data target rather than assuming the + * main data device. Realtime inodes with a separate realtime device + * are flushed before the log force, so this fallback only applies + * when the file data target is the same as the log target. */ - if (!log_flushed && !XFS_IS_REALTIME_INODE(ip) && - mp->m_logdev_targp == mp->m_ddev_targp) { - err2 = blkdev_issue_flush(mp->m_ddev_targp->bt_bdev); - if (err2 && !error) - error = err2; + if (!log_flushed) { + struct xfs_buftarg *file_targp = xfs_inode_buftarg(ip); + + if (mp->m_logdev_targp == file_targp) { + err2 = blkdev_issue_flush(file_targp->bt_bdev); + if (err2 && !error) + error = err2; + } } return error; From 6e6a31401445556b58ab9517e256285989be73da Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Wed, 29 Jul 2026 15:34:18 +0800 Subject: [PATCH 22/23] xfs: check split_sectors validity before bio_split call Change the split_sectors check from !split_sectors to split_sectors <= 0 to make the error handling explicit. While bio_split_rw_at() cannot return a negative error code for the current GC I/O path (GC I/O doesn't use REQ_ATOMIC/REQ_NOWAIT flags and has proper alignment), making the check explicit improves code clarity and makes the intent clear. This also makes the code more robust for future maintenance if different I/O patterns are introduced. Signed-off-by: Hongling Zeng Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_zone_gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index f76a09130852..e00f4771e424 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -802,7 +802,7 @@ xfs_zone_gc_split_write( split_sectors = bio_split_rw_at(&chunk->bio, lim, &nsegs, lim->max_zone_append_sectors << SECTOR_SHIFT); - if (!split_sectors) + if (split_sectors <= 0) return NULL; /* ensure the split chunk is still block size aligned */ From b7eea80be25f3334f131d52982b3131aba77b97d Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Tue, 28 Jul 2026 15:43:40 +0800 Subject: [PATCH 23/23] xfs: validate attr entry pointer before field access xfs_attr3_leaf_verify_entry() accesses lentry/rentry fields (namelen, valuelen) before checking if the entry pointer itself is within bounds. If nameidx is crafted to point near the end of the buffer, these field accesses can read out-of-bounds before the bounds check at name_end > buf_end is performed. Add explicit bounds checks for entry pointers before accessing their fields. Use offsetof() to check that the start of the flexible array member (nameval/name) is within bounds, which ensures all preceding fields are safe to access. Fixes: c84760659dcf2 ("xfs: check attribute leaf block structure") Cc: # v5.5 Signed-off-by: Hongling Zeng Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr_leaf.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 86c5c09a5db4..b6288395f853 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -325,6 +325,13 @@ xfs_attr3_leaf_verify_entry( */ if (ent->flags & XFS_ATTR_LOCAL) { lentry = xfs_attr3_leaf_name_local(leaf, idx); + + /* Validate lentry pointer is within bounds before field access */ + if ((char *)lentry >= buf_end) + return __this_address; + if ((char *)lentry + offsetof(struct xfs_attr_leaf_name_local, nameval) > buf_end) + return __this_address; + namesize = xfs_attr_leaf_entsize_local(lentry->namelen, be16_to_cpu(lentry->valuelen)); name_end = (char *)lentry + namesize; @@ -332,6 +339,13 @@ xfs_attr3_leaf_verify_entry( return __this_address; } else { rentry = xfs_attr3_leaf_name_remote(leaf, idx); + + /* Validate rentry pointer is within bounds before field access */ + if ((char *)rentry >= buf_end) + return __this_address; + if ((char *)rentry + offsetof(struct xfs_attr_leaf_name_remote, name) > buf_end) + return __this_address; + namesize = xfs_attr_leaf_entsize_remote(rentry->namelen); name_end = (char *)rentry + namesize; if (rentry->namelen == 0)