* [PATCH 1/3] ocfs2: reserve metadata for a new xattr block and its value tree
2026-10-10 11:34 [PATCH 0/3] ocfs2: fix metadata reservation for xattr value trees Joseph Qi
@ 2026-10-10 11:34 ` Joseph Qi
2026-10-10 11:34 ` [PATCH 2/3] ocfs2: reserve value tree metadata when moving an xattr into the inode Joseph Qi
2026-10-10 11:34 ` [PATCH 3/3] ocfs2: reserve xattr value tree metadata only once Joseph Qi
2 siblings, 0 replies; 4+ messages in thread
From: Joseph Qi @ 2026-10-10 11:34 UTC (permalink / raw)
To: Andrew Morton, Heming Zhao
Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel
The create side of meta_guess in ocfs2_calc_xattr_set_need() reserves
metadata for the new value's extent tree and nothing for the xattr block
that will hold it:
} else {
credits += OCFS2_XATTR_BLOCK_CREATE_CREDITS;
if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) {
struct ocfs2_extent_list *el = &def_xv.xv.xr_list;
meta_add += ocfs2_extend_meta_needed(el);
...
Commit 3ed2be719eb9 ("ocfs2: allow for more than one data extent when
creating xattr") meant to add that tree on top of the meta_add += 1 the
branch already had, but it moved the 1 into the else arm instead, so the
external-value case lost it. The 1 is not optional: it is what
ocfs2_create_xattr_block() takes from meta_ac through
ocfs2_claim_metadata(), and it is spent before the value tree exists.
ocfs2_extend_meta_needed() on the empty def_xv list returns 2, so the
branch reserves 2 where 3 are needed. The xattr block consumes one, and
when the cluster allocator is fragmented enough that the value needs
more than one extent, ocfs2_add_clusters_in_btree() comes back around
with a full value root and ocfs2_alloc_context_bits_left() of 1 against
a threshold of 2. It answers RESTART_META, which
ocfs2_xattr_extend_allocation() turns into a BUG_ON().
Commit 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for
large xattrs") downgraded that to -ENOSPC, at the cost of leaking the
clusters already handed out: ocfs2_xa_cleanup_value_truncate() takes
its !orig_clusters arm, drops the entry and leaves the clusters for
fsck.ocfs2 to reclaim.
Reserve for the block and the tree separately.
Fixes: 3ed2be719eb9 ("ocfs2: allow for more than one data extent when creating xattr")
Cc: <stable@vger.kernel.org>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
fs/ocfs2/xattr.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index a428fe908116..801d3d311115 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -3580,7 +3580,13 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode,
credits += OCFS2_XATTR_BLOCK_CREATE_CREDITS;
if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE) {
struct ocfs2_extent_list *el = &def_xv.xv.xr_list;
- meta_add += ocfs2_extend_meta_needed(el);
+ /*
+ * One block for the xattr block we may have to
+ * allocate, the rest for growing the value tree it
+ * would hold. If ocfs2_xattr_ibody_set() succeeds
+ * instead, that first block goes unused.
+ */
+ meta_add += 1 + ocfs2_extend_meta_needed(el);
credits += ocfs2_calc_extend_credits(inode->i_sb,
el);
} else {
--
2.39.3
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] ocfs2: reserve value tree metadata when moving an xattr into the inode
2026-10-10 11:34 [PATCH 0/3] ocfs2: fix metadata reservation for xattr value trees Joseph Qi
2026-10-10 11:34 ` [PATCH 1/3] ocfs2: reserve metadata for a new xattr block and its value tree Joseph Qi
@ 2026-10-10 11:34 ` Joseph Qi
2026-10-10 11:34 ` [PATCH 3/3] ocfs2: reserve xattr value tree metadata only once Joseph Qi
2 siblings, 0 replies; 4+ messages in thread
From: Joseph Qi @ 2026-10-10 11:34 UTC (permalink / raw)
To: Andrew Morton, Heming Zhao
Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel
ocfs2_calc_xattr_set_need() charges no metadata for the case where an
xattr living in an xattr block or bucket moves back into the inode:
if (old_in_xb) {
if (ocfs2_xattr_can_be_in_inode(inode, xi, xis)) {
clusters_add += new_clusters;
credits += ...;
if (!ocfs2_xattr_is_local(xe))
credits += ocfs2_calc_extend_credits(...);
goto out;
}
}
The credits for extending a value tree are there but the matching blocks
are not, so meta_add stays 0, ocfs2_init_xattr_set_ctxt() skips
ocfs2_reserve_new_metadata_blocks() and ctxt->meta_ac is left NULL.
This is the shape commit 0cdc7dde00ec ("ocfs2: fix missing metadata
reservation for large xattrs") fixed for the xattr block case, and it is
reached the same way: namevalue_size() charges a value larger than
OCFS2_XATTR_INLINE_SIZE as only OCFS2_XATTR_ROOT_SIZE, so
ocfs2_xattr_can_be_in_inode() accepts a new value that will be stored
outside. ocfs2_xa_prepare_entry() installs a value root from def_xv and
grows it, and once the cluster allocator is fragmented enough to need a
second extent ocfs2_add_clusters_in_btree() finds the root full with no
meta_ac at all and answers RESTART_META.
ocfs2_xattr_extend_allocation() turns that into -ENOSPC, or into a
BUG_ON() without that commit, and ocfs2_xa_cleanup_value_truncate() takes
its !orig_clusters arm, dropping the entry and leaking the clusters
already handed out until fsck.ocfs2 reclaims them.
Reserve for the value tree here too. The entry moves back into the
inode rather than into a new xattr block, so unlike the create side of
meta_guess this needs no block of its own.
Fixes: 78f30c314a74 ("ocfs2/xattr: Reserve meta/data at the beginning of ocfs2_xattr_set.")
Cc: <stable@vger.kernel.org>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
fs/ocfs2/xattr.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 801d3d311115..53ba52381112 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -3480,6 +3480,14 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode,
credits += ocfs2_calc_extend_credits(
inode->i_sb,
&def_xv.xv.xr_list);
+ /*
+ * A value too big to stay inline is only charged as
+ * OCFS2_XATTR_ROOT_SIZE above, so it can still need a
+ * value tree of its own. No xattr block is allocated
+ * on this path, so the tree is all we reserve for.
+ */
+ if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE)
+ meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list);
goto out;
}
}
--
2.39.3
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] ocfs2: reserve xattr value tree metadata only once
2026-10-10 11:34 [PATCH 0/3] ocfs2: fix metadata reservation for xattr value trees Joseph Qi
2026-10-10 11:34 ` [PATCH 1/3] ocfs2: reserve metadata for a new xattr block and its value tree Joseph Qi
2026-10-10 11:34 ` [PATCH 2/3] ocfs2: reserve value tree metadata when moving an xattr into the inode Joseph Qi
@ 2026-10-10 11:34 ` Joseph Qi
2 siblings, 0 replies; 4+ messages in thread
From: Joseph Qi @ 2026-10-10 11:34 UTC (permalink / raw)
To: Andrew Morton, Heming Zhao
Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel
ocfs2_calc_xattr_set_need() asks for the new value's extent tree twice
when it replaces an inline value smaller than a value root with one
large enough to be stored outside. The "stored outside" branch reserves
the tree and then falls through to meta_guess instead of going out:
if (!ocfs2_xattr_is_local(xe)) {
...
xv = (struct ocfs2_xattr_value_root *)
(base + name_offset + name_len);
value_size = OCFS2_XATTR_ROOT_SIZE;
} else
xv = &def_xv.xv;
if (old_clusters >= new_clusters) {
...
goto out;
} else {
meta_add += ocfs2_extend_meta_needed(&xv->xr_list);
...
if (value_size >= OCFS2_XATTR_ROOT_SIZE)
goto out;
}
On the fall-through xv is def_xv, so what gets added here is the two
blocks ocfs2_extend_meta_needed() wants for an empty value root -- and
both sides of meta_guess reserve that same tree again. The create side
has done so since commit 3ed2be719eb9 ("ocfs2: allow for more than one
data extent when creating xattr"), which ported this reservation to the
create case without noticing it was already on the way in, and the
existing-block side since commit 0cdc7dde00ec ("ocfs2: fix missing
metadata reservation for large xattrs"). Both were chasing the same
RESTART_META failure in ocfs2_xattr_extend_allocation(), for xattr
create and then for xattr update.
ocfs2_init_xattr_set_ctxt() hands the total straight to
ocfs2_reserve_new_metadata_blocks(), so the surplus is two blocks held
for the whole transaction. A setxattr that would otherwise fit can come
back -ENOSPC on a filesystem whose metadata allocator is nearly
exhausted.
Move the reservation into the branch that stops there, leaving meta_guess
as the only place that reserves for a value tree the fall through is
about to create. The paths that do go out keep reserving against the
value root they already have, which for an external old value is the
on-disk one and so can legitimately ask for more than two blocks.
Both sides of meta_guess have to come out even on their own now that the
copy covering for them is gone. The existing-block side only started
reserving the value tree in the commit this one is tagged against. The
create side has reserved the tree since 3ed2be719eb9, but only the tree:
the block ocfs2_create_xattr_block() claims came out of the same two, and
on the fall-through it was the duplicate removed here that made up the
difference. So "ocfs2: reserve metadata for a new xattr block and its
value tree" has to land first -- without it the create side reserves 2
where 3 are needed and the value tree is back to RESTART_META. That one
fixes a shortfall of its own on the path that reaches meta_guess
directly, where there was never a duplicate to cover it, so it is worth
taking alone; this patch is not.
Fixes: 0cdc7dde00ec ("ocfs2: fix missing metadata reservation for large xattrs")
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
fs/ocfs2/xattr.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 53ba52381112..cc6a64eb7d07 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -3509,12 +3509,21 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode,
credits += ocfs2_remove_extent_credits(inode->i_sb);
goto out;
} else {
- meta_add += ocfs2_extend_meta_needed(&xv->xr_list);
clusters_add += new_clusters - old_clusters;
credits += ocfs2_calc_extend_credits(inode->i_sb,
&xv->xr_list);
- if (value_size >= OCFS2_XATTR_ROOT_SIZE)
+ /*
+ * The old value occupies at least as much room as a
+ * value root, whether it sat outside or inline, so the
+ * new root fits where it was and no new xattr block or
+ * bucket is needed -- only the metadata to extend the
+ * tree hanging off it. A smaller one is replaced
+ * outright, which meta_guess below reserves for.
+ */
+ if (value_size >= OCFS2_XATTR_ROOT_SIZE) {
+ meta_add += ocfs2_extend_meta_needed(&xv->xr_list);
goto out;
+ }
}
} else {
/*
@@ -3566,6 +3575,10 @@ static int ocfs2_calc_xattr_set_need(struct inode *inode,
* Reserve metadata for the new xattr's value extent tree.
* The not_found path above adds credits for this tree but
* omits meta_add, leaving meta_ac NULL for large values.
+ *
+ * This is all of meta_ac on this side beyond the xattr tree
+ * above: no xattr block is allocated here, and a new bucket or
+ * index block is paid for out of data_ac.
*/
if (xi->xi_value_len > OCFS2_XATTR_INLINE_SIZE)
meta_add += ocfs2_extend_meta_needed(&def_xv.xv.xr_list);
--
2.39.3
^ permalink raw reply [flat|nested] 4+ messages in thread