summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViacheslav Dubeyko <slava@dubeyko.com>2026-04-04 02:05:55 +0300
committerViacheslav Dubeyko <slava@dubeyko.com>2026-04-09 00:23:29 +0300
commit732af3aa6337fd56025c0548a9e54d6231052144 (patch)
tree3fe7d66379f4b18188acf5870b022aea82a5b8af
parent63584d76765bb3e212f70c4c3951ea785fabef1b (diff)
downloadlinux-732af3aa6337fd56025c0548a9e54d6231052144.tar.xz
hfsplus: rework logic of map nodes creation in xattr b-tree
In hfsplus_init_header_node() when node_count > 63488 (header bitmap capacity), the code calculates map_nodes, subtracts them from free_nodes, and marks their positions used in the bitmap. However, it doesn't write the actual map node structure (type, record offsets, bitmap) for those physical positions, only node 0 is written. This patch reworks hfsplus_create_attributes_file() logic by introducing a specialized method of hfsplus_init_map_node() and writing the allocated map b-tree's nodes by means of hfsplus_write_attributes_file_node() method. cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de> cc: Yangtao Li <frank.li@vivo.com> cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> Link: https://lore.kernel.org/r/20260403230556.614171-5-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
-rw-r--r--fs/hfsplus/xattr.c127
-rw-r--r--include/linux/hfs_common.h2
2 files changed, 106 insertions, 23 deletions
diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c
index 89e2e7e46e96..75cb74466738 100644
--- a/fs/hfsplus/xattr.c
+++ b/fs/hfsplus/xattr.c
@@ -50,7 +50,7 @@ static bool is_known_namespace(const char *name)
return true;
}
-static void hfsplus_init_header_node(struct inode *attr_file,
+static u32 hfsplus_init_header_node(struct inode *attr_file,
u32 clump_size,
char *buf, u16 node_size)
{
@@ -59,6 +59,7 @@ static void hfsplus_init_header_node(struct inode *attr_file,
u16 offset;
__be16 *rec_offsets;
u32 hdr_node_map_rec_bits;
+ u32 map_nodes = 0;
char *bmp;
u32 used_nodes;
u32 used_bmp_bytes;
@@ -93,7 +94,6 @@ static void hfsplus_init_header_node(struct inode *attr_file,
hdr_node_map_rec_bits = 8 * (node_size - offset - (4 * sizeof(u16)));
if (be32_to_cpu(head->node_count) > hdr_node_map_rec_bits) {
u32 map_node_bits;
- u32 map_nodes;
desc->next = cpu_to_be32(be32_to_cpu(head->leaf_tail) + 1);
map_node_bits = 8 * (node_size - sizeof(struct hfs_bnode_desc) -
@@ -116,21 +116,100 @@ static void hfsplus_init_header_node(struct inode *attr_file,
*bmp = ~(0xFF >> used_nodes);
offset += hdr_node_map_rec_bits / 8;
*--rec_offsets = cpu_to_be16(offset);
+
+ return map_nodes;
+}
+
+/*
+ * Initialize a map node buffer. Map nodes have a single bitmap record.
+ */
+static void hfsplus_init_map_node(u8 *buf, u16 node_size, u32 next_node)
+{
+ struct hfs_bnode_desc *desc;
+ __be16 *rec_offsets;
+ size_t rec_size = sizeof(__be16);
+ u16 offset;
+
+ memset(buf, 0, node_size);
+
+ desc = (struct hfs_bnode_desc *)buf;
+ desc->type = HFS_NODE_MAP;
+ desc->num_recs = cpu_to_be16(1);
+ desc->next = cpu_to_be32(next_node);
+
+ /*
+ * A map node consists of the node descriptor and a single
+ * map record. The map record is a continuation of the map
+ * record contained in the header node. The size of the map
+ * record is the size of the node, minus the size of
+ * the node descriptor (14 bytes), minus the size of two offsets
+ * (4 bytes), minus two bytes of free space. That is, the size of
+ * the map record is the size of the node minus 20 bytes;
+ * this keeps the length of the map record an even multiple of 4 bytes.
+ * The start of the map record is not aligned to a 4-byte boundary:
+ * it starts immediately after the node descriptor
+ * (at an offset of 14 bytes).
+ *
+ * Two record offsets stored at the end of the node:
+ * record[1] = start of record 0 -> sizeof(hfs_bnode_desc)
+ * record[2] = start of free space
+ */
+ rec_offsets = (__be16 *)(buf + node_size);
+
+ /* record #1 */
+ offset = sizeof(struct hfs_bnode_desc);
+ *--rec_offsets = cpu_to_be16(offset);
+
+ /* record #2 */
+ offset = node_size;
+ offset -= (u16)HFSPLUS_BTREE_MAP_NODE_RECS_COUNT * rec_size;
+ offset -= HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES;
+ *--rec_offsets = cpu_to_be16(offset);
+}
+
+static inline
+int hfsplus_write_attributes_file_node(struct inode *attr_file, char *buf,
+ u16 node_size, int *index)
+{
+ struct address_space *mapping;
+ struct page *page;
+ void *kaddr;
+ u32 written = 0;
+
+ mapping = attr_file->i_mapping;
+
+ for (; written < node_size; (*index)++, written += PAGE_SIZE) {
+ page = read_mapping_page(mapping, *index, NULL);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ kaddr = kmap_local_page(page);
+ memcpy(kaddr, buf + written,
+ min_t(size_t, PAGE_SIZE, node_size - written));
+ kunmap_local(kaddr);
+
+ set_page_dirty(page);
+ put_page(page);
+ }
+
+ return 0;
}
static int hfsplus_create_attributes_file(struct super_block *sb)
{
- int err = 0;
struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
struct inode *attr_file;
struct hfsplus_inode_info *hip;
+ struct hfs_bnode_desc *desc;
u32 clump_size;
u16 node_size = HFSPLUS_ATTR_TREE_NODE_SIZE;
+ u32 next_node;
+ u32 map_node_idx;
+ u32 map_nodes;
char *buf;
- int index, written;
- struct address_space *mapping;
- struct page *page;
+ int index;
int old_state = HFSPLUS_EMPTY_ATTR_TREE;
+ int err = 0;
hfs_dbg("ino %d\n", HFSPLUS_ATTR_CNID);
@@ -195,7 +274,7 @@ check_attr_tree_state_again:
}
while (hip->alloc_blocks < hip->clump_blocks) {
- err = hfsplus_file_extend(attr_file, false);
+ err = hfsplus_file_extend(attr_file, true);
if (unlikely(err)) {
pr_err("failed to extend attributes file\n");
goto end_attr_file_creation;
@@ -212,28 +291,30 @@ check_attr_tree_state_again:
goto end_attr_file_creation;
}
- hfsplus_init_header_node(attr_file, clump_size, buf, node_size);
+ map_nodes = hfsplus_init_header_node(attr_file, clump_size, buf, node_size);
- mapping = attr_file->i_mapping;
+ desc = (struct hfs_bnode_desc *)buf;
+ next_node = be32_to_cpu(desc->next);
index = 0;
- written = 0;
- for (; written < node_size; index++, written += PAGE_SIZE) {
- void *kaddr;
- page = read_mapping_page(mapping, index, NULL);
- if (IS_ERR(page)) {
- err = PTR_ERR(page);
- goto failed_header_node_init;
- }
+ err = hfsplus_write_attributes_file_node(attr_file, buf,
+ node_size, &index);
+ if (unlikely(err))
+ goto failed_header_node_init;
- kaddr = kmap_atomic(page);
- memcpy(kaddr, buf + written,
- min_t(size_t, PAGE_SIZE, node_size - written));
- kunmap_atomic(kaddr);
+ for (map_node_idx = 0; map_node_idx < map_nodes; map_node_idx++) {
+ if (next_node >= map_nodes)
+ next_node = 0;
- set_page_dirty(page);
- put_page(page);
+ hfsplus_init_map_node(buf, node_size, next_node);
+
+ err = hfsplus_write_attributes_file_node(attr_file, buf,
+ node_size, &index);
+ if (unlikely(err))
+ goto failed_header_node_init;
+
+ next_node++;
}
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
index 9e71b9a03b60..07dfc39630ab 100644
--- a/include/linux/hfs_common.h
+++ b/include/linux/hfs_common.h
@@ -518,6 +518,8 @@ struct hfs_btree_header_rec {
#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header node */
#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */
#define HFSPLUS_BTREE_HDR_USER_BYTES 128
+#define HFSPLUS_BTREE_MAP_NODE_RECS_COUNT 2
+#define HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES 2
/* btree key type */
#define HFSPLUS_KEY_CASEFOLDING 0xCF /* case-insensitive */