Skip to content

Commit 897c2be

Browse files
committed
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: hfs-linux-kernel/hfs-linux-kernel#178 cc: John Paul Adrian Glaubitz <[email protected]> cc: Yangtao Li <[email protected]> cc: [email protected] Signed-off-by: Viacheslav Dubeyko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Viacheslav Dubeyko <[email protected]>
1 parent 8ad2c6a commit 897c2be

6 files changed

Lines changed: 132 additions & 55 deletions

File tree

fs/hfsplus/attributes.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
5757
if (name) {
5858
int res = hfsplus_asc2uni(sb,
5959
(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);
6162
if (res)
6263
return res;
6364
len = be16_to_cpu(key->attr.key_name.length);

fs/hfsplus/catalog.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ int hfsplus_cat_build_key(struct super_block *sb,
4747

4848
key->cat.parent = cpu_to_be32(parent);
4949
err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN,
50-
str->name, str->len);
50+
str->name, str->len, HFS_REGULAR_NAME);
5151
if (unlikely(err < 0))
5252
return err;
5353

@@ -183,7 +183,7 @@ static int hfsplus_fill_cat_thread(struct super_block *sb,
183183
entry->thread.reserved = 0;
184184
entry->thread.parentID = cpu_to_be32(parentid);
185185
err = hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN,
186-
str->name, str->len);
186+
str->name, str->len, HFS_REGULAR_NAME);
187187
if (unlikely(err < 0))
188188
return err;
189189

fs/hfsplus/hfsplus_fs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ int hfsplus_uni2asc_xattr_str(struct super_block *sb,
506506
const struct hfsplus_attr_unistr *ustr,
507507
char *astr, int *len_p);
508508
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);
510511
int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str);
511512
int hfsplus_compare_dentry(const struct dentry *dentry, unsigned int len,
512513
const char *str, const struct qstr *name);

fs/hfsplus/unicode.c

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,44 @@ static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
147147
return NULL;
148148
}
149149

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+
150184
static int hfsplus_uni2asc(struct super_block *sb,
151185
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)
153188
{
154189
const hfsplus_unichr *ip;
155190
struct nls_table *nls = HFSPLUS_SB(sb)->nls;
@@ -217,14 +252,8 @@ static int hfsplus_uni2asc(struct super_block *sb,
217252
hfsplus_compose_table, c1);
218253
if (ce1)
219254
break;
220-
switch (c0) {
221-
case 0:
222-
c0 = 0x2400;
223-
break;
224-
case '/':
225-
c0 = ':';
226-
break;
227-
}
255+
hfsplus_mac2linux_compatibility_check(c0, &c0,
256+
name_type);
228257
res = nls->uni2char(c0, op, len);
229258
if (res < 0) {
230259
if (res == -ENAMETOOLONG)
@@ -257,16 +286,8 @@ static int hfsplus_uni2asc(struct super_block *sb,
257286
}
258287
}
259288
same:
260-
switch (c0) {
261-
case 0:
262-
cc = 0x2400;
263-
break;
264-
case '/':
265-
cc = ':';
266-
break;
267-
default:
268-
cc = c0;
269-
}
289+
hfsplus_mac2linux_compatibility_check(c0, &cc,
290+
name_type);
270291
done:
271292
res = nls->uni2char(cc, op, len);
272293
if (res < 0) {
@@ -288,7 +309,10 @@ inline int hfsplus_uni2asc_str(struct super_block *sb,
288309
const struct hfsplus_unistr *ustr, char *astr,
289310
int *len_p)
290311
{
291-
return hfsplus_uni2asc(sb, ustr, HFSPLUS_MAX_STRLEN, astr, len_p);
312+
return hfsplus_uni2asc(sb,
313+
ustr, HFSPLUS_MAX_STRLEN,
314+
astr, len_p,
315+
HFS_REGULAR_NAME);
292316
}
293317
EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str);
294318

@@ -297,22 +321,32 @@ inline int hfsplus_uni2asc_xattr_str(struct super_block *sb,
297321
char *astr, int *len_p)
298322
{
299323
return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr,
300-
HFSPLUS_ATTR_MAX_STRLEN, astr, len_p);
324+
HFSPLUS_ATTR_MAX_STRLEN, astr, len_p,
325+
HFS_XATTR_NAME);
301326
}
302327
EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str);
303328

304329
/*
305-
* Convert one or more ASCII characters into a single unicode character.
306-
* Returns the number of ASCII characters corresponding to the unicode char.
330+
* In HFS+, a filename can contain / because : is the separator.
331+
* The slash is a valid filename character on macOS.
332+
* But on Linux, / is the path separator and
333+
* it cannot appear in a filename component.
334+
* There's a parallel mapping for the NUL character (0 -> U+2400).
335+
* NUL terminates strings in C/POSIX but is valid in HFS+ filenames.
307336
*/
308-
static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
309-
wchar_t *uc)
337+
static inline
338+
void hfsplus_linux2mac_compatibility_check(wchar_t *uc, int name_type)
310339
{
311-
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
312-
if (size <= 0) {
313-
*uc = '?';
314-
size = 1;
340+
switch (name_type) {
341+
case HFS_XATTR_NAME:
342+
/* ignore conversion */
343+
return;
344+
345+
default:
346+
/* continue logic */
347+
break;
315348
}
349+
316350
switch (*uc) {
317351
case 0x2400:
318352
*uc = 0;
@@ -321,6 +355,23 @@ static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
321355
*uc = '/';
322356
break;
323357
}
358+
}
359+
360+
/*
361+
* Convert one or more ASCII characters into a single unicode character.
362+
* Returns the number of ASCII characters corresponding to the unicode char.
363+
*/
364+
static inline int asc2unichar(struct super_block *sb, const char *astr, int len,
365+
wchar_t *uc, int name_type)
366+
{
367+
int size = HFSPLUS_SB(sb)->nls->char2uni(astr, len, uc);
368+
369+
if (size <= 0) {
370+
*uc = '?';
371+
size = 1;
372+
}
373+
374+
hfsplus_linux2mac_compatibility_check(uc, name_type);
324375
return size;
325376
}
326377

@@ -395,7 +446,7 @@ static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
395446

396447
int hfsplus_asc2uni(struct super_block *sb,
397448
struct hfsplus_unistr *ustr, int max_unistr_len,
398-
const char *astr, int len)
449+
const char *astr, int len, int name_type)
399450
{
400451
int size, dsize, decompose;
401452
u16 *dstr, outlen = 0;
@@ -404,7 +455,7 @@ int hfsplus_asc2uni(struct super_block *sb,
404455

405456
decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
406457
while (outlen < max_unistr_len && len > 0) {
407-
size = asc2unichar(sb, astr, len, &c);
458+
size = asc2unichar(sb, astr, len, &c, name_type);
408459

409460
if (decompose)
410461
dstr = decompose_unichar(c, &dsize, dhangul);
@@ -452,7 +503,7 @@ int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
452503
len = str->len;
453504
while (len > 0) {
454505
int dsize;
455-
size = asc2unichar(sb, astr, len, &c);
506+
size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME);
456507
astr += size;
457508
len -= size;
458509

@@ -510,7 +561,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry,
510561

511562
while (len1 > 0 && len2 > 0) {
512563
if (!dsize1) {
513-
size = asc2unichar(sb, astr1, len1, &c);
564+
size = asc2unichar(sb, astr1, len1, &c,
565+
HFS_REGULAR_NAME);
514566
astr1 += size;
515567
len1 -= size;
516568

@@ -525,7 +577,8 @@ int hfsplus_compare_dentry(const struct dentry *dentry,
525577
}
526578

527579
if (!dsize2) {
528-
size = asc2unichar(sb, astr2, len2, &c);
580+
size = asc2unichar(sb, astr2, len2, &c,
581+
HFS_REGULAR_NAME);
529582
astr2 += size;
530583
len2 -= size;
531584

0 commit comments

Comments
 (0)