From 0344a9492efac4c79202a07cf284a382f7054170 Mon Sep 17 00:00:00 2001 From: Binbin Deng <18983559317@163.com> Date: Thu, 15 Oct 2026 16:02:25 +0800 Subject: [PATCH 01/19] xfs: set healthmon mount cookie before publishing it xfs_ioc_health_monitor() allocates the healthmon object with kzalloc(), so mount_cookie starts as zero. zero is also DETACHED_MOUNT_COOKIE, but xfs_healthmon_attach() publishes hm through mp->m_healthmon before storing the mount cookie. A concurrent reporter can therefore acquire hm from xfs_healthmon_get(), see the detached cookie in xfs_healthmon_push(), and drop the event even though the monitor is being attached. This is reachable by racing XFS_IOC_HEALTH_MONITOR with an operation that reports a health event, such as a filesystem shutdown. Initialize the mount cookie before making hm visible through the RCU pointer so reporters always observe an attached monitor. Found during a static-analysis-assisted audit: candidate sites were surfaced by a Coccinelle rule, and the reader/writer paths were confirmed by LLM-assisted (GLM-5.3) code inspection. The issue was not observed at runtime; it is identified by code inspection only. Fixes: a48373e7d35a ("xfs: start creating infrastructure for health monitoring") Cc: stable@vger.kernel.org Signed-off-by: Binbin Deng <18983559317@163.com> Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_healthmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index 2bdd747f1ead..05d6f0494139 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -114,8 +114,8 @@ xfs_healthmon_attach( } refcount_inc(&hm->ref); - rcu_assign_pointer(mp->m_healthmon, hm); hm->mount_cookie = (uintptr_t)mp->m_super; + rcu_assign_pointer(mp->m_healthmon, hm); spin_unlock(&xfs_healthmon_lock); return 0; From bde0010631a207511308dc8407f34595dac13ad5 Mon Sep 17 00:00:00 2001 From: Ravi Singh Date: Wed, 21 Oct 2026 08:33:29 +0200 Subject: [PATCH 02/19] xfs: mark i_mode=0 and shut down on attr fork inactivation failure xfs_attr_inactive() can fail on an I/O error reading the attribute fork's btree blocks, for example from fault injection such as the dm-flakey errors used by xfstests generic/475. On failure, xfs_ifork_zap_attr() zeros af_nextents without freeing the attribute blocks or decrementing i_nblocks, leaving the inode in an inconsistent in-memory state. The inode is marked XFS_IRECLAIMABLE and remains in the cache. If a concurrent operation (e.g., XFS_IOC_FSBULKSTAT) calls xfs_iget() on this inode, xfs_iget_check_free_state() will allow recycling if i_mode != 0. The recycled inode reuses the stale in-memory state (af_nextents=0, i_nblocks=1). When the recycled inode is queued for inactivation again, xfs_attr_inactive() sees af_nextents=0 and skips truncation, leaving i_nblocks non-zero. This triggers the assertion in xfs_ifree(): XFS: Assertion failed: ip->i_nblocks == 0 Clear i_mode so the stale inode cannot be recycled, and force a shutdown so log recovery frees the still-unlinked inode on the next mount. Suggested-by: Dave Chinner Signed-off-by: Ravi Singh Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_inode.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 05a14da28031..fceedb699bcf 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1481,8 +1481,17 @@ xfs_inactive( */ if (xfs_inode_has_attr_fork(ip)) { error = xfs_attr_inactive(ip); - if (error) + if (error) { + /* + * Attr teardown failed, leaking blocks with no in-core + * record. Clear i_mode to block xfs_iget() recycling, + * then shut down so recovery cleans up the inode. + */ + VFS_I(ip)->i_mode = 0; + if (!xfs_is_shutdown(mp)) + xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE); goto out; + } } ASSERT(ip->i_forkoff == 0); From 7e31d6dd3419e27ca74b2f9844d5a014f05e814d Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Mon, 26 Oct 2026 15:34:55 +1000 Subject: [PATCH 03/19] xfs: fix unwind handling during xfs_mount_sysfs_init() The goto for failing to setup /error/metadata jumps to the wrong place in xfs_mount_sysfs_init(). Fix it. Fixes: a9a7b9e8686d ("xfs: add support for lazy direct read bounce buffering") Signed-off-by: Wilfred Mallawa Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c index e77917ac179d..4a6a8c92c3a1 100644 --- a/fs/xfs/xfs_sysfs.c +++ b/fs/xfs/xfs_sysfs.c @@ -906,7 +906,7 @@ xfs_mount_sysfs_init( "metadata", &mp->m_error_meta_kobj, xfs_error_meta_init); if (error) - goto out_remove_csum_dir; + goto out_remove_error_dir; return 0; From 595e67d7261673d03c96950ffbb2e545c00376ed Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Mon, 26 Oct 2026 10:54:05 +0900 Subject: [PATCH 04/19] xfs: don't skip free extents in later batches of a whole-AG FITRIM A whole-device FITRIM walks every AG but the last by length, in batches. xfs_trim_gather_extents() saves the record to restart from in tcur->start. But tcur->start is also the start of the range to trim. Later batches therefore never discard the free extents below that block. Save the restart record in a new restart_bno field. Cc: stable@vger.kernel.org # v6.10 Fixes: b0ffe661fab4 ("xfs: fix performance problems when fstrimming a subset of a fragmented AG") Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_discard.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_discard.c b/fs/xfs/xfs_discard.c index 906221ffe9ba..8120f136af93 100644 --- a/fs/xfs/xfs_discard.c +++ b/fs/xfs/xfs_discard.c @@ -155,6 +155,8 @@ struct xfs_trim_cur { xfs_agblock_t end; xfs_extlen_t minlen; bool by_bno; + /* restart point (with count) for the by-length walk */ + xfs_agblock_t restart_bno; }; static int @@ -198,14 +200,15 @@ xfs_trim_gather_extents( error = xfs_alloc_lookup_le(cur, tcur->start, 0, &i); if (!error && !i) error = xfs_alloc_lookup_ge(cur, tcur->start, 0, &i); - } else if (tcur->start == 0) { + } else if (tcur->restart_bno == NULLAGBLOCK) { /* first time through a by-len starts with max length */ cur = xfs_cntbt_init_cursor(mp, tp, agbp, pag); error = xfs_alloc_lookup_ge(cur, 0, tcur->count, &i); } else { /* nth time through a by-len starts where we left off */ cur = xfs_cntbt_init_cursor(mp, tp, agbp, pag); - error = xfs_alloc_lookup_le(cur, tcur->start, tcur->count, &i); + error = xfs_alloc_lookup_le(cur, tcur->restart_bno, + tcur->count, &i); } if (error) goto out_del_cursor; @@ -235,9 +238,15 @@ xfs_trim_gather_extents( if (--batch <= 0) { /* * Update the cursor to point at this extent so we - * restart the next batch from this extent. + * restart the next batch from this extent. The + * by-length walk does not visit extents in block + * order, so it must keep the start of the range and + * restart from restart_bno instead. */ - tcur->start = fbno; + if (tcur->by_bno) + tcur->start = fbno; + else + tcur->restart_bno = fbno; tcur->count = flen; break; } @@ -340,6 +349,7 @@ xfs_trim_perag_extents( .start = start, .end = end, .minlen = minlen, + .restart_bno = NULLAGBLOCK, }; int error = 0; From 5745f4f10a1a16aa5bb020acc38afa33f2d5ff99 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Mon, 26 Oct 2026 10:54:06 +0900 Subject: [PATCH 05/19] xfs: don't skip free extents of the longest length in a whole-AG FITRIM Since commit a66d636385d6 ("xfs: fix fstrim offset calculations"), the by-length walk of an AG in FITRIM starts with xfs_alloc_lookup_ge(cur, 0, pagf_longest) and walks down. The lookup lands on the longest free extent with the lowest block number. The other free extents of that length sort after it and are never discarded. Start every by-length batch with xfs_alloc_lookup_le() at restart_bno. Suggested-by: Dave Chinner Link: https://lore.kernel.org/r/ZgiJWxVcVwag0fIP@dread.disaster.area Cc: stable@vger.kernel.org Fixes: a66d636385d6 ("xfs: fix fstrim offset calculations") Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_discard.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/xfs/xfs_discard.c b/fs/xfs/xfs_discard.c index 8120f136af93..cdf660344c28 100644 --- a/fs/xfs/xfs_discard.c +++ b/fs/xfs/xfs_discard.c @@ -200,12 +200,12 @@ xfs_trim_gather_extents( error = xfs_alloc_lookup_le(cur, tcur->start, 0, &i); if (!error && !i) error = xfs_alloc_lookup_ge(cur, tcur->start, 0, &i); - } else if (tcur->restart_bno == NULLAGBLOCK) { - /* first time through a by-len starts with max length */ - cur = xfs_cntbt_init_cursor(mp, tp, agbp, pag); - error = xfs_alloc_lookup_ge(cur, 0, tcur->count, &i); } else { - /* nth time through a by-len starts where we left off */ + /* + * A by-len walk starts at the last record of the longest + * length (restart_bno is NULLAGBLOCK until the first batch + * ends) and then where the previous batch stopped. + */ cur = xfs_cntbt_init_cursor(mp, tp, agbp, pag); error = xfs_alloc_lookup_le(cur, tcur->restart_bno, tcur->count, &i); From 1e253beed5340fdf1aa43cc684e085a6d468f5f7 Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Wed, 21 Oct 2026 23:00:32 +0200 Subject: [PATCH 06/19] xfs: skip iunlock with empty iolock flags in xfs_file_dio_write_aligned When using RWF_NOWAIT, xfs_file_write_checks may fail to acquire an exclusive lock upgrade and return -EAGAIN for aligned DIO, setting iolock to 0. In XFS_DEBUG kernels, passing no lock flags to xfs_iunlock triggers an assertion failure. Fix this by unlocking the iolock only when there are any flags set. Fixes: 2e2383405824 ("xfs: implement direct writes to zoned RT devices") Signed-off-by: Anthony Iliopoulos Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 7d0c8a197575..1eab6dbb4c5f 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -766,7 +766,8 @@ xfs_file_dio_write_aligned( trace_xfs_file_direct_write(iocb, from); ret = iomap_dio_rw(iocb, from, ops, dops, dio_flags, ac, 0); out_unlock: - xfs_iunlock(ip, iolock); + if (iolock) + xfs_iunlock(ip, iolock); return ret; } From 2bbca049deeb1699cafd966cb2793fee576ee409 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Mon, 2 Nov 2026 23:01:41 +0800 Subject: [PATCH 07/19] xfs: fix temporary cursor leak in btree shift corruption paths In xfs_btree_lshift and xfs_btree_rshift, when XFS_IS_CORRUPT detects an invalid firstrec/lastrec result after xfs_btree_dup_cursor has successfully allocated a temporary cursor, the error path jumps to error0 which returns without freeing tcur. This leaks the cursor memory, the xfs_group reference, and the per-level buffer references held by the duplicated cursor. Jump to error1 instead, which calls xfs_btree_del_cursor(tcur, XFS_BTREE_ERROR) to properly release all resources. Cc: stable@vger.kernel.org Fixes: 9eaead51bed95 ("[XFS] implement generic xfs_btree_rshift") Signed-off-by: Jianyun Gao Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_btree.c b/fs/xfs/libxfs/xfs_btree.c index 2434c54a3570..5ce49a8b9de1 100644 --- a/fs/xfs/libxfs/xfs_btree.c +++ b/fs/xfs/libxfs/xfs_btree.c @@ -2567,7 +2567,7 @@ xfs_btree_lshift( if (XFS_IS_CORRUPT(tcur->bc_mp, i != 1)) { xfs_btree_mark_sick(cur); error = -EFSCORRUPTED; - goto error0; + goto error1; } error = xfs_btree_decrement(tcur, level, &i); @@ -2737,7 +2737,7 @@ xfs_btree_rshift( if (XFS_IS_CORRUPT(tcur->bc_mp, i != 1)) { xfs_btree_mark_sick(cur); error = -EFSCORRUPTED; - goto error0; + goto error1; } error = xfs_btree_increment(tcur, level, &i); From b7eb276a39b14e685c2fe578f8ac22fc48ff9e6c Mon Sep 17 00:00:00 2001 From: Reid Downing Date: Tue, 27 Oct 2026 12:17:48 -0400 Subject: [PATCH 08/19] xfs: fix wrong compat fields in xfs_ioctl32_bstat_copyin() bs_blocks and bs_xflags were both read from bstat32->bs_size. Read each one from its own field instead. Fixes: e94fc4a43e5c39 ("[XFS] Add compat handlers for swapext ioctl") Cc: stable@vger.kernel.org # v2.6.29 Signed-off-by: Reid Downing Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index f6875a2cc706..77bc936bf0d9 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -143,8 +143,8 @@ xfs_ioctl32_bstat_copyin( xfs_ioctl32_bstime_copyin(&bstat->bs_atime, &bstat32->bs_atime) || xfs_ioctl32_bstime_copyin(&bstat->bs_mtime, &bstat32->bs_mtime) || xfs_ioctl32_bstime_copyin(&bstat->bs_ctime, &bstat32->bs_ctime) || - get_user(bstat->bs_blocks, &bstat32->bs_size) || - get_user(bstat->bs_xflags, &bstat32->bs_size) || + get_user(bstat->bs_blocks, &bstat32->bs_blocks) || + get_user(bstat->bs_xflags, &bstat32->bs_xflags) || get_user(bstat->bs_extsize, &bstat32->bs_extsize) || get_user(bstat->bs_extents, &bstat32->bs_extents) || get_user(bstat->bs_gen, &bstat32->bs_gen) || From 8b8e265991556d99b6d721697f148975a7c68767 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:22:58 +0900 Subject: [PATCH 09/19] xfs: mark the inode preallocated for pNFS write layouts past EOF xfs_fs_map_blocks() marks the inode XFS_DIFLAG_PREALLOC only when it allocates blocks for a write layout. A write layout past EOF can also hand out allocated blocks, such as speculative preallocation. blockgc then frees them under the layout, and another file may get them. Mark the inode whenever a write mapping ends past EOF. Fixes: 527851124d10 ("xfs: implement pNFS export operations") Cc: stable@vger.kernel.org Reported-by: Chuck Lever Link: https://lore.kernel.org/r/4de5356f-bc4d-4f1a-9c81-fce09b1b46fd@slotpi15m67 Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Reviewed-by: Pankaj Raghav Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index ab385617003a..a9f0b472f307 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -110,7 +110,15 @@ xfs_fs_map_update_inode( ip->i_diflags |= XFS_DIFLAG_PREALLOC; xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); - return xfs_trans_commit(tp); + error = xfs_trans_commit(tp); + if (error) + return error; + + /* + * Force the log so that the blocks handed out to the client are + * guaranteed to be present even after a server crash. + */ + return xfs_log_force_inode(ip); } /* @@ -134,6 +142,7 @@ xfs_fs_map_blocks( uint lock_flags; int error = 0; u64 seq; + bool update_inode = false; if (xfs_is_shutdown(mp)) return -EIO; @@ -221,20 +230,27 @@ xfs_fs_map_blocks( end_fsb - offset_fsb, 0, &imap, &seq); if (error) goto out_unlock; - - /* - * Ensure the next transaction is committed synchronously so - * that the blocks allocated and handed out to the client are - * guaranteed to be present even after a server crash. - */ - error = xfs_fs_map_update_inode(ip); - if (!error) - error = xfs_log_force_inode(ip); - if (error) - goto out_unlock; - + update_inode = true; } else { xfs_iunlock(ip, lock_flags); + + /* + * Blocks past EOF that were allocated for buffered writes, such + * as speculative preallocation, can be freed by + * xfs_free_eofblocks() without breaking the layout unless the + * inode is marked as preallocated. Mark it before handing them + * out for writing, as for blocks allocated above. + */ + if (write && !(ip->i_diflags & XFS_DIFLAG_PREALLOC) && + imap.br_startoff + imap.br_blockcount > + XFS_B_TO_FSB(mp, XFS_ISIZE(ip))) + update_inode = true; + } + + if (update_inode) { + error = xfs_fs_map_update_inode(ip); + if (error) + goto out_unlock; } xfs_iunlock(ip, XFS_IOLOCK_EXCL); From f3ca3498dfe624f9b604c934263538a3acd55af5 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:22:59 +0900 Subject: [PATCH 10/19] xfs: add a flag to convert unwritten extents without allocating blocks xfs_bmapi_write() with XFS_BMAPI_CONVERT also allocates blocks for holes and delalloc extents in the range. Add XFS_BMAPI_CONVERT_ONLY for callers that may only convert already allocated blocks. It stops at a hole or delalloc extent, and returns -ENOSR if it stops before mapping anything. Marked for stable because "xfs: refuse a pNFS LAYOUTCOMMIT of a range without blocks" needs it. Suggested-by: Christoph Hellwig Link: https://lore.kernel.org/r/20261013120359.GB2944@lst.de Cc: stable@vger.kernel.org Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Reviewed-by: Pankaj Raghav Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_bmap.c | 14 ++++++++++++-- fs/xfs/libxfs/xfs_bmap.h | 10 +++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index ca49bf4a50c5..050522c58b16 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -4130,6 +4130,8 @@ xfs_bmapi_finish( * Returns a negative error code on failure, including -ENOSPC when it could not * allocate any blocks and -ENOSR when it did allocate blocks to convert a * delalloc range, but those blocks were before the passed in range. + * With XFS_BMAPI_CONVERT_ONLY, it stops at a hole or a delalloc extent instead + * of allocating blocks, and returns -ENOSR if it stops before mapping anything. */ int xfs_bmapi_write( @@ -4189,6 +4191,8 @@ xfs_bmapi_write( */ ASSERT((flags & (XFS_BMAPI_PREALLOC | XFS_BMAPI_ZERO)) != (XFS_BMAPI_PREALLOC | XFS_BMAPI_ZERO)); + ASSERT(!(flags & XFS_BMAPI_CONVERT_ONLY) || + (flags & XFS_BMAPI_CONVERT)); if (XFS_IS_CORRUPT(mp, !xfs_ifork_has_extents(ifp)) || XFS_TEST_ERROR(mp, XFS_ERRTAG_BMAPIFORMAT)) { @@ -4237,6 +4241,9 @@ xfs_bmapi_write( * that we found, if any. */ if (need_alloc || wasdelay) { + if (flags & XFS_BMAPI_CONVERT_ONLY) + break; + bma.eof = eof; bma.conv = !!(flags & XFS_BMAPI_CONVERT); bma.wasdel = wasdelay; @@ -4332,10 +4339,13 @@ xfs_bmapi_write( * * To avoid a successful return with *nmap set to 0, return the magic * -ENOSR error code for this particular case so that the caller can - * handle it. + * handle it. The same goes for XFS_BMAPI_CONVERT_ONLY stopping at a + * hole or a delalloc extent at the start of the range, in which case + * nothing has been changed. */ if (!n) { - ASSERT(bma.nallocs >= *nmap); + ASSERT((flags & XFS_BMAPI_CONVERT_ONLY) ? bma.nallocs == 0 : + bma.nallocs >= *nmap); return -ENOSR; } *nmap = n; diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index e9f5416e4c05..fb7481e8190c 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -90,6 +90,13 @@ struct xfs_bmalloca { /* Try to align allocations to the extent size hint */ #define XFS_BMAPI_EXTSZALIGN (1u << 11) +/* + * Use with XFS_BMAPI_CONVERT to convert unwritten extents without allocating + * blocks. Stop at a hole or a delalloc extent: return the mappings found so + * far, or -ENOSR if there are none. + */ +#define XFS_BMAPI_CONVERT_ONLY (1u << 12) + #define XFS_BMAPI_FLAGS \ { XFS_BMAPI_ENTIRE, "ENTIRE" }, \ { XFS_BMAPI_METADATA, "METADATA" }, \ @@ -102,7 +109,8 @@ struct xfs_bmalloca { { XFS_BMAPI_COWFORK, "COWFORK" }, \ { XFS_BMAPI_NODISCARD, "NODISCARD" }, \ { XFS_BMAPI_NORMAP, "NORMAP" },\ - { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" } + { XFS_BMAPI_EXTSZALIGN, "EXTSZALIGN" }, \ + { XFS_BMAPI_CONVERT_ONLY, "CONVERT_ONLY" } static inline int xfs_bmapi_aflag(int w) From bcf0b7f18ad11e1d410e82ffef678705eff0a317 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:00 +0900 Subject: [PATCH 11/19] xfs: refuse a pNFS LAYOUTCOMMIT of a range without blocks A client can commit a hole in a LAYOUTCOMMIT, because nfsd does not check the extents against its layouts. xfs_iomap_write_unwritten() then allocates written blocks there. A large hole overruns the block reservation and shuts XFS down. A small one exposes stale data. Refuse such a range with -EIO, using XFS_BMAPI_CONVERT_ONLY. Link: https://lore.kernel.org/r/20261014130834.GB15773@lst.de Fixes: 527851124d10 ("xfs: implement pNFS export operations") Cc: stable@vger.kernel.org Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 2 +- fs/xfs/xfs_ioend.c | 2 +- fs/xfs/xfs_iomap.c | 20 +++++++++++++++++--- fs/xfs/xfs_iomap.h | 3 ++- fs/xfs/xfs_pnfs.c | 9 ++++++++- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 1eab6dbb4c5f..d2a2124bff54 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -666,7 +666,7 @@ xfs_dio_write_end_io( * they are converted. */ if (flags & IOMAP_DIO_UNWRITTEN) { - error = xfs_iomap_write_unwritten(ip, offset, size, true); + error = xfs_iomap_write_unwritten(ip, offset, size, true, 0); goto out; } diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index 48becc3bf608..7f4a4a95afe5 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -237,7 +237,7 @@ xfs_end_ioend_write( if (ioend->io_flags & IOMAP_IOEND_SHARED) error = xfs_reflink_end_cow(ip, offset, size); else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) - error = xfs_iomap_write_unwritten(ip, offset, size, false); + error = xfs_iomap_write_unwritten(ip, offset, size, false, 0); if (!error && !(ioend->io_flags & IOMAP_IOEND_DIRECT) && diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 329c745e3120..99d48081c5ac 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -614,7 +614,8 @@ xfs_iomap_write_unwritten( xfs_inode_t *ip, xfs_off_t offset, xfs_off_t count, - bool update_isize) + bool update_isize, + uint32_t bmapi_flags) { xfs_mount_t *mp = ip->i_mount; xfs_fileoff_t offset_fsb; @@ -676,8 +677,21 @@ xfs_iomap_write_unwritten( */ nimaps = 1; error = xfs_bmapi_write(tp, ip, offset_fsb, count_fsb, - XFS_BMAPI_CONVERT, resblks, &imap, - &nimaps); + XFS_BMAPI_CONVERT | bmapi_flags, + resblks, &imap, &nimaps); + if (error == -ENOSR && (bmapi_flags & XFS_BMAPI_CONVERT_ONLY)) { + /* + * Nothing was mapped: the rest of the range starts + * with a hole or a delalloc extent. Nothing was + * changed either, but xfs_iext_count_extend() may + * have logged the inode, and cancelling a dirty + * transaction shuts the filesystem down. Commit it, + * as xfs_alloc_file_space() does on -ENOSR. + */ + error = xfs_trans_commit(tp); + xfs_iunlock(ip, XFS_ILOCK_EXCL); + return error ? error : -EIO; + } if (error) goto error_on_bmapi_transaction; diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h index f2520a9b3a13..8fc0c6fc8bea 100644 --- a/fs/xfs/xfs_iomap.h +++ b/fs/xfs/xfs_iomap.h @@ -15,7 +15,8 @@ struct xfs_zone_alloc_ctx; int xfs_iomap_write_direct(struct xfs_inode *ip, xfs_fileoff_t offset_fsb, xfs_fileoff_t count_fsb, unsigned int flags, struct xfs_bmbt_irec *imap, u64 *sequence); -int xfs_iomap_write_unwritten(struct xfs_inode *, xfs_off_t, xfs_off_t, bool); +int xfs_iomap_write_unwritten(struct xfs_inode *ip, xfs_off_t offset, + xfs_off_t count, bool update_isize, uint32_t bmapi_flags); xfs_fileoff_t xfs_iomap_eof_align_last_fsb(struct xfs_inode *ip, xfs_fileoff_t end_fsb); diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index a9f0b472f307..7ff83274a513 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -344,7 +344,14 @@ xfs_fs_commit_blocks( (end - 1) >> PAGE_SHIFT); WARN_ON_ONCE(error); - error = xfs_iomap_write_unwritten(ip, start, length, false); + /* + * A client may only write to blocks that a layout handed out, + * and xfs_fs_map_blocks() allocates blocks for any hole in a + * write layout, so refuse to allocate blocks for a hole or a + * delalloc extent in the committed range. + */ + error = xfs_iomap_write_unwritten(ip, start, length, false, + XFS_BMAPI_CONVERT_ONLY); if (error) goto out_drop_iolock; } From d8c2bc88460f89e543ab3af622fc8f34c7011d71 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:01 +0900 Subject: [PATCH 12/19] xfs: clamp a pNFS LAYOUTCOMMIT extent before computing its end xfs_fs_commit_blocks() adds the client's length to the offset before it clamps the end to the file size. A large length wraps the sum and reaches xfs_iomap_write_unwritten() unclamped. With the previous two patches the LAYOUTCOMMIT then fails with -EIO and converts nothing. Clamp the length first. Fixes: 527851124d10 ("xfs: implement pNFS export operations") Cc: stable@vger.kernel.org Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Reviewed-by: Pankaj Raghav Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index 7ff83274a513..19eb18e1290c 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -328,9 +328,7 @@ xfs_fs_commit_blocks( if (start > size) continue; - end = start + maps[i].length; - if (end > size) - end = size; + end = start + min_t(u64, maps[i].length, size - start); length = end - start; if (!length) From 66d1ce09ebd72709f8d739630c39f440445173fd Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:02 +0900 Subject: [PATCH 13/19] xfs: cap the block count of xfs_iomap_write_direct() at one extent xfs_iomap_write_direct() reserves blocks for the whole count in an unsigned int. For a pNFS layout the client chooses that count, and an RW LAYOUTGET for 16 TiB wraps the reservation and shuts XFS down. xfs_bmapi_write() maps at most XFS_MAX_BMBT_EXTLEN blocks here, so cap the count at that. nfsd asks again for the rest. The other callers get the same mapping as before. Suggested-by: Darrick J. Wong Suggested-by: Christoph Hellwig Suggested-by: Pankaj Raghav Fixes: 527851124d10 ("xfs: implement pNFS export operations") Cc: stable@vger.kernel.org Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 99d48081c5ac..c3465204b8d5 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -285,6 +285,14 @@ xfs_iomap_write_direct( ASSERT(count_fsb > 0); + /* + * xfs_bmapi_write() maps at most XFS_MAX_BMBT_EXTLEN blocks in one + * mapping, so don't reserve blocks for more. A pNFS layout range + * comes from a client, and its block count may not even fit the + * 32-bit reservation. The caller maps the rest with a later call. + */ + count_fsb = min_t(xfs_filblks_t, count_fsb, XFS_MAX_BMBT_EXTLEN); + resaligned = xfs_aligned_fsb_count(offset_fsb, count_fsb, xfs_get_extsz_hint(ip)); if (unlikely(XFS_IS_REALTIME_INODE(ip))) { From 29f3c1be4e554c9dbc66e13333034e0947477cb6 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:03 +0900 Subject: [PATCH 14/19] xfs: clamp the pNFS layout range to the maximum file size xfs_fs_map_blocks() lets two bad layout ranges through. An offset of 2^63 or more is negative and passes "offset > limit". A length above the limit, such as NFS4_UINT64_MAX, wraps "offset > limit - length" and stays unclamped. With NFS4_UINT64_MAX, a read layout is built from an uninitialized mapping. Reject a negative offset and one at the limit, and clamp the length. This needs "xfs: cap the block count of xfs_iomap_write_direct() at one extent". Fixes: 527851124d10 ("xfs: implement pNFS export operations") Cc: stable@vger.kernel.org Signed-off-by: Daejun Park Reviewed-by: Darrick J. Wong Reviewed-by: Pankaj Raghav Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index 19eb18e1290c..7153e7a1e1de 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -176,10 +176,9 @@ xfs_fs_map_blocks( if (!write) limit = max(limit, round_up(i_size_read(inode), inode->i_sb->s_blocksize)); - if (offset > limit) + if (offset < 0 || offset >= limit) goto out_unlock; - if (offset > limit - length) - length = limit - offset; + length = min_t(u64, length, limit - offset); error = filemap_write_and_wait(inode->i_mapping); if (error) From 23ba20ef280cc2563d92b3581d5123b333569190 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:04 +0900 Subject: [PATCH 15/19] xfs: take the invalidate lock while mapping a pNFS layout xfs_fs_map_blocks() flushes the page cache under the iolock, but a write fault takes only the invalidate lock. A fault on a shared mapping can then add a delalloc extent before the range is mapped. That trips an ASSERT, or nfsd warns and refuses the layout. Take the invalidate lock with the iolock, as __xfs_file_fallocate() does. Not for stable: pNFS already leaves local writers to shared mappings unsynchronized. Suggested-by: Darrick J. Wong Link: https://lore.kernel.org/r/20260519145949.GH9555@frogsfrogsfrogs Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index 7153e7a1e1de..c1a6409f0092 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -168,8 +168,11 @@ xfs_fs_map_blocks( * similar to direct I/O, except that the synchronization is much more * complicated. See the comment near xfs_break_leased_layouts * for a detailed explanation. + * + * Take the invalidate lock as well, so that page faults cannot add + * delalloc extents to the range while it is being mapped. */ - xfs_ilock(ip, XFS_IOLOCK_EXCL); + xfs_ilock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL); error = -EINVAL; limit = mp->m_super->s_maxbytes; @@ -251,13 +254,13 @@ xfs_fs_map_blocks( if (error) goto out_unlock; } - xfs_iunlock(ip, XFS_IOLOCK_EXCL); + xfs_iunlock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL); error = xfs_bmbt_to_iomap(ip, iomap, &imap, 0, 0, seq); *device_generation = mp->m_generation; return error; out_unlock: - xfs_iunlock(ip, XFS_IOLOCK_EXCL); + xfs_iunlock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL); return error; } From 199eee7c677d535f5ff5d64a6192803848729aa0 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:05 +0900 Subject: [PATCH 16/19] xfs: add datasync and log_flushed arguments to xfs_log_force_inode() Add datasync and log_flushed arguments to xfs_log_force_inode() and move the body of xfs_fsync_flush_log() into it. The next patch needs a datasync log force in the pNFS LAYOUTCOMMIT path. xfs_file_fsync() calls it instead, and the other callers pass false and NULL. No functional change. Suggested-by: Christoph Hellwig Link: https://lore.kernel.org/r/20261014130124.GA15407@lst.de Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_export.c | 2 +- fs/xfs/xfs_file.c | 50 ++++---------------------------------------- fs/xfs/xfs_inode.c | 36 +++++++++++++++++++++++++++---- fs/xfs/xfs_inode.h | 3 ++- fs/xfs/xfs_pnfs.c | 2 +- fs/xfs/xfs_zone_gc.c | 2 +- 6 files changed, 41 insertions(+), 54 deletions(-) diff --git a/fs/xfs/xfs_export.c b/fs/xfs/xfs_export.c index 9afda52ed1a2..9dd6dd8c27c8 100644 --- a/fs/xfs/xfs_export.c +++ b/fs/xfs/xfs_export.c @@ -234,7 +234,7 @@ STATIC int xfs_fs_nfs_commit_metadata( struct inode *inode) { - return xfs_log_force_inode(XFS_I(inode)); + return xfs_log_force_inode(XFS_I(inode), false, NULL); } const struct export_operations xfs_export_operations = { diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index d2a2124bff54..396aca8c66a9 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -76,49 +76,7 @@ xfs_dir_fsync( struct xfs_inode *ip = XFS_I(file->f_mapping->host); trace_xfs_dir_fsync(ip); - return xfs_log_force_inode(ip); -} - -/* - * All metadata updates are logged, which means that we just have to push the - * journal to the required sequence number than holds the updates. We track - * datasync commits separately to full sync commits, and hence only need to - * select the correct sequence number for the log force here. - * - * We don't have to serialise against concurrent modifications, as we do not - * have to wait for modifications that have not yet completed. We define a - * transaction commit as completing when the commit sequence number is updated, - * hence if the sequence number has not updated, the sync operation has been - * run before the commit completed and we don't have to wait for it. - * - * If we have concurrent fsync/fdatasync() calls, the sequence numbers remain - * set on the log item until - at least - the journal flush completes. In - * reality, they are only cleared when the inode is fully unpinned (i.e. - * persistent in the journal and not dirty in the CIL), and so we rely on - * xfs_log_force_seq() either skipping sequences that have been persisted or - * waiting on sequences that are still in flight to correctly order concurrent - * sync operations. - */ -static int -xfs_fsync_flush_log( - struct xfs_inode *ip, - bool datasync, - int *log_flushed) -{ - struct xfs_inode_log_item *iip = ip->i_itemp; - xfs_csn_t seq = 0; - - spin_lock(&iip->ili_lock); - if (datasync) - seq = iip->ili_datasync_seq; - else - seq = iip->ili_commit_seq; - spin_unlock(&iip->ili_lock); - - if (!seq) - return 0; - - return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, log_flushed); + return xfs_log_force_inode(ip, false, NULL); } STATIC int @@ -151,7 +109,7 @@ xfs_file_fsync( * no modifications to this inode that need to be pushed out. */ if (ip->i_itemp) { - error = xfs_fsync_flush_log(ip, datasync, &log_flushed); + error = xfs_log_force_inode(ip, datasync, &log_flushed); if (error) return error; } @@ -1600,7 +1558,7 @@ __xfs_file_fallocate( } if (!error && xfs_file_sync_writes(file)) - error = xfs_log_force_inode(ip); + error = xfs_log_force_inode(ip, false, NULL); out_unlock: xfs_iunlock(ip, iolock); @@ -1751,7 +1709,7 @@ xfs_file_remap_range( goto out_unlock; if (xfs_file_sync_writes(file_in) || xfs_file_sync_writes(file_out)) - xfs_log_force_inode(dest); + xfs_log_force_inode(dest, false, NULL); out_unlock: xfs_iunlock2_remapping(src, dest); if (ret) diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index fceedb699bcf..cb61305e4b80 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2678,11 +2678,36 @@ xfs_irele( } /* - * Ensure all committed transactions touching the inode are written to the log. + * Ensure all committed transactions touching the inode are written to the log, + * or with @datasync only those that fdatasync has to wait for. + * + * All metadata updates are logged, which means that we just have to push the + * journal to the required sequence number that holds the updates. We track + * datasync commits separately to full sync commits, and hence only need to + * select the correct sequence number for the log force here. + * + * We don't have to serialise against concurrent modifications, as we do not + * have to wait for modifications that have not yet completed. We define a + * transaction commit as completing when the commit sequence number is updated, + * hence if the sequence number has not updated, the sync operation has been + * run before the commit completed and we don't have to wait for it. + * + * If we have concurrent fsync/fdatasync() calls, the sequence numbers remain + * set on the log item until - at least - the journal flush completes. In + * reality, they are only cleared when the inode is fully unpinned (i.e. + * persistent in the journal and not dirty in the CIL), and so we rely on + * xfs_log_force_seq() either skipping sequences that have been persisted or + * waiting on sequences that are still in flight to correctly order concurrent + * sync operations. + * + * If @log_flushed is not NULL, it is set when the log force wrote an iclog, + * which flushes the device caches before the log write. */ int xfs_log_force_inode( - struct xfs_inode *ip) + struct xfs_inode *ip, + bool datasync, + int *log_flushed) { struct xfs_inode_log_item *iip = ip->i_itemp; xfs_csn_t seq = 0; @@ -2691,12 +2716,15 @@ xfs_log_force_inode( return 0; spin_lock(&iip->ili_lock); - seq = iip->ili_commit_seq; + if (datasync) + seq = iip->ili_datasync_seq; + else + seq = iip->ili_commit_seq; spin_unlock(&iip->ili_lock); if (!seq) return 0; - return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, NULL); + return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, log_flushed); } /* diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 75eff68c1e39..b16eee99b567 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -595,7 +595,8 @@ int xfs_ifree(struct xfs_trans *, struct xfs_inode *); int xfs_itruncate_extents_flags(struct xfs_trans **, struct xfs_inode *, int, xfs_fsize_t, int); -int xfs_log_force_inode(struct xfs_inode *ip); +int xfs_log_force_inode(struct xfs_inode *ip, bool datasync, + int *log_flushed); void xfs_iunpin_wait(xfs_inode_t *); #define xfs_ipincount(ip) ((unsigned int) atomic_read(&ip->i_pincount)) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index c1a6409f0092..f4c4a02e5c9a 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -118,7 +118,7 @@ xfs_fs_map_update_inode( * Force the log so that the blocks handed out to the client are * guaranteed to be present even after a server crash. */ - return xfs_log_force_inode(ip); + return xfs_log_force_inode(ip, false, NULL); } /* diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 54b70ed2922f..cea5447a886d 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -1018,7 +1018,7 @@ xfs_zone_gc_reset_zones( struct xfs_gc_bio *chunk; struct bio *bio; - xfs_log_force_inode(rtg_rmap(rtg)); + xfs_log_force_inode(rtg_rmap(rtg), false, NULL); next = rtg_group(rtg)->xg_next_reset; rtg_group(rtg)->xg_next_reset = NULL; From 9cc3b0eda94da41efc4b4ac85b7e6f18cf72cd67 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 29 Oct 2026 11:23:06 +0900 Subject: [PATCH 17/19] xfs: make pNFS LAYOUTCOMMIT stable the way fdatasync does Every LAYOUTCOMMIT commits a synchronous transaction and forces the log. fdatasync after the same overwrite does not, because timestamps alone do not set ili_datasync_seq. Do what fdatasync does. Commit without XFS_TRANS_SYNC, force the log up to ili_datasync_seq, and flush the data device cache if that wrote no iclog. Conversions and size changes still force the log. As with fdatasync, a crash before the next log write can lose the new timestamps and the change attribute that nfsd derives from the ctime. Signed-off-by: Daejun Park Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index f4c4a02e5c9a..065875c8b7da 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -295,10 +295,12 @@ xfs_pnfs_validate_isize( * converting any unwritten extents, flushing the disk cache and updating the * time stamps. * - * Note that we rely on the caller to always send us a timestamp update so that - * we always commit a transaction here. If that stops being true we will have - * to manually flush the cache here similar to what the fsync code path does - * for datasyncs on files that have no dirty metadata. + * Like fdatasync, a commit that only updates the timestamps does not force the + * log: they are logged as XFS_ILOG_TIMESTAMP and reach the disk with a later + * log write. The log is forced up to the last change that a datasync needs, + * such as the unwritten extent conversions or a new size, and the cache of the + * device holding the file data is flushed if that log force did not write an + * iclog itself. */ static int xfs_fs_commit_blocks( @@ -312,6 +314,7 @@ xfs_fs_commit_blocks( struct xfs_trans *tp; struct timespec64 now; bool update_isize = false; + int log_flushed = 0; int error, i; loff_t size; @@ -368,7 +371,8 @@ xfs_fs_commit_blocks( xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); - xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + xfs_trans_log_inode(tp, ip, + update_isize ? XFS_ILOG_CORE : XFS_ILOG_TIMESTAMP); now = inode_set_ctime_current(inode); inode_set_atime_to_ts(inode, now); @@ -379,8 +383,13 @@ xfs_fs_commit_blocks( ip->i_disk_size = new_size; } - xfs_trans_set_sync(tp); error = xfs_trans_commit(tp); + if (error) + goto out_drop_iolock; + + error = xfs_log_force_inode(ip, true, &log_flushed); + if (!error && !log_flushed) + error = blkdev_issue_flush(xfs_inode_buftarg(ip)->bt_bdev); out_drop_iolock: xfs_iunlock(ip, XFS_IOLOCK_EXCL); From 30bb61651d2da5a502cf504bcdad16c093c1bb08 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 28 Oct 2026 09:09:39 +0100 Subject: [PATCH 18/19] xfs: fix recovery of partial attr removals When running xfs/753 on parent pointer-enabled file systems with DEBUG enabled, log recovery will eventually crash after a few dozen iterations due to an ASSERT that XFS_ATTR_INCOMPLETE is not already set when xfs_attr3_leaf_setflag is called: XFS: Assertion failed: (entry->flags & XFS_ATTR_INCOMPLETE) == 0, file: fs/xfs/libxfs/xfs_attr_leaf.c, line: 3045 This is because parent pointer use logged attributes, and the attribute intents do not track the work that has been done as the transaction rolls, and the recovery mechanism is to run the operation from scratch. Because of this various places check for XFS_DA_OP_RECOVERY skip work already done. But while recovery tolerates an attribute already fully removed, it currently trips the above assert for an attribute that already has the INCOMPLETE bit set, but still exists. Fix this by handling the case like all the others. Fixes: 1d08e11d04d2 ("xfs: Implement attr logging and replay") Cc: stable@vger.kernel.org # v5.19 Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr_leaf.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 6068fad9dc9e..82932b5c02fd 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -3042,7 +3042,14 @@ xfs_attr3_leaf_setflag( #endif entry = &xfs_attr3_leaf_entryp(leaf)[args->index]; - ASSERT((entry->flags & XFS_ATTR_INCOMPLETE) == 0); + if (entry->flags & XFS_ATTR_INCOMPLETE) { + if (!(args->op_flags & XFS_DA_OP_RECOVERY)) { + ASSERT(args->op_flags & XFS_DA_OP_RECOVERY); + return -EFSCORRUPTED; + } + return 0; + } + entry->flags |= XFS_ATTR_INCOMPLETE; xfs_trans_log_buf(args->trans, bp, XFS_DA_LOGRANGE(leaf, entry, sizeof(*entry))); From 7ba9663607b31108b4350b604a3c85ae86c67c19 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Tue, 3 Nov 2026 18:56:54 +0800 Subject: [PATCH 19/19] xfs: kill xfs_trans_set/clear_context() helpers Open code the helpers so that tp->t_pflags access is explicit, reducing the risk of misusing them outside the transaction owner thread as seen in xfs_btree_split_worker(). Suggested-by: Brian Foster Reviewed-by: Darrick J. Wong Reviewed-by: Brian Foster Signed-off-by: Kefeng Wang Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree.c | 4 ---- fs/xfs/libxfs/xfs_trans.h | 14 -------------- fs/xfs/xfs_trans.c | 10 +++++++--- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/fs/xfs/libxfs/xfs_btree.c b/fs/xfs/libxfs/xfs_btree.c index 5ce49a8b9de1..5d1d73f99ce0 100644 --- a/fs/xfs/libxfs/xfs_btree.c +++ b/fs/xfs/libxfs/xfs_btree.c @@ -3009,10 +3009,6 @@ xfs_btree_split_worker( struct xfs_btree_split_args, work); unsigned int nofs_flags; - /* - * Don't use xfs_trans_set_context() here: it would overwrite the - * caller's saved NOFS state in tp->t_pflags. Use a local scope. - */ nofs_flags = memalloc_nofs_save(); args->result = __xfs_btree_split(args->cur, args->level, args->ptrp, diff --git a/fs/xfs/libxfs/xfs_trans.h b/fs/xfs/libxfs/xfs_trans.h index 05b3cc19a311..b0c7724d0619 100644 --- a/fs/xfs/libxfs/xfs_trans.h +++ b/fs/xfs/libxfs/xfs_trans.h @@ -264,20 +264,6 @@ int xfs_trans_alloc_dir(struct xfs_inode *dp, struct xfs_trans_res *resv, struct xfs_inode *ip, unsigned int *dblocks, struct xfs_trans **tpp, int *nospace_error); -static inline void -xfs_trans_set_context( - struct xfs_trans *tp) -{ - tp->t_pflags = memalloc_nofs_save(); -} - -static inline void -xfs_trans_clear_context( - struct xfs_trans *tp) -{ - memalloc_nofs_restore(tp->t_pflags); -} - static inline void xfs_trans_cancel( struct xfs_trans *tp) diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 8f0c308f312f..973e82e1e323 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -72,7 +72,7 @@ xfs_trans_free( xfs_extent_busy_clear(&tp->t_busy, false); trace_xfs_trans_free(tp, _RET_IP_); - xfs_trans_clear_context(tp); + memalloc_nofs_restore(tp->t_pflags); if (!(tp->t_flags & XFS_TRANS_NO_WRITECOUNT)) sb_end_intwrite(tp->t_mountp->m_super); xfs_trans_free_dqinfo(tp); @@ -220,7 +220,11 @@ __xfs_trans_alloc( tp = kmem_cache_zalloc(xfs_trans_cache, GFP_KERNEL | __GFP_NOFAIL); if (!(flags & XFS_TRANS_NO_WRITECOUNT)) sb_start_intwrite(mp->m_super); - xfs_trans_set_context(tp); + /* + * Save NOFS state for xfs_trans_free() to restore. Only the + * transaction owner thread may touch tp->t_pflags. + */ + tp->t_pflags = memalloc_nofs_save(); tp->t_flags = flags; tp->t_mountp = mp; INIT_LIST_HEAD(&tp->t_items); @@ -1038,7 +1042,7 @@ xfs_trans_roll( * xfs_trans_free. Set it again here before doing memory * allocations. */ - xfs_trans_set_context(tp); + tp->t_pflags = memalloc_nofs_save(); if (error) return error;