From: Viacheslav Dubeyko <slava@dubeyko.com>
To: glaubitz@physik.fu-berlin.de, frank.li@vivo.com
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
vdubeyko@coreweave.com, Viacheslav Dubeyko <slava@dubeyko.com>
Subject: [PATCH] hfs: fix orphaned b-tree node in hfs_bnode_split()
Date: Thu, 22 Oct 2026 16:35:47 -0700 [thread overview]
Message-ID: <20261022233548.3481882-2-slava@dubeyko.com> (raw)
In-Reply-To: <20261022233548.3481882-1-slava@dubeyko.com>
The xfstests' test-case generic/013 finishes with
corrupted HFS volume:
generic/013 27s ... [failed, exit status 1]_check_generic_filesystem: filesystem on /dev/loop50 is inconsistent
** Checking HFS volume.
The volume name is untitled
** Checking extents overflow file.
** Checking catalog file.
Invalid B-tree header
Invalid map node
(4, 0)
** Checking catalog hierarchy.
** Checking volume bitmap.
** Checking volume information.
** The volume untitled was found corrupt and needs to be repaired.
The fsck tool detected that Catalog b-tree is corrupted.
Namely, the header node's counters and map node bitmap
have inconsistent state with the real b-tree structure.
The same issue has been fixed for the case of HFS+
by commit c1307d18caa8 ("hfsplus: fix generic/642 failure").
The hfs_bnode_split() contains a loop that implements
a roughly 50/50 split of the B-tree node's records by scanning
the offset table to find where the data crosses the node's midpoint.
If this logic detects the incapability of splitting the node, then
it simply calls hfs_bnode_put() for newly created node. However,
node is not set as HFS_BNODE_DELETED and real deletion of node
doesn't happen. As a result, the empty node becomes orphaned but
it is still accounted as used. Finally, fsck tool detects this
inconsistency of HFS volume.
Add call of hfs_bnode_unlink() before hfs_bnode_put()
for the case if new node cannot be used for splitting the existing
node.
HFS port of the HFS+ commit:
commit c1307d18caa8 ("hfsplus: fix generic/642 failure").
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
---
fs/hfs/brec.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/fs/hfs/brec.c b/fs/hfs/brec.c
index 5a2f740ddefd..1ca1ee9f73ef 100644
--- a/fs/hfs/brec.c
+++ b/fs/hfs/brec.c
@@ -254,6 +254,9 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
struct hfs_bnode_desc node_desc;
int num_recs, new_rec_off, new_off, old_rec_off;
int data_start, data_end, size;
+ size_t rec_off_tbl_size;
+ size_t node_desc_size = sizeof(struct hfs_bnode_desc);
+ size_t rec_size = sizeof(__be16);
tree = fd->tree;
node = fd->bnode;
@@ -280,18 +283,22 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
return next_node;
}
- size = tree->node_size / 2 - node->num_recs * 2 - 14;
- old_rec_off = tree->node_size - 4;
+ rec_off_tbl_size = node->num_recs * rec_size;
+ size = tree->node_size / 2;
+ size -= node_desc_size;
+ size -= rec_off_tbl_size;
+ old_rec_off = tree->node_size - (2 * rec_size);
+
num_recs = 1;
for (;;) {
data_start = hfs_bnode_read_u16(node, old_rec_off);
if (data_start > size)
break;
- old_rec_off -= 2;
+ old_rec_off -= rec_size;
if (++num_recs < node->num_recs)
continue;
- /* panic? */
hfs_bnode_put(node);
+ hfs_bnode_unlink(new_node);
hfs_bnode_put(new_node);
if (next_node)
hfs_bnode_put(next_node);
@@ -302,7 +309,7 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
/* new record is in the lower half,
* so leave some more space there
*/
- old_rec_off += 2;
+ old_rec_off += rec_size;
num_recs--;
data_start = hfs_bnode_read_u16(node, old_rec_off);
} else {
@@ -310,27 +317,28 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
hfs_bnode_get(new_node);
fd->bnode = new_node;
fd->record -= num_recs;
- fd->keyoffset -= data_start - 14;
- fd->entryoffset -= data_start - 14;
+ fd->keyoffset -= data_start - node_desc_size;
+ fd->entryoffset -= data_start - node_desc_size;
}
new_node->num_recs = node->num_recs - num_recs;
node->num_recs = num_recs;
- new_rec_off = tree->node_size - 2;
- new_off = 14;
+ new_rec_off = tree->node_size - rec_size;
+ new_off = node_desc_size;
size = data_start - new_off;
num_recs = new_node->num_recs;
data_end = data_start;
while (num_recs) {
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
- old_rec_off -= 2;
- new_rec_off -= 2;
+ old_rec_off -= rec_size;
+ new_rec_off -= rec_size;
data_end = hfs_bnode_read_u16(node, old_rec_off);
new_off = data_end - size;
num_recs--;
}
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
- hfs_bnode_copy(new_node, 14, node, data_start, data_end - data_start);
+ hfs_bnode_copy(new_node, node_desc_size,
+ node, data_start, data_end - data_start);
/* update new bnode header */
node_desc.next = cpu_to_be32(new_node->next);
--
2.43.0
next prev parent reply other threads:[~2026-10-22 23:36 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 23:35 [PATCH] hfs: fix logic of alloc/free b-tree node Viacheslav Dubeyko
2026-10-22 23:35 ` Viacheslav Dubeyko [this message]
2026-10-22 23:35 ` [PATCH] hfs: fix potential race conditions in b-tree functionality Viacheslav Dubeyko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261022233548.3481882-2-slava@dubeyko.com \
--to=slava@dubeyko.com \
--cc=frank.li@vivo.com \
--cc=glaubitz@physik.fu-berlin.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=vdubeyko@coreweave.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®