]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hfsplus: rework logic of map nodes creation in xattr b-tree
authorViacheslav Dubeyko <slava@dubeyko.com>
Fri, 3 Apr 2026 23:05:55 +0000 (16:05 -0700)
committerViacheslav Dubeyko <slava@dubeyko.com>
Wed, 8 Apr 2026 21:23:29 +0000 (14:23 -0700)
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>
fs/hfsplus/xattr.c
include/linux/hfs_common.h

index 89e2e7e46e96773dae21889327363374f317e176..75cb74466738423e031a666d2c39fea522449663 100644 (file)
@@ -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);
index 9e71b9a03b60494862f03b0d7bae02dc7c5439ce..07dfc39630ab91d62713bffd7a855516f22290f9 100644 (file)
@@ -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 */