From: Viacheslav Dubeyko <slava@dubeyko.com>
To: glaubitz@physik.fu-berlin.de, frank.li@vivo.com
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
vdubeyko@coreweave.com, Viacheslav Dubeyko <slava@dubeyko.com>
Subject: [PATCH 2/2] hfsplus: add KUnit check for hidden directory name
Date: Tue, 3 Nov 2026 19:46:05 -0800 [thread overview]
Message-ID: <20261104034605.1582293-3-slava@dubeyko.com> (raw)
In-Reply-To: <20261104034605.1582293-1-slava@dubeyko.com>
The hidden directory is created and looked up by means of
precomputed UTF-16 name hfsplus_hidden_dir_name_utf16, while its
Linux representation HFSP_HIDDENDIR_NAME_UTF8 is still used
elsewhere. Add regression test that checks that hfsplus_asc2uni()
converts HFSP_HIDDENDIR_NAME_UTF8 with utf8 nls table into exactly
the same UTF-16 string, with decomposition enabled and disabled.
In particular, it checks that every U+2400 (SYMBOL FOR NULL) of
HFSP_HIDDENDIR_NAME_UTF8 is stored as U+0000 (NUL), as macOS
expects, and that the length of the converted name is
HFSP_HIDDENDIR_NAME_UTF16_LEN.
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
---
fs/hfsplus/hfsplus_fs.h | 1 +
fs/hfsplus/super.c | 4 ++-
fs/hfsplus/unicode_test.c | 55 +++++++++++++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
index e2d3f488142b..dd88053cc9b3 100644
--- a/fs/hfsplus/hfsplus_fs.h
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -487,6 +487,7 @@ int hfs_part_find(struct super_block *sb, sector_t *part_start,
sector_t *part_size);
/* super.c */
+extern const struct hfsplus_unistr hfsplus_hidden_dir_name_utf16;
struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino);
void hfsplus_mark_mdb_dirty(struct super_block *sb);
void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr);
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 57858d8685ff..4e878641fb59 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -18,6 +18,7 @@
#include <linux/slab.h>
#include <linux/vfs.h>
#include <linux/nls.h>
+#include <kunit/visibility.h>
static struct inode *hfsplus_alloc_inode(struct super_block *sb);
static void hfsplus_free_inode(struct inode *inode);
@@ -406,7 +407,7 @@ static int hfsplus_statfs(struct dentry *dentry, struct kstatfs *buf)
* by U+2400 (SYMBOL FOR NULL). The catalog key of the hidden directory
* must not depend on the nls table of the mount.
*/
-static const struct hfsplus_unistr hfsplus_hidden_dir_name_utf16 = {
+const struct hfsplus_unistr hfsplus_hidden_dir_name_utf16 = {
.length = cpu_to_be16(HFSP_HIDDENDIR_NAME_UTF16_LEN),
.unicode = {
/* unicode[0 .. HFSP_HIDDENDIR_NUL_COUNT - 1] are U+0000 */
@@ -420,6 +421,7 @@ static const struct hfsplus_unistr hfsplus_hidden_dir_name_utf16 = {
cpu_to_be16('a'),
},
};
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_hidden_dir_name_utf16);
/*
* Create the hidden directory that keeps the indirect node files
diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c
index a683c6d5fc22..cb237a40c0c3 100644
--- a/fs/hfsplus/unicode_test.c
+++ b/fs/hfsplus/unicode_test.c
@@ -1165,6 +1165,60 @@ static void hfsplus_asc2uni_legacy_seq_fixup_test(struct kunit *test)
free_mock_sb(mock_sb);
}
+/*
+ * Test that the precomputed Unicode name of the hidden directory is
+ * exactly what hfsplus_asc2uni() produces from HFSP_HIDDENDIR_NAME_UTF8
+ * with utf8 nls table. Otherwise, the catalog key of the hidden
+ * directory created or looked up by the driver will not match the key
+ * that macOS (and older versions of the driver) uses for this folder.
+ * The result must not depend on the decomposition setting either.
+ */
+static void hfsplus_hidden_dir_name_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct hfsplus_unistr ustr;
+ const struct hfsplus_unistr *expected = &hfsplus_hidden_dir_name_utf16;
+ u16 len = be16_to_cpu(expected->length);
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni_utf8;
+
+ KUNIT_EXPECT_EQ(test, HFSP_HIDDENDIR_NAME_UTF16_LEN, len);
+
+ /* Decomposition is enabled by default */
+ memset(&ustr, 0, sizeof(ustr));
+ result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
+ HFSP_HIDDENDIR_NAME_UTF8,
+ HFSP_HIDDENDIR_NAME_UTF8_LEN,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, len, be16_to_cpu(ustr.length));
+ KUNIT_EXPECT_MEMEQ(test, expected->unicode, ustr.unicode,
+ len * sizeof(ustr.unicode[0]));
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(expected, &ustr));
+
+ /* The same name with disabled decomposition */
+ set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ memset(&ustr, 0, sizeof(ustr));
+ result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN,
+ HFSP_HIDDENDIR_NAME_UTF8,
+ HFSP_HIDDENDIR_NAME_UTF8_LEN,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, len, be16_to_cpu(ustr.length));
+ KUNIT_EXPECT_MEMEQ(test, expected->unicode, ustr.unicode,
+ len * sizeof(ustr.unicode[0]));
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(expected, &ustr));
+
+ free_mock_sb(mock_sb);
+}
+
/* Mock dentry for testing hfsplus_hash_dentry */
static struct dentry test_dentry;
@@ -1873,6 +1927,7 @@ static struct kunit_case hfsplus_unicode_test_cases[] = {
KUNIT_CASE(hfsplus_unicode_combining_reorder_roundtrip_test),
KUNIT_CASE(hfsplus_asc2uni_legacy_decomp_test),
KUNIT_CASE(hfsplus_asc2uni_legacy_seq_fixup_test),
+ KUNIT_CASE(hfsplus_hidden_dir_name_test),
KUNIT_CASE(hfsplus_hash_dentry_basic_test),
KUNIT_CASE(hfsplus_hash_dentry_casefold_test),
KUNIT_CASE(hfsplus_hash_dentry_special_chars_test),
--
2.43.0
prev parent reply other threads:[~2026-11-04 3:46 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 3:46 [PATCH 0/2] hfsplus: make hidden directory name independent from nls Viacheslav Dubeyko
2026-11-04 3:46 ` [PATCH 1/2] hfsplus: make hidden directory name independent from mount's nls Viacheslav Dubeyko
2026-11-04 3:46 ` Viacheslav Dubeyko [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104034605.1582293-3-slava@dubeyko.com \
--to=slava@dubeyko.com \
--cc=frank.li@vivo.com \
--cc=glaubitz@physik.fu-berlin.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=vdubeyko@coreweave.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®