summaryrefslogtreecommitdiffstats
path: root/fs/hfsplus
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837 /fs/hfsplus
downloadlinux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz
linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
Diffstat (limited to 'fs/hfsplus')
-rw-r--r--fs/hfsplus/.kunitconfig8
-rw-r--r--fs/hfsplus/Kconfig31
-rw-r--r--fs/hfsplus/Makefile13
-rw-r--r--fs/hfsplus/attributes.c499
-rw-r--r--fs/hfsplus/bfind.c343
-rw-r--r--fs/hfsplus/bitmap.c246
-rw-r--r--fs/hfsplus/bnode.c720
-rw-r--r--fs/hfsplus/brec.c563
-rw-r--r--fs/hfsplus/btree.c679
-rw-r--r--fs/hfsplus/catalog.c540
-rw-r--r--fs/hfsplus/dir.c643
-rw-r--r--fs/hfsplus/extents.c636
-rw-r--r--fs/hfsplus/hfsplus_fs.h706
-rw-r--r--fs/hfsplus/hfsplus_raw.h20
-rw-r--r--fs/hfsplus/inode.c842
-rw-r--r--fs/hfsplus/ioctl.c70
-rw-r--r--fs/hfsplus/options.c169
-rw-r--r--fs/hfsplus/part_tbl.c157
-rw-r--r--fs/hfsplus/super.c803
-rw-r--r--fs/hfsplus/tables.c3246
-rw-r--r--fs/hfsplus/unicode.c628
-rw-r--r--fs/hfsplus/unicode_test.c1595
-rw-r--r--fs/hfsplus/wrapper.c240
-rw-r--r--fs/hfsplus/xattr.c1036
-rw-r--r--fs/hfsplus/xattr.h41
-rw-r--r--fs/hfsplus/xattr_security.c79
-rw-r--r--fs/hfsplus/xattr_trusted.c38
-rw-r--r--fs/hfsplus/xattr_user.c38
28 files changed, 14629 insertions, 0 deletions
diff --git a/fs/hfsplus/.kunitconfig b/fs/hfsplus/.kunitconfig
new file mode 100644
index 000000000..6c96dc7e8
--- /dev/null
+++ b/fs/hfsplus/.kunitconfig
@@ -0,0 +1,8 @@
+CONFIG_KUNIT=y
+CONFIG_HFSPLUS_FS=y
+CONFIG_HFSPLUS_KUNIT_TEST=y
+CONFIG_BLOCK=y
+CONFIG_BUFFER_HEAD=y
+CONFIG_NLS=y
+CONFIG_NLS_UTF8=y
+CONFIG_LEGACY_DIRECT_IO=y
diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig
new file mode 100644
index 000000000..ca8401cb6
--- /dev/null
+++ b/fs/hfsplus/Kconfig
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config HFSPLUS_FS
+ tristate "Apple Extended HFS file system support"
+ depends on BLOCK
+ select BUFFER_HEAD
+ select NLS
+ select NLS_UTF8
+ select LEGACY_DIRECT_IO
+ help
+ If you say Y here, you will be able to mount extended format
+ Macintosh-formatted hard drive partitions with full read-write access.
+
+ This file system is often called HFS+ and was introduced with
+ MacOS 8. It includes all Mac specific filesystem data such as
+ data forks and creator codes, but it also has several UNIX
+ style features such as file ownership and permissions.
+
+config HFSPLUS_KUNIT_TEST
+ tristate "KUnit tests for HFS+ filesystem" if !KUNIT_ALL_TESTS
+ depends on HFSPLUS_FS && KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds KUnit tests for the HFS+ filesystem.
+
+ KUnit tests run during boot and output the results to the debug
+ log in TAP format (https://testanything.org/). Only useful for
+ kernel devs running KUnit test harness and are not for inclusion
+ into a production build.
+
+ For more information on KUnit and unit tests in general please
+ refer to the KUnit documentation in Documentation/dev-tools/kunit/.
diff --git a/fs/hfsplus/Makefile b/fs/hfsplus/Makefile
new file mode 100644
index 000000000..f2a9ae697
--- /dev/null
+++ b/fs/hfsplus/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+## Makefile for the linux hfsplus filesystem routines.
+#
+
+obj-$(CONFIG_HFSPLUS_FS) += hfsplus.o
+
+hfsplus-objs := super.o options.o inode.o ioctl.o extents.o catalog.o dir.o btree.o \
+ bnode.o brec.o bfind.o tables.o unicode.o wrapper.o bitmap.o part_tbl.o \
+ attributes.o xattr.o xattr_user.o xattr_security.o xattr_trusted.o
+
+# KUnit tests
+obj-$(CONFIG_HFSPLUS_KUNIT_TEST) += unicode_test.o
diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c
new file mode 100644
index 000000000..7c2e589d4
--- /dev/null
+++ b/fs/hfsplus/attributes.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/attributes.c
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Handling of records in attributes tree
+ */
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+static struct kmem_cache *hfsplus_attr_tree_cachep;
+
+int __init hfsplus_create_attr_tree_cache(void)
+{
+ if (hfsplus_attr_tree_cachep)
+ return -EEXIST;
+
+ hfsplus_attr_tree_cachep =
+ kmem_cache_create("hfsplus_attr_cache",
+ sizeof(hfsplus_attr_entry), 0,
+ SLAB_HWCACHE_ALIGN, NULL);
+ if (!hfsplus_attr_tree_cachep)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void hfsplus_destroy_attr_tree_cache(void)
+{
+ kmem_cache_destroy(hfsplus_attr_tree_cachep);
+}
+
+int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2)
+{
+ __be32 k1_cnid, k2_cnid;
+
+ k1_cnid = k1->attr.cnid;
+ k2_cnid = k2->attr.cnid;
+ if (k1_cnid != k2_cnid)
+ return be32_to_cpu(k1_cnid) < be32_to_cpu(k2_cnid) ? -1 : 1;
+
+ return hfsplus_strcmp(
+ (const struct hfsplus_unistr *)&k1->attr.key_name,
+ (const struct hfsplus_unistr *)&k2->attr.key_name);
+}
+
+int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
+ u32 cnid, const char *name)
+{
+ int len;
+
+ memset(key, 0, sizeof(struct hfsplus_attr_key));
+ key->attr.cnid = cpu_to_be32(cnid);
+ if (name) {
+ int res = hfsplus_asc2uni(sb,
+ (struct hfsplus_unistr *)&key->attr.key_name,
+ HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name),
+ HFS_XATTR_NAME);
+ if (res)
+ return res;
+ len = be16_to_cpu(key->attr.key_name.length);
+ } else {
+ key->attr.key_name.length = 0;
+ len = 0;
+ }
+
+ /* The length of the key, as stored in key_len field, does not include
+ * the size of the key_len field itself.
+ * So, offsetof(hfsplus_attr_key, key_name) is a trick because
+ * it takes into consideration key_len field (__be16) of
+ * hfsplus_attr_key structure instead of length field (__be16) of
+ * hfsplus_attr_unistr structure.
+ */
+ key->key_len =
+ cpu_to_be16(offsetof(struct hfsplus_attr_key, key_name) +
+ 2 * len);
+
+ return 0;
+}
+
+hfsplus_attr_entry *hfsplus_alloc_attr_entry(void)
+{
+ return kmem_cache_alloc(hfsplus_attr_tree_cachep, GFP_KERNEL);
+}
+
+void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry)
+{
+ if (entry)
+ kmem_cache_free(hfsplus_attr_tree_cachep, entry);
+}
+
+#define HFSPLUS_INVALID_ATTR_RECORD -1
+
+static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type,
+ u32 cnid, const void *value, size_t size)
+{
+ if (record_type == HFSPLUS_ATTR_FORK_DATA) {
+ /*
+ * Mac OS X supports only inline data attributes.
+ * Do nothing
+ */
+ memset(entry, 0, sizeof(*entry));
+ return sizeof(struct hfsplus_attr_fork_data);
+ } else if (record_type == HFSPLUS_ATTR_EXTENTS) {
+ /*
+ * Mac OS X supports only inline data attributes.
+ * Do nothing.
+ */
+ memset(entry, 0, sizeof(*entry));
+ return sizeof(struct hfsplus_attr_extents);
+ } else if (record_type == HFSPLUS_ATTR_INLINE_DATA) {
+ u16 len;
+
+ memset(entry, 0, sizeof(struct hfsplus_attr_inline_data));
+ entry->inline_data.record_type = cpu_to_be32(record_type);
+ if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE)
+ len = size;
+ else {
+ hfs_dbg("value size %zu is too big\n", size);
+ return HFSPLUS_INVALID_ATTR_RECORD;
+ }
+ entry->inline_data.length = cpu_to_be16(len);
+ memcpy(entry->inline_data.raw_bytes, value, len);
+ /*
+ * Align len on two-byte boundary.
+ * It needs to add pad byte if we have odd len.
+ */
+ len = round_up(len, 2);
+ return offsetof(struct hfsplus_attr_inline_data, raw_bytes) +
+ len;
+ } else /* invalid input */
+ memset(entry, 0, sizeof(*entry));
+
+ return HFSPLUS_INVALID_ATTR_RECORD;
+}
+
+int hfsplus_find_attr(struct super_block *sb, u32 cnid,
+ const char *name, struct hfs_find_data *fd)
+{
+ int err = 0;
+
+ hfs_dbg("name %s, cnid %d\n", name ? name : NULL, cnid);
+
+ if (!HFSPLUS_SB(sb)->attr_tree) {
+ pr_err("attributes file doesn't exist\n");
+ return -EINVAL;
+ }
+
+ if (name) {
+ err = hfsplus_attr_build_key(sb, fd->search_key, cnid, name);
+ if (err)
+ goto failed_find_attr;
+ err = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (err == -ENOENT) {
+ /* file exists but xattr is absent */
+ err = -ENODATA;
+ goto failed_find_attr;
+ } else if (err)
+ goto failed_find_attr;
+ } else {
+ err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
+ if (err)
+ goto failed_find_attr;
+ err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
+ if (err == -ENOENT) {
+ /* file exists but xattr is absent */
+ err = -ENODATA;
+ goto failed_find_attr;
+ } else if (err)
+ goto failed_find_attr;
+ }
+
+failed_find_attr:
+ return err;
+}
+
+int hfsplus_attr_exists(struct inode *inode, const char *name)
+{
+ int err = 0;
+ struct super_block *sb = inode->i_sb;
+ struct hfs_find_data fd;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (!HFSPLUS_SB(sb)->attr_tree)
+ return 0;
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
+ if (err)
+ return 0;
+
+ err = hfsplus_find_attr(sb, inode->i_ino, name, &fd);
+ if (err)
+ goto attr_not_found;
+
+ hfs_find_exit(&fd);
+ return 1;
+
+attr_not_found:
+ hfs_find_exit(&fd);
+ return 0;
+}
+
+static
+int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
+ const void *value, size_t size,
+ struct hfs_find_data *fd,
+ hfsplus_attr_entry *entry_ptr)
+{
+ struct super_block *sb = inode->i_sb;
+ int entry_size;
+ int err;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (name) {
+ err = hfsplus_attr_build_key(sb, fd->search_key,
+ inode->i_ino, name);
+ if (err)
+ return err;
+ } else
+ return -EINVAL;
+
+ /* Mac OS X supports only inline data attributes. */
+ entry_size = hfsplus_attr_build_record(entry_ptr,
+ HFSPLUS_ATTR_INLINE_DATA,
+ inode->i_ino,
+ value, size);
+ if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) {
+ if (size > HFSPLUS_MAX_INLINE_DATA_SIZE)
+ err = -E2BIG;
+ else
+ err = -EINVAL;
+ hfs_dbg("unable to store value: err %d\n", err);
+ return err;
+ }
+
+ err = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ return err;
+ }
+
+ err = hfs_brec_insert(fd, entry_ptr, entry_size);
+ if (err) {
+ hfs_dbg("unable to store value: err %d\n", err);
+ return err;
+ }
+
+ hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
+
+ return 0;
+}
+
+int hfsplus_create_attr(struct inode *inode,
+ const char *name,
+ const void *value, size_t size)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfs_find_data fd;
+ hfsplus_attr_entry *entry_ptr;
+ int err;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (!HFSPLUS_SB(sb)->attr_tree) {
+ pr_err("attributes file doesn't exist\n");
+ return -EINVAL;
+ }
+
+ entry_ptr = hfsplus_alloc_attr_entry();
+ if (!entry_ptr)
+ return -ENOMEM;
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
+ if (err)
+ goto failed_init_create_attr;
+
+ /* Fail early and avoid ENOSPC during the btree operation */
+ err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
+ if (err)
+ goto failed_create_attr;
+
+ err = hfsplus_create_attr_nolock(inode, name, value, size,
+ &fd, entry_ptr);
+ if (err)
+ goto failed_create_attr;
+
+failed_create_attr:
+ hfs_find_exit(&fd);
+
+failed_init_create_attr:
+ hfsplus_destroy_attr_entry(entry_ptr);
+ return err;
+}
+
+static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
+ struct hfs_find_data *fd)
+{
+ int err;
+ __be32 found_cnid, record_type;
+
+ found_cnid = U32_MAX;
+ hfs_bnode_read(fd->bnode, &found_cnid,
+ fd->keyoffset +
+ offsetof(struct hfsplus_attr_key, cnid),
+ sizeof(__be32));
+ if (cnid != be32_to_cpu(found_cnid))
+ return -ENODATA;
+
+ hfs_bnode_read(fd->bnode, &record_type,
+ fd->entryoffset, sizeof(record_type));
+
+ switch (be32_to_cpu(record_type)) {
+ case HFSPLUS_ATTR_INLINE_DATA:
+ /* All is OK. Do nothing. */
+ break;
+ case HFSPLUS_ATTR_FORK_DATA:
+ case HFSPLUS_ATTR_EXTENTS:
+ pr_err("only inline data xattr are supported\n");
+ return -EOPNOTSUPP;
+ default:
+ pr_err("invalid extended attribute record\n");
+ return -ENOENT;
+ }
+
+ /* Avoid btree corruption */
+ hfs_bnode_read(fd->bnode, fd->search_key,
+ fd->keyoffset, fd->keylength);
+
+ err = hfs_brec_remove(fd);
+ if (err)
+ return err;
+
+ hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(inode->i_sb),
+ HFSPLUS_I_ATTR_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
+ return 0;
+}
+
+static
+int hfsplus_delete_attr_nolock(struct inode *inode, const char *name,
+ struct hfs_find_data *fd)
+{
+ struct super_block *sb = inode->i_sb;
+ int err;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (name) {
+ err = hfsplus_attr_build_key(sb, fd->search_key,
+ inode->i_ino, name);
+ if (err)
+ return err;
+ } else {
+ pr_err("invalid extended attribute name\n");
+ return -EINVAL;
+ }
+
+ err = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (err == -ENOENT) {
+ /* file exists but xattr is absent */
+ return -ENODATA;
+ } else if (err)
+ return err;
+
+ err = __hfsplus_delete_attr(inode, inode->i_ino, fd);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int hfsplus_delete_attr(struct inode *inode, const char *name)
+{
+ int err = 0;
+ struct super_block *sb = inode->i_sb;
+ struct hfs_find_data fd;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (!HFSPLUS_SB(sb)->attr_tree) {
+ pr_err("attributes file doesn't exist\n");
+ return -EINVAL;
+ }
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
+ if (err)
+ return err;
+
+ /* Fail early and avoid ENOSPC during the btree operation */
+ err = hfs_bmap_reserve(fd.tree, fd.tree->depth);
+ if (err)
+ goto out;
+
+ err = hfsplus_delete_attr_nolock(inode, name, &fd);
+ if (err)
+ goto out;
+
+out:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
+{
+ int err = 0;
+ struct hfs_find_data fd;
+
+ hfs_dbg("cnid %d\n", cnid);
+
+ if (!HFSPLUS_SB(dir->i_sb)->attr_tree) {
+ pr_err("attributes file doesn't exist\n");
+ return -EINVAL;
+ }
+
+ err = hfs_find_init(HFSPLUS_SB(dir->i_sb)->attr_tree, &fd);
+ if (err)
+ return err;
+
+ for (;;) {
+ err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
+ if (err == -ENOENT || err == -ENODATA) {
+ /*
+ * xattr has not been found
+ */
+ err = -ENODATA;
+ goto end_delete_all;
+ } else if (err) {
+ pr_err("xattr search failed\n");
+ goto end_delete_all;
+ }
+
+ err = __hfsplus_delete_attr(dir, cnid, &fd);
+ if (err)
+ goto end_delete_all;
+ }
+
+end_delete_all:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+int hfsplus_replace_attr(struct inode *inode,
+ const char *name,
+ const void *value, size_t size)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfs_find_data fd;
+ hfsplus_attr_entry *entry_ptr;
+ int err = 0;
+
+ hfs_dbg("name %s, ino %llu\n",
+ name ? name : NULL, inode->i_ino);
+
+ if (!HFSPLUS_SB(sb)->attr_tree) {
+ pr_err("attributes file doesn't exist\n");
+ return -EINVAL;
+ }
+
+ entry_ptr = hfsplus_alloc_attr_entry();
+ if (!entry_ptr)
+ return -ENOMEM;
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd);
+ if (err)
+ goto failed_init_replace_attr;
+
+ /* Fail early and avoid ENOSPC during the btree operation */
+ err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1);
+ if (err)
+ goto failed_replace_attr;
+
+ err = hfsplus_delete_attr_nolock(inode, name, &fd);
+ if (err)
+ goto failed_replace_attr;
+
+ err = hfsplus_create_attr_nolock(inode, name, value, size,
+ &fd, entry_ptr);
+ if (err)
+ goto failed_replace_attr;
+
+failed_replace_attr:
+ hfs_find_exit(&fd);
+
+failed_init_replace_attr:
+ hfsplus_destroy_attr_entry(entry_ptr);
+ return err;
+}
diff --git a/fs/hfsplus/bfind.c b/fs/hfsplus/bfind.c
new file mode 100644
index 000000000..ca9813f58
--- /dev/null
+++ b/fs/hfsplus/bfind.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/bfind.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Search routines for btrees
+ */
+
+#include <linux/slab.h>
+#include "hfsplus_fs.h"
+
+int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
+{
+ void *ptr;
+
+ fd->tree = tree;
+ fd->bnode = NULL;
+ hfs_find_result_init(fd);
+ ptr = kzalloc(tree->max_key_len * 2 + 4, GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+ fd->search_key = ptr;
+ fd->key = ptr + tree->max_key_len + 2;
+ hfs_dbg("cnid %d, caller %ps\n",
+ tree->cnid, __builtin_return_address(0));
+ mutex_lock_nested(&tree->tree_lock,
+ hfsplus_btree_lock_class(tree));
+ return 0;
+}
+
+void hfs_find_exit(struct hfs_find_data *fd)
+{
+ hfs_bnode_put(fd->bnode);
+ kfree(fd->search_key);
+ hfs_dbg("cnid %d, caller %ps\n",
+ fd->tree->cnid, __builtin_return_address(0));
+ mutex_unlock(&fd->tree->tree_lock);
+ fd->tree = NULL;
+}
+
+int hfs_find_1st_rec_by_cnid(struct hfs_bnode *bnode,
+ struct hfs_find_data *fd,
+ int *begin,
+ int *end,
+ int *cur_rec)
+{
+ __be32 cur_cnid;
+ __be32 search_cnid;
+
+ if (bnode->tree->cnid == HFSPLUS_EXT_CNID) {
+ cur_cnid = fd->key->ext.cnid;
+ search_cnid = fd->search_key->ext.cnid;
+ } else if (bnode->tree->cnid == HFSPLUS_CAT_CNID) {
+ cur_cnid = fd->key->cat.parent;
+ search_cnid = fd->search_key->cat.parent;
+ } else if (bnode->tree->cnid == HFSPLUS_ATTR_CNID) {
+ cur_cnid = fd->key->attr.cnid;
+ search_cnid = fd->search_key->attr.cnid;
+ } else {
+ cur_cnid = 0; /* used-uninitialized warning */
+ search_cnid = 0;
+ BUG();
+ }
+
+ if (cur_cnid == search_cnid) {
+ (*end) = (*cur_rec);
+ if ((*begin) == (*end))
+ return 1;
+ } else {
+ if (be32_to_cpu(cur_cnid) < be32_to_cpu(search_cnid))
+ (*begin) = (*cur_rec) + 1;
+ else
+ (*end) = (*cur_rec) - 1;
+ }
+
+ return 0;
+}
+
+int hfs_find_rec_by_key(struct hfs_bnode *bnode,
+ struct hfs_find_data *fd,
+ int *begin,
+ int *end,
+ int *cur_rec)
+{
+ int cmpval;
+
+ cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
+ if (!cmpval) {
+ (*end) = (*cur_rec);
+ return 1;
+ }
+ if (cmpval < 0)
+ (*begin) = (*cur_rec) + 1;
+ else
+ *(end) = (*cur_rec) - 1;
+
+ return 0;
+}
+
+/* Find the record in bnode that best matches key (not greater than...)*/
+int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+ search_strategy_t rec_found)
+{
+ u16 off, len, keylen;
+ int rec;
+ int b, e;
+ int res = -ENOENT;
+
+ BUG_ON(!rec_found);
+ hfs_find_result_init(fd);
+ if (hfs_bnode_num_recs_invalid(bnode))
+ goto fail;
+
+ b = 0;
+ e = bnode->num_recs - 1;
+ do {
+ rec = (e + b) / 2;
+ len = hfs_brec_lenoff(bnode, rec, &off);
+ keylen = hfs_brec_keylen(bnode, rec);
+ if (hfs_brec_len_invalid(bnode, len) ||
+ hfs_brec_len_invalid(bnode, keylen)) {
+ res = -EINVAL;
+ goto fail;
+ }
+ hfs_bnode_read(bnode, fd->key, off, keylen);
+ if (rec_found(bnode, fd, &b, &e, &rec)) {
+ res = 0;
+ goto done;
+ }
+ } while (b <= e);
+
+ if (rec != e && e >= 0) {
+ len = hfs_brec_lenoff(bnode, e, &off);
+ keylen = hfs_brec_keylen(bnode, e);
+ if (hfs_brec_len_invalid(bnode, keylen) ||
+ hfs_brec_len_invalid(bnode, len)) {
+ res = -EINVAL;
+ goto fail;
+ }
+ hfs_bnode_read(bnode, fd->key, off, keylen);
+ }
+
+done:
+ fd->record = e;
+ fd->keyoffset = off;
+ fd->keylength = keylen;
+ fd->entryoffset = off + keylen;
+ fd->entrylength = len - keylen;
+
+fail:
+ return res;
+}
+
+/* Traverse a B*Tree from the root to a leaf finding best fit to key */
+/* Return allocated copy of node found, set recnum to best record */
+int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *bnode;
+ u32 nidx, parent;
+ __be32 data;
+ int height, res;
+
+ hfs_find_result_init(fd);
+
+ tree = fd->tree;
+ if (fd->bnode)
+ hfs_bnode_put(fd->bnode);
+ fd->bnode = NULL;
+ nidx = tree->root;
+ if (!nidx)
+ return -ENOENT;
+ height = tree->depth;
+ res = 0;
+ parent = 0;
+ for (;;) {
+ bnode = hfs_bnode_find(tree, nidx);
+ if (IS_ERR(bnode)) {
+ res = PTR_ERR(bnode);
+ bnode = NULL;
+ break;
+ }
+ if (bnode->height != height)
+ goto invalid;
+ if (bnode->type != (--height ? HFS_NODE_INDEX : HFS_NODE_LEAF))
+ goto invalid;
+ bnode->parent = parent;
+
+ res = __hfs_brec_find(bnode, fd, do_key_compare);
+ if (!height)
+ break;
+ if (fd->record < 0)
+ goto release;
+
+ parent = nidx;
+ hfs_bnode_read(bnode, &data, fd->entryoffset, 4);
+ nidx = be32_to_cpu(data);
+ hfs_bnode_put(bnode);
+ }
+ fd->bnode = bnode;
+ return res;
+
+invalid:
+ pr_err("inconsistency in B*Tree (%d,%d,%d,%u,%u)\n",
+ height, bnode->height, bnode->type, nidx, parent);
+ res = -EIO;
+release:
+ hfs_bnode_put(bnode);
+ return res;
+}
+
+int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len)
+{
+ int res;
+
+ res = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (res)
+ return res;
+ if (fd->entrylength > rec_len)
+ return -EINVAL;
+ hfs_bnode_read(fd->bnode, rec, fd->entryoffset, fd->entrylength);
+ return 0;
+}
+
+int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *bnode;
+ int idx, res = 0;
+ u16 off, len, keylen;
+
+ bnode = fd->bnode;
+ tree = bnode->tree;
+
+ if (cnt < 0) {
+ cnt = -cnt;
+ while (cnt > fd->record) {
+ cnt -= fd->record + 1;
+ fd->record = bnode->num_recs - 1;
+ idx = bnode->prev;
+ if (!idx) {
+ res = -ENOENT;
+ goto out;
+ }
+ hfs_bnode_put(bnode);
+ bnode = hfs_bnode_find(tree, idx);
+ if (IS_ERR(bnode)) {
+ res = PTR_ERR(bnode);
+ bnode = NULL;
+ goto out;
+ }
+ }
+ fd->record -= cnt;
+ } else {
+ while (cnt >= bnode->num_recs - fd->record) {
+ cnt -= bnode->num_recs - fd->record;
+ fd->record = 0;
+ idx = bnode->next;
+ if (!idx) {
+ res = -ENOENT;
+ goto out;
+ }
+ hfs_bnode_put(bnode);
+ bnode = hfs_bnode_find(tree, idx);
+ if (IS_ERR(bnode)) {
+ res = PTR_ERR(bnode);
+ bnode = NULL;
+ goto out;
+ }
+ }
+ fd->record += cnt;
+ }
+
+ len = hfs_brec_lenoff(bnode, fd->record, &off);
+ keylen = hfs_brec_keylen(bnode, fd->record);
+ if (hfs_brec_len_invalid(bnode, len) ||
+ hfs_brec_len_invalid(bnode, keylen)) {
+ res = -EINVAL;
+ goto out;
+ }
+ fd->keyoffset = off;
+ fd->keylength = keylen;
+ fd->entryoffset = off + keylen;
+ fd->entrylength = len - keylen;
+ hfs_bnode_read(bnode, fd->key, off, keylen);
+out:
+ fd->bnode = bnode;
+ return res;
+}
+
+/**
+ * hfsplus_brec_read_cat - read and validate a catalog record
+ * @fd: find data structure
+ * @entry: pointer to catalog entry to read into
+ *
+ * Reads a catalog record and validates its size matches the expected
+ * size based on the record type.
+ *
+ * Returns 0 on success, or negative error code on failure.
+ */
+int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry)
+{
+ int res;
+ u32 expected_size;
+
+ res = hfs_brec_read(fd, entry, sizeof(hfsplus_cat_entry));
+ if (res)
+ return res;
+
+ /* Validate catalog record size based on type */
+ switch (be16_to_cpu(entry->type)) {
+ case HFSPLUS_FOLDER:
+ expected_size = sizeof(struct hfsplus_cat_folder);
+ break;
+ case HFSPLUS_FILE:
+ expected_size = sizeof(struct hfsplus_cat_file);
+ break;
+ case HFSPLUS_FOLDER_THREAD:
+ case HFSPLUS_FILE_THREAD:
+ /* Ensure we have at least the fixed fields before reading nodeName.length */
+ if (fd->entrylength < HFSPLUS_MIN_THREAD_SZ) {
+ pr_err("thread record too short (got %u)\n", fd->entrylength);
+ return -EIO;
+ }
+ expected_size = hfsplus_cat_thread_size(&entry->thread);
+ break;
+ default:
+ pr_err("unknown catalog record type %d\n",
+ be16_to_cpu(entry->type));
+ return -EIO;
+ }
+
+ if (fd->entrylength != expected_size) {
+ pr_err("catalog record size mismatch (type %d, got %u, expected %u)\n",
+ be16_to_cpu(entry->type), fd->entrylength, expected_size);
+ return -EIO;
+ }
+
+ return 0;
+}
diff --git a/fs/hfsplus/bitmap.c b/fs/hfsplus/bitmap.c
new file mode 100644
index 000000000..1b3af8c87
--- /dev/null
+++ b/fs/hfsplus/bitmap.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/bitmap.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of allocation file
+ */
+
+#include <linux/pagemap.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+#define PAGE_CACHE_BITS (PAGE_SIZE * 8)
+
+int hfsplus_block_allocate(struct super_block *sb, u32 size,
+ u32 offset, u32 *max)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct page *page;
+ struct address_space *mapping;
+ __be32 *pptr, *curr, *end;
+ u32 mask, start, len, n;
+ __be32 val;
+ int i;
+
+ len = *max;
+ if (!len)
+ return size;
+
+ hfs_dbg("size %u, offset %u, len %u\n", size, offset, len);
+ mutex_lock(&sbi->alloc_mutex);
+ mapping = sbi->alloc_file->i_mapping;
+ page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS, NULL);
+ if (IS_ERR(page)) {
+ start = size;
+ goto out;
+ }
+ pptr = kmap_local_page(page);
+ curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
+ i = offset % 32;
+ offset &= ~(PAGE_CACHE_BITS - 1);
+ if ((size ^ offset) / PAGE_CACHE_BITS)
+ end = pptr + PAGE_CACHE_BITS / 32;
+ else
+ end = pptr + ((size + 31) & (PAGE_CACHE_BITS - 1)) / 32;
+
+ /* scan the first partial u32 for zero bits */
+ val = *curr;
+ if (~val) {
+ n = be32_to_cpu(val);
+ mask = (1U << 31) >> i;
+ for (; i < 32; mask >>= 1, i++) {
+ if (!(n & mask))
+ goto found;
+ }
+ }
+ curr++;
+
+ /* scan complete u32s for the first zero bit */
+ while (1) {
+ while (curr < end) {
+ val = *curr;
+ if (~val) {
+ n = be32_to_cpu(val);
+ mask = 1 << 31;
+ for (i = 0; i < 32; mask >>= 1, i++) {
+ if (!(n & mask))
+ goto found;
+ }
+ }
+ curr++;
+ }
+ kunmap_local(pptr);
+ offset += PAGE_CACHE_BITS;
+ if (offset >= size)
+ break;
+ page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,
+ NULL);
+ if (IS_ERR(page)) {
+ start = size;
+ goto out;
+ }
+ curr = pptr = kmap_local_page(page);
+ if ((size ^ offset) / PAGE_CACHE_BITS)
+ end = pptr + PAGE_CACHE_BITS / 32;
+ else
+ end = pptr + ((size + 31) & (PAGE_CACHE_BITS - 1)) / 32;
+ }
+ hfs_dbg("bitmap full\n");
+ start = size;
+ goto out;
+
+found:
+ start = offset + (curr - pptr) * 32 + i;
+ if (start >= size) {
+ hfs_dbg("bitmap full\n");
+ goto out;
+ }
+ /* do any partial u32 at the start */
+ len = min(size - start, len);
+ while (1) {
+ n |= mask;
+ if (++i >= 32)
+ break;
+ mask >>= 1;
+ if (!--len || n & mask)
+ goto done;
+ }
+ if (!--len)
+ goto done;
+ *curr++ = cpu_to_be32(n);
+ /* do full u32s */
+ while (1) {
+ while (curr < end) {
+ n = be32_to_cpu(*curr);
+ if (len < 32)
+ goto last;
+ if (n) {
+ len = 32;
+ goto last;
+ }
+ *curr++ = cpu_to_be32(0xffffffff);
+ len -= 32;
+ }
+ set_page_dirty(page);
+ kunmap_local(pptr);
+ offset += PAGE_CACHE_BITS;
+ page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,
+ NULL);
+ if (IS_ERR(page)) {
+ start = size;
+ goto out;
+ }
+ pptr = kmap_local_page(page);
+ curr = pptr;
+ end = pptr + PAGE_CACHE_BITS / 32;
+ }
+last:
+ /* do any partial u32 at end */
+ mask = 1U << 31;
+ for (i = 0; i < len; i++) {
+ if (n & mask)
+ break;
+ n |= mask;
+ mask >>= 1;
+ }
+done:
+ *curr = cpu_to_be32(n);
+ set_page_dirty(page);
+ kunmap_local(pptr);
+ *max = offset + (curr - pptr) * 32 + i - start;
+ sbi->free_blocks -= *max;
+ hfsplus_mark_mdb_dirty(sb);
+ hfs_dbg("start %u, max %u\n", start, *max);
+out:
+ mutex_unlock(&sbi->alloc_mutex);
+ return start;
+}
+
+int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct page *page;
+ struct address_space *mapping;
+ __be32 *pptr, *curr, *end;
+ u32 mask, len, pnr;
+ int i;
+
+ /* is there any actual work to be done? */
+ if (!count)
+ return 0;
+
+ hfs_dbg("offset %u, count %u\n", offset, count);
+ /* are all of the bits in range? */
+ if ((offset + count) > sbi->total_blocks)
+ return -ENOENT;
+
+ mutex_lock(&sbi->alloc_mutex);
+ mapping = sbi->alloc_file->i_mapping;
+ pnr = offset / PAGE_CACHE_BITS;
+ page = read_mapping_page(mapping, pnr, NULL);
+ if (IS_ERR(page))
+ goto kaboom;
+ pptr = kmap_local_page(page);
+ curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
+ end = pptr + PAGE_CACHE_BITS / 32;
+ len = count;
+
+ /* do any partial u32 at the start */
+ i = offset % 32;
+ if (i) {
+ int j = 32 - i;
+ mask = 0xffffffffU << j;
+ if (j > count) {
+ mask |= 0xffffffffU >> (i + count);
+ *curr++ &= cpu_to_be32(mask);
+ goto out;
+ }
+ *curr++ &= cpu_to_be32(mask);
+ count -= j;
+ }
+
+ /* do full u32s */
+ while (1) {
+ while (curr < end) {
+ if (count < 32)
+ goto done;
+ *curr++ = 0;
+ count -= 32;
+ }
+ if (!count)
+ break;
+ set_page_dirty(page);
+ kunmap_local(pptr);
+ page = read_mapping_page(mapping, ++pnr, NULL);
+ if (IS_ERR(page))
+ goto kaboom;
+ pptr = kmap_local_page(page);
+ curr = pptr;
+ end = pptr + PAGE_CACHE_BITS / 32;
+ }
+done:
+ /* do any partial u32 at end */
+ if (count) {
+ mask = 0xffffffffU >> count;
+ *curr &= cpu_to_be32(mask);
+ }
+out:
+ set_page_dirty(page);
+ kunmap_local(pptr);
+ sbi->free_blocks += len;
+ hfsplus_mark_mdb_dirty(sb);
+ mutex_unlock(&sbi->alloc_mutex);
+
+ return 0;
+
+kaboom:
+ pr_crit("unable to mark blocks free: error %ld\n", PTR_ERR(page));
+ mutex_unlock(&sbi->alloc_mutex);
+
+ return -EIO;
+}
diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c
new file mode 100644
index 000000000..3a1a1fa6f
--- /dev/null
+++ b/fs/hfsplus/bnode.c
@@ -0,0 +1,720 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/bnode.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle basic btree node operations
+ */
+
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/pagemap.h>
+#include <linux/fs.h>
+#include <linux/swap.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+
+/* Copy a specified range of bytes from the raw data of a node */
+void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+{
+ struct page **pagep;
+ u32 l;
+
+ memset(buf, 0, len);
+
+ if (!is_bnode_offset_valid(node, off))
+ return;
+
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+ }
+
+ len = check_and_correct_requested_length(node, off, len);
+
+ off += node->page_offset;
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+ l = min_t(u32, len, PAGE_SIZE - off);
+ memcpy_from_page(buf, *pagep, off, l);
+
+ while ((len -= l) != 0) {
+ buf += l;
+ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_from_page(buf, *++pagep, 0, l);
+ }
+}
+
+u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+{
+ __be16 data;
+ /* TODO: optimize later... */
+ hfs_bnode_read(node, &data, off, 2);
+ return be16_to_cpu(data);
+}
+
+u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off)
+{
+ u8 data;
+ /* TODO: optimize later... */
+ hfs_bnode_read(node, &data, off, 1);
+ return data;
+}
+
+void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off)
+{
+ struct hfs_btree *tree;
+ u32 key_len;
+
+ tree = node->tree;
+ if (node->type == HFS_NODE_LEAF ||
+ tree->attributes & HFS_TREE_VARIDXKEYS ||
+ node->tree->cnid == HFSPLUS_ATTR_CNID)
+ key_len = hfs_bnode_read_u16(node, off) + 2;
+ else
+ key_len = tree->max_key_len + 2;
+
+ if (key_len > sizeof(hfsplus_btree_key) || key_len < 1) {
+ memset(key, 0, sizeof(hfsplus_btree_key));
+ pr_err("hfsplus: Invalid key length: %u\n", key_len);
+ return;
+ }
+
+ hfs_bnode_read(node, key, off, key_len);
+}
+
+void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+{
+ struct page **pagep;
+ u32 l;
+
+ if (!is_bnode_offset_valid(node, off))
+ return;
+
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+ }
+
+ len = check_and_correct_requested_length(node, off, len);
+
+ off += node->page_offset;
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+ l = min_t(u32, len, PAGE_SIZE - off);
+ memcpy_to_page(*pagep, off, buf, l);
+ set_page_dirty(*pagep);
+
+ while ((len -= l) != 0) {
+ buf += l;
+ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_to_page(*++pagep, 0, buf, l);
+ set_page_dirty(*pagep);
+ }
+}
+
+void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+{
+ __be16 v = cpu_to_be16(data);
+ /* TODO: optimize later... */
+ hfs_bnode_write(node, &v, off, 2);
+}
+
+void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+{
+ struct page **pagep;
+ u32 l;
+
+ if (!is_bnode_offset_valid(node, off))
+ return;
+
+ if (len == 0) {
+ pr_err("requested zero length: "
+ "NODE: id %u, type %#x, height %u, "
+ "node_size %u, offset %u, len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len);
+ return;
+ }
+
+ len = check_and_correct_requested_length(node, off, len);
+
+ off += node->page_offset;
+ pagep = node->page + (off >> PAGE_SHIFT);
+ off &= ~PAGE_MASK;
+
+ l = min_t(u32, len, PAGE_SIZE - off);
+ memzero_page(*pagep, off, l);
+ set_page_dirty(*pagep);
+
+ while ((len -= l) != 0) {
+ l = min_t(u32, len, PAGE_SIZE);
+ memzero_page(*++pagep, 0, l);
+ set_page_dirty(*pagep);
+ }
+}
+
+void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
+ struct hfs_bnode *src_node, u32 src, u32 len)
+{
+ struct page **src_page, **dst_page;
+ u32 l;
+
+ hfs_dbg("dst %u, src %u, len %u\n", dst, src, len);
+ if (!len)
+ return;
+
+ len = check_and_correct_requested_length(src_node, src, len);
+ len = check_and_correct_requested_length(dst_node, dst, len);
+
+ src += src_node->page_offset;
+ dst += dst_node->page_offset;
+ src_page = src_node->page + (src >> PAGE_SHIFT);
+ src &= ~PAGE_MASK;
+ dst_page = dst_node->page + (dst >> PAGE_SHIFT);
+ dst &= ~PAGE_MASK;
+
+ if (src == dst) {
+ l = min_t(u32, len, PAGE_SIZE - src);
+ memcpy_page(*dst_page, src, *src_page, src, l);
+ set_page_dirty(*dst_page);
+
+ while ((len -= l) != 0) {
+ l = min_t(u32, len, PAGE_SIZE);
+ memcpy_page(*++dst_page, 0, *++src_page, 0, l);
+ set_page_dirty(*dst_page);
+ }
+ } else {
+ void *src_ptr, *dst_ptr;
+
+ do {
+ dst_ptr = kmap_local_page(*dst_page) + dst;
+ src_ptr = kmap_local_page(*src_page) + src;
+ if (PAGE_SIZE - src < PAGE_SIZE - dst) {
+ l = PAGE_SIZE - src;
+ src = 0;
+ dst += l;
+ } else {
+ l = PAGE_SIZE - dst;
+ src += l;
+ dst = 0;
+ }
+ l = min(len, l);
+ memcpy(dst_ptr, src_ptr, l);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ if (!dst)
+ dst_page++;
+ else
+ src_page++;
+ } while ((len -= l));
+ }
+}
+
+void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+{
+ struct page **src_page, **dst_page;
+ void *src_ptr, *dst_ptr;
+ u32 l;
+
+ hfs_dbg("dst %u, src %u, len %u\n", dst, src, len);
+ if (!len)
+ return;
+
+ len = check_and_correct_requested_length(node, src, len);
+ len = check_and_correct_requested_length(node, dst, len);
+
+ src += node->page_offset;
+ dst += node->page_offset;
+ if (dst > src) {
+ src += len - 1;
+ src_page = node->page + (src >> PAGE_SHIFT);
+ src = (src & ~PAGE_MASK) + 1;
+ dst += len - 1;
+ dst_page = node->page + (dst >> PAGE_SHIFT);
+ dst = (dst & ~PAGE_MASK) + 1;
+
+ if (src == dst) {
+ while (src < len) {
+ dst_ptr = kmap_local_page(*dst_page);
+ src_ptr = kmap_local_page(*src_page);
+ memmove(dst_ptr, src_ptr, src);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ len -= src;
+ src = PAGE_SIZE;
+ src_page--;
+ dst_page--;
+ }
+ src -= len;
+ dst_ptr = kmap_local_page(*dst_page);
+ src_ptr = kmap_local_page(*src_page);
+ memmove(dst_ptr + src, src_ptr + src, len);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ } else {
+ do {
+ dst_ptr = kmap_local_page(*dst_page) + dst;
+ src_ptr = kmap_local_page(*src_page) + src;
+ if (src < dst) {
+ l = src;
+ src = PAGE_SIZE;
+ dst -= l;
+ } else {
+ l = dst;
+ src -= l;
+ dst = PAGE_SIZE;
+ }
+ l = min(len, l);
+ memmove(dst_ptr - l, src_ptr - l, l);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ if (dst == PAGE_SIZE)
+ dst_page--;
+ else
+ src_page--;
+ } while ((len -= l));
+ }
+ } else {
+ src_page = node->page + (src >> PAGE_SHIFT);
+ src &= ~PAGE_MASK;
+ dst_page = node->page + (dst >> PAGE_SHIFT);
+ dst &= ~PAGE_MASK;
+
+ if (src == dst) {
+ l = min_t(u32, len, PAGE_SIZE - src);
+
+ dst_ptr = kmap_local_page(*dst_page) + src;
+ src_ptr = kmap_local_page(*src_page) + src;
+ memmove(dst_ptr, src_ptr, l);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+
+ while ((len -= l) != 0) {
+ l = min_t(u32, len, PAGE_SIZE);
+ dst_ptr = kmap_local_page(*++dst_page);
+ src_ptr = kmap_local_page(*++src_page);
+ memmove(dst_ptr, src_ptr, l);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ }
+ } else {
+ do {
+ dst_ptr = kmap_local_page(*dst_page) + dst;
+ src_ptr = kmap_local_page(*src_page) + src;
+ if (PAGE_SIZE - src <
+ PAGE_SIZE - dst) {
+ l = PAGE_SIZE - src;
+ src = 0;
+ dst += l;
+ } else {
+ l = PAGE_SIZE - dst;
+ src += l;
+ dst = 0;
+ }
+ l = min(len, l);
+ memmove(dst_ptr, src_ptr, l);
+ kunmap_local(src_ptr);
+ set_page_dirty(*dst_page);
+ kunmap_local(dst_ptr);
+ if (!dst)
+ dst_page++;
+ else
+ src_page++;
+ } while ((len -= l));
+ }
+ }
+}
+
+void hfs_bnode_dump(struct hfs_bnode *node)
+{
+ struct hfs_bnode_desc desc;
+ __be32 cnid;
+ int i, off, key_off;
+ u16 num_recs;
+
+ hfs_dbg("node %d\n", node->this);
+ hfs_bnode_read(node, &desc, 0, sizeof(desc));
+ num_recs = node->num_recs;
+ hfs_dbg("next %d, prev %d, type %d, height %d, num_recs %d\n",
+ be32_to_cpu(desc.next), be32_to_cpu(desc.prev),
+ desc.type, desc.height, be16_to_cpu(desc.num_recs));
+
+ if (hfs_bnode_num_recs_invalid(node)) {
+ hfs_dbg("invalid num_recs %u\n", num_recs);
+ return;
+ }
+
+ off = node->tree->node_size - 2;
+ for (i = num_recs; i >= 0; off -= 2, i--) {
+ key_off = hfs_bnode_read_u16(node, off);
+ hfs_dbg(" key_off %d", key_off);
+ if (i && node->type == HFS_NODE_INDEX) {
+ int tmp;
+
+ if (node->tree->attributes & HFS_TREE_VARIDXKEYS ||
+ node->tree->cnid == HFSPLUS_ATTR_CNID)
+ tmp = hfs_bnode_read_u16(node, key_off) + 2;
+ else
+ tmp = node->tree->max_key_len + 2;
+ hfs_dbg(" (%d", tmp);
+ hfs_bnode_read(node, &cnid, key_off + tmp, 4);
+ hfs_dbg(", cnid %d)", be32_to_cpu(cnid));
+ } else if (i && node->type == HFS_NODE_LEAF) {
+ int tmp;
+
+ tmp = hfs_bnode_read_u16(node, key_off);
+ hfs_dbg(" (%d)", tmp);
+ }
+ }
+ hfs_dbg("\n");
+}
+
+void hfs_bnode_unlink(struct hfs_bnode *node)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *tmp;
+ __be32 cnid;
+
+ tree = node->tree;
+ if (node->prev) {
+ tmp = hfs_bnode_find(tree, node->prev);
+ if (IS_ERR(tmp))
+ return;
+ tmp->next = node->next;
+ cnid = cpu_to_be32(tmp->next);
+ hfs_bnode_write(tmp, &cnid,
+ offsetof(struct hfs_bnode_desc, next), 4);
+ hfs_bnode_put(tmp);
+ } else if (node->type == HFS_NODE_LEAF)
+ tree->leaf_head = node->next;
+
+ if (node->next) {
+ tmp = hfs_bnode_find(tree, node->next);
+ if (IS_ERR(tmp))
+ return;
+ tmp->prev = node->prev;
+ cnid = cpu_to_be32(tmp->prev);
+ hfs_bnode_write(tmp, &cnid,
+ offsetof(struct hfs_bnode_desc, prev), 4);
+ hfs_bnode_put(tmp);
+ } else if (node->type == HFS_NODE_LEAF)
+ tree->leaf_tail = node->prev;
+
+ /* move down? */
+ if (!node->prev && !node->next)
+ hfs_dbg("btree delete level\n");
+ if (!node->parent) {
+ tree->root = 0;
+ tree->depth = 0;
+ }
+
+ spin_lock(&tree->hash_lock);
+ set_bit(HFS_BNODE_DELETED, &node->flags);
+ spin_unlock(&tree->hash_lock);
+}
+
+static inline int hfs_bnode_hash(u32 num)
+{
+ num = (num >> 16) + num;
+ num += num >> 8;
+ return num & (NODE_HASH_SIZE - 1);
+}
+
+struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid)
+{
+ struct hfs_bnode *node;
+
+ if (cnid >= tree->node_count) {
+ pr_err("request for non-existent node %d in B*Tree\n",
+ cnid);
+ return NULL;
+ }
+
+ for (node = tree->node_hash[hfs_bnode_hash(cnid)];
+ node; node = node->next_hash)
+ if (node->this == cnid)
+ return node;
+ return NULL;
+}
+
+static struct hfs_bnode *__hfs_bnode_create(struct hfs_btree *tree, u32 cnid)
+{
+ struct hfs_bnode *node, *node2;
+ struct address_space *mapping;
+ struct page *page;
+ int block, i, hash;
+ loff_t off;
+
+ if (cnid >= tree->node_count) {
+ pr_err("request for non-existent node %d in B*Tree\n",
+ cnid);
+ return NULL;
+ }
+
+ node = kzalloc_flex(*node, page, tree->pages_per_bnode);
+ if (!node)
+ return NULL;
+ node->tree = tree;
+ node->this = cnid;
+ set_bit(HFS_BNODE_NEW, &node->flags);
+ atomic_set(&node->refcnt, 1);
+ hfs_dbg("cnid %d, node %d, refcnt 1\n",
+ node->tree->cnid, node->this);
+ init_waitqueue_head(&node->lock_wq);
+ spin_lock(&tree->hash_lock);
+ node2 = hfs_bnode_findhash(tree, cnid);
+ if (!node2) {
+ hash = hfs_bnode_hash(cnid);
+ node->next_hash = tree->node_hash[hash];
+ tree->node_hash[hash] = node;
+ tree->node_hash_cnt++;
+ } else {
+ hfs_bnode_get(node2);
+ spin_unlock(&tree->hash_lock);
+ kfree(node);
+ wait_event(node2->lock_wq,
+ !test_bit(HFS_BNODE_NEW, &node2->flags));
+ return node2;
+ }
+ spin_unlock(&tree->hash_lock);
+
+ mapping = tree->inode->i_mapping;
+ off = (loff_t)cnid << tree->node_size_shift;
+ block = off >> PAGE_SHIFT;
+ node->page_offset = off & ~PAGE_MASK;
+ for (i = 0; i < tree->pages_per_bnode; block++, i++) {
+ page = read_mapping_page(mapping, block, NULL);
+ if (IS_ERR(page))
+ goto fail;
+ node->page[i] = page;
+ }
+
+ return node;
+fail:
+ set_bit(HFS_BNODE_ERROR, &node->flags);
+ return node;
+}
+
+void hfs_bnode_unhash(struct hfs_bnode *node)
+{
+ struct hfs_bnode **p;
+
+ hfs_dbg("cnid %d, node %d, refcnt %d\n",
+ node->tree->cnid, node->this, atomic_read(&node->refcnt));
+ for (p = &node->tree->node_hash[hfs_bnode_hash(node->this)];
+ *p && *p != node; p = &(*p)->next_hash)
+ ;
+ BUG_ON(!*p);
+ *p = node->next_hash;
+ node->tree->node_hash_cnt--;
+}
+
+/* Load a particular node out of a tree */
+struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)
+{
+ struct hfs_bnode *node;
+ struct hfs_bnode_desc *desc;
+ int i, rec_off, off, next_off;
+ int entry_size, key_size;
+
+ spin_lock(&tree->hash_lock);
+ node = hfs_bnode_findhash(tree, num);
+ if (node) {
+ hfs_bnode_get(node);
+ spin_unlock(&tree->hash_lock);
+ wait_event(node->lock_wq,
+ !test_bit(HFS_BNODE_NEW, &node->flags));
+ if (test_bit(HFS_BNODE_ERROR, &node->flags))
+ goto node_error;
+ return node;
+ }
+ spin_unlock(&tree->hash_lock);
+ node = __hfs_bnode_create(tree, num);
+ if (!node)
+ return ERR_PTR(-ENOMEM);
+ if (test_bit(HFS_BNODE_ERROR, &node->flags))
+ goto node_error;
+ if (!test_bit(HFS_BNODE_NEW, &node->flags))
+ return node;
+
+ desc = (struct hfs_bnode_desc *)(kmap_local_page(node->page[0]) +
+ node->page_offset);
+ node->prev = be32_to_cpu(desc->prev);
+ node->next = be32_to_cpu(desc->next);
+ node->num_recs = be16_to_cpu(desc->num_recs);
+ node->type = desc->type;
+ node->height = desc->height;
+ kunmap_local(desc);
+
+ if (hfs_bnode_num_recs_invalid(node))
+ goto node_error;
+
+ switch (node->type) {
+ case HFS_NODE_HEADER:
+ case HFS_NODE_MAP:
+ if (node->height != 0)
+ goto node_error;
+ break;
+ case HFS_NODE_LEAF:
+ if (node->height != 1)
+ goto node_error;
+ break;
+ case HFS_NODE_INDEX:
+ if (node->height <= 1 || node->height > tree->depth)
+ goto node_error;
+ break;
+ default:
+ goto node_error;
+ }
+
+ rec_off = tree->node_size - 2;
+ off = hfs_bnode_read_u16(node, rec_off);
+ if (off != sizeof(struct hfs_bnode_desc))
+ goto node_error;
+ for (i = 1; i <= node->num_recs; off = next_off, i++) {
+ rec_off -= 2;
+ next_off = hfs_bnode_read_u16(node, rec_off);
+ if (hfs_brec_offsets_invalid(node, off, next_off))
+ goto node_error;
+ entry_size = next_off - off;
+ if (node->type != HFS_NODE_INDEX &&
+ node->type != HFS_NODE_LEAF)
+ continue;
+ key_size = hfs_bnode_read_u16(node, off) + 2;
+ if (key_size >= entry_size || key_size & 1)
+ goto node_error;
+ }
+ clear_bit(HFS_BNODE_NEW, &node->flags);
+ wake_up(&node->lock_wq);
+ return node;
+
+node_error:
+ set_bit(HFS_BNODE_ERROR, &node->flags);
+ clear_bit(HFS_BNODE_NEW, &node->flags);
+ wake_up(&node->lock_wq);
+ hfs_bnode_put(node);
+ return ERR_PTR(-EIO);
+}
+
+void hfs_bnode_free(struct hfs_bnode *node)
+{
+ int i;
+
+ for (i = 0; i < node->tree->pages_per_bnode; i++)
+ if (node->page[i])
+ put_page(node->page[i]);
+ kfree(node);
+}
+
+struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num)
+{
+ struct hfs_bnode *node;
+ struct page **pagep;
+ int i;
+
+ spin_lock(&tree->hash_lock);
+ node = hfs_bnode_findhash(tree, num);
+ spin_unlock(&tree->hash_lock);
+ if (node) {
+ pr_crit("new node %u already hashed?\n", num);
+ WARN_ON(1);
+ return ERR_PTR(-EEXIST);
+ }
+ node = __hfs_bnode_create(tree, num);
+ if (!node)
+ return ERR_PTR(-ENOMEM);
+ if (test_bit(HFS_BNODE_ERROR, &node->flags)) {
+ hfs_bnode_put(node);
+ return ERR_PTR(-EIO);
+ }
+
+ pagep = node->page;
+ memzero_page(*pagep, node->page_offset,
+ min_t(int, PAGE_SIZE, tree->node_size));
+ set_page_dirty(*pagep);
+ for (i = 1; i < tree->pages_per_bnode; i++) {
+ memzero_page(*++pagep, 0, PAGE_SIZE);
+ set_page_dirty(*pagep);
+ }
+ clear_bit(HFS_BNODE_NEW, &node->flags);
+ wake_up(&node->lock_wq);
+
+ return node;
+}
+
+void hfs_bnode_get(struct hfs_bnode *node)
+{
+ if (node) {
+ atomic_inc(&node->refcnt);
+ hfs_dbg("cnid %d, node %d, refcnt %d\n",
+ node->tree->cnid, node->this,
+ atomic_read(&node->refcnt));
+ }
+}
+
+/* Dispose of resources used by a node */
+void hfs_bnode_put(struct hfs_bnode *node)
+{
+ if (node) {
+ struct hfs_btree *tree = node->tree;
+ int i;
+
+ hfs_dbg("cnid %d, node %d, refcnt %d\n",
+ node->tree->cnid, node->this,
+ atomic_read(&node->refcnt));
+ BUG_ON(!atomic_read(&node->refcnt));
+ if (!atomic_dec_and_lock(&node->refcnt, &tree->hash_lock))
+ return;
+ for (i = 0; i < tree->pages_per_bnode; i++) {
+ if (!node->page[i])
+ continue;
+ mark_page_accessed(node->page[i]);
+ }
+
+ if (test_bit(HFS_BNODE_DELETED, &node->flags)) {
+ hfs_bnode_unhash(node);
+ spin_unlock(&tree->hash_lock);
+ if (hfs_bnode_need_zeroout(tree))
+ hfs_bnode_clear(node, 0, tree->node_size);
+ hfs_bmap_free(node);
+ hfs_bnode_free(node);
+ return;
+ }
+ spin_unlock(&tree->hash_lock);
+ }
+}
+
+/*
+ * Unused nodes have to be zeroed if this is the catalog tree and
+ * a corresponding flag in the volume header is set.
+ */
+bool hfs_bnode_need_zeroout(struct hfs_btree *tree)
+{
+ struct super_block *sb = tree->inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ const u32 volume_attr = be32_to_cpu(sbi->s_vhdr->attributes);
+
+ return volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX;
+}
diff --git a/fs/hfsplus/brec.c b/fs/hfsplus/brec.c
new file mode 100644
index 000000000..f416e562d
--- /dev/null
+++ b/fs/hfsplus/brec.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/brec.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle individual btree records
+ */
+
+#include <linux/limits.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd);
+static int hfs_brec_update_parent(struct hfs_find_data *fd);
+static int hfs_btree_inc_height(struct hfs_btree *);
+
+/* Get the length and offset of the given record in the given node */
+u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off)
+{
+ __be16 retval[2];
+ u16 data_off;
+ u16 next_off;
+
+ if (hfs_brec_record_invalid(node, rec)) {
+ *off = U16_MAX;
+ return U16_MAX;
+ }
+
+ data_off = node->tree->node_size - (rec + 2) * 2;
+ hfs_bnode_read(node, retval, data_off, 4);
+ *off = be16_to_cpu(retval[1]);
+ next_off = be16_to_cpu(retval[0]);
+ if (hfs_brec_offsets_invalid(node, *off, next_off)) {
+ *off = U16_MAX;
+ return U16_MAX;
+ }
+ return next_off - *off;
+}
+
+/* Get the length of the key from a keyed record */
+u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)
+{
+ u16 retval, recoff, len;
+
+ if (node->type != HFS_NODE_INDEX && node->type != HFS_NODE_LEAF)
+ return 0;
+ if (hfs_brec_record_invalid(node, rec))
+ return U16_MAX;
+
+ if ((node->type == HFS_NODE_INDEX) &&
+ !(node->tree->attributes & HFS_TREE_VARIDXKEYS) &&
+ (node->tree->cnid != HFSPLUS_ATTR_CNID)) {
+ retval = node->tree->max_key_len + 2;
+ } else {
+ len = hfs_brec_lenoff(node, rec, &recoff);
+ if (hfs_brec_len_invalid(node, len))
+ return len;
+
+ retval = hfs_bnode_read_u16(node, recoff) + 2;
+ if (retval > node->tree->max_key_len + 2) {
+ pr_err("keylen %d too large\n",
+ retval);
+ retval = U16_MAX;
+ }
+ }
+ return retval;
+}
+
+int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *new_node;
+ int size, key_len, rec;
+ int data_off, end_off;
+ int idx_rec_off, data_rec_off, end_rec_off;
+ __be32 cnid;
+
+ tree = fd->tree;
+ if (!fd->bnode) {
+ if (!tree->root)
+ hfs_btree_inc_height(tree);
+ node = hfs_bnode_find(tree, tree->leaf_head);
+ if (IS_ERR(node))
+ return PTR_ERR(node);
+ fd->bnode = node;
+ fd->record = -1;
+ }
+ new_node = NULL;
+ key_len = be16_to_cpu(fd->search_key->key_len) + 2;
+again:
+ /* new record idx and complete record size */
+ rec = fd->record + 1;
+ size = key_len + entry_len;
+
+ node = fd->bnode;
+ hfs_bnode_dump(node);
+ /* get last offset */
+ end_rec_off = tree->node_size - (node->num_recs + 1) * 2;
+ end_off = hfs_bnode_read_u16(node, end_rec_off);
+ end_rec_off -= 2;
+ hfs_dbg("rec %d, size %d, end_off %d, end_rec_off %d\n",
+ rec, size, end_off, end_rec_off);
+ if (size > end_rec_off - end_off) {
+ if (new_node)
+ panic("not enough room!\n");
+ new_node = hfs_bnode_split(fd);
+ if (IS_ERR(new_node))
+ return PTR_ERR(new_node);
+ goto again;
+ }
+ if (node->type == HFS_NODE_LEAF) {
+ tree->leaf_count++;
+ mark_inode_dirty(tree->inode);
+ }
+ node->num_recs++;
+ /* write new last offset */
+ hfs_bnode_write_u16(node,
+ offsetof(struct hfs_bnode_desc, num_recs),
+ node->num_recs);
+ hfs_bnode_write_u16(node, end_rec_off, end_off + size);
+ data_off = end_off;
+ data_rec_off = end_rec_off + 2;
+ idx_rec_off = tree->node_size - (rec + 1) * 2;
+ if (idx_rec_off == data_rec_off)
+ goto skip;
+ /* move all following entries */
+ do {
+ data_off = hfs_bnode_read_u16(node, data_rec_off + 2);
+ hfs_bnode_write_u16(node, data_rec_off, data_off + size);
+ data_rec_off += 2;
+ } while (data_rec_off < idx_rec_off);
+
+ /* move data away */
+ hfs_bnode_move(node, data_off + size, data_off,
+ end_off - data_off);
+
+skip:
+ hfs_bnode_write(node, fd->search_key, data_off, key_len);
+ hfs_bnode_write(node, entry, data_off + key_len, entry_len);
+ hfs_bnode_dump(node);
+
+ /*
+ * update parent key if we inserted a key
+ * at the start of the node and it is not the new node
+ */
+ if (!rec && new_node != node) {
+ hfs_bnode_read_key(node, fd->search_key, data_off + size);
+ hfs_brec_update_parent(fd);
+ }
+
+ if (new_node) {
+ hfs_bnode_put(fd->bnode);
+ if (!new_node->parent) {
+ hfs_btree_inc_height(tree);
+ new_node->parent = tree->root;
+ }
+ fd->bnode = hfs_bnode_find(tree, new_node->parent);
+
+ /* create index data entry */
+ cnid = cpu_to_be32(new_node->this);
+ entry = &cnid;
+ entry_len = sizeof(cnid);
+
+ /* get index key */
+ hfs_bnode_read_key(new_node, fd->search_key, 14);
+ __hfs_brec_find(fd->bnode, fd, hfs_find_rec_by_key);
+
+ hfs_bnode_put(new_node);
+ new_node = NULL;
+
+ if ((tree->attributes & HFS_TREE_VARIDXKEYS) ||
+ (tree->cnid == HFSPLUS_ATTR_CNID))
+ key_len = be16_to_cpu(fd->search_key->key_len) + 2;
+ else {
+ fd->search_key->key_len =
+ cpu_to_be16(tree->max_key_len);
+ key_len = tree->max_key_len + 2;
+ }
+ goto again;
+ }
+
+ return 0;
+}
+
+int hfs_brec_remove(struct hfs_find_data *fd)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *parent;
+ int end_off, rec_off, data_off, size;
+ int res;
+
+ tree = fd->tree;
+ node = fd->bnode;
+again:
+ if (hfs_brec_record_invalid(node, fd->record))
+ return -EINVAL;
+
+ rec_off = tree->node_size - (fd->record + 2) * 2;
+ end_off = tree->node_size - (node->num_recs + 1) * 2;
+
+ if (node->type == HFS_NODE_LEAF) {
+ if (tree->leaf_count == 0)
+ return -EINVAL;
+ tree->leaf_count--;
+ mark_inode_dirty(tree->inode);
+ }
+ hfs_bnode_dump(node);
+ hfs_dbg("rec %d, len %d\n",
+ fd->record, fd->keylength + fd->entrylength);
+ if (!--node->num_recs) {
+ hfs_bnode_unlink(node);
+ if (!node->parent)
+ return 0;
+ parent = hfs_bnode_find(tree, node->parent);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+ hfs_bnode_put(node);
+ node = fd->bnode = parent;
+
+ res = __hfs_brec_find(node, fd, hfs_find_rec_by_key);
+ if (res && res != -ENOENT)
+ return res;
+ goto again;
+ }
+ hfs_bnode_write_u16(node,
+ offsetof(struct hfs_bnode_desc, num_recs),
+ node->num_recs);
+
+ if (rec_off == end_off)
+ goto skip;
+ size = fd->keylength + fd->entrylength;
+
+ do {
+ data_off = hfs_bnode_read_u16(node, rec_off);
+ hfs_bnode_write_u16(node, rec_off + 2, data_off - size);
+ rec_off -= 2;
+ } while (rec_off >= end_off);
+
+ /* fill hole */
+ hfs_bnode_move(node, fd->keyoffset, fd->keyoffset + size,
+ data_off - fd->keyoffset - size);
+skip:
+ hfs_bnode_dump(node);
+ if (!fd->record)
+ hfs_brec_update_parent(fd);
+ return 0;
+}
+
+static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *new_node, *next_node;
+ struct hfs_bnode_desc node_desc;
+ int num_recs, new_rec_off, new_off, old_rec_off;
+ int data_start, data_end, size;
+ size_t rec_off_tbl_size;
+ size_t node_desc_size = sizeof(struct hfs_bnode_desc);
+ size_t rec_size = sizeof(__be16);
+
+ tree = fd->tree;
+ node = fd->bnode;
+ new_node = hfs_bmap_alloc(tree);
+ if (IS_ERR(new_node))
+ return new_node;
+ hfs_bnode_get(node);
+ hfs_dbg("this %d - new %d - next %d\n",
+ node->this, new_node->this, node->next);
+ new_node->next = node->next;
+ new_node->prev = node->this;
+ new_node->parent = node->parent;
+ new_node->type = node->type;
+ new_node->height = node->height;
+
+ if (node->next)
+ next_node = hfs_bnode_find(tree, node->next);
+ else
+ next_node = NULL;
+
+ if (IS_ERR(next_node)) {
+ hfs_bnode_put(node);
+ hfs_bnode_put(new_node);
+ return next_node;
+ }
+
+ rec_off_tbl_size = node->num_recs * rec_size;
+ size = tree->node_size / 2;
+ size -= node_desc_size;
+ size -= rec_off_tbl_size;
+ old_rec_off = tree->node_size - (2 * rec_size);
+
+ num_recs = 1;
+ for (;;) {
+ data_start = hfs_bnode_read_u16(node, old_rec_off);
+ if (data_start > size)
+ break;
+ old_rec_off -= rec_size;
+ if (++num_recs < node->num_recs)
+ continue;
+ hfs_bnode_put(node);
+ hfs_bnode_unlink(new_node);
+ hfs_bnode_put(new_node);
+ if (next_node)
+ hfs_bnode_put(next_node);
+ return ERR_PTR(-ENOSPC);
+ }
+
+ if (fd->record + 1 < num_recs) {
+ /* new record is in the lower half,
+ * so leave some more space there
+ */
+ old_rec_off += rec_size;
+ num_recs--;
+ data_start = hfs_bnode_read_u16(node, old_rec_off);
+ } else {
+ hfs_bnode_put(node);
+ hfs_bnode_get(new_node);
+ fd->bnode = new_node;
+ fd->record -= num_recs;
+ fd->keyoffset -= data_start - node_desc_size;
+ fd->entryoffset -= data_start - node_desc_size;
+ }
+ new_node->num_recs = node->num_recs - num_recs;
+ node->num_recs = num_recs;
+
+ new_rec_off = tree->node_size - rec_size;
+ new_off = node_desc_size;
+ size = data_start - new_off;
+ num_recs = new_node->num_recs;
+ data_end = data_start;
+ while (num_recs) {
+ hfs_bnode_write_u16(new_node, new_rec_off, new_off);
+ old_rec_off -= rec_size;
+ new_rec_off -= rec_size;
+ data_end = hfs_bnode_read_u16(node, old_rec_off);
+ new_off = data_end - size;
+ num_recs--;
+ }
+ hfs_bnode_write_u16(new_node, new_rec_off, new_off);
+ hfs_bnode_copy(new_node, node_desc_size,
+ node, data_start, data_end - data_start);
+
+ /* update new bnode header */
+ node_desc.next = cpu_to_be32(new_node->next);
+ node_desc.prev = cpu_to_be32(new_node->prev);
+ node_desc.type = new_node->type;
+ node_desc.height = new_node->height;
+ node_desc.num_recs = cpu_to_be16(new_node->num_recs);
+ node_desc.reserved = 0;
+ hfs_bnode_write(new_node, &node_desc, 0, sizeof(node_desc));
+
+ /* update previous bnode header */
+ node->next = new_node->this;
+ hfs_bnode_read(node, &node_desc, 0, sizeof(node_desc));
+ node_desc.next = cpu_to_be32(node->next);
+ node_desc.num_recs = cpu_to_be16(node->num_recs);
+ hfs_bnode_write(node, &node_desc, 0, sizeof(node_desc));
+
+ /* update next bnode header */
+ if (next_node) {
+ next_node->prev = new_node->this;
+ hfs_bnode_read(next_node, &node_desc, 0, sizeof(node_desc));
+ node_desc.prev = cpu_to_be32(next_node->prev);
+ hfs_bnode_write(next_node, &node_desc, 0, sizeof(node_desc));
+ hfs_bnode_put(next_node);
+ } else if (node->this == tree->leaf_tail) {
+ /* if there is no next node, this might be the new tail */
+ tree->leaf_tail = new_node->this;
+ mark_inode_dirty(tree->inode);
+ }
+
+ hfs_bnode_dump(node);
+ hfs_bnode_dump(new_node);
+ hfs_bnode_put(node);
+
+ return new_node;
+}
+
+static int hfs_brec_update_parent(struct hfs_find_data *fd)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *node, *new_node, *parent;
+ int newkeylen, diff;
+ int rec, rec_off, end_rec_off;
+ int start_off, end_off;
+ int res;
+
+ tree = fd->tree;
+ node = fd->bnode;
+ new_node = NULL;
+ if (!node->parent)
+ return 0;
+
+again:
+ parent = hfs_bnode_find(tree, node->parent);
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+ res = __hfs_brec_find(parent, fd, hfs_find_rec_by_key);
+ if (res && res != -ENOENT)
+ return res;
+ if (fd->record < 0)
+ return -ENOENT;
+ hfs_bnode_dump(parent);
+ rec = fd->record;
+
+ /* size difference between old and new key */
+ if ((tree->attributes & HFS_TREE_VARIDXKEYS) ||
+ (tree->cnid == HFSPLUS_ATTR_CNID))
+ newkeylen = hfs_bnode_read_u16(node, 14) + 2;
+ else
+ fd->keylength = newkeylen = tree->max_key_len + 2;
+ hfs_dbg("rec %d, keylength %d, newkeylen %d\n",
+ rec, fd->keylength, newkeylen);
+
+ rec_off = tree->node_size - (rec + 2) * 2;
+ end_rec_off = tree->node_size - (parent->num_recs + 1) * 2;
+ diff = newkeylen - fd->keylength;
+ if (!diff)
+ goto skip;
+ if (diff > 0) {
+ end_off = hfs_bnode_read_u16(parent, end_rec_off);
+ if (end_rec_off - end_off < diff) {
+
+ hfs_dbg("splitting index node\n");
+ fd->bnode = parent;
+ new_node = hfs_bnode_split(fd);
+ if (IS_ERR(new_node))
+ return PTR_ERR(new_node);
+ parent = fd->bnode;
+ rec = fd->record;
+ rec_off = tree->node_size - (rec + 2) * 2;
+ end_rec_off = tree->node_size -
+ (parent->num_recs + 1) * 2;
+ }
+ }
+
+ end_off = start_off = hfs_bnode_read_u16(parent, rec_off);
+ hfs_bnode_write_u16(parent, rec_off, start_off + diff);
+ start_off -= 4; /* move previous cnid too */
+
+ while (rec_off > end_rec_off) {
+ rec_off -= 2;
+ end_off = hfs_bnode_read_u16(parent, rec_off);
+ hfs_bnode_write_u16(parent, rec_off, end_off + diff);
+ }
+ hfs_bnode_move(parent, start_off + diff, start_off,
+ end_off - start_off);
+skip:
+ hfs_bnode_copy(parent, fd->keyoffset, node, 14, newkeylen);
+ hfs_bnode_dump(parent);
+
+ hfs_bnode_put(node);
+ node = parent;
+
+ if (new_node) {
+ __be32 cnid;
+
+ if (!new_node->parent) {
+ hfs_btree_inc_height(tree);
+ new_node->parent = tree->root;
+ }
+ fd->bnode = hfs_bnode_find(tree, new_node->parent);
+ /* create index key and entry */
+ hfs_bnode_read_key(new_node, fd->search_key, 14);
+ cnid = cpu_to_be32(new_node->this);
+
+ __hfs_brec_find(fd->bnode, fd, hfs_find_rec_by_key);
+ hfs_brec_insert(fd, &cnid, sizeof(cnid));
+ hfs_bnode_put(fd->bnode);
+ hfs_bnode_put(new_node);
+
+ if (!rec) {
+ if (new_node == node)
+ goto out;
+ /* restore search_key */
+ hfs_bnode_read_key(node, fd->search_key, 14);
+ }
+ new_node = NULL;
+ }
+
+ if (!rec && node->parent)
+ goto again;
+out:
+ fd->bnode = node;
+ return 0;
+}
+
+static int hfs_btree_inc_height(struct hfs_btree *tree)
+{
+ struct hfs_bnode *node, *new_node;
+ struct hfs_bnode_desc node_desc;
+ int key_size, rec;
+ __be32 cnid;
+
+ node = NULL;
+ if (tree->root) {
+ node = hfs_bnode_find(tree, tree->root);
+ if (IS_ERR(node))
+ return PTR_ERR(node);
+ }
+ new_node = hfs_bmap_alloc(tree);
+ if (IS_ERR(new_node)) {
+ hfs_bnode_put(node);
+ return PTR_ERR(new_node);
+ }
+
+ tree->root = new_node->this;
+ if (!tree->depth) {
+ tree->leaf_head = tree->leaf_tail = new_node->this;
+ new_node->type = HFS_NODE_LEAF;
+ new_node->num_recs = 0;
+ } else {
+ new_node->type = HFS_NODE_INDEX;
+ new_node->num_recs = 1;
+ }
+ new_node->parent = 0;
+ new_node->next = 0;
+ new_node->prev = 0;
+ new_node->height = ++tree->depth;
+
+ node_desc.next = cpu_to_be32(new_node->next);
+ node_desc.prev = cpu_to_be32(new_node->prev);
+ node_desc.type = new_node->type;
+ node_desc.height = new_node->height;
+ node_desc.num_recs = cpu_to_be16(new_node->num_recs);
+ node_desc.reserved = 0;
+ hfs_bnode_write(new_node, &node_desc, 0, sizeof(node_desc));
+
+ rec = tree->node_size - 2;
+ hfs_bnode_write_u16(new_node, rec, 14);
+
+ if (node) {
+ /* insert old root idx into new root */
+ node->parent = tree->root;
+ if (node->type == HFS_NODE_LEAF ||
+ tree->attributes & HFS_TREE_VARIDXKEYS ||
+ tree->cnid == HFSPLUS_ATTR_CNID)
+ key_size = hfs_bnode_read_u16(node, 14) + 2;
+ else
+ key_size = tree->max_key_len + 2;
+ hfs_bnode_copy(new_node, 14, node, 14, key_size);
+
+ if (!(tree->attributes & HFS_TREE_VARIDXKEYS) &&
+ (tree->cnid != HFSPLUS_ATTR_CNID)) {
+ key_size = tree->max_key_len + 2;
+ hfs_bnode_write_u16(new_node, 14, tree->max_key_len);
+ }
+ cnid = cpu_to_be32(node->this);
+ hfs_bnode_write(new_node, &cnid, 14 + key_size, 4);
+
+ rec -= 2;
+ hfs_bnode_write_u16(new_node, rec, 14 + key_size + 4);
+
+ hfs_bnode_put(node);
+ }
+ hfs_bnode_put(new_node);
+ mark_inode_dirty(tree->inode);
+
+ return 0;
+}
diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c
new file mode 100644
index 000000000..2ea8cd565
--- /dev/null
+++ b/fs/hfsplus/btree.c
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/btree.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle opening/closing btree
+ */
+
+#include <linux/slab.h>
+#include <linux/pagemap.h>
+#include <linux/log2.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+/*
+ * Initial source code of clump size calculation is gotten
+ * from http://opensource.apple.com/tarballs/diskdev_cmds/
+ */
+#define CLUMP_ENTRIES 15
+
+static short clumptbl[CLUMP_ENTRIES * 3] = {
+/*
+ * Volume Attributes Catalog Extents
+ * Size Clump (MB) Clump (MB) Clump (MB)
+ */
+ /* 1GB */ 4, 4, 4,
+ /* 2GB */ 6, 6, 4,
+ /* 4GB */ 8, 8, 4,
+ /* 8GB */ 11, 11, 5,
+ /*
+ * For volumes 16GB and larger, we want to make sure that a full OS
+ * install won't require fragmentation of the Catalog or Attributes
+ * B-trees. We do this by making the clump sizes sufficiently large,
+ * and by leaving a gap after the B-trees for them to grow into.
+ *
+ * For SnowLeopard 10A298, a FullNetInstall with all packages selected
+ * results in:
+ * Catalog B-tree Header
+ * nodeSize: 8192
+ * totalNodes: 31616
+ * freeNodes: 1978
+ * (used = 231.55 MB)
+ * Attributes B-tree Header
+ * nodeSize: 8192
+ * totalNodes: 63232
+ * freeNodes: 958
+ * (used = 486.52 MB)
+ *
+ * We also want Time Machine backup volumes to have a sufficiently
+ * large clump size to reduce fragmentation.
+ *
+ * The series of numbers for Catalog and Attribute form a geometric
+ * series. For Catalog (16GB to 512GB), each term is 8**(1/5) times
+ * the previous term. For Attributes (16GB to 512GB), each term is
+ * 4**(1/5) times the previous term. For 1TB to 16TB, each term is
+ * 2**(1/5) times the previous term.
+ */
+ /* 16GB */ 64, 32, 5,
+ /* 32GB */ 84, 49, 6,
+ /* 64GB */ 111, 74, 7,
+ /* 128GB */ 147, 111, 8,
+ /* 256GB */ 194, 169, 9,
+ /* 512GB */ 256, 256, 11,
+ /* 1TB */ 294, 294, 14,
+ /* 2TB */ 338, 338, 16,
+ /* 4TB */ 388, 388, 20,
+ /* 8TB */ 446, 446, 25,
+ /* 16TB */ 512, 512, 32
+};
+
+u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size,
+ u64 sectors, int file_id)
+{
+ u32 mod = max(node_size, block_size);
+ u32 clump_size;
+ int column;
+ int i;
+
+ /* Figure out which column of the above table to use for this file. */
+ switch (file_id) {
+ case HFSPLUS_ATTR_CNID:
+ column = 0;
+ break;
+ case HFSPLUS_CAT_CNID:
+ column = 1;
+ break;
+ default:
+ column = 2;
+ break;
+ }
+
+ /*
+ * The default clump size is 0.8% of the volume size. And
+ * it must also be a multiple of the node and block size.
+ */
+ if (sectors < 0x200000) {
+ clump_size = sectors << 2; /* 0.8 % */
+ if (clump_size < (8 * node_size))
+ clump_size = 8 * node_size;
+ } else {
+ /* turn exponent into table index... */
+ for (i = 0, sectors = sectors >> 22;
+ sectors && (i < CLUMP_ENTRIES - 1);
+ ++i, sectors = sectors >> 1) {
+ /* empty body */
+ }
+
+ clump_size = clumptbl[column + (i) * 3] * 1024 * 1024;
+ }
+
+ /*
+ * Round the clump size to a multiple of node and block size.
+ * NOTE: This rounds down.
+ */
+ clump_size /= mod;
+ clump_size *= mod;
+
+ /*
+ * Rounding down could have rounded down to 0 if the block size was
+ * greater than the clump size. If so, just use one block or node.
+ */
+ if (clump_size == 0)
+ clump_size = mod;
+
+ return clump_size;
+}
+
+/* Context for iterating b-tree map pages
+ * @page_idx: The index of the page within the b-node's page array
+ * @off: The byte offset within the mapped page
+ * @len: The remaining length of the map record
+ */
+struct hfs_bmap_ctx {
+ unsigned int page_idx;
+ unsigned int off;
+ u16 len;
+};
+
+/*
+ * Finds the specific page containing the requested byte offset within the map
+ * record. Automatically handles the difference between header and map nodes.
+ * Returns the struct page pointer, or an ERR_PTR on failure.
+ * Note: The caller is responsible for mapping/unmapping the returned page.
+ */
+static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,
+ struct hfs_bmap_ctx *ctx,
+ u32 byte_offset)
+{
+ u16 rec_idx, off16;
+ unsigned int page_off;
+
+ if (node->this == HFSPLUS_TREE_HEAD) {
+ if (node->type != HFS_NODE_HEADER) {
+ pr_err("hfsplus: invalid btree header node\n");
+ return ERR_PTR(-EIO);
+ }
+ rec_idx = HFSPLUS_BTREE_HDR_MAP_REC_INDEX;
+ } else {
+ if (node->type != HFS_NODE_MAP) {
+ pr_err("hfsplus: invalid btree map node\n");
+ return ERR_PTR(-EIO);
+ }
+ rec_idx = HFSPLUS_BTREE_MAP_NODE_REC_INDEX;
+ }
+
+ ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
+ if (hfs_brec_len_invalid(node, ctx->len))
+ return ERR_PTR(-EINVAL);
+
+ if (!is_bnode_offset_valid(node, off16))
+ return ERR_PTR(-EIO);
+
+ ctx->len = check_and_correct_requested_length(node, off16, ctx->len);
+
+ if (byte_offset >= ctx->len)
+ return ERR_PTR(-EINVAL);
+
+ page_off = (u32)off16 + node->page_offset + byte_offset;
+ ctx->page_idx = page_off >> PAGE_SHIFT;
+ ctx->off = page_off & ~PAGE_MASK;
+
+ return node->page[ctx->page_idx];
+}
+
+/**
+ * hfs_bmap_test_bit - test a bit in the b-tree map
+ * @node: the b-tree node containing the map record
+ * @node_bit_idx: the relative bit index within the node's map record
+ *
+ * Returns true if set, false if clear or on failure.
+ */
+static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx)
+{
+ struct hfs_bmap_ctx ctx;
+ struct page *page;
+ u8 *bmap, byte, mask;
+
+ page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
+ if (IS_ERR(page))
+ return false;
+
+ bmap = kmap_local_page(page);
+ byte = bmap[ctx.off];
+ kunmap_local(bmap);
+
+ mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
+ return (byte & mask) != 0;
+}
+
+
+/**
+ * hfs_bmap_clear_bit - clear a bit in the b-tree map
+ * @node: the b-tree node containing the map record
+ * @node_bit_idx: the relative bit index within the node's map record
+ *
+ * Returns 0 on success, -EINVAL if already clear, or negative error code.
+ */
+static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx)
+{
+ struct hfs_bmap_ctx ctx;
+ struct page *page;
+ u8 *bmap, mask;
+
+ page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ bmap = kmap_local_page(page);
+
+ mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
+
+ if (!(bmap[ctx.off] & mask)) {
+ kunmap_local(bmap);
+ return -EINVAL;
+ }
+
+ bmap[ctx.off] &= ~mask;
+ set_page_dirty(page);
+ kunmap_local(bmap);
+
+ return 0;
+}
+
+#define HFS_EXTENT_TREE_NAME "Extents Overflow File"
+#define HFS_CATALOG_TREE_NAME "Catalog File"
+#define HFS_ATTR_TREE_NAME "Attributes File"
+#define HFS_UNKNOWN_TREE_NAME "Unknown B-tree"
+
+static const char *hfs_btree_name(u32 cnid)
+{
+ switch (cnid) {
+ case HFSPLUS_EXT_CNID:
+ return HFS_EXTENT_TREE_NAME;
+ case HFSPLUS_CAT_CNID:
+ return HFS_CATALOG_TREE_NAME;
+ case HFSPLUS_ATTR_CNID:
+ return HFS_ATTR_TREE_NAME;
+ default:
+ return HFS_UNKNOWN_TREE_NAME;
+ }
+}
+
+/* Get a reference to a B*Tree and do some initial checks */
+struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id)
+{
+ struct hfs_btree *tree;
+ struct hfs_btree_header_rec *head;
+ struct address_space *mapping;
+ struct hfs_bnode *node;
+ struct inode *inode;
+ struct page *page;
+ unsigned int size;
+
+ tree = kzalloc_obj(*tree);
+ if (!tree)
+ return NULL;
+
+ mutex_init(&tree->tree_lock);
+ spin_lock_init(&tree->hash_lock);
+ tree->sb = sb;
+ tree->cnid = id;
+ inode = hfsplus_iget(sb, id);
+ if (IS_ERR(inode))
+ goto free_tree;
+ tree->inode = inode;
+
+ if (!HFSPLUS_I(tree->inode)->first_blocks) {
+ pr_err("invalid btree extent records (0 size)\n");
+ goto free_inode;
+ }
+
+ mapping = tree->inode->i_mapping;
+ page = read_mapping_page(mapping, 0, NULL);
+ if (IS_ERR(page))
+ goto free_inode;
+
+ /* Load the header */
+ head = (struct hfs_btree_header_rec *)(kmap_local_page(page) +
+ sizeof(struct hfs_bnode_desc));
+ tree->root = be32_to_cpu(head->root);
+ tree->leaf_count = be32_to_cpu(head->leaf_count);
+ tree->leaf_head = be32_to_cpu(head->leaf_head);
+ tree->leaf_tail = be32_to_cpu(head->leaf_tail);
+ tree->node_count = be32_to_cpu(head->node_count);
+ tree->free_nodes = be32_to_cpu(head->free_nodes);
+ tree->attributes = be32_to_cpu(head->attributes);
+ tree->node_size = be16_to_cpu(head->node_size);
+ tree->max_key_len = be16_to_cpu(head->max_key_len);
+ tree->depth = be16_to_cpu(head->depth);
+
+ /* Verify the tree and set the correct compare function */
+ switch (id) {
+ case HFSPLUS_EXT_CNID:
+ if (tree->max_key_len != HFSPLUS_EXT_KEYLEN - sizeof(u16)) {
+ pr_err("invalid extent max_key_len %d\n",
+ tree->max_key_len);
+ goto fail_page;
+ }
+ if (tree->attributes & HFS_TREE_VARIDXKEYS) {
+ pr_err("invalid extent btree flag\n");
+ goto fail_page;
+ }
+
+ tree->keycmp = hfsplus_ext_cmp_key;
+ break;
+ case HFSPLUS_CAT_CNID:
+ if (tree->max_key_len != HFSPLUS_CAT_KEYLEN - sizeof(u16)) {
+ pr_err("invalid catalog max_key_len %d\n",
+ tree->max_key_len);
+ goto fail_page;
+ }
+ if (!(tree->attributes & HFS_TREE_VARIDXKEYS)) {
+ pr_err("invalid catalog btree flag\n");
+ goto fail_page;
+ }
+
+ if (test_bit(HFSPLUS_SB_HFSX, &HFSPLUS_SB(sb)->flags) &&
+ (head->key_type == HFSPLUS_KEY_BINARY))
+ tree->keycmp = hfsplus_cat_bin_cmp_key;
+ else {
+ tree->keycmp = hfsplus_cat_case_cmp_key;
+ set_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
+ }
+ break;
+ case HFSPLUS_ATTR_CNID:
+ if (tree->max_key_len != HFSPLUS_ATTR_KEYLEN - sizeof(u16)) {
+ pr_err("invalid attributes max_key_len %d\n",
+ tree->max_key_len);
+ goto fail_page;
+ }
+ tree->keycmp = hfsplus_attr_bin_cmp_key;
+ break;
+ default:
+ pr_err("unknown B*Tree requested\n");
+ goto fail_page;
+ }
+
+ if (!(tree->attributes & HFS_TREE_BIGKEYS)) {
+ pr_err("invalid btree flag\n");
+ goto fail_page;
+ }
+
+ size = tree->node_size;
+ if (size < HFSPLUS_NODE_MINSZ || size > HFSPLUS_NODE_MXSZ)
+ goto fail_page;
+ if (!is_power_of_2(size))
+ goto fail_page;
+ if (!tree->node_count)
+ goto fail_page;
+
+ tree->node_size_shift = ffs(size) - 1;
+
+ tree->pages_per_bnode =
+ (tree->node_size + PAGE_SIZE - 1) >>
+ PAGE_SHIFT;
+
+ kunmap_local(head);
+ put_page(page);
+
+ node = hfs_bnode_find(tree, HFSPLUS_TREE_HEAD);
+ if (IS_ERR(node))
+ goto free_inode;
+
+ if (!hfs_bmap_test_bit(node, 0)) {
+ pr_warn("(%s): %s (cnid 0x%x) map record invalid or bitmap corruption detected, forcing read-only.\n",
+ sb->s_id, hfs_btree_name(id), id);
+ pr_warn("Run fsck.hfsplus to repair.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+
+ hfs_bnode_put(node);
+
+ return tree;
+
+ fail_page:
+ kunmap_local(head);
+ put_page(page);
+ free_inode:
+ tree->inode->i_mapping->a_ops = &hfsplus_aops;
+ iput(tree->inode);
+ free_tree:
+ kfree(tree);
+ return NULL;
+}
+
+/* Release resources used by a btree */
+void hfs_btree_close(struct hfs_btree *tree)
+{
+ struct hfs_bnode *node;
+ int i;
+
+ if (!tree)
+ return;
+
+ for (i = 0; i < NODE_HASH_SIZE; i++) {
+ while ((node = tree->node_hash[i])) {
+ tree->node_hash[i] = node->next_hash;
+ if (atomic_read(&node->refcnt))
+ pr_crit("node %d:%d "
+ "still has %d user(s)!\n",
+ node->tree->cnid, node->this,
+ atomic_read(&node->refcnt));
+ hfs_bnode_free(node);
+ tree->node_hash_cnt--;
+ }
+ }
+ iput(tree->inode);
+ kfree(tree);
+}
+
+int hfs_btree_write(struct hfs_btree *tree)
+{
+ struct hfs_btree_header_rec *head;
+ struct hfs_bnode *node;
+ struct page *page;
+
+ node = hfs_bnode_find(tree, 0);
+ if (IS_ERR(node))
+ /* panic? */
+ return -EIO;
+ /* Load the header */
+ page = node->page[0];
+ head = (struct hfs_btree_header_rec *)(kmap_local_page(page) +
+ sizeof(struct hfs_bnode_desc));
+
+ head->root = cpu_to_be32(tree->root);
+ head->leaf_count = cpu_to_be32(tree->leaf_count);
+ head->leaf_head = cpu_to_be32(tree->leaf_head);
+ head->leaf_tail = cpu_to_be32(tree->leaf_tail);
+ head->node_count = cpu_to_be32(tree->node_count);
+ head->free_nodes = cpu_to_be32(tree->free_nodes);
+ head->attributes = cpu_to_be32(tree->attributes);
+ head->depth = cpu_to_be16(tree->depth);
+
+ kunmap_local(head);
+ set_page_dirty(page);
+ hfs_bnode_put(node);
+ return 0;
+}
+
+static struct hfs_bnode *hfs_bmap_new_bmap(struct hfs_bnode *prev, u32 idx)
+{
+ struct hfs_btree *tree = prev->tree;
+ struct hfs_bnode *node;
+ struct hfs_bnode_desc desc;
+ __be32 cnid;
+
+ node = hfs_bnode_create(tree, idx);
+ if (IS_ERR(node))
+ return node;
+
+ tree->free_nodes--;
+ prev->next = idx;
+ cnid = cpu_to_be32(idx);
+ hfs_bnode_write(prev, &cnid, offsetof(struct hfs_bnode_desc, next), 4);
+
+ node->type = HFS_NODE_MAP;
+ node->num_recs = 1;
+ hfs_bnode_clear(node, 0, tree->node_size);
+ desc.next = 0;
+ desc.prev = 0;
+ desc.type = HFS_NODE_MAP;
+ desc.height = 0;
+ desc.num_recs = cpu_to_be16(1);
+ desc.reserved = 0;
+ hfs_bnode_write(node, &desc, 0, sizeof(desc));
+ hfs_bnode_write_u16(node, 14, 0x8000);
+ hfs_bnode_write_u16(node, tree->node_size - 2, 14);
+ hfs_bnode_write_u16(node, tree->node_size - 4, tree->node_size - 6);
+
+ return node;
+}
+
+/* Make sure @tree has enough space for the @rsvd_nodes */
+int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
+{
+ struct inode *inode = tree->inode;
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ u32 count;
+ int res;
+
+ lockdep_assert_held(&tree->tree_lock);
+
+ if (rsvd_nodes <= 0)
+ return 0;
+
+ while (tree->free_nodes < rsvd_nodes) {
+ res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));
+ if (res)
+ return res;
+ hip->phys_size = inode->i_size =
+ (loff_t)hip->alloc_blocks <<
+ HFSPLUS_SB(tree->sb)->alloc_blksz_shift;
+ hip->fs_blocks =
+ hip->alloc_blocks << HFSPLUS_SB(tree->sb)->fs_shift;
+ inode_set_bytes(inode, inode->i_size);
+ count = inode->i_size >> tree->node_size_shift;
+ tree->free_nodes += count - tree->node_count;
+ tree->node_count = count;
+ }
+ return 0;
+}
+
+struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
+{
+ struct hfs_bnode *node, *next_node;
+ struct hfs_bmap_ctx ctx;
+ struct page *page;
+ u32 nidx, idx;
+ u8 *data, byte, m;
+ int i, res;
+
+ lockdep_assert_held(&tree->tree_lock);
+
+ res = hfs_bmap_reserve(tree, 1);
+ if (res)
+ return ERR_PTR(res);
+
+ nidx = 0;
+ node = hfs_bnode_find(tree, nidx);
+ if (IS_ERR(node))
+ return node;
+
+ page = hfs_bmap_get_map_page(node, &ctx, 0);
+ if (IS_ERR(page)) {
+ res = PTR_ERR(page);
+ hfs_bnode_put(node);
+ return ERR_PTR(res);
+ }
+
+ data = kmap_local_page(page);
+ idx = 0;
+
+ for (;;) {
+ while (ctx.len) {
+ byte = data[ctx.off];
+ if (byte != 0xff) {
+ for (m = 0x80, i = 0; i < 8; m >>= 1, i++) {
+ if (!(byte & m)) {
+ idx += i;
+ data[ctx.off] |= m;
+ set_page_dirty(page);
+ kunmap_local(data);
+ tree->free_nodes--;
+ hfs_btree_write(tree);
+ mark_inode_dirty(tree->inode);
+ hfs_bnode_put(node);
+ return hfs_bnode_create(tree,
+ idx);
+ }
+ }
+ }
+ if (++ctx.off >= PAGE_SIZE) {
+ kunmap_local(data);
+ page = node->page[++ctx.page_idx];
+ data = kmap_local_page(page);
+ ctx.off = 0;
+ }
+ idx += 8;
+ ctx.len--;
+ }
+ kunmap_local(data);
+ nidx = node->next;
+ if (!nidx) {
+ hfs_dbg("create new bmap node\n");
+ next_node = hfs_bmap_new_bmap(node, idx);
+ hfs_btree_write(tree);
+ } else
+ next_node = hfs_bnode_find(tree, nidx);
+ hfs_bnode_put(node);
+ if (IS_ERR(next_node))
+ return next_node;
+ node = next_node;
+
+ page = hfs_bmap_get_map_page(node, &ctx, 0);
+ if (IS_ERR(page)) {
+ res = PTR_ERR(page);
+ hfs_bnode_put(node);
+ return ERR_PTR(res);
+ }
+ data = kmap_local_page(page);
+ }
+}
+
+void hfs_bmap_free(struct hfs_bnode *node)
+{
+ struct hfs_btree *tree;
+ u16 off, len;
+ u32 nidx;
+ int res;
+
+ hfs_dbg("node %u\n", node->this);
+ BUG_ON(!node->this);
+ tree = node->tree;
+ lockdep_assert_held(&tree->tree_lock);
+ nidx = node->this;
+ node = hfs_bnode_find(tree, 0);
+ if (IS_ERR(node))
+ return;
+ len = hfs_brec_lenoff(node, 2, &off);
+ if (hfs_brec_len_invalid(node, len)) {
+ pr_err("invalid bmap record length: node %u, len %u\n",
+ node->this, len);
+ hfs_bnode_put(node);
+ return;
+ }
+ while (nidx >= len * 8) {
+ u32 i;
+
+ nidx -= len * 8;
+ i = node->next;
+ if (!i) {
+ /* panic */;
+ pr_crit("unable to free bnode %u. "
+ "bmap not found!\n",
+ node->this);
+ hfs_bnode_put(node);
+ return;
+ }
+ hfs_bnode_put(node);
+ node = hfs_bnode_find(tree, i);
+ if (IS_ERR(node))
+ return;
+ if (node->type != HFS_NODE_MAP) {
+ /* panic */;
+ pr_crit("invalid bmap found! "
+ "(%u,%d)\n",
+ node->this, node->type);
+ hfs_bnode_put(node);
+ return;
+ }
+ len = hfs_brec_lenoff(node, 0, &off);
+ if (hfs_brec_len_invalid(node, len)) {
+ pr_err("invalid bmap record length: node %u, len %u\n",
+ node->this, len);
+ hfs_bnode_put(node);
+ return;
+ }
+ }
+
+ res = hfs_bmap_clear_bit(node, nidx);
+ if (res == -EINVAL) {
+ pr_crit("trying to free the freed bnode %u(%d)\n",
+ nidx, node->type);
+ } else if (res) {
+ pr_crit("fail to free bnode %u(%d)\n",
+ nidx, node->type);
+ } else {
+ tree->free_nodes++;
+ hfs_btree_write(tree);
+ mark_inode_dirty(tree->inode);
+ }
+
+ hfs_bnode_put(node);
+}
diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c
new file mode 100644
index 000000000..fe11c4b6d
--- /dev/null
+++ b/fs/hfsplus/catalog.c
@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/catalog.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of catalog records
+ */
+
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2)
+{
+ __be32 k1p, k2p;
+
+ k1p = k1->cat.parent;
+ k2p = k2->cat.parent;
+ if (k1p != k2p)
+ return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1;
+
+ return hfsplus_strcasecmp(&k1->cat.name, &k2->cat.name);
+}
+
+int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2)
+{
+ __be32 k1p, k2p;
+
+ k1p = k1->cat.parent;
+ k2p = k2->cat.parent;
+ if (k1p != k2p)
+ return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1;
+
+ return hfsplus_strcmp(&k1->cat.name, &k2->cat.name);
+}
+
+/* Generates key for catalog file/folders record. */
+int hfsplus_cat_build_key(struct super_block *sb,
+ hfsplus_btree_key *key, u32 parent, const struct qstr *str)
+{
+ int len, err;
+
+ key->cat.parent = cpu_to_be32(parent);
+ err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN,
+ str->name, str->len, HFS_REGULAR_NAME);
+ if (unlikely(err < 0))
+ return err;
+
+ len = be16_to_cpu(key->cat.name.length);
+ key->key_len = cpu_to_be16(6 + 2 * len);
+ return 0;
+}
+
+/* Generates key for catalog thread record. */
+void hfsplus_cat_build_key_with_cnid(struct super_block *sb,
+ hfsplus_btree_key *key, u32 parent)
+{
+ key->cat.parent = cpu_to_be32(parent);
+ key->cat.name.length = 0;
+ key->key_len = cpu_to_be16(6);
+}
+
+static void hfsplus_cat_build_key_uni(hfsplus_btree_key *key, u32 parent,
+ struct hfsplus_unistr *name)
+{
+ int ustrlen;
+
+ ustrlen = be16_to_cpu(name->length);
+ key->cat.parent = cpu_to_be32(parent);
+ key->cat.name.length = cpu_to_be16(ustrlen);
+ ustrlen *= 2;
+ memcpy(key->cat.name.unicode, name->unicode, ustrlen);
+ key->key_len = cpu_to_be16(6 + ustrlen);
+}
+
+void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms)
+{
+ if (inode->i_flags & S_IMMUTABLE)
+ perms->rootflags |= HFSPLUS_FLG_IMMUTABLE;
+ else
+ perms->rootflags &= ~HFSPLUS_FLG_IMMUTABLE;
+ if (inode->i_flags & S_APPEND)
+ perms->rootflags |= HFSPLUS_FLG_APPEND;
+ else
+ perms->rootflags &= ~HFSPLUS_FLG_APPEND;
+
+ perms->userflags = HFSPLUS_I(inode)->userflags;
+ perms->mode = cpu_to_be16(inode->i_mode);
+ perms->owner = cpu_to_be32(i_uid_read(inode));
+ perms->group = cpu_to_be32(i_gid_read(inode));
+
+ if (S_ISREG(inode->i_mode))
+ perms->dev = cpu_to_be32(inode->i_nlink);
+ else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode))
+ perms->dev = cpu_to_be32(inode->i_rdev);
+ else
+ perms->dev = 0;
+}
+
+static int hfsplus_cat_build_record(hfsplus_cat_entry *entry,
+ u32 cnid, struct inode *inode)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+
+ if (S_ISDIR(inode->i_mode)) {
+ struct hfsplus_cat_folder *folder;
+
+ folder = &entry->folder;
+ memset(folder, 0, sizeof(*folder));
+ folder->type = cpu_to_be16(HFSPLUS_FOLDER);
+ if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags))
+ folder->flags |= cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT);
+ folder->id = cpu_to_be32(inode->i_ino);
+ HFSPLUS_I(inode)->create_date =
+ folder->create_date =
+ folder->content_mod_date =
+ folder->attribute_mod_date =
+ folder->access_date = hfsp_now2mt();
+ hfsplus_cat_set_perms(inode, &folder->permissions);
+ if (inode == sbi->hidden_dir)
+ /* invisible and namelocked */
+ folder->user_info.frFlags = cpu_to_be16(0x5000);
+ return sizeof(*folder);
+ } else {
+ struct hfsplus_cat_file *file;
+
+ file = &entry->file;
+ memset(file, 0, sizeof(*file));
+ file->type = cpu_to_be16(HFSPLUS_FILE);
+ file->flags = cpu_to_be16(HFSPLUS_FILE_THREAD_EXISTS);
+ file->id = cpu_to_be32(cnid);
+ HFSPLUS_I(inode)->create_date =
+ file->create_date =
+ file->content_mod_date =
+ file->attribute_mod_date =
+ file->access_date = hfsp_now2mt();
+ if (cnid == inode->i_ino) {
+ hfsplus_cat_set_perms(inode, &file->permissions);
+ if (S_ISLNK(inode->i_mode)) {
+ file->user_info.fdType =
+ cpu_to_be32(HFSP_SYMLINK_TYPE);
+ file->user_info.fdCreator =
+ cpu_to_be32(HFSP_SYMLINK_CREATOR);
+ } else {
+ file->user_info.fdType =
+ cpu_to_be32(sbi->type);
+ file->user_info.fdCreator =
+ cpu_to_be32(sbi->creator);
+ }
+ if (HFSPLUS_FLG_IMMUTABLE &
+ (file->permissions.rootflags |
+ file->permissions.userflags))
+ file->flags |=
+ cpu_to_be16(HFSPLUS_FILE_LOCKED);
+ } else {
+ file->user_info.fdType =
+ cpu_to_be32(HFSP_HARDLINK_TYPE);
+ file->user_info.fdCreator =
+ cpu_to_be32(HFSP_HFSPLUS_CREATOR);
+ file->user_info.fdFlags =
+ cpu_to_be16(0x100);
+ file->create_date =
+ HFSPLUS_I(sbi->hidden_dir)->create_date;
+ file->permissions.dev =
+ cpu_to_be32(HFSPLUS_I(inode)->linkid);
+ }
+ return sizeof(*file);
+ }
+}
+
+static int hfsplus_fill_cat_thread(struct super_block *sb,
+ hfsplus_cat_entry *entry, int type,
+ u32 parentid, const struct qstr *str)
+{
+ int err;
+
+ entry->type = cpu_to_be16(type);
+ 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, HFS_REGULAR_NAME);
+ if (unlikely(err < 0))
+ return err;
+
+ return 10 + be16_to_cpu(entry->thread.nodeName.length) * 2;
+}
+
+/* Try to get a catalog entry for given catalog id */
+int hfsplus_find_cat(struct super_block *sb, u32 cnid,
+ struct hfs_find_data *fd)
+{
+ hfsplus_cat_entry tmp = {0};
+ int err;
+ u16 type;
+
+ hfsplus_cat_build_key_with_cnid(sb, fd->search_key, cnid);
+ err = hfsplus_brec_read_cat(fd, &tmp);
+ if (err)
+ return err;
+
+ type = be16_to_cpu(tmp.type);
+ if (!is_hfs_thread_record_type(type)) {
+ pr_err("found bad thread record in catalog\n");
+ return -EIO;
+ }
+
+ if (be16_to_cpu(tmp.thread.nodeName.length) > 255) {
+ pr_err("catalog name length corrupted\n");
+ return -EIO;
+ }
+
+ hfsplus_cat_build_key_uni(fd->search_key,
+ be32_to_cpu(tmp.thread.parentID),
+ &tmp.thread.nodeName);
+ return hfs_brec_find(fd, hfs_find_rec_by_key);
+}
+
+static void hfsplus_subfolders_inc(struct inode *dir)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+
+ if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) {
+ /*
+ * Increment subfolder count. Note, the value is only meaningful
+ * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set.
+ */
+ HFSPLUS_I(dir)->subfolders++;
+ }
+}
+
+static void hfsplus_subfolders_dec(struct inode *dir)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+
+ if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) {
+ /*
+ * Decrement subfolder count. Note, the value is only meaningful
+ * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set.
+ *
+ * Check for zero. Some subfolders may have been created
+ * by an implementation ignorant of this counter.
+ */
+ if (HFSPLUS_I(dir)->subfolders)
+ HFSPLUS_I(dir)->subfolders--;
+ }
+}
+
+int hfsplus_create_cat(u32 cnid, struct inode *dir,
+ const struct qstr *str, struct inode *inode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct hfs_find_data fd;
+ hfsplus_cat_entry entry;
+ int entry_size;
+ int err;
+
+ hfs_dbg("name %s, cnid %u, i_nlink %d\n",
+ str->name, cnid, inode->i_nlink);
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
+ if (err)
+ return err;
+
+ /*
+ * Fail early and avoid ENOSPC during the btree operations. We may
+ * have to split the root node at most once.
+ */
+ err = hfs_bmap_reserve(fd.tree, 2 * fd.tree->depth);
+ if (err)
+ goto err2;
+
+ hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
+ entry_size = hfsplus_fill_cat_thread(sb, &entry,
+ S_ISDIR(inode->i_mode) ?
+ HFSPLUS_FOLDER_THREAD : HFSPLUS_FILE_THREAD,
+ dir->i_ino, str);
+ if (unlikely(entry_size < 0)) {
+ err = entry_size;
+ goto err2;
+ }
+
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ goto err2;
+ }
+ err = hfs_brec_insert(&fd, &entry, entry_size);
+ if (err)
+ goto err2;
+
+ err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);
+ if (unlikely(err))
+ goto err1;
+
+ entry_size = hfsplus_cat_build_record(&entry, cnid, inode);
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err != -ENOENT) {
+ /* panic? */
+ if (!err)
+ err = -EEXIST;
+ goto err1;
+ }
+ err = hfs_brec_insert(&fd, &entry, entry_size);
+ if (err)
+ goto err1;
+
+ dir->i_size++;
+ if (S_ISDIR(inode->i_mode))
+ hfsplus_subfolders_inc(dir);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
+
+ hfs_find_exit(&fd);
+ return 0;
+
+err1:
+ hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
+ if (!hfs_brec_find(&fd, hfs_find_rec_by_key))
+ hfs_brec_remove(&fd);
+err2:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
+{
+ struct super_block *sb = dir->i_sb;
+ struct hfs_find_data fd;
+ struct hfsplus_fork_raw fork;
+ int err, off;
+ u16 type;
+
+ hfs_dbg("name %s, cnid %u\n", str ? str->name : NULL, cnid);
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
+ if (err)
+ return err;
+
+ /*
+ * Fail early and avoid ENOSPC during the btree operations. We may
+ * have to split the root node at most once.
+ */
+ err = hfs_bmap_reserve(fd.tree, 2 * (int)fd.tree->depth - 2);
+ if (err)
+ goto out;
+
+ if (!str) {
+ hfsplus_cat_entry entry = {0};
+
+ hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
+ err = hfsplus_brec_read_cat(&fd, &entry);
+ if (err)
+ goto out;
+
+ type = be16_to_cpu(entry.type);
+ if (!is_hfs_thread_record_type(type)) {
+ pr_err("found bad thread record in catalog\n");
+ err = -EIO;
+ goto out;
+ }
+
+ hfsplus_cat_build_key_uni(fd.search_key, dir->i_ino,
+ &entry.thread.nodeName);
+ } else {
+ err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);
+ if (unlikely(err))
+ goto out;
+ }
+
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+
+ type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset);
+ if (type == HFSPLUS_FILE) {
+#if 0
+ off = fd.entryoffset + offsetof(hfsplus_cat_file, data_fork);
+ hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork));
+ hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_DATA);
+#endif
+
+ off = fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, rsrc_fork);
+ hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork));
+ hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_RSRC);
+ }
+
+ err = hfs_brec_remove(&fd);
+ if (err)
+ goto out;
+
+ hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+
+ err = hfs_brec_remove(&fd);
+ if (err)
+ goto out;
+
+ dir->i_size--;
+ if (type == HFSPLUS_FOLDER)
+ hfsplus_subfolders_dec(dir);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY);
+
+ if (type == HFSPLUS_FILE || type == HFSPLUS_FOLDER) {
+ if (HFSPLUS_SB(sb)->attr_tree)
+ hfsplus_delete_all_attrs(dir, cnid);
+ }
+
+out:
+ hfs_find_exit(&fd);
+
+ return err;
+}
+
+int hfsplus_rename_cat(u32 cnid,
+ struct inode *src_dir, const struct qstr *src_name,
+ struct inode *dst_dir, const struct qstr *dst_name)
+{
+ struct super_block *sb = src_dir->i_sb;
+ struct hfs_find_data src_fd, dst_fd;
+ hfsplus_cat_entry entry;
+ int entry_size, type;
+ int err;
+
+ hfs_dbg("cnid %u - ino %llu, name %s - ino %llu, name %s\n",
+ cnid, src_dir->i_ino, src_name->name,
+ dst_dir->i_ino, dst_name->name);
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &src_fd);
+ if (err)
+ return err;
+ dst_fd = src_fd;
+
+ /*
+ * Fail early and avoid ENOSPC during the btree operations. We may
+ * have to split the root node at most twice.
+ */
+ err = hfs_bmap_reserve(src_fd.tree, 4 * (int)src_fd.tree->depth - 1);
+ if (err)
+ goto out;
+
+ /* find the old dir entry and read the data */
+ err = hfsplus_cat_build_key(sb, src_fd.search_key,
+ src_dir->i_ino, src_name);
+ if (unlikely(err))
+ goto out;
+
+ err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+ if (src_fd.entrylength > sizeof(entry) || src_fd.entrylength < 0) {
+ err = -EIO;
+ goto out;
+ }
+
+ hfs_bnode_read(src_fd.bnode, &entry, src_fd.entryoffset,
+ src_fd.entrylength);
+ type = be16_to_cpu(entry.type);
+
+ /* create new dir entry with the data from the old entry */
+ err = hfsplus_cat_build_key(sb, dst_fd.search_key,
+ dst_dir->i_ino, dst_name);
+ if (unlikely(err))
+ goto out;
+
+ err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ goto out;
+ }
+
+ err = hfs_brec_insert(&dst_fd, &entry, src_fd.entrylength);
+ if (err)
+ goto out;
+ dst_dir->i_size++;
+ if (type == HFSPLUS_FOLDER)
+ hfsplus_subfolders_inc(dst_dir);
+ inode_set_mtime_to_ts(dst_dir, inode_set_ctime_current(dst_dir));
+
+ /* finally remove the old entry */
+ err = hfsplus_cat_build_key(sb, src_fd.search_key,
+ src_dir->i_ino, src_name);
+ if (unlikely(err))
+ goto out;
+
+ err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+ err = hfs_brec_remove(&src_fd);
+ if (err)
+ goto out;
+ src_dir->i_size--;
+ if (type == HFSPLUS_FOLDER)
+ hfsplus_subfolders_dec(src_dir);
+ inode_set_mtime_to_ts(src_dir, inode_set_ctime_current(src_dir));
+
+ /* remove old thread entry */
+ hfsplus_cat_build_key_with_cnid(sb, src_fd.search_key, cnid);
+ err = hfs_brec_find(&src_fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+ type = hfs_bnode_read_u16(src_fd.bnode, src_fd.entryoffset);
+ err = hfs_brec_remove(&src_fd);
+ if (err)
+ goto out;
+
+ /* create new thread entry */
+ hfsplus_cat_build_key_with_cnid(sb, dst_fd.search_key, cnid);
+ entry_size = hfsplus_fill_cat_thread(sb, &entry, type,
+ dst_dir->i_ino, dst_name);
+ if (unlikely(entry_size < 0)) {
+ err = entry_size;
+ goto out;
+ }
+
+ err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ goto out;
+ }
+ err = hfs_brec_insert(&dst_fd, &entry, entry_size);
+
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb), HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(dst_dir, HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(src_dir, HFSPLUS_I_CAT_DIRTY);
+out:
+ hfs_bnode_put(dst_fd.bnode);
+ hfs_find_exit(&src_fd);
+ return err;
+}
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
new file mode 100644
index 000000000..51fcba2e6
--- /dev/null
+++ b/fs/hfsplus/dir.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/dir.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of directories
+ */
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/nls.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+#include "xattr.h"
+
+static inline void hfsplus_instantiate(struct dentry *dentry,
+ struct inode *inode, u32 cnid)
+{
+ dentry->d_fsdata = (void *)(unsigned long)cnid;
+ d_instantiate(dentry, inode);
+}
+
+/* Find the entry inside dir named dentry->d_name */
+static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct inode *inode = NULL;
+ struct hfs_find_data fd;
+ struct super_block *sb;
+ hfsplus_cat_entry entry;
+ int err;
+ u32 cnid, linkid = 0;
+ u16 type;
+
+ sb = dir->i_sb;
+
+ dentry->d_fsdata = NULL;
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
+ if (err)
+ return ERR_PTR(err);
+ err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino,
+ &dentry->d_name);
+ if (unlikely(err < 0))
+ goto fail;
+again:
+ err = hfsplus_brec_read_cat(&fd, &entry);
+ if (err) {
+ if (err == -ENOENT) {
+ hfs_find_exit(&fd);
+ /* No such entry */
+ inode = NULL;
+ goto out;
+ }
+ goto fail;
+ }
+ type = be16_to_cpu(entry.type);
+ if (type == HFSPLUS_FOLDER) {
+ if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
+ err = -EIO;
+ goto fail;
+ }
+ cnid = be32_to_cpu(entry.folder.id);
+ dentry->d_fsdata = (void *)(unsigned long)cnid;
+ } else if (type == HFSPLUS_FILE) {
+ if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
+ err = -EIO;
+ goto fail;
+ }
+ cnid = be32_to_cpu(entry.file.id);
+ if (entry.file.user_info.fdType ==
+ cpu_to_be32(HFSP_HARDLINK_TYPE) &&
+ entry.file.user_info.fdCreator ==
+ cpu_to_be32(HFSP_HFSPLUS_CREATOR) &&
+ HFSPLUS_SB(sb)->hidden_dir &&
+ (entry.file.create_date ==
+ HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)->
+ create_date ||
+ entry.file.create_date ==
+ HFSPLUS_I(d_inode(sb->s_root))->
+ create_date)) {
+ struct qstr str;
+ char name[32];
+
+ if (dentry->d_fsdata) {
+ /*
+ * We found a link pointing to another link,
+ * so ignore it and treat it as regular file.
+ */
+ cnid = (unsigned long)dentry->d_fsdata;
+ linkid = 0;
+ } else {
+ dentry->d_fsdata = (void *)(unsigned long)cnid;
+ linkid =
+ be32_to_cpu(entry.file.permissions.dev);
+ str.len = sprintf(name, "iNode%d", linkid);
+ str.name = name;
+ err = hfsplus_cat_build_key(sb, fd.search_key,
+ HFSPLUS_SB(sb)->hidden_dir->i_ino,
+ &str);
+ if (unlikely(err < 0))
+ goto fail;
+ goto again;
+ }
+ } else if (!dentry->d_fsdata)
+ dentry->d_fsdata = (void *)(unsigned long)cnid;
+ } else {
+ pr_err("invalid catalog entry type in lookup\n");
+ err = -EIO;
+ goto fail;
+ }
+ hfs_find_exit(&fd);
+ inode = hfsplus_iget(dir->i_sb, cnid);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+ if (S_ISREG(inode->i_mode))
+ HFSPLUS_I(inode)->linkid = linkid;
+out:
+ return d_splice_alias(inode, dentry);
+fail:
+ hfs_find_exit(&fd);
+ return ERR_PTR(err);
+}
+
+static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct inode *inode = file_inode(file);
+ struct super_block *sb = inode->i_sb;
+ int len, err;
+ char *strbuf;
+ hfsplus_cat_entry entry;
+ struct hfs_find_data fd;
+ struct hfsplus_readdir_data *rd;
+ u16 type;
+
+ if (file->f_pos >= inode->i_size)
+ return 0;
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
+ if (err)
+ return err;
+ strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN + 1, GFP_KERNEL);
+ if (!strbuf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ hfsplus_cat_build_key_with_cnid(sb, fd.search_key, inode->i_ino);
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err)
+ goto out;
+
+ if (ctx->pos == 0) {
+ /* This is completely artificial... */
+ if (!dir_emit_dot(file, ctx))
+ goto out;
+ ctx->pos = 1;
+ }
+ if (ctx->pos == 1) {
+ if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
+ err = -EIO;
+ goto out;
+ }
+
+ hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
+ fd.entrylength);
+ if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) {
+ pr_err("bad catalog folder thread\n");
+ err = -EIO;
+ goto out;
+ }
+ if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
+ pr_err("truncated catalog thread\n");
+ err = -EIO;
+ goto out;
+ }
+ if (!dir_emit(ctx, "..", 2,
+ be32_to_cpu(entry.thread.parentID), DT_DIR))
+ goto out;
+ ctx->pos = 2;
+ }
+ if (ctx->pos >= inode->i_size)
+ goto out;
+ rd = file->private_data;
+ if (rd && rd->pos == ctx->pos) {
+ memcpy(fd.search_key, &rd->key, sizeof(struct hfsplus_cat_key));
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err == -ENOENT)
+ err = hfs_brec_goto(&fd, 1);
+ } else {
+ err = hfs_brec_goto(&fd, ctx->pos - 1);
+ }
+ if (err)
+ goto out;
+ for (;;) {
+ if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) {
+ pr_err("walked past end of dir\n");
+ err = -EIO;
+ goto out;
+ }
+
+ if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
+ err = -EIO;
+ goto out;
+ }
+
+ hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
+ fd.entrylength);
+ type = be16_to_cpu(entry.type);
+ len = NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN;
+ err = hfsplus_uni2asc_str(sb, &fd.key->cat.name, strbuf, &len);
+ if (err)
+ goto out;
+ if (type == HFSPLUS_FOLDER) {
+ if (fd.entrylength <
+ sizeof(struct hfsplus_cat_folder)) {
+ pr_err("small dir entry\n");
+ err = -EIO;
+ goto out;
+ }
+ if (HFSPLUS_SB(sb)->hidden_dir &&
+ HFSPLUS_SB(sb)->hidden_dir->i_ino ==
+ be32_to_cpu(entry.folder.id))
+ goto next;
+ if (!dir_emit(ctx, strbuf, len,
+ be32_to_cpu(entry.folder.id), DT_DIR))
+ break;
+ } else if (type == HFSPLUS_FILE) {
+ u16 mode;
+ unsigned type = DT_UNKNOWN;
+
+ if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
+ pr_err("small file entry\n");
+ err = -EIO;
+ goto out;
+ }
+
+ mode = be16_to_cpu(entry.file.permissions.mode);
+ if (S_ISREG(mode))
+ type = DT_REG;
+ else if (S_ISLNK(mode))
+ type = DT_LNK;
+ else if (S_ISFIFO(mode))
+ type = DT_FIFO;
+ else if (S_ISCHR(mode))
+ type = DT_CHR;
+ else if (S_ISBLK(mode))
+ type = DT_BLK;
+ else if (S_ISSOCK(mode))
+ type = DT_SOCK;
+
+ if (!dir_emit(ctx, strbuf, len,
+ be32_to_cpu(entry.file.id), type))
+ break;
+ } else {
+ pr_err("bad catalog entry type\n");
+ err = -EIO;
+ goto out;
+ }
+next:
+ ctx->pos++;
+ if (ctx->pos >= inode->i_size)
+ goto out;
+ err = hfs_brec_goto(&fd, 1);
+ if (err)
+ goto out;
+ }
+ if (!rd) {
+ rd = kmalloc_obj(struct hfsplus_readdir_data);
+ if (!rd) {
+ err = -ENOMEM;
+ goto out;
+ }
+ file->private_data = rd;
+ }
+ rd->pos = ctx->pos;
+ memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));
+out:
+ kfree(strbuf);
+ hfs_find_exit(&fd);
+ return err;
+}
+
+static int hfsplus_dir_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
+ struct dentry *dst_dentry)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
+ struct inode *inode = d_inode(src_dentry);
+ struct inode *src_dir = d_inode(src_dentry->d_parent);
+ struct qstr str;
+ char name[32];
+ u32 cnid, id;
+ int res;
+
+ if (HFSPLUS_IS_RSRC(inode))
+ return -EPERM;
+ if (!S_ISREG(inode->i_mode))
+ return -EPERM;
+
+ hfs_dbg("src_dir->i_ino %llu, dst_dir->i_ino %llu, inode->i_ino %llu\n",
+ src_dir->i_ino, dst_dir->i_ino, inode->i_ino);
+
+ mutex_lock(&sbi->vh_mutex);
+ if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) {
+ for (;;) {
+ get_random_bytes(&id, sizeof(cnid));
+ id &= 0x3fffffff;
+ str.name = name;
+ str.len = sprintf(name, "iNode%d", id);
+ res = hfsplus_rename_cat(inode->i_ino,
+ src_dir, &src_dentry->d_name,
+ sbi->hidden_dir, &str);
+ if (!res)
+ break;
+ if (res != -EEXIST)
+ goto out;
+ }
+ HFSPLUS_I(inode)->linkid = id;
+ cnid = sbi->next_cnid++;
+ src_dentry->d_fsdata = (void *)(unsigned long)cnid;
+ res = hfsplus_create_cat(cnid, src_dir,
+ &src_dentry->d_name, inode);
+ if (res)
+ /* panic? */
+ goto out;
+ sbi->file_count++;
+ }
+ cnid = sbi->next_cnid++;
+ res = hfsplus_create_cat(cnid, dst_dir, &dst_dentry->d_name, inode);
+ if (res)
+ goto out;
+
+ inc_nlink(inode);
+ hfsplus_instantiate(dst_dentry, inode, cnid);
+ ihold(inode);
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ sbi->file_count++;
+ hfsplus_mark_mdb_dirty(dst_dir->i_sb);
+
+ res = hfsplus_cat_write_inode(src_dir);
+ if (res)
+ goto out;
+
+ res = hfsplus_cat_write_inode(dst_dir);
+ if (res)
+ goto out;
+
+ res = hfsplus_cat_write_inode(sbi->hidden_dir);
+ if (res)
+ goto out;
+
+ res = hfsplus_cat_write_inode(inode);
+
+out:
+ mutex_unlock(&sbi->vh_mutex);
+ return res;
+}
+
+static int hfsplus_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+ struct inode *inode = d_inode(dentry);
+ struct qstr str;
+ char name[32];
+ u32 cnid;
+ int res;
+
+ if (HFSPLUS_IS_RSRC(inode))
+ return -EPERM;
+
+ hfs_dbg("dir->i_ino %llu, inode->i_ino %llu\n",
+ dir->i_ino, inode->i_ino);
+
+ mutex_lock(&sbi->vh_mutex);
+ cnid = (u32)(unsigned long)dentry->d_fsdata;
+ if (inode->i_ino == cnid &&
+ atomic_read(&HFSPLUS_I(inode)->opencnt)) {
+ str.name = name;
+ str.len = sprintf(name, "temp%llu", inode->i_ino);
+ res = hfsplus_rename_cat(inode->i_ino,
+ dir, &dentry->d_name,
+ sbi->hidden_dir, &str);
+ if (!res) {
+ inode->i_flags |= S_DEAD;
+ drop_nlink(inode);
+ }
+ goto out;
+ }
+ res = hfsplus_delete_cat(cnid, dir, &dentry->d_name);
+ if (res)
+ goto out;
+
+ if (inode->i_nlink > 0)
+ drop_nlink(inode);
+ if (inode->i_ino == cnid)
+ clear_nlink(inode);
+ if (!inode->i_nlink) {
+ if (inode->i_ino != cnid) {
+ sbi->file_count--;
+ if (!atomic_read(&HFSPLUS_I(inode)->opencnt)) {
+ res = hfsplus_delete_cat(inode->i_ino,
+ sbi->hidden_dir,
+ NULL);
+ if (!res)
+ hfsplus_delete_inode(inode);
+ } else
+ inode->i_flags |= S_DEAD;
+ } else
+ hfsplus_delete_inode(inode);
+ } else
+ sbi->file_count--;
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+out:
+ if (!res) {
+ res = hfsplus_cat_write_inode(dir);
+ if (!res) {
+ res = hfsplus_cat_write_inode(sbi->hidden_dir);
+ if (!res)
+ res = hfsplus_cat_write_inode(inode);
+ }
+ }
+
+ mutex_unlock(&sbi->vh_mutex);
+ return res;
+}
+
+static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+ struct inode *inode = d_inode(dentry);
+ int res;
+
+ if (inode->i_size != 2)
+ return -ENOTEMPTY;
+
+ mutex_lock(&sbi->vh_mutex);
+ res = hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
+ if (res)
+ goto out;
+ clear_nlink(inode);
+ inode_set_ctime_current(inode);
+ hfsplus_delete_inode(inode);
+ mark_inode_dirty(inode);
+
+ res = hfsplus_cat_write_inode(dir);
+out:
+ mutex_unlock(&sbi->vh_mutex);
+ return res;
+}
+
+static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, const char *symname)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+ struct inode *inode;
+ int res = -ENOMEM;
+
+ mutex_lock(&sbi->vh_mutex);
+ inode = hfsplus_new_inode(dir->i_sb, dir, S_IFLNK | S_IRWXUGO);
+ if (!inode)
+ goto out;
+
+ hfs_dbg("dir->i_ino %llu, inode->i_ino %llu\n",
+ dir->i_ino, inode->i_ino);
+
+ res = page_symlink(inode, symname, strlen(symname) + 1);
+ if (res)
+ goto out_err;
+
+ res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
+ if (res)
+ goto out_err;
+
+ res = hfsplus_init_security(inode, dir, &dentry->d_name);
+ if (res == -EOPNOTSUPP)
+ res = 0; /* Operation is not supported. */
+ else if (res) {
+ /* Try to delete anyway without error analysis. */
+ hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
+ goto out_err;
+ }
+
+ hfsplus_instantiate(dentry, inode, inode->i_ino);
+ mark_inode_dirty(inode);
+
+ res = hfsplus_cat_write_inode(dir);
+ if (res)
+ goto out;
+
+ res = hfsplus_cat_write_inode(inode);
+ goto out;
+
+out_err:
+ clear_nlink(inode);
+ hfsplus_delete_inode(inode);
+ iput(inode);
+out:
+ mutex_unlock(&sbi->vh_mutex);
+ return res;
+}
+
+static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode, dev_t rdev)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
+ struct inode *inode;
+ int res = -ENOMEM;
+
+ mutex_lock(&sbi->vh_mutex);
+ inode = hfsplus_new_inode(dir->i_sb, dir, mode);
+ if (!inode)
+ goto out;
+
+ hfs_dbg("dir->i_ino %llu, inode->i_ino %llu\n",
+ dir->i_ino, inode->i_ino);
+
+ if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
+ init_special_inode(inode, mode, rdev);
+
+ res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
+ if (res)
+ goto failed_mknod;
+
+ res = hfsplus_init_security(inode, dir, &dentry->d_name);
+ if (res == -EOPNOTSUPP)
+ res = 0; /* Operation is not supported. */
+ else if (res) {
+ /* Try to delete anyway without error analysis. */
+ hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
+ goto failed_mknod;
+ }
+
+ hfsplus_instantiate(dentry, inode, inode->i_ino);
+ mark_inode_dirty(inode);
+
+ res = hfsplus_cat_write_inode(dir);
+ if (res)
+ goto out;
+
+ res = hfsplus_cat_write_inode(inode);
+ goto out;
+
+failed_mknod:
+ clear_nlink(inode);
+ hfsplus_delete_inode(inode);
+ iput(inode);
+out:
+ mutex_unlock(&sbi->vh_mutex);
+ return res;
+}
+
+static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0);
+}
+
+static struct dentry *hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0));
+}
+
+static int hfsplus_rename(struct mnt_idmap *idmap,
+ struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
+{
+ int res;
+
+ if (flags & ~RENAME_NOREPLACE)
+ return -EINVAL;
+
+ /* Unlink destination if it already exists */
+ if (d_really_is_positive(new_dentry)) {
+ if (d_is_dir(new_dentry))
+ res = hfsplus_rmdir(new_dir, new_dentry);
+ else
+ res = hfsplus_unlink(new_dir, new_dentry);
+ if (res)
+ return res;
+ }
+
+ res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata,
+ old_dir, &old_dentry->d_name,
+ new_dir, &new_dentry->d_name);
+ if (!res) {
+ struct inode *inode = d_inode(old_dentry);
+
+ new_dentry->d_fsdata = old_dentry->d_fsdata;
+
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+
+ res = hfsplus_cat_write_inode(old_dir);
+ if (res)
+ return res;
+
+ res = hfsplus_cat_write_inode(new_dir);
+ if (res)
+ return res;
+
+ res = hfsplus_cat_write_inode(inode);
+ }
+ return res;
+}
+
+const struct inode_operations hfsplus_dir_inode_operations = {
+ .lookup = hfsplus_lookup,
+ .create = hfsplus_create,
+ .link = hfsplus_link,
+ .unlink = hfsplus_unlink,
+ .mkdir = hfsplus_mkdir,
+ .rmdir = hfsplus_rmdir,
+ .symlink = hfsplus_symlink,
+ .mknod = hfsplus_mknod,
+ .rename = hfsplus_rename,
+ .getattr = hfsplus_getattr,
+ .listxattr = hfsplus_listxattr,
+ .fileattr_get = hfsplus_fileattr_get,
+ .fileattr_set = hfsplus_fileattr_set,
+};
+
+const struct file_operations hfsplus_dir_operations = {
+ .fsync = hfsplus_file_fsync,
+ .read = generic_read_dir,
+ .iterate_shared = hfsplus_readdir,
+ .unlocked_ioctl = hfsplus_ioctl,
+ .llseek = generic_file_llseek,
+ .release = hfsplus_dir_release,
+};
diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c
new file mode 100644
index 000000000..eb7c11524
--- /dev/null
+++ b/fs/hfsplus/extents.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/extents.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of Extents both in catalog and extents overflow trees
+ */
+
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+/* Compare two extents keys, returns 0 on same, pos/neg for difference */
+int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2)
+{
+ __be32 k1id, k2id;
+ __be32 k1s, k2s;
+
+ k1id = k1->ext.cnid;
+ k2id = k2->ext.cnid;
+ if (k1id != k2id)
+ return be32_to_cpu(k1id) < be32_to_cpu(k2id) ? -1 : 1;
+
+ if (k1->ext.fork_type != k2->ext.fork_type)
+ return k1->ext.fork_type < k2->ext.fork_type ? -1 : 1;
+
+ k1s = k1->ext.start_block;
+ k2s = k2->ext.start_block;
+ if (k1s == k2s)
+ return 0;
+ return be32_to_cpu(k1s) < be32_to_cpu(k2s) ? -1 : 1;
+}
+
+static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,
+ u32 block, u8 type)
+{
+ key->key_len = cpu_to_be16(HFSPLUS_EXT_KEYLEN - 2);
+ key->ext.cnid = cpu_to_be32(cnid);
+ key->ext.start_block = cpu_to_be32(block);
+ key->ext.fork_type = type;
+ key->ext.pad = 0;
+}
+
+static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off)
+{
+ int i;
+ u32 count;
+
+ for (i = 0; i < 8; ext++, i++) {
+ count = be32_to_cpu(ext->block_count);
+ if (off < count)
+ return be32_to_cpu(ext->start_block) + off;
+ off -= count;
+ }
+ /* panic? */
+ return 0;
+}
+
+static int hfsplus_ext_block_count(struct hfsplus_extent *ext)
+{
+ int i;
+ u32 count = 0;
+
+ for (i = 0; i < 8; ext++, i++)
+ count += be32_to_cpu(ext->block_count);
+ return count;
+}
+
+static u32 hfsplus_ext_lastblock(struct hfsplus_extent *ext)
+{
+ int i;
+
+ ext += 7;
+ for (i = 0; i < 7; ext--, i++)
+ if (ext->block_count)
+ break;
+ return be32_to_cpu(ext->start_block) + be32_to_cpu(ext->block_count);
+}
+
+static int __hfsplus_ext_write_extent(struct inode *inode,
+ struct hfs_find_data *fd)
+{
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ int res;
+
+ WARN_ON(!mutex_is_locked(&hip->extents_lock));
+
+ hfsplus_ext_build_key(fd->search_key, inode->i_ino, hip->cached_start,
+ HFSPLUS_IS_RSRC(inode) ?
+ HFSPLUS_TYPE_RSRC : HFSPLUS_TYPE_DATA);
+
+ res = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (hip->extent_state & HFSPLUS_EXT_NEW) {
+ if (res != -ENOENT)
+ return res;
+ /* Fail early and avoid ENOSPC during the btree operation */
+ res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1);
+ if (res)
+ return res;
+ hfs_brec_insert(fd, hip->cached_extents,
+ sizeof(hfsplus_extent_rec));
+ hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
+ } else {
+ if (res)
+ return res;
+ if (fd->entrylength != sizeof(hfsplus_extent_rec))
+ return -EIO;
+ hfs_bnode_write(fd->bnode, hip->cached_extents,
+ fd->entryoffset, fd->entrylength);
+ hip->extent_state &= ~HFSPLUS_EXT_DIRTY;
+ }
+
+ /*
+ * We can't just use hfsplus_mark_inode_dirty here, because we
+ * also get called from hfsplus_write_inode, which should not
+ * redirty the inode. Instead the callers have to be careful
+ * to explicily mark the inode dirty, too.
+ */
+ set_bit(HFSPLUS_I_EXT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_EXT_TREE_I(inode->i_sb))->flags);
+ set_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
+
+ return 0;
+}
+
+static int hfsplus_ext_write_extent_locked(struct inode *inode)
+{
+ int res = 0;
+
+ if (HFSPLUS_I(inode)->extent_state & HFSPLUS_EXT_DIRTY) {
+ struct hfs_find_data fd;
+
+ res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd);
+ if (res)
+ return res;
+ res = __hfsplus_ext_write_extent(inode, &fd);
+ hfs_find_exit(&fd);
+ }
+ return res;
+}
+
+int hfsplus_ext_write_extent(struct inode *inode)
+{
+ int res;
+
+ mutex_lock(&HFSPLUS_I(inode)->extents_lock);
+ res = hfsplus_ext_write_extent_locked(inode);
+ mutex_unlock(&HFSPLUS_I(inode)->extents_lock);
+
+ return res;
+}
+
+static inline int __hfsplus_ext_read_extent(struct hfs_find_data *fd,
+ struct hfsplus_extent *extent,
+ u32 cnid, u32 block, u8 type)
+{
+ int res;
+
+ hfsplus_ext_build_key(fd->search_key, cnid, block, type);
+ fd->key->ext.cnid = 0;
+ res = hfs_brec_find(fd, hfs_find_rec_by_key);
+ if (res && res != -ENOENT)
+ return res;
+ if (fd->key->ext.cnid != fd->search_key->ext.cnid ||
+ fd->key->ext.fork_type != fd->search_key->ext.fork_type)
+ return -ENOENT;
+ if (fd->entrylength != sizeof(hfsplus_extent_rec))
+ return -EIO;
+ hfs_bnode_read(fd->bnode, extent, fd->entryoffset,
+ sizeof(hfsplus_extent_rec));
+ return 0;
+}
+
+static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd,
+ struct inode *inode, u32 block)
+{
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ int res;
+
+ WARN_ON(!mutex_is_locked(&hip->extents_lock));
+
+ if (hip->extent_state & HFSPLUS_EXT_DIRTY) {
+ res = __hfsplus_ext_write_extent(inode, fd);
+ if (res)
+ return res;
+ }
+
+ res = __hfsplus_ext_read_extent(fd, hip->cached_extents, inode->i_ino,
+ block, HFSPLUS_IS_RSRC(inode) ?
+ HFSPLUS_TYPE_RSRC :
+ HFSPLUS_TYPE_DATA);
+ if (!res) {
+ hip->cached_start = be32_to_cpu(fd->key->ext.start_block);
+ hip->cached_blocks =
+ hfsplus_ext_block_count(hip->cached_extents);
+ } else {
+ hip->cached_start = hip->cached_blocks = 0;
+ hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
+ }
+ return res;
+}
+
+static int hfsplus_ext_read_extent(struct inode *inode, u32 block)
+{
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfs_find_data fd;
+ int res;
+
+ if (block >= hip->cached_start &&
+ block < hip->cached_start + hip->cached_blocks)
+ return 0;
+
+ res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd);
+ if (!res) {
+ res = __hfsplus_ext_cache_extent(&fd, inode, block);
+ hfs_find_exit(&fd);
+ }
+ return res;
+}
+
+/* Get a block at iblock for inode, possibly allocating if create */
+int hfsplus_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ int res = -EIO;
+ u32 ablock, dblock, mask;
+ sector_t sector;
+ int was_dirty = 0;
+
+ /* Convert inode block to disk allocation block */
+ ablock = iblock >> sbi->fs_shift;
+
+ if (iblock >= hip->fs_blocks) {
+ if (!create)
+ return 0;
+ if (iblock > hip->fs_blocks)
+ return -EIO;
+ if (ablock >= hip->alloc_blocks) {
+ res = hfsplus_file_extend(inode, false);
+ if (res)
+ return res;
+ }
+ } else
+ create = 0;
+
+ if (ablock < hip->first_blocks) {
+ dblock = hfsplus_ext_find_block(hip->first_extents, ablock);
+ goto done;
+ }
+
+ if (inode->i_ino == HFSPLUS_EXT_CNID)
+ return -EIO;
+
+ mutex_lock(&hip->extents_lock);
+
+ /*
+ * hfsplus_ext_read_extent will write out a cached extent into
+ * the extents btree. In that case we may have to mark the inode
+ * dirty even for a pure read of an extent here.
+ */
+ was_dirty = (hip->extent_state & HFSPLUS_EXT_DIRTY);
+ res = hfsplus_ext_read_extent(inode, ablock);
+ if (res) {
+ mutex_unlock(&hip->extents_lock);
+ return -EIO;
+ }
+ dblock = hfsplus_ext_find_block(hip->cached_extents,
+ ablock - hip->cached_start);
+ mutex_unlock(&hip->extents_lock);
+
+done:
+ hfs_dbg("ino %llu, iblock %llu - dblock %u\n",
+ inode->i_ino, (long long)iblock, dblock);
+
+ mask = (1 << sbi->fs_shift) - 1;
+ sector = ((sector_t)dblock << sbi->fs_shift) +
+ sbi->blockoffset + (iblock & mask);
+ map_bh(bh_result, sb, sector);
+
+ if (create) {
+ set_buffer_new(bh_result);
+ hip->phys_size += sb->s_blocksize;
+ hip->fs_blocks++;
+ inode_add_bytes(inode, sb->s_blocksize);
+ }
+ if (create || was_dirty)
+ mark_inode_dirty(inode);
+ return 0;
+}
+
+static void hfsplus_dump_extent(struct hfsplus_extent *extent)
+{
+ int i;
+
+ hfs_dbg("extent ");
+ for (i = 0; i < 8; i++)
+ hfs_dbg(" start_block %u, block_count %u",
+ be32_to_cpu(extent[i].start_block),
+ be32_to_cpu(extent[i].block_count));
+ hfs_dbg("\n");
+}
+
+static int hfsplus_add_extent(struct hfsplus_extent *extent, u32 offset,
+ u32 alloc_block, u32 block_count)
+{
+ u32 count, start;
+ int i;
+
+ hfsplus_dump_extent(extent);
+ for (i = 0; i < 8; extent++, i++) {
+ count = be32_to_cpu(extent->block_count);
+ if (offset == count) {
+ start = be32_to_cpu(extent->start_block);
+ if (alloc_block != start + count) {
+ if (++i >= 8)
+ return -ENOSPC;
+ extent++;
+ extent->start_block = cpu_to_be32(alloc_block);
+ } else
+ block_count += count;
+ extent->block_count = cpu_to_be32(block_count);
+ return 0;
+ } else if (offset < count)
+ break;
+ offset -= count;
+ }
+ /* panic? */
+ return -EIO;
+}
+
+static int hfsplus_free_extents(struct super_block *sb,
+ struct hfsplus_extent *extent,
+ u32 offset, u32 block_nr)
+{
+ u32 count, start;
+ int i;
+ int err = 0;
+
+ hfsplus_dump_extent(extent);
+ for (i = 0; i < 8; extent++, i++) {
+ count = be32_to_cpu(extent->block_count);
+ if (offset == count)
+ goto found;
+ else if (offset < count)
+ break;
+ offset -= count;
+ }
+ /* panic? */
+ return -EIO;
+found:
+ for (;;) {
+ start = be32_to_cpu(extent->start_block);
+ if (count <= block_nr) {
+ err = hfsplus_block_free(sb, start, count);
+ if (err) {
+ pr_err("can't free extent: start %u, count %u\n",
+ start, count);
+ }
+ extent->block_count = 0;
+ extent->start_block = 0;
+ block_nr -= count;
+ } else {
+ count -= block_nr;
+ err = hfsplus_block_free(sb, start + count, block_nr);
+ if (err) {
+ pr_err("can't free extent: start %u, count %u\n",
+ start, count);
+ }
+ extent->block_count = cpu_to_be32(count);
+ block_nr = 0;
+ }
+ if (!block_nr || !i) {
+ /*
+ * Try to free all extents and
+ * return only last error
+ */
+ return err;
+ }
+ i--;
+ extent--;
+ count = be32_to_cpu(extent->block_count);
+ }
+}
+
+int hfsplus_free_fork(struct super_block *sb, u32 cnid,
+ struct hfsplus_fork_raw *fork, int type)
+{
+ struct hfs_find_data fd;
+ hfsplus_extent_rec ext_entry;
+ u32 total_blocks, blocks, start;
+ int res, i;
+
+ total_blocks = be32_to_cpu(fork->total_blocks);
+ if (!total_blocks)
+ return 0;
+
+ blocks = 0;
+ for (i = 0; i < 8; i++)
+ blocks += be32_to_cpu(fork->extents[i].block_count);
+
+ res = hfsplus_free_extents(sb, fork->extents, blocks, blocks);
+ if (res)
+ return res;
+ if (total_blocks == blocks)
+ return 0;
+
+ res = hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd);
+ if (res)
+ return res;
+ do {
+ res = __hfsplus_ext_read_extent(&fd, ext_entry, cnid,
+ total_blocks, type);
+ if (res)
+ break;
+ start = be32_to_cpu(fd.key->ext.start_block);
+ hfs_brec_remove(&fd);
+
+ mutex_unlock(&fd.tree->tree_lock);
+ hfsplus_free_extents(sb, ext_entry, total_blocks - start,
+ total_blocks);
+ total_blocks = start;
+ mutex_lock_nested(&fd.tree->tree_lock,
+ hfsplus_btree_lock_class(fd.tree));
+ } while (total_blocks > blocks);
+ hfs_find_exit(&fd);
+
+ return res;
+}
+
+int hfsplus_file_extend(struct inode *inode, bool zeroout)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ u32 start, len, goal;
+ int res;
+
+ if (sbi->alloc_file->i_size * 8 <
+ sbi->total_blocks - sbi->free_blocks + 8) {
+ /* extend alloc file */
+ pr_err_ratelimited("extend alloc file! (%llu,%u,%u)\n",
+ sbi->alloc_file->i_size * 8,
+ sbi->total_blocks, sbi->free_blocks);
+ return -ENOSPC;
+ }
+
+ mutex_lock(&hip->extents_lock);
+ if (hip->alloc_blocks == hip->first_blocks)
+ goal = hfsplus_ext_lastblock(hip->first_extents);
+ else {
+ res = hfsplus_ext_read_extent(inode, hip->alloc_blocks);
+ if (res)
+ goto out;
+ goal = hfsplus_ext_lastblock(hip->cached_extents);
+ }
+
+ len = hip->clump_blocks;
+ start = hfsplus_block_allocate(sb, sbi->total_blocks, goal, &len);
+ if (start >= sbi->total_blocks) {
+ start = hfsplus_block_allocate(sb, goal, 0, &len);
+ if (start >= goal) {
+ res = -ENOSPC;
+ goto out;
+ }
+ }
+
+ if (zeroout) {
+ res = sb_issue_zeroout(sb, start, len, GFP_NOFS);
+ if (res)
+ goto out;
+ }
+
+ hfs_dbg("ino %llu, start %u, len %u\n", inode->i_ino, start, len);
+
+ if (hip->alloc_blocks <= hip->first_blocks) {
+ if (!hip->first_blocks) {
+ hfs_dbg("first_extent: start %u, len %u\n",
+ start, len);
+ /* no extents yet */
+ hip->first_extents[0].start_block = cpu_to_be32(start);
+ hip->first_extents[0].block_count = cpu_to_be32(len);
+ res = 0;
+ } else {
+ /* try to append to extents in inode */
+ res = hfsplus_add_extent(hip->first_extents,
+ hip->alloc_blocks,
+ start, len);
+ if (res == -ENOSPC)
+ goto insert_extent;
+ }
+ if (!res) {
+ hfsplus_dump_extent(hip->first_extents);
+ hip->first_blocks += len;
+ }
+ } else {
+ res = hfsplus_add_extent(hip->cached_extents,
+ hip->alloc_blocks - hip->cached_start,
+ start, len);
+ if (!res) {
+ hfsplus_dump_extent(hip->cached_extents);
+ hip->extent_state |= HFSPLUS_EXT_DIRTY;
+ hip->cached_blocks += len;
+ } else if (res == -ENOSPC)
+ goto insert_extent;
+ }
+out:
+ if (!res) {
+ hip->alloc_blocks += len;
+ mutex_unlock(&hip->extents_lock);
+ hfsplus_mark_inode_dirty(HFSPLUS_SB(sb)->alloc_file,
+ HFSPLUS_I_ALLOC_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
+ return 0;
+ }
+ mutex_unlock(&hip->extents_lock);
+ return res;
+
+insert_extent:
+ hfs_dbg("insert new extent\n");
+ res = hfsplus_ext_write_extent_locked(inode);
+ if (res)
+ goto out;
+
+ memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));
+ hip->cached_extents[0].start_block = cpu_to_be32(start);
+ hip->cached_extents[0].block_count = cpu_to_be32(len);
+ hfsplus_dump_extent(hip->cached_extents);
+ hip->extent_state |= HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW;
+ hip->cached_start = hip->alloc_blocks;
+ hip->cached_blocks = len;
+
+ res = 0;
+ goto out;
+}
+
+void hfsplus_file_truncate(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfs_find_data fd;
+ u32 alloc_cnt, blk_cnt, start;
+ int res;
+
+ hfs_dbg("ino %llu, phys_size %llu -> i_size %llu\n",
+ inode->i_ino, (long long)hip->phys_size, inode->i_size);
+
+ if (inode->i_size > hip->phys_size) {
+ struct address_space *mapping = inode->i_mapping;
+ struct folio *folio;
+ void *fsdata = NULL;
+ loff_t size = inode->i_size;
+
+ res = hfsplus_write_begin(NULL, mapping, size, 0,
+ &folio, &fsdata);
+ if (res)
+ return;
+ res = generic_write_end(NULL, mapping, size, 0, 0,
+ folio, fsdata);
+ if (res < 0)
+ return;
+ mark_inode_dirty(inode);
+ return;
+ } else if (inode->i_size == hip->phys_size)
+ return;
+
+ blk_cnt = (inode->i_size + HFSPLUS_SB(sb)->alloc_blksz - 1) >>
+ HFSPLUS_SB(sb)->alloc_blksz_shift;
+
+ mutex_lock(&hip->extents_lock);
+
+ alloc_cnt = hip->alloc_blocks;
+ if (blk_cnt == alloc_cnt)
+ goto out_unlock;
+
+ res = hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd);
+ if (res) {
+ mutex_unlock(&hip->extents_lock);
+ /* XXX: We lack error handling of hfsplus_file_truncate() */
+ return;
+ }
+
+ while (1) {
+ if (alloc_cnt == hip->first_blocks) {
+ mutex_unlock(&fd.tree->tree_lock);
+ hfsplus_free_extents(sb, hip->first_extents,
+ alloc_cnt, alloc_cnt - blk_cnt);
+ hfsplus_dump_extent(hip->first_extents);
+ hip->first_blocks = blk_cnt;
+ mutex_lock_nested(&fd.tree->tree_lock,
+ hfsplus_btree_lock_class(fd.tree));
+ break;
+ }
+ res = __hfsplus_ext_cache_extent(&fd, inode, alloc_cnt);
+ if (res)
+ break;
+
+ start = hip->cached_start;
+ if (blk_cnt <= start)
+ hfs_brec_remove(&fd);
+ mutex_unlock(&fd.tree->tree_lock);
+ hfsplus_free_extents(sb, hip->cached_extents,
+ alloc_cnt - start, alloc_cnt - blk_cnt);
+ hfsplus_dump_extent(hip->cached_extents);
+ mutex_lock_nested(&fd.tree->tree_lock,
+ hfsplus_btree_lock_class(fd.tree));
+ if (blk_cnt > start) {
+ hip->extent_state |= HFSPLUS_EXT_DIRTY;
+ break;
+ }
+ alloc_cnt = start;
+ hip->cached_start = hip->cached_blocks = 0;
+ hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
+ }
+ hfs_find_exit(&fd);
+
+ hip->alloc_blocks = blk_cnt;
+out_unlock:
+ mutex_unlock(&hip->extents_lock);
+ hip->phys_size = inode->i_size;
+ hip->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >>
+ sb->s_blocksize_bits;
+ inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits);
+ hfsplus_mark_inode_dirty(HFSPLUS_SB(sb)->alloc_file,
+ HFSPLUS_I_ALLOC_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ALLOC_DIRTY);
+}
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
new file mode 100644
index 000000000..1e5b58e6a
--- /dev/null
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -0,0 +1,706 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/include/linux/hfsplus_fs.h
+ *
+ * Copyright (C) 1999
+ * Brad Boyer (flar@pants.nu)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ */
+
+#ifndef _LINUX_HFSPLUS_FS_H
+#define _LINUX_HFSPLUS_FS_H
+
+#include <linux/fs.h>
+#include <linux/mutex.h>
+#include <linux/buffer_head.h>
+#include <linux/blkdev.h>
+#include <linux/fs_context.h>
+#include "hfsplus_raw.h"
+
+/* Runtime config options */
+#define HFSPLUS_DEF_CR_TYPE 0x3F3F3F3F /* '????' */
+
+#define HFSPLUS_TYPE_DATA 0x00
+#define HFSPLUS_TYPE_RSRC 0xFF
+
+typedef int (*btree_keycmp)(const hfsplus_btree_key *,
+ const hfsplus_btree_key *);
+
+#define NODE_HASH_SIZE 256
+
+/* B-tree mutex nested subclasses */
+enum hfsplus_btree_mutex_classes {
+ CATALOG_BTREE_MUTEX,
+ EXTENTS_BTREE_MUTEX,
+ ATTR_BTREE_MUTEX,
+};
+
+/* An HFS+ BTree held in memory */
+struct hfs_btree {
+ struct super_block *sb;
+ struct inode *inode;
+ btree_keycmp keycmp;
+
+ u32 cnid;
+ u32 root;
+ u32 leaf_count;
+ u32 leaf_head;
+ u32 leaf_tail;
+ u32 node_count;
+ u32 free_nodes;
+ u32 attributes;
+
+ unsigned int node_size;
+ unsigned int node_size_shift;
+ unsigned int max_key_len;
+ unsigned int depth;
+
+ struct mutex tree_lock;
+
+ unsigned int pages_per_bnode;
+ spinlock_t hash_lock;
+ struct hfs_bnode *node_hash[NODE_HASH_SIZE];
+ int node_hash_cnt;
+};
+
+struct page;
+
+/* An HFS+ BTree node in memory */
+struct hfs_bnode {
+ struct hfs_btree *tree;
+
+ u32 prev;
+ u32 this;
+ u32 next;
+ u32 parent;
+
+ u16 num_recs;
+ u8 type;
+ u8 height;
+
+ struct hfs_bnode *next_hash;
+ unsigned long flags;
+ wait_queue_head_t lock_wq;
+ atomic_t refcnt;
+ unsigned int page_offset;
+ struct page *page[];
+};
+
+#define HFS_BNODE_LOCK 0
+#define HFS_BNODE_ERROR 1
+#define HFS_BNODE_NEW 2
+#define HFS_BNODE_DIRTY 3
+#define HFS_BNODE_DELETED 4
+
+/*
+ * Attributes file states
+ */
+#define HFSPLUS_EMPTY_ATTR_TREE 0
+#define HFSPLUS_CREATING_ATTR_TREE 1
+#define HFSPLUS_VALID_ATTR_TREE 2
+#define HFSPLUS_FAILED_ATTR_TREE 3
+
+/*
+ * HFS+ superblock info (built from Volume Header on disk)
+ */
+
+struct hfsplus_vh;
+struct hfs_btree;
+
+struct hfsplus_sb_info {
+ void *s_vhdr_buf;
+ struct hfsplus_vh *s_vhdr;
+ void *s_backup_vhdr_buf;
+ struct hfsplus_vh *s_backup_vhdr;
+ struct hfs_btree *ext_tree;
+ struct hfs_btree *cat_tree;
+ struct hfs_btree *attr_tree;
+ atomic_t attr_tree_state;
+ struct inode *alloc_file;
+ struct inode *hidden_dir;
+ struct nls_table *nls;
+
+ /* Runtime variables */
+ u32 blockoffset;
+ u32 min_io_size;
+ sector_t part_start;
+ sector_t sect_count;
+ int fs_shift;
+
+ /* immutable data from the volume header */
+ u32 alloc_blksz;
+ int alloc_blksz_shift;
+ u32 total_blocks;
+ u32 data_clump_blocks, rsrc_clump_blocks;
+
+ /* mutable data from the volume header, protected by alloc_mutex */
+ u32 free_blocks;
+ struct mutex alloc_mutex;
+
+ /* mutable data from the volume header, protected by vh_mutex */
+ u32 next_cnid;
+ u32 file_count;
+ u32 folder_count;
+ struct mutex vh_mutex;
+
+ /* Config options */
+ u32 creator;
+ u32 type;
+
+ umode_t umask;
+ kuid_t uid;
+ kgid_t gid;
+
+ int part, session;
+ unsigned long flags;
+
+ int work_queued; /* non-zero delayed work is queued */
+ struct delayed_work sync_work; /* FS sync delayed work */
+ spinlock_t work_lock; /* protects sync_work and work_queued */
+ struct rcu_head rcu;
+};
+
+#define HFSPLUS_SB_WRITEBACKUP 0
+#define HFSPLUS_SB_NODECOMPOSE 1
+#define HFSPLUS_SB_FORCE 2
+#define HFSPLUS_SB_HFSX 3
+#define HFSPLUS_SB_CASEFOLD 4
+#define HFSPLUS_SB_NOBARRIER 5
+#define HFSPLUS_SB_UID 6
+#define HFSPLUS_SB_GID 7
+
+static inline struct hfsplus_sb_info *HFSPLUS_SB(struct super_block *sb)
+{
+ return sb->s_fs_info;
+}
+
+
+struct hfsplus_inode_info {
+ atomic_t opencnt;
+
+ /*
+ * Extent allocation information, protected by extents_lock.
+ */
+ u32 first_blocks;
+ u32 clump_blocks;
+ u32 alloc_blocks;
+ u32 cached_start;
+ u32 cached_blocks;
+ hfsplus_extent_rec first_extents;
+ hfsplus_extent_rec cached_extents;
+ unsigned int extent_state;
+ struct mutex extents_lock;
+
+ /*
+ * Immutable data.
+ */
+ struct inode *rsrc_inode;
+ __be32 create_date;
+
+ /*
+ * Protected by sbi->vh_mutex.
+ */
+ u32 linkid;
+
+ /*
+ * Accessed using atomic bitops.
+ */
+ unsigned long flags;
+
+ /*
+ * Protected by i_mutex.
+ */
+ sector_t fs_blocks;
+ u8 userflags; /* BSD user file flags */
+ u32 subfolders; /* Subfolder count (HFSX only) */
+ loff_t phys_size;
+
+ struct inode vfs_inode;
+};
+
+#define HFSPLUS_EXT_DIRTY 0x0001
+#define HFSPLUS_EXT_NEW 0x0002
+
+#define HFSPLUS_I_RSRC 0 /* represents a resource fork */
+#define HFSPLUS_I_CAT_DIRTY 1 /* has changes in the catalog tree */
+#define HFSPLUS_I_EXT_DIRTY 2 /* has changes in the extent tree */
+#define HFSPLUS_I_ALLOC_DIRTY 3 /* has changes in the allocation file */
+#define HFSPLUS_I_ATTR_DIRTY 4 /* has changes in the attributes tree */
+
+#define HFSPLUS_IS_RSRC(inode) \
+ test_bit(HFSPLUS_I_RSRC, &HFSPLUS_I(inode)->flags)
+
+static inline struct hfsplus_inode_info *HFSPLUS_I(struct inode *inode)
+{
+ return container_of(inode, struct hfsplus_inode_info, vfs_inode);
+}
+
+#define HFSPLUS_CAT_TREE_I(sb) \
+ HFSPLUS_SB(sb)->cat_tree->inode
+#define HFSPLUS_EXT_TREE_I(sb) \
+ HFSPLUS_SB(sb)->ext_tree->inode
+#define HFSPLUS_ATTR_TREE_I(sb) \
+ HFSPLUS_SB(sb)->attr_tree->inode
+
+/*
+ * Mark an inode dirty, and also mark the btree in which the
+ * specific type of metadata is stored.
+ * For data or metadata that gets written back by into the catalog btree
+ * by hfsplus_write_inode a plain mark_inode_dirty call is enough.
+ */
+static inline void hfsplus_mark_inode_dirty(struct inode *inode,
+ unsigned int flag)
+{
+ set_bit(flag, &HFSPLUS_I(inode)->flags);
+ mark_inode_dirty(inode);
+}
+
+struct hfs_find_data {
+ /* filled by caller */
+ hfsplus_btree_key *search_key;
+ hfsplus_btree_key *key;
+ /* filled by find */
+ struct hfs_btree *tree;
+ struct hfs_bnode *bnode;
+ /* filled by findrec */
+ int record;
+ int keyoffset, keylength;
+ int entryoffset, entrylength;
+};
+
+struct hfsplus_readdir_data {
+ loff_t pos;
+ struct hfsplus_cat_key key;
+};
+
+/*
+ * Find minimum acceptible I/O size for an hfsplus sb.
+ */
+static inline unsigned short hfsplus_min_io_size(struct super_block *sb)
+{
+ return max_t(unsigned short, HFSPLUS_SB(sb)->min_io_size,
+ HFSPLUS_SECTOR_SIZE);
+}
+
+#define hfs_btree_open hfsplus_btree_open
+#define hfs_btree_close hfsplus_btree_close
+#define hfs_btree_write hfsplus_btree_write
+#define hfs_bmap_reserve hfsplus_bmap_reserve
+#define hfs_bmap_alloc hfsplus_bmap_alloc
+#define hfs_bmap_free hfsplus_bmap_free
+#define hfs_bnode_read hfsplus_bnode_read
+#define hfs_bnode_read_u16 hfsplus_bnode_read_u16
+#define hfs_bnode_read_u8 hfsplus_bnode_read_u8
+#define hfs_bnode_read_key hfsplus_bnode_read_key
+#define hfs_bnode_write hfsplus_bnode_write
+#define hfs_bnode_write_u16 hfsplus_bnode_write_u16
+#define hfs_bnode_clear hfsplus_bnode_clear
+#define hfs_bnode_copy hfsplus_bnode_copy
+#define hfs_bnode_move hfsplus_bnode_move
+#define hfs_bnode_dump hfsplus_bnode_dump
+#define hfs_bnode_unlink hfsplus_bnode_unlink
+#define hfs_bnode_findhash hfsplus_bnode_findhash
+#define hfs_bnode_find hfsplus_bnode_find
+#define hfs_bnode_unhash hfsplus_bnode_unhash
+#define hfs_bnode_free hfsplus_bnode_free
+#define hfs_bnode_create hfsplus_bnode_create
+#define hfs_bnode_get hfsplus_bnode_get
+#define hfs_bnode_put hfsplus_bnode_put
+#define hfs_brec_lenoff hfsplus_brec_lenoff
+#define hfs_brec_keylen hfsplus_brec_keylen
+#define hfs_brec_insert hfsplus_brec_insert
+#define hfs_brec_remove hfsplus_brec_remove
+#define hfs_find_init hfsplus_find_init
+#define hfs_find_exit hfsplus_find_exit
+#define __hfs_brec_find __hfsplus_brec_find
+#define hfs_brec_find hfsplus_brec_find
+#define hfs_brec_read hfsplus_brec_read
+#define hfs_brec_goto hfsplus_brec_goto
+#define hfs_part_find hfsplus_part_find
+
+/*
+ * hfs+-specific ioctl for making the filesystem bootable
+ */
+#define HFSPLUS_IOC_BLESS _IO('h', 0x80)
+
+typedef int (*search_strategy_t)(struct hfs_bnode *,
+ struct hfs_find_data *,
+ int *, int *, int *);
+
+/*
+ * Functions in any *.c used in other files
+ */
+
+/* attributes.c */
+int __init hfsplus_create_attr_tree_cache(void);
+void hfsplus_destroy_attr_tree_cache(void);
+int hfsplus_attr_bin_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2);
+int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
+ u32 cnid, const char *name);
+hfsplus_attr_entry *hfsplus_alloc_attr_entry(void);
+void hfsplus_destroy_attr_entry(hfsplus_attr_entry *entry);
+int hfsplus_find_attr(struct super_block *sb, u32 cnid, const char *name,
+ struct hfs_find_data *fd);
+int hfsplus_attr_exists(struct inode *inode, const char *name);
+int hfsplus_create_attr(struct inode *inode, const char *name,
+ const void *value, size_t size);
+int hfsplus_delete_attr(struct inode *inode, const char *name);
+int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid);
+int hfsplus_replace_attr(struct inode *inode,
+ const char *name,
+ const void *value, size_t size);
+
+/* bitmap.c */
+int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset,
+ u32 *max);
+int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count);
+
+/* btree.c */
+u32 hfsplus_calc_btree_clump_size(u32 block_size, u32 node_size, u64 sectors,
+ int file_id);
+struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id);
+void hfs_btree_close(struct hfs_btree *tree);
+int hfs_btree_write(struct hfs_btree *tree);
+int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
+struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+void hfs_bmap_free(struct hfs_bnode *node);
+
+/* bnode.c */
+void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
+u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
+u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
+void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
+void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
+void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
+void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
+void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
+ struct hfs_bnode *src_node, u32 src, u32 len);
+void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
+void hfs_bnode_dump(struct hfs_bnode *node);
+void hfs_bnode_unlink(struct hfs_bnode *node);
+struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
+void hfs_bnode_unhash(struct hfs_bnode *node);
+struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num);
+void hfs_bnode_free(struct hfs_bnode *node);
+struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num);
+void hfs_bnode_get(struct hfs_bnode *node);
+void hfs_bnode_put(struct hfs_bnode *node);
+bool hfs_bnode_need_zeroout(struct hfs_btree *tree);
+
+/* brec.c */
+u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
+u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
+int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
+int hfs_brec_remove(struct hfs_find_data *fd);
+
+/* bfind.c */
+int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd);
+void hfs_find_exit(struct hfs_find_data *fd);
+int hfs_find_1st_rec_by_cnid(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+ int *begin, int *end, int *cur_rec);
+int hfs_find_rec_by_key(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+ int *begin, int *end, int *cur_rec);
+int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
+ search_strategy_t rec_found);
+int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare);
+int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
+int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+
+/* catalog.c */
+int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2);
+int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2);
+int hfsplus_cat_build_key(struct super_block *sb, hfsplus_btree_key *key,
+ u32 parent, const struct qstr *str);
+void hfsplus_cat_build_key_with_cnid(struct super_block *sb,
+ hfsplus_btree_key *key, u32 parent);
+void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms);
+int hfsplus_find_cat(struct super_block *sb, u32 cnid,
+ struct hfs_find_data *fd);
+int hfsplus_create_cat(u32 cnid, struct inode *dir, const struct qstr *str,
+ struct inode *inode);
+int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str);
+int hfsplus_rename_cat(u32 cnid, struct inode *src_dir, const struct qstr *src_name,
+ struct inode *dst_dir, const struct qstr *dst_name);
+
+/* dir.c */
+extern const struct inode_operations hfsplus_dir_inode_operations;
+extern const struct file_operations hfsplus_dir_operations;
+
+/* extents.c */
+int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
+ const hfsplus_btree_key *k2);
+int hfsplus_ext_write_extent(struct inode *inode);
+int hfsplus_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create);
+int hfsplus_free_fork(struct super_block *sb, u32 cnid,
+ struct hfsplus_fork_raw *fork, int type);
+int hfsplus_file_extend(struct inode *inode, bool zeroout);
+void hfsplus_file_truncate(struct inode *inode);
+
+/* inode.c */
+extern const struct address_space_operations hfsplus_aops;
+extern const struct address_space_operations hfsplus_btree_aops;
+extern const struct dentry_operations hfsplus_dentry_operations;
+
+int hfsplus_write_begin(const struct kiocb *iocb,
+ struct address_space *mapping,
+ loff_t pos, unsigned len, struct folio **foliop,
+ void **fsdata);
+struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,
+ umode_t mode);
+void hfsplus_delete_inode(struct inode *inode);
+void hfsplus_inode_read_fork(struct inode *inode,
+ struct hfsplus_fork_raw *fork);
+void hfsplus_inode_write_fork(struct inode *inode,
+ struct hfsplus_fork_raw *fork);
+int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd);
+int hfsplus_cat_write_inode(struct inode *inode);
+int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *stat, u32 request_mask,
+ unsigned int query_flags);
+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
+ int datasync);
+int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+int hfsplus_fileattr_set(struct mnt_idmap *idmap,
+ struct dentry *dentry, struct file_kattr *fa);
+
+/* ioctl.c */
+long hfsplus_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+
+/* options.c */
+void hfsplus_fill_defaults(struct hfsplus_sb_info *opts);
+int hfsplus_parse_param(struct fs_context *fc, struct fs_parameter *param);
+int hfsplus_show_options(struct seq_file *seq, struct dentry *root);
+
+/* part_tbl.c */
+int hfs_part_find(struct super_block *sb, sector_t *part_start,
+ sector_t *part_size);
+
+/* super.c */
+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);
+int hfsplus_commit_superblock(struct super_block *sb);
+
+/* tables.c */
+extern u16 hfsplus_case_fold_table[];
+extern u16 hfsplus_decompose_table[];
+extern u16 hfsplus_compose_table[];
+
+/* unicode.c */
+int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
+ const struct hfsplus_unistr *s2);
+int hfsplus_strcmp(const struct hfsplus_unistr *s1,
+ const struct hfsplus_unistr *s2);
+int hfsplus_uni2asc_str(struct super_block *sb,
+ const struct hfsplus_unistr *ustr, char *astr,
+ int *len_p);
+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 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);
+
+/* wrapper.c */
+int hfsplus_submit_bio(struct super_block *sb, sector_t sector, void *buf,
+ void **data, blk_opf_t opf);
+int hfsplus_read_wrapper(struct super_block *sb);
+
+static inline u32 hfsplus_cat_thread_size(const struct hfsplus_cat_thread *thread)
+{
+ return offsetof(struct hfsplus_cat_thread, nodeName) +
+ offsetof(struct hfsplus_unistr, unicode) +
+ be16_to_cpu(thread->nodeName.length) * sizeof(hfsplus_unichr);
+}
+
+static inline
+bool is_hfs_thread_record_type(u16 type)
+{
+ return type == HFSPLUS_FOLDER_THREAD || type == HFSPLUS_FILE_THREAD;
+}
+
+int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry);
+
+/*
+ * time helpers: convert between 1904-base and 1970-base timestamps
+ *
+ * HFS+ implementations are highly inconsistent, this one matches the
+ * traditional behavior of 64-bit Linux, giving the most useful
+ * time range between 1970 and 2106, by treating any on-disk timestamp
+ * under HFSPLUS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106.
+ */
+#define HFSPLUS_UTC_OFFSET 2082844800U
+
+static inline time64_t __hfsp_mt2ut(__be32 mt)
+{
+ time64_t ut = (u32)(be32_to_cpu(mt) - HFSPLUS_UTC_OFFSET);
+
+ return ut;
+}
+
+static inline __be32 __hfsp_ut2mt(time64_t ut)
+{
+ return cpu_to_be32(lower_32_bits(ut) + HFSPLUS_UTC_OFFSET);
+}
+
+static inline enum hfsplus_btree_mutex_classes
+hfsplus_btree_lock_class(struct hfs_btree *tree)
+{
+ enum hfsplus_btree_mutex_classes class;
+
+ switch (tree->cnid) {
+ case HFSPLUS_CAT_CNID:
+ class = CATALOG_BTREE_MUTEX;
+ break;
+ case HFSPLUS_EXT_CNID:
+ class = EXTENTS_BTREE_MUTEX;
+ break;
+ case HFSPLUS_ATTR_CNID:
+ class = ATTR_BTREE_MUTEX;
+ break;
+ default:
+ BUG();
+ }
+ return class;
+}
+
+static inline
+bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
+{
+ bool is_valid;
+
+ if (!node || !node->tree)
+ return false;
+
+ is_valid = off < node->tree->node_size;
+
+ if (!is_valid) {
+ pr_err("requested invalid offset: "
+ "NODE: id %u, type %#x, height %u, "
+ "node_size %u, offset %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off);
+ }
+
+ return is_valid;
+}
+
+static inline
+bool hfs_bnode_num_recs_invalid(struct hfs_bnode *node)
+{
+ u32 node_size;
+ u32 table_size;
+ u32 area_size;
+ u32 rec_size = sizeof(__be16);
+ u32 desc_size = sizeof(struct hfs_bnode_desc);
+
+ if (!node || !node->tree)
+ return true;
+
+ node_size = node->tree->node_size;
+ if (node_size < desc_size)
+ return true;
+
+ area_size = node_size - desc_size;
+ table_size = ((u32)node->num_recs + 1) * rec_size;
+
+ return table_size > area_size;
+}
+
+static inline
+bool hfs_brec_record_invalid(struct hfs_bnode *node, int record)
+{
+ if (hfs_bnode_num_recs_invalid(node))
+ return true;
+ if (record < 0)
+ return true;
+
+ return record >= node->num_recs;
+}
+
+static inline
+bool hfs_brec_offsets_invalid(struct hfs_bnode *node, u16 off, u16 next_off)
+{
+ u32 table_size;
+ u32 table_start;
+ u32 rec_size = sizeof(__be16);
+ u32 desc_size = sizeof(struct hfs_bnode_desc);
+
+ if (!node || !node->tree)
+ return true;
+
+ if (off < desc_size || (off & 1))
+ return true;
+
+ if (next_off <= off ||
+ next_off > node->tree->node_size ||
+ (next_off & 1))
+ return true;
+
+ table_size = ((u32)node->num_recs + 1) * rec_size;
+ table_start = node->tree->node_size - table_size;
+ if (next_off > table_start)
+ return true;
+
+ return false;
+}
+
+static inline
+bool hfs_brec_len_invalid(struct hfs_bnode *node, u16 len)
+{
+ if (!node || !node->tree)
+ return true;
+
+ return len == 0 || len > node->tree->node_size;
+}
+
+static inline
+void hfs_find_result_init(struct hfs_find_data *fd)
+{
+ fd->record = -1;
+ fd->keyoffset = -1;
+ fd->keylength = -1;
+ fd->entryoffset = -1;
+ fd->entrylength = -1;
+}
+
+static inline
+u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
+{
+ unsigned int node_size;
+
+ if (!is_bnode_offset_valid(node, off))
+ return 0;
+
+ node_size = node->tree->node_size;
+
+ if ((u64)off + len > node_size) {
+ u32 new_len = node_size - off;
+
+ pr_err("requested length has been corrected: "
+ "NODE: id %u, type %#x, height %u, "
+ "node_size %u, offset %u, "
+ "requested_len %u, corrected_len %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off, len, new_len);
+
+ return new_len;
+ }
+
+ return len;
+}
+
+/* compatibility */
+#define hfsp_mt2ut(t) (struct timespec64){ .tv_sec = __hfsp_mt2ut(t) }
+#define hfsp_ut2mt(t) __hfsp_ut2mt((t).tv_sec)
+#define hfsp_now2mt() __hfsp_ut2mt(ktime_get_real_seconds())
+
+#endif
diff --git a/fs/hfsplus/hfsplus_raw.h b/fs/hfsplus/hfsplus_raw.h
new file mode 100644
index 000000000..83b5dbde9
--- /dev/null
+++ b/fs/hfsplus/hfsplus_raw.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/include/linux/hfsplus_raw.h
+ *
+ * Copyright (C) 1999
+ * Brad Boyer (flar@pants.nu)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Format of structures on disk
+ * Information taken from Apple Technote #1150 (HFS Plus Volume Format)
+ *
+ */
+
+#ifndef _LINUX_HFSPLUS_RAW_H
+#define _LINUX_HFSPLUS_RAW_H
+
+#include <linux/types.h>
+#include <linux/hfs_common.h>
+
+#endif
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
new file mode 100644
index 000000000..2ce6de574
--- /dev/null
+++ b/fs/hfsplus/inode.c
@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/inode.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Inode handling routines
+ */
+
+#include <linux/blkdev.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/mpage.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+#include <linux/uio.h>
+#include <linux/fileattr.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+#include "xattr.h"
+
+static int hfsplus_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, hfsplus_get_block);
+}
+
+static void hfsplus_write_failed(struct address_space *mapping, loff_t to)
+{
+ struct inode *inode = mapping->host;
+
+ if (to > inode->i_size) {
+ truncate_pagecache(inode, inode->i_size);
+ hfsplus_file_truncate(inode);
+ }
+}
+
+int hfsplus_write_begin(const struct kiocb *iocb,
+ struct address_space *mapping, loff_t pos,
+ unsigned len, struct folio **foliop,
+ void **fsdata)
+{
+ struct inode *inode = mapping->host;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ loff_t total_capacity;
+ int ret;
+
+ total_capacity = (loff_t)sbi->total_blocks << sbi->alloc_blksz_shift;
+
+ if (pos >= total_capacity)
+ return -EFBIG;
+
+ ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
+ hfsplus_get_block,
+ &HFSPLUS_I(inode)->phys_size);
+ if (unlikely(ret))
+ hfsplus_write_failed(mapping, pos + len);
+
+ return ret;
+}
+
+static sector_t hfsplus_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping, block, hfsplus_get_block);
+}
+
+static bool hfsplus_release_folio(struct folio *folio, gfp_t mask)
+{
+ struct inode *inode = folio->mapping->host;
+ struct super_block *sb = inode->i_sb;
+ struct hfs_btree *tree;
+ struct hfs_bnode *node;
+ u32 nidx;
+ int i;
+ bool res = true;
+
+ switch (inode->i_ino) {
+ case HFSPLUS_EXT_CNID:
+ tree = HFSPLUS_SB(sb)->ext_tree;
+ break;
+ case HFSPLUS_CAT_CNID:
+ tree = HFSPLUS_SB(sb)->cat_tree;
+ break;
+ case HFSPLUS_ATTR_CNID:
+ tree = HFSPLUS_SB(sb)->attr_tree;
+ break;
+ default:
+ BUG();
+ return false;
+ }
+ if (!tree)
+ return false;
+ if (tree->node_size >= PAGE_SIZE) {
+ nidx = folio->index >>
+ (tree->node_size_shift - PAGE_SHIFT);
+ spin_lock(&tree->hash_lock);
+ node = hfs_bnode_findhash(tree, nidx);
+ if (!node)
+ ;
+ else if (atomic_read(&node->refcnt))
+ res = false;
+ if (res && node) {
+ hfs_bnode_unhash(node);
+ hfs_bnode_free(node);
+ }
+ spin_unlock(&tree->hash_lock);
+ } else {
+ nidx = folio->index <<
+ (PAGE_SHIFT - tree->node_size_shift);
+ i = 1 << (PAGE_SHIFT - tree->node_size_shift);
+ spin_lock(&tree->hash_lock);
+ do {
+ node = hfs_bnode_findhash(tree, nidx++);
+ if (!node)
+ continue;
+ if (atomic_read(&node->refcnt)) {
+ res = false;
+ break;
+ }
+ hfs_bnode_unhash(node);
+ hfs_bnode_free(node);
+ } while (--i && nidx < tree->node_count);
+ spin_unlock(&tree->hash_lock);
+ }
+ return res ? try_to_free_buffers(folio) : false;
+}
+
+static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+{
+ struct file *file = iocb->ki_filp;
+ struct address_space *mapping = file->f_mapping;
+ struct inode *inode = mapping->host;
+ loff_t isize;
+ size_t count = iov_iter_count(iter);
+ loff_t end = iocb->ki_pos + count;
+ ssize_t ret;
+
+ /*
+ * The hfsplus_get_block() only allows creating the next sequential block.
+ * For direct writes beyond EOF, expand the file first.
+ */
+ if (iov_iter_rw(iter) == WRITE && iocb->ki_pos > i_size_read(inode)) {
+ loff_t start_off, end_off;
+ loff_t start_page, end_page;
+
+ isize = i_size_read(inode);
+
+ /*
+ * Wait for any in-flight DIO on this inode to finish before
+ * calling generic_cont_expand_simple().
+ */
+ inode_dio_wait(inode);
+
+ ret = generic_cont_expand_simple(inode, iocb->ki_pos);
+ if (ret)
+ return ret;
+
+ start_off = isize;
+ end_off = (end > 0) ? end - 1 : end;
+
+ ret = filemap_write_and_wait_range(mapping, start_off, end_off);
+ if (ret)
+ return ret;
+
+ start_page = start_off >> PAGE_SHIFT;
+ end_page = end_off >> PAGE_SHIFT;
+
+ invalidate_inode_pages2_range(mapping, start_page, end_page);
+ }
+
+ ret = blockdev_direct_IO(iocb, inode, iter, hfsplus_get_block);
+
+ /*
+ * In case of error extending write may have instantiated a few
+ * blocks outside i_size. Trim these off again.
+ */
+ if (unlikely(iov_iter_rw(iter) == WRITE && ret < 0)) {
+ isize = i_size_read(inode);
+
+ if (end > isize)
+ hfsplus_write_failed(mapping, end);
+ }
+
+ return ret;
+}
+
+static int hfsplus_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, hfsplus_get_block);
+}
+
+const struct address_space_operations hfsplus_btree_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = hfsplus_read_folio,
+ .writepages = hfsplus_writepages,
+ .write_begin = hfsplus_write_begin,
+ .write_end = generic_write_end,
+ .migrate_folio = buffer_migrate_folio,
+ .bmap = hfsplus_bmap,
+ .release_folio = hfsplus_release_folio,
+};
+
+const struct address_space_operations hfsplus_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = hfsplus_read_folio,
+ .write_begin = hfsplus_write_begin,
+ .write_end = generic_write_end,
+ .bmap = hfsplus_bmap,
+ .direct_IO = hfsplus_direct_IO,
+ .writepages = hfsplus_writepages,
+ .migrate_folio = buffer_migrate_folio,
+};
+
+const struct dentry_operations hfsplus_dentry_operations = {
+ .d_hash = hfsplus_hash_dentry,
+ .d_compare = hfsplus_compare_dentry,
+};
+
+static int hfsplus_get_perms(struct inode *inode,
+ struct hfsplus_perm *perms, int dir)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ u16 mode;
+
+ mode = be16_to_cpu(perms->mode);
+ if (dir) {
+ if (mode && !S_ISDIR(mode))
+ goto bad_type;
+ } else if (mode) {
+ switch (mode & S_IFMT) {
+ case S_IFREG:
+ case S_IFLNK:
+ case S_IFCHR:
+ case S_IFBLK:
+ case S_IFIFO:
+ case S_IFSOCK:
+ break;
+ default:
+ goto bad_type;
+ }
+ }
+
+ i_uid_write(inode, be32_to_cpu(perms->owner));
+ if ((test_bit(HFSPLUS_SB_UID, &sbi->flags)) || (!i_uid_read(inode) && !mode))
+ inode->i_uid = sbi->uid;
+
+ i_gid_write(inode, be32_to_cpu(perms->group));
+ if ((test_bit(HFSPLUS_SB_GID, &sbi->flags)) || (!i_gid_read(inode) && !mode))
+ inode->i_gid = sbi->gid;
+
+ if (dir) {
+ mode = mode ? (mode & S_IALLUGO) : (S_IRWXUGO & ~(sbi->umask));
+ mode |= S_IFDIR;
+ } else if (!mode)
+ mode = S_IFREG | ((S_IRUGO|S_IWUGO) & ~(sbi->umask));
+ inode->i_mode = mode;
+
+ HFSPLUS_I(inode)->userflags = perms->userflags;
+ if (perms->rootflags & HFSPLUS_FLG_IMMUTABLE)
+ inode->i_flags |= S_IMMUTABLE;
+ else
+ inode->i_flags &= ~S_IMMUTABLE;
+ if (perms->rootflags & HFSPLUS_FLG_APPEND)
+ inode->i_flags |= S_APPEND;
+ else
+ inode->i_flags &= ~S_APPEND;
+ return 0;
+bad_type:
+ pr_err("invalid file type 0%04o for inode %llu\n", mode, inode->i_ino);
+ return -EIO;
+}
+
+static int hfsplus_file_open(struct inode *inode, struct file *file)
+{
+ if (HFSPLUS_IS_RSRC(inode))
+ inode = HFSPLUS_I(inode)->rsrc_inode;
+ if (!(file->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
+ return -EOVERFLOW;
+ atomic_inc(&HFSPLUS_I(inode)->opencnt);
+ return 0;
+}
+
+static int hfsplus_file_release(struct inode *inode, struct file *file)
+{
+ struct super_block *sb = inode->i_sb;
+
+ if (HFSPLUS_IS_RSRC(inode))
+ inode = HFSPLUS_I(inode)->rsrc_inode;
+ if (atomic_dec_and_test(&HFSPLUS_I(inode)->opencnt)) {
+ inode_lock(inode);
+ hfsplus_file_truncate(inode);
+ if (inode->i_flags & S_DEAD) {
+ hfsplus_delete_cat(inode->i_ino,
+ HFSPLUS_SB(sb)->hidden_dir, NULL);
+ hfsplus_delete_inode(inode);
+ }
+ inode_unlock(inode);
+ }
+ return 0;
+}
+
+static int hfsplus_setattr(struct mnt_idmap *idmap,
+ struct dentry *dentry, struct iattr *attr)
+{
+ struct inode *inode = d_inode(dentry);
+ int error;
+
+ error = setattr_prepare(&nop_mnt_idmap, dentry, attr);
+ if (error)
+ return error;
+
+ if ((attr->ia_valid & ATTR_SIZE) &&
+ attr->ia_size != i_size_read(inode)) {
+ inode_dio_wait(inode);
+ if (attr->ia_size > inode->i_size) {
+ error = generic_cont_expand_simple(inode,
+ attr->ia_size);
+ if (error)
+ return error;
+ }
+ truncate_setsize(inode, attr->ia_size);
+ hfsplus_file_truncate(inode);
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ }
+
+ setattr_copy(&nop_mnt_idmap, inode, attr);
+ mark_inode_dirty(inode);
+
+ return 0;
+}
+
+int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+{
+ struct inode *inode = d_inode(path->dentry);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+
+ if (request_mask & STATX_BTIME) {
+ stat->result_mask |= STATX_BTIME;
+ stat->btime = hfsp_mt2ut(hip->create_date);
+ }
+
+ if (inode->i_flags & S_APPEND)
+ stat->attributes |= STATX_ATTR_APPEND;
+ if (inode->i_flags & S_IMMUTABLE)
+ stat->attributes |= STATX_ATTR_IMMUTABLE;
+ if (hip->userflags & HFSPLUS_FLG_NODUMP)
+ stat->attributes |= STATX_ATTR_NODUMP;
+
+ stat->attributes_mask |= STATX_ATTR_APPEND | STATX_ATTR_IMMUTABLE |
+ STATX_ATTR_NODUMP;
+
+ generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
+ return 0;
+}
+
+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
+ int datasync)
+{
+ struct inode *inode = file->f_mapping->host;
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+ int error = 0, error2;
+
+ hfs_dbg("inode->i_ino %llu, start %llu, end %llu\n",
+ inode->i_ino, start, end);
+
+ error = file_write_and_wait_range(file, start, end);
+ if (error)
+ return error;
+ inode_lock(inode);
+
+ /*
+ * Sync inode metadata into the catalog and extent trees.
+ */
+ sync_inode_metadata(inode, 1);
+
+ /*
+ * And explicitly write out the btrees.
+ */
+ if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags);
+ error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping);
+ }
+
+ if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
+ error2 =
+ filemap_write_and_wait(sbi->ext_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ }
+
+ if (sbi->attr_tree) {
+ if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,
+ &HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags);
+ error2 =
+ filemap_write_and_wait(
+ sbi->attr_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ }
+ } else {
+ if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags))
+ pr_err("sync non-existent attributes tree\n");
+ }
+
+ if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,
+ &HFSPLUS_I(sbi->alloc_file)->flags)) {
+ clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags);
+ error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
+ if (!error)
+ error = error2;
+ }
+
+ mutex_lock(&sbi->vh_mutex);
+ hfsplus_prepare_volume_header_for_commit(vhdr);
+ mutex_unlock(&sbi->vh_mutex);
+
+ error2 = hfsplus_commit_superblock(inode->i_sb);
+ if (!error)
+ error = error2;
+
+ if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
+ blkdev_issue_flush(inode->i_sb->s_bdev);
+
+ inode_unlock(inode);
+
+ return error;
+}
+
+static const struct inode_operations hfsplus_file_inode_operations = {
+ .setattr = hfsplus_setattr,
+ .getattr = hfsplus_getattr,
+ .listxattr = hfsplus_listxattr,
+ .fileattr_get = hfsplus_fileattr_get,
+ .fileattr_set = hfsplus_fileattr_set,
+};
+
+static const struct inode_operations hfsplus_symlink_inode_operations = {
+ .get_link = page_get_link,
+ .setattr = hfsplus_setattr,
+ .getattr = hfsplus_getattr,
+ .listxattr = hfsplus_listxattr,
+};
+
+static const struct inode_operations hfsplus_special_inode_operations = {
+ .setattr = hfsplus_setattr,
+ .getattr = hfsplus_getattr,
+ .listxattr = hfsplus_listxattr,
+};
+
+static const struct file_operations hfsplus_file_operations = {
+ .llseek = generic_file_llseek,
+ .read_iter = generic_file_read_iter,
+ .write_iter = generic_file_write_iter,
+ .mmap_prepare = generic_file_mmap_prepare,
+ .splice_read = filemap_splice_read,
+ .splice_write = iter_file_splice_write,
+ .fsync = hfsplus_file_fsync,
+ .open = hfsplus_file_open,
+ .release = hfsplus_file_release,
+ .unlocked_ioctl = hfsplus_ioctl,
+};
+
+struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,
+ umode_t mode)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct inode *inode = new_inode(sb);
+ struct hfsplus_inode_info *hip;
+
+ if (!inode)
+ return NULL;
+
+ inode->i_ino = sbi->next_cnid++;
+ inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
+ set_nlink(inode, 1);
+ simple_inode_init_ts(inode);
+
+ hip = HFSPLUS_I(inode);
+ mutex_init(&hip->extents_lock);
+ atomic_set(&hip->opencnt, 0);
+ hip->extent_state = 0;
+ hip->flags = 0;
+ hip->userflags = 0;
+ hip->subfolders = 0;
+ memset(hip->first_extents, 0, sizeof(hfsplus_extent_rec));
+ memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));
+ hip->alloc_blocks = 0;
+ hip->first_blocks = 0;
+ hip->cached_start = 0;
+ hip->cached_blocks = 0;
+ hip->phys_size = 0;
+ hip->fs_blocks = 0;
+ hip->rsrc_inode = NULL;
+ if (S_ISDIR(inode->i_mode)) {
+ inode->i_size = 2;
+ sbi->folder_count++;
+ inode->i_op = &hfsplus_dir_inode_operations;
+ inode->i_fop = &hfsplus_dir_operations;
+ } else if (S_ISREG(inode->i_mode)) {
+ sbi->file_count++;
+ inode->i_op = &hfsplus_file_inode_operations;
+ inode->i_fop = &hfsplus_file_operations;
+ inode->i_mapping->a_ops = &hfsplus_aops;
+ hip->clump_blocks = sbi->data_clump_blocks;
+ } else if (S_ISLNK(inode->i_mode)) {
+ sbi->file_count++;
+ inode->i_op = &hfsplus_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mapping->a_ops = &hfsplus_aops;
+ hip->clump_blocks = 1;
+ } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
+ S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
+ sbi->file_count++;
+ inode->i_op = &hfsplus_special_inode_operations;
+ } else
+ sbi->file_count++;
+
+ insert_inode_hash(inode);
+ mark_inode_dirty(inode);
+ hfsplus_mark_mdb_dirty(sb);
+
+ return inode;
+}
+
+void hfsplus_delete_inode(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+
+ if (S_ISDIR(inode->i_mode)) {
+ HFSPLUS_SB(sb)->folder_count--;
+ hfsplus_mark_mdb_dirty(sb);
+ return;
+ }
+ HFSPLUS_SB(sb)->file_count--;
+ if (S_ISREG(inode->i_mode)) {
+ if (!inode->i_nlink) {
+ inode->i_size = 0;
+ hfsplus_file_truncate(inode);
+ }
+ } else if (S_ISLNK(inode->i_mode)) {
+ inode->i_size = 0;
+ hfsplus_file_truncate(inode);
+ }
+ hfsplus_mark_mdb_dirty(sb);
+}
+
+void hfsplus_inode_read_fork(struct inode *inode, struct hfsplus_fork_raw *fork)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ u32 count;
+ int i;
+
+ memcpy(&hip->first_extents, &fork->extents, sizeof(hfsplus_extent_rec));
+ for (count = 0, i = 0; i < 8; i++)
+ count += be32_to_cpu(fork->extents[i].block_count);
+ hip->first_blocks = count;
+ memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));
+ hip->cached_start = 0;
+ hip->cached_blocks = 0;
+
+ hip->alloc_blocks = be32_to_cpu(fork->total_blocks);
+ hip->phys_size = inode->i_size = be64_to_cpu(fork->total_size);
+ hip->fs_blocks =
+ (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
+ inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits);
+ hip->clump_blocks =
+ be32_to_cpu(fork->clump_size) >> sbi->alloc_blksz_shift;
+ if (!hip->clump_blocks) {
+ hip->clump_blocks = HFSPLUS_IS_RSRC(inode) ?
+ sbi->rsrc_clump_blocks :
+ sbi->data_clump_blocks;
+ }
+}
+
+void hfsplus_inode_write_fork(struct inode *inode,
+ struct hfsplus_fork_raw *fork)
+{
+ memcpy(&fork->extents, &HFSPLUS_I(inode)->first_extents,
+ sizeof(hfsplus_extent_rec));
+ fork->total_size = cpu_to_be64(inode->i_size);
+ fork->total_blocks = cpu_to_be32(HFSPLUS_I(inode)->alloc_blocks);
+}
+
+int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
+{
+ hfsplus_cat_entry entry;
+ int res = 0;
+ u16 type;
+
+ type = hfs_bnode_read_u16(fd->bnode, fd->entryoffset);
+
+ HFSPLUS_I(inode)->linkid = 0;
+ if (type == HFSPLUS_FOLDER) {
+ struct hfsplus_cat_folder *folder = &entry.folder;
+
+ if (fd->entrylength < sizeof(struct hfsplus_cat_folder)) {
+ pr_err("bad catalog folder entry\n");
+ res = -EIO;
+ goto out;
+ }
+ hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,
+ sizeof(struct hfsplus_cat_folder));
+ res = hfsplus_get_perms(inode, &folder->permissions, 1);
+ if (res)
+ goto out;
+ set_nlink(inode, 1);
+ inode->i_size = 2 + be32_to_cpu(folder->valence);
+ inode_set_atime_to_ts(inode, hfsp_mt2ut(folder->access_date));
+ inode_set_mtime_to_ts(inode,
+ hfsp_mt2ut(folder->content_mod_date));
+ inode_set_ctime_to_ts(inode,
+ hfsp_mt2ut(folder->attribute_mod_date));
+ HFSPLUS_I(inode)->create_date = folder->create_date;
+ HFSPLUS_I(inode)->fs_blocks = 0;
+ if (folder->flags & cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT)) {
+ HFSPLUS_I(inode)->subfolders =
+ be32_to_cpu(folder->subfolders);
+ }
+ inode->i_op = &hfsplus_dir_inode_operations;
+ inode->i_fop = &hfsplus_dir_operations;
+ } else if (type == HFSPLUS_FILE) {
+ struct hfsplus_cat_file *file = &entry.file;
+
+ if (fd->entrylength < sizeof(struct hfsplus_cat_file)) {
+ pr_err("bad catalog file entry\n");
+ res = -EIO;
+ goto out;
+ }
+ hfs_bnode_read(fd->bnode, &entry, fd->entryoffset,
+ sizeof(struct hfsplus_cat_file));
+
+ hfsplus_inode_read_fork(inode, HFSPLUS_IS_RSRC(inode) ?
+ &file->rsrc_fork : &file->data_fork);
+ res = hfsplus_get_perms(inode, &file->permissions, 0);
+ if (res)
+ goto out;
+ set_nlink(inode, 1);
+ if (S_ISREG(inode->i_mode)) {
+ if (file->permissions.dev)
+ set_nlink(inode,
+ be32_to_cpu(file->permissions.dev));
+ inode->i_op = &hfsplus_file_inode_operations;
+ inode->i_fop = &hfsplus_file_operations;
+ inode->i_mapping->a_ops = &hfsplus_aops;
+ } else if (S_ISLNK(inode->i_mode)) {
+ inode->i_op = &hfsplus_symlink_inode_operations;
+ inode_nohighmem(inode);
+ inode->i_mapping->a_ops = &hfsplus_aops;
+ } else {
+ inode->i_op = &hfsplus_special_inode_operations;
+ init_special_inode(inode, inode->i_mode,
+ be32_to_cpu(file->permissions.dev));
+ }
+ inode_set_atime_to_ts(inode, hfsp_mt2ut(file->access_date));
+ inode_set_mtime_to_ts(inode,
+ hfsp_mt2ut(file->content_mod_date));
+ inode_set_ctime_to_ts(inode,
+ hfsp_mt2ut(file->attribute_mod_date));
+ HFSPLUS_I(inode)->create_date = file->create_date;
+ } else {
+ pr_err("bad catalog entry used to create inode\n");
+ res = -EIO;
+ }
+out:
+ return res;
+}
+
+int hfsplus_cat_write_inode(struct inode *inode)
+{
+ struct inode *main_inode = inode;
+ struct hfs_btree *tree = HFSPLUS_SB(inode->i_sb)->cat_tree;
+ struct hfs_find_data fd;
+ hfsplus_cat_entry entry;
+ int res = 0;
+
+ hfs_dbg("inode->i_ino %llu\n", inode->i_ino);
+
+ if (HFSPLUS_IS_RSRC(inode))
+ main_inode = HFSPLUS_I(inode)->rsrc_inode;
+
+ if (!main_inode->i_nlink)
+ return 0;
+
+ if (hfs_find_init(tree, &fd))
+ /* panic? */
+ return -EIO;
+
+ if (hfsplus_find_cat(main_inode->i_sb, main_inode->i_ino, &fd))
+ /* panic? */
+ goto out;
+
+ if (S_ISDIR(main_inode->i_mode)) {
+ struct hfsplus_cat_folder *folder = &entry.folder;
+
+ if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
+ pr_err("bad catalog folder entry\n");
+ res = -EIO;
+ goto out;
+ }
+ hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_folder));
+ /* simple node checks? */
+ hfsplus_cat_set_perms(inode, &folder->permissions);
+ folder->access_date = hfsp_ut2mt(inode_get_atime(inode));
+ folder->content_mod_date = hfsp_ut2mt(inode_get_mtime(inode));
+ folder->attribute_mod_date = hfsp_ut2mt(inode_get_ctime(inode));
+ folder->valence = cpu_to_be32(inode->i_size - 2);
+ if (folder->flags & cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT)) {
+ folder->subfolders =
+ cpu_to_be32(HFSPLUS_I(inode)->subfolders);
+ }
+ hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_folder));
+ } else if (HFSPLUS_IS_RSRC(inode)) {
+ struct hfsplus_cat_file *file = &entry.file;
+ hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_file));
+ hfsplus_inode_write_fork(inode, &file->rsrc_fork);
+ hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_file));
+ } else {
+ struct hfsplus_cat_file *file = &entry.file;
+
+ if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
+ pr_err("bad catalog file entry\n");
+ res = -EIO;
+ goto out;
+ }
+ hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_file));
+ hfsplus_inode_write_fork(inode, &file->data_fork);
+ hfsplus_cat_set_perms(inode, &file->permissions);
+ if (HFSPLUS_FLG_IMMUTABLE &
+ (file->permissions.rootflags |
+ file->permissions.userflags))
+ file->flags |= cpu_to_be16(HFSPLUS_FILE_LOCKED);
+ else
+ file->flags &= cpu_to_be16(~HFSPLUS_FILE_LOCKED);
+ file->access_date = hfsp_ut2mt(inode_get_atime(inode));
+ file->content_mod_date = hfsp_ut2mt(inode_get_mtime(inode));
+ file->attribute_mod_date = hfsp_ut2mt(inode_get_ctime(inode));
+ hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,
+ sizeof(struct hfsplus_cat_file));
+ }
+
+ res = hfs_btree_write(tree);
+ if (res) {
+ pr_err("b-tree write err: %d, ino %llu\n",
+ res, inode->i_ino);
+ goto out;
+ }
+
+ set_bit(HFSPLUS_I_CAT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_CAT_TREE_I(inode->i_sb))->flags);
+ set_bit(HFSPLUS_I_CAT_DIRTY, &HFSPLUS_I(inode)->flags);
+out:
+ hfs_find_exit(&fd);
+
+ return res;
+}
+
+int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ unsigned int flags = 0;
+
+ if (inode->i_flags & S_IMMUTABLE)
+ flags |= FS_IMMUTABLE_FL;
+ if (inode->i_flags & S_APPEND)
+ flags |= FS_APPEND_FL;
+ if (hip->userflags & HFSPLUS_FLG_NODUMP)
+ flags |= FS_NODUMP_FL;
+ if (test_bit(HFSPLUS_SB_CASEFOLD, &sbi->flags))
+ flags |= FS_CASEFOLD_FL;
+
+ fileattr_fill_flags(fa, flags);
+
+ return 0;
+}
+
+int hfsplus_fileattr_set(struct mnt_idmap *idmap,
+ struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ unsigned int allowed = FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL;
+ unsigned int new_fl = 0;
+
+ if (fileattr_has_fsx(fa))
+ return -EOPNOTSUPP;
+
+ /*
+ * FS_CASEFOLD_FL reflects HFSPLUS_SB_CASEFOLD, a mount-time
+ * property. Accept it as a no-op so chattr's RMW round-trip
+ * succeeds; reject any attempt to enable it on a volume that
+ * was not formatted case-insensitive.
+ */
+ if (test_bit(HFSPLUS_SB_CASEFOLD, &sbi->flags))
+ allowed |= FS_CASEFOLD_FL;
+
+ /* don't silently ignore unsupported ext2 flags */
+ if (fa->flags & ~allowed)
+ return -EOPNOTSUPP;
+
+ if (fa->flags & FS_IMMUTABLE_FL)
+ new_fl |= S_IMMUTABLE;
+
+ if (fa->flags & FS_APPEND_FL)
+ new_fl |= S_APPEND;
+
+ inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
+
+ if (fa->flags & FS_NODUMP_FL)
+ hip->userflags |= HFSPLUS_FLG_NODUMP;
+ else
+ hip->userflags &= ~HFSPLUS_FLG_NODUMP;
+
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+
+ return 0;
+}
diff --git a/fs/hfsplus/ioctl.c b/fs/hfsplus/ioctl.c
new file mode 100644
index 000000000..40d04dba1
--- /dev/null
+++ b/fs/hfsplus/ioctl.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/ioctl.c
+ *
+ * Copyright (C) 2003
+ * Ethan Benson <erbenson@alaska.net>
+ * partially derived from linux/fs/ext2/ioctl.c
+ * Copyright (C) 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * hfsplus ioctls
+ */
+
+#include <linux/capability.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include "hfsplus_fs.h"
+
+/*
+ * "Blessing" an HFS+ filesystem writes metadata to the superblock informing
+ * the platform firmware which file to boot from
+ */
+static int hfsplus_ioctl_bless(struct file *file, int __user *user_flags)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct inode *inode = d_inode(dentry);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ struct hfsplus_vh *vh = sbi->s_vhdr;
+ struct hfsplus_vh *bvh = sbi->s_backup_vhdr;
+ u32 cnid = (unsigned long)dentry->d_fsdata;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ mutex_lock(&sbi->vh_mutex);
+
+ /* Directory containing the bootable system */
+ vh->finder_info[0] = bvh->finder_info[0] =
+ cpu_to_be32(d_parent_ino(dentry));
+
+ /*
+ * Bootloader. Just using the inode here breaks in the case of
+ * hard links - the firmware wants the ID of the hard link file,
+ * but the inode points at the indirect inode
+ */
+ vh->finder_info[1] = bvh->finder_info[1] = cpu_to_be32(cnid);
+
+ /* Per spec, the OS X system folder - same as finder_info[0] here */
+ vh->finder_info[5] = bvh->finder_info[5] =
+ cpu_to_be32(d_parent_ino(dentry));
+
+ mutex_unlock(&sbi->vh_mutex);
+ return 0;
+}
+
+long hfsplus_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case HFSPLUS_IOC_BLESS:
+ return hfsplus_ioctl_bless(file, argp);
+ default:
+ return -ENOTTY;
+ }
+}
diff --git a/fs/hfsplus/options.c b/fs/hfsplus/options.c
new file mode 100644
index 000000000..9b377481f
--- /dev/null
+++ b/fs/hfsplus/options.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/options.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Option parsing
+ */
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fs_struct.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/nls.h>
+#include <linux/mount.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include "hfsplus_fs.h"
+
+enum {
+ opt_creator, opt_type,
+ opt_umask, opt_uid, opt_gid,
+ opt_part, opt_session, opt_nls,
+ opt_decompose, opt_barrier,
+ opt_force,
+};
+
+static const struct fs_parameter_spec hfs_param_spec[] = {
+ fsparam_string ("creator", opt_creator),
+ fsparam_string ("type", opt_type),
+ fsparam_u32oct ("umask", opt_umask),
+ fsparam_u32 ("uid", opt_uid),
+ fsparam_u32 ("gid", opt_gid),
+ fsparam_u32 ("part", opt_part),
+ fsparam_u32 ("session", opt_session),
+ fsparam_string ("nls", opt_nls),
+ fsparam_flag_no ("decompose", opt_decompose),
+ fsparam_flag_no ("barrier", opt_barrier),
+ fsparam_flag ("force", opt_force),
+ {}
+};
+
+/* Initialize an options object to reasonable defaults */
+void hfsplus_fill_defaults(struct hfsplus_sb_info *opts)
+{
+ if (!opts)
+ return;
+
+ opts->creator = HFSPLUS_DEF_CR_TYPE;
+ opts->type = HFSPLUS_DEF_CR_TYPE;
+ opts->umask = current_umask();
+ opts->uid = current_uid();
+ opts->gid = current_gid();
+ opts->part = -1;
+ opts->session = -1;
+}
+
+/* Parse options from mount. Returns nonzero errno on failure */
+int hfsplus_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct hfsplus_sb_info *sbi = fc->s_fs_info;
+ struct fs_parse_result result;
+ int opt;
+
+ /*
+ * Only the force option is examined during remount, all others
+ * are ignored.
+ */
+ if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE &&
+ strncmp(param->key, "force", 5))
+ return 0;
+
+ opt = fs_parse(fc, hfs_param_spec, param, &result);
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case opt_creator:
+ if (strlen(param->string) != 4) {
+ pr_err("creator requires a 4 character value\n");
+ return -EINVAL;
+ }
+ memcpy(&sbi->creator, param->string, 4);
+ break;
+ case opt_type:
+ if (strlen(param->string) != 4) {
+ pr_err("type requires a 4 character value\n");
+ return -EINVAL;
+ }
+ memcpy(&sbi->type, param->string, 4);
+ break;
+ case opt_umask:
+ sbi->umask = (umode_t)result.uint_32;
+ break;
+ case opt_uid:
+ sbi->uid = result.uid;
+ set_bit(HFSPLUS_SB_UID, &sbi->flags);
+ break;
+ case opt_gid:
+ sbi->gid = result.gid;
+ set_bit(HFSPLUS_SB_GID, &sbi->flags);
+ break;
+ case opt_part:
+ sbi->part = result.uint_32;
+ break;
+ case opt_session:
+ sbi->session = result.uint_32;
+ break;
+ case opt_nls:
+ if (sbi->nls) {
+ pr_err("unable to change nls mapping\n");
+ return -EINVAL;
+ }
+ sbi->nls = load_nls(param->string);
+ if (!sbi->nls) {
+ pr_err("unable to load nls mapping \"%s\"\n",
+ param->string);
+ return -EINVAL;
+ }
+ break;
+ case opt_decompose:
+ if (result.negated)
+ set_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags);
+ else
+ clear_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags);
+ break;
+ case opt_barrier:
+ if (result.negated)
+ set_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags);
+ else
+ clear_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags);
+ break;
+ case opt_force:
+ set_bit(HFSPLUS_SB_FORCE, &sbi->flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int hfsplus_show_options(struct seq_file *seq, struct dentry *root)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(root->d_sb);
+
+ if (sbi->creator != HFSPLUS_DEF_CR_TYPE)
+ seq_show_option_n(seq, "creator", (char *)&sbi->creator, 4);
+ if (sbi->type != HFSPLUS_DEF_CR_TYPE)
+ seq_show_option_n(seq, "type", (char *)&sbi->type, 4);
+ seq_printf(seq, ",umask=%o,uid=%u,gid=%u", sbi->umask,
+ from_kuid_munged(&init_user_ns, sbi->uid),
+ from_kgid_munged(&init_user_ns, sbi->gid));
+ if (sbi->part >= 0)
+ seq_printf(seq, ",part=%u", sbi->part);
+ if (sbi->session >= 0)
+ seq_printf(seq, ",session=%u", sbi->session);
+ if (sbi->nls)
+ seq_printf(seq, ",nls=%s", sbi->nls->charset);
+ if (test_bit(HFSPLUS_SB_NODECOMPOSE, &sbi->flags))
+ seq_puts(seq, ",nodecompose");
+ if (test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
+ seq_puts(seq, ",nobarrier");
+ return 0;
+}
diff --git a/fs/hfsplus/part_tbl.c b/fs/hfsplus/part_tbl.c
new file mode 100644
index 000000000..9ec21664e
--- /dev/null
+++ b/fs/hfsplus/part_tbl.c
@@ -0,0 +1,157 @@
+/*
+ * linux/fs/hfsplus/part_tbl.c
+ *
+ * Copyright (C) 1996-1997 Paul H. Hargrove
+ * This file may be distributed under the terms of
+ * the GNU General Public License.
+ *
+ * Original code to handle the new style Mac partition table based on
+ * a patch contributed by Holger Schemel (aeglos@valinor.owl.de).
+ *
+ * In function preconditions the term "valid" applied to a pointer to
+ * a structure means that the pointer is non-NULL and the structure it
+ * points to has all fields initialized to consistent values.
+ *
+ */
+
+#include <linux/slab.h>
+#include "hfsplus_fs.h"
+
+/* offsets to various blocks */
+#define HFS_DD_BLK 0 /* Driver Descriptor block */
+#define HFS_PMAP_BLK 1 /* First block of partition map */
+#define HFS_MDB_BLK 2 /* Block (w/i partition) of MDB */
+
+/* magic numbers for various disk blocks */
+#define HFS_DRVR_DESC_MAGIC 0x4552 /* "ER": driver descriptor map */
+#define HFS_OLD_PMAP_MAGIC 0x5453 /* "TS": old-type partition map */
+#define HFS_NEW_PMAP_MAGIC 0x504D /* "PM": new-type partition map */
+#define HFS_SUPER_MAGIC 0x4244 /* "BD": HFS MDB (super block) */
+#define HFS_MFS_SUPER_MAGIC 0xD2D7 /* MFS MDB (super block) */
+
+/*
+ * The new style Mac partition map
+ *
+ * For each partition on the media there is a physical block (512-byte
+ * block) containing one of these structures. These blocks are
+ * contiguous starting at block 1.
+ */
+struct new_pmap {
+ __be16 pmSig; /* signature */
+ __be16 reSigPad; /* padding */
+ __be32 pmMapBlkCnt; /* partition blocks count */
+ __be32 pmPyPartStart; /* physical block start of partition */
+ __be32 pmPartBlkCnt; /* physical block count of partition */
+ u8 pmPartName[32]; /* (null terminated?) string
+ giving the name of this
+ partition */
+ u8 pmPartType[32]; /* (null terminated?) string
+ giving the type of this
+ partition */
+ /* a bunch more stuff we don't need */
+} __packed;
+
+/*
+ * The old style Mac partition map
+ *
+ * The partition map consists for a 2-byte signature followed by an
+ * array of these structures. The map is terminated with an all-zero
+ * one of these.
+ */
+struct old_pmap {
+ __be16 pdSig; /* Signature bytes */
+ struct old_pmap_entry {
+ __be32 pdStart;
+ __be32 pdSize;
+ __be32 pdFSID;
+ } pdEntry[42];
+} __packed;
+
+static int hfs_parse_old_pmap(struct super_block *sb, struct old_pmap *pm,
+ sector_t *part_start, sector_t *part_size)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ int i;
+
+ for (i = 0; i < 42; i++) {
+ struct old_pmap_entry *p = &pm->pdEntry[i];
+
+ if (p->pdStart && p->pdSize &&
+ p->pdFSID == cpu_to_be32(0x54465331)/*"TFS1"*/ &&
+ (sbi->part < 0 || sbi->part == i)) {
+ *part_start += be32_to_cpu(p->pdStart);
+ *part_size = be32_to_cpu(p->pdSize);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int hfs_parse_new_pmap(struct super_block *sb, void *buf,
+ struct new_pmap *pm, sector_t *part_start, sector_t *part_size)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ int size = be32_to_cpu(pm->pmMapBlkCnt);
+ int buf_size = hfsplus_min_io_size(sb);
+ int res;
+ int i = 0;
+
+ do {
+ if (!memcmp(pm->pmPartType, "Apple_HFS", 9) &&
+ (sbi->part < 0 || sbi->part == i)) {
+ *part_start += be32_to_cpu(pm->pmPyPartStart);
+ *part_size = be32_to_cpu(pm->pmPartBlkCnt);
+ return 0;
+ }
+
+ if (++i >= size)
+ return -ENOENT;
+
+ pm = (struct new_pmap *)((u8 *)pm + HFSPLUS_SECTOR_SIZE);
+ if ((u8 *)pm - (u8 *)buf >= buf_size) {
+ res = hfsplus_submit_bio(sb,
+ *part_start + HFS_PMAP_BLK + i,
+ buf, (void **)&pm, REQ_OP_READ);
+ if (res)
+ return res;
+ }
+ } while (pm->pmSig == cpu_to_be16(HFS_NEW_PMAP_MAGIC));
+
+ return -ENOENT;
+}
+
+/*
+ * Parse the partition map looking for the start and length of a
+ * HFS/HFS+ partition.
+ */
+int hfs_part_find(struct super_block *sb,
+ sector_t *part_start, sector_t *part_size)
+{
+ void *buf, *data;
+ int res;
+
+ buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ res = hfsplus_submit_bio(sb, *part_start + HFS_PMAP_BLK,
+ buf, &data, REQ_OP_READ);
+ if (res)
+ goto out;
+
+ switch (be16_to_cpu(*((__be16 *)data))) {
+ case HFS_OLD_PMAP_MAGIC:
+ res = hfs_parse_old_pmap(sb, data, part_start, part_size);
+ break;
+ case HFS_NEW_PMAP_MAGIC:
+ res = hfs_parse_new_pmap(sb, buf, data, part_start, part_size);
+ break;
+ default:
+ res = -ENOENT;
+ break;
+ }
+out:
+ kfree(buf);
+ return res;
+}
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
new file mode 100644
index 000000000..ff7d6b333
--- /dev/null
+++ b/fs/hfsplus/super.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/fs/hfsplus/super.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/slab.h>
+#include <linux/vfs.h>
+#include <linux/nls.h>
+
+static struct inode *hfsplus_alloc_inode(struct super_block *sb);
+static void hfsplus_free_inode(struct inode *inode);
+
+#include "hfsplus_fs.h"
+#include "xattr.h"
+
+static int hfsplus_system_read_inode(struct inode *inode)
+{
+ struct hfsplus_vh *vhdr = HFSPLUS_SB(inode->i_sb)->s_vhdr;
+
+ switch (inode->i_ino) {
+ case HFSPLUS_EXT_CNID:
+ hfsplus_inode_read_fork(inode, &vhdr->ext_file);
+ inode->i_mapping->a_ops = &hfsplus_btree_aops;
+ break;
+ case HFSPLUS_CAT_CNID:
+ hfsplus_inode_read_fork(inode, &vhdr->cat_file);
+ inode->i_mapping->a_ops = &hfsplus_btree_aops;
+ break;
+ case HFSPLUS_ALLOC_CNID:
+ hfsplus_inode_read_fork(inode, &vhdr->alloc_file);
+ inode->i_mapping->a_ops = &hfsplus_aops;
+ break;
+ case HFSPLUS_START_CNID:
+ hfsplus_inode_read_fork(inode, &vhdr->start_file);
+ break;
+ case HFSPLUS_ATTR_CNID:
+ hfsplus_inode_read_fork(inode, &vhdr->attr_file);
+ inode->i_mapping->a_ops = &hfsplus_btree_aops;
+ break;
+ default:
+ return -EIO;
+ }
+
+ /*
+ * Assign a dummy file type, for may_open() requires that
+ * an inode has a valid file type.
+ */
+ inode->i_mode = S_IFREG;
+
+ return 0;
+}
+
+struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)
+{
+ struct hfs_find_data fd;
+ struct inode *inode;
+ int err;
+
+ inode = iget_locked(sb, ino);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+ if (!(inode_state_read_once(inode) & I_NEW))
+ return inode;
+
+ atomic_set(&HFSPLUS_I(inode)->opencnt, 0);
+ HFSPLUS_I(inode)->first_blocks = 0;
+ HFSPLUS_I(inode)->clump_blocks = 0;
+ HFSPLUS_I(inode)->alloc_blocks = 0;
+ HFSPLUS_I(inode)->cached_start = U32_MAX;
+ HFSPLUS_I(inode)->cached_blocks = 0;
+ memset(HFSPLUS_I(inode)->first_extents, 0, sizeof(hfsplus_extent_rec));
+ memset(HFSPLUS_I(inode)->cached_extents, 0, sizeof(hfsplus_extent_rec));
+ HFSPLUS_I(inode)->extent_state = 0;
+ mutex_init(&HFSPLUS_I(inode)->extents_lock);
+ HFSPLUS_I(inode)->rsrc_inode = NULL;
+ HFSPLUS_I(inode)->create_date = 0;
+ HFSPLUS_I(inode)->linkid = 0;
+ HFSPLUS_I(inode)->flags = 0;
+ HFSPLUS_I(inode)->fs_blocks = 0;
+ HFSPLUS_I(inode)->userflags = 0;
+ HFSPLUS_I(inode)->subfolders = 0;
+ HFSPLUS_I(inode)->phys_size = 0;
+
+ if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||
+ inode->i_ino == HFSPLUS_ROOT_CNID) {
+ err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd);
+ if (!err) {
+ err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
+ if (!err)
+ err = hfsplus_cat_read_inode(inode, &fd);
+ hfs_find_exit(&fd);
+ }
+ } else {
+ err = hfsplus_system_read_inode(inode);
+ }
+
+ if (err) {
+ iget_failed(inode);
+ return ERR_PTR(err);
+ }
+
+ unlock_new_inode(inode);
+ return inode;
+}
+
+static int hfsplus_system_write_inode(struct inode *inode)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+ struct hfsplus_fork_raw *fork;
+ struct hfs_btree *tree = NULL;
+
+ switch (inode->i_ino) {
+ case HFSPLUS_EXT_CNID:
+ fork = &vhdr->ext_file;
+ tree = sbi->ext_tree;
+ break;
+ case HFSPLUS_CAT_CNID:
+ fork = &vhdr->cat_file;
+ tree = sbi->cat_tree;
+ break;
+ case HFSPLUS_ALLOC_CNID:
+ fork = &vhdr->alloc_file;
+ break;
+ case HFSPLUS_START_CNID:
+ fork = &vhdr->start_file;
+ break;
+ case HFSPLUS_ATTR_CNID:
+ fork = &vhdr->attr_file;
+ tree = sbi->attr_tree;
+ break;
+ default:
+ return -EIO;
+ }
+
+ if (fork->total_size != cpu_to_be64(inode->i_size)) {
+ set_bit(HFSPLUS_SB_WRITEBACKUP, &sbi->flags);
+ hfsplus_mark_mdb_dirty(inode->i_sb);
+ }
+ hfsplus_inode_write_fork(inode, fork);
+ if (tree) {
+ mutex_lock_nested(&tree->tree_lock,
+ hfsplus_btree_lock_class(tree));
+ int err = hfs_btree_write(tree);
+ mutex_unlock(&tree->tree_lock);
+
+ if (err) {
+ pr_err("b-tree write err: %d, ino %llu\n",
+ err, inode->i_ino);
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int hfsplus_write_inode(struct inode *inode,
+ struct writeback_control *wbc)
+{
+ int err;
+
+ hfs_dbg("ino %llu\n", inode->i_ino);
+
+ err = hfsplus_ext_write_extent(inode);
+ if (err)
+ return err;
+
+ if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||
+ inode->i_ino == HFSPLUS_ROOT_CNID)
+ return hfsplus_cat_write_inode(inode);
+ else
+ return hfsplus_system_write_inode(inode);
+}
+
+static void hfsplus_evict_inode(struct inode *inode)
+{
+ hfs_dbg("ino %llu\n", inode->i_ino);
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ if (HFSPLUS_IS_RSRC(inode)) {
+ HFSPLUS_I(HFSPLUS_I(inode)->rsrc_inode)->rsrc_inode = NULL;
+ iput(HFSPLUS_I(inode)->rsrc_inode);
+ }
+}
+
+int hfsplus_commit_superblock(struct super_block *sb)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+ int write_backup = 0;
+ int error = 0, error2;
+
+ hfs_dbg("starting...\n");
+
+ mutex_lock(&sbi->vh_mutex);
+ mutex_lock(&sbi->alloc_mutex);
+ vhdr->free_blocks = cpu_to_be32(sbi->free_blocks);
+ vhdr->next_cnid = cpu_to_be32(sbi->next_cnid);
+ vhdr->folder_count = cpu_to_be32(sbi->folder_count);
+ vhdr->file_count = cpu_to_be32(sbi->file_count);
+
+ hfs_dbg("free_blocks %u, next_cnid %u, folder_count %u, file_count %u\n",
+ sbi->free_blocks, sbi->next_cnid,
+ sbi->folder_count, sbi->file_count);
+
+ if (test_and_clear_bit(HFSPLUS_SB_WRITEBACKUP, &sbi->flags)) {
+ memcpy(sbi->s_backup_vhdr, sbi->s_vhdr, sizeof(*sbi->s_vhdr));
+ write_backup = 1;
+ }
+
+ error2 = hfsplus_submit_bio(sb,
+ sbi->part_start + HFSPLUS_VOLHEAD_SECTOR,
+ sbi->s_vhdr_buf, NULL, REQ_OP_WRITE);
+ if (!error)
+ error = error2;
+ if (!write_backup)
+ goto out;
+
+ error2 = hfsplus_submit_bio(sb,
+ sbi->part_start + sbi->sect_count - 2,
+ sbi->s_backup_vhdr_buf, NULL, REQ_OP_WRITE);
+ if (!error)
+ error = error2;
+out:
+ mutex_unlock(&sbi->alloc_mutex);
+ mutex_unlock(&sbi->vh_mutex);
+
+ hfs_dbg("finished: err %d\n", error);
+
+ return error;
+}
+
+static int hfsplus_sync_fs(struct super_block *sb, int wait)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ int error, error2;
+
+ if (!wait)
+ return 0;
+
+ hfs_dbg("starting...\n");
+
+ /*
+ * Explicitly write out the special metadata inodes.
+ *
+ * While these special inodes are marked as hashed and written
+ * out peridocically by the flusher threads we redirty them
+ * during writeout of normal inodes, and thus the life lock
+ * prevents us from getting the latest state to disk.
+ */
+ error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping);
+ error2 = filemap_write_and_wait(sbi->ext_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ if (sbi->attr_tree) {
+ error2 =
+ filemap_write_and_wait(sbi->attr_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ }
+ error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
+ if (!error)
+ error = error2;
+
+ error2 = hfsplus_commit_superblock(sb);
+ if (!error)
+ error = error2;
+
+ if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
+ blkdev_issue_flush(sb->s_bdev);
+
+ hfs_dbg("finished: err %d\n", error);
+
+ return error;
+}
+
+static void delayed_sync_fs(struct work_struct *work)
+{
+ int err;
+ struct hfsplus_sb_info *sbi;
+
+ sbi = container_of(work, struct hfsplus_sb_info, sync_work.work);
+
+ spin_lock(&sbi->work_lock);
+ sbi->work_queued = 0;
+ spin_unlock(&sbi->work_lock);
+
+ err = hfsplus_sync_fs(sbi->alloc_file->i_sb, 1);
+ if (err)
+ pr_err("delayed sync fs err %d\n", err);
+}
+
+void hfsplus_mark_mdb_dirty(struct super_block *sb)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ unsigned long delay;
+
+ if (sb_rdonly(sb))
+ return;
+
+ spin_lock(&sbi->work_lock);
+ if (!sbi->work_queued) {
+ delay = msecs_to_jiffies(dirty_writeback_interval * 10);
+ queue_delayed_work(system_dfl_long_wq, &sbi->sync_work, delay);
+ sbi->work_queued = 1;
+ }
+ spin_unlock(&sbi->work_lock);
+}
+
+static void delayed_free(struct rcu_head *p)
+{
+ struct hfsplus_sb_info *sbi = container_of(p, struct hfsplus_sb_info, rcu);
+
+ unload_nls(sbi->nls);
+ kfree(sbi);
+}
+
+static void hfsplus_put_super(struct super_block *sb)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+
+ hfs_dbg("starting...\n");
+
+ cancel_delayed_work_sync(&sbi->sync_work);
+
+ if (!sb_rdonly(sb) && sbi->s_vhdr) {
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+
+ vhdr->modify_date = hfsp_now2mt();
+ vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_UNMNT);
+ vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_INCNSTNT);
+
+ hfsplus_sync_fs(sb, 1);
+ }
+
+ iput(sbi->alloc_file);
+ iput(sbi->hidden_dir);
+ hfs_btree_close(sbi->attr_tree);
+ hfs_btree_close(sbi->cat_tree);
+ hfs_btree_close(sbi->ext_tree);
+ kfree(sbi->s_vhdr_buf);
+ kfree(sbi->s_backup_vhdr_buf);
+ hfs_dbg("finished\n");
+}
+
+static int hfsplus_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+
+ buf->f_type = HFSPLUS_SUPER_MAGIC;
+ buf->f_bsize = sb->s_blocksize;
+ buf->f_blocks = sbi->total_blocks << sbi->fs_shift;
+ buf->f_bfree = sbi->free_blocks << sbi->fs_shift;
+ buf->f_bavail = buf->f_bfree;
+ buf->f_files = 0xFFFFFFFF;
+ buf->f_ffree = 0xFFFFFFFF - sbi->next_cnid;
+ buf->f_fsid = u64_to_fsid(id);
+ buf->f_namelen = HFSPLUS_MAX_STRLEN;
+
+ return 0;
+}
+
+static int hfsplus_reconfigure(struct fs_context *fc)
+{
+ struct super_block *sb = fc->root->d_sb;
+
+ sync_filesystem(sb);
+ if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb))
+ return 0;
+ if (!(fc->sb_flags & SB_RDONLY)) {
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+
+ if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) {
+ pr_warn("filesystem was not cleanly unmounted, running fsck.hfsplus is recommended. leaving read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ fc->sb_flags |= SB_RDONLY;
+ } else if (test_bit(HFSPLUS_SB_FORCE, &sbi->flags)) {
+ /* nothing */
+ } else if (vhdr->attributes &
+ cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) {
+ pr_warn("filesystem is marked locked, leaving read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ fc->sb_flags |= SB_RDONLY;
+ } else if (vhdr->attributes &
+ cpu_to_be32(HFSPLUS_VOL_JOURNALED)) {
+ pr_warn("filesystem is marked journaled, leaving read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ fc->sb_flags |= SB_RDONLY;
+ }
+ }
+ return 0;
+}
+
+static const struct super_operations hfsplus_sops = {
+ .alloc_inode = hfsplus_alloc_inode,
+ .free_inode = hfsplus_free_inode,
+ .write_inode = hfsplus_write_inode,
+ .evict_inode = hfsplus_evict_inode,
+ .put_super = hfsplus_put_super,
+ .sync_fs = hfsplus_sync_fs,
+ .statfs = hfsplus_statfs,
+ .show_options = hfsplus_show_options,
+};
+
+void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr)
+{
+ vhdr->last_mount_vers = cpu_to_be32(HFSP_MOUNT_VERSION);
+ vhdr->modify_date = hfsp_now2mt();
+ be32_add_cpu(&vhdr->write_count, 1);
+ vhdr->attributes &= cpu_to_be32(~HFSPLUS_VOL_UNMNT);
+ vhdr->attributes |= cpu_to_be32(HFSPLUS_VOL_INCNSTNT);
+}
+
+static inline int hfsplus_get_hidden_dir_entry(struct super_block *sb,
+ const struct qstr *str,
+ hfsplus_cat_entry *entry)
+{
+ struct hfs_find_data fd;
+ int err;
+
+ err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
+ if (unlikely(err))
+ return err;
+
+ err = hfsplus_cat_build_key(sb, fd.search_key, HFSPLUS_ROOT_CNID, str);
+ if (unlikely(err))
+ goto free_fd;
+
+ err = hfsplus_brec_read_cat(&fd, entry);
+ if (err)
+ err = -ENOENT;
+
+free_fd:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+ struct hfsplus_vh *vhdr;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ hfsplus_cat_entry entry;
+ struct inode *root, *inode;
+ struct qstr str;
+ struct nls_table *nls;
+ u64 last_fs_block, last_fs_page;
+ int silent = fc->sb_flags & SB_SILENT;
+ int err;
+
+ mutex_init(&sbi->alloc_mutex);
+ mutex_init(&sbi->vh_mutex);
+ spin_lock_init(&sbi->work_lock);
+ INIT_DELAYED_WORK(&sbi->sync_work, delayed_sync_fs);
+
+ err = -EINVAL;
+ if (!sbi->nls) {
+ /* try utf8 first, as this is the old default behaviour */
+ sbi->nls = load_nls("utf8");
+ if (!sbi->nls)
+ sbi->nls = load_nls_default();
+ }
+
+ /* temporarily use utf8 to correctly find the hidden dir below */
+ nls = sbi->nls;
+ sbi->nls = load_nls("utf8");
+ if (!sbi->nls) {
+ pr_err("unable to load nls for utf8\n");
+ goto out_unload_nls;
+ }
+
+ /* Grab the volume header */
+ if (hfsplus_read_wrapper(sb)) {
+ if (!silent)
+ pr_warn("unable to find HFS+ superblock\n");
+ goto out_unload_nls;
+ }
+ vhdr = sbi->s_vhdr;
+
+ /* Copy parts of the volume header into the superblock */
+ sb->s_magic = HFSPLUS_VOLHEAD_SIG;
+ if (be16_to_cpu(vhdr->version) < HFSPLUS_MIN_VERSION ||
+ be16_to_cpu(vhdr->version) > HFSPLUS_CURRENT_VERSION) {
+ pr_err("wrong filesystem version\n");
+ goto out_free_vhdr;
+ }
+ sbi->total_blocks = be32_to_cpu(vhdr->total_blocks);
+ sbi->free_blocks = be32_to_cpu(vhdr->free_blocks);
+ sbi->next_cnid = be32_to_cpu(vhdr->next_cnid);
+ sbi->file_count = be32_to_cpu(vhdr->file_count);
+ sbi->folder_count = be32_to_cpu(vhdr->folder_count);
+ sbi->data_clump_blocks =
+ be32_to_cpu(vhdr->data_clump_sz) >> sbi->alloc_blksz_shift;
+ if (!sbi->data_clump_blocks)
+ sbi->data_clump_blocks = 1;
+ sbi->rsrc_clump_blocks =
+ be32_to_cpu(vhdr->rsrc_clump_sz) >> sbi->alloc_blksz_shift;
+ if (!sbi->rsrc_clump_blocks)
+ sbi->rsrc_clump_blocks = 1;
+
+ err = -EFBIG;
+ last_fs_block = sbi->total_blocks - 1;
+ last_fs_page = (last_fs_block << sbi->alloc_blksz_shift) >>
+ PAGE_SHIFT;
+
+ if ((last_fs_block > (sector_t)(~0ULL) >> (sbi->alloc_blksz_shift - 9)) ||
+ (last_fs_page > (pgoff_t)(~0ULL))) {
+ pr_err("filesystem size too large\n");
+ goto out_free_vhdr;
+ }
+
+ /* Set up operations so we can load metadata */
+ sb->s_op = &hfsplus_sops;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+
+ if (!(vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_UNMNT))) {
+ pr_warn("Filesystem was not cleanly unmounted, running fsck.hfsplus is recommended. mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ } else if (test_and_clear_bit(HFSPLUS_SB_FORCE, &sbi->flags)) {
+ /* nothing */
+ } else if (vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_SOFTLOCK)) {
+ pr_warn("Filesystem is marked locked, mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ } else if ((vhdr->attributes & cpu_to_be32(HFSPLUS_VOL_JOURNALED)) &&
+ !sb_rdonly(sb)) {
+ pr_warn("write access to a journaled filesystem is not supported, use the force option at your own risk, mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+
+ err = -EINVAL;
+
+ /* Load metadata objects (B*Trees) */
+ sbi->ext_tree = hfs_btree_open(sb, HFSPLUS_EXT_CNID);
+ if (!sbi->ext_tree) {
+ pr_err("failed to load extents file\n");
+ goto out_free_vhdr;
+ }
+ sbi->cat_tree = hfs_btree_open(sb, HFSPLUS_CAT_CNID);
+ if (!sbi->cat_tree) {
+ pr_err("failed to load catalog file\n");
+ goto out_close_ext_tree;
+ }
+ atomic_set(&sbi->attr_tree_state, HFSPLUS_EMPTY_ATTR_TREE);
+ if (vhdr->attr_file.total_blocks != 0) {
+ sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
+ if (!sbi->attr_tree) {
+ pr_err("failed to load attributes file\n");
+ goto out_close_cat_tree;
+ }
+ atomic_set(&sbi->attr_tree_state, HFSPLUS_VALID_ATTR_TREE);
+ }
+ sb->s_xattr = hfsplus_xattr_handlers;
+
+ inode = hfsplus_iget(sb, HFSPLUS_ALLOC_CNID);
+ if (IS_ERR(inode)) {
+ pr_err("failed to load allocation file\n");
+ err = PTR_ERR(inode);
+ goto out_close_attr_tree;
+ }
+ sbi->alloc_file = inode;
+
+ /* Load the root directory */
+ root = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);
+ if (IS_ERR(root)) {
+ pr_err("failed to load root directory\n");
+ err = PTR_ERR(root);
+ goto out_put_alloc_file;
+ }
+
+ set_default_d_op(sb, &hfsplus_dentry_operations);
+ sb->s_root = d_make_root(root);
+ if (!sb->s_root) {
+ err = -ENOMEM;
+ goto out_put_alloc_file;
+ }
+
+ str.len = sizeof(HFSP_HIDDENDIR_NAME) - 1;
+ str.name = HFSP_HIDDENDIR_NAME;
+ err = hfsplus_get_hidden_dir_entry(sb, &str, &entry);
+ if (err == -ENOENT) {
+ /*
+ * Hidden directory is absent or it cannot be read.
+ */
+ } else if (unlikely(err)) {
+ goto out_put_root;
+ } else {
+ if (entry.type != cpu_to_be16(HFSPLUS_FOLDER)) {
+ err = -EIO;
+ goto out_put_root;
+ }
+ inode = hfsplus_iget(sb, be32_to_cpu(entry.folder.id));
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ goto out_put_root;
+ }
+ sbi->hidden_dir = inode;
+ }
+
+ if (!sb_rdonly(sb)) {
+ /*
+ * H+LX == hfsplusutils, H+Lx == this driver, H+lx is unused
+ * all three are registered with Apple for our use
+ */
+ hfsplus_prepare_volume_header_for_commit(vhdr);
+ hfsplus_sync_fs(sb, 1);
+
+ if (!sbi->hidden_dir) {
+ mutex_lock(&sbi->vh_mutex);
+ sbi->hidden_dir = hfsplus_new_inode(sb, root, S_IFDIR);
+ if (!sbi->hidden_dir) {
+ mutex_unlock(&sbi->vh_mutex);
+ err = -ENOMEM;
+ goto out_put_root;
+ }
+ err = hfsplus_create_cat(sbi->hidden_dir->i_ino, root,
+ &str, sbi->hidden_dir);
+ if (err) {
+ mutex_unlock(&sbi->vh_mutex);
+ goto out_put_hidden_dir;
+ }
+
+ err = hfsplus_init_security(sbi->hidden_dir,
+ root, &str);
+ if (err == -EOPNOTSUPP)
+ err = 0; /* Operation is not supported. */
+ else if (err) {
+ /*
+ * Try to delete anyway without
+ * error analysis.
+ */
+ hfsplus_delete_cat(sbi->hidden_dir->i_ino,
+ root, &str);
+ mutex_unlock(&sbi->vh_mutex);
+ goto out_put_hidden_dir;
+ }
+
+ mutex_unlock(&sbi->vh_mutex);
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(sbi->hidden_dir,
+ HFSPLUS_I_CAT_DIRTY);
+ }
+ }
+
+ unload_nls(sbi->nls);
+ sbi->nls = nls;
+ return 0;
+
+out_put_hidden_dir:
+ cancel_delayed_work_sync(&sbi->sync_work);
+ iput(sbi->hidden_dir);
+out_put_root:
+ dput(sb->s_root);
+ sb->s_root = NULL;
+out_put_alloc_file:
+ iput(sbi->alloc_file);
+out_close_attr_tree:
+ hfs_btree_close(sbi->attr_tree);
+out_close_cat_tree:
+ hfs_btree_close(sbi->cat_tree);
+out_close_ext_tree:
+ hfs_btree_close(sbi->ext_tree);
+out_free_vhdr:
+ kfree(sbi->s_vhdr_buf);
+ kfree(sbi->s_backup_vhdr_buf);
+out_unload_nls:
+ unload_nls(nls);
+ return err;
+}
+
+MODULE_AUTHOR("Brad Boyer");
+MODULE_DESCRIPTION("Extended Macintosh Filesystem");
+MODULE_LICENSE("GPL");
+
+static struct kmem_cache *hfsplus_inode_cachep;
+
+static struct inode *hfsplus_alloc_inode(struct super_block *sb)
+{
+ struct hfsplus_inode_info *i;
+
+ i = alloc_inode_sb(sb, hfsplus_inode_cachep, GFP_KERNEL);
+ return i ? &i->vfs_inode : NULL;
+}
+
+static void hfsplus_free_inode(struct inode *inode)
+{
+ kmem_cache_free(hfsplus_inode_cachep, HFSPLUS_I(inode));
+}
+
+#define HFSPLUS_INODE_SIZE sizeof(struct hfsplus_inode_info)
+
+static int hfsplus_get_tree(struct fs_context *fc)
+{
+ return get_tree_bdev(fc, hfsplus_fill_super);
+}
+
+static void hfsplus_free_fc(struct fs_context *fc)
+{
+ kfree(fc->s_fs_info);
+}
+
+static const struct fs_context_operations hfsplus_context_ops = {
+ .parse_param = hfsplus_parse_param,
+ .get_tree = hfsplus_get_tree,
+ .reconfigure = hfsplus_reconfigure,
+ .free = hfsplus_free_fc,
+};
+
+static int hfsplus_init_fs_context(struct fs_context *fc)
+{
+ struct hfsplus_sb_info *sbi;
+
+ sbi = kzalloc_obj(struct hfsplus_sb_info);
+ if (!sbi)
+ return -ENOMEM;
+
+ if (fc->purpose != FS_CONTEXT_FOR_RECONFIGURE)
+ hfsplus_fill_defaults(sbi);
+
+ fc->s_fs_info = sbi;
+ fc->ops = &hfsplus_context_ops;
+
+ return 0;
+}
+
+static void hfsplus_kill_super(struct super_block *sb)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+
+ kill_block_super(sb);
+ call_rcu(&sbi->rcu, delayed_free);
+}
+
+static struct file_system_type hfsplus_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "hfsplus",
+ .kill_sb = hfsplus_kill_super,
+ .fs_flags = FS_REQUIRES_DEV,
+ .init_fs_context = hfsplus_init_fs_context,
+};
+MODULE_ALIAS_FS("hfsplus");
+
+static void hfsplus_init_once(void *p)
+{
+ struct hfsplus_inode_info *i = p;
+
+ inode_init_once(&i->vfs_inode);
+}
+
+static int __init init_hfsplus_fs(void)
+{
+ int err;
+
+ hfsplus_inode_cachep = kmem_cache_create("hfsplus_icache",
+ HFSPLUS_INODE_SIZE, 0, SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT,
+ hfsplus_init_once);
+ if (!hfsplus_inode_cachep)
+ return -ENOMEM;
+ err = hfsplus_create_attr_tree_cache();
+ if (err)
+ goto destroy_inode_cache;
+ err = register_filesystem(&hfsplus_fs_type);
+ if (err)
+ goto destroy_attr_tree_cache;
+ return 0;
+
+destroy_attr_tree_cache:
+ hfsplus_destroy_attr_tree_cache();
+
+destroy_inode_cache:
+ kmem_cache_destroy(hfsplus_inode_cachep);
+
+ return err;
+}
+
+static void __exit exit_hfsplus_fs(void)
+{
+ unregister_filesystem(&hfsplus_fs_type);
+
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+ hfsplus_destroy_attr_tree_cache();
+ kmem_cache_destroy(hfsplus_inode_cachep);
+}
+
+module_init(init_hfsplus_fs)
+module_exit(exit_hfsplus_fs)
diff --git a/fs/hfsplus/tables.c b/fs/hfsplus/tables.c
new file mode 100644
index 000000000..a5fb8ee7d
--- /dev/null
+++ b/fs/hfsplus/tables.c
@@ -0,0 +1,3246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/tables.c
+ *
+ * Various data tables
+ */
+
+#include "hfsplus_fs.h"
+
+/*
+ * Unicode case folding table taken from Apple Technote #1150
+ * (HFS Plus Volume Format)
+ */
+
+u16 hfsplus_case_fold_table[] = {
+/*
+ * The lower case table consists of a 256-entry high-byte table followed by
+ * some number of 256-entry subtables. The high-byte table contains either an
+ * offset to the subtable for characters with that high byte or zero, which
+ * means that there are no case mappings or ignored characters in that block.
+ * Ignored characters are mapped to zero.
+ */
+
+ // High-byte indices ( == 0 iff no case mapping and no ignorables )
+
+
+ /* 0 */ 0x0100, 0x0200, 0x0000, 0x0300, 0x0400, 0x0500, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 1 */ 0x0600, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 2 */ 0x0700, 0x0800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 3 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 4 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 5 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 6 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 7 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 8 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 9 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* A */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* B */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* C */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* D */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* E */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* F */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0900, 0x0A00,
+
+ // Table 1 (for high byte 0x00)
+
+ /* 0 */ 0xFFFF, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+ /* 1 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
+ /* 2 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+ /* 3 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+ /* 4 */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ /* 5 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+ /* 6 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ /* 7 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
+ /* 8 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
+ /* 9 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
+ /* A */ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+ /* B */ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
+ /* C */ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00E6, 0x00C7,
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+ /* D */ 0x00F0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+ 0x00F8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00FE, 0x00DF,
+ /* E */ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+ /* F */ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
+
+ // Table 2 (for high byte 0x01)
+
+ /* 0 */ 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107,
+ 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F,
+ /* 1 */ 0x0111, 0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117,
+ 0x0118, 0x0119, 0x011A, 0x011B, 0x011C, 0x011D, 0x011E, 0x011F,
+ /* 2 */ 0x0120, 0x0121, 0x0122, 0x0123, 0x0124, 0x0125, 0x0127, 0x0127,
+ 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
+ /* 3 */ 0x0130, 0x0131, 0x0133, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137,
+ 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x0140,
+ /* 4 */ 0x0140, 0x0142, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0147,
+ 0x0148, 0x0149, 0x014B, 0x014B, 0x014C, 0x014D, 0x014E, 0x014F,
+ /* 5 */ 0x0150, 0x0151, 0x0153, 0x0153, 0x0154, 0x0155, 0x0156, 0x0157,
+ 0x0158, 0x0159, 0x015A, 0x015B, 0x015C, 0x015D, 0x015E, 0x015F,
+ /* 6 */ 0x0160, 0x0161, 0x0162, 0x0163, 0x0164, 0x0165, 0x0167, 0x0167,
+ 0x0168, 0x0169, 0x016A, 0x016B, 0x016C, 0x016D, 0x016E, 0x016F,
+ /* 7 */ 0x0170, 0x0171, 0x0172, 0x0173, 0x0174, 0x0175, 0x0176, 0x0177,
+ 0x0178, 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, 0x017F,
+ /* 8 */ 0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188,
+ 0x0188, 0x0256, 0x0257, 0x018C, 0x018C, 0x018D, 0x01DD, 0x0259,
+ /* 9 */ 0x025B, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268,
+ 0x0199, 0x0199, 0x019A, 0x019B, 0x026F, 0x0272, 0x019E, 0x0275,
+ /* A */ 0x01A0, 0x01A1, 0x01A3, 0x01A3, 0x01A5, 0x01A5, 0x01A6, 0x01A8,
+ 0x01A8, 0x0283, 0x01AA, 0x01AB, 0x01AD, 0x01AD, 0x0288, 0x01AF,
+ /* B */ 0x01B0, 0x028A, 0x028B, 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x0292,
+ 0x01B9, 0x01B9, 0x01BA, 0x01BB, 0x01BD, 0x01BD, 0x01BE, 0x01BF,
+ /* C */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C6, 0x01C6, 0x01C6, 0x01C9,
+ 0x01C9, 0x01C9, 0x01CC, 0x01CC, 0x01CC, 0x01CD, 0x01CE, 0x01CF,
+ /* D */ 0x01D0, 0x01D1, 0x01D2, 0x01D3, 0x01D4, 0x01D5, 0x01D6, 0x01D7,
+ 0x01D8, 0x01D9, 0x01DA, 0x01DB, 0x01DC, 0x01DD, 0x01DE, 0x01DF,
+ /* E */ 0x01E0, 0x01E1, 0x01E2, 0x01E3, 0x01E5, 0x01E5, 0x01E6, 0x01E7,
+ 0x01E8, 0x01E9, 0x01EA, 0x01EB, 0x01EC, 0x01ED, 0x01EE, 0x01EF,
+ /* F */ 0x01F0, 0x01F3, 0x01F3, 0x01F3, 0x01F4, 0x01F5, 0x01F6, 0x01F7,
+ 0x01F8, 0x01F9, 0x01FA, 0x01FB, 0x01FC, 0x01FD, 0x01FE, 0x01FF,
+
+ // Table 3 (for high byte 0x03)
+
+ /* 0 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
+ /* 1 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
+ /* 2 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
+ /* 3 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
+ /* 4 */ 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347,
+ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
+ /* 5 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
+ /* 6 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
+ /* 7 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377,
+ 0x0378, 0x0379, 0x037A, 0x037B, 0x037C, 0x037D, 0x037E, 0x037F,
+ /* 8 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0386, 0x0387,
+ 0x0388, 0x0389, 0x038A, 0x038B, 0x038C, 0x038D, 0x038E, 0x038F,
+ /* 9 */ 0x0390, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+ /* A */ 0x03C0, 0x03C1, 0x03A2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+ 0x03C8, 0x03C9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF,
+ /* B */ 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+ /* C */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+ 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x03CF,
+ /* D */ 0x03D0, 0x03D1, 0x03D2, 0x03D3, 0x03D4, 0x03D5, 0x03D6, 0x03D7,
+ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
+ /* E */ 0x03E0, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7,
+ 0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03EF,
+ /* F */ 0x03F0, 0x03F1, 0x03F2, 0x03F3, 0x03F4, 0x03F5, 0x03F6, 0x03F7,
+ 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
+
+ // Table 4 (for high byte 0x04)
+
+ /* 0 */ 0x0400, 0x0401, 0x0452, 0x0403, 0x0454, 0x0455, 0x0456, 0x0407,
+ 0x0458, 0x0459, 0x045A, 0x045B, 0x040C, 0x040D, 0x040E, 0x045F,
+ /* 1 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ 0x0438, 0x0419, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ /* 2 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ /* 3 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+ /* 4 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+ 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+ /* 5 */ 0x0450, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457,
+ 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x045D, 0x045E, 0x045F,
+ /* 6 */ 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467,
+ 0x0469, 0x0469, 0x046B, 0x046B, 0x046D, 0x046D, 0x046F, 0x046F,
+ /* 7 */ 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0476, 0x0477,
+ 0x0479, 0x0479, 0x047B, 0x047B, 0x047D, 0x047D, 0x047F, 0x047F,
+ /* 8 */ 0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
+ /* 9 */ 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497,
+ 0x0499, 0x0499, 0x049B, 0x049B, 0x049D, 0x049D, 0x049F, 0x049F,
+ /* A */ 0x04A1, 0x04A1, 0x04A3, 0x04A3, 0x04A5, 0x04A5, 0x04A7, 0x04A7,
+ 0x04A9, 0x04A9, 0x04AB, 0x04AB, 0x04AD, 0x04AD, 0x04AF, 0x04AF,
+ /* B */ 0x04B1, 0x04B1, 0x04B3, 0x04B3, 0x04B5, 0x04B5, 0x04B7, 0x04B7,
+ 0x04B9, 0x04B9, 0x04BB, 0x04BB, 0x04BD, 0x04BD, 0x04BF, 0x04BF,
+ /* C */ 0x04C0, 0x04C1, 0x04C2, 0x04C4, 0x04C4, 0x04C5, 0x04C6, 0x04C8,
+ 0x04C8, 0x04C9, 0x04CA, 0x04CC, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
+ /* D */ 0x04D0, 0x04D1, 0x04D2, 0x04D3, 0x04D4, 0x04D5, 0x04D6, 0x04D7,
+ 0x04D8, 0x04D9, 0x04DA, 0x04DB, 0x04DC, 0x04DD, 0x04DE, 0x04DF,
+ /* E */ 0x04E0, 0x04E1, 0x04E2, 0x04E3, 0x04E4, 0x04E5, 0x04E6, 0x04E7,
+ 0x04E8, 0x04E9, 0x04EA, 0x04EB, 0x04EC, 0x04ED, 0x04EE, 0x04EF,
+ /* F */ 0x04F0, 0x04F1, 0x04F2, 0x04F3, 0x04F4, 0x04F5, 0x04F6, 0x04F7,
+ 0x04F8, 0x04F9, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
+
+ // Table 5 (for high byte 0x05)
+
+ /* 0 */ 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507,
+ 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x050F,
+ /* 1 */ 0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517,
+ 0x0518, 0x0519, 0x051A, 0x051B, 0x051C, 0x051D, 0x051E, 0x051F,
+ /* 2 */ 0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527,
+ 0x0528, 0x0529, 0x052A, 0x052B, 0x052C, 0x052D, 0x052E, 0x052F,
+ /* 3 */ 0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+ 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F,
+ /* 4 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+ 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F,
+ /* 5 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557,
+ 0x0558, 0x0559, 0x055A, 0x055B, 0x055C, 0x055D, 0x055E, 0x055F,
+ /* 6 */ 0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+ 0x0568, 0x0569, 0x056A, 0x056B, 0x056C, 0x056D, 0x056E, 0x056F,
+ /* 7 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+ 0x0578, 0x0579, 0x057A, 0x057B, 0x057C, 0x057D, 0x057E, 0x057F,
+ /* 8 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0587,
+ 0x0588, 0x0589, 0x058A, 0x058B, 0x058C, 0x058D, 0x058E, 0x058F,
+ /* 9 */ 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597,
+ 0x0598, 0x0599, 0x059A, 0x059B, 0x059C, 0x059D, 0x059E, 0x059F,
+ /* A */ 0x05A0, 0x05A1, 0x05A2, 0x05A3, 0x05A4, 0x05A5, 0x05A6, 0x05A7,
+ 0x05A8, 0x05A9, 0x05AA, 0x05AB, 0x05AC, 0x05AD, 0x05AE, 0x05AF,
+ /* B */ 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7,
+ 0x05B8, 0x05B9, 0x05BA, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF,
+ /* C */ 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05C4, 0x05C5, 0x05C6, 0x05C7,
+ 0x05C8, 0x05C9, 0x05CA, 0x05CB, 0x05CC, 0x05CD, 0x05CE, 0x05CF,
+ /* D */ 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7,
+ 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
+ /* E */ 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
+ 0x05E8, 0x05E9, 0x05EA, 0x05EB, 0x05EC, 0x05ED, 0x05EE, 0x05EF,
+ /* F */ 0x05F0, 0x05F1, 0x05F2, 0x05F3, 0x05F4, 0x05F5, 0x05F6, 0x05F7,
+ 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF,
+
+ // Table 6 (for high byte 0x10)
+
+ /* 0 */ 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007,
+ 0x1008, 0x1009, 0x100A, 0x100B, 0x100C, 0x100D, 0x100E, 0x100F,
+ /* 1 */ 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017,
+ 0x1018, 0x1019, 0x101A, 0x101B, 0x101C, 0x101D, 0x101E, 0x101F,
+ /* 2 */ 0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027,
+ 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E, 0x102F,
+ /* 3 */ 0x1030, 0x1031, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037,
+ 0x1038, 0x1039, 0x103A, 0x103B, 0x103C, 0x103D, 0x103E, 0x103F,
+ /* 4 */ 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047,
+ 0x1048, 0x1049, 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F,
+ /* 5 */ 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057,
+ 0x1058, 0x1059, 0x105A, 0x105B, 0x105C, 0x105D, 0x105E, 0x105F,
+ /* 6 */ 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067,
+ 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F,
+ /* 7 */ 0x1070, 0x1071, 0x1072, 0x1073, 0x1074, 0x1075, 0x1076, 0x1077,
+ 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D, 0x107E, 0x107F,
+ /* 8 */ 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087,
+ 0x1088, 0x1089, 0x108A, 0x108B, 0x108C, 0x108D, 0x108E, 0x108F,
+ /* 9 */ 0x1090, 0x1091, 0x1092, 0x1093, 0x1094, 0x1095, 0x1096, 0x1097,
+ 0x1098, 0x1099, 0x109A, 0x109B, 0x109C, 0x109D, 0x109E, 0x109F,
+ /* A */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7,
+ 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF,
+ /* B */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7,
+ 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF,
+ /* C */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10C6, 0x10C7,
+ 0x10C8, 0x10C9, 0x10CA, 0x10CB, 0x10CC, 0x10CD, 0x10CE, 0x10CF,
+ /* D */ 0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10D7,
+ 0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10DD, 0x10DE, 0x10DF,
+ /* E */ 0x10E0, 0x10E1, 0x10E2, 0x10E3, 0x10E4, 0x10E5, 0x10E6, 0x10E7,
+ 0x10E8, 0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10EF,
+ /* F */ 0x10F0, 0x10F1, 0x10F2, 0x10F3, 0x10F4, 0x10F5, 0x10F6, 0x10F7,
+ 0x10F8, 0x10F9, 0x10FA, 0x10FB, 0x10FC, 0x10FD, 0x10FE, 0x10FF,
+
+ // Table 7 (for high byte 0x20)
+
+ /* 0 */ 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007,
+ 0x2008, 0x2009, 0x200A, 0x200B, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 1 */ 0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x2017,
+ 0x2018, 0x2019, 0x201A, 0x201B, 0x201C, 0x201D, 0x201E, 0x201F,
+ /* 2 */ 0x2020, 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026, 0x2027,
+ 0x2028, 0x2029, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x202F,
+ /* 3 */ 0x2030, 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037,
+ 0x2038, 0x2039, 0x203A, 0x203B, 0x203C, 0x203D, 0x203E, 0x203F,
+ /* 4 */ 0x2040, 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x2047,
+ 0x2048, 0x2049, 0x204A, 0x204B, 0x204C, 0x204D, 0x204E, 0x204F,
+ /* 5 */ 0x2050, 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2057,
+ 0x2058, 0x2059, 0x205A, 0x205B, 0x205C, 0x205D, 0x205E, 0x205F,
+ /* 6 */ 0x2060, 0x2061, 0x2062, 0x2063, 0x2064, 0x2065, 0x2066, 0x2067,
+ 0x2068, 0x2069, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* 7 */ 0x2070, 0x2071, 0x2072, 0x2073, 0x2074, 0x2075, 0x2076, 0x2077,
+ 0x2078, 0x2079, 0x207A, 0x207B, 0x207C, 0x207D, 0x207E, 0x207F,
+ /* 8 */ 0x2080, 0x2081, 0x2082, 0x2083, 0x2084, 0x2085, 0x2086, 0x2087,
+ 0x2088, 0x2089, 0x208A, 0x208B, 0x208C, 0x208D, 0x208E, 0x208F,
+ /* 9 */ 0x2090, 0x2091, 0x2092, 0x2093, 0x2094, 0x2095, 0x2096, 0x2097,
+ 0x2098, 0x2099, 0x209A, 0x209B, 0x209C, 0x209D, 0x209E, 0x209F,
+ /* A */ 0x20A0, 0x20A1, 0x20A2, 0x20A3, 0x20A4, 0x20A5, 0x20A6, 0x20A7,
+ 0x20A8, 0x20A9, 0x20AA, 0x20AB, 0x20AC, 0x20AD, 0x20AE, 0x20AF,
+ /* B */ 0x20B0, 0x20B1, 0x20B2, 0x20B3, 0x20B4, 0x20B5, 0x20B6, 0x20B7,
+ 0x20B8, 0x20B9, 0x20BA, 0x20BB, 0x20BC, 0x20BD, 0x20BE, 0x20BF,
+ /* C */ 0x20C0, 0x20C1, 0x20C2, 0x20C3, 0x20C4, 0x20C5, 0x20C6, 0x20C7,
+ 0x20C8, 0x20C9, 0x20CA, 0x20CB, 0x20CC, 0x20CD, 0x20CE, 0x20CF,
+ /* D */ 0x20D0, 0x20D1, 0x20D2, 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7,
+ 0x20D8, 0x20D9, 0x20DA, 0x20DB, 0x20DC, 0x20DD, 0x20DE, 0x20DF,
+ /* E */ 0x20E0, 0x20E1, 0x20E2, 0x20E3, 0x20E4, 0x20E5, 0x20E6, 0x20E7,
+ 0x20E8, 0x20E9, 0x20EA, 0x20EB, 0x20EC, 0x20ED, 0x20EE, 0x20EF,
+ /* F */ 0x20F0, 0x20F1, 0x20F2, 0x20F3, 0x20F4, 0x20F5, 0x20F6, 0x20F7,
+ 0x20F8, 0x20F9, 0x20FA, 0x20FB, 0x20FC, 0x20FD, 0x20FE, 0x20FF,
+
+ // Table 8 (for high byte 0x21)
+
+ /* 0 */ 0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107,
+ 0x2108, 0x2109, 0x210A, 0x210B, 0x210C, 0x210D, 0x210E, 0x210F,
+ /* 1 */ 0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117,
+ 0x2118, 0x2119, 0x211A, 0x211B, 0x211C, 0x211D, 0x211E, 0x211F,
+ /* 2 */ 0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x2126, 0x2127,
+ 0x2128, 0x2129, 0x212A, 0x212B, 0x212C, 0x212D, 0x212E, 0x212F,
+ /* 3 */ 0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137,
+ 0x2138, 0x2139, 0x213A, 0x213B, 0x213C, 0x213D, 0x213E, 0x213F,
+ /* 4 */ 0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147,
+ 0x2148, 0x2149, 0x214A, 0x214B, 0x214C, 0x214D, 0x214E, 0x214F,
+ /* 5 */ 0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157,
+ 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F,
+ /* 6 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177,
+ 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F,
+ /* 7 */ 0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177,
+ 0x2178, 0x2179, 0x217A, 0x217B, 0x217C, 0x217D, 0x217E, 0x217F,
+ /* 8 */ 0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187,
+ 0x2188, 0x2189, 0x218A, 0x218B, 0x218C, 0x218D, 0x218E, 0x218F,
+ /* 9 */ 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197,
+ 0x2198, 0x2199, 0x219A, 0x219B, 0x219C, 0x219D, 0x219E, 0x219F,
+ /* A */ 0x21A0, 0x21A1, 0x21A2, 0x21A3, 0x21A4, 0x21A5, 0x21A6, 0x21A7,
+ 0x21A8, 0x21A9, 0x21AA, 0x21AB, 0x21AC, 0x21AD, 0x21AE, 0x21AF,
+ /* B */ 0x21B0, 0x21B1, 0x21B2, 0x21B3, 0x21B4, 0x21B5, 0x21B6, 0x21B7,
+ 0x21B8, 0x21B9, 0x21BA, 0x21BB, 0x21BC, 0x21BD, 0x21BE, 0x21BF,
+ /* C */ 0x21C0, 0x21C1, 0x21C2, 0x21C3, 0x21C4, 0x21C5, 0x21C6, 0x21C7,
+ 0x21C8, 0x21C9, 0x21CA, 0x21CB, 0x21CC, 0x21CD, 0x21CE, 0x21CF,
+ /* D */ 0x21D0, 0x21D1, 0x21D2, 0x21D3, 0x21D4, 0x21D5, 0x21D6, 0x21D7,
+ 0x21D8, 0x21D9, 0x21DA, 0x21DB, 0x21DC, 0x21DD, 0x21DE, 0x21DF,
+ /* E */ 0x21E0, 0x21E1, 0x21E2, 0x21E3, 0x21E4, 0x21E5, 0x21E6, 0x21E7,
+ 0x21E8, 0x21E9, 0x21EA, 0x21EB, 0x21EC, 0x21ED, 0x21EE, 0x21EF,
+ /* F */ 0x21F0, 0x21F1, 0x21F2, 0x21F3, 0x21F4, 0x21F5, 0x21F6, 0x21F7,
+ 0x21F8, 0x21F9, 0x21FA, 0x21FB, 0x21FC, 0x21FD, 0x21FE, 0x21FF,
+
+ // Table 9 (for high byte 0xFE)
+
+ /* 0 */ 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07,
+ 0xFE08, 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F,
+ /* 1 */ 0xFE10, 0xFE11, 0xFE12, 0xFE13, 0xFE14, 0xFE15, 0xFE16, 0xFE17,
+ 0xFE18, 0xFE19, 0xFE1A, 0xFE1B, 0xFE1C, 0xFE1D, 0xFE1E, 0xFE1F,
+ /* 2 */ 0xFE20, 0xFE21, 0xFE22, 0xFE23, 0xFE24, 0xFE25, 0xFE26, 0xFE27,
+ 0xFE28, 0xFE29, 0xFE2A, 0xFE2B, 0xFE2C, 0xFE2D, 0xFE2E, 0xFE2F,
+ /* 3 */ 0xFE30, 0xFE31, 0xFE32, 0xFE33, 0xFE34, 0xFE35, 0xFE36, 0xFE37,
+ 0xFE38, 0xFE39, 0xFE3A, 0xFE3B, 0xFE3C, 0xFE3D, 0xFE3E, 0xFE3F,
+ /* 4 */ 0xFE40, 0xFE41, 0xFE42, 0xFE43, 0xFE44, 0xFE45, 0xFE46, 0xFE47,
+ 0xFE48, 0xFE49, 0xFE4A, 0xFE4B, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F,
+ /* 5 */ 0xFE50, 0xFE51, 0xFE52, 0xFE53, 0xFE54, 0xFE55, 0xFE56, 0xFE57,
+ 0xFE58, 0xFE59, 0xFE5A, 0xFE5B, 0xFE5C, 0xFE5D, 0xFE5E, 0xFE5F,
+ /* 6 */ 0xFE60, 0xFE61, 0xFE62, 0xFE63, 0xFE64, 0xFE65, 0xFE66, 0xFE67,
+ 0xFE68, 0xFE69, 0xFE6A, 0xFE6B, 0xFE6C, 0xFE6D, 0xFE6E, 0xFE6F,
+ /* 7 */ 0xFE70, 0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE75, 0xFE76, 0xFE77,
+ 0xFE78, 0xFE79, 0xFE7A, 0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F,
+ /* 8 */ 0xFE80, 0xFE81, 0xFE82, 0xFE83, 0xFE84, 0xFE85, 0xFE86, 0xFE87,
+ 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C, 0xFE8D, 0xFE8E, 0xFE8F,
+ /* 9 */ 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95, 0xFE96, 0xFE97,
+ 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E, 0xFE9F,
+ /* A */ 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7,
+ 0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF,
+ /* B */ 0xFEB0, 0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7,
+ 0xFEB8, 0xFEB9, 0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF,
+ /* C */ 0xFEC0, 0xFEC1, 0xFEC2, 0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7,
+ 0xFEC8, 0xFEC9, 0xFECA, 0xFECB, 0xFECC, 0xFECD, 0xFECE, 0xFECF,
+ /* D */ 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4, 0xFED5, 0xFED6, 0xFED7,
+ 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD, 0xFEDE, 0xFEDF,
+ /* E */ 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6, 0xFEE7,
+ 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF,
+ /* F */ 0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7,
+ 0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0xFEFD, 0xFEFE, 0x0000,
+
+ // Table 10 (for high byte 0xFF)
+
+ /* 0 */ 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07,
+ 0xFF08, 0xFF09, 0xFF0A, 0xFF0B, 0xFF0C, 0xFF0D, 0xFF0E, 0xFF0F,
+ /* 1 */ 0xFF10, 0xFF11, 0xFF12, 0xFF13, 0xFF14, 0xFF15, 0xFF16, 0xFF17,
+ 0xFF18, 0xFF19, 0xFF1A, 0xFF1B, 0xFF1C, 0xFF1D, 0xFF1E, 0xFF1F,
+ /* 2 */ 0xFF20, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47,
+ 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F,
+ /* 3 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57,
+ 0xFF58, 0xFF59, 0xFF5A, 0xFF3B, 0xFF3C, 0xFF3D, 0xFF3E, 0xFF3F,
+ /* 4 */ 0xFF40, 0xFF41, 0xFF42, 0xFF43, 0xFF44, 0xFF45, 0xFF46, 0xFF47,
+ 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFF4C, 0xFF4D, 0xFF4E, 0xFF4F,
+ /* 5 */ 0xFF50, 0xFF51, 0xFF52, 0xFF53, 0xFF54, 0xFF55, 0xFF56, 0xFF57,
+ 0xFF58, 0xFF59, 0xFF5A, 0xFF5B, 0xFF5C, 0xFF5D, 0xFF5E, 0xFF5F,
+ /* 6 */ 0xFF60, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67,
+ 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F,
+ /* 7 */ 0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77,
+ 0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F,
+ /* 8 */ 0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87,
+ 0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F,
+ /* 9 */ 0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97,
+ 0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F,
+ /* A */ 0xFFA0, 0xFFA1, 0xFFA2, 0xFFA3, 0xFFA4, 0xFFA5, 0xFFA6, 0xFFA7,
+ 0xFFA8, 0xFFA9, 0xFFAA, 0xFFAB, 0xFFAC, 0xFFAD, 0xFFAE, 0xFFAF,
+ /* B */ 0xFFB0, 0xFFB1, 0xFFB2, 0xFFB3, 0xFFB4, 0xFFB5, 0xFFB6, 0xFFB7,
+ 0xFFB8, 0xFFB9, 0xFFBA, 0xFFBB, 0xFFBC, 0xFFBD, 0xFFBE, 0xFFBF,
+ /* C */ 0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7,
+ 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF,
+ /* D */ 0xFFD0, 0xFFD1, 0xFFD2, 0xFFD3, 0xFFD4, 0xFFD5, 0xFFD6, 0xFFD7,
+ 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF,
+ /* E */ 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7,
+ 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF,
+ /* F */ 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7,
+ 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF,
+};
+
+u16 hfsplus_decompose_table[] = {
+ /* base table */
+ 0x0010, 0x04c0, 0x0000, 0x06f0, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0x07b0,
+ /* char table 0x0___ */
+ 0x0020, 0x0070, 0x0160, 0x0190, 0x0230, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x02d0, 0x0340, 0x0360, 0x03b0, 0x03e0, 0x0400, 0x0430,
+ /* char table 0x00__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0030, 0x0040, 0x0050, 0x0060,
+ /* char values 0x00c_ */
+ 0x2042, 0x204a, 0x2052, 0x205a, 0x2062, 0x206a, 0x0000, 0x2072,
+ 0x207a, 0x2082, 0x208a, 0x2092, 0x209a, 0x20a2, 0x20aa, 0x20b2,
+ /* char values 0x00d_ */
+ 0x0000, 0x20ba, 0x20c2, 0x20ca, 0x20d2, 0x20da, 0x20e2, 0x0000,
+ 0x0000, 0x20ea, 0x20f2, 0x20fa, 0x2102, 0x210a, 0x0000, 0x0000,
+ /* char values 0x00e_ */
+ 0x2112, 0x211a, 0x2122, 0x212a, 0x2132, 0x213a, 0x0000, 0x2142,
+ 0x214a, 0x2152, 0x215a, 0x2162, 0x216a, 0x2172, 0x217a, 0x2182,
+ /* char values 0x00f_ */
+ 0x0000, 0x218a, 0x2192, 0x219a, 0x21a2, 0x21aa, 0x21b2, 0x0000,
+ 0x0000, 0x21ba, 0x21c2, 0x21ca, 0x21d2, 0x21da, 0x0000, 0x21e2,
+ /* char table 0x01__ */
+ 0x0080, 0x0090, 0x00a0, 0x00b0, 0x00c0, 0x00d0, 0x00e0, 0x00f0,
+ 0x0000, 0x0000, 0x0100, 0x0110, 0x0120, 0x0130, 0x0140, 0x0150,
+ /* char values 0x010_ */
+ 0x21ea, 0x21f2, 0x21fa, 0x2202, 0x220a, 0x2212, 0x221a, 0x2222,
+ 0x222a, 0x2232, 0x223a, 0x2242, 0x224a, 0x2252, 0x225a, 0x2262,
+ /* char values 0x011_ */
+ 0x0000, 0x0000, 0x226a, 0x2272, 0x227a, 0x2282, 0x228a, 0x2292,
+ 0x229a, 0x22a2, 0x22aa, 0x22b2, 0x22ba, 0x22c2, 0x22ca, 0x22d2,
+ /* char values 0x012_ */
+ 0x22da, 0x22e2, 0x22ea, 0x22f2, 0x22fa, 0x2302, 0x0000, 0x0000,
+ 0x230a, 0x2312, 0x231a, 0x2322, 0x232a, 0x2332, 0x233a, 0x2342,
+ /* char values 0x013_ */
+ 0x234a, 0x0000, 0x0000, 0x0000, 0x2352, 0x235a, 0x2362, 0x236a,
+ 0x0000, 0x2372, 0x237a, 0x2382, 0x238a, 0x2392, 0x239a, 0x0000,
+ /* char values 0x014_ */
+ 0x0000, 0x0000, 0x0000, 0x23a2, 0x23aa, 0x23b2, 0x23ba, 0x23c2,
+ 0x23ca, 0x0000, 0x0000, 0x0000, 0x23d2, 0x23da, 0x23e2, 0x23ea,
+ /* char values 0x015_ */
+ 0x23f2, 0x23fa, 0x0000, 0x0000, 0x2402, 0x240a, 0x2412, 0x241a,
+ 0x2422, 0x242a, 0x2432, 0x243a, 0x2442, 0x244a, 0x2452, 0x245a,
+ /* char values 0x016_ */
+ 0x2462, 0x246a, 0x2472, 0x247a, 0x2482, 0x248a, 0x0000, 0x0000,
+ 0x2492, 0x249a, 0x24a2, 0x24aa, 0x24b2, 0x24ba, 0x24c2, 0x24ca,
+ /* char values 0x017_ */
+ 0x24d2, 0x24da, 0x24e2, 0x24ea, 0x24f2, 0x24fa, 0x2502, 0x250a,
+ 0x2512, 0x251a, 0x2522, 0x252a, 0x2532, 0x253a, 0x2542, 0x0000,
+ /* char values 0x01a_ */
+ 0x254a, 0x2552, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x255a,
+ /* char values 0x01b_ */
+ 0x2562, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x01c_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x256a, 0x2572, 0x257a,
+ /* char values 0x01d_ */
+ 0x2582, 0x258a, 0x2592, 0x259a, 0x25a2, 0x25ab, 0x25b7, 0x25c3,
+ 0x25cf, 0x25db, 0x25e7, 0x25f3, 0x25ff, 0x0000, 0x260b, 0x2617,
+ /* char values 0x01e_ */
+ 0x2623, 0x262f, 0x263a, 0x2642, 0x0000, 0x0000, 0x264a, 0x2652,
+ 0x265a, 0x2662, 0x266a, 0x2672, 0x267b, 0x2687, 0x2692, 0x269a,
+ /* char values 0x01f_ */
+ 0x26a2, 0x0000, 0x0000, 0x0000, 0x26aa, 0x26b2, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x26bb, 0x26c7, 0x26d2, 0x26da, 0x26e2, 0x26ea,
+ /* char table 0x02__ */
+ 0x0170, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x020_ */
+ 0x26f2, 0x26fa, 0x2702, 0x270a, 0x2712, 0x271a, 0x2722, 0x272a,
+ 0x2732, 0x273a, 0x2742, 0x274a, 0x2752, 0x275a, 0x2762, 0x276a,
+ /* char values 0x021_ */
+ 0x2772, 0x277a, 0x2782, 0x278a, 0x2792, 0x279a, 0x27a2, 0x27aa,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x03__ */
+ 0x0000, 0x01a0, 0x0000, 0x0000, 0x01b0, 0x0000, 0x0000, 0x01c0,
+ 0x01d0, 0x01e0, 0x01f0, 0x0200, 0x0210, 0x0220, 0x0000, 0x0000,
+ /* char values 0x031_ */
+ 0x27b2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x034_ */
+ 0x27b9, 0x27bd, 0x0000, 0x27c1, 0x27c6, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x037_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x27cd, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x27d1, 0x0000,
+ /* char values 0x038_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x27d6, 0x27de, 0x27e5,
+ 0x27ea, 0x27f2, 0x27fa, 0x0000, 0x2802, 0x0000, 0x280a, 0x2812,
+ /* char values 0x039_ */
+ 0x281b, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x03a_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2826, 0x282e, 0x2836, 0x283e, 0x2846, 0x284e,
+ /* char values 0x03b_ */
+ 0x2857, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x03c_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2862, 0x286a, 0x2872, 0x287a, 0x2882, 0x0000,
+ /* char values 0x03d_ */
+ 0x0000, 0x0000, 0x0000, 0x288a, 0x2892, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x04__ */
+ 0x0240, 0x0250, 0x0000, 0x0260, 0x0000, 0x0270, 0x0000, 0x0280,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0290, 0x02a0, 0x02b0, 0x02c0,
+ /* char values 0x040_ */
+ 0x0000, 0x289a, 0x0000, 0x28a2, 0x0000, 0x0000, 0x0000, 0x28aa,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x28b2, 0x0000, 0x28ba, 0x0000,
+ /* char values 0x041_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x28c2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x043_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x28ca, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x045_ */
+ 0x0000, 0x28d2, 0x0000, 0x28da, 0x0000, 0x0000, 0x0000, 0x28e2,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x28ea, 0x0000, 0x28f2, 0x0000,
+ /* char values 0x047_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x28fa, 0x2902,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x04c_ */
+ 0x0000, 0x290a, 0x2912, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x04d_ */
+ 0x291a, 0x2922, 0x292a, 0x2932, 0x2939, 0x293d, 0x2942, 0x294a,
+ 0x2951, 0x2955, 0x295a, 0x2962, 0x296a, 0x2972, 0x297a, 0x2982,
+ /* char values 0x04e_ */
+ 0x2989, 0x298d, 0x2992, 0x299a, 0x29a2, 0x29aa, 0x29b2, 0x29ba,
+ 0x29c1, 0x29c5, 0x29ca, 0x29d2, 0x0000, 0x0000, 0x29da, 0x29e2,
+ /* char values 0x04f_ */
+ 0x29ea, 0x29f2, 0x29fa, 0x2a02, 0x2a0a, 0x2a12, 0x0000, 0x0000,
+ 0x2a1a, 0x2a22, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x09__ */
+ 0x0000, 0x0000, 0x02e0, 0x02f0, 0x0000, 0x0300, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0310, 0x0320, 0x0330, 0x0000, 0x0000,
+ /* char values 0x092_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x2a2a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x093_ */
+ 0x0000, 0x2a32, 0x0000, 0x0000, 0x2a3a, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x095_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x2a42, 0x2a4a, 0x2a52, 0x2a5a, 0x2a62, 0x2a6a, 0x2a72, 0x2a7a,
+ /* char values 0x09b_ */
+ 0x2a82, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x09c_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x2a8a, 0x2a92, 0x0000, 0x0000, 0x0000,
+ /* char values 0x09d_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x2a9a, 0x2aa2, 0x0000, 0x2aaa,
+ /* char table 0x0a__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0350, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0a5_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x2ab2, 0x2aba, 0x2ac2, 0x2aca, 0x0000, 0x2ad2, 0x0000,
+ /* char table 0x0b__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0370, 0x0380, 0x0000, 0x0000,
+ 0x0000, 0x0390, 0x0000, 0x0000, 0x03a0, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0b4_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x2ada, 0x0000, 0x0000, 0x2ae2, 0x2aea, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0b5_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x2af2, 0x2afa, 0x0000, 0x2b02,
+ /* char values 0x0b9_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x2b0a, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0bc_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2b12, 0x2b1a, 0x2b22, 0x0000, 0x0000, 0x0000,
+ /* char table 0x0c__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x03c0, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x03d0, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0c4_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x2b2a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0cc_ */
+ 0x2b32, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2b3a,
+ 0x2b42, 0x0000, 0x2b4a, 0x2b53, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x0d__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x03f0, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0d4_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2b5e, 0x2b66, 0x2b6e, 0x0000, 0x0000, 0x0000,
+ /* char table 0x0e__ */
+ 0x0000, 0x0000, 0x0000, 0x0410, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0420, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0e3_ */
+ 0x0000, 0x0000, 0x0000, 0x2b76, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0eb_ */
+ 0x0000, 0x0000, 0x0000, 0x2b7e, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x0f__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0440, 0x0450, 0x0460, 0x0470,
+ 0x0480, 0x0490, 0x04a0, 0x04b0, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0f4_ */
+ 0x0000, 0x0000, 0x0000, 0x2b86, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2b8e, 0x0000, 0x0000,
+ /* char values 0x0f5_ */
+ 0x0000, 0x0000, 0x2b96, 0x0000, 0x0000, 0x0000, 0x0000, 0x2b9e,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x2ba6, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0f6_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x2bae, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0f7_ */
+ 0x0000, 0x0000, 0x0000, 0x2bb6, 0x0000, 0x2bbe, 0x2bc6, 0x2bcf,
+ 0x2bda, 0x2be3, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0f8_ */
+ 0x0000, 0x2bee, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0f9_ */
+ 0x0000, 0x0000, 0x0000, 0x2bf6, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2bfe, 0x0000, 0x0000,
+ /* char values 0x0fa_ */
+ 0x0000, 0x0000, 0x2c06, 0x0000, 0x0000, 0x0000, 0x0000, 0x2c0e,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x2c16, 0x0000, 0x0000, 0x0000,
+ /* char values 0x0fb_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x2c1e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x1___ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x04d0, 0x05e0,
+ /* char table 0x1e__ */
+ 0x04e0, 0x04f0, 0x0500, 0x0510, 0x0520, 0x0530, 0x0540, 0x0550,
+ 0x0560, 0x0570, 0x0580, 0x0590, 0x05a0, 0x05b0, 0x05c0, 0x05d0,
+ /* char values 0x1e0_ */
+ 0x2c26, 0x2c2e, 0x2c36, 0x2c3e, 0x2c46, 0x2c4e, 0x2c56, 0x2c5e,
+ 0x2c67, 0x2c73, 0x2c7e, 0x2c86, 0x2c8e, 0x2c96, 0x2c9e, 0x2ca6,
+ /* char values 0x1e1_ */
+ 0x2cae, 0x2cb6, 0x2cbe, 0x2cc6, 0x2ccf, 0x2cdb, 0x2ce7, 0x2cf3,
+ 0x2cfe, 0x2d06, 0x2d0e, 0x2d16, 0x2d1f, 0x2d2b, 0x2d36, 0x2d3e,
+ /* char values 0x1e2_ */
+ 0x2d46, 0x2d4e, 0x2d56, 0x2d5e, 0x2d66, 0x2d6e, 0x2d76, 0x2d7e,
+ 0x2d86, 0x2d8e, 0x2d96, 0x2d9e, 0x2da6, 0x2dae, 0x2db7, 0x2dc3,
+ /* char values 0x1e3_ */
+ 0x2dce, 0x2dd6, 0x2dde, 0x2de6, 0x2dee, 0x2df6, 0x2dfe, 0x2e06,
+ 0x2e0f, 0x2e1b, 0x2e26, 0x2e2e, 0x2e36, 0x2e3e, 0x2e46, 0x2e4e,
+ /* char values 0x1e4_ */
+ 0x2e56, 0x2e5e, 0x2e66, 0x2e6e, 0x2e76, 0x2e7e, 0x2e86, 0x2e8e,
+ 0x2e96, 0x2e9e, 0x2ea6, 0x2eae, 0x2eb7, 0x2ec3, 0x2ecf, 0x2edb,
+ /* char values 0x1e5_ */
+ 0x2ee7, 0x2ef3, 0x2eff, 0x2f0b, 0x2f16, 0x2f1e, 0x2f26, 0x2f2e,
+ 0x2f36, 0x2f3e, 0x2f46, 0x2f4e, 0x2f57, 0x2f63, 0x2f6e, 0x2f76,
+ /* char values 0x1e6_ */
+ 0x2f7e, 0x2f86, 0x2f8e, 0x2f96, 0x2f9f, 0x2fab, 0x2fb7, 0x2fc3,
+ 0x2fcf, 0x2fdb, 0x2fe6, 0x2fee, 0x2ff6, 0x2ffe, 0x3006, 0x300e,
+ /* char values 0x1e7_ */
+ 0x3016, 0x301e, 0x3026, 0x302e, 0x3036, 0x303e, 0x3046, 0x304e,
+ 0x3057, 0x3063, 0x306f, 0x307b, 0x3086, 0x308e, 0x3096, 0x309e,
+ /* char values 0x1e8_ */
+ 0x30a6, 0x30ae, 0x30b6, 0x30be, 0x30c6, 0x30ce, 0x30d6, 0x30de,
+ 0x30e6, 0x30ee, 0x30f6, 0x30fe, 0x3106, 0x310e, 0x3116, 0x311e,
+ /* char values 0x1e9_ */
+ 0x3126, 0x312e, 0x3136, 0x313e, 0x3146, 0x314e, 0x3156, 0x315e,
+ 0x3166, 0x316e, 0x0000, 0x3176, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x1ea_ */
+ 0x317e, 0x3186, 0x318e, 0x3196, 0x319f, 0x31ab, 0x31b7, 0x31c3,
+ 0x31cf, 0x31db, 0x31e7, 0x31f3, 0x31ff, 0x320b, 0x3217, 0x3223,
+ /* char values 0x1eb_ */
+ 0x322f, 0x323b, 0x3247, 0x3253, 0x325f, 0x326b, 0x3277, 0x3283,
+ 0x328e, 0x3296, 0x329e, 0x32a6, 0x32ae, 0x32b6, 0x32bf, 0x32cb,
+ /* char values 0x1ec_ */
+ 0x32d7, 0x32e3, 0x32ef, 0x32fb, 0x3307, 0x3313, 0x331f, 0x332b,
+ 0x3336, 0x333e, 0x3346, 0x334e, 0x3356, 0x335e, 0x3366, 0x336e,
+ /* char values 0x1ed_ */
+ 0x3377, 0x3383, 0x338f, 0x339b, 0x33a7, 0x33b3, 0x33bf, 0x33cb,
+ 0x33d7, 0x33e3, 0x33ef, 0x33fb, 0x3407, 0x3413, 0x341f, 0x342b,
+ /* char values 0x1ee_ */
+ 0x3437, 0x3443, 0x344f, 0x345b, 0x3466, 0x346e, 0x3476, 0x347e,
+ 0x3487, 0x3493, 0x349f, 0x34ab, 0x34b7, 0x34c3, 0x34cf, 0x34db,
+ /* char values 0x1ef_ */
+ 0x34e7, 0x34f3, 0x34fe, 0x3506, 0x350e, 0x3516, 0x351e, 0x3526,
+ 0x352e, 0x3536, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x1f__ */
+ 0x05f0, 0x0600, 0x0610, 0x0620, 0x0630, 0x0640, 0x0650, 0x0660,
+ 0x0670, 0x0680, 0x0690, 0x06a0, 0x06b0, 0x06c0, 0x06d0, 0x06e0,
+ /* char values 0x1f0_ */
+ 0x353e, 0x3546, 0x354f, 0x355b, 0x3567, 0x3573, 0x357f, 0x358b,
+ 0x3596, 0x359e, 0x35a7, 0x35b3, 0x35bf, 0x35cb, 0x35d7, 0x35e3,
+ /* char values 0x1f1_ */
+ 0x35ee, 0x35f6, 0x35ff, 0x360b, 0x3617, 0x3623, 0x0000, 0x0000,
+ 0x362e, 0x3636, 0x363f, 0x364b, 0x3657, 0x3663, 0x0000, 0x0000,
+ /* char values 0x1f2_ */
+ 0x366e, 0x3676, 0x367f, 0x368b, 0x3697, 0x36a3, 0x36af, 0x36bb,
+ 0x36c6, 0x36ce, 0x36d7, 0x36e3, 0x36ef, 0x36fb, 0x3707, 0x3713,
+ /* char values 0x1f3_ */
+ 0x371e, 0x3726, 0x372f, 0x373b, 0x3747, 0x3753, 0x375f, 0x376b,
+ 0x3776, 0x377e, 0x3787, 0x3793, 0x379f, 0x37ab, 0x37b7, 0x37c3,
+ /* char values 0x1f4_ */
+ 0x37ce, 0x37d6, 0x37df, 0x37eb, 0x37f7, 0x3803, 0x0000, 0x0000,
+ 0x380e, 0x3816, 0x381f, 0x382b, 0x3837, 0x3843, 0x0000, 0x0000,
+ /* char values 0x1f5_ */
+ 0x384e, 0x3856, 0x385f, 0x386b, 0x3877, 0x3883, 0x388f, 0x389b,
+ 0x0000, 0x38a6, 0x0000, 0x38af, 0x0000, 0x38bb, 0x0000, 0x38c7,
+ /* char values 0x1f6_ */
+ 0x38d2, 0x38da, 0x38e3, 0x38ef, 0x38fb, 0x3907, 0x3913, 0x391f,
+ 0x392a, 0x3932, 0x393b, 0x3947, 0x3953, 0x395f, 0x396b, 0x3977,
+ /* char values 0x1f7_ */
+ 0x3982, 0x398a, 0x3992, 0x399a, 0x39a2, 0x39aa, 0x39b2, 0x39ba,
+ 0x39c2, 0x39ca, 0x39d2, 0x39da, 0x39e2, 0x39ea, 0x0000, 0x0000,
+ /* char values 0x1f8_ */
+ 0x39f3, 0x39ff, 0x3a0c, 0x3a1c, 0x3a2c, 0x3a3c, 0x3a4c, 0x3a5c,
+ 0x3a6b, 0x3a77, 0x3a84, 0x3a94, 0x3aa4, 0x3ab4, 0x3ac4, 0x3ad4,
+ /* char values 0x1f9_ */
+ 0x3ae3, 0x3aef, 0x3afc, 0x3b0c, 0x3b1c, 0x3b2c, 0x3b3c, 0x3b4c,
+ 0x3b5b, 0x3b67, 0x3b74, 0x3b84, 0x3b94, 0x3ba4, 0x3bb4, 0x3bc4,
+ /* char values 0x1fa_ */
+ 0x3bd3, 0x3bdf, 0x3bec, 0x3bfc, 0x3c0c, 0x3c1c, 0x3c2c, 0x3c3c,
+ 0x3c4b, 0x3c57, 0x3c64, 0x3c74, 0x3c84, 0x3c94, 0x3ca4, 0x3cb4,
+ /* char values 0x1fb_ */
+ 0x3cc2, 0x3cca, 0x3cd3, 0x3cde, 0x3ce7, 0x0000, 0x3cf2, 0x3cfb,
+ 0x3d06, 0x3d0e, 0x3d16, 0x3d1e, 0x3d26, 0x0000, 0x3d2d, 0x0000,
+ /* char values 0x1fc_ */
+ 0x0000, 0x3d32, 0x3d3b, 0x3d46, 0x3d4f, 0x0000, 0x3d5a, 0x3d63,
+ 0x3d6e, 0x3d76, 0x3d7e, 0x3d86, 0x3d8e, 0x3d96, 0x3d9e, 0x3da6,
+ /* char values 0x1fd_ */
+ 0x3dae, 0x3db6, 0x3dbf, 0x3dcb, 0x0000, 0x0000, 0x3dd6, 0x3ddf,
+ 0x3dea, 0x3df2, 0x3dfa, 0x3e02, 0x0000, 0x3e0a, 0x3e12, 0x3e1a,
+ /* char values 0x1fe_ */
+ 0x3e22, 0x3e2a, 0x3e33, 0x3e3f, 0x3e4a, 0x3e52, 0x3e5a, 0x3e63,
+ 0x3e6e, 0x3e76, 0x3e7e, 0x3e86, 0x3e8e, 0x3e96, 0x3e9e, 0x3ea5,
+ /* char values 0x1ff_ */
+ 0x0000, 0x0000, 0x3eab, 0x3eb6, 0x3ebf, 0x0000, 0x3eca, 0x3ed3,
+ 0x3ede, 0x3ee6, 0x3eee, 0x3ef6, 0x3efe, 0x3f05, 0x0000, 0x0000,
+ /* char table 0x3___ */
+ 0x0700, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0x30__ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0710, 0x0720, 0x0730, 0x0740,
+ 0x0000, 0x0750, 0x0760, 0x0770, 0x0780, 0x0790, 0x0000, 0x07a0,
+ /* char values 0x304_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x3f0a, 0x0000, 0x3f12, 0x0000,
+ /* char values 0x305_ */
+ 0x3f1a, 0x0000, 0x3f22, 0x0000, 0x3f2a, 0x0000, 0x3f32, 0x0000,
+ 0x3f3a, 0x0000, 0x3f42, 0x0000, 0x3f4a, 0x0000, 0x3f52, 0x0000,
+ /* char values 0x306_ */
+ 0x3f5a, 0x0000, 0x3f62, 0x0000, 0x0000, 0x3f6a, 0x0000, 0x3f72,
+ 0x0000, 0x3f7a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x307_ */
+ 0x3f82, 0x3f8a, 0x0000, 0x3f92, 0x3f9a, 0x0000, 0x3fa2, 0x3faa,
+ 0x0000, 0x3fb2, 0x3fba, 0x0000, 0x3fc2, 0x3fca, 0x0000, 0x0000,
+ /* char values 0x309_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x3fd2, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3fda, 0x0000,
+ /* char values 0x30a_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x3fe2, 0x0000, 0x3fea, 0x0000,
+ /* char values 0x30b_ */
+ 0x3ff2, 0x0000, 0x3ffa, 0x0000, 0x4002, 0x0000, 0x400a, 0x0000,
+ 0x4012, 0x0000, 0x401a, 0x0000, 0x4022, 0x0000, 0x402a, 0x0000,
+ /* char values 0x30c_ */
+ 0x4032, 0x0000, 0x403a, 0x0000, 0x0000, 0x4042, 0x0000, 0x404a,
+ 0x0000, 0x4052, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0x30d_ */
+ 0x405a, 0x4062, 0x0000, 0x406a, 0x4072, 0x0000, 0x407a, 0x4082,
+ 0x0000, 0x408a, 0x4092, 0x0000, 0x409a, 0x40a2, 0x0000, 0x0000,
+ /* char values 0x30f_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x40aa, 0x0000, 0x0000, 0x40b2,
+ 0x40ba, 0x40c2, 0x40ca, 0x0000, 0x0000, 0x0000, 0x40d2, 0x0000,
+ /* char table 0xf___ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x07c0, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char table 0xfb__ */
+ 0x0000, 0x07d0, 0x07e0, 0x07f0, 0x0800, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ /* char values 0xfb1_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x40da,
+ /* char values 0xfb2_ */
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x40e2, 0x40ea, 0x40f3, 0x40ff, 0x410a, 0x4112,
+ /* char values 0xfb3_ */
+ 0x411a, 0x4122, 0x412a, 0x4132, 0x413a, 0x4142, 0x414a, 0x0000,
+ 0x4152, 0x415a, 0x4162, 0x416a, 0x4172, 0x0000, 0x417a, 0x0000,
+ /* char values 0xfb4_ */
+ 0x4182, 0x418a, 0x0000, 0x4192, 0x419a, 0x0000, 0x41a2, 0x41aa,
+ 0x41b2, 0x41ba, 0x41c2, 0x41ca, 0x41d2, 0x41da, 0x41e2, 0x0000,
+ /* decomposed characters */
+ 0x0041, 0x0300, 0x0041, 0x0301, 0x0041, 0x0302, 0x0041, 0x0303,
+ 0x0041, 0x0308, 0x0041, 0x030a, 0x0043, 0x0327, 0x0045, 0x0300,
+ 0x0045, 0x0301, 0x0045, 0x0302, 0x0045, 0x0308, 0x0049, 0x0300,
+ 0x0049, 0x0301, 0x0049, 0x0302, 0x0049, 0x0308, 0x004e, 0x0303,
+ 0x004f, 0x0300, 0x004f, 0x0301, 0x004f, 0x0302, 0x004f, 0x0303,
+ 0x004f, 0x0308, 0x0055, 0x0300, 0x0055, 0x0301, 0x0055, 0x0302,
+ 0x0055, 0x0308, 0x0059, 0x0301, 0x0061, 0x0300, 0x0061, 0x0301,
+ 0x0061, 0x0302, 0x0061, 0x0303, 0x0061, 0x0308, 0x0061, 0x030a,
+ 0x0063, 0x0327, 0x0065, 0x0300, 0x0065, 0x0301, 0x0065, 0x0302,
+ 0x0065, 0x0308, 0x0069, 0x0300, 0x0069, 0x0301, 0x0069, 0x0302,
+ 0x0069, 0x0308, 0x006e, 0x0303, 0x006f, 0x0300, 0x006f, 0x0301,
+ 0x006f, 0x0302, 0x006f, 0x0303, 0x006f, 0x0308, 0x0075, 0x0300,
+ 0x0075, 0x0301, 0x0075, 0x0302, 0x0075, 0x0308, 0x0079, 0x0301,
+ 0x0079, 0x0308, 0x0041, 0x0304, 0x0061, 0x0304, 0x0041, 0x0306,
+ 0x0061, 0x0306, 0x0041, 0x0328, 0x0061, 0x0328, 0x0043, 0x0301,
+ 0x0063, 0x0301, 0x0043, 0x0302, 0x0063, 0x0302, 0x0043, 0x0307,
+ 0x0063, 0x0307, 0x0043, 0x030c, 0x0063, 0x030c, 0x0044, 0x030c,
+ 0x0064, 0x030c, 0x0045, 0x0304, 0x0065, 0x0304, 0x0045, 0x0306,
+ 0x0065, 0x0306, 0x0045, 0x0307, 0x0065, 0x0307, 0x0045, 0x0328,
+ 0x0065, 0x0328, 0x0045, 0x030c, 0x0065, 0x030c, 0x0047, 0x0302,
+ 0x0067, 0x0302, 0x0047, 0x0306, 0x0067, 0x0306, 0x0047, 0x0307,
+ 0x0067, 0x0307, 0x0047, 0x0327, 0x0067, 0x0327, 0x0048, 0x0302,
+ 0x0068, 0x0302, 0x0049, 0x0303, 0x0069, 0x0303, 0x0049, 0x0304,
+ 0x0069, 0x0304, 0x0049, 0x0306, 0x0069, 0x0306, 0x0049, 0x0328,
+ 0x0069, 0x0328, 0x0049, 0x0307, 0x004a, 0x0302, 0x006a, 0x0302,
+ 0x004b, 0x0327, 0x006b, 0x0327, 0x004c, 0x0301, 0x006c, 0x0301,
+ 0x004c, 0x0327, 0x006c, 0x0327, 0x004c, 0x030c, 0x006c, 0x030c,
+ 0x004e, 0x0301, 0x006e, 0x0301, 0x004e, 0x0327, 0x006e, 0x0327,
+ 0x004e, 0x030c, 0x006e, 0x030c, 0x004f, 0x0304, 0x006f, 0x0304,
+ 0x004f, 0x0306, 0x006f, 0x0306, 0x004f, 0x030b, 0x006f, 0x030b,
+ 0x0052, 0x0301, 0x0072, 0x0301, 0x0052, 0x0327, 0x0072, 0x0327,
+ 0x0052, 0x030c, 0x0072, 0x030c, 0x0053, 0x0301, 0x0073, 0x0301,
+ 0x0053, 0x0302, 0x0073, 0x0302, 0x0053, 0x0327, 0x0073, 0x0327,
+ 0x0053, 0x030c, 0x0073, 0x030c, 0x0054, 0x0327, 0x0074, 0x0327,
+ 0x0054, 0x030c, 0x0074, 0x030c, 0x0055, 0x0303, 0x0075, 0x0303,
+ 0x0055, 0x0304, 0x0075, 0x0304, 0x0055, 0x0306, 0x0075, 0x0306,
+ 0x0055, 0x030a, 0x0075, 0x030a, 0x0055, 0x030b, 0x0075, 0x030b,
+ 0x0055, 0x0328, 0x0075, 0x0328, 0x0057, 0x0302, 0x0077, 0x0302,
+ 0x0059, 0x0302, 0x0079, 0x0302, 0x0059, 0x0308, 0x005a, 0x0301,
+ 0x007a, 0x0301, 0x005a, 0x0307, 0x007a, 0x0307, 0x005a, 0x030c,
+ 0x007a, 0x030c, 0x004f, 0x031b, 0x006f, 0x031b, 0x0055, 0x031b,
+ 0x0075, 0x031b, 0x0041, 0x030c, 0x0061, 0x030c, 0x0049, 0x030c,
+ 0x0069, 0x030c, 0x004f, 0x030c, 0x006f, 0x030c, 0x0055, 0x030c,
+ 0x0075, 0x030c, 0x0055, 0x0308, 0x0304, 0x0075, 0x0308, 0x0304,
+ 0x0055, 0x0308, 0x0301, 0x0075, 0x0308, 0x0301, 0x0055, 0x0308,
+ 0x030c, 0x0075, 0x0308, 0x030c, 0x0055, 0x0308, 0x0300, 0x0075,
+ 0x0308, 0x0300, 0x0041, 0x0308, 0x0304, 0x0061, 0x0308, 0x0304,
+ 0x0041, 0x0307, 0x0304, 0x0061, 0x0307, 0x0304, 0x00c6, 0x0304,
+ 0x00e6, 0x0304, 0x0047, 0x030c, 0x0067, 0x030c, 0x004b, 0x030c,
+ 0x006b, 0x030c, 0x004f, 0x0328, 0x006f, 0x0328, 0x004f, 0x0328,
+ 0x0304, 0x006f, 0x0328, 0x0304, 0x01b7, 0x030c, 0x0292, 0x030c,
+ 0x006a, 0x030c, 0x0047, 0x0301, 0x0067, 0x0301, 0x0041, 0x030a,
+ 0x0301, 0x0061, 0x030a, 0x0301, 0x00c6, 0x0301, 0x00e6, 0x0301,
+ 0x00d8, 0x0301, 0x00f8, 0x0301, 0x0041, 0x030f, 0x0061, 0x030f,
+ 0x0041, 0x0311, 0x0061, 0x0311, 0x0045, 0x030f, 0x0065, 0x030f,
+ 0x0045, 0x0311, 0x0065, 0x0311, 0x0049, 0x030f, 0x0069, 0x030f,
+ 0x0049, 0x0311, 0x0069, 0x0311, 0x004f, 0x030f, 0x006f, 0x030f,
+ 0x004f, 0x0311, 0x006f, 0x0311, 0x0052, 0x030f, 0x0072, 0x030f,
+ 0x0052, 0x0311, 0x0072, 0x0311, 0x0055, 0x030f, 0x0075, 0x030f,
+ 0x0055, 0x0311, 0x0075, 0x0311, 0x0306, 0x0307, 0x0300, 0x0301,
+ 0x0313, 0x0308, 0x030d, 0x02b9, 0x003b, 0x00a8, 0x030d, 0x0391,
+ 0x030d, 0x00b7, 0x0395, 0x030d, 0x0397, 0x030d, 0x0399, 0x030d,
+ 0x039f, 0x030d, 0x03a5, 0x030d, 0x03a9, 0x030d, 0x03b9, 0x0308,
+ 0x030d, 0x0399, 0x0308, 0x03a5, 0x0308, 0x03b1, 0x030d, 0x03b5,
+ 0x030d, 0x03b7, 0x030d, 0x03b9, 0x030d, 0x03c5, 0x0308, 0x030d,
+ 0x03b9, 0x0308, 0x03c5, 0x0308, 0x03bf, 0x030d, 0x03c5, 0x030d,
+ 0x03c9, 0x030d, 0x03d2, 0x030d, 0x03d2, 0x0308, 0x0415, 0x0308,
+ 0x0413, 0x0301, 0x0406, 0x0308, 0x041a, 0x0301, 0x0423, 0x0306,
+ 0x0418, 0x0306, 0x0438, 0x0306, 0x0435, 0x0308, 0x0433, 0x0301,
+ 0x0456, 0x0308, 0x043a, 0x0301, 0x0443, 0x0306, 0x0474, 0x030f,
+ 0x0475, 0x030f, 0x0416, 0x0306, 0x0436, 0x0306, 0x0410, 0x0306,
+ 0x0430, 0x0306, 0x0410, 0x0308, 0x0430, 0x0308, 0x00c6, 0x00e6,
+ 0x0415, 0x0306, 0x0435, 0x0306, 0x018f, 0x0259, 0x018f, 0x0308,
+ 0x0259, 0x0308, 0x0416, 0x0308, 0x0436, 0x0308, 0x0417, 0x0308,
+ 0x0437, 0x0308, 0x01b7, 0x0292, 0x0418, 0x0304, 0x0438, 0x0304,
+ 0x0418, 0x0308, 0x0438, 0x0308, 0x041e, 0x0308, 0x043e, 0x0308,
+ 0x019f, 0x0275, 0x019f, 0x0308, 0x0275, 0x0308, 0x0423, 0x0304,
+ 0x0443, 0x0304, 0x0423, 0x0308, 0x0443, 0x0308, 0x0423, 0x030b,
+ 0x0443, 0x030b, 0x0427, 0x0308, 0x0447, 0x0308, 0x042b, 0x0308,
+ 0x044b, 0x0308, 0x0928, 0x093c, 0x0930, 0x093c, 0x0933, 0x093c,
+ 0x0915, 0x093c, 0x0916, 0x093c, 0x0917, 0x093c, 0x091c, 0x093c,
+ 0x0921, 0x093c, 0x0922, 0x093c, 0x092b, 0x093c, 0x092f, 0x093c,
+ 0x09ac, 0x09bc, 0x09c7, 0x09be, 0x09c7, 0x09d7, 0x09a1, 0x09bc,
+ 0x09a2, 0x09bc, 0x09af, 0x09bc, 0x0a16, 0x0a3c, 0x0a17, 0x0a3c,
+ 0x0a1c, 0x0a3c, 0x0a21, 0x0a3c, 0x0a2b, 0x0a3c, 0x0b47, 0x0b56,
+ 0x0b47, 0x0b3e, 0x0b47, 0x0b57, 0x0b21, 0x0b3c, 0x0b22, 0x0b3c,
+ 0x0b2f, 0x0b3c, 0x0b92, 0x0bd7, 0x0bc6, 0x0bbe, 0x0bc7, 0x0bbe,
+ 0x0bc6, 0x0bd7, 0x0c46, 0x0c56, 0x0cbf, 0x0cd5, 0x0cc6, 0x0cd5,
+ 0x0cc6, 0x0cd6, 0x0cc6, 0x0cc2, 0x0cc6, 0x0cc2, 0x0cd5, 0x0d46,
+ 0x0d3e, 0x0d47, 0x0d3e, 0x0d46, 0x0d57, 0x0e4d, 0x0e32, 0x0ecd,
+ 0x0eb2, 0x0f42, 0x0fb7, 0x0f4c, 0x0fb7, 0x0f51, 0x0fb7, 0x0f56,
+ 0x0fb7, 0x0f5b, 0x0fb7, 0x0f40, 0x0fb5, 0x0f72, 0x0f71, 0x0f74,
+ 0x0f71, 0x0fb2, 0x0f80, 0x0fb2, 0x0f80, 0x0f71, 0x0fb3, 0x0f80,
+ 0x0fb3, 0x0f80, 0x0f71, 0x0f80, 0x0f71, 0x0f92, 0x0fb7, 0x0f9c,
+ 0x0fb7, 0x0fa1, 0x0fb7, 0x0fa6, 0x0fb7, 0x0fab, 0x0fb7, 0x0f90,
+ 0x0fb5, 0x0041, 0x0325, 0x0061, 0x0325, 0x0042, 0x0307, 0x0062,
+ 0x0307, 0x0042, 0x0323, 0x0062, 0x0323, 0x0042, 0x0331, 0x0062,
+ 0x0331, 0x0043, 0x0327, 0x0301, 0x0063, 0x0327, 0x0301, 0x0044,
+ 0x0307, 0x0064, 0x0307, 0x0044, 0x0323, 0x0064, 0x0323, 0x0044,
+ 0x0331, 0x0064, 0x0331, 0x0044, 0x0327, 0x0064, 0x0327, 0x0044,
+ 0x032d, 0x0064, 0x032d, 0x0045, 0x0304, 0x0300, 0x0065, 0x0304,
+ 0x0300, 0x0045, 0x0304, 0x0301, 0x0065, 0x0304, 0x0301, 0x0045,
+ 0x032d, 0x0065, 0x032d, 0x0045, 0x0330, 0x0065, 0x0330, 0x0045,
+ 0x0327, 0x0306, 0x0065, 0x0327, 0x0306, 0x0046, 0x0307, 0x0066,
+ 0x0307, 0x0047, 0x0304, 0x0067, 0x0304, 0x0048, 0x0307, 0x0068,
+ 0x0307, 0x0048, 0x0323, 0x0068, 0x0323, 0x0048, 0x0308, 0x0068,
+ 0x0308, 0x0048, 0x0327, 0x0068, 0x0327, 0x0048, 0x032e, 0x0068,
+ 0x032e, 0x0049, 0x0330, 0x0069, 0x0330, 0x0049, 0x0308, 0x0301,
+ 0x0069, 0x0308, 0x0301, 0x004b, 0x0301, 0x006b, 0x0301, 0x004b,
+ 0x0323, 0x006b, 0x0323, 0x004b, 0x0331, 0x006b, 0x0331, 0x004c,
+ 0x0323, 0x006c, 0x0323, 0x004c, 0x0323, 0x0304, 0x006c, 0x0323,
+ 0x0304, 0x004c, 0x0331, 0x006c, 0x0331, 0x004c, 0x032d, 0x006c,
+ 0x032d, 0x004d, 0x0301, 0x006d, 0x0301, 0x004d, 0x0307, 0x006d,
+ 0x0307, 0x004d, 0x0323, 0x006d, 0x0323, 0x004e, 0x0307, 0x006e,
+ 0x0307, 0x004e, 0x0323, 0x006e, 0x0323, 0x004e, 0x0331, 0x006e,
+ 0x0331, 0x004e, 0x032d, 0x006e, 0x032d, 0x004f, 0x0303, 0x0301,
+ 0x006f, 0x0303, 0x0301, 0x004f, 0x0303, 0x0308, 0x006f, 0x0303,
+ 0x0308, 0x004f, 0x0304, 0x0300, 0x006f, 0x0304, 0x0300, 0x004f,
+ 0x0304, 0x0301, 0x006f, 0x0304, 0x0301, 0x0050, 0x0301, 0x0070,
+ 0x0301, 0x0050, 0x0307, 0x0070, 0x0307, 0x0052, 0x0307, 0x0072,
+ 0x0307, 0x0052, 0x0323, 0x0072, 0x0323, 0x0052, 0x0323, 0x0304,
+ 0x0072, 0x0323, 0x0304, 0x0052, 0x0331, 0x0072, 0x0331, 0x0053,
+ 0x0307, 0x0073, 0x0307, 0x0053, 0x0323, 0x0073, 0x0323, 0x0053,
+ 0x0301, 0x0307, 0x0073, 0x0301, 0x0307, 0x0053, 0x030c, 0x0307,
+ 0x0073, 0x030c, 0x0307, 0x0053, 0x0323, 0x0307, 0x0073, 0x0323,
+ 0x0307, 0x0054, 0x0307, 0x0074, 0x0307, 0x0054, 0x0323, 0x0074,
+ 0x0323, 0x0054, 0x0331, 0x0074, 0x0331, 0x0054, 0x032d, 0x0074,
+ 0x032d, 0x0055, 0x0324, 0x0075, 0x0324, 0x0055, 0x0330, 0x0075,
+ 0x0330, 0x0055, 0x032d, 0x0075, 0x032d, 0x0055, 0x0303, 0x0301,
+ 0x0075, 0x0303, 0x0301, 0x0055, 0x0304, 0x0308, 0x0075, 0x0304,
+ 0x0308, 0x0056, 0x0303, 0x0076, 0x0303, 0x0056, 0x0323, 0x0076,
+ 0x0323, 0x0057, 0x0300, 0x0077, 0x0300, 0x0057, 0x0301, 0x0077,
+ 0x0301, 0x0057, 0x0308, 0x0077, 0x0308, 0x0057, 0x0307, 0x0077,
+ 0x0307, 0x0057, 0x0323, 0x0077, 0x0323, 0x0058, 0x0307, 0x0078,
+ 0x0307, 0x0058, 0x0308, 0x0078, 0x0308, 0x0059, 0x0307, 0x0079,
+ 0x0307, 0x005a, 0x0302, 0x007a, 0x0302, 0x005a, 0x0323, 0x007a,
+ 0x0323, 0x005a, 0x0331, 0x007a, 0x0331, 0x0068, 0x0331, 0x0074,
+ 0x0308, 0x0077, 0x030a, 0x0079, 0x030a, 0x017f, 0x0307, 0x0041,
+ 0x0323, 0x0061, 0x0323, 0x0041, 0x0309, 0x0061, 0x0309, 0x0041,
+ 0x0302, 0x0301, 0x0061, 0x0302, 0x0301, 0x0041, 0x0302, 0x0300,
+ 0x0061, 0x0302, 0x0300, 0x0041, 0x0302, 0x0309, 0x0061, 0x0302,
+ 0x0309, 0x0041, 0x0302, 0x0303, 0x0061, 0x0302, 0x0303, 0x0041,
+ 0x0323, 0x0302, 0x0061, 0x0323, 0x0302, 0x0041, 0x0306, 0x0301,
+ 0x0061, 0x0306, 0x0301, 0x0041, 0x0306, 0x0300, 0x0061, 0x0306,
+ 0x0300, 0x0041, 0x0306, 0x0309, 0x0061, 0x0306, 0x0309, 0x0041,
+ 0x0306, 0x0303, 0x0061, 0x0306, 0x0303, 0x0041, 0x0323, 0x0306,
+ 0x0061, 0x0323, 0x0306, 0x0045, 0x0323, 0x0065, 0x0323, 0x0045,
+ 0x0309, 0x0065, 0x0309, 0x0045, 0x0303, 0x0065, 0x0303, 0x0045,
+ 0x0302, 0x0301, 0x0065, 0x0302, 0x0301, 0x0045, 0x0302, 0x0300,
+ 0x0065, 0x0302, 0x0300, 0x0045, 0x0302, 0x0309, 0x0065, 0x0302,
+ 0x0309, 0x0045, 0x0302, 0x0303, 0x0065, 0x0302, 0x0303, 0x0045,
+ 0x0323, 0x0302, 0x0065, 0x0323, 0x0302, 0x0049, 0x0309, 0x0069,
+ 0x0309, 0x0049, 0x0323, 0x0069, 0x0323, 0x004f, 0x0323, 0x006f,
+ 0x0323, 0x004f, 0x0309, 0x006f, 0x0309, 0x004f, 0x0302, 0x0301,
+ 0x006f, 0x0302, 0x0301, 0x004f, 0x0302, 0x0300, 0x006f, 0x0302,
+ 0x0300, 0x004f, 0x0302, 0x0309, 0x006f, 0x0302, 0x0309, 0x004f,
+ 0x0302, 0x0303, 0x006f, 0x0302, 0x0303, 0x004f, 0x0323, 0x0302,
+ 0x006f, 0x0323, 0x0302, 0x004f, 0x031b, 0x0301, 0x006f, 0x031b,
+ 0x0301, 0x004f, 0x031b, 0x0300, 0x006f, 0x031b, 0x0300, 0x004f,
+ 0x031b, 0x0309, 0x006f, 0x031b, 0x0309, 0x004f, 0x031b, 0x0303,
+ 0x006f, 0x031b, 0x0303, 0x004f, 0x031b, 0x0323, 0x006f, 0x031b,
+ 0x0323, 0x0055, 0x0323, 0x0075, 0x0323, 0x0055, 0x0309, 0x0075,
+ 0x0309, 0x0055, 0x031b, 0x0301, 0x0075, 0x031b, 0x0301, 0x0055,
+ 0x031b, 0x0300, 0x0075, 0x031b, 0x0300, 0x0055, 0x031b, 0x0309,
+ 0x0075, 0x031b, 0x0309, 0x0055, 0x031b, 0x0303, 0x0075, 0x031b,
+ 0x0303, 0x0055, 0x031b, 0x0323, 0x0075, 0x031b, 0x0323, 0x0059,
+ 0x0300, 0x0079, 0x0300, 0x0059, 0x0323, 0x0079, 0x0323, 0x0059,
+ 0x0309, 0x0079, 0x0309, 0x0059, 0x0303, 0x0079, 0x0303, 0x03b1,
+ 0x0313, 0x03b1, 0x0314, 0x03b1, 0x0313, 0x0300, 0x03b1, 0x0314,
+ 0x0300, 0x03b1, 0x0313, 0x0301, 0x03b1, 0x0314, 0x0301, 0x03b1,
+ 0x0313, 0x0342, 0x03b1, 0x0314, 0x0342, 0x0391, 0x0313, 0x0391,
+ 0x0314, 0x0391, 0x0313, 0x0300, 0x0391, 0x0314, 0x0300, 0x0391,
+ 0x0313, 0x0301, 0x0391, 0x0314, 0x0301, 0x0391, 0x0313, 0x0342,
+ 0x0391, 0x0314, 0x0342, 0x03b5, 0x0313, 0x03b5, 0x0314, 0x03b5,
+ 0x0313, 0x0300, 0x03b5, 0x0314, 0x0300, 0x03b5, 0x0313, 0x0301,
+ 0x03b5, 0x0314, 0x0301, 0x0395, 0x0313, 0x0395, 0x0314, 0x0395,
+ 0x0313, 0x0300, 0x0395, 0x0314, 0x0300, 0x0395, 0x0313, 0x0301,
+ 0x0395, 0x0314, 0x0301, 0x03b7, 0x0313, 0x03b7, 0x0314, 0x03b7,
+ 0x0313, 0x0300, 0x03b7, 0x0314, 0x0300, 0x03b7, 0x0313, 0x0301,
+ 0x03b7, 0x0314, 0x0301, 0x03b7, 0x0313, 0x0342, 0x03b7, 0x0314,
+ 0x0342, 0x0397, 0x0313, 0x0397, 0x0314, 0x0397, 0x0313, 0x0300,
+ 0x0397, 0x0314, 0x0300, 0x0397, 0x0313, 0x0301, 0x0397, 0x0314,
+ 0x0301, 0x0397, 0x0313, 0x0342, 0x0397, 0x0314, 0x0342, 0x03b9,
+ 0x0313, 0x03b9, 0x0314, 0x03b9, 0x0313, 0x0300, 0x03b9, 0x0314,
+ 0x0300, 0x03b9, 0x0313, 0x0301, 0x03b9, 0x0314, 0x0301, 0x03b9,
+ 0x0313, 0x0342, 0x03b9, 0x0314, 0x0342, 0x0399, 0x0313, 0x0399,
+ 0x0314, 0x0399, 0x0313, 0x0300, 0x0399, 0x0314, 0x0300, 0x0399,
+ 0x0313, 0x0301, 0x0399, 0x0314, 0x0301, 0x0399, 0x0313, 0x0342,
+ 0x0399, 0x0314, 0x0342, 0x03bf, 0x0313, 0x03bf, 0x0314, 0x03bf,
+ 0x0313, 0x0300, 0x03bf, 0x0314, 0x0300, 0x03bf, 0x0313, 0x0301,
+ 0x03bf, 0x0314, 0x0301, 0x039f, 0x0313, 0x039f, 0x0314, 0x039f,
+ 0x0313, 0x0300, 0x039f, 0x0314, 0x0300, 0x039f, 0x0313, 0x0301,
+ 0x039f, 0x0314, 0x0301, 0x03c5, 0x0313, 0x03c5, 0x0314, 0x03c5,
+ 0x0313, 0x0300, 0x03c5, 0x0314, 0x0300, 0x03c5, 0x0313, 0x0301,
+ 0x03c5, 0x0314, 0x0301, 0x03c5, 0x0313, 0x0342, 0x03c5, 0x0314,
+ 0x0342, 0x03a5, 0x0314, 0x03a5, 0x0314, 0x0300, 0x03a5, 0x0314,
+ 0x0301, 0x03a5, 0x0314, 0x0342, 0x03c9, 0x0313, 0x03c9, 0x0314,
+ 0x03c9, 0x0313, 0x0300, 0x03c9, 0x0314, 0x0300, 0x03c9, 0x0313,
+ 0x0301, 0x03c9, 0x0314, 0x0301, 0x03c9, 0x0313, 0x0342, 0x03c9,
+ 0x0314, 0x0342, 0x03a9, 0x0313, 0x03a9, 0x0314, 0x03a9, 0x0313,
+ 0x0300, 0x03a9, 0x0314, 0x0300, 0x03a9, 0x0313, 0x0301, 0x03a9,
+ 0x0314, 0x0301, 0x03a9, 0x0313, 0x0342, 0x03a9, 0x0314, 0x0342,
+ 0x03b1, 0x0300, 0x03b1, 0x0301, 0x03b5, 0x0300, 0x03b5, 0x0301,
+ 0x03b7, 0x0300, 0x03b7, 0x0301, 0x03b9, 0x0300, 0x03b9, 0x0301,
+ 0x03bf, 0x0300, 0x03bf, 0x0301, 0x03c5, 0x0300, 0x03c5, 0x0301,
+ 0x03c9, 0x0300, 0x03c9, 0x0301, 0x03b1, 0x0345, 0x0313, 0x03b1,
+ 0x0345, 0x0314, 0x03b1, 0x0345, 0x0313, 0x0300, 0x03b1, 0x0345,
+ 0x0314, 0x0300, 0x03b1, 0x0345, 0x0313, 0x0301, 0x03b1, 0x0345,
+ 0x0314, 0x0301, 0x03b1, 0x0345, 0x0313, 0x0342, 0x03b1, 0x0345,
+ 0x0314, 0x0342, 0x0391, 0x0345, 0x0313, 0x0391, 0x0345, 0x0314,
+ 0x0391, 0x0345, 0x0313, 0x0300, 0x0391, 0x0345, 0x0314, 0x0300,
+ 0x0391, 0x0345, 0x0313, 0x0301, 0x0391, 0x0345, 0x0314, 0x0301,
+ 0x0391, 0x0345, 0x0313, 0x0342, 0x0391, 0x0345, 0x0314, 0x0342,
+ 0x03b7, 0x0345, 0x0313, 0x03b7, 0x0345, 0x0314, 0x03b7, 0x0345,
+ 0x0313, 0x0300, 0x03b7, 0x0345, 0x0314, 0x0300, 0x03b7, 0x0345,
+ 0x0313, 0x0301, 0x03b7, 0x0345, 0x0314, 0x0301, 0x03b7, 0x0345,
+ 0x0313, 0x0342, 0x03b7, 0x0345, 0x0314, 0x0342, 0x0397, 0x0345,
+ 0x0313, 0x0397, 0x0345, 0x0314, 0x0397, 0x0345, 0x0313, 0x0300,
+ 0x0397, 0x0345, 0x0314, 0x0300, 0x0397, 0x0345, 0x0313, 0x0301,
+ 0x0397, 0x0345, 0x0314, 0x0301, 0x0397, 0x0345, 0x0313, 0x0342,
+ 0x0397, 0x0345, 0x0314, 0x0342, 0x03c9, 0x0345, 0x0313, 0x03c9,
+ 0x0345, 0x0314, 0x03c9, 0x0345, 0x0313, 0x0300, 0x03c9, 0x0345,
+ 0x0314, 0x0300, 0x03c9, 0x0345, 0x0313, 0x0301, 0x03c9, 0x0345,
+ 0x0314, 0x0301, 0x03c9, 0x0345, 0x0313, 0x0342, 0x03c9, 0x0345,
+ 0x0314, 0x0342, 0x03a9, 0x0345, 0x0313, 0x03a9, 0x0345, 0x0314,
+ 0x03a9, 0x0345, 0x0313, 0x0300, 0x03a9, 0x0345, 0x0314, 0x0300,
+ 0x03a9, 0x0345, 0x0313, 0x0301, 0x03a9, 0x0345, 0x0314, 0x0301,
+ 0x03a9, 0x0345, 0x0313, 0x0342, 0x03a9, 0x0345, 0x0314, 0x0342,
+ 0x03b1, 0x0306, 0x03b1, 0x0304, 0x03b1, 0x0345, 0x0300, 0x03b1,
+ 0x0345, 0x03b1, 0x0345, 0x0301, 0x03b1, 0x0342, 0x03b1, 0x0345,
+ 0x0342, 0x0391, 0x0306, 0x0391, 0x0304, 0x0391, 0x0300, 0x0391,
+ 0x0301, 0x0391, 0x0345, 0x03b9, 0x00a8, 0x0342, 0x03b7, 0x0345,
+ 0x0300, 0x03b7, 0x0345, 0x03b7, 0x0345, 0x0301, 0x03b7, 0x0342,
+ 0x03b7, 0x0345, 0x0342, 0x0395, 0x0300, 0x0395, 0x0301, 0x0397,
+ 0x0300, 0x0397, 0x0301, 0x0397, 0x0345, 0x1fbf, 0x0300, 0x1fbf,
+ 0x0301, 0x1fbf, 0x0342, 0x03b9, 0x0306, 0x03b9, 0x0304, 0x03b9,
+ 0x0308, 0x0300, 0x03b9, 0x0308, 0x0301, 0x03b9, 0x0342, 0x03b9,
+ 0x0308, 0x0342, 0x0399, 0x0306, 0x0399, 0x0304, 0x0399, 0x0300,
+ 0x0399, 0x0301, 0x1ffe, 0x0300, 0x1ffe, 0x0301, 0x1ffe, 0x0342,
+ 0x03c5, 0x0306, 0x03c5, 0x0304, 0x03c5, 0x0308, 0x0300, 0x03c5,
+ 0x0308, 0x0301, 0x03c1, 0x0313, 0x03c1, 0x0314, 0x03c5, 0x0342,
+ 0x03c5, 0x0308, 0x0342, 0x03a5, 0x0306, 0x03a5, 0x0304, 0x03a5,
+ 0x0300, 0x03a5, 0x0301, 0x03a1, 0x0314, 0x00a8, 0x0300, 0x00a8,
+ 0x0301, 0x0060, 0x03c9, 0x0345, 0x0300, 0x03c9, 0x0345, 0x03bf,
+ 0x0345, 0x0301, 0x03c9, 0x0342, 0x03c9, 0x0345, 0x0342, 0x039f,
+ 0x0300, 0x039f, 0x0301, 0x03a9, 0x0300, 0x03a9, 0x0301, 0x03a9,
+ 0x0345, 0x00b4, 0x304b, 0x3099, 0x304d, 0x3099, 0x304f, 0x3099,
+ 0x3051, 0x3099, 0x3053, 0x3099, 0x3055, 0x3099, 0x3057, 0x3099,
+ 0x3059, 0x3099, 0x305b, 0x3099, 0x305d, 0x3099, 0x305f, 0x3099,
+ 0x3061, 0x3099, 0x3064, 0x3099, 0x3066, 0x3099, 0x3068, 0x3099,
+ 0x306f, 0x3099, 0x306f, 0x309a, 0x3072, 0x3099, 0x3072, 0x309a,
+ 0x3075, 0x3099, 0x3075, 0x309a, 0x3078, 0x3099, 0x3078, 0x309a,
+ 0x307b, 0x3099, 0x307b, 0x309a, 0x3046, 0x3099, 0x309d, 0x3099,
+ 0x30ab, 0x3099, 0x30ad, 0x3099, 0x30af, 0x3099, 0x30b1, 0x3099,
+ 0x30b3, 0x3099, 0x30b5, 0x3099, 0x30b7, 0x3099, 0x30b9, 0x3099,
+ 0x30bb, 0x3099, 0x30bd, 0x3099, 0x30bf, 0x3099, 0x30c1, 0x3099,
+ 0x30c4, 0x3099, 0x30c6, 0x3099, 0x30c8, 0x3099, 0x30cf, 0x3099,
+ 0x30cf, 0x309a, 0x30d2, 0x3099, 0x30d2, 0x309a, 0x30d5, 0x3099,
+ 0x30d5, 0x309a, 0x30d8, 0x3099, 0x30d8, 0x309a, 0x30db, 0x3099,
+ 0x30db, 0x309a, 0x30a6, 0x3099, 0x30ef, 0x3099, 0x30f0, 0x3099,
+ 0x30f1, 0x3099, 0x30f2, 0x3099, 0x30fd, 0x3099, 0x05f2, 0x05b7,
+ 0x05e9, 0x05c1, 0x05e9, 0x05c2, 0x05e9, 0x05bc, 0x05c1, 0x05e9,
+ 0x05bc, 0x05c2, 0x05d0, 0x05b7, 0x05d0, 0x05b8, 0x05d0, 0x05bc,
+ 0x05d1, 0x05bc, 0x05d2, 0x05bc, 0x05d3, 0x05bc, 0x05d4, 0x05bc,
+ 0x05d5, 0x05bc, 0x05d6, 0x05bc, 0x05d8, 0x05bc, 0x05d9, 0x05bc,
+ 0x05da, 0x05bc, 0x05db, 0x05bc, 0x05dc, 0x05bc, 0x05de, 0x05bc,
+ 0x05e0, 0x05bc, 0x05e1, 0x05bc, 0x05e3, 0x05bc, 0x05e4, 0x05bc,
+ 0x05e6, 0x05bc, 0x05e7, 0x05bc, 0x05e8, 0x05bc, 0x05e9, 0x05bc,
+ 0x05ea, 0x05bc, 0x05d5, 0x05b9, 0x05d1, 0x05bf, 0x05db, 0x05bf,
+ 0x05e4, 0x05bf
+};
+
+u16 hfsplus_compose_table[] = {
+ /* base */
+ 0x0000, 0x0050, 0x0300, 0x00a4, 0x0301, 0x00e4, 0x0302, 0x015c,
+ 0x0303, 0x0192, 0x0304, 0x01b4, 0x0306, 0x01e6, 0x0307, 0x0220,
+ 0x0308, 0x0270, 0x0309, 0x02d2, 0x030a, 0x02ec, 0x030b, 0x02fa,
+ 0x030c, 0x0308, 0x030d, 0x034c, 0x030f, 0x0370, 0x0311, 0x038e,
+ 0x0313, 0x03a8, 0x0314, 0x03c6, 0x031b, 0x03e8, 0x0323, 0x03f2,
+ 0x0324, 0x0440, 0x0325, 0x0446, 0x0327, 0x044c, 0x0328, 0x047a,
+ 0x032d, 0x0490, 0x032e, 0x04aa, 0x0330, 0x04b0, 0x0331, 0x04be,
+ 0x0342, 0x04e2, 0x0345, 0x04f4, 0x05b7, 0x0504, 0x05b8, 0x050a,
+ 0x05b9, 0x050e, 0x05bc, 0x0512, 0x05bf, 0x0540, 0x05c1, 0x0548,
+ 0x05c2, 0x054c, 0x093c, 0x0550, 0x09bc, 0x0568, 0x09be, 0x0572,
+ 0x09d7, 0x0576, 0x0a3c, 0x057a, 0x0b3c, 0x0586, 0x0b3e, 0x058e,
+ 0x0b56, 0x0592, 0x0b57, 0x0596, 0x0bbe, 0x059a, 0x0bd7, 0x05a0,
+ 0x0c56, 0x05a6, 0x0cc2, 0x05aa, 0x0cd5, 0x05ae, 0x0cd6, 0x05b4,
+ 0x0d3e, 0x05b8, 0x0d57, 0x05be, 0x0e32, 0x05c2, 0x0eb2, 0x05c6,
+ 0x0f71, 0x05ca, 0x0f80, 0x05d2, 0x0fb5, 0x05d8, 0x0fb7, 0x05de,
+ 0x1100, 0x00a2, 0x1101, 0x00a2, 0x1102, 0x00a2, 0x1103, 0x00a2,
+ 0x1104, 0x00a2, 0x1105, 0x00a2, 0x1106, 0x00a2, 0x1107, 0x00a2,
+ 0x1108, 0x00a2, 0x1109, 0x00a2, 0x110a, 0x00a2, 0x110b, 0x00a2,
+ 0x110c, 0x00a2, 0x110d, 0x00a2, 0x110e, 0x00a2, 0x110f, 0x00a2,
+ 0x1110, 0x00a2, 0x1111, 0x00a2, 0x1112, 0x00a2, 0x3099, 0x05f4,
+ 0x309a, 0x0656,
+ /* hangul marker */
+ 0xffff, 0x0000,
+ /* 0x0300 */
+ 0x0340, 0x001f, 0x0041, 0x066c, 0x0045, 0x066e, 0x0049, 0x0670,
+ 0x004f, 0x0672, 0x0055, 0x0674, 0x0057, 0x0676, 0x0059, 0x0678,
+ 0x0061, 0x067a, 0x0065, 0x067c, 0x0069, 0x067e, 0x006f, 0x0680,
+ 0x0075, 0x0682, 0x0077, 0x0684, 0x0079, 0x0686, 0x00a8, 0x0688,
+ 0x0391, 0x068a, 0x0395, 0x068c, 0x0397, 0x068e, 0x0399, 0x0690,
+ 0x039f, 0x0692, 0x03a5, 0x0694, 0x03a9, 0x0696, 0x03b1, 0x0698,
+ 0x03b5, 0x069a, 0x03b7, 0x069c, 0x03b9, 0x069e, 0x03bf, 0x06a0,
+ 0x03c5, 0x06a2, 0x03c9, 0x06a4, 0x1fbf, 0x06a6, 0x1ffe, 0x06a8,
+ /* 0x0301 */
+ 0x0341, 0x003b, 0x0041, 0x06aa, 0x0043, 0x06ac, 0x0045, 0x06ae,
+ 0x0047, 0x06b0, 0x0049, 0x06b2, 0x004b, 0x06b4, 0x004c, 0x06b6,
+ 0x004d, 0x06b8, 0x004e, 0x06ba, 0x004f, 0x06bc, 0x0050, 0x06be,
+ 0x0052, 0x06c0, 0x0053, 0x06c2, 0x0055, 0x06c6, 0x0057, 0x06c8,
+ 0x0059, 0x06ca, 0x005a, 0x06cc, 0x0061, 0x06ce, 0x0063, 0x06d0,
+ 0x0065, 0x06d2, 0x0067, 0x06d4, 0x0069, 0x06d6, 0x006b, 0x06d8,
+ 0x006c, 0x06da, 0x006d, 0x06dc, 0x006e, 0x06de, 0x006f, 0x06e0,
+ 0x0070, 0x06e2, 0x0072, 0x06e4, 0x0073, 0x06e6, 0x0075, 0x06ea,
+ 0x0077, 0x06ec, 0x0079, 0x06ee, 0x007a, 0x06f0, 0x00a8, 0x06f2,
+ 0x00c6, 0x06f4, 0x00d8, 0x06f6, 0x00e6, 0x06f8, 0x00f8, 0x06fa,
+ 0x0391, 0x06fc, 0x0395, 0x06fe, 0x0397, 0x0700, 0x0399, 0x0702,
+ 0x039f, 0x0704, 0x03a5, 0x0706, 0x03a9, 0x0708, 0x03b1, 0x070a,
+ 0x03b5, 0x070c, 0x03b7, 0x070e, 0x03b9, 0x0710, 0x03bf, 0x0712,
+ 0x03c5, 0x0714, 0x03c9, 0x0716, 0x0413, 0x0718, 0x041a, 0x071a,
+ 0x0433, 0x071c, 0x043a, 0x071e, 0x1fbf, 0x0720, 0x1ffe, 0x0722,
+ /* 0x0302 */
+ 0x0000, 0x001a, 0x0041, 0x0724, 0x0043, 0x072e, 0x0045, 0x0730,
+ 0x0047, 0x073a, 0x0048, 0x073c, 0x0049, 0x073e, 0x004a, 0x0740,
+ 0x004f, 0x0742, 0x0053, 0x074c, 0x0055, 0x074e, 0x0057, 0x0750,
+ 0x0059, 0x0752, 0x005a, 0x0754, 0x0061, 0x0756, 0x0063, 0x0760,
+ 0x0065, 0x0762, 0x0067, 0x076c, 0x0068, 0x076e, 0x0069, 0x0770,
+ 0x006a, 0x0772, 0x006f, 0x0774, 0x0073, 0x077e, 0x0075, 0x0780,
+ 0x0077, 0x0782, 0x0079, 0x0784, 0x007a, 0x0786,
+ /* 0x0303 */
+ 0x0000, 0x0010, 0x0041, 0x0788, 0x0045, 0x078a, 0x0049, 0x078c,
+ 0x004e, 0x078e, 0x004f, 0x0790, 0x0055, 0x0796, 0x0056, 0x079a,
+ 0x0059, 0x079c, 0x0061, 0x079e, 0x0065, 0x07a0, 0x0069, 0x07a2,
+ 0x006e, 0x07a4, 0x006f, 0x07a6, 0x0075, 0x07ac, 0x0076, 0x07b0,
+ 0x0079, 0x07b2,
+ /* 0x0304 */
+ 0x0000, 0x0018, 0x0041, 0x07b4, 0x0045, 0x07b6, 0x0047, 0x07bc,
+ 0x0049, 0x07be, 0x004f, 0x07c0, 0x0055, 0x07c6, 0x0061, 0x07ca,
+ 0x0065, 0x07cc, 0x0067, 0x07d2, 0x0069, 0x07d4, 0x006f, 0x07d6,
+ 0x0075, 0x07dc, 0x00c6, 0x07e0, 0x00e6, 0x07e2, 0x0391, 0x07e4,
+ 0x0399, 0x07e6, 0x03a5, 0x07e8, 0x03b1, 0x07ea, 0x03b9, 0x07ec,
+ 0x03c5, 0x07ee, 0x0418, 0x07f0, 0x0423, 0x07f2, 0x0438, 0x07f4,
+ 0x0443, 0x07f6,
+ /* 0x0306 */
+ 0x0000, 0x001c, 0x0041, 0x07f8, 0x0045, 0x0802, 0x0047, 0x0804,
+ 0x0049, 0x0806, 0x004f, 0x0808, 0x0055, 0x080a, 0x0061, 0x080c,
+ 0x0065, 0x0816, 0x0067, 0x0818, 0x0069, 0x081a, 0x006f, 0x081c,
+ 0x0075, 0x081e, 0x0391, 0x0820, 0x0399, 0x0822, 0x03a5, 0x0824,
+ 0x03b1, 0x0826, 0x03b9, 0x0828, 0x03c5, 0x082a, 0x0410, 0x082c,
+ 0x0415, 0x082e, 0x0416, 0x0830, 0x0418, 0x0832, 0x0423, 0x0834,
+ 0x0430, 0x0836, 0x0435, 0x0838, 0x0436, 0x083a, 0x0438, 0x083c,
+ 0x0443, 0x083e,
+ /* 0x0307 */
+ 0x0000, 0x0027, 0x0041, 0x0840, 0x0042, 0x0844, 0x0043, 0x0846,
+ 0x0044, 0x0848, 0x0045, 0x084a, 0x0046, 0x084c, 0x0047, 0x084e,
+ 0x0048, 0x0850, 0x0049, 0x0852, 0x004d, 0x0854, 0x004e, 0x0856,
+ 0x0050, 0x0858, 0x0052, 0x085a, 0x0053, 0x085c, 0x0054, 0x085e,
+ 0x0057, 0x0860, 0x0058, 0x0862, 0x0059, 0x0864, 0x005a, 0x0866,
+ 0x0061, 0x0868, 0x0062, 0x086c, 0x0063, 0x086e, 0x0064, 0x0870,
+ 0x0065, 0x0872, 0x0066, 0x0874, 0x0067, 0x0876, 0x0068, 0x0878,
+ 0x006d, 0x087a, 0x006e, 0x087c, 0x0070, 0x087e, 0x0072, 0x0880,
+ 0x0073, 0x0882, 0x0074, 0x0884, 0x0077, 0x0886, 0x0078, 0x0888,
+ 0x0079, 0x088a, 0x007a, 0x088c, 0x017f, 0x088e, 0x0306, 0x0890,
+ /* 0x0308 */
+ 0x0000, 0x0030, 0x0041, 0x0892, 0x0045, 0x0896, 0x0048, 0x0898,
+ 0x0049, 0x089a, 0x004f, 0x089e, 0x0055, 0x08a0, 0x0057, 0x08aa,
+ 0x0058, 0x08ac, 0x0059, 0x08ae, 0x0061, 0x08b0, 0x0065, 0x08b4,
+ 0x0068, 0x08b6, 0x0069, 0x08b8, 0x006f, 0x08bc, 0x0074, 0x08be,
+ 0x0075, 0x08c0, 0x0077, 0x08ca, 0x0078, 0x08cc, 0x0079, 0x08ce,
+ 0x018f, 0x08d0, 0x019f, 0x08d2, 0x0259, 0x08d4, 0x0275, 0x08d6,
+ 0x0399, 0x08d8, 0x03a5, 0x08da, 0x03b9, 0x08dc, 0x03c5, 0x08e6,
+ 0x03d2, 0x08f0, 0x0406, 0x08f2, 0x0410, 0x08f4, 0x0415, 0x08f6,
+ 0x0416, 0x08f8, 0x0417, 0x08fa, 0x0418, 0x08fc, 0x041e, 0x08fe,
+ 0x0423, 0x0900, 0x0427, 0x0902, 0x042b, 0x0904, 0x0430, 0x0906,
+ 0x0435, 0x0908, 0x0436, 0x090a, 0x0437, 0x090c, 0x0438, 0x090e,
+ 0x043e, 0x0910, 0x0443, 0x0912, 0x0447, 0x0914, 0x044b, 0x0916,
+ 0x0456, 0x0918,
+ /* 0x0309 */
+ 0x0000, 0x000c, 0x0041, 0x091a, 0x0045, 0x091c, 0x0049, 0x091e,
+ 0x004f, 0x0920, 0x0055, 0x0922, 0x0059, 0x0924, 0x0061, 0x0926,
+ 0x0065, 0x0928, 0x0069, 0x092a, 0x006f, 0x092c, 0x0075, 0x092e,
+ 0x0079, 0x0930,
+ /* 0x030a */
+ 0x0000, 0x0006, 0x0041, 0x0932, 0x0055, 0x0936, 0x0061, 0x0938,
+ 0x0075, 0x093c, 0x0077, 0x093e, 0x0079, 0x0940,
+ /* 0x030b */
+ 0x0000, 0x0006, 0x004f, 0x0942, 0x0055, 0x0944, 0x006f, 0x0946,
+ 0x0075, 0x0948, 0x0423, 0x094a, 0x0443, 0x094c,
+ /* 0x030c */
+ 0x0000, 0x0021, 0x0041, 0x094e, 0x0043, 0x0950, 0x0044, 0x0952,
+ 0x0045, 0x0954, 0x0047, 0x0956, 0x0049, 0x0958, 0x004b, 0x095a,
+ 0x004c, 0x095c, 0x004e, 0x095e, 0x004f, 0x0960, 0x0052, 0x0962,
+ 0x0053, 0x0964, 0x0054, 0x0968, 0x0055, 0x096a, 0x005a, 0x096c,
+ 0x0061, 0x096e, 0x0063, 0x0970, 0x0064, 0x0972, 0x0065, 0x0974,
+ 0x0067, 0x0976, 0x0069, 0x0978, 0x006a, 0x097a, 0x006b, 0x097c,
+ 0x006c, 0x097e, 0x006e, 0x0980, 0x006f, 0x0982, 0x0072, 0x0984,
+ 0x0073, 0x0986, 0x0074, 0x098a, 0x0075, 0x098c, 0x007a, 0x098e,
+ 0x01b7, 0x0990, 0x0292, 0x0992,
+ /* 0x030d */
+ 0x0000, 0x0011, 0x00a8, 0x0994, 0x0308, 0x0996, 0x0391, 0x0998,
+ 0x0395, 0x099a, 0x0397, 0x099c, 0x0399, 0x099e, 0x039f, 0x09a0,
+ 0x03a5, 0x09a2, 0x03a9, 0x09a4, 0x03b1, 0x09a6, 0x03b5, 0x09a8,
+ 0x03b7, 0x09aa, 0x03b9, 0x09ac, 0x03bf, 0x09ae, 0x03c5, 0x09b0,
+ 0x03c9, 0x09b2, 0x03d2, 0x09b4,
+ /* 0x030f */
+ 0x0000, 0x000e, 0x0041, 0x09b6, 0x0045, 0x09b8, 0x0049, 0x09ba,
+ 0x004f, 0x09bc, 0x0052, 0x09be, 0x0055, 0x09c0, 0x0061, 0x09c2,
+ 0x0065, 0x09c4, 0x0069, 0x09c6, 0x006f, 0x09c8, 0x0072, 0x09ca,
+ 0x0075, 0x09cc, 0x0474, 0x09ce, 0x0475, 0x09d0,
+ /* 0x0311 */
+ 0x0000, 0x000c, 0x0041, 0x09d2, 0x0045, 0x09d4, 0x0049, 0x09d6,
+ 0x004f, 0x09d8, 0x0052, 0x09da, 0x0055, 0x09dc, 0x0061, 0x09de,
+ 0x0065, 0x09e0, 0x0069, 0x09e2, 0x006f, 0x09e4, 0x0072, 0x09e6,
+ 0x0075, 0x09e8,
+ /* 0x0313 */
+ 0x0343, 0x000e, 0x0391, 0x09ea, 0x0395, 0x09f2, 0x0397, 0x09f8,
+ 0x0399, 0x0a00, 0x039f, 0x0a08, 0x03a9, 0x0a0e, 0x03b1, 0x0a16,
+ 0x03b5, 0x0a1e, 0x03b7, 0x0a24, 0x03b9, 0x0a2c, 0x03bf, 0x0a34,
+ 0x03c1, 0x0a3a, 0x03c5, 0x0a3c, 0x03c9, 0x0a44,
+ /* 0x0314 */
+ 0x0000, 0x0010, 0x0391, 0x0a4c, 0x0395, 0x0a54, 0x0397, 0x0a5a,
+ 0x0399, 0x0a62, 0x039f, 0x0a6a, 0x03a1, 0x0a70, 0x03a5, 0x0a72,
+ 0x03a9, 0x0a7a, 0x03b1, 0x0a82, 0x03b5, 0x0a8a, 0x03b7, 0x0a90,
+ 0x03b9, 0x0a98, 0x03bf, 0x0aa0, 0x03c1, 0x0aa6, 0x03c5, 0x0aa8,
+ 0x03c9, 0x0ab0,
+ /* 0x031b */
+ 0x0000, 0x0004, 0x004f, 0x0ab8, 0x0055, 0x0ac4, 0x006f, 0x0ad0,
+ 0x0075, 0x0adc,
+ /* 0x0323 */
+ 0x0000, 0x0026, 0x0041, 0x0ae8, 0x0042, 0x0aee, 0x0044, 0x0af0,
+ 0x0045, 0x0af2, 0x0048, 0x0af6, 0x0049, 0x0af8, 0x004b, 0x0afa,
+ 0x004c, 0x0afc, 0x004d, 0x0b00, 0x004e, 0x0b02, 0x004f, 0x0b04,
+ 0x0052, 0x0b08, 0x0053, 0x0b0c, 0x0054, 0x0b10, 0x0055, 0x0b12,
+ 0x0056, 0x0b14, 0x0057, 0x0b16, 0x0059, 0x0b18, 0x005a, 0x0b1a,
+ 0x0061, 0x0b1c, 0x0062, 0x0b22, 0x0064, 0x0b24, 0x0065, 0x0b26,
+ 0x0068, 0x0b2a, 0x0069, 0x0b2c, 0x006b, 0x0b2e, 0x006c, 0x0b30,
+ 0x006d, 0x0b34, 0x006e, 0x0b36, 0x006f, 0x0b38, 0x0072, 0x0b3c,
+ 0x0073, 0x0b40, 0x0074, 0x0b44, 0x0075, 0x0b46, 0x0076, 0x0b48,
+ 0x0077, 0x0b4a, 0x0079, 0x0b4c, 0x007a, 0x0b4e,
+ /* 0x0324 */
+ 0x0000, 0x0002, 0x0055, 0x0b50, 0x0075, 0x0b52,
+ /* 0x0325 */
+ 0x0000, 0x0002, 0x0041, 0x0b54, 0x0061, 0x0b56,
+ /* 0x0327 */
+ 0x0000, 0x0016, 0x0043, 0x0b58, 0x0044, 0x0b5c, 0x0045, 0x0b5e,
+ 0x0047, 0x0b62, 0x0048, 0x0b64, 0x004b, 0x0b66, 0x004c, 0x0b68,
+ 0x004e, 0x0b6a, 0x0052, 0x0b6c, 0x0053, 0x0b6e, 0x0054, 0x0b70,
+ 0x0063, 0x0b72, 0x0064, 0x0b76, 0x0065, 0x0b78, 0x0067, 0x0b7c,
+ 0x0068, 0x0b7e, 0x006b, 0x0b80, 0x006c, 0x0b82, 0x006e, 0x0b84,
+ 0x0072, 0x0b86, 0x0073, 0x0b88, 0x0074, 0x0b8a,
+ /* 0x0328 */
+ 0x0000, 0x000a, 0x0041, 0x0b8c, 0x0045, 0x0b8e, 0x0049, 0x0b90,
+ 0x004f, 0x0b92, 0x0055, 0x0b96, 0x0061, 0x0b98, 0x0065, 0x0b9a,
+ 0x0069, 0x0b9c, 0x006f, 0x0b9e, 0x0075, 0x0ba2,
+ /* 0x032d */
+ 0x0000, 0x000c, 0x0044, 0x0ba4, 0x0045, 0x0ba6, 0x004c, 0x0ba8,
+ 0x004e, 0x0baa, 0x0054, 0x0bac, 0x0055, 0x0bae, 0x0064, 0x0bb0,
+ 0x0065, 0x0bb2, 0x006c, 0x0bb4, 0x006e, 0x0bb6, 0x0074, 0x0bb8,
+ 0x0075, 0x0bba,
+ /* 0x032e */
+ 0x0000, 0x0002, 0x0048, 0x0bbc, 0x0068, 0x0bbe,
+ /* 0x0330 */
+ 0x0000, 0x0006, 0x0045, 0x0bc0, 0x0049, 0x0bc2, 0x0055, 0x0bc4,
+ 0x0065, 0x0bc6, 0x0069, 0x0bc8, 0x0075, 0x0bca,
+ /* 0x0331 */
+ 0x0000, 0x0011, 0x0042, 0x0bcc, 0x0044, 0x0bce, 0x004b, 0x0bd0,
+ 0x004c, 0x0bd2, 0x004e, 0x0bd4, 0x0052, 0x0bd6, 0x0054, 0x0bd8,
+ 0x005a, 0x0bda, 0x0062, 0x0bdc, 0x0064, 0x0bde, 0x0068, 0x0be0,
+ 0x006b, 0x0be2, 0x006c, 0x0be4, 0x006e, 0x0be6, 0x0072, 0x0be8,
+ 0x0074, 0x0bea, 0x007a, 0x0bec,
+ /* 0x0342 */
+ 0x0000, 0x0008, 0x00a8, 0x0bee, 0x03b1, 0x0bf0, 0x03b7, 0x0bf2,
+ 0x03b9, 0x0bf4, 0x03c5, 0x0bf6, 0x03c9, 0x0bf8, 0x1fbf, 0x0bfa,
+ 0x1ffe, 0x0bfc,
+ /* 0x0345 */
+ 0x0000, 0x0007, 0x0391, 0x0bfe, 0x0397, 0x0c04, 0x03a9, 0x0c0a,
+ 0x03b1, 0x0c10, 0x03b7, 0x0c1c, 0x03bf, 0x0c28, 0x03c9, 0x0c2c,
+ /* 0x05b7 */
+ 0x0000, 0x0002, 0x05d0, 0x0c36, 0x05f2, 0x0c38,
+ /* 0x05b8 */
+ 0x0000, 0x0001, 0x05d0, 0x0c3a,
+ /* 0x05b9 */
+ 0x0000, 0x0001, 0x05d5, 0x0c3c,
+ /* 0x05bc */
+ 0x0000, 0x0016, 0x05d0, 0x0c3e, 0x05d1, 0x0c40, 0x05d2, 0x0c42,
+ 0x05d3, 0x0c44, 0x05d4, 0x0c46, 0x05d5, 0x0c48, 0x05d6, 0x0c4a,
+ 0x05d8, 0x0c4c, 0x05d9, 0x0c4e, 0x05da, 0x0c50, 0x05db, 0x0c52,
+ 0x05dc, 0x0c54, 0x05de, 0x0c56, 0x05e0, 0x0c58, 0x05e1, 0x0c5a,
+ 0x05e3, 0x0c5c, 0x05e4, 0x0c5e, 0x05e6, 0x0c60, 0x05e7, 0x0c62,
+ 0x05e8, 0x0c64, 0x05e9, 0x0c66, 0x05ea, 0x0c6c,
+ /* 0x05bf */
+ 0x0000, 0x0003, 0x05d1, 0x0c6e, 0x05db, 0x0c70, 0x05e4, 0x0c72,
+ /* 0x05c1 */
+ 0x0000, 0x0001, 0x05e9, 0x0c74,
+ /* 0x05c2 */
+ 0x0000, 0x0001, 0x05e9, 0x0c76,
+ /* 0x093c */
+ 0x0000, 0x000b, 0x0915, 0x0c78, 0x0916, 0x0c7a, 0x0917, 0x0c7c,
+ 0x091c, 0x0c7e, 0x0921, 0x0c80, 0x0922, 0x0c82, 0x0928, 0x0c84,
+ 0x092b, 0x0c86, 0x092f, 0x0c88, 0x0930, 0x0c8a, 0x0933, 0x0c8c,
+ /* 0x09bc */
+ 0x0000, 0x0004, 0x09a1, 0x0c8e, 0x09a2, 0x0c90, 0x09ac, 0x0c92,
+ 0x09af, 0x0c94,
+ /* 0x09be */
+ 0x0000, 0x0001, 0x09c7, 0x0c96,
+ /* 0x09d7 */
+ 0x0000, 0x0001, 0x09c7, 0x0c98,
+ /* 0x0a3c */
+ 0x0000, 0x0005, 0x0a16, 0x0c9a, 0x0a17, 0x0c9c, 0x0a1c, 0x0c9e,
+ 0x0a21, 0x0ca0, 0x0a2b, 0x0ca2,
+ /* 0x0b3c */
+ 0x0000, 0x0003, 0x0b21, 0x0ca4, 0x0b22, 0x0ca6, 0x0b2f, 0x0ca8,
+ /* 0x0b3e */
+ 0x0000, 0x0001, 0x0b47, 0x0caa,
+ /* 0x0b56 */
+ 0x0000, 0x0001, 0x0b47, 0x0cac,
+ /* 0x0b57 */
+ 0x0000, 0x0001, 0x0b47, 0x0cae,
+ /* 0x0bbe */
+ 0x0000, 0x0002, 0x0bc6, 0x0cb0, 0x0bc7, 0x0cb2,
+ /* 0x0bd7 */
+ 0x0000, 0x0002, 0x0b92, 0x0cb4, 0x0bc6, 0x0cb6,
+ /* 0x0c56 */
+ 0x0000, 0x0001, 0x0c46, 0x0cb8,
+ /* 0x0cc2 */
+ 0x0000, 0x0001, 0x0cc6, 0x0cba,
+ /* 0x0cd5 */
+ 0x0000, 0x0002, 0x0cbf, 0x0cbe, 0x0cc6, 0x0cc0,
+ /* 0x0cd6 */
+ 0x0000, 0x0001, 0x0cc6, 0x0cc2,
+ /* 0x0d3e */
+ 0x0000, 0x0002, 0x0d46, 0x0cc4, 0x0d47, 0x0cc6,
+ /* 0x0d57 */
+ 0x0000, 0x0001, 0x0d46, 0x0cc8,
+ /* 0x0e32 */
+ 0x0000, 0x0001, 0x0e4d, 0x0cca,
+ /* 0x0eb2 */
+ 0x0000, 0x0001, 0x0ecd, 0x0ccc,
+ /* 0x0f71 */
+ 0x0000, 0x0003, 0x0f72, 0x0cce, 0x0f74, 0x0cd0, 0x0f80, 0x0cd2,
+ /* 0x0f80 */
+ 0x0000, 0x0002, 0x0fb2, 0x0cd4, 0x0fb3, 0x0cd8,
+ /* 0x0fb5 */
+ 0x0000, 0x0002, 0x0f40, 0x0cdc, 0x0f90, 0x0cde,
+ /* 0x0fb7 */
+ 0x0000, 0x000a, 0x0f42, 0x0ce0, 0x0f4c, 0x0ce2, 0x0f51, 0x0ce4,
+ 0x0f56, 0x0ce6, 0x0f5b, 0x0ce8, 0x0f92, 0x0cea, 0x0f9c, 0x0cec,
+ 0x0fa1, 0x0cee, 0x0fa6, 0x0cf0, 0x0fab, 0x0cf2,
+ /* 0x3099 */
+ 0x0000, 0x0030, 0x3046, 0x0cf4, 0x304b, 0x0cf6, 0x304d, 0x0cf8,
+ 0x304f, 0x0cfa, 0x3051, 0x0cfc, 0x3053, 0x0cfe, 0x3055, 0x0d00,
+ 0x3057, 0x0d02, 0x3059, 0x0d04, 0x305b, 0x0d06, 0x305d, 0x0d08,
+ 0x305f, 0x0d0a, 0x3061, 0x0d0c, 0x3064, 0x0d0e, 0x3066, 0x0d10,
+ 0x3068, 0x0d12, 0x306f, 0x0d14, 0x3072, 0x0d16, 0x3075, 0x0d18,
+ 0x3078, 0x0d1a, 0x307b, 0x0d1c, 0x309d, 0x0d1e, 0x30a6, 0x0d20,
+ 0x30ab, 0x0d22, 0x30ad, 0x0d24, 0x30af, 0x0d26, 0x30b1, 0x0d28,
+ 0x30b3, 0x0d2a, 0x30b5, 0x0d2c, 0x30b7, 0x0d2e, 0x30b9, 0x0d30,
+ 0x30bb, 0x0d32, 0x30bd, 0x0d34, 0x30bf, 0x0d36, 0x30c1, 0x0d38,
+ 0x30c4, 0x0d3a, 0x30c6, 0x0d3c, 0x30c8, 0x0d3e, 0x30cf, 0x0d40,
+ 0x30d2, 0x0d42, 0x30d5, 0x0d44, 0x30d8, 0x0d46, 0x30db, 0x0d48,
+ 0x30ef, 0x0d4a, 0x30f0, 0x0d4c, 0x30f1, 0x0d4e, 0x30f2, 0x0d50,
+ 0x30fd, 0x0d52,
+ /* 0x309a */
+ 0x0000, 0x000a, 0x306f, 0x0d54, 0x3072, 0x0d56, 0x3075, 0x0d58,
+ 0x3078, 0x0d5a, 0x307b, 0x0d5c, 0x30cf, 0x0d5e, 0x30d2, 0x0d60,
+ 0x30d5, 0x0d62, 0x30d8, 0x0d64, 0x30db, 0x0d66,
+ /* 0x0041 0x0300 */
+ 0x00c0, 0x0000,
+ /* 0x0045 0x0300 */
+ 0x00c8, 0x0000,
+ /* 0x0049 0x0300 */
+ 0x00cc, 0x0000,
+ /* 0x004f 0x0300 */
+ 0x00d2, 0x0000,
+ /* 0x0055 0x0300 */
+ 0x00d9, 0x0000,
+ /* 0x0057 0x0300 */
+ 0x1e80, 0x0000,
+ /* 0x0059 0x0300 */
+ 0x1ef2, 0x0000,
+ /* 0x0061 0x0300 */
+ 0x00e0, 0x0000,
+ /* 0x0065 0x0300 */
+ 0x00e8, 0x0000,
+ /* 0x0069 0x0300 */
+ 0x00ec, 0x0000,
+ /* 0x006f 0x0300 */
+ 0x00f2, 0x0000,
+ /* 0x0075 0x0300 */
+ 0x00f9, 0x0000,
+ /* 0x0077 0x0300 */
+ 0x1e81, 0x0000,
+ /* 0x0079 0x0300 */
+ 0x1ef3, 0x0000,
+ /* 0x00a8 0x0300 */
+ 0x1fed, 0x0000,
+ /* 0x0391 0x0300 */
+ 0x1fba, 0x0000,
+ /* 0x0395 0x0300 */
+ 0x1fc8, 0x0000,
+ /* 0x0397 0x0300 */
+ 0x1fca, 0x0000,
+ /* 0x0399 0x0300 */
+ 0x1fda, 0x0000,
+ /* 0x039f 0x0300 */
+ 0x1ff8, 0x0000,
+ /* 0x03a5 0x0300 */
+ 0x1fea, 0x0000,
+ /* 0x03a9 0x0300 */
+ 0x1ffa, 0x0000,
+ /* 0x03b1 0x0300 */
+ 0x1f70, 0x0000,
+ /* 0x03b5 0x0300 */
+ 0x1f72, 0x0000,
+ /* 0x03b7 0x0300 */
+ 0x1f74, 0x0000,
+ /* 0x03b9 0x0300 */
+ 0x1f76, 0x0000,
+ /* 0x03bf 0x0300 */
+ 0x1f78, 0x0000,
+ /* 0x03c5 0x0300 */
+ 0x1f7a, 0x0000,
+ /* 0x03c9 0x0300 */
+ 0x1f7c, 0x0000,
+ /* 0x1fbf 0x0300 */
+ 0x1fcd, 0x0000,
+ /* 0x1ffe 0x0300 */
+ 0x1fdd, 0x0000,
+ /* 0x0041 0x0301 */
+ 0x00c1, 0x0000,
+ /* 0x0043 0x0301 */
+ 0x0106, 0x0000,
+ /* 0x0045 0x0301 */
+ 0x00c9, 0x0000,
+ /* 0x0047 0x0301 */
+ 0x01f4, 0x0000,
+ /* 0x0049 0x0301 */
+ 0x00cd, 0x0000,
+ /* 0x004b 0x0301 */
+ 0x1e30, 0x0000,
+ /* 0x004c 0x0301 */
+ 0x0139, 0x0000,
+ /* 0x004d 0x0301 */
+ 0x1e3e, 0x0000,
+ /* 0x004e 0x0301 */
+ 0x0143, 0x0000,
+ /* 0x004f 0x0301 */
+ 0x00d3, 0x0000,
+ /* 0x0050 0x0301 */
+ 0x1e54, 0x0000,
+ /* 0x0052 0x0301 */
+ 0x0154, 0x0000,
+ /* 0x0053 0x0301 */
+ 0x015a, 0x0001, 0x0307, 0x0d68,
+ /* 0x0055 0x0301 */
+ 0x00da, 0x0000,
+ /* 0x0057 0x0301 */
+ 0x1e82, 0x0000,
+ /* 0x0059 0x0301 */
+ 0x00dd, 0x0000,
+ /* 0x005a 0x0301 */
+ 0x0179, 0x0000,
+ /* 0x0061 0x0301 */
+ 0x00e1, 0x0000,
+ /* 0x0063 0x0301 */
+ 0x0107, 0x0000,
+ /* 0x0065 0x0301 */
+ 0x00e9, 0x0000,
+ /* 0x0067 0x0301 */
+ 0x01f5, 0x0000,
+ /* 0x0069 0x0301 */
+ 0x00ed, 0x0000,
+ /* 0x006b 0x0301 */
+ 0x1e31, 0x0000,
+ /* 0x006c 0x0301 */
+ 0x013a, 0x0000,
+ /* 0x006d 0x0301 */
+ 0x1e3f, 0x0000,
+ /* 0x006e 0x0301 */
+ 0x0144, 0x0000,
+ /* 0x006f 0x0301 */
+ 0x00f3, 0x0000,
+ /* 0x0070 0x0301 */
+ 0x1e55, 0x0000,
+ /* 0x0072 0x0301 */
+ 0x0155, 0x0000,
+ /* 0x0073 0x0301 */
+ 0x015b, 0x0001, 0x0307, 0x0d6a,
+ /* 0x0075 0x0301 */
+ 0x00fa, 0x0000,
+ /* 0x0077 0x0301 */
+ 0x1e83, 0x0000,
+ /* 0x0079 0x0301 */
+ 0x00fd, 0x0000,
+ /* 0x007a 0x0301 */
+ 0x017a, 0x0000,
+ /* 0x00a8 0x0301 */
+ 0x1fee, 0x0000,
+ /* 0x00c6 0x0301 */
+ 0x01fc, 0x0000,
+ /* 0x00d8 0x0301 */
+ 0x01fe, 0x0000,
+ /* 0x00e6 0x0301 */
+ 0x01fd, 0x0000,
+ /* 0x00f8 0x0301 */
+ 0x01ff, 0x0000,
+ /* 0x0391 0x0301 */
+ 0x1fbb, 0x0000,
+ /* 0x0395 0x0301 */
+ 0x1fc9, 0x0000,
+ /* 0x0397 0x0301 */
+ 0x1fcb, 0x0000,
+ /* 0x0399 0x0301 */
+ 0x1fdb, 0x0000,
+ /* 0x039f 0x0301 */
+ 0x1ff9, 0x0000,
+ /* 0x03a5 0x0301 */
+ 0x1feb, 0x0000,
+ /* 0x03a9 0x0301 */
+ 0x1ffb, 0x0000,
+ /* 0x03b1 0x0301 */
+ 0x1f71, 0x0000,
+ /* 0x03b5 0x0301 */
+ 0x1f73, 0x0000,
+ /* 0x03b7 0x0301 */
+ 0x1f75, 0x0000,
+ /* 0x03b9 0x0301 */
+ 0x1f77, 0x0000,
+ /* 0x03bf 0x0301 */
+ 0x1f79, 0x0000,
+ /* 0x03c5 0x0301 */
+ 0x1f7b, 0x0000,
+ /* 0x03c9 0x0301 */
+ 0x1f7d, 0x0000,
+ /* 0x0413 0x0301 */
+ 0x0403, 0x0000,
+ /* 0x041a 0x0301 */
+ 0x040c, 0x0000,
+ /* 0x0433 0x0301 */
+ 0x0453, 0x0000,
+ /* 0x043a 0x0301 */
+ 0x045c, 0x0000,
+ /* 0x1fbf 0x0301 */
+ 0x1fce, 0x0000,
+ /* 0x1ffe 0x0301 */
+ 0x1fde, 0x0000,
+ /* 0x0041 0x0302 */
+ 0x00c2, 0x0004, 0x0300, 0x0d6c, 0x0301, 0x0d6e, 0x0303, 0x0d70,
+ 0x0309, 0x0d72,
+ /* 0x0043 0x0302 */
+ 0x0108, 0x0000,
+ /* 0x0045 0x0302 */
+ 0x00ca, 0x0004, 0x0300, 0x0d74, 0x0301, 0x0d76, 0x0303, 0x0d78,
+ 0x0309, 0x0d7a,
+ /* 0x0047 0x0302 */
+ 0x011c, 0x0000,
+ /* 0x0048 0x0302 */
+ 0x0124, 0x0000,
+ /* 0x0049 0x0302 */
+ 0x00ce, 0x0000,
+ /* 0x004a 0x0302 */
+ 0x0134, 0x0000,
+ /* 0x004f 0x0302 */
+ 0x00d4, 0x0004, 0x0300, 0x0d7c, 0x0301, 0x0d7e, 0x0303, 0x0d80,
+ 0x0309, 0x0d82,
+ /* 0x0053 0x0302 */
+ 0x015c, 0x0000,
+ /* 0x0055 0x0302 */
+ 0x00db, 0x0000,
+ /* 0x0057 0x0302 */
+ 0x0174, 0x0000,
+ /* 0x0059 0x0302 */
+ 0x0176, 0x0000,
+ /* 0x005a 0x0302 */
+ 0x1e90, 0x0000,
+ /* 0x0061 0x0302 */
+ 0x00e2, 0x0004, 0x0300, 0x0d84, 0x0301, 0x0d86, 0x0303, 0x0d88,
+ 0x0309, 0x0d8a,
+ /* 0x0063 0x0302 */
+ 0x0109, 0x0000,
+ /* 0x0065 0x0302 */
+ 0x00ea, 0x0004, 0x0300, 0x0d8c, 0x0301, 0x0d8e, 0x0303, 0x0d90,
+ 0x0309, 0x0d92,
+ /* 0x0067 0x0302 */
+ 0x011d, 0x0000,
+ /* 0x0068 0x0302 */
+ 0x0125, 0x0000,
+ /* 0x0069 0x0302 */
+ 0x00ee, 0x0000,
+ /* 0x006a 0x0302 */
+ 0x0135, 0x0000,
+ /* 0x006f 0x0302 */
+ 0x00f4, 0x0004, 0x0300, 0x0d94, 0x0301, 0x0d96, 0x0303, 0x0d98,
+ 0x0309, 0x0d9a,
+ /* 0x0073 0x0302 */
+ 0x015d, 0x0000,
+ /* 0x0075 0x0302 */
+ 0x00fb, 0x0000,
+ /* 0x0077 0x0302 */
+ 0x0175, 0x0000,
+ /* 0x0079 0x0302 */
+ 0x0177, 0x0000,
+ /* 0x007a 0x0302 */
+ 0x1e91, 0x0000,
+ /* 0x0041 0x0303 */
+ 0x00c3, 0x0000,
+ /* 0x0045 0x0303 */
+ 0x1ebc, 0x0000,
+ /* 0x0049 0x0303 */
+ 0x0128, 0x0000,
+ /* 0x004e 0x0303 */
+ 0x00d1, 0x0000,
+ /* 0x004f 0x0303 */
+ 0x00d5, 0x0002, 0x0301, 0x0d9c, 0x0308, 0x0d9e,
+ /* 0x0055 0x0303 */
+ 0x0168, 0x0001, 0x0301, 0x0da0,
+ /* 0x0056 0x0303 */
+ 0x1e7c, 0x0000,
+ /* 0x0059 0x0303 */
+ 0x1ef8, 0x0000,
+ /* 0x0061 0x0303 */
+ 0x00e3, 0x0000,
+ /* 0x0065 0x0303 */
+ 0x1ebd, 0x0000,
+ /* 0x0069 0x0303 */
+ 0x0129, 0x0000,
+ /* 0x006e 0x0303 */
+ 0x00f1, 0x0000,
+ /* 0x006f 0x0303 */
+ 0x00f5, 0x0002, 0x0301, 0x0da2, 0x0308, 0x0da4,
+ /* 0x0075 0x0303 */
+ 0x0169, 0x0001, 0x0301, 0x0da6,
+ /* 0x0076 0x0303 */
+ 0x1e7d, 0x0000,
+ /* 0x0079 0x0303 */
+ 0x1ef9, 0x0000,
+ /* 0x0041 0x0304 */
+ 0x0100, 0x0000,
+ /* 0x0045 0x0304 */
+ 0x0112, 0x0002, 0x0300, 0x0da8, 0x0301, 0x0daa,
+ /* 0x0047 0x0304 */
+ 0x1e20, 0x0000,
+ /* 0x0049 0x0304 */
+ 0x012a, 0x0000,
+ /* 0x004f 0x0304 */
+ 0x014c, 0x0002, 0x0300, 0x0dac, 0x0301, 0x0dae,
+ /* 0x0055 0x0304 */
+ 0x016a, 0x0001, 0x0308, 0x0db0,
+ /* 0x0061 0x0304 */
+ 0x0101, 0x0000,
+ /* 0x0065 0x0304 */
+ 0x0113, 0x0002, 0x0300, 0x0db2, 0x0301, 0x0db4,
+ /* 0x0067 0x0304 */
+ 0x1e21, 0x0000,
+ /* 0x0069 0x0304 */
+ 0x012b, 0x0000,
+ /* 0x006f 0x0304 */
+ 0x014d, 0x0002, 0x0300, 0x0db6, 0x0301, 0x0db8,
+ /* 0x0075 0x0304 */
+ 0x016b, 0x0001, 0x0308, 0x0dba,
+ /* 0x00c6 0x0304 */
+ 0x01e2, 0x0000,
+ /* 0x00e6 0x0304 */
+ 0x01e3, 0x0000,
+ /* 0x0391 0x0304 */
+ 0x1fb9, 0x0000,
+ /* 0x0399 0x0304 */
+ 0x1fd9, 0x0000,
+ /* 0x03a5 0x0304 */
+ 0x1fe9, 0x0000,
+ /* 0x03b1 0x0304 */
+ 0x1fb1, 0x0000,
+ /* 0x03b9 0x0304 */
+ 0x1fd1, 0x0000,
+ /* 0x03c5 0x0304 */
+ 0x1fe1, 0x0000,
+ /* 0x0418 0x0304 */
+ 0x04e2, 0x0000,
+ /* 0x0423 0x0304 */
+ 0x04ee, 0x0000,
+ /* 0x0438 0x0304 */
+ 0x04e3, 0x0000,
+ /* 0x0443 0x0304 */
+ 0x04ef, 0x0000,
+ /* 0x0041 0x0306 */
+ 0x0102, 0x0004, 0x0300, 0x0dbc, 0x0301, 0x0dbe, 0x0303, 0x0dc0,
+ 0x0309, 0x0dc2,
+ /* 0x0045 0x0306 */
+ 0x0114, 0x0000,
+ /* 0x0047 0x0306 */
+ 0x011e, 0x0000,
+ /* 0x0049 0x0306 */
+ 0x012c, 0x0000,
+ /* 0x004f 0x0306 */
+ 0x014e, 0x0000,
+ /* 0x0055 0x0306 */
+ 0x016c, 0x0000,
+ /* 0x0061 0x0306 */
+ 0x0103, 0x0004, 0x0300, 0x0dc4, 0x0301, 0x0dc6, 0x0303, 0x0dc8,
+ 0x0309, 0x0dca,
+ /* 0x0065 0x0306 */
+ 0x0115, 0x0000,
+ /* 0x0067 0x0306 */
+ 0x011f, 0x0000,
+ /* 0x0069 0x0306 */
+ 0x012d, 0x0000,
+ /* 0x006f 0x0306 */
+ 0x014f, 0x0000,
+ /* 0x0075 0x0306 */
+ 0x016d, 0x0000,
+ /* 0x0391 0x0306 */
+ 0x1fb8, 0x0000,
+ /* 0x0399 0x0306 */
+ 0x1fd8, 0x0000,
+ /* 0x03a5 0x0306 */
+ 0x1fe8, 0x0000,
+ /* 0x03b1 0x0306 */
+ 0x1fb0, 0x0000,
+ /* 0x03b9 0x0306 */
+ 0x1fd0, 0x0000,
+ /* 0x03c5 0x0306 */
+ 0x1fe0, 0x0000,
+ /* 0x0410 0x0306 */
+ 0x04d0, 0x0000,
+ /* 0x0415 0x0306 */
+ 0x04d6, 0x0000,
+ /* 0x0416 0x0306 */
+ 0x04c1, 0x0000,
+ /* 0x0418 0x0306 */
+ 0x0419, 0x0000,
+ /* 0x0423 0x0306 */
+ 0x040e, 0x0000,
+ /* 0x0430 0x0306 */
+ 0x04d1, 0x0000,
+ /* 0x0435 0x0306 */
+ 0x04d7, 0x0000,
+ /* 0x0436 0x0306 */
+ 0x04c2, 0x0000,
+ /* 0x0438 0x0306 */
+ 0x0439, 0x0000,
+ /* 0x0443 0x0306 */
+ 0x045e, 0x0000,
+ /* 0x0041 0x0307 */
+ 0x0000, 0x0001, 0x0304, 0x0dcc,
+ /* 0x0042 0x0307 */
+ 0x1e02, 0x0000,
+ /* 0x0043 0x0307 */
+ 0x010a, 0x0000,
+ /* 0x0044 0x0307 */
+ 0x1e0a, 0x0000,
+ /* 0x0045 0x0307 */
+ 0x0116, 0x0000,
+ /* 0x0046 0x0307 */
+ 0x1e1e, 0x0000,
+ /* 0x0047 0x0307 */
+ 0x0120, 0x0000,
+ /* 0x0048 0x0307 */
+ 0x1e22, 0x0000,
+ /* 0x0049 0x0307 */
+ 0x0130, 0x0000,
+ /* 0x004d 0x0307 */
+ 0x1e40, 0x0000,
+ /* 0x004e 0x0307 */
+ 0x1e44, 0x0000,
+ /* 0x0050 0x0307 */
+ 0x1e56, 0x0000,
+ /* 0x0052 0x0307 */
+ 0x1e58, 0x0000,
+ /* 0x0053 0x0307 */
+ 0x1e60, 0x0000,
+ /* 0x0054 0x0307 */
+ 0x1e6a, 0x0000,
+ /* 0x0057 0x0307 */
+ 0x1e86, 0x0000,
+ /* 0x0058 0x0307 */
+ 0x1e8a, 0x0000,
+ /* 0x0059 0x0307 */
+ 0x1e8e, 0x0000,
+ /* 0x005a 0x0307 */
+ 0x017b, 0x0000,
+ /* 0x0061 0x0307 */
+ 0x0000, 0x0001, 0x0304, 0x0dce,
+ /* 0x0062 0x0307 */
+ 0x1e03, 0x0000,
+ /* 0x0063 0x0307 */
+ 0x010b, 0x0000,
+ /* 0x0064 0x0307 */
+ 0x1e0b, 0x0000,
+ /* 0x0065 0x0307 */
+ 0x0117, 0x0000,
+ /* 0x0066 0x0307 */
+ 0x1e1f, 0x0000,
+ /* 0x0067 0x0307 */
+ 0x0121, 0x0000,
+ /* 0x0068 0x0307 */
+ 0x1e23, 0x0000,
+ /* 0x006d 0x0307 */
+ 0x1e41, 0x0000,
+ /* 0x006e 0x0307 */
+ 0x1e45, 0x0000,
+ /* 0x0070 0x0307 */
+ 0x1e57, 0x0000,
+ /* 0x0072 0x0307 */
+ 0x1e59, 0x0000,
+ /* 0x0073 0x0307 */
+ 0x1e61, 0x0000,
+ /* 0x0074 0x0307 */
+ 0x1e6b, 0x0000,
+ /* 0x0077 0x0307 */
+ 0x1e87, 0x0000,
+ /* 0x0078 0x0307 */
+ 0x1e8b, 0x0000,
+ /* 0x0079 0x0307 */
+ 0x1e8f, 0x0000,
+ /* 0x007a 0x0307 */
+ 0x017c, 0x0000,
+ /* 0x017f 0x0307 */
+ 0x1e9b, 0x0000,
+ /* 0x0306 0x0307 */
+ 0x0310, 0x0000,
+ /* 0x0041 0x0308 */
+ 0x00c4, 0x0001, 0x0304, 0x0dd0,
+ /* 0x0045 0x0308 */
+ 0x00cb, 0x0000,
+ /* 0x0048 0x0308 */
+ 0x1e26, 0x0000,
+ /* 0x0049 0x0308 */
+ 0x00cf, 0x0001, 0x0301, 0x0dd2,
+ /* 0x004f 0x0308 */
+ 0x00d6, 0x0000,
+ /* 0x0055 0x0308 */
+ 0x00dc, 0x0004, 0x0300, 0x0dd4, 0x0301, 0x0dd6, 0x0304, 0x0dd8,
+ 0x030c, 0x0dda,
+ /* 0x0057 0x0308 */
+ 0x1e84, 0x0000,
+ /* 0x0058 0x0308 */
+ 0x1e8c, 0x0000,
+ /* 0x0059 0x0308 */
+ 0x0178, 0x0000,
+ /* 0x0061 0x0308 */
+ 0x00e4, 0x0001, 0x0304, 0x0ddc,
+ /* 0x0065 0x0308 */
+ 0x00eb, 0x0000,
+ /* 0x0068 0x0308 */
+ 0x1e27, 0x0000,
+ /* 0x0069 0x0308 */
+ 0x00ef, 0x0001, 0x0301, 0x0dde,
+ /* 0x006f 0x0308 */
+ 0x00f6, 0x0000,
+ /* 0x0074 0x0308 */
+ 0x1e97, 0x0000,
+ /* 0x0075 0x0308 */
+ 0x00fc, 0x0004, 0x0300, 0x0de0, 0x0301, 0x0de2, 0x0304, 0x0de4,
+ 0x030c, 0x0de6,
+ /* 0x0077 0x0308 */
+ 0x1e85, 0x0000,
+ /* 0x0078 0x0308 */
+ 0x1e8d, 0x0000,
+ /* 0x0079 0x0308 */
+ 0x00ff, 0x0000,
+ /* 0x018f 0x0308 */
+ 0x04da, 0x0000,
+ /* 0x019f 0x0308 */
+ 0x04ea, 0x0000,
+ /* 0x0259 0x0308 */
+ 0x04db, 0x0000,
+ /* 0x0275 0x0308 */
+ 0x04eb, 0x0000,
+ /* 0x0399 0x0308 */
+ 0x03aa, 0x0000,
+ /* 0x03a5 0x0308 */
+ 0x03ab, 0x0000,
+ /* 0x03b9 0x0308 */
+ 0x03ca, 0x0004, 0x0300, 0x0de8, 0x0301, 0x0dea, 0x030d, 0x0dec,
+ 0x0342, 0x0dee,
+ /* 0x03c5 0x0308 */
+ 0x03cb, 0x0004, 0x0300, 0x0df0, 0x0301, 0x0df2, 0x030d, 0x0df4,
+ 0x0342, 0x0df6,
+ /* 0x03d2 0x0308 */
+ 0x03d4, 0x0000,
+ /* 0x0406 0x0308 */
+ 0x0407, 0x0000,
+ /* 0x0410 0x0308 */
+ 0x04d2, 0x0000,
+ /* 0x0415 0x0308 */
+ 0x0401, 0x0000,
+ /* 0x0416 0x0308 */
+ 0x04dc, 0x0000,
+ /* 0x0417 0x0308 */
+ 0x04de, 0x0000,
+ /* 0x0418 0x0308 */
+ 0x04e4, 0x0000,
+ /* 0x041e 0x0308 */
+ 0x04e6, 0x0000,
+ /* 0x0423 0x0308 */
+ 0x04f0, 0x0000,
+ /* 0x0427 0x0308 */
+ 0x04f4, 0x0000,
+ /* 0x042b 0x0308 */
+ 0x04f8, 0x0000,
+ /* 0x0430 0x0308 */
+ 0x04d3, 0x0000,
+ /* 0x0435 0x0308 */
+ 0x0451, 0x0000,
+ /* 0x0436 0x0308 */
+ 0x04dd, 0x0000,
+ /* 0x0437 0x0308 */
+ 0x04df, 0x0000,
+ /* 0x0438 0x0308 */
+ 0x04e5, 0x0000,
+ /* 0x043e 0x0308 */
+ 0x04e7, 0x0000,
+ /* 0x0443 0x0308 */
+ 0x04f1, 0x0000,
+ /* 0x0447 0x0308 */
+ 0x04f5, 0x0000,
+ /* 0x044b 0x0308 */
+ 0x04f9, 0x0000,
+ /* 0x0456 0x0308 */
+ 0x0457, 0x0000,
+ /* 0x0041 0x0309 */
+ 0x1ea2, 0x0000,
+ /* 0x0045 0x0309 */
+ 0x1eba, 0x0000,
+ /* 0x0049 0x0309 */
+ 0x1ec8, 0x0000,
+ /* 0x004f 0x0309 */
+ 0x1ece, 0x0000,
+ /* 0x0055 0x0309 */
+ 0x1ee6, 0x0000,
+ /* 0x0059 0x0309 */
+ 0x1ef6, 0x0000,
+ /* 0x0061 0x0309 */
+ 0x1ea3, 0x0000,
+ /* 0x0065 0x0309 */
+ 0x1ebb, 0x0000,
+ /* 0x0069 0x0309 */
+ 0x1ec9, 0x0000,
+ /* 0x006f 0x0309 */
+ 0x1ecf, 0x0000,
+ /* 0x0075 0x0309 */
+ 0x1ee7, 0x0000,
+ /* 0x0079 0x0309 */
+ 0x1ef7, 0x0000,
+ /* 0x0041 0x030a */
+ 0x00c5, 0x0001, 0x0301, 0x0df8,
+ /* 0x0055 0x030a */
+ 0x016e, 0x0000,
+ /* 0x0061 0x030a */
+ 0x00e5, 0x0001, 0x0301, 0x0dfa,
+ /* 0x0075 0x030a */
+ 0x016f, 0x0000,
+ /* 0x0077 0x030a */
+ 0x1e98, 0x0000,
+ /* 0x0079 0x030a */
+ 0x1e99, 0x0000,
+ /* 0x004f 0x030b */
+ 0x0150, 0x0000,
+ /* 0x0055 0x030b */
+ 0x0170, 0x0000,
+ /* 0x006f 0x030b */
+ 0x0151, 0x0000,
+ /* 0x0075 0x030b */
+ 0x0171, 0x0000,
+ /* 0x0423 0x030b */
+ 0x04f2, 0x0000,
+ /* 0x0443 0x030b */
+ 0x04f3, 0x0000,
+ /* 0x0041 0x030c */
+ 0x01cd, 0x0000,
+ /* 0x0043 0x030c */
+ 0x010c, 0x0000,
+ /* 0x0044 0x030c */
+ 0x010e, 0x0000,
+ /* 0x0045 0x030c */
+ 0x011a, 0x0000,
+ /* 0x0047 0x030c */
+ 0x01e6, 0x0000,
+ /* 0x0049 0x030c */
+ 0x01cf, 0x0000,
+ /* 0x004b 0x030c */
+ 0x01e8, 0x0000,
+ /* 0x004c 0x030c */
+ 0x013d, 0x0000,
+ /* 0x004e 0x030c */
+ 0x0147, 0x0000,
+ /* 0x004f 0x030c */
+ 0x01d1, 0x0000,
+ /* 0x0052 0x030c */
+ 0x0158, 0x0000,
+ /* 0x0053 0x030c */
+ 0x0160, 0x0001, 0x0307, 0x0dfc,
+ /* 0x0054 0x030c */
+ 0x0164, 0x0000,
+ /* 0x0055 0x030c */
+ 0x01d3, 0x0000,
+ /* 0x005a 0x030c */
+ 0x017d, 0x0000,
+ /* 0x0061 0x030c */
+ 0x01ce, 0x0000,
+ /* 0x0063 0x030c */
+ 0x010d, 0x0000,
+ /* 0x0064 0x030c */
+ 0x010f, 0x0000,
+ /* 0x0065 0x030c */
+ 0x011b, 0x0000,
+ /* 0x0067 0x030c */
+ 0x01e7, 0x0000,
+ /* 0x0069 0x030c */
+ 0x01d0, 0x0000,
+ /* 0x006a 0x030c */
+ 0x01f0, 0x0000,
+ /* 0x006b 0x030c */
+ 0x01e9, 0x0000,
+ /* 0x006c 0x030c */
+ 0x013e, 0x0000,
+ /* 0x006e 0x030c */
+ 0x0148, 0x0000,
+ /* 0x006f 0x030c */
+ 0x01d2, 0x0000,
+ /* 0x0072 0x030c */
+ 0x0159, 0x0000,
+ /* 0x0073 0x030c */
+ 0x0161, 0x0001, 0x0307, 0x0dfe,
+ /* 0x0074 0x030c */
+ 0x0165, 0x0000,
+ /* 0x0075 0x030c */
+ 0x01d4, 0x0000,
+ /* 0x007a 0x030c */
+ 0x017e, 0x0000,
+ /* 0x01b7 0x030c */
+ 0x01ee, 0x0000,
+ /* 0x0292 0x030c */
+ 0x01ef, 0x0000,
+ /* 0x00a8 0x030d */
+ 0x0385, 0x0000,
+ /* 0x0308 0x030d */
+ 0x0344, 0x0000,
+ /* 0x0391 0x030d */
+ 0x0386, 0x0000,
+ /* 0x0395 0x030d */
+ 0x0388, 0x0000,
+ /* 0x0397 0x030d */
+ 0x0389, 0x0000,
+ /* 0x0399 0x030d */
+ 0x038a, 0x0000,
+ /* 0x039f 0x030d */
+ 0x038c, 0x0000,
+ /* 0x03a5 0x030d */
+ 0x038e, 0x0000,
+ /* 0x03a9 0x030d */
+ 0x038f, 0x0000,
+ /* 0x03b1 0x030d */
+ 0x03ac, 0x0000,
+ /* 0x03b5 0x030d */
+ 0x03ad, 0x0000,
+ /* 0x03b7 0x030d */
+ 0x03ae, 0x0000,
+ /* 0x03b9 0x030d */
+ 0x03af, 0x0000,
+ /* 0x03bf 0x030d */
+ 0x03cc, 0x0000,
+ /* 0x03c5 0x030d */
+ 0x03cd, 0x0000,
+ /* 0x03c9 0x030d */
+ 0x03ce, 0x0000,
+ /* 0x03d2 0x030d */
+ 0x03d3, 0x0000,
+ /* 0x0041 0x030f */
+ 0x0200, 0x0000,
+ /* 0x0045 0x030f */
+ 0x0204, 0x0000,
+ /* 0x0049 0x030f */
+ 0x0208, 0x0000,
+ /* 0x004f 0x030f */
+ 0x020c, 0x0000,
+ /* 0x0052 0x030f */
+ 0x0210, 0x0000,
+ /* 0x0055 0x030f */
+ 0x0214, 0x0000,
+ /* 0x0061 0x030f */
+ 0x0201, 0x0000,
+ /* 0x0065 0x030f */
+ 0x0205, 0x0000,
+ /* 0x0069 0x030f */
+ 0x0209, 0x0000,
+ /* 0x006f 0x030f */
+ 0x020d, 0x0000,
+ /* 0x0072 0x030f */
+ 0x0211, 0x0000,
+ /* 0x0075 0x030f */
+ 0x0215, 0x0000,
+ /* 0x0474 0x030f */
+ 0x0476, 0x0000,
+ /* 0x0475 0x030f */
+ 0x0477, 0x0000,
+ /* 0x0041 0x0311 */
+ 0x0202, 0x0000,
+ /* 0x0045 0x0311 */
+ 0x0206, 0x0000,
+ /* 0x0049 0x0311 */
+ 0x020a, 0x0000,
+ /* 0x004f 0x0311 */
+ 0x020e, 0x0000,
+ /* 0x0052 0x0311 */
+ 0x0212, 0x0000,
+ /* 0x0055 0x0311 */
+ 0x0216, 0x0000,
+ /* 0x0061 0x0311 */
+ 0x0203, 0x0000,
+ /* 0x0065 0x0311 */
+ 0x0207, 0x0000,
+ /* 0x0069 0x0311 */
+ 0x020b, 0x0000,
+ /* 0x006f 0x0311 */
+ 0x020f, 0x0000,
+ /* 0x0072 0x0311 */
+ 0x0213, 0x0000,
+ /* 0x0075 0x0311 */
+ 0x0217, 0x0000,
+ /* 0x0391 0x0313 */
+ 0x1f08, 0x0003, 0x0300, 0x0e00, 0x0301, 0x0e02, 0x0342, 0x0e04,
+ /* 0x0395 0x0313 */
+ 0x1f18, 0x0002, 0x0300, 0x0e06, 0x0301, 0x0e08,
+ /* 0x0397 0x0313 */
+ 0x1f28, 0x0003, 0x0300, 0x0e0a, 0x0301, 0x0e0c, 0x0342, 0x0e0e,
+ /* 0x0399 0x0313 */
+ 0x1f38, 0x0003, 0x0300, 0x0e10, 0x0301, 0x0e12, 0x0342, 0x0e14,
+ /* 0x039f 0x0313 */
+ 0x1f48, 0x0002, 0x0300, 0x0e16, 0x0301, 0x0e18,
+ /* 0x03a9 0x0313 */
+ 0x1f68, 0x0003, 0x0300, 0x0e1a, 0x0301, 0x0e1c, 0x0342, 0x0e1e,
+ /* 0x03b1 0x0313 */
+ 0x1f00, 0x0003, 0x0300, 0x0e20, 0x0301, 0x0e22, 0x0342, 0x0e24,
+ /* 0x03b5 0x0313 */
+ 0x1f10, 0x0002, 0x0300, 0x0e26, 0x0301, 0x0e28,
+ /* 0x03b7 0x0313 */
+ 0x1f20, 0x0003, 0x0300, 0x0e2a, 0x0301, 0x0e2c, 0x0342, 0x0e2e,
+ /* 0x03b9 0x0313 */
+ 0x1f30, 0x0003, 0x0300, 0x0e30, 0x0301, 0x0e32, 0x0342, 0x0e34,
+ /* 0x03bf 0x0313 */
+ 0x1f40, 0x0002, 0x0300, 0x0e36, 0x0301, 0x0e38,
+ /* 0x03c1 0x0313 */
+ 0x1fe4, 0x0000,
+ /* 0x03c5 0x0313 */
+ 0x1f50, 0x0003, 0x0300, 0x0e3a, 0x0301, 0x0e3c, 0x0342, 0x0e3e,
+ /* 0x03c9 0x0313 */
+ 0x1f60, 0x0003, 0x0300, 0x0e40, 0x0301, 0x0e42, 0x0342, 0x0e44,
+ /* 0x0391 0x0314 */
+ 0x1f09, 0x0003, 0x0300, 0x0e46, 0x0301, 0x0e48, 0x0342, 0x0e4a,
+ /* 0x0395 0x0314 */
+ 0x1f19, 0x0002, 0x0300, 0x0e4c, 0x0301, 0x0e4e,
+ /* 0x0397 0x0314 */
+ 0x1f29, 0x0003, 0x0300, 0x0e50, 0x0301, 0x0e52, 0x0342, 0x0e54,
+ /* 0x0399 0x0314 */
+ 0x1f39, 0x0003, 0x0300, 0x0e56, 0x0301, 0x0e58, 0x0342, 0x0e5a,
+ /* 0x039f 0x0314 */
+ 0x1f49, 0x0002, 0x0300, 0x0e5c, 0x0301, 0x0e5e,
+ /* 0x03a1 0x0314 */
+ 0x1fec, 0x0000,
+ /* 0x03a5 0x0314 */
+ 0x1f59, 0x0003, 0x0300, 0x0e60, 0x0301, 0x0e62, 0x0342, 0x0e64,
+ /* 0x03a9 0x0314 */
+ 0x1f69, 0x0003, 0x0300, 0x0e66, 0x0301, 0x0e68, 0x0342, 0x0e6a,
+ /* 0x03b1 0x0314 */
+ 0x1f01, 0x0003, 0x0300, 0x0e6c, 0x0301, 0x0e6e, 0x0342, 0x0e70,
+ /* 0x03b5 0x0314 */
+ 0x1f11, 0x0002, 0x0300, 0x0e72, 0x0301, 0x0e74,
+ /* 0x03b7 0x0314 */
+ 0x1f21, 0x0003, 0x0300, 0x0e76, 0x0301, 0x0e78, 0x0342, 0x0e7a,
+ /* 0x03b9 0x0314 */
+ 0x1f31, 0x0003, 0x0300, 0x0e7c, 0x0301, 0x0e7e, 0x0342, 0x0e80,
+ /* 0x03bf 0x0314 */
+ 0x1f41, 0x0002, 0x0300, 0x0e82, 0x0301, 0x0e84,
+ /* 0x03c1 0x0314 */
+ 0x1fe5, 0x0000,
+ /* 0x03c5 0x0314 */
+ 0x1f51, 0x0003, 0x0300, 0x0e86, 0x0301, 0x0e88, 0x0342, 0x0e8a,
+ /* 0x03c9 0x0314 */
+ 0x1f61, 0x0003, 0x0300, 0x0e8c, 0x0301, 0x0e8e, 0x0342, 0x0e90,
+ /* 0x004f 0x031b */
+ 0x01a0, 0x0005, 0x0300, 0x0e92, 0x0301, 0x0e94, 0x0303, 0x0e96,
+ 0x0309, 0x0e98, 0x0323, 0x0e9a,
+ /* 0x0055 0x031b */
+ 0x01af, 0x0005, 0x0300, 0x0e9c, 0x0301, 0x0e9e, 0x0303, 0x0ea0,
+ 0x0309, 0x0ea2, 0x0323, 0x0ea4,
+ /* 0x006f 0x031b */
+ 0x01a1, 0x0005, 0x0300, 0x0ea6, 0x0301, 0x0ea8, 0x0303, 0x0eaa,
+ 0x0309, 0x0eac, 0x0323, 0x0eae,
+ /* 0x0075 0x031b */
+ 0x01b0, 0x0005, 0x0300, 0x0eb0, 0x0301, 0x0eb2, 0x0303, 0x0eb4,
+ 0x0309, 0x0eb6, 0x0323, 0x0eb8,
+ /* 0x0041 0x0323 */
+ 0x1ea0, 0x0002, 0x0302, 0x0eba, 0x0306, 0x0ebc,
+ /* 0x0042 0x0323 */
+ 0x1e04, 0x0000,
+ /* 0x0044 0x0323 */
+ 0x1e0c, 0x0000,
+ /* 0x0045 0x0323 */
+ 0x1eb8, 0x0001, 0x0302, 0x0ebe,
+ /* 0x0048 0x0323 */
+ 0x1e24, 0x0000,
+ /* 0x0049 0x0323 */
+ 0x1eca, 0x0000,
+ /* 0x004b 0x0323 */
+ 0x1e32, 0x0000,
+ /* 0x004c 0x0323 */
+ 0x1e36, 0x0001, 0x0304, 0x0ec0,
+ /* 0x004d 0x0323 */
+ 0x1e42, 0x0000,
+ /* 0x004e 0x0323 */
+ 0x1e46, 0x0000,
+ /* 0x004f 0x0323 */
+ 0x1ecc, 0x0001, 0x0302, 0x0ec2,
+ /* 0x0052 0x0323 */
+ 0x1e5a, 0x0001, 0x0304, 0x0ec4,
+ /* 0x0053 0x0323 */
+ 0x1e62, 0x0001, 0x0307, 0x0ec6,
+ /* 0x0054 0x0323 */
+ 0x1e6c, 0x0000,
+ /* 0x0055 0x0323 */
+ 0x1ee4, 0x0000,
+ /* 0x0056 0x0323 */
+ 0x1e7e, 0x0000,
+ /* 0x0057 0x0323 */
+ 0x1e88, 0x0000,
+ /* 0x0059 0x0323 */
+ 0x1ef4, 0x0000,
+ /* 0x005a 0x0323 */
+ 0x1e92, 0x0000,
+ /* 0x0061 0x0323 */
+ 0x1ea1, 0x0002, 0x0302, 0x0ec8, 0x0306, 0x0eca,
+ /* 0x0062 0x0323 */
+ 0x1e05, 0x0000,
+ /* 0x0064 0x0323 */
+ 0x1e0d, 0x0000,
+ /* 0x0065 0x0323 */
+ 0x1eb9, 0x0001, 0x0302, 0x0ecc,
+ /* 0x0068 0x0323 */
+ 0x1e25, 0x0000,
+ /* 0x0069 0x0323 */
+ 0x1ecb, 0x0000,
+ /* 0x006b 0x0323 */
+ 0x1e33, 0x0000,
+ /* 0x006c 0x0323 */
+ 0x1e37, 0x0001, 0x0304, 0x0ece,
+ /* 0x006d 0x0323 */
+ 0x1e43, 0x0000,
+ /* 0x006e 0x0323 */
+ 0x1e47, 0x0000,
+ /* 0x006f 0x0323 */
+ 0x1ecd, 0x0001, 0x0302, 0x0ed0,
+ /* 0x0072 0x0323 */
+ 0x1e5b, 0x0001, 0x0304, 0x0ed2,
+ /* 0x0073 0x0323 */
+ 0x1e63, 0x0001, 0x0307, 0x0ed4,
+ /* 0x0074 0x0323 */
+ 0x1e6d, 0x0000,
+ /* 0x0075 0x0323 */
+ 0x1ee5, 0x0000,
+ /* 0x0076 0x0323 */
+ 0x1e7f, 0x0000,
+ /* 0x0077 0x0323 */
+ 0x1e89, 0x0000,
+ /* 0x0079 0x0323 */
+ 0x1ef5, 0x0000,
+ /* 0x007a 0x0323 */
+ 0x1e93, 0x0000,
+ /* 0x0055 0x0324 */
+ 0x1e72, 0x0000,
+ /* 0x0075 0x0324 */
+ 0x1e73, 0x0000,
+ /* 0x0041 0x0325 */
+ 0x1e00, 0x0000,
+ /* 0x0061 0x0325 */
+ 0x1e01, 0x0000,
+ /* 0x0043 0x0327 */
+ 0x00c7, 0x0001, 0x0301, 0x0ed6,
+ /* 0x0044 0x0327 */
+ 0x1e10, 0x0000,
+ /* 0x0045 0x0327 */
+ 0x0000, 0x0001, 0x0306, 0x0ed8,
+ /* 0x0047 0x0327 */
+ 0x0122, 0x0000,
+ /* 0x0048 0x0327 */
+ 0x1e28, 0x0000,
+ /* 0x004b 0x0327 */
+ 0x0136, 0x0000,
+ /* 0x004c 0x0327 */
+ 0x013b, 0x0000,
+ /* 0x004e 0x0327 */
+ 0x0145, 0x0000,
+ /* 0x0052 0x0327 */
+ 0x0156, 0x0000,
+ /* 0x0053 0x0327 */
+ 0x015e, 0x0000,
+ /* 0x0054 0x0327 */
+ 0x0162, 0x0000,
+ /* 0x0063 0x0327 */
+ 0x00e7, 0x0001, 0x0301, 0x0eda,
+ /* 0x0064 0x0327 */
+ 0x1e11, 0x0000,
+ /* 0x0065 0x0327 */
+ 0x0000, 0x0001, 0x0306, 0x0edc,
+ /* 0x0067 0x0327 */
+ 0x0123, 0x0000,
+ /* 0x0068 0x0327 */
+ 0x1e29, 0x0000,
+ /* 0x006b 0x0327 */
+ 0x0137, 0x0000,
+ /* 0x006c 0x0327 */
+ 0x013c, 0x0000,
+ /* 0x006e 0x0327 */
+ 0x0146, 0x0000,
+ /* 0x0072 0x0327 */
+ 0x0157, 0x0000,
+ /* 0x0073 0x0327 */
+ 0x015f, 0x0000,
+ /* 0x0074 0x0327 */
+ 0x0163, 0x0000,
+ /* 0x0041 0x0328 */
+ 0x0104, 0x0000,
+ /* 0x0045 0x0328 */
+ 0x0118, 0x0000,
+ /* 0x0049 0x0328 */
+ 0x012e, 0x0000,
+ /* 0x004f 0x0328 */
+ 0x01ea, 0x0001, 0x0304, 0x0ede,
+ /* 0x0055 0x0328 */
+ 0x0172, 0x0000,
+ /* 0x0061 0x0328 */
+ 0x0105, 0x0000,
+ /* 0x0065 0x0328 */
+ 0x0119, 0x0000,
+ /* 0x0069 0x0328 */
+ 0x012f, 0x0000,
+ /* 0x006f 0x0328 */
+ 0x01eb, 0x0001, 0x0304, 0x0ee0,
+ /* 0x0075 0x0328 */
+ 0x0173, 0x0000,
+ /* 0x0044 0x032d */
+ 0x1e12, 0x0000,
+ /* 0x0045 0x032d */
+ 0x1e18, 0x0000,
+ /* 0x004c 0x032d */
+ 0x1e3c, 0x0000,
+ /* 0x004e 0x032d */
+ 0x1e4a, 0x0000,
+ /* 0x0054 0x032d */
+ 0x1e70, 0x0000,
+ /* 0x0055 0x032d */
+ 0x1e76, 0x0000,
+ /* 0x0064 0x032d */
+ 0x1e13, 0x0000,
+ /* 0x0065 0x032d */
+ 0x1e19, 0x0000,
+ /* 0x006c 0x032d */
+ 0x1e3d, 0x0000,
+ /* 0x006e 0x032d */
+ 0x1e4b, 0x0000,
+ /* 0x0074 0x032d */
+ 0x1e71, 0x0000,
+ /* 0x0075 0x032d */
+ 0x1e77, 0x0000,
+ /* 0x0048 0x032e */
+ 0x1e2a, 0x0000,
+ /* 0x0068 0x032e */
+ 0x1e2b, 0x0000,
+ /* 0x0045 0x0330 */
+ 0x1e1a, 0x0000,
+ /* 0x0049 0x0330 */
+ 0x1e2c, 0x0000,
+ /* 0x0055 0x0330 */
+ 0x1e74, 0x0000,
+ /* 0x0065 0x0330 */
+ 0x1e1b, 0x0000,
+ /* 0x0069 0x0330 */
+ 0x1e2d, 0x0000,
+ /* 0x0075 0x0330 */
+ 0x1e75, 0x0000,
+ /* 0x0042 0x0331 */
+ 0x1e06, 0x0000,
+ /* 0x0044 0x0331 */
+ 0x1e0e, 0x0000,
+ /* 0x004b 0x0331 */
+ 0x1e34, 0x0000,
+ /* 0x004c 0x0331 */
+ 0x1e3a, 0x0000,
+ /* 0x004e 0x0331 */
+ 0x1e48, 0x0000,
+ /* 0x0052 0x0331 */
+ 0x1e5e, 0x0000,
+ /* 0x0054 0x0331 */
+ 0x1e6e, 0x0000,
+ /* 0x005a 0x0331 */
+ 0x1e94, 0x0000,
+ /* 0x0062 0x0331 */
+ 0x1e07, 0x0000,
+ /* 0x0064 0x0331 */
+ 0x1e0f, 0x0000,
+ /* 0x0068 0x0331 */
+ 0x1e96, 0x0000,
+ /* 0x006b 0x0331 */
+ 0x1e35, 0x0000,
+ /* 0x006c 0x0331 */
+ 0x1e3b, 0x0000,
+ /* 0x006e 0x0331 */
+ 0x1e49, 0x0000,
+ /* 0x0072 0x0331 */
+ 0x1e5f, 0x0000,
+ /* 0x0074 0x0331 */
+ 0x1e6f, 0x0000,
+ /* 0x007a 0x0331 */
+ 0x1e95, 0x0000,
+ /* 0x00a8 0x0342 */
+ 0x1fc1, 0x0000,
+ /* 0x03b1 0x0342 */
+ 0x1fb6, 0x0000,
+ /* 0x03b7 0x0342 */
+ 0x1fc6, 0x0000,
+ /* 0x03b9 0x0342 */
+ 0x1fd6, 0x0000,
+ /* 0x03c5 0x0342 */
+ 0x1fe6, 0x0000,
+ /* 0x03c9 0x0342 */
+ 0x1ff6, 0x0000,
+ /* 0x1fbf 0x0342 */
+ 0x1fcf, 0x0000,
+ /* 0x1ffe 0x0342 */
+ 0x1fdf, 0x0000,
+ /* 0x0391 0x0345 */
+ 0x1fbc, 0x0002, 0x0313, 0x0ee2, 0x0314, 0x0eea,
+ /* 0x0397 0x0345 */
+ 0x1fcc, 0x0002, 0x0313, 0x0ef2, 0x0314, 0x0efa,
+ /* 0x03a9 0x0345 */
+ 0x1ffc, 0x0002, 0x0313, 0x0f02, 0x0314, 0x0f0a,
+ /* 0x03b1 0x0345 */
+ 0x1fb3, 0x0005, 0x0300, 0x0f12, 0x0301, 0x0f14, 0x0313, 0x0f16,
+ 0x0314, 0x0f1e, 0x0342, 0x0f26,
+ /* 0x03b7 0x0345 */
+ 0x1fc3, 0x0005, 0x0300, 0x0f28, 0x0301, 0x0f2a, 0x0313, 0x0f2c,
+ 0x0314, 0x0f34, 0x0342, 0x0f3c,
+ /* 0x03bf 0x0345 */
+ 0x0000, 0x0001, 0x0301, 0x0f3e,
+ /* 0x03c9 0x0345 */
+ 0x1ff3, 0x0004, 0x0300, 0x0f40, 0x0313, 0x0f42, 0x0314, 0x0f4a,
+ 0x0342, 0x0f52,
+ /* 0x05d0 0x05b7 */
+ 0xfb2e, 0x0000,
+ /* 0x05f2 0x05b7 */
+ 0xfb1f, 0x0000,
+ /* 0x05d0 0x05b8 */
+ 0xfb2f, 0x0000,
+ /* 0x05d5 0x05b9 */
+ 0xfb4b, 0x0000,
+ /* 0x05d0 0x05bc */
+ 0xfb30, 0x0000,
+ /* 0x05d1 0x05bc */
+ 0xfb31, 0x0000,
+ /* 0x05d2 0x05bc */
+ 0xfb32, 0x0000,
+ /* 0x05d3 0x05bc */
+ 0xfb33, 0x0000,
+ /* 0x05d4 0x05bc */
+ 0xfb34, 0x0000,
+ /* 0x05d5 0x05bc */
+ 0xfb35, 0x0000,
+ /* 0x05d6 0x05bc */
+ 0xfb36, 0x0000,
+ /* 0x05d8 0x05bc */
+ 0xfb38, 0x0000,
+ /* 0x05d9 0x05bc */
+ 0xfb39, 0x0000,
+ /* 0x05da 0x05bc */
+ 0xfb3a, 0x0000,
+ /* 0x05db 0x05bc */
+ 0xfb3b, 0x0000,
+ /* 0x05dc 0x05bc */
+ 0xfb3c, 0x0000,
+ /* 0x05de 0x05bc */
+ 0xfb3e, 0x0000,
+ /* 0x05e0 0x05bc */
+ 0xfb40, 0x0000,
+ /* 0x05e1 0x05bc */
+ 0xfb41, 0x0000,
+ /* 0x05e3 0x05bc */
+ 0xfb43, 0x0000,
+ /* 0x05e4 0x05bc */
+ 0xfb44, 0x0000,
+ /* 0x05e6 0x05bc */
+ 0xfb46, 0x0000,
+ /* 0x05e7 0x05bc */
+ 0xfb47, 0x0000,
+ /* 0x05e8 0x05bc */
+ 0xfb48, 0x0000,
+ /* 0x05e9 0x05bc */
+ 0xfb49, 0x0002, 0x05c1, 0x0f54, 0x05c2, 0x0f56,
+ /* 0x05ea 0x05bc */
+ 0xfb4a, 0x0000,
+ /* 0x05d1 0x05bf */
+ 0xfb4c, 0x0000,
+ /* 0x05db 0x05bf */
+ 0xfb4d, 0x0000,
+ /* 0x05e4 0x05bf */
+ 0xfb4e, 0x0000,
+ /* 0x05e9 0x05c1 */
+ 0xfb2a, 0x0000,
+ /* 0x05e9 0x05c2 */
+ 0xfb2b, 0x0000,
+ /* 0x0915 0x093c */
+ 0x0958, 0x0000,
+ /* 0x0916 0x093c */
+ 0x0959, 0x0000,
+ /* 0x0917 0x093c */
+ 0x095a, 0x0000,
+ /* 0x091c 0x093c */
+ 0x095b, 0x0000,
+ /* 0x0921 0x093c */
+ 0x095c, 0x0000,
+ /* 0x0922 0x093c */
+ 0x095d, 0x0000,
+ /* 0x0928 0x093c */
+ 0x0929, 0x0000,
+ /* 0x092b 0x093c */
+ 0x095e, 0x0000,
+ /* 0x092f 0x093c */
+ 0x095f, 0x0000,
+ /* 0x0930 0x093c */
+ 0x0931, 0x0000,
+ /* 0x0933 0x093c */
+ 0x0934, 0x0000,
+ /* 0x09a1 0x09bc */
+ 0x09dc, 0x0000,
+ /* 0x09a2 0x09bc */
+ 0x09dd, 0x0000,
+ /* 0x09ac 0x09bc */
+ 0x09b0, 0x0000,
+ /* 0x09af 0x09bc */
+ 0x09df, 0x0000,
+ /* 0x09c7 0x09be */
+ 0x09cb, 0x0000,
+ /* 0x09c7 0x09d7 */
+ 0x09cc, 0x0000,
+ /* 0x0a16 0x0a3c */
+ 0x0a59, 0x0000,
+ /* 0x0a17 0x0a3c */
+ 0x0a5a, 0x0000,
+ /* 0x0a1c 0x0a3c */
+ 0x0a5b, 0x0000,
+ /* 0x0a21 0x0a3c */
+ 0x0a5c, 0x0000,
+ /* 0x0a2b 0x0a3c */
+ 0x0a5e, 0x0000,
+ /* 0x0b21 0x0b3c */
+ 0x0b5c, 0x0000,
+ /* 0x0b22 0x0b3c */
+ 0x0b5d, 0x0000,
+ /* 0x0b2f 0x0b3c */
+ 0x0b5f, 0x0000,
+ /* 0x0b47 0x0b3e */
+ 0x0b4b, 0x0000,
+ /* 0x0b47 0x0b56 */
+ 0x0b48, 0x0000,
+ /* 0x0b47 0x0b57 */
+ 0x0b4c, 0x0000,
+ /* 0x0bc6 0x0bbe */
+ 0x0bca, 0x0000,
+ /* 0x0bc7 0x0bbe */
+ 0x0bcb, 0x0000,
+ /* 0x0b92 0x0bd7 */
+ 0x0b94, 0x0000,
+ /* 0x0bc6 0x0bd7 */
+ 0x0bcc, 0x0000,
+ /* 0x0c46 0x0c56 */
+ 0x0c48, 0x0000,
+ /* 0x0cc6 0x0cc2 */
+ 0x0cca, 0x0001, 0x0cd5, 0x0f58,
+ /* 0x0cbf 0x0cd5 */
+ 0x0cc0, 0x0000,
+ /* 0x0cc6 0x0cd5 */
+ 0x0cc7, 0x0000,
+ /* 0x0cc6 0x0cd6 */
+ 0x0cc8, 0x0000,
+ /* 0x0d46 0x0d3e */
+ 0x0d4a, 0x0000,
+ /* 0x0d47 0x0d3e */
+ 0x0d4b, 0x0000,
+ /* 0x0d46 0x0d57 */
+ 0x0d4c, 0x0000,
+ /* 0x0e4d 0x0e32 */
+ 0x0e33, 0x0000,
+ /* 0x0ecd 0x0eb2 */
+ 0x0eb3, 0x0000,
+ /* 0x0f72 0x0f71 */
+ 0x0f73, 0x0000,
+ /* 0x0f74 0x0f71 */
+ 0x0f75, 0x0000,
+ /* 0x0f80 0x0f71 */
+ 0x0f81, 0x0000,
+ /* 0x0fb2 0x0f80 */
+ 0x0f76, 0x0001, 0x0f71, 0x0f5a,
+ /* 0x0fb3 0x0f80 */
+ 0x0f78, 0x0001, 0x0f71, 0x0f5c,
+ /* 0x0f40 0x0fb5 */
+ 0x0f69, 0x0000,
+ /* 0x0f90 0x0fb5 */
+ 0x0fb9, 0x0000,
+ /* 0x0f42 0x0fb7 */
+ 0x0f43, 0x0000,
+ /* 0x0f4c 0x0fb7 */
+ 0x0f4d, 0x0000,
+ /* 0x0f51 0x0fb7 */
+ 0x0f52, 0x0000,
+ /* 0x0f56 0x0fb7 */
+ 0x0f57, 0x0000,
+ /* 0x0f5b 0x0fb7 */
+ 0x0f5c, 0x0000,
+ /* 0x0f92 0x0fb7 */
+ 0x0f93, 0x0000,
+ /* 0x0f9c 0x0fb7 */
+ 0x0f9d, 0x0000,
+ /* 0x0fa1 0x0fb7 */
+ 0x0fa2, 0x0000,
+ /* 0x0fa6 0x0fb7 */
+ 0x0fa7, 0x0000,
+ /* 0x0fab 0x0fb7 */
+ 0x0fac, 0x0000,
+ /* 0x3046 0x3099 */
+ 0x3094, 0x0000,
+ /* 0x304b 0x3099 */
+ 0x304c, 0x0000,
+ /* 0x304d 0x3099 */
+ 0x304e, 0x0000,
+ /* 0x304f 0x3099 */
+ 0x3050, 0x0000,
+ /* 0x3051 0x3099 */
+ 0x3052, 0x0000,
+ /* 0x3053 0x3099 */
+ 0x3054, 0x0000,
+ /* 0x3055 0x3099 */
+ 0x3056, 0x0000,
+ /* 0x3057 0x3099 */
+ 0x3058, 0x0000,
+ /* 0x3059 0x3099 */
+ 0x305a, 0x0000,
+ /* 0x305b 0x3099 */
+ 0x305c, 0x0000,
+ /* 0x305d 0x3099 */
+ 0x305e, 0x0000,
+ /* 0x305f 0x3099 */
+ 0x3060, 0x0000,
+ /* 0x3061 0x3099 */
+ 0x3062, 0x0000,
+ /* 0x3064 0x3099 */
+ 0x3065, 0x0000,
+ /* 0x3066 0x3099 */
+ 0x3067, 0x0000,
+ /* 0x3068 0x3099 */
+ 0x3069, 0x0000,
+ /* 0x306f 0x3099 */
+ 0x3070, 0x0000,
+ /* 0x3072 0x3099 */
+ 0x3073, 0x0000,
+ /* 0x3075 0x3099 */
+ 0x3076, 0x0000,
+ /* 0x3078 0x3099 */
+ 0x3079, 0x0000,
+ /* 0x307b 0x3099 */
+ 0x307c, 0x0000,
+ /* 0x309d 0x3099 */
+ 0x309e, 0x0000,
+ /* 0x30a6 0x3099 */
+ 0x30f4, 0x0000,
+ /* 0x30ab 0x3099 */
+ 0x30ac, 0x0000,
+ /* 0x30ad 0x3099 */
+ 0x30ae, 0x0000,
+ /* 0x30af 0x3099 */
+ 0x30b0, 0x0000,
+ /* 0x30b1 0x3099 */
+ 0x30b2, 0x0000,
+ /* 0x30b3 0x3099 */
+ 0x30b4, 0x0000,
+ /* 0x30b5 0x3099 */
+ 0x30b6, 0x0000,
+ /* 0x30b7 0x3099 */
+ 0x30b8, 0x0000,
+ /* 0x30b9 0x3099 */
+ 0x30ba, 0x0000,
+ /* 0x30bb 0x3099 */
+ 0x30bc, 0x0000,
+ /* 0x30bd 0x3099 */
+ 0x30be, 0x0000,
+ /* 0x30bf 0x3099 */
+ 0x30c0, 0x0000,
+ /* 0x30c1 0x3099 */
+ 0x30c2, 0x0000,
+ /* 0x30c4 0x3099 */
+ 0x30c5, 0x0000,
+ /* 0x30c6 0x3099 */
+ 0x30c7, 0x0000,
+ /* 0x30c8 0x3099 */
+ 0x30c9, 0x0000,
+ /* 0x30cf 0x3099 */
+ 0x30d0, 0x0000,
+ /* 0x30d2 0x3099 */
+ 0x30d3, 0x0000,
+ /* 0x30d5 0x3099 */
+ 0x30d6, 0x0000,
+ /* 0x30d8 0x3099 */
+ 0x30d9, 0x0000,
+ /* 0x30db 0x3099 */
+ 0x30dc, 0x0000,
+ /* 0x30ef 0x3099 */
+ 0x30f7, 0x0000,
+ /* 0x30f0 0x3099 */
+ 0x30f8, 0x0000,
+ /* 0x30f1 0x3099 */
+ 0x30f9, 0x0000,
+ /* 0x30f2 0x3099 */
+ 0x30fa, 0x0000,
+ /* 0x30fd 0x3099 */
+ 0x30fe, 0x0000,
+ /* 0x306f 0x309a */
+ 0x3071, 0x0000,
+ /* 0x3072 0x309a */
+ 0x3074, 0x0000,
+ /* 0x3075 0x309a */
+ 0x3077, 0x0000,
+ /* 0x3078 0x309a */
+ 0x307a, 0x0000,
+ /* 0x307b 0x309a */
+ 0x307d, 0x0000,
+ /* 0x30cf 0x309a */
+ 0x30d1, 0x0000,
+ /* 0x30d2 0x309a */
+ 0x30d4, 0x0000,
+ /* 0x30d5 0x309a */
+ 0x30d7, 0x0000,
+ /* 0x30d8 0x309a */
+ 0x30da, 0x0000,
+ /* 0x30db 0x309a */
+ 0x30dd, 0x0000,
+ /* 0x0307 0x0053 0x0301 */
+ 0x1e64, 0x0000,
+ /* 0x0307 0x0073 0x0301 */
+ 0x1e65, 0x0000,
+ /* 0x0300 0x0041 0x0302 */
+ 0x1ea6, 0x0000,
+ /* 0x0301 0x0041 0x0302 */
+ 0x1ea4, 0x0000,
+ /* 0x0303 0x0041 0x0302 */
+ 0x1eaa, 0x0000,
+ /* 0x0309 0x0041 0x0302 */
+ 0x1ea8, 0x0000,
+ /* 0x0300 0x0045 0x0302 */
+ 0x1ec0, 0x0000,
+ /* 0x0301 0x0045 0x0302 */
+ 0x1ebe, 0x0000,
+ /* 0x0303 0x0045 0x0302 */
+ 0x1ec4, 0x0000,
+ /* 0x0309 0x0045 0x0302 */
+ 0x1ec2, 0x0000,
+ /* 0x0300 0x004f 0x0302 */
+ 0x1ed2, 0x0000,
+ /* 0x0301 0x004f 0x0302 */
+ 0x1ed0, 0x0000,
+ /* 0x0303 0x004f 0x0302 */
+ 0x1ed6, 0x0000,
+ /* 0x0309 0x004f 0x0302 */
+ 0x1ed4, 0x0000,
+ /* 0x0300 0x0061 0x0302 */
+ 0x1ea7, 0x0000,
+ /* 0x0301 0x0061 0x0302 */
+ 0x1ea5, 0x0000,
+ /* 0x0303 0x0061 0x0302 */
+ 0x1eab, 0x0000,
+ /* 0x0309 0x0061 0x0302 */
+ 0x1ea9, 0x0000,
+ /* 0x0300 0x0065 0x0302 */
+ 0x1ec1, 0x0000,
+ /* 0x0301 0x0065 0x0302 */
+ 0x1ebf, 0x0000,
+ /* 0x0303 0x0065 0x0302 */
+ 0x1ec5, 0x0000,
+ /* 0x0309 0x0065 0x0302 */
+ 0x1ec3, 0x0000,
+ /* 0x0300 0x006f 0x0302 */
+ 0x1ed3, 0x0000,
+ /* 0x0301 0x006f 0x0302 */
+ 0x1ed1, 0x0000,
+ /* 0x0303 0x006f 0x0302 */
+ 0x1ed7, 0x0000,
+ /* 0x0309 0x006f 0x0302 */
+ 0x1ed5, 0x0000,
+ /* 0x0301 0x004f 0x0303 */
+ 0x1e4c, 0x0000,
+ /* 0x0308 0x004f 0x0303 */
+ 0x1e4e, 0x0000,
+ /* 0x0301 0x0055 0x0303 */
+ 0x1e78, 0x0000,
+ /* 0x0301 0x006f 0x0303 */
+ 0x1e4d, 0x0000,
+ /* 0x0308 0x006f 0x0303 */
+ 0x1e4f, 0x0000,
+ /* 0x0301 0x0075 0x0303 */
+ 0x1e79, 0x0000,
+ /* 0x0300 0x0045 0x0304 */
+ 0x1e14, 0x0000,
+ /* 0x0301 0x0045 0x0304 */
+ 0x1e16, 0x0000,
+ /* 0x0300 0x004f 0x0304 */
+ 0x1e50, 0x0000,
+ /* 0x0301 0x004f 0x0304 */
+ 0x1e52, 0x0000,
+ /* 0x0308 0x0055 0x0304 */
+ 0x1e7a, 0x0000,
+ /* 0x0300 0x0065 0x0304 */
+ 0x1e15, 0x0000,
+ /* 0x0301 0x0065 0x0304 */
+ 0x1e17, 0x0000,
+ /* 0x0300 0x006f 0x0304 */
+ 0x1e51, 0x0000,
+ /* 0x0301 0x006f 0x0304 */
+ 0x1e53, 0x0000,
+ /* 0x0308 0x0075 0x0304 */
+ 0x1e7b, 0x0000,
+ /* 0x0300 0x0041 0x0306 */
+ 0x1eb0, 0x0000,
+ /* 0x0301 0x0041 0x0306 */
+ 0x1eae, 0x0000,
+ /* 0x0303 0x0041 0x0306 */
+ 0x1eb4, 0x0000,
+ /* 0x0309 0x0041 0x0306 */
+ 0x1eb2, 0x0000,
+ /* 0x0300 0x0061 0x0306 */
+ 0x1eb1, 0x0000,
+ /* 0x0301 0x0061 0x0306 */
+ 0x1eaf, 0x0000,
+ /* 0x0303 0x0061 0x0306 */
+ 0x1eb5, 0x0000,
+ /* 0x0309 0x0061 0x0306 */
+ 0x1eb3, 0x0000,
+ /* 0x0304 0x0041 0x0307 */
+ 0x01e0, 0x0000,
+ /* 0x0304 0x0061 0x0307 */
+ 0x01e1, 0x0000,
+ /* 0x0304 0x0041 0x0308 */
+ 0x01de, 0x0000,
+ /* 0x0301 0x0049 0x0308 */
+ 0x1e2e, 0x0000,
+ /* 0x0300 0x0055 0x0308 */
+ 0x01db, 0x0000,
+ /* 0x0301 0x0055 0x0308 */
+ 0x01d7, 0x0000,
+ /* 0x0304 0x0055 0x0308 */
+ 0x01d5, 0x0000,
+ /* 0x030c 0x0055 0x0308 */
+ 0x01d9, 0x0000,
+ /* 0x0304 0x0061 0x0308 */
+ 0x01df, 0x0000,
+ /* 0x0301 0x0069 0x0308 */
+ 0x1e2f, 0x0000,
+ /* 0x0300 0x0075 0x0308 */
+ 0x01dc, 0x0000,
+ /* 0x0301 0x0075 0x0308 */
+ 0x01d8, 0x0000,
+ /* 0x0304 0x0075 0x0308 */
+ 0x01d6, 0x0000,
+ /* 0x030c 0x0075 0x0308 */
+ 0x01da, 0x0000,
+ /* 0x0300 0x03b9 0x0308 */
+ 0x1fd2, 0x0000,
+ /* 0x0301 0x03b9 0x0308 */
+ 0x1fd3, 0x0000,
+ /* 0x030d 0x03b9 0x0308 */
+ 0x0390, 0x0000,
+ /* 0x0342 0x03b9 0x0308 */
+ 0x1fd7, 0x0000,
+ /* 0x0300 0x03c5 0x0308 */
+ 0x1fe2, 0x0000,
+ /* 0x0301 0x03c5 0x0308 */
+ 0x1fe3, 0x0000,
+ /* 0x030d 0x03c5 0x0308 */
+ 0x03b0, 0x0000,
+ /* 0x0342 0x03c5 0x0308 */
+ 0x1fe7, 0x0000,
+ /* 0x0301 0x0041 0x030a */
+ 0x01fa, 0x0000,
+ /* 0x0301 0x0061 0x030a */
+ 0x01fb, 0x0000,
+ /* 0x0307 0x0053 0x030c */
+ 0x1e66, 0x0000,
+ /* 0x0307 0x0073 0x030c */
+ 0x1e67, 0x0000,
+ /* 0x0300 0x0391 0x0313 */
+ 0x1f0a, 0x0000,
+ /* 0x0301 0x0391 0x0313 */
+ 0x1f0c, 0x0000,
+ /* 0x0342 0x0391 0x0313 */
+ 0x1f0e, 0x0000,
+ /* 0x0300 0x0395 0x0313 */
+ 0x1f1a, 0x0000,
+ /* 0x0301 0x0395 0x0313 */
+ 0x1f1c, 0x0000,
+ /* 0x0300 0x0397 0x0313 */
+ 0x1f2a, 0x0000,
+ /* 0x0301 0x0397 0x0313 */
+ 0x1f2c, 0x0000,
+ /* 0x0342 0x0397 0x0313 */
+ 0x1f2e, 0x0000,
+ /* 0x0300 0x0399 0x0313 */
+ 0x1f3a, 0x0000,
+ /* 0x0301 0x0399 0x0313 */
+ 0x1f3c, 0x0000,
+ /* 0x0342 0x0399 0x0313 */
+ 0x1f3e, 0x0000,
+ /* 0x0300 0x039f 0x0313 */
+ 0x1f4a, 0x0000,
+ /* 0x0301 0x039f 0x0313 */
+ 0x1f4c, 0x0000,
+ /* 0x0300 0x03a9 0x0313 */
+ 0x1f6a, 0x0000,
+ /* 0x0301 0x03a9 0x0313 */
+ 0x1f6c, 0x0000,
+ /* 0x0342 0x03a9 0x0313 */
+ 0x1f6e, 0x0000,
+ /* 0x0300 0x03b1 0x0313 */
+ 0x1f02, 0x0000,
+ /* 0x0301 0x03b1 0x0313 */
+ 0x1f04, 0x0000,
+ /* 0x0342 0x03b1 0x0313 */
+ 0x1f06, 0x0000,
+ /* 0x0300 0x03b5 0x0313 */
+ 0x1f12, 0x0000,
+ /* 0x0301 0x03b5 0x0313 */
+ 0x1f14, 0x0000,
+ /* 0x0300 0x03b7 0x0313 */
+ 0x1f22, 0x0000,
+ /* 0x0301 0x03b7 0x0313 */
+ 0x1f24, 0x0000,
+ /* 0x0342 0x03b7 0x0313 */
+ 0x1f26, 0x0000,
+ /* 0x0300 0x03b9 0x0313 */
+ 0x1f32, 0x0000,
+ /* 0x0301 0x03b9 0x0313 */
+ 0x1f34, 0x0000,
+ /* 0x0342 0x03b9 0x0313 */
+ 0x1f36, 0x0000,
+ /* 0x0300 0x03bf 0x0313 */
+ 0x1f42, 0x0000,
+ /* 0x0301 0x03bf 0x0313 */
+ 0x1f44, 0x0000,
+ /* 0x0300 0x03c5 0x0313 */
+ 0x1f52, 0x0000,
+ /* 0x0301 0x03c5 0x0313 */
+ 0x1f54, 0x0000,
+ /* 0x0342 0x03c5 0x0313 */
+ 0x1f56, 0x0000,
+ /* 0x0300 0x03c9 0x0313 */
+ 0x1f62, 0x0000,
+ /* 0x0301 0x03c9 0x0313 */
+ 0x1f64, 0x0000,
+ /* 0x0342 0x03c9 0x0313 */
+ 0x1f66, 0x0000,
+ /* 0x0300 0x0391 0x0314 */
+ 0x1f0b, 0x0000,
+ /* 0x0301 0x0391 0x0314 */
+ 0x1f0d, 0x0000,
+ /* 0x0342 0x0391 0x0314 */
+ 0x1f0f, 0x0000,
+ /* 0x0300 0x0395 0x0314 */
+ 0x1f1b, 0x0000,
+ /* 0x0301 0x0395 0x0314 */
+ 0x1f1d, 0x0000,
+ /* 0x0300 0x0397 0x0314 */
+ 0x1f2b, 0x0000,
+ /* 0x0301 0x0397 0x0314 */
+ 0x1f2d, 0x0000,
+ /* 0x0342 0x0397 0x0314 */
+ 0x1f2f, 0x0000,
+ /* 0x0300 0x0399 0x0314 */
+ 0x1f3b, 0x0000,
+ /* 0x0301 0x0399 0x0314 */
+ 0x1f3d, 0x0000,
+ /* 0x0342 0x0399 0x0314 */
+ 0x1f3f, 0x0000,
+ /* 0x0300 0x039f 0x0314 */
+ 0x1f4b, 0x0000,
+ /* 0x0301 0x039f 0x0314 */
+ 0x1f4d, 0x0000,
+ /* 0x0300 0x03a5 0x0314 */
+ 0x1f5b, 0x0000,
+ /* 0x0301 0x03a5 0x0314 */
+ 0x1f5d, 0x0000,
+ /* 0x0342 0x03a5 0x0314 */
+ 0x1f5f, 0x0000,
+ /* 0x0300 0x03a9 0x0314 */
+ 0x1f6b, 0x0000,
+ /* 0x0301 0x03a9 0x0314 */
+ 0x1f6d, 0x0000,
+ /* 0x0342 0x03a9 0x0314 */
+ 0x1f6f, 0x0000,
+ /* 0x0300 0x03b1 0x0314 */
+ 0x1f03, 0x0000,
+ /* 0x0301 0x03b1 0x0314 */
+ 0x1f05, 0x0000,
+ /* 0x0342 0x03b1 0x0314 */
+ 0x1f07, 0x0000,
+ /* 0x0300 0x03b5 0x0314 */
+ 0x1f13, 0x0000,
+ /* 0x0301 0x03b5 0x0314 */
+ 0x1f15, 0x0000,
+ /* 0x0300 0x03b7 0x0314 */
+ 0x1f23, 0x0000,
+ /* 0x0301 0x03b7 0x0314 */
+ 0x1f25, 0x0000,
+ /* 0x0342 0x03b7 0x0314 */
+ 0x1f27, 0x0000,
+ /* 0x0300 0x03b9 0x0314 */
+ 0x1f33, 0x0000,
+ /* 0x0301 0x03b9 0x0314 */
+ 0x1f35, 0x0000,
+ /* 0x0342 0x03b9 0x0314 */
+ 0x1f37, 0x0000,
+ /* 0x0300 0x03bf 0x0314 */
+ 0x1f43, 0x0000,
+ /* 0x0301 0x03bf 0x0314 */
+ 0x1f45, 0x0000,
+ /* 0x0300 0x03c5 0x0314 */
+ 0x1f53, 0x0000,
+ /* 0x0301 0x03c5 0x0314 */
+ 0x1f55, 0x0000,
+ /* 0x0342 0x03c5 0x0314 */
+ 0x1f57, 0x0000,
+ /* 0x0300 0x03c9 0x0314 */
+ 0x1f63, 0x0000,
+ /* 0x0301 0x03c9 0x0314 */
+ 0x1f65, 0x0000,
+ /* 0x0342 0x03c9 0x0314 */
+ 0x1f67, 0x0000,
+ /* 0x0300 0x004f 0x031b */
+ 0x1edc, 0x0000,
+ /* 0x0301 0x004f 0x031b */
+ 0x1eda, 0x0000,
+ /* 0x0303 0x004f 0x031b */
+ 0x1ee0, 0x0000,
+ /* 0x0309 0x004f 0x031b */
+ 0x1ede, 0x0000,
+ /* 0x0323 0x004f 0x031b */
+ 0x1ee2, 0x0000,
+ /* 0x0300 0x0055 0x031b */
+ 0x1eea, 0x0000,
+ /* 0x0301 0x0055 0x031b */
+ 0x1ee8, 0x0000,
+ /* 0x0303 0x0055 0x031b */
+ 0x1eee, 0x0000,
+ /* 0x0309 0x0055 0x031b */
+ 0x1eec, 0x0000,
+ /* 0x0323 0x0055 0x031b */
+ 0x1ef0, 0x0000,
+ /* 0x0300 0x006f 0x031b */
+ 0x1edd, 0x0000,
+ /* 0x0301 0x006f 0x031b */
+ 0x1edb, 0x0000,
+ /* 0x0303 0x006f 0x031b */
+ 0x1ee1, 0x0000,
+ /* 0x0309 0x006f 0x031b */
+ 0x1edf, 0x0000,
+ /* 0x0323 0x006f 0x031b */
+ 0x1ee3, 0x0000,
+ /* 0x0300 0x0075 0x031b */
+ 0x1eeb, 0x0000,
+ /* 0x0301 0x0075 0x031b */
+ 0x1ee9, 0x0000,
+ /* 0x0303 0x0075 0x031b */
+ 0x1eef, 0x0000,
+ /* 0x0309 0x0075 0x031b */
+ 0x1eed, 0x0000,
+ /* 0x0323 0x0075 0x031b */
+ 0x1ef1, 0x0000,
+ /* 0x0302 0x0041 0x0323 */
+ 0x1eac, 0x0000,
+ /* 0x0306 0x0041 0x0323 */
+ 0x1eb6, 0x0000,
+ /* 0x0302 0x0045 0x0323 */
+ 0x1ec6, 0x0000,
+ /* 0x0304 0x004c 0x0323 */
+ 0x1e38, 0x0000,
+ /* 0x0302 0x004f 0x0323 */
+ 0x1ed8, 0x0000,
+ /* 0x0304 0x0052 0x0323 */
+ 0x1e5c, 0x0000,
+ /* 0x0307 0x0053 0x0323 */
+ 0x1e68, 0x0000,
+ /* 0x0302 0x0061 0x0323 */
+ 0x1ead, 0x0000,
+ /* 0x0306 0x0061 0x0323 */
+ 0x1eb7, 0x0000,
+ /* 0x0302 0x0065 0x0323 */
+ 0x1ec7, 0x0000,
+ /* 0x0304 0x006c 0x0323 */
+ 0x1e39, 0x0000,
+ /* 0x0302 0x006f 0x0323 */
+ 0x1ed9, 0x0000,
+ /* 0x0304 0x0072 0x0323 */
+ 0x1e5d, 0x0000,
+ /* 0x0307 0x0073 0x0323 */
+ 0x1e69, 0x0000,
+ /* 0x0301 0x0043 0x0327 */
+ 0x1e08, 0x0000,
+ /* 0x0306 0x0045 0x0327 */
+ 0x1e1c, 0x0000,
+ /* 0x0301 0x0063 0x0327 */
+ 0x1e09, 0x0000,
+ /* 0x0306 0x0065 0x0327 */
+ 0x1e1d, 0x0000,
+ /* 0x0304 0x004f 0x0328 */
+ 0x01ec, 0x0000,
+ /* 0x0304 0x006f 0x0328 */
+ 0x01ed, 0x0000,
+ /* 0x0313 0x0391 0x0345 */
+ 0x1f88, 0x0003, 0x0300, 0x0f5e, 0x0301, 0x0f60, 0x0342, 0x0f62,
+ /* 0x0314 0x0391 0x0345 */
+ 0x1f89, 0x0003, 0x0300, 0x0f64, 0x0301, 0x0f66, 0x0342, 0x0f68,
+ /* 0x0313 0x0397 0x0345 */
+ 0x1f98, 0x0003, 0x0300, 0x0f6a, 0x0301, 0x0f6c, 0x0342, 0x0f6e,
+ /* 0x0314 0x0397 0x0345 */
+ 0x1f99, 0x0003, 0x0300, 0x0f70, 0x0301, 0x0f72, 0x0342, 0x0f74,
+ /* 0x0313 0x03a9 0x0345 */
+ 0x1fa8, 0x0003, 0x0300, 0x0f76, 0x0301, 0x0f78, 0x0342, 0x0f7a,
+ /* 0x0314 0x03a9 0x0345 */
+ 0x1fa9, 0x0003, 0x0300, 0x0f7c, 0x0301, 0x0f7e, 0x0342, 0x0f80,
+ /* 0x0300 0x03b1 0x0345 */
+ 0x1fb2, 0x0000,
+ /* 0x0301 0x03b1 0x0345 */
+ 0x1fb4, 0x0000,
+ /* 0x0313 0x03b1 0x0345 */
+ 0x1f80, 0x0003, 0x0300, 0x0f82, 0x0301, 0x0f84, 0x0342, 0x0f86,
+ /* 0x0314 0x03b1 0x0345 */
+ 0x1f81, 0x0003, 0x0300, 0x0f88, 0x0301, 0x0f8a, 0x0342, 0x0f8c,
+ /* 0x0342 0x03b1 0x0345 */
+ 0x1fb7, 0x0000,
+ /* 0x0300 0x03b7 0x0345 */
+ 0x1fc2, 0x0000,
+ /* 0x0301 0x03b7 0x0345 */
+ 0x1fc4, 0x0000,
+ /* 0x0313 0x03b7 0x0345 */
+ 0x1f90, 0x0003, 0x0300, 0x0f8e, 0x0301, 0x0f90, 0x0342, 0x0f92,
+ /* 0x0314 0x03b7 0x0345 */
+ 0x1f91, 0x0003, 0x0300, 0x0f94, 0x0301, 0x0f96, 0x0342, 0x0f98,
+ /* 0x0342 0x03b7 0x0345 */
+ 0x1fc7, 0x0000,
+ /* 0x0301 0x03bf 0x0345 */
+ 0x1ff4, 0x0000,
+ /* 0x0300 0x03c9 0x0345 */
+ 0x1ff2, 0x0000,
+ /* 0x0313 0x03c9 0x0345 */
+ 0x1fa0, 0x0003, 0x0300, 0x0f9a, 0x0301, 0x0f9c, 0x0342, 0x0f9e,
+ /* 0x0314 0x03c9 0x0345 */
+ 0x1fa1, 0x0003, 0x0300, 0x0fa0, 0x0301, 0x0fa2, 0x0342, 0x0fa4,
+ /* 0x0342 0x03c9 0x0345 */
+ 0x1ff7, 0x0000,
+ /* 0x05c1 0x05e9 0x05bc */
+ 0xfb2c, 0x0000,
+ /* 0x05c2 0x05e9 0x05bc */
+ 0xfb2d, 0x0000,
+ /* 0x0cd5 0x0cc6 0x0cc2 */
+ 0x0ccb, 0x0000,
+ /* 0x0f71 0x0fb2 0x0f80 */
+ 0x0f77, 0x0000,
+ /* 0x0f71 0x0fb3 0x0f80 */
+ 0x0f79, 0x0000,
+ /* 0x0300 0x0313 0x0391 0x0345 */
+ 0x1f8a, 0x0000,
+ /* 0x0301 0x0313 0x0391 0x0345 */
+ 0x1f8c, 0x0000,
+ /* 0x0342 0x0313 0x0391 0x0345 */
+ 0x1f8e, 0x0000,
+ /* 0x0300 0x0314 0x0391 0x0345 */
+ 0x1f8b, 0x0000,
+ /* 0x0301 0x0314 0x0391 0x0345 */
+ 0x1f8d, 0x0000,
+ /* 0x0342 0x0314 0x0391 0x0345 */
+ 0x1f8f, 0x0000,
+ /* 0x0300 0x0313 0x0397 0x0345 */
+ 0x1f9a, 0x0000,
+ /* 0x0301 0x0313 0x0397 0x0345 */
+ 0x1f9c, 0x0000,
+ /* 0x0342 0x0313 0x0397 0x0345 */
+ 0x1f9e, 0x0000,
+ /* 0x0300 0x0314 0x0397 0x0345 */
+ 0x1f9b, 0x0000,
+ /* 0x0301 0x0314 0x0397 0x0345 */
+ 0x1f9d, 0x0000,
+ /* 0x0342 0x0314 0x0397 0x0345 */
+ 0x1f9f, 0x0000,
+ /* 0x0300 0x0313 0x03a9 0x0345 */
+ 0x1faa, 0x0000,
+ /* 0x0301 0x0313 0x03a9 0x0345 */
+ 0x1fac, 0x0000,
+ /* 0x0342 0x0313 0x03a9 0x0345 */
+ 0x1fae, 0x0000,
+ /* 0x0300 0x0314 0x03a9 0x0345 */
+ 0x1fab, 0x0000,
+ /* 0x0301 0x0314 0x03a9 0x0345 */
+ 0x1fad, 0x0000,
+ /* 0x0342 0x0314 0x03a9 0x0345 */
+ 0x1faf, 0x0000,
+ /* 0x0300 0x0313 0x03b1 0x0345 */
+ 0x1f82, 0x0000,
+ /* 0x0301 0x0313 0x03b1 0x0345 */
+ 0x1f84, 0x0000,
+ /* 0x0342 0x0313 0x03b1 0x0345 */
+ 0x1f86, 0x0000,
+ /* 0x0300 0x0314 0x03b1 0x0345 */
+ 0x1f83, 0x0000,
+ /* 0x0301 0x0314 0x03b1 0x0345 */
+ 0x1f85, 0x0000,
+ /* 0x0342 0x0314 0x03b1 0x0345 */
+ 0x1f87, 0x0000,
+ /* 0x0300 0x0313 0x03b7 0x0345 */
+ 0x1f92, 0x0000,
+ /* 0x0301 0x0313 0x03b7 0x0345 */
+ 0x1f94, 0x0000,
+ /* 0x0342 0x0313 0x03b7 0x0345 */
+ 0x1f96, 0x0000,
+ /* 0x0300 0x0314 0x03b7 0x0345 */
+ 0x1f93, 0x0000,
+ /* 0x0301 0x0314 0x03b7 0x0345 */
+ 0x1f95, 0x0000,
+ /* 0x0342 0x0314 0x03b7 0x0345 */
+ 0x1f97, 0x0000,
+ /* 0x0300 0x0313 0x03c9 0x0345 */
+ 0x1fa2, 0x0000,
+ /* 0x0301 0x0313 0x03c9 0x0345 */
+ 0x1fa4, 0x0000,
+ /* 0x0342 0x0313 0x03c9 0x0345 */
+ 0x1fa6, 0x0000,
+ /* 0x0300 0x0314 0x03c9 0x0345 */
+ 0x1fa3, 0x0000,
+ /* 0x0301 0x0314 0x03c9 0x0345 */
+ 0x1fa5, 0x0000,
+ /* 0x0342 0x0314 0x03c9 0x0345 */
+ 0x1fa7, 0x0000,
+};
diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c
new file mode 100644
index 000000000..008fec186
--- /dev/null
+++ b/fs/hfsplus/unicode.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/unicode.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handler routines for unicode strings
+ */
+
+#include <linux/types.h>
+#include <linux/nls.h>
+
+#include <kunit/visibility.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+/* Fold the case of a unicode char, given the 16 bit value */
+/* Returns folded char, or 0 if ignorable */
+static inline u16 case_fold(u16 c)
+{
+ u16 tmp;
+
+ tmp = hfsplus_case_fold_table[c >> 8];
+ if (tmp)
+ tmp = hfsplus_case_fold_table[tmp + (c & 0xff)];
+ else
+ tmp = c;
+ return tmp;
+}
+
+/* Compare unicode strings, return values like normal strcmp */
+int hfsplus_strcasecmp(const struct hfsplus_unistr *s1,
+ const struct hfsplus_unistr *s2)
+{
+ u16 len1, len2, c1, c2;
+ const hfsplus_unichr *p1, *p2;
+
+ len1 = be16_to_cpu(s1->length);
+ len2 = be16_to_cpu(s2->length);
+ p1 = s1->unicode;
+ p2 = s2->unicode;
+
+ if (len1 > HFSPLUS_MAX_STRLEN) {
+ len1 = HFSPLUS_MAX_STRLEN;
+ pr_err("invalid length %u has been corrected to %d\n",
+ be16_to_cpu(s1->length), len1);
+ }
+
+ if (len2 > HFSPLUS_MAX_STRLEN) {
+ len2 = HFSPLUS_MAX_STRLEN;
+ pr_err("invalid length %u has been corrected to %d\n",
+ be16_to_cpu(s2->length), len2);
+ }
+
+ while (1) {
+ c1 = c2 = 0;
+
+ while (len1 && !c1) {
+ c1 = case_fold(be16_to_cpu(*p1));
+ p1++;
+ len1--;
+ }
+ while (len2 && !c2) {
+ c2 = case_fold(be16_to_cpu(*p2));
+ p2++;
+ len2--;
+ }
+
+ if (c1 != c2)
+ return (c1 < c2) ? -1 : 1;
+ if (!c1 && !c2)
+ return 0;
+ }
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_strcasecmp);
+
+/* Compare names as a sequence of 16-bit unsigned integers */
+int hfsplus_strcmp(const struct hfsplus_unistr *s1,
+ const struct hfsplus_unistr *s2)
+{
+ u16 len1, len2, c1, c2;
+ const hfsplus_unichr *p1, *p2;
+ int len;
+
+ len1 = be16_to_cpu(s1->length);
+ len2 = be16_to_cpu(s2->length);
+ p1 = s1->unicode;
+ p2 = s2->unicode;
+
+ if (len1 > HFSPLUS_MAX_STRLEN) {
+ len1 = HFSPLUS_MAX_STRLEN;
+ pr_err("invalid length %u has been corrected to %d\n",
+ be16_to_cpu(s1->length), len1);
+ }
+
+ if (len2 > HFSPLUS_MAX_STRLEN) {
+ len2 = HFSPLUS_MAX_STRLEN;
+ pr_err("invalid length %u has been corrected to %d\n",
+ be16_to_cpu(s2->length), len2);
+ }
+
+ for (len = min(len1, len2); len > 0; len--) {
+ c1 = be16_to_cpu(*p1);
+ c2 = be16_to_cpu(*p2);
+ if (c1 != c2)
+ return c1 < c2 ? -1 : 1;
+ p1++;
+ p2++;
+ }
+
+ return len1 < len2 ? -1 :
+ len1 > len2 ? 1 : 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_strcmp);
+
+#define Hangul_SBase 0xac00
+#define Hangul_LBase 0x1100
+#define Hangul_VBase 0x1161
+#define Hangul_TBase 0x11a7
+#define Hangul_SCount 11172
+#define Hangul_LCount 19
+#define Hangul_VCount 21
+#define Hangul_TCount 28
+#define Hangul_NCount (Hangul_VCount * Hangul_TCount)
+
+
+static u16 *hfsplus_compose_lookup(u16 *p, u16 cc)
+{
+ int i, s, e;
+
+ s = 1;
+ e = p[1];
+ if (!e || cc < p[s * 2] || cc > p[e * 2])
+ return NULL;
+ do {
+ i = (s + e) / 2;
+ if (cc > p[i * 2])
+ s = i + 1;
+ else if (cc < p[i * 2])
+ e = i - 1;
+ else
+ return hfsplus_compose_table + p[i * 2 + 1];
+ } while (s <= e);
+ 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 name_type)
+{
+ const hfsplus_unichr *ip;
+ struct nls_table *nls = HFSPLUS_SB(sb)->nls;
+ u8 *op;
+ u16 cc, c0, c1;
+ u16 *ce1, *ce2;
+ int i, len, ustrlen, res, compose;
+
+ op = astr;
+ ip = ustr->unicode;
+
+ ustrlen = be16_to_cpu(ustr->length);
+ if (ustrlen > max_len) {
+ ustrlen = max_len;
+ pr_err("invalid length %u has been corrected to %d\n",
+ be16_to_cpu(ustr->length), ustrlen);
+ }
+
+ len = *len_p;
+ ce1 = NULL;
+ compose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
+
+ while (ustrlen > 0) {
+ c0 = be16_to_cpu(*ip++);
+ ustrlen--;
+ /* search for single decomposed char */
+ if (likely(compose))
+ ce1 = hfsplus_compose_lookup(hfsplus_compose_table, c0);
+ if (ce1)
+ cc = ce1[0];
+ else
+ cc = 0;
+ if (cc) {
+ /* start of a possibly decomposed Hangul char */
+ if (cc != 0xffff)
+ goto done;
+ if (!ustrlen)
+ goto same;
+ c1 = be16_to_cpu(*ip) - Hangul_VBase;
+ if (c1 < Hangul_VCount) {
+ /* compose the Hangul char */
+ cc = (c0 - Hangul_LBase) * Hangul_VCount;
+ cc = (cc + c1) * Hangul_TCount;
+ cc += Hangul_SBase;
+ ip++;
+ ustrlen--;
+ if (!ustrlen)
+ goto done;
+ c1 = be16_to_cpu(*ip) - Hangul_TBase;
+ if (c1 > 0 && c1 < Hangul_TCount) {
+ cc += c1;
+ ip++;
+ ustrlen--;
+ }
+ goto done;
+ }
+ }
+ while (1) {
+ /* main loop for common case of not composed chars */
+ if (!ustrlen)
+ goto same;
+ c1 = be16_to_cpu(*ip);
+ if (likely(compose))
+ ce1 = hfsplus_compose_lookup(
+ hfsplus_compose_table, c1);
+ if (ce1)
+ break;
+ hfsplus_mac2linux_compatibility_check(c0, &c0,
+ name_type);
+ res = nls->uni2char(c0, op, len);
+ if (res < 0) {
+ if (res == -ENAMETOOLONG)
+ goto out;
+ *op = '?';
+ res = 1;
+ }
+ op += res;
+ len -= res;
+ c0 = c1;
+ ip++;
+ ustrlen--;
+ }
+ ce2 = hfsplus_compose_lookup(ce1, c0);
+ if (ce2) {
+ i = 1;
+ while (i < ustrlen) {
+ ce1 = hfsplus_compose_lookup(ce2,
+ be16_to_cpu(ip[i]));
+ if (!ce1)
+ break;
+ i++;
+ ce2 = ce1;
+ }
+ cc = ce2[0];
+ if (cc) {
+ ip += i;
+ ustrlen -= i;
+ goto done;
+ }
+ }
+same:
+ hfsplus_mac2linux_compatibility_check(c0, &cc,
+ name_type);
+done:
+ res = nls->uni2char(cc, op, len);
+ if (res < 0) {
+ if (res == -ENAMETOOLONG)
+ goto out;
+ *op = '?';
+ res = 1;
+ }
+ op += res;
+ len -= res;
+ }
+ res = 0;
+out:
+ *len_p = (char *)op - astr;
+ return res;
+}
+
+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,
+ HFS_REGULAR_NAME);
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_str);
+
+inline int hfsplus_uni2asc_xattr_str(struct super_block *sb,
+ const struct hfsplus_attr_unistr *ustr,
+ char *astr, int *len_p)
+{
+ return hfsplus_uni2asc(sb, (const struct hfsplus_unistr *)ustr,
+ HFSPLUS_ATTR_MAX_STRLEN, astr, len_p,
+ HFS_XATTR_NAME);
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_uni2asc_xattr_str);
+
+/*
+ * 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_linux2mac_compatibility_check(wchar_t *uc, int name_type)
+{
+ switch (name_type) {
+ case HFS_XATTR_NAME:
+ /* ignore conversion */
+ return;
+
+ default:
+ /* continue logic */
+ break;
+ }
+
+ switch (*uc) {
+ case 0x2400:
+ *uc = 0;
+ break;
+ case ':':
+ *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;
+}
+
+/* Decomposes a non-Hangul unicode character. */
+static u16 *hfsplus_decompose_nonhangul(wchar_t uc, int *size)
+{
+ int off;
+
+ off = hfsplus_decompose_table[(uc >> 12) & 0xf];
+ if (off == 0 || off == 0xffff)
+ return NULL;
+
+ off = hfsplus_decompose_table[off + ((uc >> 8) & 0xf)];
+ if (!off)
+ return NULL;
+
+ off = hfsplus_decompose_table[off + ((uc >> 4) & 0xf)];
+ if (!off)
+ return NULL;
+
+ off = hfsplus_decompose_table[off + (uc & 0xf)];
+ *size = off & 3;
+ if (*size == 0)
+ return NULL;
+ return hfsplus_decompose_table + (off / 4);
+}
+
+/*
+ * Try to decompose a unicode character as Hangul. Return 0 if @uc is not
+ * precomposed Hangul, otherwise return the length of the decomposition.
+ *
+ * This function was adapted from sample code from the Unicode Standard
+ * Annex #15: Unicode Normalization Forms, version 3.2.0.
+ *
+ * Copyright (C) 1991-2018 Unicode, Inc. All rights reserved. Distributed
+ * under the Terms of Use in http://www.unicode.org/copyright.html.
+ */
+static int hfsplus_try_decompose_hangul(wchar_t uc, u16 *result)
+{
+ int index;
+ int l, v, t;
+
+ index = uc - Hangul_SBase;
+ if (index < 0 || index >= Hangul_SCount)
+ return 0;
+
+ l = Hangul_LBase + index / Hangul_NCount;
+ v = Hangul_VBase + (index % Hangul_NCount) / Hangul_TCount;
+ t = Hangul_TBase + index % Hangul_TCount;
+
+ result[0] = l;
+ result[1] = v;
+ if (t != Hangul_TBase) {
+ result[2] = t;
+ return 3;
+ }
+ return 2;
+}
+
+/* Decomposes a single unicode character. */
+static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer)
+{
+ u16 *result;
+
+ /* Hangul is handled separately */
+ result = hangul_buffer;
+ *size = hfsplus_try_decompose_hangul(uc, result);
+ if (*size == 0)
+ result = hfsplus_decompose_nonhangul(uc, size);
+ return result;
+}
+
+int hfsplus_asc2uni(struct super_block *sb,
+ struct hfsplus_unistr *ustr, int max_unistr_len,
+ const char *astr, int len, int name_type)
+{
+ int size, dsize, decompose;
+ u16 *dstr, outlen = 0;
+ wchar_t c;
+ u16 dhangul[3];
+
+ decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
+ while (outlen < max_unistr_len && len > 0) {
+ size = asc2unichar(sb, astr, len, &c, name_type);
+
+ if (decompose)
+ dstr = decompose_unichar(c, &dsize, dhangul);
+ else
+ dstr = NULL;
+ if (dstr) {
+ if (outlen + dsize > max_unistr_len)
+ break;
+ do {
+ ustr->unicode[outlen++] = cpu_to_be16(*dstr++);
+ } while (--dsize > 0);
+ } else
+ ustr->unicode[outlen++] = cpu_to_be16(c);
+
+ astr += size;
+ len -= size;
+ }
+ ustr->length = cpu_to_be16(outlen);
+ if (len > 0)
+ return -ENAMETOOLONG;
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni);
+
+/*
+ * Hash a string to an integer as appropriate for the HFS+ filesystem.
+ * Composed unicode characters are decomposed and case-folding is performed
+ * if the appropriate bits are (un)set on the superblock.
+ */
+int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str)
+{
+ struct super_block *sb = dentry->d_sb;
+ const char *astr;
+ const u16 *dstr;
+ int casefold, decompose, size, len;
+ unsigned long hash;
+ wchar_t c;
+ u16 c2;
+ u16 dhangul[3];
+
+ casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
+ decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
+ hash = init_name_hash(dentry);
+ astr = str->name;
+ len = str->len;
+ while (len > 0) {
+ int dsize;
+ size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME);
+ astr += size;
+ len -= size;
+
+ if (decompose)
+ dstr = decompose_unichar(c, &dsize, dhangul);
+ else
+ dstr = NULL;
+ if (dstr) {
+ do {
+ c2 = *dstr++;
+ if (casefold)
+ c2 = case_fold(c2);
+ if (!casefold || c2)
+ hash = partial_name_hash(c2, hash);
+ } while (--dsize > 0);
+ } else {
+ c2 = c;
+ if (casefold)
+ c2 = case_fold(c2);
+ if (!casefold || c2)
+ hash = partial_name_hash(c2, hash);
+ }
+ }
+ str->hash = end_name_hash(hash);
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_hash_dentry);
+
+/*
+ * Compare strings with HFS+ filename ordering.
+ * Composed unicode characters are decomposed and case-folding is performed
+ * if the appropriate bits are (un)set on the superblock.
+ */
+int hfsplus_compare_dentry(const struct dentry *dentry,
+ unsigned int len, const char *str, const struct qstr *name)
+{
+ struct super_block *sb = dentry->d_sb;
+ int casefold, decompose, size;
+ int dsize1, dsize2, len1, len2;
+ const u16 *dstr1, *dstr2;
+ const char *astr1, *astr2;
+ u16 c1, c2;
+ wchar_t c;
+ u16 dhangul_1[3], dhangul_2[3];
+
+ casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags);
+ decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags);
+ astr1 = str;
+ len1 = len;
+ astr2 = name->name;
+ len2 = name->len;
+ dsize1 = dsize2 = 0;
+ dstr1 = dstr2 = NULL;
+
+ while (len1 > 0 && len2 > 0) {
+ if (!dsize1) {
+ size = asc2unichar(sb, astr1, len1, &c,
+ HFS_REGULAR_NAME);
+ astr1 += size;
+ len1 -= size;
+
+ if (decompose)
+ dstr1 = decompose_unichar(c, &dsize1,
+ dhangul_1);
+ if (!decompose || !dstr1) {
+ c1 = c;
+ dstr1 = &c1;
+ dsize1 = 1;
+ }
+ }
+
+ if (!dsize2) {
+ size = asc2unichar(sb, astr2, len2, &c,
+ HFS_REGULAR_NAME);
+ astr2 += size;
+ len2 -= size;
+
+ if (decompose)
+ dstr2 = decompose_unichar(c, &dsize2,
+ dhangul_2);
+ if (!decompose || !dstr2) {
+ c2 = c;
+ dstr2 = &c2;
+ dsize2 = 1;
+ }
+ }
+
+ c1 = *dstr1;
+ c2 = *dstr2;
+ if (casefold) {
+ c1 = case_fold(c1);
+ if (!c1) {
+ dstr1++;
+ dsize1--;
+ continue;
+ }
+ c2 = case_fold(c2);
+ if (!c2) {
+ dstr2++;
+ dsize2--;
+ continue;
+ }
+ }
+ if (c1 < c2)
+ return -1;
+ else if (c1 > c2)
+ return 1;
+
+ dstr1++;
+ dsize1--;
+ dstr2++;
+ dsize2--;
+ }
+
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfsplus_compare_dentry);
diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c
new file mode 100644
index 000000000..7f6b2a3c6
--- /dev/null
+++ b/fs/hfsplus/unicode_test.c
@@ -0,0 +1,1595 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for HFS+ Unicode string operations
+ *
+ * Copyright (C) 2025 Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/nls.h>
+#include <linux/dcache.h>
+#include <linux/stringhash.h>
+#include "hfsplus_fs.h"
+
+struct test_mock_string_env {
+ struct hfsplus_unistr str1;
+ struct hfsplus_unistr str2;
+ char *buf;
+ u32 buf_size;
+};
+
+static struct test_mock_string_env *setup_mock_str_env(u32 buf_size)
+{
+ struct test_mock_string_env *env;
+
+ env = kzalloc_obj(struct test_mock_string_env);
+ if (!env)
+ return NULL;
+
+ env->buf = kzalloc(buf_size, GFP_KERNEL);
+ if (!env->buf) {
+ kfree(env);
+ return NULL;
+ }
+
+ env->buf_size = buf_size;
+
+ return env;
+}
+
+static void free_mock_str_env(struct test_mock_string_env *env)
+{
+ kfree(env->buf);
+ kfree(env);
+}
+
+/* Helper function to create hfsplus_unistr */
+static void create_unistr(struct hfsplus_unistr *ustr, const char *ascii_str)
+{
+ int len = strlen(ascii_str);
+ int i;
+
+ memset(ustr->unicode, 0, sizeof(ustr->unicode));
+
+ ustr->length = cpu_to_be16(len);
+ for (i = 0; i < len && i < HFSPLUS_MAX_STRLEN; i++)
+ ustr->unicode[i] = cpu_to_be16((u16)ascii_str[i]);
+}
+
+static void corrupt_unistr(struct hfsplus_unistr *ustr)
+{
+ ustr->length = cpu_to_be16(U16_MAX);
+}
+
+/* Test hfsplus_strcasecmp function */
+static void hfsplus_strcasecmp_test(struct kunit *test)
+{
+ struct test_mock_string_env *mock_env;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ /* Test identical strings */
+ create_unistr(&mock_env->str1, "hello");
+ create_unistr(&mock_env->str2, "hello");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test case insensitive comparison */
+ create_unistr(&mock_env->str1, "Hello");
+ create_unistr(&mock_env->str2, "hello");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "HELLO");
+ create_unistr(&mock_env->str2, "hello");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test different strings */
+ create_unistr(&mock_env->str1, "apple");
+ create_unistr(&mock_env->str2, "banana");
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "zebra");
+ create_unistr(&mock_env->str2, "apple");
+ KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test different lengths */
+ create_unistr(&mock_env->str1, "test");
+ create_unistr(&mock_env->str2, "testing");
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ create_unistr(&mock_env->str2, "test");
+ KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test empty strings */
+ create_unistr(&mock_env->str1, "");
+ create_unistr(&mock_env->str2, "");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "");
+ create_unistr(&mock_env->str2, "test");
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test single characters */
+ create_unistr(&mock_env->str1, "A");
+ create_unistr(&mock_env->str2, "a");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "A");
+ create_unistr(&mock_env->str2, "B");
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test maximum length strings */
+ memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
+ mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
+ create_unistr(&mock_env->str1, mock_env->buf);
+ create_unistr(&mock_env->str2, mock_env->buf);
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Change one character in the middle */
+ mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
+ create_unistr(&mock_env->str2, mock_env->buf);
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test corrupted strings */
+ create_unistr(&mock_env->str1, "");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "");
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "");
+ create_unistr(&mock_env->str2, "");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "test");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "testing");
+ KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "test");
+ create_unistr(&mock_env->str2, "testing");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "test");
+ KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ create_unistr(&mock_env->str2, "test");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_LT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ free_mock_str_env(mock_env);
+}
+
+/* Test hfsplus_strcmp function (case-sensitive) */
+static void hfsplus_strcmp_test(struct kunit *test)
+{
+ struct test_mock_string_env *mock_env;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ /* Test identical strings */
+ create_unistr(&mock_env->str1, "hello");
+ create_unistr(&mock_env->str2, "hello");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test case sensitive comparison - should NOT be equal */
+ create_unistr(&mock_env->str1, "Hello");
+ create_unistr(&mock_env->str2, "hello");
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+ /* 'H' < 'h' in Unicode */
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test lexicographic ordering */
+ create_unistr(&mock_env->str1, "apple");
+ create_unistr(&mock_env->str2, "banana");
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "zebra");
+ create_unistr(&mock_env->str2, "apple");
+ KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test different lengths with common prefix */
+ create_unistr(&mock_env->str1, "test");
+ create_unistr(&mock_env->str2, "testing");
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ create_unistr(&mock_env->str2, "test");
+ KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test empty strings */
+ create_unistr(&mock_env->str1, "");
+ create_unistr(&mock_env->str2, "");
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test maximum length strings */
+ memset(mock_env->buf, 'a', HFSPLUS_MAX_STRLEN);
+ mock_env->buf[HFSPLUS_MAX_STRLEN] = '\0';
+ create_unistr(&mock_env->str1, mock_env->buf);
+ create_unistr(&mock_env->str2, mock_env->buf);
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Change one character in the middle */
+ mock_env->buf[HFSPLUS_MAX_STRLEN / 2] = 'b';
+ create_unistr(&mock_env->str2, mock_env->buf);
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test corrupted strings */
+ create_unistr(&mock_env->str1, "");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "");
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "");
+ create_unistr(&mock_env->str2, "");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ create_unistr(&mock_env->str1, "test");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "testing");
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "test");
+ create_unistr(&mock_env->str2, "testing");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ corrupt_unistr(&mock_env->str1);
+ create_unistr(&mock_env->str2, "test");
+ KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ create_unistr(&mock_env->str1, "testing");
+ create_unistr(&mock_env->str2, "test");
+ corrupt_unistr(&mock_env->str2);
+ KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ free_mock_str_env(mock_env);
+}
+
+/* Test Unicode edge cases */
+static void hfsplus_unicode_edge_cases_test(struct kunit *test)
+{
+ struct test_mock_string_env *mock_env;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ /* Test with special characters */
+ mock_env->str1.length = cpu_to_be16(3);
+ mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
+ mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
+ mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
+
+ mock_env->str2.length = cpu_to_be16(3);
+ mock_env->str2.unicode[0] = cpu_to_be16(0x00E9); /* é */
+ mock_env->str2.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
+ mock_env->str2.unicode[2] = cpu_to_be16(0x00FC); /* ü */
+
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test with different special characters */
+ mock_env->str2.unicode[1] = cpu_to_be16(0x00F2); /* ò */
+ KUNIT_EXPECT_NE(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test null characters within string (should be handled correctly) */
+ mock_env->str1.length = cpu_to_be16(3);
+ mock_env->str1.unicode[0] = cpu_to_be16('a');
+ mock_env->str1.unicode[1] = cpu_to_be16(0x0000); /* null */
+ mock_env->str1.unicode[2] = cpu_to_be16('b');
+
+ mock_env->str2.length = cpu_to_be16(3);
+ mock_env->str2.unicode[0] = cpu_to_be16('a');
+ mock_env->str2.unicode[1] = cpu_to_be16(0x0000); /* null */
+ mock_env->str2.unicode[2] = cpu_to_be16('b');
+
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ free_mock_str_env(mock_env);
+}
+
+/* Test boundary conditions */
+static void hfsplus_unicode_boundary_test(struct kunit *test)
+{
+ struct test_mock_string_env *mock_env;
+ int i;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ /* Test maximum length boundary */
+ mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
+ mock_env->str2.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
+
+ for (i = 0; i < HFSPLUS_MAX_STRLEN; i++) {
+ mock_env->str1.unicode[i] = cpu_to_be16('A');
+ mock_env->str2.unicode[i] = cpu_to_be16('A');
+ }
+
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Change last character */
+ mock_env->str2.unicode[HFSPLUS_MAX_STRLEN - 1] = cpu_to_be16('B');
+ KUNIT_EXPECT_LT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ /* Test zero length strings */
+ mock_env->str1.length = cpu_to_be16(0);
+ mock_env->str2.length = cpu_to_be16(0);
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2));
+ KUNIT_EXPECT_EQ(test, 0, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2));
+
+ /* Test one character vs empty */
+ mock_env->str1.length = cpu_to_be16(1);
+ mock_env->str1.unicode[0] = cpu_to_be16('A');
+ mock_env->str2.length = cpu_to_be16(0);
+ KUNIT_EXPECT_GT(test, hfsplus_strcmp(&mock_env->str1,
+ &mock_env->str2), 0);
+ KUNIT_EXPECT_GT(test, hfsplus_strcasecmp(&mock_env->str1,
+ &mock_env->str2), 0);
+
+ free_mock_str_env(mock_env);
+}
+
+/* Mock superblock and NLS table for testing hfsplus_uni2asc */
+struct test_mock_sb {
+ struct nls_table nls;
+ struct hfsplus_sb_info sb_info;
+ struct super_block sb;
+};
+
+static struct test_mock_sb *setup_mock_sb(void)
+{
+ struct test_mock_sb *ptr;
+
+ ptr = kzalloc_obj(struct test_mock_sb);
+ if (!ptr)
+ return NULL;
+
+ ptr->nls.charset = "utf8";
+ ptr->nls.uni2char = NULL; /* Will use default behavior */
+ ptr->sb_info.nls = &ptr->nls;
+ ptr->sb.s_fs_info = &ptr->sb_info;
+
+ /* Set default flags - no decomposition, no case folding */
+ clear_bit(HFSPLUS_SB_NODECOMPOSE, &ptr->sb_info.flags);
+ clear_bit(HFSPLUS_SB_CASEFOLD, &ptr->sb_info.flags);
+
+ return ptr;
+}
+
+static void free_mock_sb(struct test_mock_sb *ptr)
+{
+ kfree(ptr);
+}
+
+/* Simple uni2char implementation for testing */
+static int test_uni2char(wchar_t uni, unsigned char *out, int boundlen)
+{
+ if (boundlen <= 0)
+ return -ENAMETOOLONG;
+
+ if (uni < 0x80) {
+ *out = (unsigned char)uni;
+ return 1;
+ }
+
+ /* For non-ASCII, just use '?' as fallback */
+ *out = '?';
+ return 1;
+}
+
+/* Test hfsplus_uni2asc basic functionality */
+static void hfsplus_uni2asc_basic_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int len, result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.uni2char = test_uni2char;
+
+ /* Test simple ASCII string conversion */
+ create_unistr(&mock_env->str1, "hello");
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 5, len);
+ KUNIT_EXPECT_STREQ(test, "hello", mock_env->buf);
+
+ /* Test empty string */
+ create_unistr(&mock_env->str1, "");
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 0, len);
+
+ /* Test single character */
+ create_unistr(&mock_env->str1, "A");
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 1, len);
+ KUNIT_EXPECT_EQ(test, 'A', mock_env->buf[0]);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test special character handling */
+static void hfsplus_uni2asc_special_chars_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int len, result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.uni2char = test_uni2char;
+
+ /* Test null character conversion (should become 0x2400) */
+ mock_env->str1.length = cpu_to_be16(1);
+ mock_env->str1.unicode[0] = cpu_to_be16(0x0000);
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 1, len);
+ /* Our test implementation returns '?' for non-ASCII */
+ KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
+
+ /* Test forward slash conversion (should become colon) */
+ mock_env->str1.length = cpu_to_be16(1);
+ mock_env->str1.unicode[0] = cpu_to_be16('/');
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 1, len);
+ KUNIT_EXPECT_EQ(test, ':', mock_env->buf[0]);
+
+ /* Test string with mixed special characters */
+ mock_env->str1.length = cpu_to_be16(3);
+ mock_env->str1.unicode[0] = cpu_to_be16('a');
+ mock_env->str1.unicode[1] = cpu_to_be16('/');
+ mock_env->str1.unicode[2] = cpu_to_be16('b');
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 3, len);
+ KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[0]);
+ KUNIT_EXPECT_EQ(test, ':', mock_env->buf[1]);
+ KUNIT_EXPECT_EQ(test, 'b', mock_env->buf[2]);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test buffer length handling */
+static void hfsplus_uni2asc_buffer_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int len, result;
+
+ mock_env = setup_mock_str_env(10);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.uni2char = test_uni2char;
+
+ /* Test insufficient buffer space */
+ create_unistr(&mock_env->str1, "toolongstring");
+ len = 5; /* Buffer too small */
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
+ KUNIT_EXPECT_EQ(test, 5, len); /* Should be set to consumed length */
+
+ /* Test exact buffer size */
+ create_unistr(&mock_env->str1, "exact");
+ len = 5;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 5, len);
+
+ /* Test zero length buffer */
+ create_unistr(&mock_env->str1, "test");
+ len = 0;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
+ KUNIT_EXPECT_EQ(test, 0, len);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test corrupted unicode string handling */
+static void hfsplus_uni2asc_corrupted_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int len, result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.uni2char = test_uni2char;
+
+ /* Test corrupted length (too large) */
+ create_unistr(&mock_env->str1, "test");
+ corrupt_unistr(&mock_env->str1); /* Sets length to U16_MAX */
+ len = mock_env->buf_size;
+
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ /* Should still work but with corrected length */
+ KUNIT_EXPECT_EQ(test, 0, result);
+ /*
+ * Length should be corrected to HFSPLUS_MAX_STRLEN
+ * and processed accordingly
+ */
+ KUNIT_EXPECT_GT(test, len, 0);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test edge cases and boundary conditions */
+static void hfsplus_uni2asc_edge_cases_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int len, result;
+ int i;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN * 2);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.uni2char = test_uni2char;
+
+ /* Test maximum length string */
+ mock_env->str1.length = cpu_to_be16(HFSPLUS_MAX_STRLEN);
+ for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
+ mock_env->str1.unicode[i] = cpu_to_be16('a');
+
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN, len);
+
+ /* Verify all characters are 'a' */
+ for (i = 0; i < HFSPLUS_MAX_STRLEN; i++)
+ KUNIT_EXPECT_EQ(test, 'a', mock_env->buf[i]);
+
+ /* Test string with high Unicode values (non-ASCII) */
+ mock_env->str1.length = cpu_to_be16(3);
+ mock_env->str1.unicode[0] = cpu_to_be16(0x00E9); /* é */
+ mock_env->str1.unicode[1] = cpu_to_be16(0x00F1); /* ñ */
+ mock_env->str1.unicode[2] = cpu_to_be16(0x00FC); /* ü */
+ len = mock_env->buf_size;
+ result = hfsplus_uni2asc_str(&mock_sb->sb, &mock_env->str1,
+ mock_env->buf, &len);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 3, len);
+ /* Our test implementation converts non-ASCII to '?' */
+ KUNIT_EXPECT_EQ(test, '?', mock_env->buf[0]);
+ KUNIT_EXPECT_EQ(test, '?', mock_env->buf[1]);
+ KUNIT_EXPECT_EQ(test, '?', mock_env->buf[2]);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Simple char2uni implementation for testing */
+static int test_char2uni(const unsigned char *rawstring,
+ int boundlen, wchar_t *uni)
+{
+ if (boundlen <= 0)
+ return -EINVAL;
+
+ *uni = (wchar_t)*rawstring;
+ return 1;
+}
+
+/* Helper function to check unicode string contents */
+static void check_unistr_content(struct kunit *test,
+ struct hfsplus_unistr *ustr,
+ const char *expected_ascii)
+{
+ int expected_len = strlen(expected_ascii);
+ int actual_len = be16_to_cpu(ustr->length);
+ int i;
+
+ KUNIT_EXPECT_EQ(test, expected_len, actual_len);
+
+ for (i = 0; i < expected_len && i < actual_len; i++) {
+ u16 expected_char = (u16)expected_ascii[i];
+ u16 actual_char = be16_to_cpu(ustr->unicode[i]);
+
+ KUNIT_EXPECT_EQ(test, expected_char, actual_char);
+ }
+}
+
+/* Test hfsplus_asc2uni basic functionality */
+static void hfsplus_asc2uni_basic_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test simple ASCII string conversion */
+ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
+ 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,
+ 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,
+ 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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ check_unistr_content(test, &mock_env->str1, "test");
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test special character handling in asc2uni */
+static void hfsplus_asc2uni_special_chars_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test colon conversion (should become forward slash) */
+ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
+ 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));
+ KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
+
+ /* Test string with mixed special characters */
+ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
+ 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));
+ KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(mock_env->str1.unicode[0]));
+ KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
+ KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(mock_env->str1.unicode[2]));
+
+ /* Test multiple special characters */
+ result = hfsplus_asc2uni(&mock_sb->sb, &mock_env->str1,
+ 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));
+ KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[0]));
+ KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[1]));
+ KUNIT_EXPECT_EQ(test, '/', be16_to_cpu(mock_env->str1.unicode[2]));
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test buffer length limits */
+static void hfsplus_asc2uni_buffer_limits_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 10);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test exact maximum length */
+ 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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
+ be16_to_cpu(mock_env->str1.length));
+
+ /* Test exceeding maximum length */
+ 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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
+ KUNIT_EXPECT_EQ(test, HFSPLUS_MAX_STRLEN,
+ be16_to_cpu(mock_env->str1.length));
+
+ /* Test with smaller max_unistr_len */
+ result = hfsplus_asc2uni(&mock_sb->sb,
+ &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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, -ENAMETOOLONG, result);
+ KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(mock_env->str1.length));
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test error handling and edge cases */
+static void hfsplus_asc2uni_edge_cases_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct hfsplus_unistr ustr;
+ char test_str[] = {'a', '\0', 'b'};
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test zero length input */
+ result = hfsplus_asc2uni(&mock_sb->sb,
+ &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,
+ 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,
+ 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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length));
+ KUNIT_EXPECT_EQ(test, 'a', be16_to_cpu(ustr.unicode[0]));
+ KUNIT_EXPECT_EQ(test, 0, be16_to_cpu(ustr.unicode[1]));
+ KUNIT_EXPECT_EQ(test, 'b', be16_to_cpu(ustr.unicode[2]));
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test decomposition flag behavior */
+static void hfsplus_asc2uni_decompose_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ int result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* 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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ check_unistr_content(test, &mock_env->str1, "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,
+ HFS_REGULAR_NAME);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ check_unistr_content(test, &mock_env->str2, "test");
+
+ /* For simple ASCII, both should produce the same result */
+ KUNIT_EXPECT_EQ(test,
+ be16_to_cpu(mock_env->str1.length),
+ be16_to_cpu(mock_env->str2.length));
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Mock dentry for testing hfsplus_hash_dentry */
+static struct dentry test_dentry;
+
+static void setup_mock_dentry(struct super_block *sb)
+{
+ memset(&test_dentry, 0, sizeof(test_dentry));
+ test_dentry.d_sb = sb;
+}
+
+/* Helper function to create qstr */
+static void create_qstr(struct qstr *str, const char *name)
+{
+ str->name = name;
+ str->len = strlen(name);
+ str->hash = 0; /* Will be set by hash function */
+}
+
+/* Test hfsplus_hash_dentry basic functionality */
+static void hfsplus_hash_dentry_basic_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr str1, str2;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test basic string hashing */
+ create_qstr(&str1, "hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_NE(test, 0, str1.hash);
+
+ /* Test that identical strings produce identical hashes */
+ create_qstr(&str2, "hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
+
+ /* Test empty string */
+ create_qstr(&str1, "");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+
+ /* Empty string should still produce a hash */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test single character */
+ create_qstr(&str1, "A");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_NE(test, 0, str1.hash);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test case folding behavior in hash */
+static void hfsplus_hash_dentry_casefold_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr str1, str2;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test with case folding disabled (default) */
+ clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+
+ create_qstr(&str1, "Hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&str2, "hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /*
+ * Without case folding, different cases
+ * should produce different hashes
+ */
+ KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
+
+ /* Test with case folding enabled */
+ set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+
+ create_qstr(&str1, "Hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&str2, "hello");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* With case folding, different cases should produce same hash */
+ KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
+
+ /* Test mixed case */
+ create_qstr(&str1, "HeLLo");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test special character handling in hash */
+static void hfsplus_hash_dentry_special_chars_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr str1, str2;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test colon conversion (: becomes /) */
+ create_qstr(&str1, "file:name");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&str2, "file/name");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* After conversion, these should produce the same hash */
+ KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
+
+ /* Test multiple special characters */
+ create_qstr(&str1, ":::");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&str2, "///");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ KUNIT_EXPECT_EQ(test, str1.hash, str2.hash);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test decomposition flag behavior in hash */
+static void hfsplus_hash_dentry_decompose_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr str1, str2;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test with decomposition disabled (default) */
+ clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&str1, "test");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test with decomposition enabled */
+ set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&str2, "test");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /*
+ * For simple ASCII, decomposition shouldn't change
+ * the hash much but the function should still work correctly
+ */
+ KUNIT_EXPECT_NE(test, 0, str2.hash);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test hash consistency and distribution */
+static void hfsplus_hash_dentry_consistency_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr str1, str2, str3;
+ unsigned long hash1;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test that same string always produces same hash */
+ create_qstr(&str1, "consistent");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+ hash1 = str1.hash;
+
+ create_qstr(&str2, "consistent");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ KUNIT_EXPECT_EQ(test, hash1, str2.hash);
+
+ /* Test that different strings produce different hashes */
+ create_qstr(&str3, "different");
+ result = hfsplus_hash_dentry(&test_dentry, &str3);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ KUNIT_EXPECT_NE(test, str1.hash, str3.hash);
+
+ /* Test similar strings should have different hashes */
+ create_qstr(&str1, "file1");
+ result = hfsplus_hash_dentry(&test_dentry, &str1);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&str2, "file2");
+ result = hfsplus_hash_dentry(&test_dentry, &str2);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ KUNIT_EXPECT_NE(test, str1.hash, str2.hash);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test edge cases and boundary conditions */
+static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct test_mock_string_env *mock_env;
+ struct qstr str;
+ int result;
+
+ mock_env = setup_mock_str_env(HFSPLUS_MAX_STRLEN + 1);
+ KUNIT_ASSERT_NOT_NULL(test, mock_env);
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test very long filename */
+ memset(mock_env->buf, 'a', mock_env->buf_size - 1);
+ mock_env->buf[mock_env->buf_size - 1] = '\0';
+
+ create_qstr(&str, mock_env->buf);
+ result = hfsplus_hash_dentry(&test_dentry, &str);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_NE(test, 0, str.hash);
+
+ /* Test filename with all printable ASCII characters */
+ create_qstr(&str, "!@#$%^&*()_+-=[]{}|;':\",./<>?");
+ result = hfsplus_hash_dentry(&test_dentry, &str);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_NE(test, 0, str.hash);
+
+ /* Test with embedded null (though not typical for filenames) */
+ str.name = "file\0hidden";
+ str.len = 11; /* Include the null and text after it */
+ str.hash = 0;
+ result = hfsplus_hash_dentry(&test_dentry, &str);
+
+ KUNIT_EXPECT_EQ(test, 0, result);
+ KUNIT_EXPECT_NE(test, 0, str.hash);
+
+ free_mock_str_env(mock_env);
+ free_mock_sb(mock_sb);
+}
+
+/* Test hfsplus_compare_dentry basic functionality */
+static void hfsplus_compare_dentry_basic_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test identical strings */
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test different strings - lexicographic order */
+ create_qstr(&name, "world");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* "hello" < "world" */
+
+ result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "world", &name);
+ KUNIT_EXPECT_GT(test, result, 0); /* "world" > "hello" */
+
+ /* Test empty strings */
+ create_qstr(&name, "");
+ result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test one empty, one non-empty */
+ create_qstr(&name, "test");
+ result = hfsplus_compare_dentry(&test_dentry, 0, "", &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* "" < "test" */
+
+ create_qstr(&name, "");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_GT(test, result, 0); /* "test" > "" */
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test case folding behavior in comparison */
+static void hfsplus_compare_dentry_casefold_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test with case folding disabled (default) */
+ clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
+ /* Case sensitive: "Hello" != "hello" */
+ KUNIT_EXPECT_NE(test, 0, result);
+
+ create_qstr(&name, "Hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
+ /* Case sensitive: "hello" != "Hello" */
+ KUNIT_EXPECT_NE(test, 0, result);
+
+ /* Test with case folding enabled */
+ set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "Hello", &name);
+ /* Case insensitive: "Hello" == "hello" */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&name, "Hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "hello", &name);
+ /* Case insensitive: "hello" == "Hello" */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test mixed case */
+ create_qstr(&name, "TeSt");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&name, "test");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "TEST", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test special character handling in comparison */
+static void hfsplus_compare_dentry_special_chars_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test colon conversion (: becomes /) */
+ create_qstr(&name, "file/name");
+ result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
+ /* "file:name" == "file/name" after conversion */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ create_qstr(&name, "file:name");
+ result = hfsplus_compare_dentry(&test_dentry, 9, "file/name", &name);
+ /* "file/name" == "file:name" after conversion */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test multiple special characters */
+ create_qstr(&name, "///");
+ result = hfsplus_compare_dentry(&test_dentry, 3, ":::", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test mixed special and regular characters */
+ create_qstr(&name, "a/b:c");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "a:b/c", &name);
+ /* Both become "a/b/c" after conversion */
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test length differences */
+static void hfsplus_compare_dentry_length_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test different lengths with common prefix */
+ create_qstr(&name, "testing");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* "test" < "testing" */
+
+ create_qstr(&name, "test");
+ result = hfsplus_compare_dentry(&test_dentry, 7, "testing", &name);
+ KUNIT_EXPECT_GT(test, result, 0); /* "testing" > "test" */
+
+ /* Test exact length match */
+ create_qstr(&name, "exact");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "exact", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test length parameter vs actual string content */
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 3, "hel", &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* "hel" < "hello" */
+
+ /* Test longer first string but shorter length parameter */
+ create_qstr(&name, "hi");
+ result = hfsplus_compare_dentry(&test_dentry, 2, "hello", &name);
+ /* "he" < "hi" (only first 2 chars compared) */
+ KUNIT_EXPECT_LT(test, result, 0);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test decomposition flag behavior */
+static void hfsplus_compare_dentry_decompose_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test with decomposition disabled (default) */
+ clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "test");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test with decomposition enabled */
+ set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "test");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* For simple ASCII, decomposition shouldn't affect the result */
+ create_qstr(&name, "different");
+ result = hfsplus_compare_dentry(&test_dentry, 4, "test", &name);
+ KUNIT_EXPECT_NE(test, 0, result);
+
+ free_mock_sb(mock_sb);
+}
+
+/* Test edge cases and boundary conditions */
+static void hfsplus_compare_dentry_edge_cases_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ char *long_str;
+ char *long_str2;
+ u32 str_size = HFSPLUS_MAX_STRLEN + 1;
+ struct qstr null_name = {
+ .name = "a\0b",
+ .len = 3,
+ .hash = 0
+ };
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ long_str = kzalloc(str_size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, long_str);
+
+ long_str2 = kzalloc(str_size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, long_str2);
+
+ /* Test very long strings */
+ memset(long_str, 'a', str_size - 1);
+ long_str[str_size - 1] = '\0';
+
+ create_qstr(&name, long_str);
+ result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
+ long_str, &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test with difference at the end of long strings */
+ memset(long_str2, 'a', str_size - 1);
+ long_str2[str_size - 1] = '\0';
+ long_str2[str_size - 2] = 'b';
+ create_qstr(&name, long_str2);
+ result = hfsplus_compare_dentry(&test_dentry, str_size - 1,
+ long_str, &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
+
+ /* Test single character differences */
+ create_qstr(&name, "b");
+ result = hfsplus_compare_dentry(&test_dentry, 1, "a", &name);
+ KUNIT_EXPECT_LT(test, result, 0); /* 'a' < 'b' */
+
+ create_qstr(&name, "a");
+ result = hfsplus_compare_dentry(&test_dentry, 1, "b", &name);
+ KUNIT_EXPECT_GT(test, result, 0); /* 'b' > 'a' */
+
+ /* Test with null characters in the middle */
+ result = hfsplus_compare_dentry(&test_dentry, 3, "a\0b", &null_name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test all printable ASCII characters */
+ create_qstr(&name, "!@#$%^&*()");
+ result = hfsplus_compare_dentry(&test_dentry, 10, "!@#$%^&*()", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ kfree(long_str);
+ kfree(long_str2);
+ free_mock_sb(mock_sb);
+}
+
+/* Test combined flag behaviors */
+static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test)
+{
+ struct test_mock_sb *mock_sb;
+ struct qstr name;
+ int result;
+
+ mock_sb = setup_mock_sb();
+ KUNIT_ASSERT_NOT_NULL(test, mock_sb);
+
+ setup_mock_dentry(&mock_sb->sb);
+ mock_sb->nls.char2uni = test_char2uni;
+
+ /* Test with both casefold and decompose enabled */
+ set_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+ set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test special chars with case folding */
+ create_qstr(&name, "File/Name");
+ result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ /* Test with both flags disabled */
+ clear_bit(HFSPLUS_SB_CASEFOLD, &mock_sb->sb_info.flags);
+ clear_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags);
+
+ create_qstr(&name, "hello");
+ result = hfsplus_compare_dentry(&test_dentry, 5, "HELLO", &name);
+ KUNIT_EXPECT_NE(test, 0, result); /* Case sensitive */
+
+ /* But special chars should still be converted */
+ create_qstr(&name, "file/name");
+ result = hfsplus_compare_dentry(&test_dentry, 9, "file:name", &name);
+ KUNIT_EXPECT_EQ(test, 0, result);
+
+ free_mock_sb(mock_sb);
+}
+
+static struct kunit_case hfsplus_unicode_test_cases[] = {
+ KUNIT_CASE(hfsplus_strcasecmp_test),
+ KUNIT_CASE(hfsplus_strcmp_test),
+ KUNIT_CASE(hfsplus_unicode_edge_cases_test),
+ KUNIT_CASE(hfsplus_unicode_boundary_test),
+ KUNIT_CASE(hfsplus_uni2asc_basic_test),
+ KUNIT_CASE(hfsplus_uni2asc_special_chars_test),
+ KUNIT_CASE(hfsplus_uni2asc_buffer_test),
+ KUNIT_CASE(hfsplus_uni2asc_corrupted_test),
+ KUNIT_CASE(hfsplus_uni2asc_edge_cases_test),
+ KUNIT_CASE(hfsplus_asc2uni_basic_test),
+ KUNIT_CASE(hfsplus_asc2uni_special_chars_test),
+ KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test),
+ KUNIT_CASE(hfsplus_asc2uni_edge_cases_test),
+ KUNIT_CASE(hfsplus_asc2uni_decompose_test),
+ KUNIT_CASE(hfsplus_hash_dentry_basic_test),
+ KUNIT_CASE(hfsplus_hash_dentry_casefold_test),
+ KUNIT_CASE(hfsplus_hash_dentry_special_chars_test),
+ KUNIT_CASE(hfsplus_hash_dentry_decompose_test),
+ KUNIT_CASE(hfsplus_hash_dentry_consistency_test),
+ KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test),
+ KUNIT_CASE(hfsplus_compare_dentry_basic_test),
+ KUNIT_CASE(hfsplus_compare_dentry_casefold_test),
+ KUNIT_CASE(hfsplus_compare_dentry_special_chars_test),
+ KUNIT_CASE(hfsplus_compare_dentry_length_test),
+ KUNIT_CASE(hfsplus_compare_dentry_decompose_test),
+ KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test),
+ KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test),
+ {}
+};
+
+static struct kunit_suite hfsplus_unicode_test_suite = {
+ .name = "hfsplus_unicode",
+ .test_cases = hfsplus_unicode_test_cases,
+};
+
+kunit_test_suite(hfsplus_unicode_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for HFS+ Unicode string operations");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/fs/hfsplus/wrapper.c b/fs/hfsplus/wrapper.c
new file mode 100644
index 000000000..30cf4fe78
--- /dev/null
+++ b/fs/hfsplus/wrapper.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/wrapper.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handling of HFS wrappers around HFS+ volumes
+ */
+
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/cdrom.h>
+#include <linux/unaligned.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+
+struct hfsplus_wd {
+ u32 ablk_size;
+ u16 ablk_start;
+ u16 embed_start;
+ u16 embed_count;
+};
+
+/**
+ * hfsplus_submit_bio - Perform block I/O
+ * @sb: super block of volume for I/O
+ * @sector: block to read or write, for blocks of HFSPLUS_SECTOR_SIZE bytes
+ * @buf: buffer for I/O
+ * @data: output pointer for location of requested data
+ * @opf: I/O operation type and flags
+ *
+ * The unit of I/O is hfsplus_min_io_size(sb), which may be bigger than
+ * HFSPLUS_SECTOR_SIZE, and @buf must be sized accordingly. On reads
+ * @data will return a pointer to the start of the requested sector,
+ * which may not be the same location as @buf.
+ *
+ * If @sector is not aligned to the bdev logical block size it will
+ * be rounded down. For writes this means that @buf should contain data
+ * that starts at the rounded-down address. As long as the data was
+ * read using hfsplus_submit_bio() and the same buffer is used things
+ * will work correctly.
+ *
+ * Returns: %0 on success else -errno code
+ */
+int hfsplus_submit_bio(struct super_block *sb, sector_t sector,
+ void *buf, void **data, blk_opf_t opf)
+{
+ u64 io_size = hfsplus_min_io_size(sb);
+ loff_t start = (loff_t)sector << HFSPLUS_SECTOR_SHIFT;
+ int offset = start & (io_size - 1);
+
+ if ((opf & REQ_OP_MASK) != REQ_OP_WRITE && data)
+ *data = (u8 *)buf + offset;
+
+ /*
+ * Align sector to hardware sector size and find offset. We assume that
+ * io_size is a power of two, which _should_ be true.
+ */
+ sector &= ~((io_size >> HFSPLUS_SECTOR_SHIFT) - 1);
+ return bdev_rw_virt(sb->s_bdev, sector, buf, io_size, opf);
+}
+
+static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)
+{
+ u32 extent;
+ u16 attrib;
+ __be16 sig;
+
+ sig = *(__be16 *)(bufptr + HFSP_WRAPOFF_EMBEDSIG);
+ if (sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIG) &&
+ sig != cpu_to_be16(HFSPLUS_VOLHEAD_SIGX))
+ return 0;
+
+ attrib = be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ATTRIB));
+ if (!(attrib & HFSP_WRAP_ATTRIB_SLOCK) ||
+ !(attrib & HFSP_WRAP_ATTRIB_SPARED))
+ return 0;
+
+ wd->ablk_size =
+ be32_to_cpu(*(__be32 *)(bufptr + HFSP_WRAPOFF_ABLKSIZE));
+ if (wd->ablk_size < HFSPLUS_SECTOR_SIZE)
+ return 0;
+ if (wd->ablk_size % HFSPLUS_SECTOR_SIZE)
+ return 0;
+ wd->ablk_start =
+ be16_to_cpu(*(__be16 *)(bufptr + HFSP_WRAPOFF_ABLKSTART));
+
+ extent = get_unaligned_be32(bufptr + HFSP_WRAPOFF_EMBEDEXT);
+ wd->embed_start = (extent >> 16) & 0xFFFF;
+ wd->embed_count = extent & 0xFFFF;
+
+ return 1;
+}
+
+static int hfsplus_get_last_session(struct super_block *sb,
+ sector_t *start, sector_t *size)
+{
+ struct cdrom_device_info *cdi = disk_to_cdi(sb->s_bdev->bd_disk);
+
+ /* default values */
+ *start = 0;
+ *size = bdev_nr_sectors(sb->s_bdev);
+
+ if (HFSPLUS_SB(sb)->session >= 0) {
+ struct cdrom_tocentry te;
+
+ if (!cdi)
+ return -EINVAL;
+
+ te.cdte_track = HFSPLUS_SB(sb)->session;
+ te.cdte_format = CDROM_LBA;
+ if (cdrom_read_tocentry(cdi, &te) ||
+ (te.cdte_ctrl & CDROM_DATA_TRACK) != 4) {
+ pr_err("invalid session number or type of track\n");
+ return -EINVAL;
+ }
+ *start = (sector_t)te.cdte_addr.lba << 2;
+ } else if (cdi) {
+ struct cdrom_multisession ms_info;
+
+ ms_info.addr_format = CDROM_LBA;
+ if (cdrom_multisession(cdi, &ms_info) == 0 && ms_info.xa_flag)
+ *start = (sector_t)ms_info.addr.lba << 2;
+ }
+
+ return 0;
+}
+
+/* Find the volume header and fill in some minimum bits in superblock */
+/* Takes in super block, returns true if good data read */
+int hfsplus_read_wrapper(struct super_block *sb)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_wd wd;
+ sector_t part_start, part_size;
+ u32 blocksize;
+ int error = 0;
+
+ error = -EINVAL;
+ blocksize = sb_min_blocksize(sb, HFSPLUS_SECTOR_SIZE);
+ if (!blocksize)
+ goto out;
+
+ sbi->min_io_size = blocksize;
+
+ if (hfsplus_get_last_session(sb, &part_start, &part_size))
+ goto out;
+
+ error = -ENOMEM;
+ sbi->s_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
+ if (!sbi->s_vhdr_buf)
+ goto out;
+ sbi->s_backup_vhdr_buf = kmalloc(hfsplus_min_io_size(sb), GFP_KERNEL);
+ if (!sbi->s_backup_vhdr_buf)
+ goto out_free_vhdr;
+
+reread:
+ error = hfsplus_submit_bio(sb, part_start + HFSPLUS_VOLHEAD_SECTOR,
+ sbi->s_vhdr_buf, (void **)&sbi->s_vhdr,
+ REQ_OP_READ);
+ if (error)
+ goto out_free_backup_vhdr;
+
+ error = -EINVAL;
+ switch (sbi->s_vhdr->signature) {
+ case cpu_to_be16(HFSPLUS_VOLHEAD_SIGX):
+ set_bit(HFSPLUS_SB_HFSX, &sbi->flags);
+ fallthrough;
+ case cpu_to_be16(HFSPLUS_VOLHEAD_SIG):
+ break;
+ case cpu_to_be16(HFSP_WRAP_MAGIC):
+ if (!hfsplus_read_mdb(sbi->s_vhdr, &wd))
+ goto out_free_backup_vhdr;
+ wd.ablk_size >>= HFSPLUS_SECTOR_SHIFT;
+ part_start += (sector_t)wd.ablk_start +
+ (sector_t)wd.embed_start * wd.ablk_size;
+ part_size = (sector_t)wd.embed_count * wd.ablk_size;
+ goto reread;
+ default:
+ /*
+ * Check for a partition block.
+ *
+ * (should do this only for cdrom/loop though)
+ */
+ if (hfs_part_find(sb, &part_start, &part_size))
+ goto out_free_backup_vhdr;
+ goto reread;
+ }
+
+ error = hfsplus_submit_bio(sb, part_start + part_size - 2,
+ sbi->s_backup_vhdr_buf,
+ (void **)&sbi->s_backup_vhdr, REQ_OP_READ);
+ if (error)
+ goto out_free_backup_vhdr;
+
+ error = -EINVAL;
+ if (sbi->s_backup_vhdr->signature != sbi->s_vhdr->signature) {
+ pr_warn("invalid secondary volume header\n");
+ goto out_free_backup_vhdr;
+ }
+
+ blocksize = be32_to_cpu(sbi->s_vhdr->blocksize);
+
+ /*
+ * Block size must be at least as large as a sector and a multiple of 2.
+ */
+ if (blocksize < HFSPLUS_SECTOR_SIZE || ((blocksize - 1) & blocksize))
+ goto out_free_backup_vhdr;
+ sbi->alloc_blksz = blocksize;
+ sbi->alloc_blksz_shift = ilog2(blocksize);
+ blocksize = min_t(u32, sbi->alloc_blksz, PAGE_SIZE);
+
+ /*
+ * Align block size to block offset.
+ */
+ while (part_start & ((blocksize >> HFSPLUS_SECTOR_SHIFT) - 1))
+ blocksize >>= 1;
+
+ if (sb_set_blocksize(sb, blocksize) != blocksize) {
+ pr_err("unable to set blocksize to %u!\n", blocksize);
+ goto out_free_backup_vhdr;
+ }
+
+ sbi->blockoffset =
+ part_start >> (sb->s_blocksize_bits - HFSPLUS_SECTOR_SHIFT);
+ sbi->part_start = part_start;
+ sbi->sect_count = part_size;
+ sbi->fs_shift = sbi->alloc_blksz_shift - sb->s_blocksize_bits;
+ return 0;
+
+out_free_backup_vhdr:
+ kfree(sbi->s_backup_vhdr_buf);
+out_free_vhdr:
+ kfree(sbi->s_vhdr_buf);
+out:
+ return error;
+}
diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c
new file mode 100644
index 000000000..21a1c196c
--- /dev/null
+++ b/fs/hfsplus/xattr.c
@@ -0,0 +1,1036 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/xattr.c
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Logic of processing extended attributes
+ */
+
+#include "hfsplus_fs.h"
+#include <linux/nls.h>
+#include "xattr.h"
+
+static int hfsplus_removexattr(struct inode *inode, const char *name);
+
+const struct xattr_handler * const hfsplus_xattr_handlers[] = {
+ &hfsplus_xattr_osx_handler,
+ &hfsplus_xattr_user_handler,
+ &hfsplus_xattr_trusted_handler,
+ &hfsplus_xattr_security_handler,
+ NULL
+};
+
+static int strcmp_xattr_finder_info(const char *name)
+{
+ if (name) {
+ return strncmp(name, HFSPLUS_XATTR_FINDER_INFO_NAME,
+ sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME));
+ }
+ return -1;
+}
+
+static int strcmp_xattr_acl(const char *name)
+{
+ if (name) {
+ return strncmp(name, HFSPLUS_XATTR_ACL_NAME,
+ sizeof(HFSPLUS_XATTR_ACL_NAME));
+ }
+ return -1;
+}
+
+static bool is_known_namespace(const char *name)
+{
+ if (strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) &&
+ strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
+ strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) &&
+ strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN))
+ return false;
+
+ return true;
+}
+
+static u32 hfsplus_init_header_node(struct inode *attr_file,
+ u32 clump_size,
+ char *buf, u16 node_size)
+{
+ struct hfs_bnode_desc *desc;
+ struct hfs_btree_header_rec *head;
+ u16 offset;
+ __be16 *rec_offsets;
+ u32 hdr_node_map_rec_bits;
+ u32 map_nodes = 0;
+ char *bmp;
+ u32 used_nodes;
+ u32 used_bmp_bytes;
+ u64 tmp;
+
+ hfs_dbg("clump %u, node_size %u\n",
+ clump_size, node_size);
+
+ /* The end of the node contains list of record offsets */
+ rec_offsets = (__be16 *)(buf + node_size);
+
+ desc = (struct hfs_bnode_desc *)buf;
+ desc->type = HFS_NODE_HEADER;
+ desc->num_recs = cpu_to_be16(HFSPLUS_BTREE_HDR_NODE_RECS_COUNT);
+ offset = sizeof(struct hfs_bnode_desc);
+ *--rec_offsets = cpu_to_be16(offset);
+
+ head = (struct hfs_btree_header_rec *)(buf + offset);
+ head->node_size = cpu_to_be16(node_size);
+ tmp = i_size_read(attr_file);
+ do_div(tmp, node_size);
+ head->node_count = cpu_to_be32(tmp);
+ head->free_nodes = cpu_to_be32(be32_to_cpu(head->node_count) - 1);
+ head->clump_size = cpu_to_be32(clump_size);
+ head->attributes |= cpu_to_be32(HFS_TREE_BIGKEYS | HFS_TREE_VARIDXKEYS);
+ head->max_key_len = cpu_to_be16(HFSPLUS_ATTR_KEYLEN - sizeof(u16));
+ offset += sizeof(struct hfs_btree_header_rec);
+ *--rec_offsets = cpu_to_be16(offset);
+ offset += HFSPLUS_BTREE_HDR_USER_BYTES;
+ *--rec_offsets = cpu_to_be16(offset);
+
+ 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;
+
+ desc->next = cpu_to_be32(be32_to_cpu(head->leaf_tail) + 1);
+ map_node_bits = 8 * (node_size - sizeof(struct hfs_bnode_desc) -
+ (2 * sizeof(u16)) - 2);
+ map_nodes = (be32_to_cpu(head->node_count) -
+ hdr_node_map_rec_bits +
+ (map_node_bits - 1)) / map_node_bits;
+ be32_add_cpu(&head->free_nodes, 0 - map_nodes);
+ }
+
+ bmp = buf + offset;
+ used_nodes =
+ be32_to_cpu(head->node_count) - be32_to_cpu(head->free_nodes);
+ used_bmp_bytes = used_nodes / 8;
+ if (used_bmp_bytes) {
+ memset(bmp, 0xFF, used_bmp_bytes);
+ bmp += used_bmp_bytes;
+ used_nodes %= 8;
+ }
+ *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)
+{
+ 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;
+ int old_state = HFSPLUS_EMPTY_ATTR_TREE;
+ int err = 0;
+
+ hfs_dbg("ino %d\n", HFSPLUS_ATTR_CNID);
+
+check_attr_tree_state_again:
+ switch (atomic_read(&sbi->attr_tree_state)) {
+ case HFSPLUS_EMPTY_ATTR_TREE:
+ if (old_state != atomic_cmpxchg(&sbi->attr_tree_state,
+ old_state,
+ HFSPLUS_CREATING_ATTR_TREE))
+ goto check_attr_tree_state_again;
+ break;
+ case HFSPLUS_CREATING_ATTR_TREE:
+ /*
+ * This state means that another thread is in process
+ * of AttributesFile creation. Theoretically, it is
+ * possible to be here. But really __setxattr() method
+ * first of all calls hfs_find_init() for lookup in
+ * B-tree of CatalogFile. This method locks mutex of
+ * CatalogFile's B-tree. As a result, if some thread
+ * is inside AttributedFile creation operation then
+ * another threads will be waiting unlocking of
+ * CatalogFile's B-tree's mutex. However, if code will
+ * change then we will return error code (-EAGAIN) from
+ * here. Really, it means that first try to set of xattr
+ * fails with error but second attempt will have success.
+ */
+ return -EAGAIN;
+ case HFSPLUS_VALID_ATTR_TREE:
+ return 0;
+ case HFSPLUS_FAILED_ATTR_TREE:
+ return -EOPNOTSUPP;
+ default:
+ BUG();
+ }
+
+ attr_file = hfsplus_iget(sb, HFSPLUS_ATTR_CNID);
+ if (IS_ERR(attr_file)) {
+ pr_err("failed to load attributes file\n");
+ return PTR_ERR(attr_file);
+ }
+
+ if (i_size_read(attr_file) != 0) {
+ err = -EIO;
+ pr_err("detected inconsistent attributes file, running fsck.hfsplus is recommended.\n");
+ goto end_attr_file_creation;
+ }
+
+ hip = HFSPLUS_I(attr_file);
+
+ clump_size = hfsplus_calc_btree_clump_size(sb->s_blocksize,
+ node_size,
+ sbi->sect_count,
+ HFSPLUS_ATTR_CNID);
+
+ mutex_lock(&hip->extents_lock);
+ hip->clump_blocks = clump_size >> sbi->alloc_blksz_shift;
+ mutex_unlock(&hip->extents_lock);
+
+ if (sbi->free_blocks <= (hip->clump_blocks << 1)) {
+ err = -ENOSPC;
+ goto end_attr_file_creation;
+ }
+
+ while (hip->alloc_blocks < hip->clump_blocks) {
+ err = hfsplus_file_extend(attr_file, true);
+ if (unlikely(err)) {
+ pr_err("failed to extend attributes file\n");
+ goto end_attr_file_creation;
+ }
+ hip->phys_size = attr_file->i_size =
+ (loff_t)hip->alloc_blocks << sbi->alloc_blksz_shift;
+ hip->fs_blocks = hip->alloc_blocks << sbi->fs_shift;
+ inode_set_bytes(attr_file, attr_file->i_size);
+ }
+
+ buf = kzalloc(node_size, GFP_NOFS);
+ if (!buf) {
+ err = -ENOMEM;
+ goto end_attr_file_creation;
+ }
+
+ map_nodes = hfsplus_init_header_node(attr_file, clump_size, buf, node_size);
+
+ desc = (struct hfs_bnode_desc *)buf;
+ next_node = be32_to_cpu(desc->next);
+
+ index = 0;
+
+ err = hfsplus_write_attributes_file_node(attr_file, buf,
+ node_size, &index);
+ if (unlikely(err))
+ goto failed_header_node_init;
+
+ for (map_node_idx = 0; map_node_idx < map_nodes; map_node_idx++) {
+ if (next_node >= map_nodes)
+ next_node = 0;
+
+ 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(attr_file, HFSPLUS_I_ATTR_DIRTY);
+
+ sbi->attr_tree = hfs_btree_open(sb, HFSPLUS_ATTR_CNID);
+ if (!sbi->attr_tree)
+ pr_err("failed to load attributes file\n");
+
+failed_header_node_init:
+ kfree(buf);
+
+end_attr_file_creation:
+ iput(attr_file);
+
+ if (!err)
+ atomic_set(&sbi->attr_tree_state, HFSPLUS_VALID_ATTR_TREE);
+ else if (err == -ENOSPC)
+ atomic_set(&sbi->attr_tree_state, HFSPLUS_EMPTY_ATTR_TREE);
+ else
+ atomic_set(&sbi->attr_tree_state, HFSPLUS_FAILED_ATTR_TREE);
+
+ return err;
+}
+
+static inline
+bool is_xattr_operation_supported(struct inode *inode)
+{
+ if (HFSPLUS_IS_RSRC(inode))
+ return false;
+
+ return true;
+}
+
+int __hfsplus_setxattr(struct inode *inode, const char *name,
+ const void *value, size_t size, int flags)
+{
+ int err;
+ struct hfs_find_data cat_fd;
+ hfsplus_cat_entry entry;
+ u16 cat_entry_flags, cat_entry_type;
+ u16 folder_finderinfo_len = sizeof(DInfo) + sizeof(DXInfo);
+ u16 file_finderinfo_len = sizeof(FInfo) + sizeof(FXInfo);
+
+ hfs_dbg("ino %llu, name %s, value %p, size %zu\n",
+ inode->i_ino, name ? name : NULL,
+ value, size);
+
+ if (!is_xattr_operation_supported(inode))
+ return -EOPNOTSUPP;
+
+ if (value == NULL)
+ return hfsplus_removexattr(inode, name);
+
+ err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &cat_fd);
+ if (err) {
+ pr_err("can't init xattr find struct\n");
+ return err;
+ }
+
+ err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &cat_fd);
+ if (err) {
+ pr_err("catalog searching failed\n");
+ goto end_setxattr;
+ }
+
+ if (!strcmp_xattr_finder_info(name)) {
+ if (flags & XATTR_CREATE) {
+ pr_err("xattr exists yet\n");
+ err = -EOPNOTSUPP;
+ goto end_setxattr;
+ }
+ hfs_bnode_read(cat_fd.bnode, &entry, cat_fd.entryoffset,
+ sizeof(hfsplus_cat_entry));
+ if (be16_to_cpu(entry.type) == HFSPLUS_FOLDER) {
+ if (size == folder_finderinfo_len) {
+ memcpy(&entry.folder.info, value,
+ folder_finderinfo_len);
+ hfs_bnode_write(cat_fd.bnode, &entry,
+ cat_fd.entryoffset,
+ sizeof(struct hfsplus_cat_folder));
+ hfsplus_mark_inode_dirty(
+ HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode,
+ HFSPLUS_I_CAT_DIRTY);
+ } else {
+ err = -ERANGE;
+ goto end_setxattr;
+ }
+ } else if (be16_to_cpu(entry.type) == HFSPLUS_FILE) {
+ if (size == file_finderinfo_len) {
+ memcpy(&entry.file.info, value,
+ file_finderinfo_len);
+ hfs_bnode_write(cat_fd.bnode, &entry,
+ cat_fd.entryoffset,
+ sizeof(struct hfsplus_cat_file));
+ hfsplus_mark_inode_dirty(
+ HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode,
+ HFSPLUS_I_CAT_DIRTY);
+ } else {
+ err = -ERANGE;
+ goto end_setxattr;
+ }
+ } else {
+ err = -EOPNOTSUPP;
+ goto end_setxattr;
+ }
+ goto end_setxattr;
+ }
+
+ if (!HFSPLUS_SB(inode->i_sb)->attr_tree) {
+ err = hfsplus_create_attributes_file(inode->i_sb);
+ if (unlikely(err))
+ goto end_setxattr;
+ }
+
+ if (hfsplus_attr_exists(inode, name)) {
+ if (flags & XATTR_CREATE) {
+ pr_err("xattr exists yet\n");
+ err = -EOPNOTSUPP;
+ goto end_setxattr;
+ }
+ err = hfsplus_replace_attr(inode, name, value, size);
+ if (err) {
+ hfs_dbg("unable to replace xattr: err %d\n", err);
+ goto end_setxattr;
+ }
+ } else {
+ if (flags & XATTR_REPLACE) {
+ pr_err("cannot replace xattr\n");
+ err = -EOPNOTSUPP;
+ goto end_setxattr;
+ }
+ err = hfsplus_create_attr(inode, name, value, size);
+ if (err) {
+ hfs_dbg("unable to store value: err %d\n", err);
+ goto end_setxattr;
+ }
+ }
+
+ cat_entry_type = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset);
+ if (cat_entry_type == HFSPLUS_FOLDER) {
+ cat_entry_flags = hfs_bnode_read_u16(cat_fd.bnode,
+ cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, flags));
+ cat_entry_flags |= HFSPLUS_XATTR_EXISTS;
+ if (!strcmp_xattr_acl(name))
+ cat_entry_flags |= HFSPLUS_ACL_EXISTS;
+ hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, flags),
+ cat_entry_flags);
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
+ } else if (cat_entry_type == HFSPLUS_FILE) {
+ cat_entry_flags = hfs_bnode_read_u16(cat_fd.bnode,
+ cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, flags));
+ cat_entry_flags |= HFSPLUS_XATTR_EXISTS;
+ if (!strcmp_xattr_acl(name))
+ cat_entry_flags |= HFSPLUS_ACL_EXISTS;
+ hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, flags),
+ cat_entry_flags);
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
+ } else {
+ pr_err("invalid catalog entry type\n");
+ err = -EIO;
+ goto end_setxattr;
+ }
+
+ inode_set_ctime_current(inode);
+
+end_setxattr:
+ hfs_find_exit(&cat_fd);
+ hfs_dbg("finished: res %d\n", err);
+ return err;
+}
+
+static size_t name_len(const char *xattr_name, size_t xattr_name_len)
+{
+ size_t len = xattr_name_len + 1;
+
+ if (!is_known_namespace(xattr_name))
+ len += XATTR_MAC_OSX_PREFIX_LEN;
+
+ return len;
+}
+
+static ssize_t copy_name(char *buffer, const char *xattr_name, size_t name_len)
+{
+ ssize_t len;
+
+ memset(buffer, 0, name_len);
+
+ if (!is_known_namespace(xattr_name)) {
+ len = scnprintf(buffer, name_len + XATTR_MAC_OSX_PREFIX_LEN,
+ "%s%s", XATTR_MAC_OSX_PREFIX, xattr_name);
+ } else {
+ len = strscpy(buffer, xattr_name, name_len + 1);
+ if (len < 0) {
+ pr_err("fail to copy name: err %zd\n", len);
+ len = 0;
+ }
+ }
+
+ /* include NUL-byte in length for non-empty name */
+ if (len >= 0)
+ len++;
+ return len;
+}
+
+int hfsplus_setxattr(struct inode *inode, const char *name,
+ const void *value, size_t size, int flags,
+ const char *prefix, size_t prefixlen)
+{
+ char *xattr_name;
+ size_t xattr_name_len =
+ NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1;
+ int res;
+
+ hfs_dbg("ino %llu, name %s, prefix %s, prefixlen %zu, "
+ "value %p, size %zu\n",
+ inode->i_ino, name ? name : NULL,
+ prefix ? prefix : NULL, prefixlen,
+ value, size);
+
+ xattr_name = kmalloc(xattr_name_len, GFP_KERNEL);
+ if (!xattr_name)
+ return -ENOMEM;
+ strcpy(xattr_name, prefix);
+ strcpy(xattr_name + prefixlen, name);
+ res = __hfsplus_setxattr(inode, xattr_name, value, size, flags);
+ kfree(xattr_name);
+
+ hfs_dbg("finished: res %d\n", res);
+
+ return res;
+}
+
+static ssize_t hfsplus_getxattr_finder_info(struct inode *inode,
+ void *value, size_t size)
+{
+ ssize_t res = 0;
+ struct hfs_find_data fd;
+ u16 entry_type;
+ u16 folder_rec_len = sizeof(DInfo) + sizeof(DXInfo);
+ u16 file_rec_len = sizeof(FInfo) + sizeof(FXInfo);
+ u16 record_len = max(folder_rec_len, file_rec_len);
+ u8 folder_finder_info[sizeof(DInfo) + sizeof(DXInfo)];
+ u8 file_finder_info[sizeof(FInfo) + sizeof(FXInfo)];
+
+ if (size >= record_len) {
+ res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd);
+ if (res) {
+ pr_err("can't init xattr find struct\n");
+ return res;
+ }
+ res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
+ if (res)
+ goto end_getxattr_finder_info;
+ entry_type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset);
+
+ if (entry_type == HFSPLUS_FOLDER) {
+ hfs_bnode_read(fd.bnode, folder_finder_info,
+ fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, user_info),
+ folder_rec_len);
+ memcpy(value, folder_finder_info, folder_rec_len);
+ res = folder_rec_len;
+ } else if (entry_type == HFSPLUS_FILE) {
+ hfs_bnode_read(fd.bnode, file_finder_info,
+ fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, user_info),
+ file_rec_len);
+ memcpy(value, file_finder_info, file_rec_len);
+ res = file_rec_len;
+ } else {
+ res = -EOPNOTSUPP;
+ goto end_getxattr_finder_info;
+ }
+ } else
+ res = size ? -ERANGE : record_len;
+
+end_getxattr_finder_info:
+ if (size >= record_len)
+ hfs_find_exit(&fd);
+ return res;
+}
+
+ssize_t __hfsplus_getxattr(struct inode *inode, const char *name,
+ void *value, size_t size)
+{
+ struct hfs_find_data fd;
+ hfsplus_attr_entry *entry;
+ __be32 xattr_record_type;
+ u32 record_type;
+ u16 record_length = 0;
+ ssize_t res;
+
+ if (!is_xattr_operation_supported(inode))
+ return -EOPNOTSUPP;
+
+ if (!strcmp_xattr_finder_info(name))
+ return hfsplus_getxattr_finder_info(inode, value, size);
+
+ if (!HFSPLUS_SB(inode->i_sb)->attr_tree)
+ return -EOPNOTSUPP;
+
+ entry = hfsplus_alloc_attr_entry();
+ if (!entry) {
+ pr_err("can't allocate xattr entry\n");
+ return -ENOMEM;
+ }
+
+ res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->attr_tree, &fd);
+ if (res) {
+ pr_err("can't init xattr find struct\n");
+ goto failed_getxattr_init;
+ }
+
+ res = hfsplus_find_attr(inode->i_sb, inode->i_ino, name, &fd);
+ if (res) {
+ if (res == -ENOENT || res == -ENODATA)
+ res = -ENODATA;
+ else
+ pr_err("xattr search failed\n");
+ goto out;
+ }
+
+ hfs_bnode_read(fd.bnode, &xattr_record_type,
+ fd.entryoffset, sizeof(xattr_record_type));
+ record_type = be32_to_cpu(xattr_record_type);
+ if (record_type == HFSPLUS_ATTR_INLINE_DATA) {
+ record_length = hfs_bnode_read_u16(fd.bnode,
+ fd.entryoffset +
+ offsetof(struct hfsplus_attr_inline_data,
+ length));
+ if (record_length > HFSPLUS_MAX_INLINE_DATA_SIZE) {
+ pr_err("invalid xattr record size\n");
+ res = -EIO;
+ goto out;
+ }
+ } else if (record_type == HFSPLUS_ATTR_FORK_DATA ||
+ record_type == HFSPLUS_ATTR_EXTENTS) {
+ pr_err("only inline data xattr are supported\n");
+ res = -EOPNOTSUPP;
+ goto out;
+ } else {
+ pr_err("invalid xattr record\n");
+ res = -EIO;
+ goto out;
+ }
+
+ if (size) {
+ hfs_bnode_read(fd.bnode, entry, fd.entryoffset,
+ offsetof(struct hfsplus_attr_inline_data,
+ raw_bytes) + record_length);
+ }
+
+ if (size >= record_length) {
+ memcpy(value, entry->inline_data.raw_bytes, record_length);
+ res = record_length;
+ } else
+ res = size ? -ERANGE : record_length;
+
+out:
+ hfs_find_exit(&fd);
+
+failed_getxattr_init:
+ hfsplus_destroy_attr_entry(entry);
+ return res;
+}
+
+ssize_t hfsplus_getxattr(struct inode *inode, const char *name,
+ void *value, size_t size,
+ const char *prefix, size_t prefixlen)
+{
+ int res;
+ char *xattr_name;
+
+ hfs_dbg("ino %llu, name %s, prefix %s\n",
+ inode->i_ino, name ? name : NULL,
+ prefix ? prefix : NULL);
+
+ xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1,
+ GFP_KERNEL);
+ if (!xattr_name)
+ return -ENOMEM;
+
+ strcpy(xattr_name, prefix);
+ strcpy(xattr_name + prefixlen, name);
+
+ res = __hfsplus_getxattr(inode, xattr_name, value, size);
+ kfree(xattr_name);
+
+ hfs_dbg("finished: res %d\n", res);
+
+ return res;
+
+}
+
+static inline int can_list(const char *xattr_name)
+{
+ if (!xattr_name)
+ return 0;
+
+ return strncmp(xattr_name, XATTR_TRUSTED_PREFIX,
+ XATTR_TRUSTED_PREFIX_LEN) ||
+ capable(CAP_SYS_ADMIN);
+}
+
+static ssize_t hfsplus_listxattr_finder_info(struct dentry *dentry,
+ char *buffer, size_t size)
+{
+ ssize_t res;
+ struct inode *inode = d_inode(dentry);
+ struct hfs_find_data fd;
+ u16 entry_type;
+ u8 folder_finder_info[sizeof(DInfo) + sizeof(DXInfo)];
+ u8 file_finder_info[sizeof(FInfo) + sizeof(FXInfo)];
+ unsigned long len, found_bit;
+ int xattr_name_len, symbols_count;
+
+ res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd);
+ if (res) {
+ pr_err("can't init xattr find struct\n");
+ return res;
+ }
+
+ res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
+ if (res)
+ goto end_listxattr_finder_info;
+
+ entry_type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset);
+ if (entry_type == HFSPLUS_FOLDER) {
+ len = sizeof(DInfo) + sizeof(DXInfo);
+ hfs_bnode_read(fd.bnode, folder_finder_info,
+ fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, user_info),
+ len);
+ found_bit = find_first_bit((void *)folder_finder_info, len*8);
+ } else if (entry_type == HFSPLUS_FILE) {
+ len = sizeof(FInfo) + sizeof(FXInfo);
+ hfs_bnode_read(fd.bnode, file_finder_info,
+ fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, user_info),
+ len);
+ found_bit = find_first_bit((void *)file_finder_info, len*8);
+ } else {
+ res = -EOPNOTSUPP;
+ goto end_listxattr_finder_info;
+ }
+
+ if (found_bit >= (len*8))
+ res = 0;
+ else {
+ symbols_count = sizeof(HFSPLUS_XATTR_FINDER_INFO_NAME) - 1;
+ xattr_name_len =
+ name_len(HFSPLUS_XATTR_FINDER_INFO_NAME, symbols_count);
+ if (!buffer || !size) {
+ if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME))
+ res = xattr_name_len;
+ } else if (can_list(HFSPLUS_XATTR_FINDER_INFO_NAME)) {
+ if (size < xattr_name_len)
+ res = -ERANGE;
+ else {
+ res = copy_name(buffer,
+ HFSPLUS_XATTR_FINDER_INFO_NAME,
+ symbols_count);
+ }
+ }
+ }
+
+end_listxattr_finder_info:
+ hfs_find_exit(&fd);
+
+ return res;
+}
+
+ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ ssize_t err;
+ ssize_t res;
+ struct inode *inode = d_inode(dentry);
+ struct hfs_find_data fd;
+ struct hfsplus_attr_key attr_key;
+ char *strbuf;
+ size_t strbuf_size;
+ int xattr_name_len;
+
+ hfs_dbg("ino %llu\n", inode->i_ino);
+
+ if (!is_xattr_operation_supported(inode))
+ return -EOPNOTSUPP;
+
+ res = hfsplus_listxattr_finder_info(dentry, buffer, size);
+ if (res < 0)
+ return res;
+ else if (!HFSPLUS_SB(inode->i_sb)->attr_tree)
+ return (res == 0) ? -EOPNOTSUPP : res;
+
+ err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->attr_tree, &fd);
+ if (err) {
+ pr_err("can't init xattr find struct\n");
+ return err;
+ }
+
+ strbuf_size = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN +
+ XATTR_MAC_OSX_PREFIX_LEN + 1;
+ strbuf = kzalloc(strbuf_size, GFP_KERNEL);
+ if (!strbuf) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ err = hfsplus_find_attr(inode->i_sb, inode->i_ino, NULL, &fd);
+ if (err) {
+ if (err == -ENOENT || err == -ENODATA) {
+ res = 0;
+ goto end_listxattr;
+ } else {
+ res = err;
+ goto end_listxattr;
+ }
+ }
+
+ for (;;) {
+ u16 key_len = hfs_bnode_read_u16(fd.bnode, fd.keyoffset);
+
+ if (key_len == 0 || key_len > fd.tree->max_key_len) {
+ pr_err("invalid xattr key length: %d\n", key_len);
+ res = -EIO;
+ goto end_listxattr;
+ }
+
+ hfs_bnode_read(fd.bnode, &attr_key,
+ fd.keyoffset, key_len + sizeof(key_len));
+
+ if (be32_to_cpu(attr_key.cnid) != inode->i_ino)
+ goto end_listxattr;
+
+ xattr_name_len = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN;
+ if (hfsplus_uni2asc_xattr_str(inode->i_sb,
+ &fd.key->attr.key_name, strbuf,
+ &xattr_name_len)) {
+ pr_err("unicode conversion failed\n");
+ res = -EIO;
+ goto end_listxattr;
+ }
+
+ if (!buffer || !size) {
+ if (can_list(strbuf))
+ res += name_len(strbuf, xattr_name_len);
+ } else if (can_list(strbuf)) {
+ if (size < (res + name_len(strbuf, xattr_name_len))) {
+ pr_err("size %zu, res %zd, name_len %zu\n",
+ size, res,
+ name_len(strbuf, xattr_name_len));
+ res = -ERANGE;
+ goto end_listxattr;
+ } else
+ res += copy_name(buffer + res,
+ strbuf, xattr_name_len);
+ }
+
+ memset(fd.key->attr.key_name.unicode, 0,
+ sizeof(fd.key->attr.key_name.unicode));
+ memset(strbuf, 0, strbuf_size);
+
+ if (hfs_brec_goto(&fd, 1))
+ goto end_listxattr;
+ }
+
+end_listxattr:
+ kfree(strbuf);
+out:
+ hfs_find_exit(&fd);
+
+ hfs_dbg("finished: res %zd\n", res);
+
+ return res;
+}
+
+static int hfsplus_removexattr(struct inode *inode, const char *name)
+{
+ int err;
+ struct hfs_find_data cat_fd;
+ u16 flags;
+ u16 cat_entry_type;
+ int is_xattr_acl_deleted;
+ int is_all_xattrs_deleted;
+
+ hfs_dbg("ino %llu, name %s\n",
+ inode->i_ino, name ? name : NULL);
+
+ if (!HFSPLUS_SB(inode->i_sb)->attr_tree)
+ return -EOPNOTSUPP;
+
+ if (!strcmp_xattr_finder_info(name))
+ return -EOPNOTSUPP;
+
+ err = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &cat_fd);
+ if (err) {
+ pr_err("can't init xattr find struct\n");
+ return err;
+ }
+
+ err = hfsplus_find_cat(inode->i_sb, inode->i_ino, &cat_fd);
+ if (err) {
+ pr_err("catalog searching failed\n");
+ goto end_removexattr;
+ }
+
+ err = hfsplus_delete_attr(inode, name);
+ if (err)
+ goto end_removexattr;
+
+ is_xattr_acl_deleted = !strcmp_xattr_acl(name);
+ is_all_xattrs_deleted = !hfsplus_attr_exists(inode, NULL);
+
+ if (!is_xattr_acl_deleted && !is_all_xattrs_deleted)
+ goto end_removexattr;
+
+ cat_entry_type = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset);
+
+ if (cat_entry_type == HFSPLUS_FOLDER) {
+ flags = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, flags));
+ if (is_xattr_acl_deleted)
+ flags &= ~HFSPLUS_ACL_EXISTS;
+ if (is_all_xattrs_deleted)
+ flags &= ~HFSPLUS_XATTR_EXISTS;
+ hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_folder, flags),
+ flags);
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
+ } else if (cat_entry_type == HFSPLUS_FILE) {
+ flags = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, flags));
+ if (is_xattr_acl_deleted)
+ flags &= ~HFSPLUS_ACL_EXISTS;
+ if (is_all_xattrs_deleted)
+ flags &= ~HFSPLUS_XATTR_EXISTS;
+ hfs_bnode_write_u16(cat_fd.bnode, cat_fd.entryoffset +
+ offsetof(struct hfsplus_cat_file, flags),
+ flags);
+ hfsplus_mark_inode_dirty(HFSPLUS_CAT_TREE_I(inode->i_sb),
+ HFSPLUS_I_CAT_DIRTY);
+ hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
+ } else {
+ pr_err("invalid catalog entry type\n");
+ err = -EIO;
+ goto end_removexattr;
+ }
+
+ inode_set_ctime_current(inode);
+
+end_removexattr:
+ hfs_find_exit(&cat_fd);
+
+ hfs_dbg("finished: err %d\n", err);
+
+ return err;
+}
+
+static int hfsplus_osx_getxattr(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode,
+ const char *name, void *buffer, size_t size)
+{
+ /*
+ * Don't allow retrieving properly prefixed attributes
+ * by prepending them with "osx."
+ */
+ if (is_known_namespace(name))
+ return -EOPNOTSUPP;
+
+ /*
+ * osx is the namespace we use to indicate an unprefixed
+ * attribute on the filesystem (like the ones that OS X
+ * creates), so we pass the name through unmodified (after
+ * ensuring it doesn't conflict with another namespace).
+ */
+ return __hfsplus_getxattr(inode, name, buffer, size);
+}
+
+static int hfsplus_osx_setxattr(const struct xattr_handler *handler,
+ struct mnt_idmap *idmap,
+ struct dentry *unused, struct inode *inode,
+ const char *name, const void *buffer,
+ size_t size, int flags)
+{
+ /*
+ * Don't allow setting properly prefixed attributes
+ * by prepending them with "osx."
+ */
+ if (is_known_namespace(name))
+ return -EOPNOTSUPP;
+
+ /*
+ * osx is the namespace we use to indicate an unprefixed
+ * attribute on the filesystem (like the ones that OS X
+ * creates), so we pass the name through unmodified (after
+ * ensuring it doesn't conflict with another namespace).
+ */
+ return __hfsplus_setxattr(inode, name, buffer, size, flags);
+}
+
+const struct xattr_handler hfsplus_xattr_osx_handler = {
+ .prefix = XATTR_MAC_OSX_PREFIX,
+ .get = hfsplus_osx_getxattr,
+ .set = hfsplus_osx_setxattr,
+};
diff --git a/fs/hfsplus/xattr.h b/fs/hfsplus/xattr.h
new file mode 100644
index 000000000..15cc55e41
--- /dev/null
+++ b/fs/hfsplus/xattr.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/fs/hfsplus/xattr.h
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Logic of processing extended attributes
+ */
+
+#ifndef _LINUX_HFSPLUS_XATTR_H
+#define _LINUX_HFSPLUS_XATTR_H
+
+#include <linux/xattr.h>
+
+extern const struct xattr_handler hfsplus_xattr_osx_handler;
+extern const struct xattr_handler hfsplus_xattr_user_handler;
+extern const struct xattr_handler hfsplus_xattr_trusted_handler;
+extern const struct xattr_handler hfsplus_xattr_security_handler;
+
+extern const struct xattr_handler * const hfsplus_xattr_handlers[];
+
+int __hfsplus_setxattr(struct inode *inode, const char *name,
+ const void *value, size_t size, int flags);
+
+int hfsplus_setxattr(struct inode *inode, const char *name,
+ const void *value, size_t size, int flags,
+ const char *prefix, size_t prefixlen);
+
+ssize_t __hfsplus_getxattr(struct inode *inode, const char *name,
+ void *value, size_t size);
+
+ssize_t hfsplus_getxattr(struct inode *inode, const char *name,
+ void *value, size_t size,
+ const char *prefix, size_t prefixlen);
+
+ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size);
+
+int hfsplus_init_security(struct inode *inode, struct inode *dir,
+ const struct qstr *qstr);
+
+#endif
diff --git a/fs/hfsplus/xattr_security.c b/fs/hfsplus/xattr_security.c
new file mode 100644
index 000000000..90f68ec11
--- /dev/null
+++ b/fs/hfsplus/xattr_security.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/xattr_trusted.c
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Handler for storing security labels as extended attributes.
+ */
+
+#include <linux/security.h>
+#include <linux/nls.h>
+
+#include "hfsplus_fs.h"
+#include "xattr.h"
+
+static int hfsplus_security_getxattr(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode,
+ const char *name, void *buffer, size_t size)
+{
+ return hfsplus_getxattr(inode, name, buffer, size,
+ XATTR_SECURITY_PREFIX,
+ XATTR_SECURITY_PREFIX_LEN);
+}
+
+static int hfsplus_security_setxattr(const struct xattr_handler *handler,
+ struct mnt_idmap *idmap,
+ struct dentry *unused, struct inode *inode,
+ const char *name, const void *buffer,
+ size_t size, int flags)
+{
+ return hfsplus_setxattr(inode, name, buffer, size, flags,
+ XATTR_SECURITY_PREFIX,
+ XATTR_SECURITY_PREFIX_LEN);
+}
+
+static int hfsplus_initxattrs(struct inode *inode,
+ const struct xattr *xattr_array,
+ void *fs_info)
+{
+ const struct xattr *xattr;
+ char *xattr_name;
+ int err = 0;
+
+ xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1,
+ GFP_KERNEL);
+ if (!xattr_name)
+ return -ENOMEM;
+ for (xattr = xattr_array; xattr->name != NULL; xattr++) {
+
+ if (!strcmp(xattr->name, ""))
+ continue;
+
+ strcpy(xattr_name, XATTR_SECURITY_PREFIX);
+ strcpy(xattr_name +
+ XATTR_SECURITY_PREFIX_LEN, xattr->name);
+ memset(xattr_name +
+ XATTR_SECURITY_PREFIX_LEN + strlen(xattr->name), 0, 1);
+
+ err = __hfsplus_setxattr(inode, xattr_name,
+ xattr->value, xattr->value_len, 0);
+ if (err)
+ break;
+ }
+ kfree(xattr_name);
+ return err;
+}
+
+int hfsplus_init_security(struct inode *inode, struct inode *dir,
+ const struct qstr *qstr)
+{
+ return security_inode_init_security(inode, dir, qstr,
+ &hfsplus_initxattrs, NULL);
+}
+
+const struct xattr_handler hfsplus_xattr_security_handler = {
+ .prefix = XATTR_SECURITY_PREFIX,
+ .get = hfsplus_security_getxattr,
+ .set = hfsplus_security_setxattr,
+};
diff --git a/fs/hfsplus/xattr_trusted.c b/fs/hfsplus/xattr_trusted.c
new file mode 100644
index 000000000..fdbaebc1c
--- /dev/null
+++ b/fs/hfsplus/xattr_trusted.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/xattr_trusted.c
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Handler for trusted extended attributes.
+ */
+
+#include <linux/nls.h>
+
+#include "hfsplus_fs.h"
+#include "xattr.h"
+
+static int hfsplus_trusted_getxattr(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode,
+ const char *name, void *buffer, size_t size)
+{
+ return hfsplus_getxattr(inode, name, buffer, size,
+ XATTR_TRUSTED_PREFIX,
+ XATTR_TRUSTED_PREFIX_LEN);
+}
+
+static int hfsplus_trusted_setxattr(const struct xattr_handler *handler,
+ struct mnt_idmap *idmap,
+ struct dentry *unused, struct inode *inode,
+ const char *name, const void *buffer,
+ size_t size, int flags)
+{
+ return hfsplus_setxattr(inode, name, buffer, size, flags,
+ XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN);
+}
+
+const struct xattr_handler hfsplus_xattr_trusted_handler = {
+ .prefix = XATTR_TRUSTED_PREFIX,
+ .get = hfsplus_trusted_getxattr,
+ .set = hfsplus_trusted_setxattr,
+};
diff --git a/fs/hfsplus/xattr_user.c b/fs/hfsplus/xattr_user.c
new file mode 100644
index 000000000..6464b6c3d
--- /dev/null
+++ b/fs/hfsplus/xattr_user.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/xattr_user.c
+ *
+ * Vyacheslav Dubeyko <slava@dubeyko.com>
+ *
+ * Handler for user extended attributes.
+ */
+
+#include <linux/nls.h>
+
+#include "hfsplus_fs.h"
+#include "xattr.h"
+
+static int hfsplus_user_getxattr(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode,
+ const char *name, void *buffer, size_t size)
+{
+
+ return hfsplus_getxattr(inode, name, buffer, size,
+ XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
+}
+
+static int hfsplus_user_setxattr(const struct xattr_handler *handler,
+ struct mnt_idmap *idmap,
+ struct dentry *unused, struct inode *inode,
+ const char *name, const void *buffer,
+ size_t size, int flags)
+{
+ return hfsplus_setxattr(inode, name, buffer, size, flags,
+ XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
+}
+
+const struct xattr_handler hfsplus_xattr_user_handler = {
+ .prefix = XATTR_USER_PREFIX,
+ .get = hfsplus_user_getxattr,
+ .set = hfsplus_user_setxattr,
+};