From a8eed0ba6a4b2f1803ecdfa9f11a4818cf87c474 Mon Sep 17 00:00:00 2001 From: Shardul Bankar Date: Wed, 18 Mar 2026 13:08:22 +0530 Subject: hfsplus: refactor b-tree map page access and add node-type validation In HFS+ b-trees, the node allocation bitmap is stored across multiple records. The first chunk resides in the b-tree Header Node at record index 2, while all subsequent chunks are stored in dedicated Map Nodes at record index 0. This structural quirk forces callers like hfs_bmap_alloc() and hfs_bmap_free() to duplicate boilerplate code to validate offsets, correct lengths, and map the underlying pages via kmap_local_page(). There is also currently no strict node-type validation before reading these records, leaving the allocator vulnerable if a corrupted image points a map linkage to an Index or Leaf node. Introduce a unified bit-level API to encapsulate the map record access: 1. A new `struct hfs_bmap_ctx` to cleanly pass state and safely handle page math across all architectures. 2. `hfs_bmap_get_map_page()`: Automatically validates node types (HFS_NODE_HEADER vs HFS_NODE_MAP), infers the correct record index, handles page-boundary math, and returns the unmapped `struct page *` directly to the caller to avoid asymmetric mappings. 3. `hfs_bmap_clear_bit()`: A clean wrapper that internally handles page mapping/unmapping for single-bit operations. Refactor hfs_bmap_alloc() and hfs_bmap_free() to utilize this new API. This deduplicates the allocator logic, hardens the map traversal against fuzzed images, and provides the exact abstractions needed for upcoming mount-time validation checks. Signed-off-by: Shardul Bankar Reviewed-by: Viacheslav Dubeyko Tested-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260318073823.3933718-2-shardul.b@mpiricsoftware.com Signed-off-by: Viacheslav Dubeyko --- include/linux/hfs_common.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/linux') diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h index dadb5e0aa8a3..be24c687858e 100644 --- a/include/linux/hfs_common.h +++ b/include/linux/hfs_common.h @@ -510,6 +510,8 @@ struct hfs_btree_header_rec { #define HFSPLUS_NODE_MXSZ 32768 #define HFSPLUS_ATTR_TREE_NODE_SIZE 8192 #define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT 3 +#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 /* btree key type */ -- cgit v1.2.3 From 897c2beb4a7799154a67942fa85a9678f885f36b Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Mon, 23 Mar 2026 17:39:50 -0700 Subject: hfsplus: fix generic/523 test-case failure The xfstests' test-case generic/523 fails to execute correctly: FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.15.0-rc4+ #8 SMP PREEMPT_DYNAMIC Thu May 1 16:43:22 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/523 - output mismatch (see xfstests-dev/results//generic/523.out.bad) The test-case expects to have '/' in the xattr name. However, HFS+ unicode logic makes conversion of '/' into ':'. In HFS+, a filename can contain '/' because ':' is the separator. The slash is a valid filename character on macOS. But on Linux, / is the path separator and it cannot appear in a filename component. But xattr name can contain any of these symbols. It means that this unicode logic conversion doesn't need to be executed for the case of xattr name. This patch adds distinguishing the regular and xattr names. If we have a regular name, then this conversion of special symbols will be executed. Otherwise, the conversion is skipped for the case of xattr names. sudo ./check -g auto FSTYP -- hfsplus PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.0.0-rc1+ #24 SMP PREEMPT_DYNAMIC Fri Mar 20 12:36:49 PDT 2026 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/523 33s ... 25s Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/178 cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260324003949.417048-2-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- fs/hfsplus/attributes.c | 3 +- fs/hfsplus/catalog.c | 4 +- fs/hfsplus/hfsplus_fs.h | 3 +- fs/hfsplus/unicode.c | 121 ++++++++++++++++++++++++++++++++------------- fs/hfsplus/unicode_test.c | 51 ++++++++++++------- include/linux/hfs_common.h | 5 ++ 6 files changed, 132 insertions(+), 55 deletions(-) (limited to 'include/linux') diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c index 704635c65e9a..fa87496409c9 100644 --- a/fs/hfsplus/attributes.c +++ b/fs/hfsplus/attributes.c @@ -57,7 +57,8 @@ int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key, if (name) { int res = hfsplus_asc2uni(sb, (struct hfsplus_unistr *)&key->attr.key_name, - HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name)); + HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name), + HFS_XATTR_NAME); if (res) return res; len = be16_to_cpu(key->attr.key_name.length); diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c index 708046c5dae6..1f7ef61fc318 100644 --- a/fs/hfsplus/catalog.c +++ b/fs/hfsplus/catalog.c @@ -47,7 +47,7 @@ int hfsplus_cat_build_key(struct super_block *sb, key->cat.parent = cpu_to_be32(parent); err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN, - str->name, str->len); + str->name, str->len, HFS_REGULAR_NAME); if (unlikely(err < 0)) return err; @@ -183,7 +183,7 @@ static int hfsplus_fill_cat_thread(struct super_block *sb, entry->thread.reserved = 0; entry->thread.parentID = cpu_to_be32(parentid); err = hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN, - str->name, str->len); + str->name, str->len, HFS_REGULAR_NAME); if (unlikely(err < 0)) return err; diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index caba698814fe..3545b8dbf11c 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -506,7 +506,8 @@ int hfsplus_uni2asc_xattr_str(struct super_block *sb, const struct hfsplus_attr_unistr *ustr, char *astr, int *len_p); int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, - int max_unistr_len, const char *astr, int len); + int max_unistr_len, const char *astr, int len, + int name_type); int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str); int hfsplus_compare_dentry(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name); diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c index d3a142f4518b..008fec186382 100644 --- a/fs/hfsplus/unicode.c +++ b/fs/hfsplus/unicode.c @@ -147,9 +147,44 @@ static u16 *hfsplus_compose_lookup(u16 *p, u16 cc) return NULL; } +/* + * In HFS+, a filename can contain / because : is the separator. + * The slash is a valid filename character on macOS. + * But on Linux, / is the path separator and + * it cannot appear in a filename component. + * There's a parallel mapping for the NUL character (0 -> U+2400). + * NUL terminates strings in C/POSIX but is valid in HFS+ filenames. + */ +static inline +void hfsplus_mac2linux_compatibility_check(u16 symbol, u16 *conversion, + int name_type) +{ + *conversion = symbol; + + switch (name_type) { + case HFS_XATTR_NAME: + /* ignore conversion */ + return; + + default: + /* continue logic */ + break; + } + + switch (symbol) { + case 0: + *conversion = 0x2400; + break; + case '/': + *conversion = ':'; + break; + } +} + static int hfsplus_uni2asc(struct super_block *sb, const struct hfsplus_unistr *ustr, - int max_len, char *astr, int *len_p) + int max_len, char *astr, int *len_p, + int name_type) { const hfsplus_unichr *ip; struct nls_table *nls = HFSPLUS_SB(sb)->nls; @@ -217,14 +252,8 @@ static int hfsplus_uni2asc(struct super_block *sb, hfsplus_compose_table, c1); if (ce1) break; - switch (c0) { - case 0: - c0 = 0x2400; - break; - case '/': - c0 = ':'; - break; - } + hfsplus_mac2linux_compatibility_check(c0, &c0, + name_type); res = nls->uni2char(c0, op, len); if (res < 0) { if (res == -ENAMETOOLONG) @@ -257,16 +286,8 @@ static int hfsplus_uni2asc(struct super_block *sb, } } same: - switch (c0) { - case 0: - cc = 0x2400; - break; - case '/': - cc = ':'; - break; - default: - cc = c0; - } + hfsplus_mac2linux_compatibility_check(c0, &cc, + name_type); done: res = nls->uni2char(cc, op, len); if (res < 0) { @@ -288,7 +309,10 @@ inline int hfsplus_uni2asc_str(struct super_block *sb, const struct hfsplus_unistr *ustr, char *astr, int *len_p) { - return hfsplus_uni2asc(sb, ustr, HFSPLUS_MAX_STRLEN, astr, len_p); + return hfsplus_uni2asc(sb, + ustr, HFSPLUS_MAX_STRLEN, + astr, len_p, + HFS_REGULAR_NAME); } EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str); @@ -297,22 +321,32 @@ inline int hfsplus_uni2asc_xattr_str(struct super_block *sb, char *astr, int *len_p) { return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr, - HFSPLUS_ATTR_MAX_STRLEN, astr, len_p); + HFSPLUS_ATTR_MAX_STRLEN, astr, len_p, + HFS_XATTR_NAME); } EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str); /* - * Convert one or more ASCII characters into a single unicode character. - * Returns the number of ASCII characters corresponding to the unicode char. + * In HFS+, a filename can contain / because : is the separator. + * The slash is a valid filename character on macOS. + * But on Linux, / is the path separator and + * it cannot appear in a filename component. + * There's a parallel mapping for the NUL character (0 -> U+2400). + * NUL terminates strings in C/POSIX but is valid in HFS+ filenames. */ -static inline int asc2unichar(struct super_block *sb, const char *astr, int len, - wchar_t *uc) +static inline +void hfsplus_linux2mac_compatibility_check(wchar_t *uc, int name_type) { - int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc); - if (size <= 0) { - *uc = '?'; - size = 1; + switch (name_type) { + case HFS_XATTR_NAME: + /* ignore conversion */ + return; + + default: + /* continue logic */ + break; } + switch (*uc) { case 0x2400: *uc = 0; @@ -321,6 +355,23 @@ static inline int asc2unichar(struct super_block *sb, const char *astr, int len, *uc = '/'; break; } +} + +/* + * Convert one or more ASCII characters into a single unicode character. + * Returns the number of ASCII characters corresponding to the unicode char. + */ +static inline int asc2unichar(struct super_block *sb, const char *astr, int len, + wchar_t *uc, int name_type) +{ + int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc); + + if (size <= 0) { + *uc = '?'; + size = 1; + } + + hfsplus_linux2mac_compatibility_check(uc, name_type); return size; } @@ -395,7 +446,7 @@ static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer) int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, int max_unistr_len, - const char *astr, int len) + const char *astr, int len, int name_type) { int size, dsize, decompose; u16 *dstr, outlen = 0; @@ -404,7 +455,7 @@ int hfsplus_asc2uni(struct super_block *sb, decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); while (outlen < max_unistr_len && len > 0) { - size = asc2unichar(sb, astr, len, &c); + size = asc2unichar(sb, astr, len, &c, name_type); if (decompose) dstr = decompose_unichar(c, &dsize, dhangul); @@ -452,7 +503,7 @@ int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str) len = str->len; while (len > 0) { int dsize; - size = asc2unichar(sb, astr, len, &c); + size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME); astr += size; len -= size; @@ -510,7 +561,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry, while (len1 > 0 && len2 > 0) { if (!dsize1) { - size = asc2unichar(sb, astr1, len1, &c); + size = asc2unichar(sb, astr1, len1, &c, + HFS_REGULAR_NAME); astr1 += size; len1 -= size; @@ -525,7 +577,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry, } if (!dsize2) { - size = asc2unichar(sb, astr2, len2, &c); + size = asc2unichar(sb, astr2, len2, &c, + HFS_REGULAR_NAME); astr2 += size; len2 -= size; diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c index 26145bf88946..83737c9bafa0 100644 --- a/fs/hfsplus/unicode_test.c +++ b/fs/hfsplus/unicode_test.c @@ -715,28 +715,32 @@ static void hfsplus_asc2uni_basic_test(struct kunit *test) /* Test simple ASCII string conversion */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "hello", 5); + HFSPLUS_MAX_STRLEN, "hello", 5, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &mock_env->str1, "hello"); /* Test empty string */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "", 0); + HFSPLUS_MAX_STRLEN, "", 0, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); /* Test single character */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "A", 1); + HFSPLUS_MAX_STRLEN, "A", 1, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &mock_env->str1, "A"); /* Test null-terminated string with explicit length */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "test\0extra", 4); + HFSPLUS_MAX_STRLEN, "test\0extra", 4, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &mock_env->str1, "test"); @@ -762,7 +766,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test) /* Test colon conversion (should become forward slash) */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, ":", 1); + HFSPLUS_MAX_STRLEN, ":", 1, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length)); @@ -770,7 +775,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test) /* Test string with mixed special characters */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "a:b", 3); + HFSPLUS_MAX_STRLEN, "a:b", 3, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); @@ -780,7 +786,8 @@ static void hfsplus_asc2uni_special_chars_test(struct kunit *test) /* Test multiple special characters */ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, ":::", 3); + HFSPLUS_MAX_STRLEN, ":::", 3, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); @@ -811,7 +818,8 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test) memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN); result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, HFSPLUS_MAX_STRLEN, - mock_env->buf, HFSPLUS_MAX_STRLEN); + mock_env->buf, HFSPLUS_MAX_STRLEN, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, @@ -821,7 +829,8 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test) memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5); result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, HFSPLUS_MAX_STRLEN, - mock_env->buf, HFSPLUS_MAX_STRLEN + 5); + mock_env->buf, HFSPLUS_MAX_STRLEN + 5, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, @@ -829,13 +838,15 @@ static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test) /* Test with smaller max_unistr_len */ result = hfsplus_asc2uni(&mock_sb->sb, - &mock_env->str1, 5, "toolongstring", 13); + &mock_env->str1, 5, "toolongstring", 13, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length)); /* Test zero max length */ - result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4); + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); @@ -859,28 +870,32 @@ static void hfsplus_asc2uni_edge_cases_test(struct kunit *test) /* Test zero length input */ result = hfsplus_asc2uni(&mock_sb->sb, - &ustr, HFSPLUS_MAX_STRLEN, "test", 0); + &ustr, HFSPLUS_MAX_STRLEN, "test", 0, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length)); /* Test input with length mismatch */ result = hfsplus_asc2uni(&mock_sb->sb, - &ustr, HFSPLUS_MAX_STRLEN, "hello", 3); + &ustr, HFSPLUS_MAX_STRLEN, "hello", 3, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &ustr, "hel"); /* Test with various printable ASCII characters */ result = hfsplus_asc2uni(&mock_sb->sb, - &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9); + &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &ustr, "ABC123!@#"); /* Test null character in the middle */ result = hfsplus_asc2uni(&mock_sb->sb, - &ustr, HFSPLUS_MAX_STRLEN, test_str, 3); + &ustr, HFSPLUS_MAX_STRLEN, test_str, 3, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); @@ -909,7 +924,8 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test) /* Test with decomposition disabled (default) */ clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, - HFSPLUS_MAX_STRLEN, "test", 4); + HFSPLUS_MAX_STRLEN, "test", 4, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &mock_env->str1, "test"); @@ -917,7 +933,8 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test) /* Test with decomposition enabled */ set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2, - HFSPLUS_MAX_STRLEN, "test", 4); + HFSPLUS_MAX_STRLEN, "test", 4, + HFS_REGULAR_NAME); KUNIT_EXPECT_EQ(test, 0, result); check_unistr_content(test, &mock_env->str2, "test"); diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h index be24c687858e..9e71b9a03b60 100644 --- a/include/linux/hfs_common.h +++ b/include/linux/hfs_common.h @@ -166,6 +166,11 @@ struct hfsplus_attr_unistr { hfsplus_unichr unicode[HFSPLUS_ATTR_MAX_STRLEN]; } __packed; +enum { + HFS_REGULAR_NAME, + HFS_XATTR_NAME, +}; + struct hfs_extent { __be16 block; __be16 count; -- cgit v1.2.3 From 732af3aa6337fd56025c0548a9e54d6231052144 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Fri, 3 Apr 2026 16:05:55 -0700 Subject: 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 cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260403230556.614171-5-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- fs/hfsplus/xattr.c | 127 +++++++++++++++++++++++++++++++++++++-------- include/linux/hfs_common.h | 2 + 2 files changed, 106 insertions(+), 23 deletions(-) (limited to 'include/linux') 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 */ -- cgit v1.2.3