[PATCH 1/3] ocfs2: reserve metadata for a new xattr block and its value tree

From: Joseph Qi

Date: Sat Oct 10 2026 - 07:40:28 EST


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@xxxxxxxxxxxxxxx>
Signed-off-by: Joseph Qi <joseph.qi@xxxxxxxxxxxxxxxxx>
---
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