* [PATCH v3 1/6] xfs: initialise error in xfs_defer_finish_one()
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
2026-08-10 23:06 ` [PATCH v3 2/6] xfs: give the deferred barrier op type a name Javier Tia
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel, stable
xfs_defer_finish_one() declares error without an initialiser and only
assigns it inside the loop over dfp->dfp_work. When that list is empty
the loop body never runs, control falls through to the "Done with the
dfp, free it" path, and the function returns an indeterminate value.
An item-less pending item reaches this through xfs_defer_add_barrier(),
which xfs_reap_ag_blocks() uses on any CONFIG_XFS_ONLINE_REPAIR kernel.
xfs_defer_finish_noroll() treats any non-EAGAIN return as fatal, so a
non-zero stack value turns a successful barrier into a
SHUTDOWN_CORRUPT_INCORE in the middle of a repair. Zero is the correct
result: reaching the free path means the item loop drained without a
non-zero error.
Fixes: 3f3cec031099 ("xfs: force small EFIs for reaping btree extents")
Cc: <stable@vger.kernel.org>
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_defer.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 89501e8bd2f8..843c33304441 100644
--- a/fs/xfs/libxfs/xfs_defer.c
+++ b/fs/xfs/libxfs/xfs_defer.c
@@ -583,7 +583,7 @@ xfs_defer_finish_one(
const struct xfs_defer_op_type *ops = dfp->dfp_ops;
struct xfs_btree_cur *state = NULL;
struct list_head *li, *n;
- int error;
+ int error = 0;
trace_xfs_defer_pending_finish(tp->t_mountp, dfp);
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 2/6] xfs: give the deferred barrier op type a name
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
2026-08-10 23:06 ` [PATCH v3 1/6] xfs: initialise error in xfs_defer_finish_one() Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
2026-08-10 23:06 ` [PATCH v3 3/6] xfs: report the error that made deferred work shut down the fs Javier Tia
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel
xfs_barrier_defer_type is the only xfs_defer_op_type with no .name.
Every other one carries a short string used for tracing and reporting:
attr, bmap, extent_free, agfl_free, rtextent_free, refcount,
rtrefcount, rmap, rtrmap and exchmaps.
That has been harmless because nothing dereferences the field, but it
leaves a NULL in a table where every other entry is populated, so the
first caller to print it gets "(null)" in the kernel and undefined
behaviour in the userspace libxfs build of this file, where xfs_alert
lands in fprintf. xfs_defer_add() already treats a missing member of
this table as worth shutting the filesystem down for, so an unpopulated
one is out of step with how the file handles its own ops tables.
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_defer.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 843c33304441..75f0d37914d5 100644
--- a/fs/xfs/libxfs/xfs_defer.c
+++ b/fs/xfs/libxfs/xfs_defer.c
@@ -229,6 +229,7 @@ xfs_defer_barrier_cancel_item(
}
static const struct xfs_defer_op_type xfs_barrier_defer_type = {
+ .name = "barrier",
.max_items = 1,
.create_intent = xfs_defer_barrier_create_intent,
.abort_intent = xfs_defer_barrier_abort_intent,
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 3/6] xfs: report the error that made deferred work shut down the fs
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
2026-08-10 23:06 ` [PATCH v3 1/6] xfs: initialise error in xfs_defer_finish_one() Javier Tia
2026-08-10 23:06 ` [PATCH v3 2/6] xfs: give the deferred barrier op type a name Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
2026-08-10 23:06 ` [PATCH v3 4/6] xfs: correct the parent pointer space reservation comment Javier Tia
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel
When a deferred operation fails and shuts the filesystem down,
xfs_defer_finish_noroll() reports neither the errno nor which operation
originated it, so the log cannot tell a transient -ENOSPC from real
corruption. Report the operation type, errno and remaining reservation.
trace_xfs_defer_finish_error() runs after xfs_force_shutdown(), which
BUGs under fs.xfs.panic_mask and so never fires for the first failure;
move it ahead of the shutdown and mirror it to xfs_alert() for systems
without tracing armed. Capture the op name while the item is live (dfp
is freed once its work list drains) and suppress the alert once the fs is
already down.
Signed-off-by: Javier Tia <floss@jetm.me>
---
fs/xfs/libxfs/xfs_defer.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c
index 75f0d37914d5..3152acdc335d 100644
--- a/fs/xfs/libxfs/xfs_defer.c
+++ b/fs/xfs/libxfs/xfs_defer.c
@@ -656,6 +656,7 @@ xfs_defer_finish_noroll(
struct xfs_trans **tp)
{
struct xfs_defer_pending *dfp = NULL;
+ const char *what = "chain";
int error = 0;
LIST_HEAD(dop_pending);
LIST_HEAD(dop_paused);
@@ -705,9 +706,17 @@ xfs_defer_finish_noroll(
struct xfs_defer_pending, dfp_list);
if (!dfp)
break;
+ what = dfp->dfp_ops->name;
error = xfs_defer_finish_one(*tp, dfp);
if (error && error != -EAGAIN)
goto out_shutdown;
+ /*
+ * A finished item is no longer a candidate for a later
+ * failure. An -EAGAIN one is not finished, so it keeps the
+ * attribution across the roll that completes it.
+ */
+ if (!error)
+ what = "chain";
}
/* Requeue the paused items in the outgoing transaction. */
@@ -719,8 +728,12 @@ xfs_defer_finish_noroll(
out_shutdown:
list_splice_tail_init(&dop_paused, &dop_pending);
xfs_defer_trans_abort(*tp, &dop_pending);
- xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE);
trace_xfs_defer_finish_error(*tp, error);
+ if (!xfs_is_shutdown((*tp)->t_mountp))
+ xfs_alert((*tp)->t_mountp,
+ "deferred %s work failed, error %d, %u blocks reserved",
+ what, error, (*tp)->t_blk_res);
+ xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE);
xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending);
xfs_defer_cancel(*tp);
return error;
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 4/6] xfs: correct the parent pointer space reservation comment
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
` (2 preceding siblings ...)
2026-08-10 23:06 ` [PATCH v3 3/6] xfs: report the error that made deferred work shut down the fs Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
2026-08-10 23:06 ` [PATCH v3 5/6] xfs: initialise args->total for parent pointer updates Javier Tia
2026-08-10 23:06 ` [PATCH v3 6/6] xfs: assert the reservation covers each da fork growth Javier Tia
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel
The comment on xfs_parent_calc_space_res() claims parent pointers are
"always the first attr in an attr tree". They are not: a parent pointer
is recorded per dirent, so by the Nth hardlink the attr fork is already
in leaf or node format. The reservation is still correct, because
XFS_DAENTER_SPACE_RES() covers a split at every level of a maximum-depth
attr dabtree whatever format the fork is in, but anyone auditing a
shortfall here is led by the comment to look for a bug that is not
there.
Rewrite the comment to state what actually bounds the result, and record
why the double split allowance and the extent-add term differ from
xfs_attr_calc_size().
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_trans_space.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_trans_space.c b/fs/xfs/libxfs/xfs_trans_space.c
index 9b8f495c9049..c4cd547033e5 100644
--- a/fs/xfs/libxfs/xfs_trans_space.c
+++ b/fs/xfs/libxfs/xfs_trans_space.c
@@ -22,8 +22,23 @@ xfs_parent_calc_space_res(
unsigned int namelen)
{
/*
- * Parent pointers are always the first attr in an attr tree, and never
- * larger than a block
+ * A parent pointer is recorded per dirent, so an inode with N links
+ * carries N of them and the attr fork can already be in leaf or node
+ * format when one is added. That does not affect the reservation:
+ * XFS_DAENTER_SPACE_RES covers a split at every level of a
+ * maximum-depth attr dabtree, whatever format the fork is in now.
+ *
+ * The name is a dirent name and the value is a struct xfs_parent_rec,
+ * so the leaf entry is always local and never exceeds 272 bytes.
+ * Parent pointers require V5, hence a 1k minimum block size, so the
+ * entry always stays under half a block and this needs none of the
+ * double split allowance that xfs_attr_calc_size() makes.
+ *
+ * The second term hands a byte count to a macro whose parameter counts
+ * mappings, so it asks for more extent-add allowance than the single
+ * mapping a parent pointer adds - how much more depends on the block
+ * size. It over-reserves either way, which is why it is left alone:
+ * correcting the unit would shrink a reservation that is only generous.
*/
return XFS_DAENTER_SPACE_RES(mp, XFS_ATTR_FORK) +
XFS_NEXTENTADD_SPACE_RES(mp, namelen, XFS_ATTR_FORK);
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 5/6] xfs: initialise args->total for parent pointer updates
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
` (3 preceding siblings ...)
2026-08-10 23:06 ` [PATCH v3 4/6] xfs: correct the parent pointer space reservation comment Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
2026-08-10 23:06 ` [PATCH v3 6/6] xfs: assert the reservation covers each da fork growth Javier Tia
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel, stable
xfs_parent_da_args_init() builds an xfs_da_args from a zeroed
xfs_parent_args (kmem_cache_zalloc), leaving args->total == 0.
xfs_da_grow_inode_int() treats that field as a running block reservation
and subtracts from it; because it is an xfs_extlen_t (uint32_t), the
first attr-fork growth wraps it to ~0U. That defeats the free-space
check in xfs_alloc_space_available(), and when it coincides with an AG
that has exactly zero available blocks the allocation is clamped to
maxlen 0 and returns -ENOSPC, which xfs_defer_finish_noroll() escalates
to a filesystem shutdown.
Set args->total the way the log recovery path does
(xfs_attri_recover_work(), xfs_attr_item.c:706), in the add and replace
paths that can grow the fork. Removals and lookups never grow it, so
they leave the field alone, matching that switch.
Fixes: b7c62d90c12c ("xfs: parent pointer attribute creation")
Cc: <stable@vger.kernel.org> # v6.10
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_parent.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_parent.c b/fs/xfs/libxfs/xfs_parent.c
index 3509cc4b2175..312f33086d53 100644
--- a/fs/xfs/libxfs/xfs_parent.c
+++ b/fs/xfs/libxfs/xfs_parent.c
@@ -194,7 +194,7 @@ xfs_parent_addname(
const struct xfs_name *parent_name,
struct xfs_inode *child)
{
- int error;
+ int error, local;
error = xfs_parent_iread_extents(tp, child);
if (error)
@@ -204,6 +204,10 @@ xfs_parent_addname(
xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
child->i_ino, parent_name);
+ /* Growing the attr fork needs a real reservation in args->total. */
+ ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local);
+ ASSERT(local);
+
return xfs_attr_setname(&ppargs->args, 0);
}
@@ -240,7 +244,7 @@ xfs_parent_replacename(
const struct xfs_name *new_name,
struct xfs_inode *child)
{
- int error;
+ int error, local;
error = xfs_parent_iread_extents(tp, child);
if (error)
@@ -250,6 +254,10 @@ xfs_parent_replacename(
xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
child->i_ino, old_name);
+ /* Growing the attr fork needs a real reservation in args->total. */
+ ppargs->args.total = xfs_attr_calc_size(&ppargs->args, &local);
+ ASSERT(local);
+
xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp);
ppargs->args.new_name = new_name->name;
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 6/6] xfs: assert the reservation covers each da fork growth
2026-08-10 23:06 [PATCH v3 0/6] xfs: fix filesystem shutdown from parent pointer reservation underflow Javier Tia
` (4 preceding siblings ...)
2026-08-10 23:06 ` [PATCH v3 5/6] xfs: initialise args->total for parent pointer updates Javier Tia
@ 2026-08-10 23:06 ` Javier Tia
5 siblings, 0 replies; 7+ messages in thread
From: Javier Tia @ 2026-08-10 23:06 UTC (permalink / raw)
To: Carlos Maiolino
Cc: Darrick J . Wong, Dave Chinner, Allison Henderson,
Andrey Albershteyn, linux-xfs, linux-kernel
xfs_da_grow_inode_int() subtracts the blocks it just allocated from
args->total, the caller's remaining block reservation. The subtraction
is unsigned, so a caller that reaches it with too small a total wraps
the field instead of failing, and every allocation afterwards runs with
a bogus reservation. Assert the remaining reservation still covers the
step, so an under-reserved or uninitialised total trips in debug builds
instead of silently wrapping.
Suggested-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Javier Tia <floss@jetm.me>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_da_btree.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c
index ad801b7bd2dd..9be407affc6e 100644
--- a/fs/xfs/libxfs/xfs_da_btree.c
+++ b/fs/xfs/libxfs/xfs_da_btree.c
@@ -2385,6 +2385,7 @@ xfs_da_grow_inode_int(
}
/* account for newly allocated blocks in reserved blocks total */
+ ASSERT(args->total >= dp->i_nblocks - nblks);
args->total -= dp->i_nblocks - nblks;
out_free_map:
--
Javier Tia
^ permalink raw reply related [flat|nested] 7+ messages in thread