Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

<skipped>
generic/523 33s ... 25s
<skipped>

Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/178
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/20260324003949.417048-2-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>

+132 -55
+2 -1
fs/hfsplus/attributes.c
··· 57 57 if (name) { 58 58 int res = hfsplus_asc2uni(sb, 59 59 (struct hfsplus_unistr *)&key->attr.key_name, 60 - HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name)); 60 + HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name), 61 + HFS_XATTR_NAME); 61 62 if (res) 62 63 return res; 63 64 len = be16_to_cpu(key->attr.key_name.length);
+2 -2
fs/hfsplus/catalog.c
··· 47 47 48 48 key->cat.parent = cpu_to_be32(parent); 49 49 err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN, 50 - str->name, str->len); 50 + str->name, str->len, HFS_REGULAR_NAME); 51 51 if (unlikely(err < 0)) 52 52 return err; 53 53 ··· 183 183 entry->thread.reserved = 0; 184 184 entry->thread.parentID = cpu_to_be32(parentid); 185 185 err = hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN, 186 - str->name, str->len); 186 + str->name, str->len, HFS_REGULAR_NAME); 187 187 if (unlikely(err < 0)) 188 188 return err; 189 189
+2 -1
fs/hfsplus/hfsplus_fs.h
··· 506 506 const struct hfsplus_attr_unistr *ustr, 507 507 char *astr, int *len_p); 508 508 int hfsplus_asc2uni(struct super_block *sb, struct hfsplus_unistr *ustr, 509 - int max_unistr_len, const char *astr, int len); 509 + int max_unistr_len, const char *astr, int len, 510 + int name_type); 510 511 int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str); 511 512 int hfsplus_compare_dentry(const struct dentry *dentry, unsigned int len, 512 513 const char *str, const struct qstr *name);
+87 -34
fs/hfsplus/unicode.c
··· 147 147 return NULL; 148 148 } 149 149 150 + /* 151 + * In HFS+, a filename can contain / because : is the separator. 152 + * The slash is a valid filename character on macOS. 153 + * But on Linux, / is the path separator and 154 + * it cannot appear in a filename component. 155 + * There's a parallel mapping for the NUL character (0 -> U+2400). 156 + * NUL terminates strings in C/POSIX but is valid in HFS+ filenames. 157 + */ 158 + static inline 159 + void hfsplus_mac2linux_compatibility_check(u16 symbol, u16 *conversion, 160 + int name_type) 161 + { 162 + *conversion = symbol; 163 + 164 + switch (name_type) { 165 + case HFS_XATTR_NAME: 166 + /* ignore conversion */ 167 + return; 168 + 169 + default: 170 + /* continue logic */ 171 + break; 172 + } 173 + 174 + switch (symbol) { 175 + case 0: 176 + *conversion = 0x2400; 177 + break; 178 + case '/': 179 + *conversion = ':'; 180 + break; 181 + } 182 + } 183 + 150 184 static int hfsplus_uni2asc(struct super_block *sb, 151 185 const struct hfsplus_unistr *ustr, 152 - int max_len, char *astr, int *len_p) 186 + int max_len, char *astr, int *len_p, 187 + int name_type) 153 188 { 154 189 const hfsplus_unichr *ip; 155 190 struct nls_table *nls = HFSPLUS_SB(sb)->nls; ··· 252 217 hfsplus_compose_table, c1); 253 218 if (ce1) 254 219 break; 255 - switch (c0) { 256 - case 0: 257 - c0 = 0x2400; 258 - break; 259 - case '/': 260 - c0 = ':'; 261 - break; 262 - } 220 + hfsplus_mac2linux_compatibility_check(c0, &c0, 221 + name_type); 263 222 res = nls->uni2char(c0, op, len); 264 223 if (res < 0) { 265 224 if (res == -ENAMETOOLONG) ··· 286 257 } 287 258 } 288 259 same: 289 - switch (c0) { 290 - case 0: 291 - cc = 0x2400; 292 - break; 293 - case '/': 294 - cc = ':'; 295 - break; 296 - default: 297 - cc = c0; 298 - } 260 + hfsplus_mac2linux_compatibility_check(c0, &cc, 261 + name_type); 299 262 done: 300 263 res = nls->uni2char(cc, op, len); 301 264 if (res < 0) { ··· 309 288 const struct hfsplus_unistr *ustr, char *astr, 310 289 int *len_p) 311 290 { 312 - return hfsplus_uni2asc(sb, ustr, HFSPLUS_MAX_STRLEN, astr, len_p); 291 + return hfsplus_uni2asc(sb, 292 + ustr, HFSPLUS_MAX_STRLEN, 293 + astr, len_p, 294 + HFS_REGULAR_NAME); 313 295 } 314 296 EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str); 315 297 ··· 321 297 char *astr, int *len_p) 322 298 { 323 299 return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr, 324 - HFSPLUS_ATTR_MAX_STRLEN, astr, len_p); 300 + HFSPLUS_ATTR_MAX_STRLEN, astr, len_p, 301 + HFS_XATTR_NAME); 325 302 } 326 303 EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str); 327 304 328 305 /* 329 - * Convert one or more ASCII characters into a single unicode character. 330 - * Returns the number of ASCII characters corresponding to the unicode char. 306 + * In HFS+, a filename can contain / because : is the separator. 307 + * The slash is a valid filename character on macOS. 308 + * But on Linux, / is the path separator and 309 + * it cannot appear in a filename component. 310 + * There's a parallel mapping for the NUL character (0 -> U+2400). 311 + * NUL terminates strings in C/POSIX but is valid in HFS+ filenames. 331 312 */ 332 - static inline int asc2unichar(struct super_block *sb, const char *astr, int len, 333 - wchar_t *uc) 313 + static inline 314 + void hfsplus_linux2mac_compatibility_check(wchar_t *uc, int name_type) 334 315 { 335 - int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc); 336 - if (size <= 0) { 337 - *uc = '?'; 338 - size = 1; 316 + switch (name_type) { 317 + case HFS_XATTR_NAME: 318 + /* ignore conversion */ 319 + return; 320 + 321 + default: 322 + /* continue logic */ 323 + break; 339 324 } 325 + 340 326 switch (*uc) { 341 327 case 0x2400: 342 328 *uc = 0; ··· 355 321 *uc = '/'; 356 322 break; 357 323 } 324 + } 325 + 326 + /* 327 + * Convert one or more ASCII characters into a single unicode character. 328 + * Returns the number of ASCII characters corresponding to the unicode char. 329 + */ 330 + static inline int asc2unichar(struct super_block *sb, const char *astr, int len, 331 + wchar_t *uc, int name_type) 332 + { 333 + int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc); 334 + 335 + if (size <= 0) { 336 + *uc = '?'; 337 + size = 1; 338 + } 339 + 340 + hfsplus_linux2mac_compatibility_check(uc, name_type); 358 341 return size; 359 342 } 360 343 ··· 446 395 447 396 int hfsplus_asc2uni(struct super_block *sb, 448 397 struct hfsplus_unistr *ustr, int max_unistr_len, 449 - const char *astr, int len) 398 + const char *astr, int len, int name_type) 450 399 { 451 400 int size, dsize, decompose; 452 401 u16 *dstr, outlen = 0; ··· 455 404 456 405 decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); 457 406 while (outlen < max_unistr_len && len > 0) { 458 - size = asc2unichar(sb, astr, len, &c); 407 + size = asc2unichar(sb, astr, len, &c, name_type); 459 408 460 409 if (decompose) 461 410 dstr = decompose_unichar(c, &dsize, dhangul); ··· 503 452 len = str->len; 504 453 while (len > 0) { 505 454 int dsize; 506 - size = asc2unichar(sb, astr, len, &c); 455 + size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME); 507 456 astr += size; 508 457 len -= size; 509 458 ··· 561 510 562 511 while (len1 > 0 && len2 > 0) { 563 512 if (!dsize1) { 564 - size = asc2unichar(sb, astr1, len1, &c); 513 + size = asc2unichar(sb, astr1, len1, &c, 514 + HFS_REGULAR_NAME); 565 515 astr1 += size; 566 516 len1 -= size; 567 517 ··· 577 525 } 578 526 579 527 if (!dsize2) { 580 - size = asc2unichar(sb, astr2, len2, &c); 528 + size = asc2unichar(sb, astr2, len2, &c, 529 + HFS_REGULAR_NAME); 581 530 astr2 += size; 582 531 len2 -= size; 583 532
+34 -17
fs/hfsplus/unicode_test.c
··· 715 715 716 716 /* Test simple ASCII string conversion */ 717 717 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 718 - HFSPLUS_MAX_STRLEN, "hello", 5); 718 + HFSPLUS_MAX_STRLEN, "hello", 5, 719 + HFS_REGULAR_NAME); 719 720 720 721 KUNIT_EXPECT_EQ(test, 0, result); 721 722 check_unistr_content(test, &mock_env->str1, "hello"); 722 723 723 724 /* Test empty string */ 724 725 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 725 - HFSPLUS_MAX_STRLEN, "", 0); 726 + HFSPLUS_MAX_STRLEN, "", 0, 727 + HFS_REGULAR_NAME); 726 728 727 729 KUNIT_EXPECT_EQ(test, 0, result); 728 730 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); 729 731 730 732 /* Test single character */ 731 733 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 732 - HFSPLUS_MAX_STRLEN, "A", 1); 734 + HFSPLUS_MAX_STRLEN, "A", 1, 735 + HFS_REGULAR_NAME); 733 736 734 737 KUNIT_EXPECT_EQ(test, 0, result); 735 738 check_unistr_content(test, &mock_env->str1, "A"); 736 739 737 740 /* Test null-terminated string with explicit length */ 738 741 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 739 - HFSPLUS_MAX_STRLEN, "test\0extra", 4); 742 + HFSPLUS_MAX_STRLEN, "test\0extra", 4, 743 + HFS_REGULAR_NAME); 740 744 741 745 KUNIT_EXPECT_EQ(test, 0, result); 742 746 check_unistr_content(test, &mock_env->str1, "test"); ··· 766 762 767 763 /* Test colon conversion (should become forward slash) */ 768 764 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 769 - HFSPLUS_MAX_STRLEN, ":", 1); 765 + HFSPLUS_MAX_STRLEN, ":", 1, 766 + HFS_REGULAR_NAME); 770 767 771 768 KUNIT_EXPECT_EQ(test, 0, result); 772 769 KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(mock_env->str1.length)); ··· 775 770 776 771 /* Test string with mixed special characters */ 777 772 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 778 - HFSPLUS_MAX_STRLEN, "a:b", 3); 773 + HFSPLUS_MAX_STRLEN, "a:b", 3, 774 + HFS_REGULAR_NAME); 779 775 780 776 KUNIT_EXPECT_EQ(test, 0, result); 781 777 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); ··· 786 780 787 781 /* Test multiple special characters */ 788 782 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 789 - HFSPLUS_MAX_STRLEN, ":::", 3); 783 + HFSPLUS_MAX_STRLEN, ":::", 3, 784 + HFS_REGULAR_NAME); 790 785 791 786 KUNIT_EXPECT_EQ(test, 0, result); 792 787 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(mock_env->str1.length)); ··· 818 811 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN); 819 812 result = hfsplus_asc2uni(&mock_sb->sb, 820 813 &mock_env->str1, HFSPLUS_MAX_STRLEN, 821 - mock_env->buf, HFSPLUS_MAX_STRLEN); 814 + mock_env->buf, HFSPLUS_MAX_STRLEN, 815 + HFS_REGULAR_NAME); 822 816 823 817 KUNIT_EXPECT_EQ(test, 0, result); 824 818 KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, ··· 829 821 memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN + 5); 830 822 result = hfsplus_asc2uni(&mock_sb->sb, 831 823 &mock_env->str1, HFSPLUS_MAX_STRLEN, 832 - mock_env->buf, HFSPLUS_MAX_STRLEN + 5); 824 + mock_env->buf, HFSPLUS_MAX_STRLEN + 5, 825 + HFS_REGULAR_NAME); 833 826 834 827 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); 835 828 KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, ··· 838 829 839 830 /* Test with smaller max_unistr_len */ 840 831 result = hfsplus_asc2uni(&mock_sb->sb, 841 - &mock_env->str1, 5, "toolongstring", 13); 832 + &mock_env->str1, 5, "toolongstring", 13, 833 + HFS_REGULAR_NAME); 842 834 843 835 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); 844 836 KUNIT_EXPECT_EQ(test, 5, be16_to_cpu(mock_env->str1.length)); 845 837 846 838 /* Test zero max length */ 847 - result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4); 839 + result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 0, "test", 4, 840 + HFS_REGULAR_NAME); 848 841 849 842 KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result); 850 843 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length)); ··· 870 859 871 860 /* Test zero length input */ 872 861 result = hfsplus_asc2uni(&mock_sb->sb, 873 - &ustr, HFSPLUS_MAX_STRLEN, "test", 0); 862 + &ustr, HFSPLUS_MAX_STRLEN, "test", 0, 863 + HFS_REGULAR_NAME); 874 864 875 865 KUNIT_EXPECT_EQ(test, 0, result); 876 866 KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.length)); 877 867 878 868 /* Test input with length mismatch */ 879 869 result = hfsplus_asc2uni(&mock_sb->sb, 880 - &ustr, HFSPLUS_MAX_STRLEN, "hello", 3); 870 + &ustr, HFSPLUS_MAX_STRLEN, "hello", 3, 871 + HFS_REGULAR_NAME); 881 872 882 873 KUNIT_EXPECT_EQ(test, 0, result); 883 874 check_unistr_content(test, &ustr, "hel"); 884 875 885 876 /* Test with various printable ASCII characters */ 886 877 result = hfsplus_asc2uni(&mock_sb->sb, 887 - &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9); 878 + &ustr, HFSPLUS_MAX_STRLEN, "ABC123!@#", 9, 879 + HFS_REGULAR_NAME); 888 880 889 881 KUNIT_EXPECT_EQ(test, 0, result); 890 882 check_unistr_content(test, &ustr, "ABC123!@#"); 891 883 892 884 /* Test null character in the middle */ 893 885 result = hfsplus_asc2uni(&mock_sb->sb, 894 - &ustr, HFSPLUS_MAX_STRLEN, test_str, 3); 886 + &ustr, HFSPLUS_MAX_STRLEN, test_str, 3, 887 + HFS_REGULAR_NAME); 895 888 896 889 KUNIT_EXPECT_EQ(test, 0, result); 897 890 KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); ··· 924 909 /* Test with decomposition disabled (default) */ 925 910 clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); 926 911 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1, 927 - HFSPLUS_MAX_STRLEN, "test", 4); 912 + HFSPLUS_MAX_STRLEN, "test", 4, 913 + HFS_REGULAR_NAME); 928 914 929 915 KUNIT_EXPECT_EQ(test, 0, result); 930 916 check_unistr_content(test, &mock_env->str1, "test"); ··· 933 917 /* Test with decomposition enabled */ 934 918 set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); 935 919 result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str2, 936 - HFSPLUS_MAX_STRLEN, "test", 4); 920 + HFSPLUS_MAX_STRLEN, "test", 4, 921 + HFS_REGULAR_NAME); 937 922 938 923 KUNIT_EXPECT_EQ(test, 0, result); 939 924 check_unistr_content(test, &mock_env->str2, "test");
+5
include/linux/hfs_common.h
··· 166 166 hfsplus_unichr unicode[HFSPLUS_ATTR_MAX_STRLEN]; 167 167 } __packed; 168 168 169 + enum { 170 + HFS_REGULAR_NAME, 171 + HFS_XATTR_NAME, 172 + }; 173 + 169 174 struct hfs_extent { 170 175 __be16 block; 171 176 __be16 count;