From b7e53968cb8882c2d276429ea8550848a4940874 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sun, 12 Jul 2026 03:42:56 +0000 Subject: [PATCH 01/18] xfs: propagate errors from xfs_rtginode_load xfs_rtginode_ensure() treats every xfs_rtginode_load() error other than -ENOENT as success. This can leave the realtime group inode unset after an I/O, allocation, or corruption error. Growfs then continues as though the inode had been loaded. Only -ENOENT means that the inode needs to be created. Return all other errors to the growfs caller. Fixes: ae897e0bed0f ("xfs: support creating per-RTG files in growfs") Cc: stable@vger.kernel.org Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_rtalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c index 7a3f97686989..84efe5a8fb11 100644 --- a/fs/xfs/xfs_rtalloc.c +++ b/fs/xfs/xfs_rtalloc.c @@ -737,7 +737,7 @@ xfs_rtginode_ensure( xfs_trans_cancel(tp); if (error != -ENOENT) - return 0; + return error; return xfs_rtginode_create(rtg, type, true); } From 87b2a82e135ba81e49d82b59e3d72468cb66ea98 Mon Sep 17 00:00:00 2001 From: Qiang Ma Date: Thu, 16 Jul 2026 17:22:45 +0800 Subject: [PATCH 02/18] xfs: clear zapped attr fork state when bmap repair finds no attr fork xfstests xfs/377 can make xfs_scrub repeatedly check and repair the attr block map after inode repair zaps an attr fork. When inode repair zaps an attr fork, it records XFS_SICK_INO_BMBTA_ZAPPED so that scrub/repair can revisit the attr fork block map. If the fork has been reset to an empty state and removed, BMBTA repair has no attr fork mappings to rebuild and can return success. The post-repair scrub then runs with XREP_ALREADY_FIXED set, which means xchk_file_looks_zapped() deliberately ignores the stale zapped health bit and asks xchk_bmap() to check the current attr fork. For an absent attr fork, xchk_bmap() returns -ENOENT. Returning that error prevents xchk_bmap_attr() from marking XFS_SICK_INO_BMBTA_ZAPPED healthy, leaving the zapped health state behind even though there are no attr fork mappings left to check. Treat -ENOENT during post-repair BMBTA revalidation as a clean result for the zapped attr fork: clear XFS_SICK_INO_BMBTA_ZAPPED and return success. Keep the existing -ENOENT behavior for ordinary scrubs of absent attr forks. Fixes: d9041681dd2f ("xfs: set inode sick state flags when we zap either ondisk fork") Cc: stable@vger.kernel.org # v6.8 Reviewed-by: "Darrick J. Wong" Signed-off-by: Qiang Ma Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 70028da1aacc..401c278725d2 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -1170,6 +1170,11 @@ xchk_bmap_attr( } error = xchk_bmap(sc, XFS_ATTR_FORK); + /* A repaired, empty attr fork no longer has mappings to check. */ + if (error == -ENOENT && (sc->flags & XREP_ALREADY_FIXED)) { + xchk_mark_healthy_if_clean(sc, XFS_SICK_INO_BMBTA_ZAPPED); + return 0; + } if (error) return error; From cc3144da377de5fb422d44a2311f978623f7c900 Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Tue, 14 Jul 2026 14:56:12 -0700 Subject: [PATCH 03/18] xfs: fix off-by-one in rtrefcount btree root level validation xfs_rtrefcountbt_compute_maxlevels() sets mp->m_rtrefc_maxlevels = min(d_maxlevels, r_maxlevels) + 1; where the trailing "+ 1" already accounts for the inode-root level, so the deepest valid on-disk root level is m_rtrefc_maxlevels - 1 and a cursor must satisfy bc_nlevels <= bc_maxlevels (= m_rtrefc_maxlevels). The two on-disk validation paths, xfs_rtrefcountbt_verify() and xfs_iformat_rtrefcount(), check the root level with ">" instead of ">=", so a crafted rtreflink (metadir + realtime + reflink) image whose /rtgroups/N.refcount inode has bb_level == m_rtrefc_maxlevels is accepted on mount. xfs_rtrefcountbt_init_cursor() then sets bc_nlevels = bb_level + 1, exceeding bc_maxlevels by one. Since the xfs_rtrefcountbt_cur slab object is sized for exactly bc_maxlevels entries, the first btree op on such a cursor indexes bc_levels[m_rtrefc_maxlevels] past the end of the object. This is reached by the first rtrefcount cursor built after mount, via log/CoW recovery (xfs_reflink_recover_cow() during xfs_mountfs()) or an FS_IOC_GETFSMAP over the realtime device. Reject a root level equal to m_rtrefc_maxlevels, matching the ">=" form already used by the sibling data-device refcount/rmap verifiers and the in-memory rtrmap verifier. BUG: KASAN: slab-out-of-bounds in xfs_btree_lookup (fs/xfs/libxfs/xfs_btree.c:2101) Write of size 2 at addr ffff888018391658 by task exploit/144 xfs_btree_lookup (fs/xfs/libxfs/xfs_btree.c:2101) xfs_btree_query_range (fs/xfs/libxfs/xfs_btree.c:5308) xfs_refcount_recover_cow_leftovers (fs/xfs/libxfs/xfs_refcount.c:2113) xfs_reflink_recover_cow (fs/xfs/xfs_reflink.c:1085) xlog_recover_finish (fs/xfs/xfs_log_recover.c:3551) xfs_mountfs (fs/xfs/xfs_mount.c:1158) xfs_fs_fill_super (fs/xfs/xfs_super.c:1940) get_tree_bdev_flags (fs/super.c:1634) vfs_get_tree (fs/super.c:1694) path_mount (fs/namespace.c:4161) __x64_sys_mount (fs/namespace.c:4367) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) The buggy address belongs to the cache xfs_rtrefcountbt_cur of size 216 The buggy address is located 8 bytes to the right of allocated 216-byte region [ffff888018391578, ffff888018391650) Kernel panic - not syncing: Fatal exception Cc: stable@vger.kernel.org # v6.14 Fixes: 9abe03a0e4f978 ("xfs: introduce realtime refcount btree ondisk definitions") Reported-by: Weiming Shi Signed-off-by: Xiang Mei Reviewed-by: "Darrick J. Wong" Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index f27b80a199ba..22acc1411aac 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -201,7 +201,7 @@ xfs_rtrefcountbt_verify( if (fa) return fa; level = be16_to_cpu(block->bb_level); - if (level > mp->m_rtrefc_maxlevels) + if (level >= mp->m_rtrefc_maxlevels) return __this_address; return xfs_btree_fsblock_verify(bp, mp->m_rtrefc_mxr[level != 0]); @@ -651,7 +651,7 @@ xfs_iformat_rtrefcount( numrecs = be16_to_cpu(dfp->bb_numrecs); level = be16_to_cpu(dfp->bb_level); - if (level > mp->m_rtrefc_maxlevels || + if (level >= mp->m_rtrefc_maxlevels || xfs_rtrefcount_droot_space_calc(level, numrecs) > dsize) { xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE); return -EFSCORRUPTED; From 813f8136a2ce1fee266d02a7df73db6e8a541604 Mon Sep 17 00:00:00 2001 From: Ibrahim Hashimov Date: Wed, 15 Jul 2026 09:17:23 +0200 Subject: [PATCH 04/18] xfs: bounds-check buffer log item's dirty bitmap xlog_recover_do_reg_buffer() replays each dirty region described by a buffer log item's bitmap into the buffer read for that item: memcpy(xfs_buf_offset(bp, (uint)bit << XFS_BLF_SHIFT), item->ri_buf[i].iov_base, nbits << XFS_BLF_SHIFT); The destination offset (bit/nbits, from the logged dirty bitmap) and the buffer size (from the logged blf_len) are both attacker-controlled and otherwise unrelated, yet the only thing bounding the copy is an ASSERT(), which compiles away on production kernels. A crafted image logging a small blf_len together with a bitmap bit past the end of that buffer drives the memcpy() past the buffer's allocation, corrupting adjacent kernel heap during mount-time log recovery. This is reachable by anyone who can get a crafted image mounted -- the malicious-filesystem threat model XFS already guards against elsewhere. Turn the ASSERT() into a real XFS_IS_CORRUPT() check that aborts recovery of the buffer with -EFSCORRUPTED, consistent with the validate-and-fail idiom already used in xlog_recover_do_inode_buffer() and xfs_dquot_item_recover.c. xlog_recover_do_reg_buffer() therefore becomes STATIC int and its three callers propagate the error. Found and confirmed with KASAN on a CONFIG_XFS_DEBUG=n build: the crafted image trips a slab-out-of-bounds write before this change and fails recovery cleanly with -EFSCORRUPTED after it. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Ibrahim Hashimov Reviewed-by: "Darrick J. Wong" Reviewed-by: Brian Foster Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf_item_recover.c | 57 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c index 02b95b89d1b5..240deb3f7827 100644 --- a/fs/xfs/xfs_buf_item_recover.c +++ b/fs/xfs/xfs_buf_item_recover.c @@ -461,7 +461,7 @@ xlog_recover_validate_buf_type( * given buffer. The bitmap in the buf log format structure indicates * where to place the logged data. */ -STATIC void +STATIC int xlog_recover_do_reg_buffer( struct xfs_mount *mp, struct xlog_recover_item *item, @@ -489,8 +489,24 @@ xlog_recover_do_reg_buffer( ASSERT(nbits > 0); ASSERT(item->ri_buf[i].iov_base != NULL); ASSERT(item->ri_buf[i].iov_len % XFS_BLF_CHUNK == 0); - ASSERT(BBTOB(bp->b_length) >= - ((uint)bit << XFS_BLF_SHIFT) + (nbits << XFS_BLF_SHIFT)); + /* + * The bitmap is only trustworthy to the extent that it + * describes a region that actually fits inside the buffer we + * read in based on the (attacker-controlled) blf_len. Do not + * rely on an ASSERT() for this -- it compiles away entirely on + * non-DEBUG kernels, which is exactly where this matters, so + * validate it for real and abort recovery of this buffer rather + * than copying past the end of it. + */ + if (XFS_IS_CORRUPT(mp, BBTOB(bp->b_length) < + ((uint)bit << XFS_BLF_SHIFT) + + (nbits << XFS_BLF_SHIFT))) { + xfs_alert(mp, + "Bad buffer log item dirty bitmap (bit %d, nbits %d) for %d-byte buffer at daddr 0x%llx.", + bit, nbits, BBTOB(bp->b_length), + xfs_buf_daddr(bp)); + return -EFSCORRUPTED; + } /* * The dirty regions logged in the buffer, even though @@ -544,6 +560,7 @@ xlog_recover_do_reg_buffer( ASSERT(i == item->ri_total); xlog_recover_validate_buf_type(mp, bp, buf_f, current_lsn); + return 0; } /* @@ -552,10 +569,10 @@ xlog_recover_do_reg_buffer( * (ie. USR or GRP), then just toss this buffer away; don't recover it. * Else, treat it as a regular buffer and do recovery. * - * Return false if the buffer was tossed and true if we recovered the buffer to - * indicate to the caller if the buffer needs writing. + * Return 0 if the buffer was not recovered (tossed), 1 if it was recovered and + * needs writing, or a negative errno if recovery of the buffer failed. */ -STATIC bool +STATIC int xlog_recover_do_dquot_buffer( struct xfs_mount *mp, struct xlog *log, @@ -564,6 +581,7 @@ xlog_recover_do_dquot_buffer( struct xfs_buf_log_format *buf_f) { uint type; + int error; trace_xfs_log_recover_buf_dquot_buf(log, buf_f); @@ -571,7 +589,7 @@ xlog_recover_do_dquot_buffer( * Filesystems are required to send in quota flags at mount time. */ if (!mp->m_qflags) - return false; + return 0; type = 0; if (buf_f->blf_flags & XFS_BLF_UDQUOT_BUF) @@ -584,10 +602,12 @@ xlog_recover_do_dquot_buffer( * This type of quotas was turned off, so ignore this buffer */ if (log->l_quotaoffs_flag & type) - return false; + return 0; - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN); - return true; + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN); + if (error) + return error; + return 1; } /* @@ -724,7 +744,9 @@ xlog_recover_do_primary_sb_buffer( xfs_rgnumber_t orig_rgcount = mp->m_sb.sb_rgcount; int error; - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + if (error) + return error; if (orig_agcount == 0) { xfs_alert(mp, "Trying to grow file system without AGs"); @@ -1081,11 +1103,11 @@ xlog_recover_buf_commit_pass2( goto out_release; } else if (buf_f->blf_flags & (XFS_BLF_UDQUOT_BUF|XFS_BLF_PDQUOT_BUF|XFS_BLF_GDQUOT_BUF)) { - bool dirty; - - dirty = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f); - if (!dirty) + error = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f); + if (error <= 0) goto out_release; + /* write dirty buffer */ + error = 0; } else if ((xfs_blft_from_flags(buf_f) & XFS_BLFT_SB_BUF) && xfs_buf_daddr(bp) == 0) { error = xlog_recover_do_primary_sb_buffer(mp, item, bp, buf_f, @@ -1105,7 +1127,10 @@ xlog_recover_buf_commit_pass2( xfs_buf_relse(rtsb_bp); } } else { - xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn); + error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, + current_lsn); + if (error) + goto out_release; } /* From d852729c5f4f830fbe7413df032e29459b3daf83 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Sun, 19 Jul 2026 23:11:24 +0800 Subject: [PATCH 05/18] xfs: handle NULL b_addr in xfs_buf_free When xfs_buf_alloc_backing_mem() fails, xfs_buf_free() is called with bp->b_addr still NULL. The code falls through to the folio_put path which calls virt_to_folio(NULL), dereferencing an invalid address and causing a kernel crash. Call Trace: xfs_buf_free+0x25f/0x510 xfs_buf_alloc+0xc98/0x19b0 xfs_buf_find_insert+0x55/0x14d0 xfs_buf_get_map+0x122b/0x17c0 xfbtree_init_leaf_block+0x11c/0x4a0 xfbtree_init+0x1bb/0x460 xrep_rmap_setup_scan+0x100/0x1f0 xrep_rmapbt+0x41/0xc0 Fix this by skipping folio_put() when bp->b_addr is NULL. Fixes: 5076a6040ca1 ("xfs: support in-memory buffer cache targets") Reported-by: syzbot+94c22d92f72f5a235b7d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=94c22d92f72f5a235b7d Signed-off-by: Yun Zhou Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_buf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index e1465e950acc..48d7dfd3e15f 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -114,7 +114,7 @@ xfs_buf_free( vfree(bp->b_addr); else if (bp->b_flags & _XBF_KMEM) kfree(bp->b_addr); - else + else if (bp->b_addr) folio_put(virt_to_folio(bp->b_addr)); call_rcu(&bp->b_rcu, xfs_buf_free_callback); From 7aa67044e7d8b6eebc44b25745f16c83ccc40a94 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 20 Jul 2026 16:08:47 +0200 Subject: [PATCH 06/18] xfs: update BDI {io,ra}_pages values based on the RT device limits When using XFS with a main device on an SSD that stores metadata and a RT device to store data on a HDD, we fail to take the I/O sizes for the RT device into accounting, leading to up to 5% slower read performance when using an SSD for metadata vs storing data and metadata on the HDD. Fix this up by taking the RT settings into account at mount an restoring the old settings at unmount time, unless the BDI settings have changed from those set by XFS. Reported-by: Filip Blagojevic Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Reviewed-by: Damien Le Moal Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_mount.h | 7 ++++++ fs/xfs/xfs_super.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 66a02d1b9ad7..216a38a354e7 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -349,6 +349,13 @@ typedef struct xfs_mount { /* Index of uuid record in the uuid xarray. */ unsigned int m_uuid_table_index; + + /* + * Old io_pages/ra_pages valued in the main bdev BDI, and our initial + * calculated values. + */ + unsigned long m_old_io_pages, m_initial_io_pages; + unsigned long m_old_ra_pages, m_initial_ra_pages; } xfs_mount_t; #define M_IGEO(mp) (&(mp)->m_ino_geo) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index eac7f9503805..323aa1228b6c 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -545,6 +545,52 @@ xfs_open_devices( return error; } +/* + * When using a RT device some or all data I/O is using the RT device, but + * the BDI is inherited from the main data device. When the underlying block + * device for the RT device has larger I/O sizes, the BDI settings might be + * incorrect, which is especially bad if the main device is a SSD and the + * RT device is a HDD, as the io_opt fixup in blk_apply_bdi_limits is missing + * for this case. + * + * Update the BDI values to the max of the data and RT device to cover our + * bases. + */ +static void +xfs_update_bdi_rahead( + struct xfs_mount *mp) +{ + struct backing_dev_info *rt_bdi = + mp->m_rtdev_targp->bt_bdev->bd_disk->bdi; + struct backing_dev_info *sb_bdi = mp->m_super->s_bdi; + + mp->m_old_io_pages = sb_bdi->io_pages; + mp->m_old_ra_pages = sb_bdi->ra_pages; + + sb_bdi->io_pages = mp->m_initial_io_pages = + max(sb_bdi->io_pages, rt_bdi->io_pages); + sb_bdi->ra_pages = mp->m_initial_ra_pages = + max(sb_bdi->ra_pages, rt_bdi->ra_pages); +} + +static void +xfs_restore_bdi_rahead( + struct xfs_mount *mp) +{ + struct backing_dev_info *sb_bdi = mp->m_super->s_bdi; + + if (sb_bdi->io_pages == mp->m_initial_io_pages) + sb_bdi->io_pages = mp->m_old_io_pages; + else + xfs_info(mp, "io_pages changed from %lu to %lu, not restoring.", + mp->m_initial_io_pages, sb_bdi->io_pages); + if (sb_bdi->ra_pages == mp->m_initial_ra_pages) + sb_bdi->ra_pages = mp->m_old_ra_pages; + else + xfs_info(mp, "ra_pages changed from %lu to %lu, not restoring.", + mp->m_initial_ra_pages, sb_bdi->ra_pages); +} + /* * Setup xfs_mount buffer target pointers based on superblock */ @@ -582,6 +628,7 @@ xfs_setup_devices( mp->m_sb.sb_sectsize, mp->m_sb.sb_rblocks); if (error) return error; + xfs_update_bdi_rahead(mp); } return 0; @@ -2280,8 +2327,12 @@ static void xfs_kill_sb( struct super_block *sb) { + struct xfs_mount *mp = XFS_M(sb); + + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) + xfs_restore_bdi_rahead(mp); kill_block_super(sb); - xfs_mount_free(XFS_M(sb)); + xfs_mount_free(mp); } static struct file_system_type xfs_fs_type = { From 270ffcd9b0a46254fbf3079d0e3341db7d7ec0e6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:23:46 -0700 Subject: [PATCH 07/18] xfs: check cowextsize in xrep_inode_cowextsize LOLLM points out that the function that corrects cowextsize should check i_cowextsize, not i_extsize. Cc: stable@vger.kernel.org # v6.14 Fixes: a9600db96f74af ("xfs: detect and repair misaligned rtinherit directory cowextsize hints") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 3ec41c198351..b88427a4460c 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1960,7 +1960,7 @@ xrep_inode_cowextsize( /* Fix misaligned CoW extent size hints on a directory. */ if ((sc->ip->i_diflags & XFS_DIFLAG_RTINHERIT) && (sc->ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) && - sc->ip->i_extsize % sc->mp->m_sb.sb_rextsize > 0) { + xfs_extlen_to_rtxmod(sc->mp, sc->ip->i_cowextsize) > 0) { sc->ip->i_cowextsize = 0; sc->ip->i_diflags2 &= ~XFS_DIFLAG2_COWEXTSIZE; } From 61606f8846a9da8ab7d2d36ff4617134c9d6df1b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:02 -0700 Subject: [PATCH 08/18] xfs: fix transaction block reservation in xrep_rtbitmap LOLLM pointed out an inconsistency in the block reservation code in xrep_rtbitmap. The first is that the reservation computation is not consistent between the code that sets up the repair and the code that tries to avoid exceeding the transaction reservation once we know how big the rtbitmap really must be. As a result, the logic doesn't work. In fixing that, a second problem emerges: if we do readjust, we ask for the entire reservation all over again. We really only need the delta, so ask only for that. Fix all these problems by hoisting the computation to a trivial helper so that it gets used in both places. Cc: stable@vger.kernel.org # v6.14 Fixes: 8defee8dff2b20 ("xfs: online repair of realtime bitmaps for a realtime group") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtbitmap_repair.c | 46 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index dc64902d6c25..442a17bf9720 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -36,6 +36,24 @@ /* rt bitmap content repairs */ +/* + * Reserve enough blocks to write out a completely new bitmap file, plus twice + * as many blocks as we would need if we can only allocate one block per data + * fork mapping. This should cover the preallocation of the temporary file and + * exchanging the extent mappings. + * + * We cannot use xfs_exchmaps_estimate because we have not yet constructed the + * replacement bitmap and therefore do not know how many extents it will use. + * By the time we do, we will have a dirty transaction (which we cannot drop + * because we cannot drop the rtbitmap ILOCK) and cannot ask for more + * reservation. + */ +static inline unsigned long long +xrep_rtbitmap_calc_blocks(struct xfs_mount *mp, unsigned long long blocks) +{ + return blocks + (xfs_bmbt_calc_size(mp, blocks) * 2); +} + /* Set up to repair the realtime bitmap for this group. */ int xrep_setup_rtbitmap( @@ -56,20 +74,7 @@ xrep_setup_rtbitmap( if (error) return error; - /* - * Reserve enough blocks to write out a completely new bitmap file, - * plus twice as many blocks as we would need if we can only allocate - * one block per data fork mapping. This should cover the - * preallocation of the temporary file and exchanging the extent - * mappings. - * - * We cannot use xfs_exchmaps_estimate because we have not yet - * constructed the replacement bitmap and therefore do not know how - * many extents it will use. By the time we do, we will have a dirty - * transaction (which we cannot drop because we cannot drop the - * rtbitmap ILOCK) and cannot ask for more reservation. - */ - blocks += xfs_bmbt_calc_size(mp, blocks) * 2; + blocks = xrep_rtbitmap_calc_blocks(mp, mp->m_sb.sb_rbmblocks); if (blocks > UINT_MAX) return -EOPNOTSUPP; @@ -512,7 +517,7 @@ xrep_rtbitmap( struct xchk_rtbitmap *rtb = sc->buf; struct xfs_mount *mp = sc->mp; struct xfs_group *xg = rtg_group(sc->sr.rtg); - unsigned long long blocks = 0; + unsigned long long blocks; unsigned int busy_gen; int error; @@ -532,15 +537,20 @@ xrep_rtbitmap( * figure out if we need to adjust the block reservation in the * transaction. */ - blocks = xfs_bmbt_calc_size(mp, rtb->rbmblocks); + blocks = xrep_rtbitmap_calc_blocks(mp, rtb->rbmblocks); if (blocks > UINT_MAX) return -EOPNOTSUPP; if (blocks > rtb->resblks) { - error = xfs_trans_reserve_more(sc->tp, blocks, 0); + uint64_t delta = blocks - rtb->resblks; + + if (delta > UINT_MAX) + return -EOPNOTSUPP; + + error = xfs_trans_reserve_more(sc->tp, delta, 0); if (error) return error; - rtb->resblks += blocks; + rtb->resblks += delta; } /* Fix inode core and forks. */ From 8ed78104722b57df69478b0b1608a408c1036085 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:17 -0700 Subject: [PATCH 09/18] xfs: rtsummary scrub should treat rtbitmap corruption errors as an xref error LOLLM notes the discrepancy between the comment saying that this is an xref error and the code, which marks a (non-xref) corruption error. This appears to be a regression. Cc: stable@vger.kernel.org # v6.13 Fixes: e3088ae2dcae3c ("xfs: move RT bitmap and summary information to the rtgroup") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtsummary.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtsummary.c b/fs/xfs/scrub/rtsummary.c index 78f72a046887..546b335ade13 100644 --- a/fs/xfs/scrub/rtsummary.c +++ b/fs/xfs/scrub/rtsummary.c @@ -358,7 +358,7 @@ xchk_rtsummary( * EFSCORRUPTED means the rtbitmap is corrupt, which is an xref * error since we're checking the summary file. */ - xchk_ip_set_corrupt(sc, rbmip); + xchk_ip_xref_set_corrupt(sc, rbmip); return 0; } if (error) From 49933254ba8d421ed706cbe6ed0fbc264e572cab Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:33 -0700 Subject: [PATCH 10/18] xfs: zero i_nlink before repair puts inode on unlinked list LOLLM observes that we don't reset i_nlink if we encounter a file with no parent that isn't on the unlinked list. This causes unnecessary assertion trips on debugging kernels and an inconsistent file, so let's fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: 669dfe883c8e20 ("xfs: update the unlinked list when repairing link counts") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/nlinks_repair.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c index fbc2ff809fc0..09e097e16689 100644 --- a/fs/xfs/scrub/nlinks_repair.c +++ b/fs/xfs/scrub/nlinks_repair.c @@ -232,9 +232,14 @@ xrep_nlinks_repair_inode( * unlinked list, put it on the unlinked list. */ if (total_links == 0 && !xfs_inode_on_unlinked_list(ip)) { + if (actual_nlink) + clear_nlink(VFS_I(ip)); error = xfs_iunlink(sc->tp, ip); - if (error) + if (error) { + if (actual_nlink) + set_nlink(VFS_I(ip), actual_nlink); goto out_trans; + } dirty = true; } From b28d23c51635b646784a2a62c71ba99458c07d5e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 20 Jul 2026 20:24:48 -0700 Subject: [PATCH 11/18] xfs: mark nonzero sb_gquotino as corrupt on metadir filesystems On a metadir filesystem, the superblock gquotino field is always zero because we find the quota inodes through the metadata directory tree. A nonzero value is therefore a corruption (as pointed out by LOLLM) so mark the failure as such. Cc: stable@vger.kernel.org # v6.13 Fixes: 06b20ef09ba163 ("xfs: check pre-metadir fields correctly") Signed-off-by: "Darrick J. Wong" Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 9ed053b5f061..62ed5eaf08fb 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -266,7 +266,7 @@ xchk_superblock( xchk_block_set_corrupt(sc, bp); if (sb->sb_gquotino != cpu_to_be64(0)) - xchk_block_set_preen(sc, bp); + xchk_block_set_corrupt(sc, bp); } else { if (sb->sb_uquotino != cpu_to_be64(mp->m_sb.sb_uquotino)) xchk_block_set_preen(sc, bp); From 86e332447d726127e042b28f7c65ca3784443794 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Mon, 6 Jul 2026 10:41:16 +0200 Subject: [PATCH 12/18] 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 @@ out_unlock: 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 13/18] 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 @@ out_unlock: } /* - * 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 14/18] 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 15/18] 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 16/18] 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 17/18] 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 @@ out_teardown: * 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 18/18] 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);