summaryrefslogtreecommitdiffstats
path: root/fs/hfs
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-09-30 09:59:20 +0000
committerKumar Kartikeya Dwivedi <memxor@gmail.com>2026-10-01 18:40:05 +0200
commitaeb709c767aeca996e6011133e4f7bdb2a4af652 (patch)
treec1c2bdbb4b6b7174a5bbe9e4c2c1f1c0204adcb5 /fs/hfs
downloadlinux-stable-aeb709c767aeca996e6011133e4f7bdb2a4af652.tar.gz
linux-stable-aeb709c767aeca996e6011133e4f7bdb2a4af652.zip
selftests/bpf: Add a test for objects stuck in free_by_rcu_ttracegrafted
Delete all elements of BPF_F_NO_PREALLOC hash map in one batch. The first free_bulk() starts RCU tasks trace GP and the rest of the elements are freed while it's in flight. Wait for call_rcu_ttrace_in_progress to clear in bpf_mem_cache of every cpu and check that free_by_rcu_ttrace and waiting_for_gp_ttrace lists are empty. Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20260930095920.601738-4-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Diffstat (limited to 'fs/hfs')
-rw-r--r--fs/hfs/.kunitconfig7
-rw-r--r--fs/hfs/Kconfig30
-rw-r--r--fs/hfs/Makefile13
-rw-r--r--fs/hfs/attr.c153
-rw-r--r--fs/hfs/bfind.c246
-rw-r--r--fs/hfs/bitmap.c243
-rw-r--r--fs/hfs/bnode.c542
-rw-r--r--fs/hfs/brec.c548
-rw-r--r--fs/hfs/btree.c544
-rw-r--r--fs/hfs/btree.h171
-rw-r--r--fs/hfs/catalog.c536
-rw-r--r--fs/hfs/dir.c331
-rw-r--r--fs/hfs/extent.c552
-rw-r--r--fs/hfs/hfs.h21
-rw-r--r--fs/hfs/hfs_fs.h319
-rw-r--r--fs/hfs/inode.c752
-rw-r--r--fs/hfs/mdb.c462
-rw-r--r--fs/hfs/part_tbl.c117
-rw-r--r--fs/hfs/string.c119
-rw-r--r--fs/hfs/string_test.c133
-rw-r--r--fs/hfs/super.c503
-rw-r--r--fs/hfs/sysdep.c50
-rw-r--r--fs/hfs/trans.c150
23 files changed, 6542 insertions, 0 deletions
diff --git a/fs/hfs/.kunitconfig b/fs/hfs/.kunitconfig
new file mode 100644
index 000000000..5caa9af1e
--- /dev/null
+++ b/fs/hfs/.kunitconfig
@@ -0,0 +1,7 @@
+CONFIG_KUNIT=y
+CONFIG_HFS_FS=y
+CONFIG_HFS_KUNIT_TEST=y
+CONFIG_BLOCK=y
+CONFIG_BUFFER_HEAD=y
+CONFIG_NLS=y
+CONFIG_LEGACY_DIRECT_IO=y
diff --git a/fs/hfs/Kconfig b/fs/hfs/Kconfig
new file mode 100644
index 000000000..7f3cbe43b
--- /dev/null
+++ b/fs/hfs/Kconfig
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config HFS_FS
+ tristate "Apple Macintosh file system support"
+ depends on BLOCK
+ select BUFFER_HEAD
+ select NLS
+ select LEGACY_DIRECT_IO
+ help
+ If you say Y here, you will be able to mount Macintosh-formatted
+ floppy disks and hard drive partitions with full read-write access.
+ Please read <file:Documentation/filesystems/hfs.rst> to learn about
+ the available mount options.
+
+ To compile this file system support as a module, choose M here: the
+ module will be called hfs.
+
+config HFS_KUNIT_TEST
+ tristate "KUnit tests for HFS filesystem" if !KUNIT_ALL_TESTS
+ depends on HFS_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/hfs/Makefile b/fs/hfs/Makefile
new file mode 100644
index 000000000..a7c9ce6b4
--- /dev/null
+++ b/fs/hfs/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the Linux hfs filesystem routines.
+#
+
+obj-$(CONFIG_HFS_FS) += hfs.o
+
+hfs-objs := bitmap.o bfind.o bnode.o brec.o btree.o \
+ catalog.o dir.o extent.o inode.o attr.o mdb.o \
+ part_tbl.o string.o super.o sysdep.o trans.o
+
+# KUnit tests
+obj-$(CONFIG_HFS_KUNIT_TEST) += string_test.o
diff --git a/fs/hfs/attr.c b/fs/hfs/attr.c
new file mode 100644
index 000000000..f8395cdd1
--- /dev/null
+++ b/fs/hfs/attr.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfs/attr.c
+ *
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Export hfs data via xattr
+ */
+
+
+#include <linux/fs.h>
+#include <linux/xattr.h>
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+enum hfs_xattr_type {
+ HFS_TYPE,
+ HFS_CREATOR,
+};
+
+static int __hfs_setxattr(struct inode *inode, enum hfs_xattr_type type,
+ const void *value, size_t size, int flags)
+{
+ struct hfs_find_data fd;
+ hfs_cat_rec rec;
+ struct hfs_cat_file *file;
+ int res;
+
+ if (!S_ISREG(inode->i_mode) || HFS_IS_RSRC(inode))
+ return -EOPNOTSUPP;
+
+ res = hfs_find_init(HFS_SB(inode->i_sb)->cat_tree, &fd);
+ if (res)
+ return res;
+ fd.search_key->cat = HFS_I(inode)->cat_key;
+ res = hfs_brec_find(&fd);
+ if (res)
+ goto out;
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ file = &rec.file;
+
+ switch (type) {
+ case HFS_TYPE:
+ if (size == 4)
+ memcpy(&file->UsrWds.fdType, value, 4);
+ else
+ res = -ERANGE;
+ break;
+
+ case HFS_CREATOR:
+ if (size == 4)
+ memcpy(&file->UsrWds.fdCreator, value, 4);
+ else
+ res = -ERANGE;
+ break;
+ }
+
+ if (!res)
+ hfs_bnode_write(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+out:
+ hfs_find_exit(&fd);
+ return res;
+}
+
+static ssize_t __hfs_getxattr(struct inode *inode, enum hfs_xattr_type type,
+ void *value, size_t size)
+{
+ struct hfs_find_data fd;
+ hfs_cat_rec rec;
+ struct hfs_cat_file *file;
+ ssize_t res = 0;
+
+ if (!S_ISREG(inode->i_mode) || HFS_IS_RSRC(inode))
+ return -EOPNOTSUPP;
+
+ if (size) {
+ res = hfs_find_init(HFS_SB(inode->i_sb)->cat_tree, &fd);
+ if (res)
+ return res;
+ fd.search_key->cat = HFS_I(inode)->cat_key;
+ res = hfs_brec_find(&fd);
+ if (res)
+ goto out;
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ }
+ file = &rec.file;
+
+ switch (type) {
+ case HFS_TYPE:
+ if (size >= 4) {
+ memcpy(value, &file->UsrWds.fdType, 4);
+ res = 4;
+ } else
+ res = size ? -ERANGE : 4;
+ break;
+
+ case HFS_CREATOR:
+ if (size >= 4) {
+ memcpy(value, &file->UsrWds.fdCreator, 4);
+ res = 4;
+ } else
+ res = size ? -ERANGE : 4;
+ break;
+ }
+
+out:
+ if (size)
+ hfs_find_exit(&fd);
+ return res;
+}
+
+static int hfs_xattr_get(const struct xattr_handler *handler,
+ struct dentry *unused, struct inode *inode,
+ const char *name, void *value, size_t size)
+{
+ return __hfs_getxattr(inode, handler->flags, value, size);
+}
+
+static int hfs_xattr_set(const struct xattr_handler *handler,
+ struct mnt_idmap *idmap,
+ struct dentry *unused, struct inode *inode,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ if (!value)
+ return -EOPNOTSUPP;
+
+ return __hfs_setxattr(inode, handler->flags, value, size, flags);
+}
+
+static const struct xattr_handler hfs_creator_handler = {
+ .name = "hfs.creator",
+ .flags = HFS_CREATOR,
+ .get = hfs_xattr_get,
+ .set = hfs_xattr_set,
+};
+
+static const struct xattr_handler hfs_type_handler = {
+ .name = "hfs.type",
+ .flags = HFS_TYPE,
+ .get = hfs_xattr_get,
+ .set = hfs_xattr_set,
+};
+
+const struct xattr_handler * const hfs_xattr_handlers[] = {
+ &hfs_creator_handler,
+ &hfs_type_handler,
+ NULL
+};
diff --git a/fs/hfs/bfind.c b/fs/hfs/bfind.c
new file mode 100644
index 000000000..d56e47bdc
--- /dev/null
+++ b/fs/hfs/bfind.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfs/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 "btree.h"
+
+int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
+{
+ void *ptr;
+
+ if (!tree || !fd)
+ return -EINVAL;
+
+ fd->tree = tree;
+ fd->bnode = NULL;
+ 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));
+ switch (tree->cnid) {
+ case HFS_CAT_CNID:
+ mutex_lock_nested(&tree->tree_lock, CATALOG_BTREE_MUTEX);
+ break;
+ case HFS_EXT_CNID:
+ mutex_lock_nested(&tree->tree_lock, EXTENTS_BTREE_MUTEX);
+ break;
+ case HFS_ATTR_CNID:
+ mutex_lock_nested(&tree->tree_lock, ATTR_BTREE_MUTEX);
+ break;
+ default:
+ return -EINVAL;
+ }
+ 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;
+}
+
+/* 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)
+{
+ int cmpval;
+ u16 off, len, keylen;
+ int rec;
+ int b, e;
+ int res;
+
+ b = 0;
+ e = bnode->num_recs - 1;
+ res = -ENOENT;
+ do {
+ rec = (e + b) / 2;
+ len = hfs_brec_lenoff(bnode, rec, &off);
+ keylen = hfs_brec_keylen(bnode, rec);
+ if (keylen == 0) {
+ res = -EINVAL;
+ goto fail;
+ }
+ hfs_bnode_read(bnode, fd->key, off, keylen);
+ cmpval = bnode->tree->keycmp(fd->key, fd->search_key);
+ if (!cmpval) {
+ e = rec;
+ res = 0;
+ goto done;
+ }
+ if (cmpval < 0)
+ b = rec + 1;
+ else
+ e = rec - 1;
+ } while (b <= e);
+ if (rec != e && e >= 0) {
+ len = hfs_brec_lenoff(bnode, e, &off);
+ keylen = hfs_brec_keylen(bnode, e);
+ if (keylen == 0) {
+ 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)
+{
+ struct hfs_btree *tree;
+ struct hfs_bnode *bnode;
+ u32 nidx, parent;
+ __be32 data;
+ int height, res;
+
+ fd->record = -1;
+ fd->keyoffset = -1;
+ fd->keylength = -1;
+ fd->entryoffset = -1;
+ fd->entrylength = -1;
+
+ 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);
+ 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);
+ 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 (keylen == 0) {
+ 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;
+}
diff --git a/fs/hfs/bitmap.c b/fs/hfs/bitmap.c
new file mode 100644
index 000000000..5e84833a4
--- /dev/null
+++ b/fs/hfs/bitmap.c
@@ -0,0 +1,243 @@
+/*
+ * linux/fs/hfs/bitmap.c
+ *
+ * Copyright (C) 1996-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * Based on GPLed code Copyright (C) 1995 Michael Dreher
+ *
+ * This file contains the code to modify the volume bitmap:
+ * search/set/clear bits.
+ */
+
+#include "hfs_fs.h"
+
+/*
+ * hfs_find_zero_bit()
+ *
+ * Description:
+ * Given a block of memory, its length in bits, and a starting bit number,
+ * determine the number of the first zero bits (in left-to-right ordering)
+ * in that range.
+ *
+ * Returns >= 'size' if no zero bits are found in the range.
+ *
+ * Accesses memory in 32-bit aligned chunks of 32-bits and thus
+ * may read beyond the 'size'th bit.
+ */
+static u32 hfs_find_set_zero_bits(__be32 *bitmap, u32 size, u32 offset, u32 *max)
+{
+ __be32 *curr, *end;
+ u32 mask, start, len, n;
+ __be32 val;
+ int i;
+
+ len = *max;
+ if (!len)
+ return size;
+
+ curr = bitmap + (offset / 32);
+ end = bitmap + ((size + 31) / 32);
+
+ /* scan the first partial u32 for zero bits */
+ val = *curr;
+ if (~val) {
+ n = be32_to_cpu(val);
+ i = offset % 32;
+ mask = (1U << 31) >> i;
+ for (; i < 32; mask >>= 1, i++) {
+ if (!(n & mask))
+ goto found;
+ }
+ }
+
+ /* scan complete u32s for the first zero bit */
+ 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;
+ }
+ }
+ }
+ return size;
+
+found:
+ start = (curr - bitmap) * 32 + i;
+ if (start >= size)
+ return start;
+ /* 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) {
+ n = be32_to_cpu(*curr);
+ if (len < 32)
+ break;
+ if (n) {
+ len = 32;
+ break;
+ }
+ *curr++ = cpu_to_be32(0xffffffff);
+ len -= 32;
+ }
+ /* 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);
+ *max = (curr - bitmap) * 32 + i - start;
+ return start;
+}
+
+/*
+ * hfs_vbm_search_free()
+ *
+ * Description:
+ * Search for 'num_bits' consecutive cleared bits in the bitmap blocks of
+ * the hfs MDB. 'mdb' had better be locked or the returned range
+ * may be no longer free, when this functions returns!
+ * XXX Currently the search starts from bit 0, but it should start with
+ * the bit number stored in 's_alloc_ptr' of the MDB.
+ * Input Variable(s):
+ * struct hfs_mdb *mdb: Pointer to the hfs MDB
+ * u16 *num_bits: Pointer to the number of cleared bits
+ * to search for
+ * Output Variable(s):
+ * u16 *num_bits: The number of consecutive clear bits of the
+ * returned range. If the bitmap is fragmented, this will be less than
+ * requested and it will be zero, when the disk is full.
+ * Returns:
+ * The number of the first bit of the range of cleared bits which has been
+ * found. When 'num_bits' is zero, this is invalid!
+ * Preconditions:
+ * 'mdb' points to a "valid" (struct hfs_mdb).
+ * 'num_bits' points to a variable of type (u16), which contains
+ * the number of cleared bits to find.
+ * Postconditions:
+ * 'num_bits' is set to the length of the found sequence.
+ */
+u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits)
+{
+ void *bitmap;
+ u32 pos;
+
+ /* make sure we have actual work to perform */
+ if (!*num_bits)
+ return 0;
+
+ mutex_lock(&HFS_SB(sb)->bitmap_lock);
+ bitmap = HFS_SB(sb)->bitmap;
+
+ pos = hfs_find_set_zero_bits(bitmap, HFS_SB(sb)->fs_ablocks, goal, num_bits);
+ if (pos >= HFS_SB(sb)->fs_ablocks) {
+ if (goal)
+ pos = hfs_find_set_zero_bits(bitmap, goal, 0, num_bits);
+ if (pos >= HFS_SB(sb)->fs_ablocks) {
+ *num_bits = pos = 0;
+ goto out;
+ }
+ }
+
+ hfs_dbg("pos %u, num_bits %u\n", pos, *num_bits);
+ HFS_SB(sb)->free_ablocks -= *num_bits;
+ hfs_bitmap_dirty(sb);
+out:
+ mutex_unlock(&HFS_SB(sb)->bitmap_lock);
+ return pos;
+}
+
+
+/*
+ * hfs_clear_vbm_bits()
+ *
+ * Description:
+ * Clear the requested bits in the volume bitmap of the hfs filesystem
+ * Input Variable(s):
+ * struct hfs_mdb *mdb: Pointer to the hfs MDB
+ * u16 start: The offset of the first bit
+ * u16 count: The number of bits
+ * Output Variable(s):
+ * None
+ * Returns:
+ * 0: no error
+ * -1: One of the bits was already clear. This is a strange
+ * error and when it happens, the filesystem must be repaired!
+ * -2: One or more of the bits are out of range of the bitmap.
+ * Preconditions:
+ * 'mdb' points to a "valid" (struct hfs_mdb).
+ * Postconditions:
+ * Starting with bit number 'start', 'count' bits in the volume bitmap
+ * are cleared. The affected bitmap blocks are marked "dirty", the free
+ * block count of the MDB is updated and the MDB is marked dirty.
+ */
+int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count)
+{
+ __be32 *curr;
+ u32 mask;
+ int i, len;
+
+ /* is there any actual work to be done? */
+ if (!count)
+ return 0;
+
+ hfs_dbg("start %u, count %u\n", start, count);
+ /* are all of the bits in range? */
+ if ((start + count) > HFS_SB(sb)->fs_ablocks)
+ return -2;
+
+ mutex_lock(&HFS_SB(sb)->bitmap_lock);
+ /* bitmap is always on a 32-bit boundary */
+ curr = HFS_SB(sb)->bitmap + (start / 32);
+ len = count;
+
+ /* do any partial u32 at the start */
+ i = start % 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 (count >= 32) {
+ *curr++ = 0;
+ count -= 32;
+ }
+ /* do any partial u32 at end */
+ if (count) {
+ mask = 0xffffffffU >> count;
+ *curr &= cpu_to_be32(mask);
+ }
+out:
+ HFS_SB(sb)->free_ablocks += len;
+ mutex_unlock(&HFS_SB(sb)->bitmap_lock);
+ hfs_bitmap_dirty(sb);
+
+ return 0;
+}
diff --git a/fs/hfs/bnode.c b/fs/hfs/bnode.c
new file mode 100644
index 000000000..fcb5b9cd1
--- /dev/null
+++ b/fs/hfs/bnode.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfs/bnode.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle basic btree node operations
+ */
+
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+
+#include "btree.h"
+
+void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
+{
+ struct page *page;
+ u32 pagenum;
+ u32 bytes_read;
+ u32 bytes_to_read;
+
+ 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;
+ pagenum = off >> PAGE_SHIFT;
+ off &= ~PAGE_MASK; /* compute page offset for the first page */
+
+ for (bytes_read = 0; bytes_read < len; bytes_read += bytes_to_read) {
+ if (pagenum >= node->tree->pages_per_bnode)
+ break;
+ page = node->page[pagenum];
+ bytes_to_read = min_t(u32, len - bytes_read, PAGE_SIZE - off);
+
+ memcpy_from_page(buf + bytes_read, page, off, bytes_to_read);
+
+ pagenum++;
+ off = 0; /* page offset only applies to the first page */
+ }
+}
+
+u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off)
+{
+ __be16 data;
+ // 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;
+ // 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)
+ key_len = hfs_bnode_read_u8(node, off) + 1;
+ else
+ key_len = tree->max_key_len + 1;
+
+ if (key_len > sizeof(hfs_btree_key) || key_len < 1) {
+ memset(key, 0, sizeof(hfs_btree_key));
+ pr_err("hfs: 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 *page;
+
+ 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;
+ page = node->page[0];
+
+ memcpy_to_page(page, off, buf, len);
+ set_page_dirty(page);
+}
+
+void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data)
+{
+ __be16 v = cpu_to_be16(data);
+ // optimize later...
+ hfs_bnode_write(node, &v, off, 2);
+}
+
+void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data)
+{
+ // optimize later...
+ hfs_bnode_write(node, &data, off, 1);
+}
+
+void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len)
+{
+ struct page *page;
+
+ 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;
+ page = node->page[0];
+
+ memzero_page(page, off, len);
+ set_page_dirty(page);
+}
+
+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;
+
+ 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[0];
+ dst_page = dst_node->page[0];
+
+ memcpy_page(dst_page, dst, src_page, src, len);
+ set_page_dirty(dst_page);
+}
+
+void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len)
+{
+ struct page *page;
+ void *ptr;
+
+ 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;
+ page = node->page[0];
+ ptr = kmap_local_page(page);
+ memmove(ptr + dst, ptr + src, len);
+ kunmap_local(ptr);
+ set_page_dirty(page);
+}
+
+void hfs_bnode_dump(struct hfs_bnode *node)
+{
+ struct hfs_bnode_desc desc;
+ __be32 cnid;
+ int i, off, key_off;
+
+ hfs_dbg("node %d\n", node->this);
+ hfs_bnode_read(node, &desc, 0, sizeof(desc));
+ 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));
+
+ off = node->tree->node_size - 2;
+ for (i = be16_to_cpu(desc.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)
+ tmp = (hfs_bnode_read_u8(node, key_off) | 1) + 1;
+ else
+ tmp = node->tree->max_key_len + 1;
+ hfs_dbg(" (%d,%d",
+ tmp, hfs_bnode_read_u8(node, key_off));
+ 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_u8(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) {
+ printk(KERN_DEBUG "hfs_btree_del_level\n");
+ }
+ if (!node->parent) {
+ tree->root = 0;
+ tree->depth = 0;
+ }
+ set_bit(HFS_BNODE_DELETED, &node->flags);
+}
+
+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;
+ block = off >> PAGE_SHIFT;
+ node->page_offset = off & ~PAGE_MASK;
+ for (i = 0; i < tree->pages_per_bnode; 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);
+
+ 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 (next_off <= off ||
+ next_off > tree->node_size ||
+ next_off & 1)
+ 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_u8(node, off) + 1;
+ 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 node;
+ }
+ 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((int)PAGE_SIZE, (int)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);
+ hfs_bnode_clear(node, 0, tree->node_size);
+ hfs_bmap_free(node);
+ hfs_bnode_free(node);
+ return;
+ }
+ spin_unlock(&tree->hash_lock);
+ }
+}
diff --git a/fs/hfs/brec.c b/fs/hfs/brec.c
new file mode 100644
index 000000000..5a2f740dd
--- /dev/null
+++ b/fs/hfs/brec.c
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfs/brec.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle individual btree records
+ */
+
+#include "btree.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 *tree);
+
+/* 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 dataoff;
+
+ dataoff = node->tree->node_size - (rec + 2) * 2;
+ hfs_bnode_read(node, retval, dataoff, 4);
+ *off = be16_to_cpu(retval[1]);
+ return be16_to_cpu(retval[0]) - *off;
+}
+
+/* Get the length of the key from a keyed record */
+u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)
+{
+ u16 retval, recoff;
+
+ if (node->type != HFS_NODE_INDEX && node->type != HFS_NODE_LEAF)
+ return 0;
+
+ if ((node->type == HFS_NODE_INDEX) &&
+ !(node->tree->attributes & HFS_TREE_VARIDXKEYS)) {
+ if (node->tree->attributes & HFS_TREE_BIGKEYS)
+ retval = node->tree->max_key_len + 2;
+ else
+ retval = node->tree->max_key_len + 1;
+ } else {
+ recoff = hfs_bnode_read_u16(node, node->tree->node_size - (rec + 1) * 2);
+ if (!recoff)
+ return 0;
+ if (node->tree->attributes & HFS_TREE_BIGKEYS) {
+ 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 = 0;
+ }
+ } else {
+ retval = (hfs_bnode_read_u8(node, recoff) | 1) + 1;
+ if (retval > node->tree->max_key_len + 1) {
+ pr_err("keylen %d too large\n", retval);
+ retval = 0;
+ }
+ }
+ }
+ 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 = (fd->search_key->key_len | 1) + 1;
+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_bnode_put(new_node);
+ new_node = NULL;
+
+ if (tree->attributes & HFS_TREE_VARIDXKEYS)
+ key_len = fd->search_key->key_len + 1;
+ else {
+ fd->search_key->key_len = tree->max_key_len;
+ key_len = tree->max_key_len + 1;
+ }
+ 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 src, dst, len;
+
+ tree = fd->tree;
+ node = fd->bnode;
+again:
+ 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) {
+ 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;
+
+ __hfs_brec_find(node, fd);
+ goto again;
+ }
+ hfs_bnode_write_u16(node, offsetof(struct hfs_bnode_desc, num_recs), node->num_recs);
+
+ size = fd->keylength + fd->entrylength;
+
+ if (rec_off == end_off) {
+ src = fd->keyoffset;
+ hfs_bnode_clear(node, src, size);
+ goto skip;
+ }
+
+ 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 */
+ dst = fd->keyoffset;
+ src = fd->keyoffset + size;
+ len = data_off - src;
+
+ hfs_bnode_move(node, dst, src, len);
+
+ src = dst + len;
+ len = data_off - src;
+
+ hfs_bnode_clear(node, src, len);
+
+skip:
+ /*
+ * Remove the obsolete offset to free space.
+ */
+ hfs_bnode_write_u16(node, end_off, 0);
+
+ 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;
+
+ 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;
+ }
+
+ size = tree->node_size / 2 - node->num_recs * 2 - 14;
+ old_rec_off = tree->node_size - 4;
+ num_recs = 1;
+ for (;;) {
+ data_start = hfs_bnode_read_u16(node, old_rec_off);
+ if (data_start > size)
+ break;
+ old_rec_off -= 2;
+ if (++num_recs < node->num_recs)
+ continue;
+ /* panic? */
+ hfs_bnode_put(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 += 2;
+ 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 - 14;
+ fd->entryoffset -= data_start - 14;
+ }
+ new_node->num_recs = node->num_recs - num_recs;
+ node->num_recs = num_recs;
+
+ new_rec_off = tree->node_size - 2;
+ new_off = 14;
+ 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 -= 2;
+ new_rec_off -= 2;
+ 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, 14, 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;
+
+ 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);
+ __hfs_brec_find(parent, fd);
+ 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)
+ newkeylen = (hfs_bnode_read_u8(node, 14) | 1) + 1;
+ else
+ fd->keylength = newkeylen = tree->max_key_len + 1;
+ 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) {
+
+ printk(KERN_DEBUG "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);
+ if (!(tree->attributes & HFS_TREE_VARIDXKEYS))
+ hfs_bnode_write_u8(parent, fd->keyoffset, newkeylen - 1);
+ 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_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)
+ key_size = hfs_bnode_read_u8(node, 14) + 1;
+ else
+ key_size = tree->max_key_len + 1;
+ hfs_bnode_copy(new_node, 14, node, 14, key_size);
+
+ if (!(tree->attributes & HFS_TREE_VARIDXKEYS)) {
+ key_size = tree->max_key_len + 1;
+ hfs_bnode_write_u8(new_node, 14, tree->max_key_len);
+ }
+ key_size = (key_size + 1) & -2;
+ 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/hfs/btree.c b/fs/hfs/btree.c
new file mode 100644
index 000000000..41b4e8fc9
--- /dev/null
+++ b/fs/hfs/btree.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfs/btree.c
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ *
+ * Handle opening/closing btree
+ */
+
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/log2.h>
+
+#include "btree.h"
+
+/* 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 == HFS_TREE_HEAD) {
+ if (node->type != HFS_NODE_HEADER) {
+ pr_err("hfs: invalid btree header node\n");
+ return ERR_PTR(-EIO);
+ }
+ rec_idx = HFS_BTREE_HDR_MAP_REC_INDEX;
+ } else {
+ if (node->type != HFS_NODE_MAP) {
+ pr_err("hfs: invalid btree map node\n");
+ return ERR_PTR(-EIO);
+ }
+ rec_idx = HFS_BTREE_MAP_NODE_REC_INDEX;
+ }
+
+ ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
+ if (!ctx->len)
+ return ERR_PTR(-ENOENT);
+
+ 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;
+}
+
+/* Get a reference to a B*Tree and do some initial checks */
+struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
+{
+ struct hfs_btree *tree;
+ struct hfs_btree_header_rec *head;
+ struct address_space *mapping;
+ struct folio *folio;
+ struct buffer_head *bh;
+ struct hfs_bnode *node;
+ unsigned int size;
+ u16 dblock;
+ sector_t start_block;
+ loff_t offset;
+
+ tree = kzalloc_obj(*tree);
+ if (!tree)
+ return NULL;
+
+ mutex_init(&tree->tree_lock);
+ spin_lock_init(&tree->hash_lock);
+ /* Set the correct compare function */
+ tree->sb = sb;
+ tree->cnid = id;
+ tree->keycmp = keycmp;
+
+ tree->inode = iget_locked(sb, id);
+ if (!tree->inode)
+ goto free_tree;
+ BUG_ON(!(inode_state_read_once(tree->inode) & I_NEW));
+ {
+ struct hfs_mdb *mdb = HFS_SB(sb)->mdb;
+ HFS_I(tree->inode)->flags = 0;
+ mutex_init(&HFS_I(tree->inode)->extents_lock);
+ switch (id) {
+ case HFS_EXT_CNID:
+ hfs_inode_read_fork(tree->inode, mdb->drXTExtRec, mdb->drXTFlSize,
+ mdb->drXTFlSize, be32_to_cpu(mdb->drXTClpSiz));
+ if (HFS_I(tree->inode)->alloc_blocks >
+ HFS_I(tree->inode)->first_blocks) {
+ pr_err("invalid btree extent records\n");
+ unlock_new_inode(tree->inode);
+ goto free_inode;
+ }
+
+ tree->inode->i_mapping->a_ops = &hfs_btree_aops;
+ break;
+ case HFS_CAT_CNID:
+ hfs_inode_read_fork(tree->inode, mdb->drCTExtRec, mdb->drCTFlSize,
+ mdb->drCTFlSize, be32_to_cpu(mdb->drCTClpSiz));
+
+ if (!HFS_I(tree->inode)->first_blocks) {
+ pr_err("invalid btree extent records (0 size)\n");
+ unlock_new_inode(tree->inode);
+ goto free_inode;
+ }
+
+ tree->inode->i_mapping->a_ops = &hfs_btree_aops;
+ break;
+ default:
+ BUG();
+ }
+ }
+ unlock_new_inode(tree->inode);
+
+ mapping = tree->inode->i_mapping;
+ folio = filemap_grab_folio(mapping, 0);
+ if (IS_ERR(folio))
+ goto free_inode;
+
+ folio_zero_range(folio, 0, folio_size(folio));
+
+ dblock = hfs_ext_find_block(HFS_I(tree->inode)->first_extents, 0);
+ start_block = HFS_SB(sb)->fs_start + (dblock * HFS_SB(sb)->fs_div);
+
+ size = folio_size(folio);
+ offset = 0;
+ while (size > 0) {
+ size_t len;
+
+ bh = sb_bread(sb, start_block);
+ if (!bh) {
+ pr_err("unable to read tree header\n");
+ goto put_folio;
+ }
+
+ len = min_t(size_t, folio_size(folio), sb->s_blocksize);
+ memcpy_to_folio(folio, offset, bh->b_data, sb->s_blocksize);
+
+ brelse(bh);
+
+ start_block++;
+ offset += len;
+ size -= len;
+ }
+
+ folio_mark_uptodate(folio);
+
+ /* Load the header */
+ head = (struct hfs_btree_header_rec *)(kmap_local_folio(folio, 0) +
+ 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);
+
+ size = tree->node_size;
+ if (!is_power_of_2(size))
+ goto fail_folio;
+ if (!tree->node_count)
+ goto fail_folio;
+ switch (id) {
+ case HFS_EXT_CNID:
+ if (tree->max_key_len != HFS_MAX_EXT_KEYLEN) {
+ pr_err("invalid extent max_key_len %d\n",
+ tree->max_key_len);
+ goto fail_folio;
+ }
+ break;
+ case HFS_CAT_CNID:
+ if (tree->max_key_len != HFS_MAX_CAT_KEYLEN) {
+ pr_err("invalid catalog max_key_len %d\n",
+ tree->max_key_len);
+ goto fail_folio;
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ tree->node_size_shift = ffs(size) - 1;
+ tree->pages_per_bnode = (tree->node_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+ kunmap_local(head);
+ folio_unlock(folio);
+ folio_put(folio);
+
+ node = hfs_bnode_find(tree, HFS_TREE_HEAD);
+ if (IS_ERR(node))
+ goto free_inode;
+
+ if (!hfs_bmap_test_bit(node, HFS_TREE_HEAD)) {
+ pr_warn("(%s): %s (cnid 0x%x) bitmap corrupted, forcing rdonly\n",
+ sb->s_id, id == HFS_EXT_CNID ? "extents" : "catalog", id);
+ pr_warn("Run fsck.hfs to repair.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+
+ hfs_bnode_put(node);
+
+ return tree;
+
+fail_folio:
+ kunmap_local(head);
+put_folio:
+ folio_unlock(folio);
+ folio_put(folio);
+free_inode:
+ tree->inode->i_mapping->a_ops = &hfs_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_err("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);
+}
+
+void 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;
+ /* 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);
+}
+
+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;
+
+ if (!tree->free_nodes)
+ panic("FIXME!!!");
+ 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;
+ u32 count;
+ int res;
+
+ while (tree->free_nodes < rsvd_nodes) {
+ res = hfs_extend_file(inode);
+ if (res)
+ return res;
+ HFS_I(inode)->phys_size = inode->i_size =
+ (loff_t)HFS_I(inode)->alloc_blocks *
+ HFS_SB(tree->sb)->alloc_blksz;
+ HFS_I(inode)->fs_blocks = inode->i_size >>
+ tree->sb->s_blocksize_bits;
+ 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;
+
+ 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--;
+ 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) {
+ printk(KERN_DEBUG "create new bmap node...\n");
+ next_node = hfs_bmap_new_bmap(node, idx);
+ } 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);
+ tree = node->tree;
+ nidx = node->this;
+ node = hfs_bnode_find(tree, 0);
+ if (IS_ERR(node))
+ return;
+ len = hfs_brec_lenoff(node, 2, &off);
+ 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);
+ }
+
+ res = hfs_bmap_clear_bit(node, nidx);
+ if (res == -EINVAL) {
+ pr_crit("trying to free free 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++;
+ mark_inode_dirty(tree->inode);
+ }
+
+ hfs_bnode_put(node);
+}
diff --git a/fs/hfs/btree.h b/fs/hfs/btree.h
new file mode 100644
index 000000000..b4c3f2a31
--- /dev/null
+++ b/fs/hfs/btree.h
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/fs/hfs/btree.h
+ *
+ * Copyright (C) 2001
+ * Brad Boyer (flar@allandria.com)
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ */
+
+#include "hfs_fs.h"
+
+typedef int (*btree_keycmp)(const btree_key *, const btree_key *);
+
+#define NODE_HASH_SIZE 256
+
+/* B-tree mutex nested subclasses */
+enum hfs_btree_mutex_classes {
+ CATALOG_BTREE_MUTEX,
+ EXTENTS_BTREE_MUTEX,
+ ATTR_BTREE_MUTEX,
+};
+
+/* A 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;
+
+ //unsigned int map1_size, map_size;
+ 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;
+};
+
+/* A 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_ERROR 0
+#define HFS_BNODE_NEW 1
+#define HFS_BNODE_DELETED 2
+
+struct hfs_find_data {
+ btree_key *key;
+ btree_key *search_key;
+ struct hfs_btree *tree;
+ struct hfs_bnode *bnode;
+ int record;
+ int keyoffset, keylength;
+ int entryoffset, entrylength;
+};
+
+
+/* btree.c */
+extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id,
+ btree_keycmp keycmp);
+extern void hfs_btree_close(struct hfs_btree *tree);
+extern void hfs_btree_write(struct hfs_btree *tree);
+extern int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes);
+extern struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree);
+extern void hfs_bmap_free(struct hfs_bnode *node);
+
+/* bnode.c */
+extern void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len);
+extern u16 hfs_bnode_read_u16(struct hfs_bnode *node, u32 off);
+extern u8 hfs_bnode_read_u8(struct hfs_bnode *node, u32 off);
+extern void hfs_bnode_read_key(struct hfs_bnode *node, void *key, u32 off);
+extern void hfs_bnode_write(struct hfs_bnode *node, void *buf, u32 off, u32 len);
+extern void hfs_bnode_write_u16(struct hfs_bnode *node, u32 off, u16 data);
+extern void hfs_bnode_write_u8(struct hfs_bnode *node, u32 off, u8 data);
+extern void hfs_bnode_clear(struct hfs_bnode *node, u32 off, u32 len);
+extern void hfs_bnode_copy(struct hfs_bnode *dst_node, u32 dst,
+ struct hfs_bnode *src_node, u32 src, u32 len);
+extern void hfs_bnode_move(struct hfs_bnode *node, u32 dst, u32 src, u32 len);
+extern void hfs_bnode_dump(struct hfs_bnode *node);
+extern void hfs_bnode_unlink(struct hfs_bnode *node);
+extern struct hfs_bnode *hfs_bnode_findhash(struct hfs_btree *tree, u32 cnid);
+extern struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num);
+extern void hfs_bnode_unhash(struct hfs_bnode *node);
+extern void hfs_bnode_free(struct hfs_bnode *node);
+extern struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num);
+extern void hfs_bnode_get(struct hfs_bnode *node);
+extern void hfs_bnode_put(struct hfs_bnode *node);
+
+/* brec.c */
+extern u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off);
+extern u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec);
+extern int hfs_brec_insert(struct hfs_find_data *fd, void *entry, u32 entry_len);
+extern int hfs_brec_remove(struct hfs_find_data *fd);
+
+/* bfind.c */
+extern int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd);
+extern void hfs_find_exit(struct hfs_find_data *fd);
+extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);
+extern int hfs_brec_find(struct hfs_find_data *fd);
+extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
+extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
+
+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("invalid offset: id %u, type %#x, h %u, sz %u, off %u\n",
+ node->this, node->type, node->height,
+ node->tree->node_size, off);
+ }
+
+ return is_valid;
+}
+
+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("corrected len: id %u, type %#x, h %u, sz %u, off %u, len %u->%u\n",
+ node->this, node->type, node->height,
+ node_size, off, len, new_len);
+
+ return new_len;
+ }
+
+ return len;
+}
diff --git a/fs/hfs/catalog.c b/fs/hfs/catalog.c
new file mode 100644
index 000000000..490c5e130
--- /dev/null
+++ b/fs/hfs/catalog.c
@@ -0,0 +1,536 @@
+/*
+ * linux/fs/hfs/catalog.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains the functions related to the catalog B-tree.
+ *
+ * Cache code shamelessly stolen from
+ * linux/fs/inode.c Copyright (C) 1991, 1992 Linus Torvalds
+ * re-shamelessly stolen Copyright (C) 1997 Linus Torvalds
+ */
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+/*
+ * hfs_cat_build_key()
+ *
+ * Given the ID of the parent and the name build a search key.
+ */
+void hfs_cat_build_key(struct super_block *sb, btree_key *key, u32 parent, const struct qstr *name)
+{
+ key->cat.reserved = 0;
+ key->cat.ParID = cpu_to_be32(parent);
+ if (name) {
+ hfs_asc2mac(sb, &key->cat.CName, name);
+ key->key_len = 6 + key->cat.CName.len;
+ } else {
+ memset(&key->cat.CName, 0, sizeof(struct hfs_name));
+ key->key_len = 6;
+ }
+}
+
+static int hfs_cat_build_record(hfs_cat_rec *rec, u32 cnid, struct inode *inode)
+{
+ __be32 mtime = hfs_mtime();
+
+ memset(rec, 0, sizeof(*rec));
+ if (S_ISDIR(inode->i_mode)) {
+ rec->type = HFS_CDR_DIR;
+ rec->dir.DirID = cpu_to_be32(cnid);
+ rec->dir.CrDat = mtime;
+ rec->dir.MdDat = mtime;
+ rec->dir.BkDat = 0;
+ rec->dir.UsrInfo.frView = cpu_to_be16(0xff);
+ return sizeof(struct hfs_cat_dir);
+ } else {
+ /* init some fields for the file record */
+ rec->type = HFS_CDR_FIL;
+ rec->file.Flags = HFS_FIL_USED | HFS_FIL_THD;
+ if (!(inode->i_mode & S_IWUSR))
+ rec->file.Flags |= HFS_FIL_LOCK;
+ rec->file.FlNum = cpu_to_be32(cnid);
+ rec->file.CrDat = mtime;
+ rec->file.MdDat = mtime;
+ rec->file.BkDat = 0;
+ rec->file.UsrWds.fdType = HFS_SB(inode->i_sb)->s_type;
+ rec->file.UsrWds.fdCreator = HFS_SB(inode->i_sb)->s_creator;
+ return sizeof(struct hfs_cat_file);
+ }
+}
+
+static int hfs_cat_build_thread(struct super_block *sb,
+ hfs_cat_rec *rec, int type,
+ u32 parentid, const struct qstr *name)
+{
+ rec->type = type;
+ memset(rec->thread.reserved, 0, sizeof(rec->thread.reserved));
+ rec->thread.ParID = cpu_to_be32(parentid);
+ hfs_asc2mac(sb, &rec->thread.CName, name);
+ return sizeof(struct hfs_cat_thread);
+}
+
+/*
+ * create_entry()
+ *
+ * Add a new file or directory to the catalog B-tree and
+ * return a (struct hfs_cat_entry) for it in '*result'.
+ */
+int hfs_cat_create(u32 cnid, struct inode *dir, const struct qstr *str, struct inode *inode)
+{
+ struct hfs_find_data fd;
+ struct super_block *sb;
+ union hfs_cat_rec entry;
+ int entry_size;
+ int err;
+
+ hfs_dbg("name %s, cnid %u, i_nlink %d\n",
+ str->name, cnid, inode->i_nlink);
+ if (dir->i_size >= HFS_MAX_VALENCE)
+ return -ENOSPC;
+
+ sb = dir->i_sb;
+ err = hfs_find_init(HFS_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;
+
+ hfs_cat_build_key(sb, fd.search_key, cnid, NULL);
+ entry_size = hfs_cat_build_thread(sb, &entry, S_ISDIR(inode->i_mode) ?
+ HFS_CDR_THD : HFS_CDR_FTH,
+ dir->i_ino, str);
+ err = hfs_brec_find(&fd);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ goto err2;
+ }
+ err = hfs_brec_insert(&fd, &entry, entry_size);
+ if (err)
+ goto err2;
+
+ hfs_cat_build_key(sb, fd.search_key, dir->i_ino, str);
+ entry_size = hfs_cat_build_record(&entry, cnid, inode);
+ err = hfs_brec_find(&fd);
+ 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++;
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
+ hfs_find_exit(&fd);
+ return 0;
+
+err1:
+ hfs_cat_build_key(sb, fd.search_key, cnid, NULL);
+ if (!hfs_brec_find(&fd))
+ hfs_brec_remove(&fd);
+err2:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+/*
+ * hfs_cat_compare()
+ *
+ * Description:
+ * This is the comparison function used for the catalog B-tree. In
+ * comparing catalog B-tree entries, the parent id is the most
+ * significant field (compared as unsigned ints). The name field is
+ * the least significant (compared in "Macintosh lexical order",
+ * see hfs_strcmp() in string.c)
+ * Input Variable(s):
+ * struct hfs_cat_key *key1: pointer to the first key to compare
+ * struct hfs_cat_key *key2: pointer to the second key to compare
+ * Output Variable(s):
+ * NONE
+ * Returns:
+ * int: negative if key1<key2, positive if key1>key2, and 0 if key1==key2
+ * Preconditions:
+ * key1 and key2 point to "valid" (struct hfs_cat_key)s.
+ * Postconditions:
+ * This function has no side-effects
+ */
+int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2)
+{
+ __be32 k1p, k2p;
+
+ k1p = key1->cat.ParID;
+ k2p = key2->cat.ParID;
+
+ if (k1p != k2p)
+ return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1;
+
+ return hfs_strcmp(key1->cat.CName.name, key1->cat.CName.len,
+ key2->cat.CName.name, key2->cat.CName.len);
+}
+
+static int hfs_cat_validate_found_cnid(struct hfs_find_data *fd, u32 cnid)
+{
+ hfs_cat_rec rec;
+ u32 found_cnid;
+ int rec_len;
+
+ rec_len = fd->entrylength;
+ if (rec_len <= 0 || (size_t)rec_len > sizeof(rec))
+ return -EIO;
+
+ memset(&rec, 0, sizeof(rec));
+ hfs_bnode_read(fd->bnode, &rec, fd->entryoffset, rec_len);
+
+ switch (rec.type) {
+ case HFS_CDR_FIL:
+ if ((size_t)rec_len != sizeof(struct hfs_cat_file))
+ return -EIO;
+ found_cnid = be32_to_cpu(rec.file.FlNum);
+ break;
+ case HFS_CDR_DIR:
+ if ((size_t)rec_len != sizeof(struct hfs_cat_dir))
+ return -EIO;
+ found_cnid = be32_to_cpu(rec.dir.DirID);
+ break;
+ default:
+ return -EIO;
+ }
+
+ if (found_cnid != cnid)
+ return -EIO;
+
+ return 0;
+}
+
+/* Try to get a catalog entry for given catalog id */
+// move to read_super???
+int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
+ struct hfs_find_data *fd)
+{
+ hfs_cat_rec rec;
+ int res, len, type;
+
+ hfs_cat_build_key(sb, fd->search_key, cnid, NULL);
+ res = hfs_brec_read(fd, &rec, sizeof(rec));
+ if (res)
+ return res;
+
+ type = rec.type;
+ if (type != HFS_CDR_THD && type != HFS_CDR_FTH) {
+ pr_err("found bad thread record in catalog\n");
+ return -EIO;
+ }
+
+ fd->search_key->cat.ParID = rec.thread.ParID;
+ len = fd->search_key->cat.CName.len = rec.thread.CName.len;
+ if (len > HFS_NAMELEN) {
+ pr_err("bad catalog namelength\n");
+ return -EIO;
+ }
+ memcpy(fd->search_key->cat.CName.name, rec.thread.CName.name, len);
+
+ res = hfs_brec_find(fd);
+ if (res)
+ return res;
+
+ return hfs_cat_validate_found_cnid(fd, cnid);
+}
+
+static inline
+void hfs_set_next_unused_CNID(struct super_block *sb,
+ u32 deleted_cnid, u32 found_cnid)
+{
+ if (found_cnid < HFS_FIRSTUSER_CNID) {
+ atomic64_cmpxchg(&HFS_SB(sb)->next_id,
+ deleted_cnid + 1, HFS_FIRSTUSER_CNID);
+ } else {
+ atomic64_cmpxchg(&HFS_SB(sb)->next_id,
+ deleted_cnid + 1, found_cnid + 1);
+ }
+}
+
+/*
+ * hfs_correct_next_unused_CNID()
+ *
+ * Correct the next unused CNID of Catalog Tree.
+ */
+static
+int hfs_correct_next_unused_CNID(struct super_block *sb, u32 cnid)
+{
+ struct hfs_btree *cat_tree;
+ struct hfs_bnode *node;
+ s64 leaf_head;
+ s64 leaf_tail;
+ s64 node_id;
+
+ hfs_dbg("cnid %u, next_id %lld\n",
+ cnid, atomic64_read(&HFS_SB(sb)->next_id));
+
+ if ((cnid + 1) < atomic64_read(&HFS_SB(sb)->next_id)) {
+ /* next ID should be unchanged */
+ return 0;
+ }
+
+ cat_tree = HFS_SB(sb)->cat_tree;
+ leaf_head = cat_tree->leaf_head;
+ leaf_tail = cat_tree->leaf_tail;
+
+ if (leaf_head > leaf_tail) {
+ pr_err("node is corrupted: leaf_head %lld, leaf_tail %lld\n",
+ leaf_head, leaf_tail);
+ return -ERANGE;
+ }
+
+ node = hfs_bnode_find(cat_tree, leaf_tail);
+ if (IS_ERR(node)) {
+ pr_err("fail to find leaf node: node ID %lld\n",
+ leaf_tail);
+ return -ENOENT;
+ }
+
+ node_id = leaf_tail;
+
+ do {
+ int i;
+
+ if (node_id != leaf_tail) {
+ node = hfs_bnode_find(cat_tree, node_id);
+ if (IS_ERR(node))
+ return -ENOENT;
+ }
+
+ hfs_dbg("node %lld, leaf_tail %lld, leaf_head %lld\n",
+ node_id, leaf_tail, leaf_head);
+
+ hfs_bnode_dump(node);
+
+ for (i = node->num_recs - 1; i >= 0; i--) {
+ hfs_cat_rec rec;
+ u16 off, len, keylen;
+ int entryoffset;
+ int entrylength;
+ u32 found_cnid;
+
+ len = hfs_brec_lenoff(node, i, &off);
+ keylen = hfs_brec_keylen(node, i);
+ if (keylen == 0) {
+ pr_err("fail to get the keylen: "
+ "node_id %lld, record index %d\n",
+ node_id, i);
+ return -EINVAL;
+ }
+
+ entryoffset = off + keylen;
+ entrylength = len - keylen;
+
+ if (entrylength > sizeof(rec)) {
+ pr_err("unexpected record length: "
+ "entrylength %d\n",
+ entrylength);
+ return -EINVAL;
+ }
+
+ hfs_bnode_read(node, &rec, entryoffset, entrylength);
+
+ if (rec.type == HFS_CDR_DIR) {
+ found_cnid = be32_to_cpu(rec.dir.DirID);
+ hfs_dbg("found_cnid %u\n", found_cnid);
+ hfs_set_next_unused_CNID(sb, cnid, found_cnid);
+ hfs_bnode_put(node);
+ return 0;
+ } else if (rec.type == HFS_CDR_FIL) {
+ found_cnid = be32_to_cpu(rec.file.FlNum);
+ hfs_dbg("found_cnid %u\n", found_cnid);
+ hfs_set_next_unused_CNID(sb, cnid, found_cnid);
+ hfs_bnode_put(node);
+ return 0;
+ }
+ }
+
+ node_id = node->prev;
+ hfs_bnode_put(node);
+
+ } while (node_id >= leaf_head);
+
+ return -ENOENT;
+}
+
+/*
+ * hfs_cat_delete()
+ *
+ * Delete the indicated file or directory.
+ * The associated thread is also removed unless ('with_thread'==0).
+ */
+int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str)
+{
+ struct super_block *sb;
+ struct hfs_find_data fd;
+ int res, type;
+
+ hfs_dbg("name %s, cnid %u\n", str ? str->name : NULL, cnid);
+ sb = dir->i_sb;
+ res = hfs_find_init(HFS_SB(sb)->cat_tree, &fd);
+ if (res)
+ return res;
+
+ hfs_cat_build_key(sb, fd.search_key, dir->i_ino, str);
+ res = hfs_brec_find(&fd);
+ if (res)
+ goto out;
+
+ type = hfs_bnode_read_u8(fd.bnode, fd.entryoffset);
+ if (type == HFS_CDR_FIL) {
+ struct hfs_cat_file file;
+ hfs_bnode_read(fd.bnode, &file, fd.entryoffset, sizeof(file));
+ if (be32_to_cpu(file.FlNum) == cnid) {
+#if 0
+ hfs_free_fork(sb, &file, HFS_FK_DATA);
+#endif
+ hfs_free_fork(sb, &file, HFS_FK_RSRC);
+ }
+ }
+
+ res = hfs_brec_remove(&fd);
+ if (res)
+ goto out;
+
+ hfs_cat_build_key(sb, fd.search_key, cnid, NULL);
+ res = hfs_brec_find(&fd);
+ if (!res) {
+ res = hfs_brec_remove(&fd);
+ if (res)
+ goto out;
+ }
+
+ dir->i_size--;
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
+
+ res = hfs_correct_next_unused_CNID(sb, cnid);
+ if (res)
+ goto out;
+
+ res = 0;
+out:
+ hfs_find_exit(&fd);
+
+ return res;
+}
+
+/*
+ * hfs_cat_move()
+ *
+ * Rename a file or directory, possibly to a new directory.
+ * If the destination exists it is removed and a
+ * (struct hfs_cat_entry) for it is returned in '*result'.
+ */
+int hfs_cat_move(u32 cnid, struct inode *src_dir, const struct qstr *src_name,
+ struct inode *dst_dir, const struct qstr *dst_name)
+{
+ struct super_block *sb;
+ struct hfs_find_data src_fd, dst_fd;
+ union hfs_cat_rec 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);
+ sb = src_dir->i_sb;
+ err = hfs_find_init(HFS_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 once.
+ */
+ err = hfs_bmap_reserve(src_fd.tree, 2 * src_fd.tree->depth);
+ if (err)
+ goto out;
+
+ /* find the old dir entry and read the data */
+ hfs_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
+ err = hfs_brec_find(&src_fd);
+ 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);
+
+ /* create new dir entry with the data from the old entry */
+ hfs_cat_build_key(sb, dst_fd.search_key, dst_dir->i_ino, dst_name);
+ err = hfs_brec_find(&dst_fd);
+ 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++;
+ inode_set_mtime_to_ts(dst_dir, inode_set_ctime_current(dst_dir));
+ mark_inode_dirty(dst_dir);
+
+ /* finally remove the old entry */
+ hfs_cat_build_key(sb, src_fd.search_key, src_dir->i_ino, src_name);
+ err = hfs_brec_find(&src_fd);
+ if (err)
+ goto out;
+ err = hfs_brec_remove(&src_fd);
+ if (err)
+ goto out;
+ src_dir->i_size--;
+ inode_set_mtime_to_ts(src_dir, inode_set_ctime_current(src_dir));
+ mark_inode_dirty(src_dir);
+
+ type = entry.type;
+ if (type == HFS_CDR_FIL && !(entry.file.Flags & HFS_FIL_THD))
+ goto out;
+
+ /* remove old thread entry */
+ hfs_cat_build_key(sb, src_fd.search_key, cnid, NULL);
+ err = hfs_brec_find(&src_fd);
+ if (err)
+ goto out;
+ err = hfs_brec_remove(&src_fd);
+ if (err)
+ goto out;
+
+ /* create new thread entry */
+ hfs_cat_build_key(sb, dst_fd.search_key, cnid, NULL);
+ entry_size = hfs_cat_build_thread(sb, &entry, type == HFS_CDR_FIL ? HFS_CDR_FTH : HFS_CDR_THD,
+ dst_dir->i_ino, dst_name);
+ err = hfs_brec_find(&dst_fd);
+ if (err != -ENOENT) {
+ if (!err)
+ err = -EEXIST;
+ goto out;
+ }
+ err = hfs_brec_insert(&dst_fd, &entry, entry_size);
+out:
+ hfs_bnode_put(dst_fd.bnode);
+ hfs_find_exit(&src_fd);
+ return err;
+}
diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c
new file mode 100644
index 000000000..e1f1fb351
--- /dev/null
+++ b/fs/hfs/dir.c
@@ -0,0 +1,331 @@
+/*
+ * linux/fs/hfs/dir.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains directory-related functions independent of which
+ * scheme is being used to represent forks.
+ *
+ * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds
+ */
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+/*
+ * hfs_lookup()
+ */
+static struct dentry *hfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ hfs_cat_rec rec;
+ struct hfs_find_data fd;
+ struct inode *inode = NULL;
+ int res;
+
+ res = hfs_find_init(HFS_SB(dir->i_sb)->cat_tree, &fd);
+ if (res)
+ return ERR_PTR(res);
+ hfs_cat_build_key(dir->i_sb, fd.search_key, dir->i_ino, &dentry->d_name);
+ res = hfs_brec_read(&fd, &rec, sizeof(rec));
+ if (res) {
+ if (res != -ENOENT)
+ inode = ERR_PTR(res);
+ } else {
+ inode = hfs_iget(dir->i_sb, &fd.search_key->cat, &rec);
+ if (!inode)
+ inode = ERR_PTR(-EACCES);
+ }
+ hfs_find_exit(&fd);
+ return d_splice_alias(inode, dentry);
+}
+
+/*
+ * hfs_readdir
+ */
+static int hfs_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[HFS_MAX_NAMELEN];
+ union hfs_cat_rec entry;
+ struct hfs_find_data fd;
+ struct hfs_readdir_data *rd;
+ u16 type;
+
+ if (ctx->pos >= inode->i_size)
+ return 0;
+
+ err = hfs_find_init(HFS_SB(sb)->cat_tree, &fd);
+ if (err)
+ return err;
+ hfs_cat_build_key(sb, fd.search_key, inode->i_ino, NULL);
+ err = hfs_brec_find(&fd);
+ 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 (entry.type != HFS_CDR_THD) {
+ pr_err("bad catalog folder thread\n");
+ err = -EIO;
+ goto out;
+ }
+ //if (fd.entrylength < HFS_MIN_THREAD_SZ) {
+ // pr_err("truncated catalog thread\n");
+ // err = -EIO;
+ // goto out;
+ //}
+ if (!dir_emit(ctx, "..", 2,
+ be32_to_cpu(entry.thread.ParID), 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 hfs_cat_key));
+ err = hfs_brec_find(&fd);
+ 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.ParID) != 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 = entry.type;
+ len = hfs_mac2asc(sb, strbuf, &fd.key->cat.CName);
+ if (type == HFS_CDR_DIR) {
+ if (fd.entrylength < sizeof(struct hfs_cat_dir)) {
+ pr_err("small dir entry\n");
+ err = -EIO;
+ goto out;
+ }
+ if (!dir_emit(ctx, strbuf, len,
+ be32_to_cpu(entry.dir.DirID), DT_DIR))
+ break;
+ } else if (type == HFS_CDR_FIL) {
+ if (fd.entrylength < sizeof(struct hfs_cat_file)) {
+ pr_err("small file entry\n");
+ err = -EIO;
+ goto out;
+ }
+ if (!dir_emit(ctx, strbuf, len,
+ be32_to_cpu(entry.file.FlNum), DT_REG))
+ break;
+ } else {
+ pr_err("bad catalog entry type %d\n", type);
+ err = -EIO;
+ goto out;
+ }
+ 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 hfs_readdir_data);
+ if (!rd) {
+ err = -ENOMEM;
+ goto out;
+ }
+ file->private_data = rd;
+ }
+ rd->pos = ctx->pos;
+ memcpy(&rd->key, &fd.key->cat, sizeof(struct hfs_cat_key));
+out:
+ hfs_find_exit(&fd);
+ return err;
+}
+
+static int hfs_dir_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+/*
+ * hfs_create()
+ *
+ * This is the create() entry in the inode_operations structure for
+ * regular HFS directories. The purpose is to create a new file in
+ * a directory and return a corresponding inode, given the inode for
+ * the directory and the name (and its length) of the new file.
+ */
+static int hfs_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode *inode;
+ int res;
+
+ inode = hfs_new_inode(dir, &dentry->d_name, mode);
+ if (IS_ERR(inode))
+ return PTR_ERR(inode);
+
+ res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode);
+ if (res) {
+ clear_nlink(inode);
+ hfs_delete_inode(inode);
+ iput(inode);
+ return res;
+ }
+ d_instantiate(dentry, inode);
+ mark_inode_dirty(inode);
+ return 0;
+}
+
+/*
+ * hfs_mkdir()
+ *
+ * This is the mkdir() entry in the inode_operations structure for
+ * regular HFS directories. The purpose is to create a new directory
+ * in a directory, given the inode for the parent directory and the
+ * name (and its length) of the new directory.
+ */
+static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+{
+ struct inode *inode;
+ int res;
+
+ inode = hfs_new_inode(dir, &dentry->d_name, mode);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
+
+ res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode);
+ if (res) {
+ clear_nlink(inode);
+ hfs_delete_inode(inode);
+ iput(inode);
+ return ERR_PTR(res);
+ }
+ d_instantiate(dentry, inode);
+ mark_inode_dirty(inode);
+ return NULL;
+}
+
+/*
+ * hfs_remove()
+ *
+ * This serves as both unlink() and rmdir() in the inode_operations
+ * structure for regular HFS directories. The purpose is to delete
+ * an existing child, given the inode for the parent directory and
+ * the name (and its length) of the existing directory.
+ *
+ * HFS does not have hardlinks, so both rmdir and unlink set the
+ * link count to 0. The only difference is the emptiness check.
+ */
+static int hfs_remove(struct inode *dir, struct dentry *dentry)
+{
+ struct super_block *sb = dir->i_sb;
+ struct inode *inode = d_inode(dentry);
+ int res;
+
+ if (S_ISDIR(inode->i_mode) && inode->i_size != 2)
+ return -ENOTEMPTY;
+
+ if (unlikely(!is_hfs_cnid_counts_valid(sb))) {
+ pr_err("cannot remove file/folder\n");
+ return -ERANGE;
+ }
+
+ res = hfs_cat_delete(inode->i_ino, dir, &dentry->d_name);
+ if (res)
+ return res;
+ clear_nlink(inode);
+ inode_set_ctime_current(inode);
+ hfs_delete_inode(inode);
+ mark_inode_dirty(inode);
+ return 0;
+}
+
+/*
+ * hfs_rename()
+ *
+ * This is the rename() entry in the inode_operations structure for
+ * regular HFS directories. The purpose is to rename an existing
+ * file or directory, given the inode for the current directory and
+ * the name (and its length) of the existing file/directory and the
+ * inode for the new directory and the name (and its length) of the
+ * new file/directory.
+ * XXX: how do you handle must_be dir?
+ */
+static int hfs_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)) {
+ res = hfs_remove(new_dir, new_dentry);
+ if (res)
+ return res;
+ }
+
+ res = hfs_cat_move(d_inode(old_dentry)->i_ino,
+ old_dir, &old_dentry->d_name,
+ new_dir, &new_dentry->d_name);
+ if (!res) {
+ struct inode *inode = d_inode(old_dentry);
+
+ hfs_cat_build_key(old_dir->i_sb,
+ (btree_key *)&HFS_I(inode)->cat_key,
+ new_dir->i_ino, &new_dentry->d_name);
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+ }
+ return res;
+}
+
+const struct file_operations hfs_dir_operations = {
+ .read = generic_read_dir,
+ .iterate_shared = hfs_readdir,
+ .llseek = generic_file_llseek,
+ .release = hfs_dir_release,
+};
+
+const struct inode_operations hfs_dir_inode_operations = {
+ .create = hfs_create,
+ .lookup = hfs_lookup,
+ .unlink = hfs_remove,
+ .mkdir = hfs_mkdir,
+ .rmdir = hfs_remove,
+ .rename = hfs_rename,
+ .setattr = hfs_inode_setattr,
+ .fileattr_get = hfs_fileattr_get,
+};
diff --git a/fs/hfs/extent.c b/fs/hfs/extent.c
new file mode 100644
index 000000000..f066a99a8
--- /dev/null
+++ b/fs/hfs/extent.c
@@ -0,0 +1,552 @@
+/*
+ * linux/fs/hfs/extent.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains the functions related to the extents B-tree.
+ */
+
+#include <linux/pagemap.h>
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+/*================ File-local functions ================*/
+
+/*
+ * build_key
+ */
+static void hfs_ext_build_key(hfs_btree_key *key, u32 cnid, u16 block, u8 type)
+{
+ key->key_len = 7;
+ key->ext.FkType = type;
+ key->ext.FNum = cpu_to_be32(cnid);
+ key->ext.FABN = cpu_to_be16(block);
+}
+
+/*
+ * hfs_ext_compare()
+ *
+ * Description:
+ * This is the comparison function used for the extents B-tree. In
+ * comparing extent B-tree entries, the file id is the most
+ * significant field (compared as unsigned ints); the fork type is
+ * the second most significant field (compared as unsigned chars);
+ * and the allocation block number field is the least significant
+ * (compared as unsigned ints).
+ * Input Variable(s):
+ * struct hfs_ext_key *key1: pointer to the first key to compare
+ * struct hfs_ext_key *key2: pointer to the second key to compare
+ * Output Variable(s):
+ * NONE
+ * Returns:
+ * int: negative if key1<key2, positive if key1>key2, and 0 if key1==key2
+ * Preconditions:
+ * key1 and key2 point to "valid" (struct hfs_ext_key)s.
+ * Postconditions:
+ * This function has no side-effects */
+int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2)
+{
+ __be32 fnum1, fnum2;
+ __be16 block1, block2;
+
+ fnum1 = key1->ext.FNum;
+ fnum2 = key2->ext.FNum;
+ if (fnum1 != fnum2)
+ return be32_to_cpu(fnum1) < be32_to_cpu(fnum2) ? -1 : 1;
+ if (key1->ext.FkType != key2->ext.FkType)
+ return key1->ext.FkType < key2->ext.FkType ? -1 : 1;
+
+ block1 = key1->ext.FABN;
+ block2 = key2->ext.FABN;
+ if (block1 == block2)
+ return 0;
+ return be16_to_cpu(block1) < be16_to_cpu(block2) ? -1 : 1;
+}
+
+/*
+ * hfs_ext_find_block
+ *
+ * Find a block within an extent record
+ */
+u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off)
+{
+ int i;
+ u16 count;
+
+ for (i = 0; i < 3; ext++, i++) {
+ count = be16_to_cpu(ext->count);
+ if (off < count)
+ return be16_to_cpu(ext->block) + off;
+ off -= count;
+ }
+ /* panic? */
+ return 0;
+}
+
+static int hfs_ext_block_count(struct hfs_extent *ext)
+{
+ int i;
+ u16 count = 0;
+
+ for (i = 0; i < 3; ext++, i++)
+ count += be16_to_cpu(ext->count);
+ return count;
+}
+
+static u16 hfs_ext_lastblock(struct hfs_extent *ext)
+{
+ int i;
+
+ ext += 2;
+ for (i = 0; i < 2; ext--, i++)
+ if (ext->count)
+ break;
+ return be16_to_cpu(ext->block) + be16_to_cpu(ext->count);
+}
+
+static int __hfs_ext_write_extent(struct inode *inode, struct hfs_find_data *fd)
+{
+ int res;
+
+ hfs_ext_build_key(fd->search_key, inode->i_ino, HFS_I(inode)->cached_start,
+ HFS_IS_RSRC(inode) ? HFS_FK_RSRC : HFS_FK_DATA);
+ res = hfs_brec_find(fd);
+ if (HFS_I(inode)->flags & HFS_FLG_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, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec));
+ HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
+ } else {
+ if (res)
+ return res;
+ hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents, fd->entryoffset, fd->entrylength);
+ HFS_I(inode)->flags &= ~HFS_FLG_EXT_DIRTY;
+ }
+ return 0;
+}
+
+int hfs_ext_write_extent(struct inode *inode)
+{
+ struct hfs_find_data fd;
+ int res = 0;
+
+ if (HFS_I(inode)->flags & HFS_FLG_EXT_DIRTY) {
+ res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd);
+ if (res)
+ return res;
+ res = __hfs_ext_write_extent(inode, &fd);
+ hfs_find_exit(&fd);
+ }
+ return res;
+}
+
+static inline int __hfs_ext_read_extent(struct hfs_find_data *fd, struct hfs_extent *extent,
+ u32 cnid, u32 block, u8 type)
+{
+ int res;
+
+ hfs_ext_build_key(fd->search_key, cnid, block, type);
+ fd->key->ext.FNum = 0;
+ res = hfs_brec_find(fd);
+ if (res && res != -ENOENT)
+ return res;
+ if (fd->key->ext.FNum != fd->search_key->ext.FNum ||
+ fd->key->ext.FkType != fd->search_key->ext.FkType)
+ return -ENOENT;
+ if (fd->entrylength != sizeof(hfs_extent_rec))
+ return -EIO;
+ hfs_bnode_read(fd->bnode, extent, fd->entryoffset, sizeof(hfs_extent_rec));
+ return 0;
+}
+
+static inline int __hfs_ext_cache_extent(struct hfs_find_data *fd, struct inode *inode, u32 block)
+{
+ int res;
+
+ if (HFS_I(inode)->flags & HFS_FLG_EXT_DIRTY) {
+ res = __hfs_ext_write_extent(inode, fd);
+ if (res)
+ return res;
+ }
+
+ res = __hfs_ext_read_extent(fd, HFS_I(inode)->cached_extents, inode->i_ino,
+ block, HFS_IS_RSRC(inode) ? HFS_FK_RSRC : HFS_FK_DATA);
+ if (!res) {
+ HFS_I(inode)->cached_start = be16_to_cpu(fd->key->ext.FABN);
+ HFS_I(inode)->cached_blocks = hfs_ext_block_count(HFS_I(inode)->cached_extents);
+ } else {
+ HFS_I(inode)->cached_start = HFS_I(inode)->cached_blocks = 0;
+ HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
+ }
+ return res;
+}
+
+static int hfs_ext_read_extent(struct inode *inode, u16 block)
+{
+ struct hfs_find_data fd;
+ int res;
+
+ if (block >= HFS_I(inode)->cached_start &&
+ block < HFS_I(inode)->cached_start + HFS_I(inode)->cached_blocks)
+ return 0;
+
+ res = hfs_find_init(HFS_SB(inode->i_sb)->ext_tree, &fd);
+ if (!res) {
+ res = __hfs_ext_cache_extent(&fd, inode, block);
+ hfs_find_exit(&fd);
+ }
+ return res;
+}
+
+static void hfs_dump_extent(struct hfs_extent *extent)
+{
+ int i;
+
+ hfs_dbg("extent: ");
+ for (i = 0; i < 3; i++)
+ hfs_dbg(" block %u, count %u",
+ be16_to_cpu(extent[i].block),
+ be16_to_cpu(extent[i].count));
+ hfs_dbg("\n");
+}
+
+static int hfs_add_extent(struct hfs_extent *extent, u16 offset,
+ u16 alloc_block, u16 block_count)
+{
+ u16 count, start;
+ int i;
+
+ hfs_dump_extent(extent);
+ for (i = 0; i < 3; extent++, i++) {
+ count = be16_to_cpu(extent->count);
+ if (offset == count) {
+ start = be16_to_cpu(extent->block);
+ if (alloc_block != start + count) {
+ if (++i >= 3)
+ return -ENOSPC;
+ extent++;
+ extent->block = cpu_to_be16(alloc_block);
+ } else
+ block_count += count;
+ extent->count = cpu_to_be16(block_count);
+ return 0;
+ } else if (offset < count)
+ break;
+ offset -= count;
+ }
+ /* panic? */
+ return -EIO;
+}
+
+static int hfs_free_extents(struct super_block *sb, struct hfs_extent *extent,
+ u16 offset, u16 block_nr)
+{
+ u16 count, start;
+ int i;
+
+ hfs_dump_extent(extent);
+ for (i = 0; i < 3; extent++, i++) {
+ count = be16_to_cpu(extent->count);
+ if (offset == count)
+ goto found;
+ else if (offset < count)
+ break;
+ offset -= count;
+ }
+ /* panic? */
+ return -EIO;
+found:
+ for (;;) {
+ start = be16_to_cpu(extent->block);
+ if (count <= block_nr) {
+ hfs_clear_vbm_bits(sb, start, count);
+ extent->block = 0;
+ extent->count = 0;
+ block_nr -= count;
+ } else {
+ count -= block_nr;
+ hfs_clear_vbm_bits(sb, start + count, block_nr);
+ extent->count = cpu_to_be16(count);
+ block_nr = 0;
+ }
+ if (!block_nr || !i)
+ return 0;
+ i--;
+ extent--;
+ count = be16_to_cpu(extent->count);
+ }
+}
+
+int hfs_free_fork(struct super_block *sb, struct hfs_cat_file *file, int type)
+{
+ struct hfs_find_data fd;
+ u32 total_blocks, blocks, start;
+ u32 cnid = be32_to_cpu(file->FlNum);
+ struct hfs_extent *extent;
+ int res, i;
+
+ if (type == HFS_FK_DATA) {
+ total_blocks = be32_to_cpu(file->PyLen);
+ extent = file->ExtRec;
+ } else {
+ total_blocks = be32_to_cpu(file->RPyLen);
+ extent = file->RExtRec;
+ }
+ total_blocks /= HFS_SB(sb)->alloc_blksz;
+ if (!total_blocks)
+ return 0;
+
+ blocks = 0;
+ for (i = 0; i < 3; i++)
+ blocks += be16_to_cpu(extent[i].count);
+
+ res = hfs_free_extents(sb, extent, blocks, blocks);
+ if (res)
+ return res;
+ if (total_blocks == blocks)
+ return 0;
+
+ res = hfs_find_init(HFS_SB(sb)->ext_tree, &fd);
+ if (res)
+ return res;
+ do {
+ res = __hfs_ext_read_extent(&fd, extent, cnid, total_blocks, type);
+ if (res)
+ break;
+ start = be16_to_cpu(fd.key->ext.FABN);
+ hfs_free_extents(sb, extent, total_blocks - start, total_blocks);
+ hfs_brec_remove(&fd);
+ total_blocks = start;
+ } while (total_blocks > blocks);
+ hfs_find_exit(&fd);
+
+ return res;
+}
+
+/*
+ * hfs_get_block
+ */
+int hfs_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int create)
+{
+ struct super_block *sb;
+ u16 dblock, ablock;
+ int res;
+
+ sb = inode->i_sb;
+ /* Convert inode block to disk allocation block */
+ ablock = (u32)block / HFS_SB(sb)->fs_div;
+
+ if (block >= HFS_I(inode)->fs_blocks) {
+ if (!create)
+ return 0;
+ if (block > HFS_I(inode)->fs_blocks)
+ return -EIO;
+ if (ablock >= HFS_I(inode)->alloc_blocks) {
+ res = hfs_extend_file(inode);
+ if (res)
+ return res;
+ }
+ } else
+ create = 0;
+
+ if (ablock < HFS_I(inode)->first_blocks) {
+ dblock = hfs_ext_find_block(HFS_I(inode)->first_extents, ablock);
+ goto done;
+ }
+
+ mutex_lock(&HFS_I(inode)->extents_lock);
+ res = hfs_ext_read_extent(inode, ablock);
+ if (!res)
+ dblock = hfs_ext_find_block(HFS_I(inode)->cached_extents,
+ ablock - HFS_I(inode)->cached_start);
+ else {
+ mutex_unlock(&HFS_I(inode)->extents_lock);
+ return -EIO;
+ }
+ mutex_unlock(&HFS_I(inode)->extents_lock);
+
+done:
+ map_bh(bh_result, sb, HFS_SB(sb)->fs_start +
+ dblock * HFS_SB(sb)->fs_div +
+ (u32)block % HFS_SB(sb)->fs_div);
+
+ if (create) {
+ set_buffer_new(bh_result);
+ HFS_I(inode)->phys_size += sb->s_blocksize;
+ HFS_I(inode)->fs_blocks++;
+ inode_add_bytes(inode, sb->s_blocksize);
+ mark_inode_dirty(inode);
+ }
+ return 0;
+}
+
+int hfs_extend_file(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ u32 start, len, goal;
+ int res;
+
+ mutex_lock(&HFS_I(inode)->extents_lock);
+ if (HFS_I(inode)->alloc_blocks == HFS_I(inode)->first_blocks)
+ goal = hfs_ext_lastblock(HFS_I(inode)->first_extents);
+ else {
+ res = hfs_ext_read_extent(inode, HFS_I(inode)->alloc_blocks);
+ if (res)
+ goto out;
+ goal = hfs_ext_lastblock(HFS_I(inode)->cached_extents);
+ }
+
+ len = HFS_I(inode)->clump_blocks;
+ start = hfs_vbm_search_free(sb, goal, &len);
+ if (!len) {
+ res = -ENOSPC;
+ goto out;
+ }
+
+ hfs_dbg("ino %llu, start %u, len %u\n", inode->i_ino, start, len);
+ if (HFS_I(inode)->alloc_blocks == HFS_I(inode)->first_blocks) {
+ if (!HFS_I(inode)->first_blocks) {
+ hfs_dbg("first_extent: start %u, len %u\n",
+ start, len);
+ /* no extents yet */
+ HFS_I(inode)->first_extents[0].block = cpu_to_be16(start);
+ HFS_I(inode)->first_extents[0].count = cpu_to_be16(len);
+ res = 0;
+ } else {
+ /* try to append to extents in inode */
+ res = hfs_add_extent(HFS_I(inode)->first_extents,
+ HFS_I(inode)->alloc_blocks,
+ start, len);
+ if (res == -ENOSPC)
+ goto insert_extent;
+ }
+ if (!res) {
+ hfs_dump_extent(HFS_I(inode)->first_extents);
+ HFS_I(inode)->first_blocks += len;
+ }
+ } else {
+ res = hfs_add_extent(HFS_I(inode)->cached_extents,
+ HFS_I(inode)->alloc_blocks -
+ HFS_I(inode)->cached_start,
+ start, len);
+ if (!res) {
+ hfs_dump_extent(HFS_I(inode)->cached_extents);
+ HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY;
+ HFS_I(inode)->cached_blocks += len;
+ } else if (res == -ENOSPC)
+ goto insert_extent;
+ }
+out:
+ mutex_unlock(&HFS_I(inode)->extents_lock);
+ if (!res) {
+ HFS_I(inode)->alloc_blocks += len;
+ mark_inode_dirty(inode);
+ if (inode->i_ino < HFS_FIRSTUSER_CNID)
+ set_bit(HFS_FLG_ALT_MDB_DIRTY, &HFS_SB(sb)->flags);
+ set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
+ hfs_mark_mdb_dirty(sb);
+ }
+ return res;
+
+insert_extent:
+ hfs_dbg("insert new extent\n");
+ res = hfs_ext_write_extent(inode);
+ if (res)
+ goto out;
+
+ memset(HFS_I(inode)->cached_extents, 0, sizeof(hfs_extent_rec));
+ HFS_I(inode)->cached_extents[0].block = cpu_to_be16(start);
+ HFS_I(inode)->cached_extents[0].count = cpu_to_be16(len);
+ hfs_dump_extent(HFS_I(inode)->cached_extents);
+ HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW;
+ HFS_I(inode)->cached_start = HFS_I(inode)->alloc_blocks;
+ HFS_I(inode)->cached_blocks = len;
+
+ res = 0;
+ goto out;
+}
+
+void hfs_file_truncate(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfs_find_data fd;
+ u16 blk_cnt, alloc_cnt, start;
+ u32 size;
+ int res;
+
+ hfs_dbg("ino %llu, phys_size %llu -> i_size %llu\n",
+ inode->i_ino, (long long)HFS_I(inode)->phys_size,
+ inode->i_size);
+ if (inode->i_size > HFS_I(inode)->phys_size) {
+ struct address_space *mapping = inode->i_mapping;
+ void *fsdata = NULL;
+ struct folio *folio;
+
+ /* XXX: Can use generic_cont_expand? */
+ size = inode->i_size - 1;
+ res = hfs_write_begin(NULL, mapping, size + 1, 0, &folio,
+ &fsdata);
+ if (!res) {
+ res = generic_write_end(NULL, mapping, size + 1, 0, 0,
+ folio, fsdata);
+ }
+ if (res)
+ inode->i_size = HFS_I(inode)->phys_size;
+ return;
+ } else if (inode->i_size == HFS_I(inode)->phys_size)
+ return;
+ size = inode->i_size + HFS_SB(sb)->alloc_blksz - 1;
+ blk_cnt = size / HFS_SB(sb)->alloc_blksz;
+ alloc_cnt = HFS_I(inode)->alloc_blocks;
+ if (blk_cnt == alloc_cnt)
+ goto out;
+
+ mutex_lock(&HFS_I(inode)->extents_lock);
+ res = hfs_find_init(HFS_SB(sb)->ext_tree, &fd);
+ if (res) {
+ mutex_unlock(&HFS_I(inode)->extents_lock);
+ /* XXX: We lack error handling of hfs_file_truncate() */
+ return;
+ }
+ while (1) {
+ if (alloc_cnt == HFS_I(inode)->first_blocks) {
+ hfs_free_extents(sb, HFS_I(inode)->first_extents,
+ alloc_cnt, alloc_cnt - blk_cnt);
+ hfs_dump_extent(HFS_I(inode)->first_extents);
+ HFS_I(inode)->first_blocks = blk_cnt;
+ break;
+ }
+ res = __hfs_ext_cache_extent(&fd, inode, alloc_cnt);
+ if (res)
+ break;
+ start = HFS_I(inode)->cached_start;
+ hfs_free_extents(sb, HFS_I(inode)->cached_extents,
+ alloc_cnt - start, alloc_cnt - blk_cnt);
+ hfs_dump_extent(HFS_I(inode)->cached_extents);
+ if (blk_cnt > start) {
+ HFS_I(inode)->flags |= HFS_FLG_EXT_DIRTY;
+ break;
+ }
+ alloc_cnt = start;
+ HFS_I(inode)->cached_start = HFS_I(inode)->cached_blocks = 0;
+ HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW);
+ hfs_brec_remove(&fd);
+ }
+ hfs_find_exit(&fd);
+ mutex_unlock(&HFS_I(inode)->extents_lock);
+
+ HFS_I(inode)->alloc_blocks = blk_cnt;
+out:
+ HFS_I(inode)->phys_size = inode->i_size;
+ HFS_I(inode)->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
+ inode_set_bytes(inode, HFS_I(inode)->fs_blocks << sb->s_blocksize_bits);
+ mark_inode_dirty(inode);
+}
diff --git a/fs/hfs/hfs.h b/fs/hfs/hfs.h
new file mode 100644
index 000000000..2e958c46b
--- /dev/null
+++ b/fs/hfs/hfs.h
@@ -0,0 +1,21 @@
+/*
+ * linux/fs/hfs/hfs.h
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ */
+
+#ifndef _HFS_H
+#define _HFS_H
+
+#include <linux/hfs_common.h>
+
+/*======== Data structures kept in memory ========*/
+
+struct hfs_readdir_data {
+ loff_t pos;
+ struct hfs_cat_key key;
+};
+
+#endif
diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
new file mode 100644
index 000000000..e250f87a5
--- /dev/null
+++ b/fs/hfs/hfs_fs.h
@@ -0,0 +1,319 @@
+/*
+ * linux/fs/hfs/hfs_fs.h
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ */
+
+#ifndef _LINUX_HFS_FS_H
+#define _LINUX_HFS_FS_H
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/workqueue.h>
+
+#include <asm/byteorder.h>
+#include <linux/uaccess.h>
+
+#include "hfs.h"
+
+/*
+ * struct hfs_inode_info
+ *
+ * The HFS-specific part of a Linux (struct inode)
+ */
+struct hfs_inode_info {
+ atomic_t opencnt;
+
+ unsigned int flags;
+
+ /* to deal with localtime ugliness */
+ int tz_secondswest;
+
+ struct hfs_cat_key cat_key;
+
+ struct inode *rsrc_inode;
+
+ struct mutex extents_lock;
+
+ u16 alloc_blocks, clump_blocks;
+ sector_t fs_blocks;
+ /* Allocation extents from catlog record or volume header */
+ hfs_extent_rec first_extents;
+ u16 first_blocks;
+ hfs_extent_rec cached_extents;
+ u16 cached_start, cached_blocks;
+
+ loff_t phys_size;
+ struct inode vfs_inode;
+};
+
+#define HFS_FLG_RSRC 0x0001
+#define HFS_FLG_EXT_DIRTY 0x0002
+#define HFS_FLG_EXT_NEW 0x0004
+
+#define HFS_IS_RSRC(inode) (HFS_I(inode)->flags & HFS_FLG_RSRC)
+
+/*
+ * struct hfs_sb_info
+ *
+ * The HFS-specific part of a Linux (struct super_block)
+ */
+struct hfs_sb_info {
+ struct mutex mdb_lock; /* MDB operations lock */
+ struct buffer_head *mdb_bh; /* The hfs_buffer
+ holding the real
+ superblock (aka VIB
+ or MDB) */
+ unsigned int mdb_offset; /* byte offset of the MDB
+ sector within mdb_bh's
+ data */
+ struct hfs_mdb *mdb; /* in-memory copy of the MDB */
+ struct buffer_head *alt_mdb_bh; /* The hfs_buffer holding
+ the alternate superblock */
+ unsigned int alt_mdb_offset; /* byte offset of the alternate
+ MDB sector within
+ alt_mdb_bh's data */
+ struct hfs_mdb *alt_mdb; /* in-memory copy of the
+ alternate MDB */
+ __be32 *bitmap; /* The page holding the
+ allocation bitmap */
+ struct hfs_btree *ext_tree; /* Information about
+ the extents b-tree */
+ struct hfs_btree *cat_tree; /* Information about
+ the catalog b-tree */
+ atomic64_t file_count; /* The number of
+ regular files in
+ the filesystem */
+ atomic64_t folder_count; /* The number of
+ directories in the
+ filesystem */
+ atomic64_t next_id; /* The next available
+ file id number */
+ u32 clumpablks; /* The number of allocation
+ blocks to try to add when
+ extending a file */
+ u32 fs_start; /* The first 512-byte
+ block represented
+ in the bitmap */
+ u32 part_start;
+ u16 root_files; /* The number of
+ regular
+ (non-directory)
+ files in the root
+ directory */
+ u16 root_dirs; /* The number of
+ directories in the
+ root directory */
+ u16 fs_ablocks; /* The number of
+ allocation blocks
+ in the filesystem */
+ u16 free_ablocks; /* the number of unused
+ allocation blocks
+ in the filesystem */
+ u32 alloc_blksz; /* The size of an
+ "allocation block" */
+ int s_quiet; /* Silent failure when
+ changing owner or mode? */
+ __be32 s_type; /* Type for new files */
+ __be32 s_creator; /* Creator for new files */
+ umode_t s_file_umask; /* The umask applied to the
+ permissions on all files */
+ umode_t s_dir_umask; /* The umask applied to the
+ permissions on all dirs */
+ kuid_t s_uid; /* The uid of all files */
+ kgid_t s_gid; /* The gid of all files */
+
+ int session, part;
+ struct nls_table *nls_io, *nls_disk;
+ struct mutex bitmap_lock;
+ unsigned long flags;
+ u16 blockoffset;
+ int fs_div;
+ struct super_block *sb;
+ int work_queued; /* non-zero delayed work is queued */
+ struct delayed_work mdb_work; /* MDB flush delayed work */
+ spinlock_t work_lock; /* protects mdb_work and work_queued */
+};
+
+#define HFS_FLG_BITMAP_DIRTY 0
+#define HFS_FLG_MDB_DIRTY 1
+#define HFS_FLG_ALT_MDB_DIRTY 2
+
+/* bitmap.c */
+extern u32 hfs_vbm_search_free(struct super_block *sb, u32 goal, u32 *num_bits);
+extern int hfs_clear_vbm_bits(struct super_block *sb, u16 start, u16 count);
+
+/* catalog.c */
+extern int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2);
+struct hfs_find_data;
+extern int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
+ struct hfs_find_data *fd);
+extern int hfs_cat_create(u32 cnid, struct inode *dir,
+ const struct qstr *str, struct inode *inode);
+extern int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str);
+extern int hfs_cat_move(u32 cnid, struct inode *src_dir,
+ const struct qstr *src_name,
+ struct inode *dst_dir,
+ const struct qstr *dst_name);
+extern void hfs_cat_build_key(struct super_block *sb, btree_key *key,
+ u32 parent, const struct qstr *name);
+
+/*
+ * Validate the CNID of a catalog record.
+ */
+static inline bool hfs_is_valid_cnid(u32 cnid, u8 type)
+{
+ if (likely(cnid >= HFS_FIRSTUSER_CNID))
+ return true;
+
+ switch (cnid) {
+ case HFS_ROOT_CNID:
+ return type == HFS_CDR_DIR;
+ case HFS_EXT_CNID:
+ case HFS_CAT_CNID:
+ return type == HFS_CDR_FIL;
+ default:
+ return false;
+ }
+}
+
+/* dir.c */
+extern const struct file_operations hfs_dir_operations;
+extern const struct inode_operations hfs_dir_inode_operations;
+
+/* extent.c */
+extern int hfs_ext_keycmp(const btree_key *key1, const btree_key *key2);
+extern u16 hfs_ext_find_block(struct hfs_extent *ext, u16 off);
+extern int hfs_free_fork(struct super_block *sb,
+ struct hfs_cat_file *file, int type);
+extern int hfs_ext_write_extent(struct inode *inode);
+extern int hfs_extend_file(struct inode *inode);
+extern void hfs_file_truncate(struct inode *inode);
+
+extern int hfs_get_block(struct inode *inode, sector_t block,
+ struct buffer_head *bh_result, int create);
+
+/* inode.c */
+extern const struct address_space_operations hfs_aops;
+extern const struct address_space_operations hfs_btree_aops;
+
+struct file_kattr;
+int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
+ loff_t pos, unsigned int len, struct folio **foliop,
+ void **fsdata);
+extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name,
+ umode_t mode);
+extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext,
+ __be32 *log_size, __be32 *phys_size);
+extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc);
+extern int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct iattr *attr);
+extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext,
+ __be32 __log_size, __be32 phys_size,
+ u32 clump_size);
+extern struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key,
+ hfs_cat_rec *rec);
+extern void hfs_evict_inode(struct inode *inode);
+extern void hfs_delete_inode(struct inode *inode);
+
+/* attr.c */
+extern const struct xattr_handler * const hfs_xattr_handlers[];
+
+/* mdb.c */
+extern bool is_hfs_cnid_counts_valid(struct super_block *sb);
+extern int hfs_mdb_get(struct super_block *sb);
+extern int hfs_mdb_commit(struct super_block *sb);
+extern void hfs_mdb_close(struct super_block *sb);
+extern void hfs_mdb_put(struct super_block *sb);
+
+/* part_tbl.c */
+extern int hfs_part_find(struct super_block *sb,
+ sector_t *part_start, sector_t *part_size);
+
+/* string.c */
+extern const struct dentry_operations hfs_dentry_operations;
+
+extern int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this);
+extern int hfs_strcmp(const unsigned char *s1, unsigned int len1,
+ const unsigned char *s2, unsigned int len2);
+extern int hfs_compare_dentry(const struct dentry *dentry,
+ unsigned int len, const char *str,
+ const struct qstr *name);
+
+/* trans.c */
+extern void hfs_asc2mac(struct super_block *sb,
+ struct hfs_name *out, const struct qstr *in);
+extern int hfs_mac2asc(struct super_block *sb,
+ char *out, const struct hfs_name *in);
+
+/* super.c */
+extern void hfs_mark_mdb_dirty(struct super_block *sb);
+
+/*
+ * There are two time systems. Both are based on seconds since
+ * a particular time/date.
+ * Unix: signed little-endian since 00:00 GMT, Jan. 1, 1970
+ * mac: unsigned big-endian since 00:00 GMT, Jan. 1, 1904
+ *
+ * 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 HFS_UTC_OFFSET (Jan 1 1970) as a time between 2040 and 2106.
+ */
+#define HFS_UTC_OFFSET 2082844800U
+
+static inline time64_t __hfs_m_to_utime(__be32 mt)
+{
+ time64_t ut = (u32)(be32_to_cpu(mt) - HFS_UTC_OFFSET);
+
+ return ut + sys_tz.tz_minuteswest * 60;
+}
+
+static inline __be32 __hfs_u_to_mtime(time64_t ut)
+{
+ ut -= sys_tz.tz_minuteswest * 60;
+
+ return cpu_to_be32(lower_32_bits(ut) + HFS_UTC_OFFSET);
+}
+#define HFS_I(inode) (container_of(inode, struct hfs_inode_info, vfs_inode))
+#define HFS_SB(sb) ((struct hfs_sb_info *)(sb)->s_fs_info)
+
+#define hfs_m_to_utime(time) (struct timespec64){ .tv_sec = __hfs_m_to_utime(time) }
+#define hfs_u_to_mtime(time) __hfs_u_to_mtime((time).tv_sec)
+#define hfs_mtime() __hfs_u_to_mtime(ktime_get_real_seconds())
+
+static inline const char *hfs_mdb_name(struct super_block *sb)
+{
+ return sb->s_id;
+}
+
+static inline void hfs_bitmap_dirty(struct super_block *sb)
+{
+ set_bit(HFS_FLG_BITMAP_DIRTY, &HFS_SB(sb)->flags);
+ hfs_mark_mdb_dirty(sb);
+}
+
+#define sb_bread512(sb, sec, data) ({ \
+ struct buffer_head *__bh; \
+ sector_t __block; \
+ loff_t __start; \
+ int __offset; \
+ \
+ __start = (loff_t)(sec) << HFS_SECTOR_SIZE_BITS;\
+ __block = __start >> (sb)->s_blocksize_bits; \
+ __offset = __start & ((sb)->s_blocksize - 1); \
+ __bh = sb_bread((sb), __block); \
+ if (likely(__bh != NULL)) \
+ data = (void *)(__bh->b_data + __offset);\
+ else \
+ data = NULL; \
+ __bh; \
+})
+
+#endif
diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
new file mode 100644
index 000000000..2aef3c36a
--- /dev/null
+++ b/fs/hfs/inode.c
@@ -0,0 +1,752 @@
+/*
+ * linux/fs/hfs/inode.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains inode-related functions which do not depend on
+ * which scheme is being used to represent forks.
+ *
+ * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds
+ */
+
+#include <linux/pagemap.h>
+#include <linux/mpage.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+#include <linux/uio.h>
+#include <linux/xattr.h>
+#include <linux/blkdev.h>
+#include <linux/fileattr.h>
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+static const struct file_operations hfs_file_operations;
+static const struct inode_operations hfs_file_inode_operations;
+
+/*================ Variable-like macros ================*/
+
+#define HFS_VALID_MODE_BITS (S_IFREG | S_IFDIR | S_IRWXUGO)
+
+static int hfs_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, hfs_get_block);
+}
+
+static void hfs_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);
+ hfs_file_truncate(inode);
+ }
+}
+
+int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
+ loff_t pos, unsigned int len, struct folio **foliop,
+ void **fsdata)
+{
+ struct inode *inode = mapping->host;
+ struct hfs_sb_info *sbi = HFS_SB(inode->i_sb);
+ loff_t total_capacity;
+ int ret;
+
+ total_capacity = (loff_t)sbi->fs_ablocks * sbi->alloc_blksz;
+
+ if (pos >= total_capacity)
+ return -EFBIG;
+
+ ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
+ hfs_get_block,
+ &HFS_I(inode)->phys_size);
+ if (unlikely(ret))
+ hfs_write_failed(mapping, pos + len);
+
+ return ret;
+}
+
+static sector_t hfs_bmap(struct address_space *mapping, sector_t block)
+{
+ return generic_block_bmap(mapping, block, hfs_get_block);
+}
+
+static bool hfs_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 HFS_EXT_CNID:
+ tree = HFS_SB(sb)->ext_tree;
+ break;
+ case HFS_CAT_CNID:
+ tree = HFS_SB(sb)->cat_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 hfs_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;
+ size_t count = iov_iter_count(iter);
+ ssize_t ret;
+
+ ret = blockdev_direct_IO(iocb, inode, iter, hfs_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)) {
+ loff_t isize = i_size_read(inode);
+ loff_t end = iocb->ki_pos + count;
+
+ if (end > isize)
+ hfs_write_failed(mapping, end);
+ }
+
+ return ret;
+}
+
+static int hfs_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, hfs_get_block);
+}
+
+const struct address_space_operations hfs_btree_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = hfs_read_folio,
+ .writepages = hfs_writepages,
+ .write_begin = hfs_write_begin,
+ .write_end = generic_write_end,
+ .migrate_folio = buffer_migrate_folio,
+ .bmap = hfs_bmap,
+ .release_folio = hfs_release_folio,
+};
+
+const struct address_space_operations hfs_aops = {
+ .dirty_folio = block_dirty_folio,
+ .invalidate_folio = block_invalidate_folio,
+ .read_folio = hfs_read_folio,
+ .write_begin = hfs_write_begin,
+ .write_end = generic_write_end,
+ .bmap = hfs_bmap,
+ .direct_IO = hfs_direct_IO,
+ .writepages = hfs_writepages,
+ .migrate_folio = buffer_migrate_folio,
+};
+
+/*
+ * hfs_new_inode
+ */
+struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t mode)
+{
+ struct super_block *sb = dir->i_sb;
+ struct inode *inode = new_inode(sb);
+ s64 next_id;
+ s64 file_count;
+ s64 folder_count;
+ int err = -ENOMEM;
+
+ if (!inode)
+ goto out_err;
+
+ err = -ERANGE;
+
+ mutex_init(&HFS_I(inode)->extents_lock);
+ hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name);
+ next_id = atomic64_inc_return(&HFS_SB(sb)->next_id);
+ if (next_id > U32_MAX) {
+ atomic64_dec(&HFS_SB(sb)->next_id);
+ pr_err("cannot create new inode: next CNID exceeds limit\n");
+ goto out_discard;
+ }
+ inode->i_ino = (u32)next_id - 1;
+ inode->i_mode = mode;
+ inode->i_uid = current_fsuid();
+ inode->i_gid = current_fsgid();
+ set_nlink(inode, 1);
+ simple_inode_init_ts(inode);
+ HFS_I(inode)->flags = 0;
+ HFS_I(inode)->rsrc_inode = NULL;
+ HFS_I(inode)->fs_blocks = 0;
+ HFS_I(inode)->tz_secondswest = sys_tz.tz_minuteswest * 60;
+ if (S_ISDIR(mode)) {
+ inode->i_size = 2;
+ folder_count = atomic64_inc_return(&HFS_SB(sb)->folder_count);
+ if (folder_count> U32_MAX) {
+ atomic64_dec(&HFS_SB(sb)->folder_count);
+ pr_err("cannot create new inode: folder count exceeds limit\n");
+ goto out_discard;
+ }
+ if (dir->i_ino == HFS_ROOT_CNID)
+ HFS_SB(sb)->root_dirs++;
+ inode->i_op = &hfs_dir_inode_operations;
+ inode->i_fop = &hfs_dir_operations;
+ inode->i_mode |= S_IRWXUGO;
+ inode->i_mode &= ~HFS_SB(inode->i_sb)->s_dir_umask;
+ } else if (S_ISREG(mode)) {
+ HFS_I(inode)->clump_blocks = HFS_SB(sb)->clumpablks;
+ file_count = atomic64_inc_return(&HFS_SB(sb)->file_count);
+ if (file_count > U32_MAX) {
+ atomic64_dec(&HFS_SB(sb)->file_count);
+ pr_err("cannot create new inode: file count exceeds limit\n");
+ goto out_discard;
+ }
+ if (dir->i_ino == HFS_ROOT_CNID)
+ HFS_SB(sb)->root_files++;
+ inode->i_op = &hfs_file_inode_operations;
+ inode->i_fop = &hfs_file_operations;
+ inode->i_mapping->a_ops = &hfs_aops;
+ inode->i_mode |= S_IRUGO|S_IXUGO;
+ if (mode & S_IWUSR)
+ inode->i_mode |= S_IWUGO;
+ inode->i_mode &= ~HFS_SB(inode->i_sb)->s_file_umask;
+ HFS_I(inode)->phys_size = 0;
+ HFS_I(inode)->alloc_blocks = 0;
+ HFS_I(inode)->first_blocks = 0;
+ HFS_I(inode)->cached_start = 0;
+ HFS_I(inode)->cached_blocks = 0;
+ memset(HFS_I(inode)->first_extents, 0, sizeof(hfs_extent_rec));
+ memset(HFS_I(inode)->cached_extents, 0, sizeof(hfs_extent_rec));
+ }
+ insert_inode_hash(inode);
+ mark_inode_dirty(inode);
+ set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
+ hfs_mark_mdb_dirty(sb);
+
+ return inode;
+
+ out_discard:
+ iput(inode);
+ out_err:
+ return ERR_PTR(err);
+}
+
+void hfs_delete_inode(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+
+ hfs_dbg("ino %llu\n", inode->i_ino);
+ if (S_ISDIR(inode->i_mode)) {
+ atomic64_dec(&HFS_SB(sb)->folder_count);
+ if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
+ HFS_SB(sb)->root_dirs--;
+ set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
+ hfs_mark_mdb_dirty(sb);
+ return;
+ }
+
+ atomic64_dec(&HFS_SB(sb)->file_count);
+ if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
+ HFS_SB(sb)->root_files--;
+ if (S_ISREG(inode->i_mode)) {
+ if (!inode->i_nlink) {
+ inode->i_size = 0;
+ hfs_file_truncate(inode);
+ }
+ }
+ set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
+ hfs_mark_mdb_dirty(sb);
+}
+
+void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext,
+ __be32 __log_size, __be32 phys_size, u32 clump_size)
+{
+ struct super_block *sb = inode->i_sb;
+ u32 log_size = be32_to_cpu(__log_size);
+ u16 count;
+ int i;
+
+ memcpy(HFS_I(inode)->first_extents, ext, sizeof(hfs_extent_rec));
+ for (count = 0, i = 0; i < 3; i++)
+ count += be16_to_cpu(ext[i].count);
+ HFS_I(inode)->first_blocks = count;
+ HFS_I(inode)->cached_start = 0;
+ HFS_I(inode)->cached_blocks = 0;
+
+ inode->i_size = HFS_I(inode)->phys_size = log_size;
+ HFS_I(inode)->fs_blocks = (log_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
+ inode_set_bytes(inode, HFS_I(inode)->fs_blocks << sb->s_blocksize_bits);
+ HFS_I(inode)->alloc_blocks = be32_to_cpu(phys_size) /
+ HFS_SB(sb)->alloc_blksz;
+ HFS_I(inode)->clump_blocks = clump_size / HFS_SB(sb)->alloc_blksz;
+ if (!HFS_I(inode)->clump_blocks)
+ HFS_I(inode)->clump_blocks = HFS_SB(sb)->clumpablks;
+}
+
+struct hfs_iget_data {
+ struct hfs_cat_key *key;
+ hfs_cat_rec *rec;
+};
+
+static int hfs_test_inode(struct inode *inode, void *data)
+{
+ struct hfs_iget_data *idata = data;
+ hfs_cat_rec *rec;
+
+ rec = idata->rec;
+ switch (rec->type) {
+ case HFS_CDR_DIR:
+ return inode->i_ino == be32_to_cpu(rec->dir.DirID);
+ case HFS_CDR_FIL:
+ return inode->i_ino == be32_to_cpu(rec->file.FlNum);
+ default:
+ BUG();
+ return 1;
+ }
+}
+
+/*
+ * hfs_read_inode
+ */
+static int hfs_read_inode(struct inode *inode, void *data)
+{
+ struct hfs_iget_data *idata = data;
+ struct hfs_sb_info *hsb = HFS_SB(inode->i_sb);
+ hfs_cat_rec *rec;
+ struct timespec64 mtime;
+
+ HFS_I(inode)->flags = 0;
+ HFS_I(inode)->rsrc_inode = NULL;
+ mutex_init(&HFS_I(inode)->extents_lock);
+
+ /* Initialize the inode */
+ inode->i_uid = hsb->s_uid;
+ inode->i_gid = hsb->s_gid;
+ set_nlink(inode, 1);
+
+ if (idata->key)
+ HFS_I(inode)->cat_key = *idata->key;
+ else
+ HFS_I(inode)->flags |= HFS_FLG_RSRC;
+ HFS_I(inode)->tz_secondswest = sys_tz.tz_minuteswest * 60;
+
+ rec = idata->rec;
+ switch (rec->type) {
+ case HFS_CDR_FIL:
+ if (!hfs_is_valid_cnid(be32_to_cpu(rec->file.FlNum), rec->type))
+ return -EIO;
+
+ if (!HFS_IS_RSRC(inode)) {
+ hfs_inode_read_fork(inode, rec->file.ExtRec, rec->file.LgLen,
+ rec->file.PyLen, be16_to_cpu(rec->file.ClpSize));
+ } else {
+ hfs_inode_read_fork(inode, rec->file.RExtRec, rec->file.RLgLen,
+ rec->file.RPyLen, be16_to_cpu(rec->file.ClpSize));
+ }
+
+ inode->i_ino = be32_to_cpu(rec->file.FlNum);
+ inode->i_mode = S_IRUGO | S_IXUGO;
+ if (!(rec->file.Flags & HFS_FIL_LOCK))
+ inode->i_mode |= S_IWUGO;
+ inode->i_mode &= ~hsb->s_file_umask;
+ inode->i_mode |= S_IFREG;
+ mtime = hfs_m_to_utime(rec->file.MdDat);
+ inode_set_ctime_to_ts(inode, mtime);
+ inode_set_atime_to_ts(inode, mtime);
+ inode_set_mtime_to_ts(inode, mtime);
+ inode->i_op = &hfs_file_inode_operations;
+ inode->i_fop = &hfs_file_operations;
+ inode->i_mapping->a_ops = &hfs_aops;
+ break;
+ case HFS_CDR_DIR:
+ if (!hfs_is_valid_cnid(be32_to_cpu(rec->dir.DirID), rec->type))
+ return -EIO;
+
+ inode->i_ino = be32_to_cpu(rec->dir.DirID);
+ inode->i_size = be16_to_cpu(rec->dir.Val) + 2;
+ HFS_I(inode)->fs_blocks = 0;
+ inode->i_mode = S_IFDIR | (S_IRWXUGO & ~hsb->s_dir_umask);
+ mtime = hfs_m_to_utime(rec->dir.MdDat);
+ inode_set_ctime_to_ts(inode, mtime);
+ inode_set_atime_to_ts(inode, mtime);
+ inode_set_mtime_to_ts(inode, mtime);
+ inode->i_op = &hfs_dir_inode_operations;
+ inode->i_fop = &hfs_dir_operations;
+ break;
+ default:
+ make_bad_inode(inode);
+ }
+ return 0;
+}
+
+/*
+ * __hfs_iget()
+ *
+ * Given the MDB for a HFS filesystem, a 'key' and an 'entry' in
+ * the catalog B-tree and the 'type' of the desired file return the
+ * inode for that file/directory or NULL. Note that 'type' indicates
+ * whether we want the actual file or directory, or the corresponding
+ * metadata (AppleDouble header file or CAP metadata file).
+ */
+struct inode *hfs_iget(struct super_block *sb, struct hfs_cat_key *key, hfs_cat_rec *rec)
+{
+ struct hfs_iget_data data = { key, rec };
+ struct inode *inode;
+ u32 cnid;
+
+ switch (rec->type) {
+ case HFS_CDR_DIR:
+ cnid = be32_to_cpu(rec->dir.DirID);
+ break;
+ case HFS_CDR_FIL:
+ cnid = be32_to_cpu(rec->file.FlNum);
+ break;
+ default:
+ return NULL;
+ }
+ inode = iget5_locked(sb, cnid, hfs_test_inode, hfs_read_inode, &data);
+ if (inode && (inode_state_read_once(inode) & I_NEW))
+ unlock_new_inode(inode);
+ return inode;
+}
+
+void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext,
+ __be32 *log_size, __be32 *phys_size)
+{
+ memcpy(ext, HFS_I(inode)->first_extents, sizeof(hfs_extent_rec));
+
+ if (log_size)
+ *log_size = cpu_to_be32(inode->i_size);
+ if (phys_size)
+ *phys_size = cpu_to_be32(HFS_I(inode)->alloc_blocks *
+ HFS_SB(inode->i_sb)->alloc_blksz);
+}
+
+int hfs_write_inode(struct inode *inode, struct writeback_control *wbc)
+{
+ struct inode *main_inode = inode;
+ struct hfs_find_data fd;
+ hfs_cat_rec rec;
+ int res;
+
+ hfs_dbg("ino %llu\n", inode->i_ino);
+ res = hfs_ext_write_extent(inode);
+ if (res)
+ return res;
+
+ if (inode->i_ino < HFS_FIRSTUSER_CNID) {
+ switch (inode->i_ino) {
+ case HFS_ROOT_CNID:
+ break;
+ case HFS_EXT_CNID:
+ hfs_btree_write(HFS_SB(inode->i_sb)->ext_tree);
+ return 0;
+ case HFS_CAT_CNID:
+ hfs_btree_write(HFS_SB(inode->i_sb)->cat_tree);
+ return 0;
+ default:
+ BUG();
+ return -EIO;
+ }
+ }
+
+ if (HFS_IS_RSRC(inode))
+ main_inode = HFS_I(inode)->rsrc_inode;
+
+ if (!main_inode->i_nlink)
+ return 0;
+
+ if (hfs_find_init(HFS_SB(main_inode->i_sb)->cat_tree, &fd))
+ /* panic? */
+ return -EIO;
+
+ res = -EIO;
+ if (HFS_I(main_inode)->cat_key.CName.len > HFS_NAMELEN)
+ goto out;
+ fd.search_key->cat = HFS_I(main_inode)->cat_key;
+ if (hfs_brec_find(&fd))
+ goto out;
+
+ if (S_ISDIR(main_inode->i_mode)) {
+ if (fd.entrylength < sizeof(struct hfs_cat_dir))
+ goto out;
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_dir));
+ if (rec.type != HFS_CDR_DIR ||
+ be32_to_cpu(rec.dir.DirID) != inode->i_ino) {
+ }
+
+ rec.dir.MdDat = hfs_u_to_mtime(inode_get_mtime(inode));
+ rec.dir.Val = cpu_to_be16(inode->i_size - 2);
+
+ hfs_bnode_write(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_dir));
+ } else if (HFS_IS_RSRC(inode)) {
+ if (fd.entrylength < sizeof(struct hfs_cat_file))
+ goto out;
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ hfs_inode_write_fork(inode, rec.file.RExtRec,
+ &rec.file.RLgLen, &rec.file.RPyLen);
+ hfs_bnode_write(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ } else {
+ if (fd.entrylength < sizeof(struct hfs_cat_file))
+ goto out;
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ if (rec.type != HFS_CDR_FIL ||
+ be32_to_cpu(rec.file.FlNum) != inode->i_ino) {
+ }
+
+ if (inode->i_mode & S_IWUSR)
+ rec.file.Flags &= ~HFS_FIL_LOCK;
+ else
+ rec.file.Flags |= HFS_FIL_LOCK;
+ hfs_inode_write_fork(inode, rec.file.ExtRec, &rec.file.LgLen, &rec.file.PyLen);
+ rec.file.MdDat = hfs_u_to_mtime(inode_get_mtime(inode));
+
+ hfs_bnode_write(fd.bnode, &rec, fd.entryoffset,
+ sizeof(struct hfs_cat_file));
+ }
+ res = 0;
+out:
+ hfs_find_exit(&fd);
+ return res;
+}
+
+static struct dentry *hfs_file_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct inode *inode = NULL;
+ hfs_cat_rec rec;
+ struct hfs_find_data fd;
+ int res;
+
+ if (HFS_IS_RSRC(dir) || strcmp(dentry->d_name.name, "rsrc"))
+ goto out;
+
+ inode = HFS_I(dir)->rsrc_inode;
+ if (inode)
+ goto out;
+
+ inode = new_inode(dir->i_sb);
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ res = hfs_find_init(HFS_SB(dir->i_sb)->cat_tree, &fd);
+ if (res) {
+ iput(inode);
+ return ERR_PTR(res);
+ }
+ fd.search_key->cat = HFS_I(dir)->cat_key;
+ res = hfs_brec_read(&fd, &rec, sizeof(rec));
+ if (!res) {
+ struct hfs_iget_data idata = { NULL, &rec };
+ res = hfs_read_inode(inode, &idata);
+ }
+ hfs_find_exit(&fd);
+ if (res) {
+ iput(inode);
+ return ERR_PTR(res);
+ }
+
+ if (is_bad_inode(inode)) {
+ iput(inode);
+ return ERR_PTR(-EIO);
+ }
+ HFS_I(inode)->rsrc_inode = dir;
+ HFS_I(dir)->rsrc_inode = inode;
+ igrab(dir);
+ inode_fake_hash(inode);
+ mark_inode_dirty(inode);
+ dont_mount(dentry);
+out:
+ return d_splice_alias(inode, dentry);
+}
+
+void hfs_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+ if (HFS_IS_RSRC(inode) && HFS_I(inode)->rsrc_inode) {
+ HFS_I(HFS_I(inode)->rsrc_inode)->rsrc_inode = NULL;
+ iput(HFS_I(inode)->rsrc_inode);
+ }
+}
+
+static int hfs_file_open(struct inode *inode, struct file *file)
+{
+ if (HFS_IS_RSRC(inode))
+ inode = HFS_I(inode)->rsrc_inode;
+ atomic_inc(&HFS_I(inode)->opencnt);
+ return 0;
+}
+
+static int hfs_file_release(struct inode *inode, struct file *file)
+{
+ //struct super_block *sb = inode->i_sb;
+
+ if (HFS_IS_RSRC(inode))
+ inode = HFS_I(inode)->rsrc_inode;
+ if (atomic_dec_and_test(&HFS_I(inode)->opencnt)) {
+ inode_lock(inode);
+ hfs_file_truncate(inode);
+ //if (inode->i_flags & S_DEAD) {
+ // hfs_delete_cat(inode->i_ino, HFSPLUS_SB(sb).hidden_dir, NULL);
+ // hfs_delete_inode(inode);
+ //}
+ inode_unlock(inode);
+ }
+ return 0;
+}
+
+int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct iattr *attr)
+{
+ struct inode *inode = d_inode(dentry);
+ struct hfs_sb_info *hsb = HFS_SB(inode->i_sb);
+ int error;
+
+ error = setattr_prepare(&nop_mnt_idmap, dentry, attr);
+ if (error)
+ return error;
+
+ /* no uig/gid changes and limit which mode bits can be set */
+ if (((attr->ia_valid & ATTR_UID) &&
+ (!uid_eq(attr->ia_uid, hsb->s_uid))) ||
+ ((attr->ia_valid & ATTR_GID) &&
+ (!gid_eq(attr->ia_gid, hsb->s_gid))) ||
+ ((attr->ia_valid & ATTR_MODE) &&
+ ((S_ISDIR(inode->i_mode) &&
+ (attr->ia_mode != inode->i_mode)) ||
+ (attr->ia_mode & ~HFS_VALID_MODE_BITS)))) {
+ return hsb->s_quiet ? 0 : error;
+ }
+
+ /* map file permissions to the closest allowable permissions in HFS */
+ if (attr->ia_valid & ATTR_MODE) {
+ /* Only the 'w' bits can ever change and only all together. */
+ if (attr->ia_mode & S_IWUSR)
+ attr->ia_mode = inode->i_mode | S_IWUGO;
+ else
+ attr->ia_mode = inode->i_mode & ~S_IWUGO;
+ attr->ia_mode &= S_ISDIR(inode->i_mode) ? ~hsb->s_dir_umask: ~hsb->s_file_umask;
+ }
+
+ if ((attr->ia_valid & ATTR_SIZE) &&
+ attr->ia_size != i_size_read(inode)) {
+ inode_dio_wait(inode);
+
+ error = inode_newsize_ok(inode, attr->ia_size);
+ if (error)
+ return error;
+
+ truncate_setsize(inode, attr->ia_size);
+ hfs_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;
+}
+
+static int hfs_file_fsync(struct file *filp, loff_t start, loff_t end,
+ int datasync)
+{
+ struct inode *inode = filp->f_mapping->host;
+ struct super_block * sb;
+ int ret, err;
+
+ ret = file_write_and_wait_range(filp, start, end);
+ if (ret)
+ return ret;
+ inode_lock(inode);
+
+ /* sync the inode to buffers */
+ ret = write_inode_now(inode, 0);
+
+ /* sync the superblock to buffers */
+ sb = inode->i_sb;
+ flush_delayed_work(&HFS_SB(sb)->mdb_work);
+ /* .. finally sync the buffers to disk */
+ err = sync_blockdev(sb->s_bdev);
+ if (!ret)
+ ret = err;
+ inode_unlock(inode);
+ return ret;
+}
+
+int hfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ /*
+ * HFS compares filenames using Mac OS Roman case folding, so
+ * lookup is always case-insensitive. Names are stored on disk
+ * with case intact; CASENONPRESERVING stays clear.
+ */
+ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
+ fa->flags |= FS_CASEFOLD_FL;
+ return 0;
+}
+
+static const struct file_operations hfs_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 = hfs_file_fsync,
+ .open = hfs_file_open,
+ .release = hfs_file_release,
+};
+
+static const struct inode_operations hfs_file_inode_operations = {
+ .lookup = hfs_file_lookup,
+ .setattr = hfs_inode_setattr,
+ .listxattr = generic_listxattr,
+ .fileattr_get = hfs_fileattr_get,
+};
diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c
new file mode 100644
index 000000000..277de712f
--- /dev/null
+++ b/fs/hfs/mdb.c
@@ -0,0 +1,462 @@
+/*
+ * linux/fs/hfs/mdb.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains functions for reading/writing the MDB.
+ */
+
+#include <linux/cdrom.h>
+#include <linux/blkdev.h>
+#include <linux/nls.h>
+#include <linux/slab.h>
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+/*================ File-local data types ================*/
+
+/*
+ * The HFS Master Directory Block (MDB).
+ *
+ * Also known as the Volume Information Block (VIB), this structure is
+ * the HFS equivalent of a superblock.
+ *
+ * Reference: _Inside Macintosh: Files_ pages 2-59 through 2-62
+ *
+ * modified for HFS Extended
+ */
+
+static int hfs_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 (HFS_SB(sb)->session >= 0) {
+ struct cdrom_tocentry te;
+
+ if (!cdi)
+ return -EINVAL;
+
+ te.cdte_track = HFS_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;
+}
+
+bool is_hfs_cnid_counts_valid(struct super_block *sb)
+{
+ struct hfs_sb_info *sbi = HFS_SB(sb);
+ bool corrupted = false;
+
+ if (unlikely(atomic64_read(&sbi->next_id) > U32_MAX)) {
+ pr_warn("next CNID exceeds limit\n");
+ corrupted = true;
+ }
+ if (unlikely(atomic64_read(&sbi->file_count) > U32_MAX)) {
+ pr_warn("file count exceeds limit\n");
+ corrupted = true;
+ }
+ if (unlikely(atomic64_read(&sbi->folder_count) > U32_MAX)) {
+ pr_warn("folder count exceeds limit\n");
+ corrupted = true;
+ }
+
+ return !corrupted;
+}
+
+/*
+ * hfs_sect_offset() - get byte offset within the buffer_head.
+ */
+static unsigned int hfs_sect_offset(struct super_block *sb, sector_t sec)
+{
+ loff_t start = (loff_t)sec << HFS_SECTOR_SIZE_BITS;
+
+ return start & (sb->s_blocksize - 1);
+}
+
+/*
+ * hfs_mdb_publish() - copy the in-memory primary MDB to the on-disk buffer.
+ */
+static void hfs_mdb_publish(struct hfs_sb_info *sbi)
+{
+ lock_buffer(sbi->mdb_bh);
+ memcpy(sbi->mdb_bh->b_data + sbi->mdb_offset, sbi->mdb, HFS_SECTOR_SIZE);
+ mark_buffer_dirty(sbi->mdb_bh);
+ unlock_buffer(sbi->mdb_bh);
+}
+
+/*
+ * hfs_alt_mdb_publish() - copy the in-memory alternate MDB to its buffer.
+ */
+static void hfs_alt_mdb_publish(struct hfs_sb_info *sbi)
+{
+ lock_buffer(sbi->alt_mdb_bh);
+ memcpy(sbi->alt_mdb_bh->b_data + sbi->alt_mdb_offset, sbi->alt_mdb,
+ HFS_SECTOR_SIZE);
+ mark_buffer_dirty(sbi->alt_mdb_bh);
+ unlock_buffer(sbi->alt_mdb_bh);
+}
+
+/*
+ * hfs_mdb_get()
+ *
+ * Build the in-core MDB for a filesystem, including
+ * the B-trees and the volume bitmap.
+ */
+int hfs_mdb_get(struct super_block *sb)
+{
+ struct buffer_head *bh;
+ struct hfs_mdb *mdb, *alt_mdb;
+ unsigned int block;
+ char *ptr;
+ int off2, len, size, sect;
+ sector_t part_start, part_size;
+ loff_t off;
+ __be16 attrib;
+
+ /* set the device driver to 512-byte blocks */
+ size = sb_min_blocksize(sb, HFS_SECTOR_SIZE);
+ if (!size)
+ return -EINVAL;
+
+ if (hfs_get_last_session(sb, &part_start, &part_size))
+ return -EINVAL;
+ while (1) {
+ /* See if this is an HFS filesystem */
+ bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb);
+ if (!bh)
+ return -EIO;
+
+ if (mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC))
+ break;
+ brelse(bh);
+
+ /* check for a partition block
+ * (should do this only for cdrom/loop though)
+ */
+ if (hfs_part_find(sb, &part_start, &part_size))
+ return -EIO;
+ }
+
+ HFS_SB(sb)->alloc_blksz = size = be32_to_cpu(mdb->drAlBlkSiz);
+ if (!size || (size & (HFS_SECTOR_SIZE - 1))) {
+ pr_err("bad allocation block size %d\n", size);
+ brelse(bh);
+ return -EIO;
+ }
+
+ size = min(HFS_SB(sb)->alloc_blksz, (u32)PAGE_SIZE);
+ /* size must be a multiple of 512 */
+ while (size & (size - 1))
+ size -= HFS_SECTOR_SIZE;
+ sect = be16_to_cpu(mdb->drAlBlSt) + part_start;
+ /* align block size to first sector */
+ while (sect & ((size - 1) >> HFS_SECTOR_SIZE_BITS))
+ size >>= 1;
+ /* align block size to weird alloc size */
+ while (HFS_SB(sb)->alloc_blksz & (size - 1))
+ size >>= 1;
+ brelse(bh);
+ if (!sb_set_blocksize(sb, size)) {
+ pr_err("unable to set blocksize to %u\n", size);
+ return -EIO;
+ }
+
+ bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb);
+ if (!bh)
+ return -EIO;
+ if (mdb->drSigWord != cpu_to_be16(HFS_SUPER_MAGIC)) {
+ brelse(bh);
+ return -EIO;
+ }
+
+ mdb = kmemdup(mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
+ if (!mdb) {
+ brelse(bh);
+ return -ENOMEM;
+ }
+
+ HFS_SB(sb)->mdb_bh = bh;
+ HFS_SB(sb)->mdb_offset = hfs_sect_offset(sb, part_start + HFS_MDB_BLK);
+ HFS_SB(sb)->mdb = mdb;
+
+ /* These parameters are read from the MDB, and never written */
+ HFS_SB(sb)->part_start = part_start;
+ HFS_SB(sb)->fs_ablocks = be16_to_cpu(mdb->drNmAlBlks);
+ HFS_SB(sb)->fs_div = HFS_SB(sb)->alloc_blksz >> sb->s_blocksize_bits;
+ HFS_SB(sb)->clumpablks = be32_to_cpu(mdb->drClpSiz) /
+ HFS_SB(sb)->alloc_blksz;
+ if (!HFS_SB(sb)->clumpablks)
+ HFS_SB(sb)->clumpablks = 1;
+ HFS_SB(sb)->fs_start = (be16_to_cpu(mdb->drAlBlSt) + part_start) >>
+ (sb->s_blocksize_bits - HFS_SECTOR_SIZE_BITS);
+
+ /* These parameters are read from and written to the MDB */
+ HFS_SB(sb)->free_ablocks = be16_to_cpu(mdb->drFreeBks);
+ atomic64_set(&HFS_SB(sb)->next_id, be32_to_cpu(mdb->drNxtCNID));
+ HFS_SB(sb)->root_files = be16_to_cpu(mdb->drNmFls);
+ HFS_SB(sb)->root_dirs = be16_to_cpu(mdb->drNmRtDirs);
+ atomic64_set(&HFS_SB(sb)->file_count, be32_to_cpu(mdb->drFilCnt));
+ atomic64_set(&HFS_SB(sb)->folder_count, be32_to_cpu(mdb->drDirCnt));
+
+ if (!is_hfs_cnid_counts_valid(sb)) {
+ pr_warn("filesystem possibly corrupted, running fsck.hfs is recommended. Mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+
+ /* TRY to get the alternate (backup) MDB. */
+ sect = part_start + part_size - 2;
+ bh = sb_bread512(sb, sect, alt_mdb);
+ if (bh) {
+ if (alt_mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) {
+ alt_mdb = kmemdup(alt_mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
+ if (alt_mdb) {
+ HFS_SB(sb)->alt_mdb_bh = bh;
+ HFS_SB(sb)->alt_mdb_offset =
+ hfs_sect_offset(sb, sect);
+ HFS_SB(sb)->alt_mdb = alt_mdb;
+ } else {
+ brelse(bh);
+ }
+ } else
+ brelse(bh);
+ }
+
+ if (!HFS_SB(sb)->alt_mdb) {
+ pr_warn("unable to locate alternate MDB\n");
+ pr_warn("continuing without an alternate MDB\n");
+ }
+
+ HFS_SB(sb)->bitmap = kzalloc(8192, GFP_KERNEL);
+ if (!HFS_SB(sb)->bitmap)
+ return -EIO;
+
+ /* read in the bitmap */
+ block = be16_to_cpu(mdb->drVBMSt) + part_start;
+ off = (loff_t)block << HFS_SECTOR_SIZE_BITS;
+ size = (HFS_SB(sb)->fs_ablocks + 8) / 8;
+ ptr = (u8 *)HFS_SB(sb)->bitmap;
+ while (size) {
+ bh = sb_bread(sb, off >> sb->s_blocksize_bits);
+ if (!bh) {
+ pr_err("unable to read volume bitmap\n");
+ return -EIO;
+ }
+ off2 = off & (sb->s_blocksize - 1);
+ len = min((int)sb->s_blocksize - off2, size);
+ memcpy(ptr, bh->b_data + off2, len);
+ brelse(bh);
+ ptr += len;
+ off += len;
+ size -= len;
+ }
+
+ HFS_SB(sb)->ext_tree = hfs_btree_open(sb, HFS_EXT_CNID, hfs_ext_keycmp);
+ if (!HFS_SB(sb)->ext_tree) {
+ pr_err("unable to open extent tree\n");
+ return -EIO;
+ }
+ HFS_SB(sb)->cat_tree = hfs_btree_open(sb, HFS_CAT_CNID, hfs_cat_keycmp);
+ if (!HFS_SB(sb)->cat_tree) {
+ pr_err("unable to open catalog tree\n");
+ return -EIO;
+ }
+
+ attrib = mdb->drAtrb;
+ if (!(attrib & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) {
+ pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended. Mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+ if ((attrib & cpu_to_be16(HFS_SB_ATTRIB_SLOCK))) {
+ pr_warn("filesystem is marked locked, mounting read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ }
+ if (!sb_rdonly(sb)) {
+ /* Mark the volume uncleanly unmounted in case we crash */
+ attrib &= cpu_to_be16(~HFS_SB_ATTRIB_UNMNT);
+ attrib |= cpu_to_be16(HFS_SB_ATTRIB_INCNSTNT);
+ mdb->drAtrb = attrib;
+ be32_add_cpu(&mdb->drWrCnt, 1);
+ mdb->drLsMod = hfs_mtime();
+
+ hfs_mdb_publish(HFS_SB(sb));
+ sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
+ }
+
+ return 0;
+}
+
+/*
+ * hfs_mdb_commit()
+ *
+ * Description:
+ * This updates the MDB on disk.
+ * It does not check, if the superblock has been modified, or
+ * if the filesystem has been mounted read-only. It is mainly
+ * called by hfs_sync_fs() and flush_mdb().
+ * Input Variable(s):
+ * struct hfs_mdb *mdb: Pointer to the hfs MDB
+ * int backup;
+ * Output Variable(s):
+ * NONE
+ * Returns:
+ * 0 on success, -EIO if the MDB or alternate MDB buffer is no longer
+ * valid (e.g. after a prior write error), in which case the volume is
+ * remounted read-only.
+ * Preconditions:
+ * 'mdb' points to a "valid" (struct hfs_mdb).
+ * Postconditions:
+ * The HFS MDB and on disk will be updated, by copying the possibly
+ * modified fields from the in memory MDB (in native byte order) to
+ * the disk block buffer.
+ * If 'backup' is non-zero then the alternate MDB is also written
+ * and the function doesn't return until it is actually on disk.
+ */
+int hfs_mdb_commit(struct super_block *sb)
+{
+ struct hfs_mdb *mdb = HFS_SB(sb)->mdb;
+ int ret = 0;
+
+ if (sb_rdonly(sb))
+ return 0;
+
+ if (!buffer_uptodate(HFS_SB(sb)->mdb_bh)) {
+ pr_err("primary MDB is corrupt, mounting read-only\n");
+ sb->s_flags |= SB_RDONLY;
+ return -EIO;
+ }
+
+ if (test_and_clear_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags)) {
+ /* These parameters may have been modified, so write them back */
+ mdb->drLsMod = hfs_mtime();
+ mdb->drFreeBks = cpu_to_be16(HFS_SB(sb)->free_ablocks);
+ mdb->drNxtCNID =
+ cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->next_id));
+ mdb->drNmFls = cpu_to_be16(HFS_SB(sb)->root_files);
+ mdb->drNmRtDirs = cpu_to_be16(HFS_SB(sb)->root_dirs);
+ mdb->drFilCnt =
+ cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->file_count));
+ mdb->drDirCnt =
+ cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count));
+
+ hfs_inode_write_fork(HFS_SB(sb)->ext_tree->inode, mdb->drXTExtRec,
+ &mdb->drXTFlSize, NULL);
+ hfs_inode_write_fork(HFS_SB(sb)->cat_tree->inode, mdb->drCTExtRec,
+ &mdb->drCTFlSize, NULL);
+
+ /* write MDB to disk */
+ hfs_mdb_publish(HFS_SB(sb));
+ sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
+ }
+
+ /* write the backup MDB, not returning until it is written.
+ * we only do this when either the catalog or extents overflow
+ * files grow. */
+ if (test_and_clear_bit(HFS_FLG_ALT_MDB_DIRTY, &HFS_SB(sb)->flags) &&
+ HFS_SB(sb)->alt_mdb) {
+ if (!buffer_uptodate(HFS_SB(sb)->alt_mdb_bh)) {
+ pr_err("alternate MDB is corrupt, mounting read-only\n");
+ sb->s_flags |= SB_RDONLY;
+ ret = -EIO;
+ goto out;
+ }
+
+ memcpy(HFS_SB(sb)->alt_mdb, mdb, HFS_SECTOR_SIZE);
+ HFS_SB(sb)->alt_mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
+ HFS_SB(sb)->alt_mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
+
+ hfs_alt_mdb_publish(HFS_SB(sb));
+ sync_dirty_buffer(HFS_SB(sb)->alt_mdb_bh);
+ }
+
+ if (test_and_clear_bit(HFS_FLG_BITMAP_DIRTY, &HFS_SB(sb)->flags)) {
+ struct buffer_head *bh;
+ sector_t block;
+ char *ptr;
+ int off, size, len;
+
+ block = be16_to_cpu(HFS_SB(sb)->mdb->drVBMSt) + HFS_SB(sb)->part_start;
+ off = (block << HFS_SECTOR_SIZE_BITS) & (sb->s_blocksize - 1);
+ block >>= sb->s_blocksize_bits - HFS_SECTOR_SIZE_BITS;
+ size = (HFS_SB(sb)->fs_ablocks + 7) / 8;
+ ptr = (u8 *)HFS_SB(sb)->bitmap;
+ while (size) {
+ bh = sb_bread(sb, block);
+ if (!bh) {
+ pr_err("unable to read volume bitmap\n");
+ break;
+ }
+ len = min((int)sb->s_blocksize - off, size);
+
+ lock_buffer(bh);
+ memcpy(bh->b_data + off, ptr, len);
+ unlock_buffer(bh);
+
+ mark_buffer_dirty(bh);
+ brelse(bh);
+ block++;
+ off = 0;
+ ptr += len;
+ size -= len;
+ }
+ }
+out:
+ return ret;
+}
+
+void hfs_mdb_close(struct super_block *sb)
+{
+ /* update volume attributes */
+ if (sb_rdonly(sb))
+ return;
+
+ if (!buffer_uptodate(HFS_SB(sb)->mdb_bh))
+ return;
+
+ HFS_SB(sb)->mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
+ HFS_SB(sb)->mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
+ hfs_mdb_publish(HFS_SB(sb));
+}
+
+/*
+ * hfs_mdb_put()
+ *
+ * Release the resources associated with the in-core MDB. */
+void hfs_mdb_put(struct super_block *sb)
+{
+ /* free the B-trees */
+ hfs_btree_close(HFS_SB(sb)->ext_tree);
+ hfs_btree_close(HFS_SB(sb)->cat_tree);
+
+ /* free the buffers holding the primary and alternate MDBs */
+ brelse(HFS_SB(sb)->mdb_bh);
+ brelse(HFS_SB(sb)->alt_mdb_bh);
+ kfree(HFS_SB(sb)->mdb);
+ kfree(HFS_SB(sb)->alt_mdb);
+
+ unload_nls(HFS_SB(sb)->nls_io);
+ unload_nls(HFS_SB(sb)->nls_disk);
+
+ kfree(HFS_SB(sb)->bitmap);
+}
diff --git a/fs/hfs/part_tbl.c b/fs/hfs/part_tbl.c
new file mode 100644
index 000000000..36add537d
--- /dev/null
+++ b/fs/hfs/part_tbl.c
@@ -0,0 +1,117 @@
+/*
+ * linux/fs/hfs/part_tbl.c
+ *
+ * Copyright (C) 1996-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * 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).
+ */
+
+#include "hfs_fs.h"
+
+/*
+ * 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;
+
+/*
+ * hfs_part_find()
+ *
+ * Parse the partition map looking for the
+ * start and length of the 'part'th HFS partition.
+ */
+int hfs_part_find(struct super_block *sb,
+ sector_t *part_start, sector_t *part_size)
+{
+ struct buffer_head *bh;
+ __be16 *data;
+ int i, size, res;
+
+ res = -ENOENT;
+ bh = sb_bread512(sb, *part_start + HFS_PMAP_BLK, data);
+ if (!bh)
+ return -EIO;
+
+ switch (be16_to_cpu(*data)) {
+ case HFS_OLD_PMAP_MAGIC:
+ {
+ struct old_pmap *pm;
+ struct old_pmap_entry *p;
+
+ pm = (struct old_pmap *)bh->b_data;
+ p = pm->pdEntry;
+ size = 42;
+ for (i = 0; i < size; p++, i++) {
+ if (p->pdStart && p->pdSize &&
+ p->pdFSID == cpu_to_be32(0x54465331)/*"TFS1"*/ &&
+ (HFS_SB(sb)->part < 0 || HFS_SB(sb)->part == i)) {
+ *part_start += be32_to_cpu(p->pdStart);
+ *part_size = be32_to_cpu(p->pdSize);
+ res = 0;
+ }
+ }
+ break;
+ }
+ case HFS_NEW_PMAP_MAGIC:
+ {
+ struct new_pmap *pm;
+
+ pm = (struct new_pmap *)bh->b_data;
+ size = be32_to_cpu(pm->pmMapBlkCnt);
+ for (i = 0; i < size;) {
+ if (!memcmp(pm->pmPartType,"Apple_HFS", 9) &&
+ (HFS_SB(sb)->part < 0 || HFS_SB(sb)->part == i)) {
+ *part_start += be32_to_cpu(pm->pmPyPartStart);
+ *part_size = be32_to_cpu(pm->pmPartBlkCnt);
+ res = 0;
+ break;
+ }
+ brelse(bh);
+ bh = sb_bread512(sb, *part_start + HFS_PMAP_BLK + ++i, pm);
+ if (!bh)
+ return -EIO;
+ if (pm->pmSig != cpu_to_be16(HFS_NEW_PMAP_MAGIC))
+ break;
+ }
+ break;
+ }
+ }
+ brelse(bh);
+
+ return res;
+}
diff --git a/fs/hfs/string.c b/fs/hfs/string.c
new file mode 100644
index 000000000..0cfa35e82
--- /dev/null
+++ b/fs/hfs/string.c
@@ -0,0 +1,119 @@
+/*
+ * linux/fs/hfs/string.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains the string comparison function for the
+ * Macintosh character set.
+ *
+ * The code in this file is derived from code which is copyright
+ * 1986, 1989, 1990 by Abacus Research and Development, Inc. (ARDI)
+ * It is used here by the permission of ARDI's president Cliff Matthews.
+ */
+
+#include "hfs_fs.h"
+#include <linux/dcache.h>
+
+#include <kunit/visibility.h>
+
+/*================ File-local variables ================*/
+
+/*
+ * unsigned char caseorder[]
+ *
+ * Defines the lexical ordering of characters on the Macintosh
+ *
+ * Composition of the 'casefold' and 'order' tables from ARDI's code
+ * with the entry for 0x20 changed to match that for 0xCA to remove
+ * special case for those two characters.
+ */
+static unsigned char caseorder[256] = {
+ 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
+ 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
+ 0x20,0x22,0x23,0x28,0x29,0x2A,0x2B,0x2C,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,
+ 0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44,0x45,0x46,
+ 0x47,0x48,0x57,0x59,0x5D,0x5F,0x66,0x68,0x6A,0x6C,0x72,0x74,0x76,0x78,0x7A,0x7E,
+ 0x8C,0x8E,0x90,0x92,0x95,0x97,0x9E,0xA0,0xA2,0xA4,0xA7,0xA9,0xAA,0xAB,0xAC,0xAD,
+ 0x4E,0x48,0x57,0x59,0x5D,0x5F,0x66,0x68,0x6A,0x6C,0x72,0x74,0x76,0x78,0x7A,0x7E,
+ 0x8C,0x8E,0x90,0x92,0x95,0x97,0x9E,0xA0,0xA2,0xA4,0xA7,0xAF,0xB0,0xB1,0xB2,0xB3,
+ 0x4A,0x4C,0x5A,0x60,0x7B,0x7F,0x98,0x4F,0x49,0x51,0x4A,0x4B,0x4C,0x5A,0x60,0x63,
+ 0x64,0x65,0x6E,0x6F,0x70,0x71,0x7B,0x84,0x85,0x86,0x7F,0x80,0x9A,0x9B,0x9C,0x98,
+ 0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0x94,0xBB,0xBC,0xBD,0xBE,0xBF,0xC0,0x4D,0x81,
+ 0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0x55,0x8A,0xCC,0x4D,0x81,
+ 0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0x26,0x27,0xD4,0x20,0x49,0x4B,0x80,0x82,0x82,
+ 0xD5,0xD6,0x24,0x25,0x2D,0x2E,0xD7,0xD8,0xA6,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
+};
+
+/*================ Global functions ================*/
+
+/*
+ * Hash a string to an integer in a case-independent way
+ */
+int hfs_hash_dentry(const struct dentry *dentry, struct qstr *this)
+{
+ const unsigned char *name = this->name;
+ unsigned int hash, len = this->len;
+
+ if (len > HFS_NAMELEN)
+ len = HFS_NAMELEN;
+
+ hash = init_name_hash(dentry);
+ for (; len; len--)
+ hash = partial_name_hash(caseorder[*name++], hash);
+ this->hash = end_name_hash(hash);
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfs_hash_dentry);
+
+/*
+ * Compare two strings in the HFS filename character ordering
+ * Returns positive, negative, or zero, not just 0 or (+/-)1
+ *
+ * Equivalent to ARDI's call:
+ * ROMlib_RelString(s1+1, s2+1, true, false, (s1[0]<<16) | s2[0])
+ */
+int hfs_strcmp(const unsigned char *s1, unsigned int len1,
+ const unsigned char *s2, unsigned int len2)
+{
+ int len, tmp;
+
+ len = (len1 > len2) ? len2 : len1;
+
+ while (len--) {
+ tmp = (int)caseorder[*(s1++)] - (int)caseorder[*(s2++)];
+ if (tmp)
+ return tmp;
+ }
+ return len1 - len2;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfs_strcmp);
+
+/*
+ * Test for equality of two strings in the HFS filename character ordering.
+ * return 1 on failure and 0 on success
+ */
+int hfs_compare_dentry(const struct dentry *dentry,
+ unsigned int len, const char *str, const struct qstr *name)
+{
+ const unsigned char *n1, *n2;
+
+ if (len >= HFS_NAMELEN) {
+ if (name->len < HFS_NAMELEN)
+ return 1;
+ len = HFS_NAMELEN;
+ } else if (len != name->len)
+ return 1;
+
+ n1 = str;
+ n2 = name->name;
+ while (len--) {
+ if (caseorder[*n1++] != caseorder[*n2++])
+ return 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(hfs_compare_dentry);
diff --git a/fs/hfs/string_test.c b/fs/hfs/string_test.c
new file mode 100644
index 000000000..e1bf6f954
--- /dev/null
+++ b/fs/hfs/string_test.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for HFS string operations
+ *
+ * Copyright (C) 2025 Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/dcache.h>
+#include "hfs_fs.h"
+
+/* Test hfs_strcmp function */
+static void hfs_strcmp_test(struct kunit *test)
+{
+ /* Test equal strings */
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("hello", 5, "hello", 5));
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("test", 4, "test", 4));
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("", 0, "", 0));
+
+ /* Test unequal strings */
+ KUNIT_EXPECT_NE(test, 0, hfs_strcmp("hello", 5, "world", 5));
+ KUNIT_EXPECT_NE(test, 0, hfs_strcmp("test", 4, "testing", 7));
+
+ /* Test different lengths */
+ KUNIT_EXPECT_LT(test, hfs_strcmp("test", 4, "testing", 7), 0);
+ KUNIT_EXPECT_GT(test, hfs_strcmp("testing", 7, "test", 4), 0);
+
+ /* Test case insensitive comparison (HFS should handle case) */
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("Test", 4, "TEST", 4));
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("hello", 5, "HELLO", 5));
+
+ /* Test with special characters */
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("file.txt", 8, "file.txt", 8));
+ KUNIT_EXPECT_NE(test, 0, hfs_strcmp("file.txt", 8, "file.dat", 8));
+
+ /* Test boundary cases */
+ KUNIT_EXPECT_EQ(test, 0, hfs_strcmp("a", 1, "a", 1));
+ KUNIT_EXPECT_NE(test, 0, hfs_strcmp("a", 1, "b", 1));
+}
+
+/* Test hfs_hash_dentry function */
+static void hfs_hash_dentry_test(struct kunit *test)
+{
+ struct qstr test_name1, test_name2, test_name3;
+ struct dentry dentry = {};
+ char name1[] = "testfile";
+ char name2[] = "TestFile";
+ char name3[] = "different";
+
+ /* Initialize test strings */
+ test_name1.name = name1;
+ test_name1.len = strlen(name1);
+ test_name1.hash = 0;
+
+ test_name2.name = name2;
+ test_name2.len = strlen(name2);
+ test_name2.hash = 0;
+
+ test_name3.name = name3;
+ test_name3.len = strlen(name3);
+ test_name3.hash = 0;
+
+ /* Test hashing */
+ KUNIT_EXPECT_EQ(test, 0, hfs_hash_dentry(&dentry, &test_name1));
+ KUNIT_EXPECT_EQ(test, 0, hfs_hash_dentry(&dentry, &test_name2));
+ KUNIT_EXPECT_EQ(test, 0, hfs_hash_dentry(&dentry, &test_name3));
+
+ /* Case insensitive names should hash the same */
+ KUNIT_EXPECT_EQ(test, test_name1.hash, test_name2.hash);
+
+ /* Different names should have different hashes */
+ KUNIT_EXPECT_NE(test, test_name1.hash, test_name3.hash);
+}
+
+/* Test hfs_compare_dentry function */
+static void hfs_compare_dentry_test(struct kunit *test)
+{
+ struct qstr test_name;
+ struct dentry dentry = {};
+ char name[] = "TestFile";
+
+ test_name.name = name;
+ test_name.len = strlen(name);
+
+ /* Test exact match */
+ KUNIT_EXPECT_EQ(test, 0, hfs_compare_dentry(&dentry, 8,
+ "TestFile", &test_name));
+
+ /* Test case insensitive match */
+ KUNIT_EXPECT_EQ(test, 0, hfs_compare_dentry(&dentry, 8,
+ "testfile", &test_name));
+ KUNIT_EXPECT_EQ(test, 0, hfs_compare_dentry(&dentry, 8,
+ "TESTFILE", &test_name));
+
+ /* Test different names */
+ KUNIT_EXPECT_EQ(test, 1, hfs_compare_dentry(&dentry, 8,
+ "DiffFile", &test_name));
+
+ /* Test different lengths */
+ KUNIT_EXPECT_EQ(test, 1, hfs_compare_dentry(&dentry, 7,
+ "TestFil", &test_name));
+ KUNIT_EXPECT_EQ(test, 1, hfs_compare_dentry(&dentry, 9,
+ "TestFiles", &test_name));
+
+ /* Test empty string */
+ test_name.name = "";
+ test_name.len = 0;
+ KUNIT_EXPECT_EQ(test, 0, hfs_compare_dentry(&dentry, 0, "", &test_name));
+
+ /* Test HFS_NAMELEN boundary */
+ test_name.name = "This_is_a_very_long_filename_that_exceeds_normal_limits";
+ test_name.len = strlen(test_name.name);
+ KUNIT_EXPECT_EQ(test, 0, hfs_compare_dentry(&dentry, HFS_NAMELEN,
+ "This_is_a_very_long_filename_th", &test_name));
+}
+
+static struct kunit_case hfs_string_test_cases[] = {
+ KUNIT_CASE(hfs_strcmp_test),
+ KUNIT_CASE(hfs_hash_dentry_test),
+ KUNIT_CASE(hfs_compare_dentry_test),
+ {}
+};
+
+static struct kunit_suite hfs_string_test_suite = {
+ .name = "hfs_string",
+ .test_cases = hfs_string_test_cases,
+};
+
+kunit_test_suite(hfs_string_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for HFS string operations");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
diff --git a/fs/hfs/super.c b/fs/hfs/super.c
new file mode 100644
index 000000000..5e6d66ff4
--- /dev/null
+++ b/fs/hfs/super.c
@@ -0,0 +1,503 @@
+/*
+ * linux/fs/hfs/super.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains hfs_read_super(), some of the super_ops and
+ * init_hfs_fs() and exit_hfs_fs(). The remaining super_ops are in
+ * inode.c since they deal with inodes.
+ *
+ * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds
+ */
+
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/backing-dev.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/mount.h>
+#include <linux/init.h>
+#include <linux/nls.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/vfs.h>
+
+#include "hfs_fs.h"
+#include "btree.h"
+
+static struct kmem_cache *hfs_inode_cachep;
+
+MODULE_DESCRIPTION("Apple Macintosh file system support");
+MODULE_LICENSE("GPL");
+
+static int hfs_sync_fs(struct super_block *sb, int wait)
+{
+ int ret;
+
+ mutex_lock(&HFS_SB(sb)->mdb_lock);
+ is_hfs_cnid_counts_valid(sb);
+ ret = hfs_mdb_commit(sb);
+ mutex_unlock(&HFS_SB(sb)->mdb_lock);
+
+ return ret;
+}
+
+/*
+ * hfs_put_super()
+ *
+ * This is the put_super() entry in the super_operations structure for
+ * HFS filesystems. The purpose is to release the resources
+ * associated with the superblock sb.
+ */
+static void hfs_put_super(struct super_block *sb)
+{
+ cancel_delayed_work_sync(&HFS_SB(sb)->mdb_work);
+ hfs_mdb_close(sb);
+ /* release the MDB's resources */
+ hfs_mdb_put(sb);
+}
+
+static void flush_mdb(struct work_struct *work)
+{
+ struct hfs_sb_info *sbi;
+ struct super_block *sb;
+
+ sbi = container_of(work, struct hfs_sb_info, mdb_work.work);
+ sb = sbi->sb;
+
+ spin_lock(&sbi->work_lock);
+ sbi->work_queued = 0;
+ spin_unlock(&sbi->work_lock);
+
+ mutex_lock(&sbi->mdb_lock);
+ is_hfs_cnid_counts_valid(sb);
+ hfs_mdb_commit(sb);
+ mutex_unlock(&sbi->mdb_lock);
+}
+
+void hfs_mark_mdb_dirty(struct super_block *sb)
+{
+ struct hfs_sb_info *sbi = HFS_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->mdb_work, delay);
+ sbi->work_queued = 1;
+ }
+ spin_unlock(&sbi->work_lock);
+}
+
+/*
+ * hfs_statfs()
+ *
+ * This is the statfs() entry in the super_operations structure for
+ * HFS filesystems. The purpose is to return various data about the
+ * filesystem.
+ *
+ * changed f_files/f_ffree to reflect the fs_ablock/free_ablocks.
+ */
+static int hfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ struct super_block *sb = dentry->d_sb;
+ u64 id = huge_encode_dev(sb->s_bdev->bd_dev);
+
+ buf->f_type = HFS_SUPER_MAGIC;
+ buf->f_bsize = sb->s_blocksize;
+ buf->f_blocks = (u32)HFS_SB(sb)->fs_ablocks * HFS_SB(sb)->fs_div;
+ buf->f_bfree = (u32)HFS_SB(sb)->free_ablocks * HFS_SB(sb)->fs_div;
+ buf->f_bavail = buf->f_bfree;
+ buf->f_files = HFS_SB(sb)->fs_ablocks;
+ buf->f_ffree = HFS_SB(sb)->free_ablocks;
+ buf->f_fsid = u64_to_fsid(id);
+ buf->f_namelen = HFS_NAMELEN;
+
+ return 0;
+}
+
+static int hfs_reconfigure(struct fs_context *fc)
+{
+ struct super_block *sb = fc->root->d_sb;
+
+ sync_filesystem(sb);
+ fc->sb_flags |= SB_NODIRATIME;
+ if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb))
+ return 0;
+
+ if (!(fc->sb_flags & SB_RDONLY)) {
+ if (!(HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) {
+ pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended. leaving read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ fc->sb_flags |= SB_RDONLY;
+ } else if (HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_SLOCK)) {
+ pr_warn("filesystem is marked locked, leaving read-only.\n");
+ sb->s_flags |= SB_RDONLY;
+ fc->sb_flags |= SB_RDONLY;
+ }
+ }
+ return 0;
+}
+
+static int hfs_show_options(struct seq_file *seq, struct dentry *root)
+{
+ struct hfs_sb_info *sbi = HFS_SB(root->d_sb);
+
+ if (sbi->s_creator != cpu_to_be32(0x3f3f3f3f))
+ seq_show_option_n(seq, "creator", (char *)&sbi->s_creator, 4);
+ if (sbi->s_type != cpu_to_be32(0x3f3f3f3f))
+ seq_show_option_n(seq, "type", (char *)&sbi->s_type, 4);
+ seq_printf(seq, ",uid=%u,gid=%u",
+ from_kuid_munged(&init_user_ns, sbi->s_uid),
+ from_kgid_munged(&init_user_ns, sbi->s_gid));
+ if (sbi->s_file_umask != 0133)
+ seq_printf(seq, ",file_umask=%o", sbi->s_file_umask);
+ if (sbi->s_dir_umask != 0022)
+ seq_printf(seq, ",dir_umask=%o", sbi->s_dir_umask);
+ 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_disk)
+ seq_printf(seq, ",codepage=%s", sbi->nls_disk->charset);
+ if (sbi->nls_io)
+ seq_printf(seq, ",iocharset=%s", sbi->nls_io->charset);
+ if (sbi->s_quiet)
+ seq_printf(seq, ",quiet");
+ return 0;
+}
+
+static struct inode *hfs_alloc_inode(struct super_block *sb)
+{
+ struct hfs_inode_info *i;
+
+ i = alloc_inode_sb(sb, hfs_inode_cachep, GFP_KERNEL);
+ return i ? &i->vfs_inode : NULL;
+}
+
+static void hfs_free_inode(struct inode *inode)
+{
+ kmem_cache_free(hfs_inode_cachep, HFS_I(inode));
+}
+
+static const struct super_operations hfs_super_operations = {
+ .alloc_inode = hfs_alloc_inode,
+ .free_inode = hfs_free_inode,
+ .write_inode = hfs_write_inode,
+ .evict_inode = hfs_evict_inode,
+ .put_super = hfs_put_super,
+ .sync_fs = hfs_sync_fs,
+ .statfs = hfs_statfs,
+ .show_options = hfs_show_options,
+};
+
+enum {
+ opt_uid, opt_gid, opt_umask, opt_file_umask, opt_dir_umask,
+ opt_part, opt_session, opt_type, opt_creator, opt_quiet,
+ opt_codepage, opt_iocharset,
+};
+
+static const struct fs_parameter_spec hfs_param_spec[] = {
+ fsparam_u32 ("uid", opt_uid),
+ fsparam_u32 ("gid", opt_gid),
+ fsparam_u32oct ("umask", opt_umask),
+ fsparam_u32oct ("file_umask", opt_file_umask),
+ fsparam_u32oct ("dir_umask", opt_dir_umask),
+ fsparam_u32 ("part", opt_part),
+ fsparam_u32 ("session", opt_session),
+ fsparam_string ("type", opt_type),
+ fsparam_string ("creator", opt_creator),
+ fsparam_flag ("quiet", opt_quiet),
+ fsparam_string ("codepage", opt_codepage),
+ fsparam_string ("iocharset", opt_iocharset),
+ {}
+};
+
+/*
+ * hfs_parse_param()
+ *
+ * This function is called by the vfs to parse the mount options.
+ */
+static int hfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct hfs_sb_info *hsb = fc->s_fs_info;
+ struct fs_parse_result result;
+ int opt;
+
+ /* hfs does not honor any fs-specific options on remount */
+ if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)
+ return 0;
+
+ opt = fs_parse(fc, hfs_param_spec, param, &result);
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case opt_uid:
+ hsb->s_uid = result.uid;
+ break;
+ case opt_gid:
+ hsb->s_gid = result.gid;
+ break;
+ case opt_umask:
+ hsb->s_file_umask = (umode_t)result.uint_32;
+ hsb->s_dir_umask = (umode_t)result.uint_32;
+ break;
+ case opt_file_umask:
+ hsb->s_file_umask = (umode_t)result.uint_32;
+ break;
+ case opt_dir_umask:
+ hsb->s_dir_umask = (umode_t)result.uint_32;
+ break;
+ case opt_part:
+ hsb->part = result.uint_32;
+ break;
+ case opt_session:
+ hsb->session = result.uint_32;
+ break;
+ case opt_type:
+ if (strlen(param->string) != 4) {
+ pr_err("type requires a 4 character value\n");
+ return -EINVAL;
+ }
+ memcpy(&hsb->s_type, param->string, 4);
+ break;
+ case opt_creator:
+ if (strlen(param->string) != 4) {
+ pr_err("creator requires a 4 character value\n");
+ return -EINVAL;
+ }
+ memcpy(&hsb->s_creator, param->string, 4);
+ break;
+ case opt_quiet:
+ hsb->s_quiet = 1;
+ break;
+ case opt_codepage:
+ if (hsb->nls_disk) {
+ pr_err("unable to change codepage\n");
+ return -EINVAL;
+ }
+ hsb->nls_disk = load_nls(param->string);
+ if (!hsb->nls_disk) {
+ pr_err("unable to load codepage \"%s\"\n",
+ param->string);
+ return -EINVAL;
+ }
+ break;
+ case opt_iocharset:
+ if (hsb->nls_io) {
+ pr_err("unable to change iocharset\n");
+ return -EINVAL;
+ }
+ hsb->nls_io = load_nls(param->string);
+ if (!hsb->nls_io) {
+ pr_err("unable to load iocharset \"%s\"\n",
+ param->string);
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * hfs_read_super()
+ *
+ * This is the function that is responsible for mounting an HFS
+ * filesystem. It performs all the tasks necessary to get enough data
+ * from the disk to read the root inode. This includes parsing the
+ * mount options, dealing with Macintosh partitions, reading the
+ * superblock and the allocation bitmap blocks, calling
+ * hfs_btree_init() to get the necessary data about the extents and
+ * catalog B-trees and, finally, reading the root inode into memory.
+ */
+static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+ struct hfs_sb_info *sbi = HFS_SB(sb);
+ struct hfs_find_data fd;
+ hfs_cat_rec rec;
+ struct inode *root_inode;
+ int silent = fc->sb_flags & SB_SILENT;
+ int res;
+
+ atomic64_set(&sbi->file_count, 0);
+ atomic64_set(&sbi->folder_count, 0);
+ atomic64_set(&sbi->next_id, 0);
+
+ /* load_nls_default does not fail */
+ if (sbi->nls_disk && !sbi->nls_io)
+ sbi->nls_io = load_nls_default();
+ sbi->s_dir_umask &= 0777;
+ sbi->s_file_umask &= 0577;
+
+ spin_lock_init(&sbi->work_lock);
+ INIT_DELAYED_WORK(&sbi->mdb_work, flush_mdb);
+
+ sbi->sb = sb;
+ sb->s_op = &hfs_super_operations;
+ sb->s_xattr = hfs_xattr_handlers;
+ sb->s_flags |= SB_NOATIME | SB_NODIRATIME;
+ mutex_init(&sbi->mdb_lock);
+ mutex_init(&sbi->bitmap_lock);
+
+ mutex_lock(&sbi->mdb_lock);
+ res = hfs_mdb_get(sb);
+ mutex_unlock(&sbi->mdb_lock);
+ if (res) {
+ if (!silent)
+ pr_warn("can't find a HFS filesystem on dev %s\n",
+ hfs_mdb_name(sb));
+ res = -EINVAL;
+ goto bail;
+ }
+
+ /* try to get the root inode */
+ res = hfs_find_init(HFS_SB(sb)->cat_tree, &fd);
+ if (res)
+ goto bail_no_root;
+ res = hfs_cat_find_brec(sb, HFS_ROOT_CNID, &fd);
+ if (!res) {
+ if (fd.entrylength != sizeof(rec.dir)) {
+ res = -EIO;
+ goto bail_hfs_find;
+ }
+ hfs_bnode_read(fd.bnode, &rec, fd.entryoffset, fd.entrylength);
+ if (rec.type != HFS_CDR_DIR)
+ res = -EIO;
+ }
+ if (res)
+ goto bail_hfs_find;
+ res = -EINVAL;
+ root_inode = hfs_iget(sb, &fd.search_key->cat, &rec);
+ hfs_find_exit(&fd);
+ if (!root_inode)
+ goto bail_no_root;
+
+ if (is_bad_inode(root_inode)) {
+ iput(root_inode);
+ goto bail_no_root;
+ }
+
+ set_default_d_op(sb, &hfs_dentry_operations);
+ res = -ENOMEM;
+ sb->s_root = d_make_root(root_inode);
+ if (!sb->s_root)
+ goto bail_no_root;
+
+ /* everything's okay */
+ return 0;
+
+bail_hfs_find:
+ hfs_find_exit(&fd);
+bail_no_root:
+ pr_err("get root inode failed\n");
+bail:
+ hfs_mdb_put(sb);
+ return res;
+}
+
+static int hfs_get_tree(struct fs_context *fc)
+{
+ return get_tree_bdev(fc, hfs_fill_super);
+}
+
+static void hfs_free_fc(struct fs_context *fc)
+{
+ kfree(fc->s_fs_info);
+}
+
+static const struct fs_context_operations hfs_context_ops = {
+ .parse_param = hfs_parse_param,
+ .get_tree = hfs_get_tree,
+ .reconfigure = hfs_reconfigure,
+ .free = hfs_free_fc,
+};
+
+static int hfs_init_fs_context(struct fs_context *fc)
+{
+ struct hfs_sb_info *hsb;
+
+ hsb = kzalloc_obj(struct hfs_sb_info);
+ if (!hsb)
+ return -ENOMEM;
+
+ fc->s_fs_info = hsb;
+ fc->ops = &hfs_context_ops;
+
+ if (fc->purpose != FS_CONTEXT_FOR_RECONFIGURE) {
+ /* initialize options with defaults */
+ hsb->s_uid = current_uid();
+ hsb->s_gid = current_gid();
+ hsb->s_file_umask = 0133;
+ hsb->s_dir_umask = 0022;
+ hsb->s_type = cpu_to_be32(0x3f3f3f3f); /* == '????' */
+ hsb->s_creator = cpu_to_be32(0x3f3f3f3f); /* == '????' */
+ hsb->s_quiet = 0;
+ hsb->part = -1;
+ hsb->session = -1;
+ }
+
+ return 0;
+}
+
+static void hfs_kill_super(struct super_block *sb)
+{
+ struct hfs_sb_info *hsb = HFS_SB(sb);
+
+ kill_block_super(sb);
+ kfree(hsb);
+}
+
+static struct file_system_type hfs_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "hfs",
+ .kill_sb = hfs_kill_super,
+ .fs_flags = FS_REQUIRES_DEV,
+ .init_fs_context = hfs_init_fs_context,
+};
+MODULE_ALIAS_FS("hfs");
+
+static void hfs_init_once(void *p)
+{
+ struct hfs_inode_info *i = p;
+
+ inode_init_once(&i->vfs_inode);
+}
+
+static int __init init_hfs_fs(void)
+{
+ int err;
+
+ hfs_inode_cachep = kmem_cache_create("hfs_inode_cache",
+ sizeof(struct hfs_inode_info), 0,
+ SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT, hfs_init_once);
+ if (!hfs_inode_cachep)
+ return -ENOMEM;
+ err = register_filesystem(&hfs_fs_type);
+ if (err)
+ kmem_cache_destroy(hfs_inode_cachep);
+ return err;
+}
+
+static void __exit exit_hfs_fs(void)
+{
+ unregister_filesystem(&hfs_fs_type);
+
+ /*
+ * Make sure all delayed rcu free inodes are flushed before we
+ * destroy cache.
+ */
+ rcu_barrier();
+ kmem_cache_destroy(hfs_inode_cachep);
+}
+
+module_init(init_hfs_fs)
+module_exit(exit_hfs_fs)
diff --git a/fs/hfs/sysdep.c b/fs/hfs/sysdep.c
new file mode 100644
index 000000000..ef54fc809
--- /dev/null
+++ b/fs/hfs/sysdep.c
@@ -0,0 +1,50 @@
+/*
+ * linux/fs/hfs/sysdep.c
+ *
+ * Copyright (C) 1996 Paul H. Hargrove
+ * (C) 2003 Ardis Technologies <roman@ardistech.com>
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains the code to do various system dependent things.
+ */
+
+#include <linux/namei.h>
+#include "hfs_fs.h"
+
+/* dentry case-handling: just lowercase everything */
+
+static int hfs_revalidate_dentry(struct inode *dir, const struct qstr *name,
+ struct dentry *dentry, unsigned int flags)
+{
+ struct inode *inode;
+ int diff;
+
+ if (flags & LOOKUP_RCU)
+ return -ECHILD;
+
+ inode = d_inode(dentry);
+ if(!inode)
+ return 1;
+
+ /* fix up inode on a timezone change */
+ diff = sys_tz.tz_minuteswest * 60 - HFS_I(inode)->tz_secondswest;
+ if (diff) {
+ struct timespec64 ts = inode_get_ctime(inode);
+
+ inode_set_ctime(inode, ts.tv_sec + diff, ts.tv_nsec);
+ ts = inode_get_atime(inode);
+ inode_set_atime(inode, ts.tv_sec + diff, ts.tv_nsec);
+ ts = inode_get_mtime(inode);
+ inode_set_mtime(inode, ts.tv_sec + diff, ts.tv_nsec);
+ HFS_I(inode)->tz_secondswest += diff;
+ }
+ return 1;
+}
+
+const struct dentry_operations hfs_dentry_operations =
+{
+ .d_revalidate = hfs_revalidate_dentry,
+ .d_hash = hfs_hash_dentry,
+ .d_compare = hfs_compare_dentry,
+};
+
diff --git a/fs/hfs/trans.c b/fs/hfs/trans.c
new file mode 100644
index 000000000..fdb0edb8a
--- /dev/null
+++ b/fs/hfs/trans.c
@@ -0,0 +1,150 @@
+/*
+ * linux/fs/hfs/trans.c
+ *
+ * Copyright (C) 1995-1997 Paul H. Hargrove
+ * This file may be distributed under the terms of the GNU General Public License.
+ *
+ * This file contains routines for converting between the Macintosh
+ * character set and various other encodings. This includes dealing
+ * with ':' vs. '/' as the path-element separator.
+ */
+
+#include <linux/types.h>
+#include <linux/nls.h>
+
+#include "hfs_fs.h"
+
+/*================ Global functions ================*/
+
+/*
+ * hfs_mac2asc()
+ *
+ * Given a 'Pascal String' (a string preceded by a length byte) in
+ * the Macintosh character set produce the corresponding filename using
+ * the 'trivial' name-mangling scheme, returning the length of the
+ * mangled filename. Note that the output string is not NULL
+ * terminated.
+ *
+ * The name-mangling works as follows:
+ * The character '/', which is illegal in Linux filenames is replaced
+ * by ':' which never appears in HFS filenames. All other characters
+ * are passed unchanged from input to output.
+ */
+int hfs_mac2asc(struct super_block *sb, char *out, const struct hfs_name *in)
+{
+ struct nls_table *nls_disk = HFS_SB(sb)->nls_disk;
+ struct nls_table *nls_io = HFS_SB(sb)->nls_io;
+ const char *src;
+ char *dst;
+ int srclen, dstlen, size;
+
+ src = in->name;
+ srclen = in->len;
+ if (srclen > HFS_NAMELEN)
+ srclen = HFS_NAMELEN;
+ dst = out;
+ dstlen = HFS_MAX_NAMELEN;
+ if (nls_io) {
+ wchar_t ch;
+
+ while (srclen > 0) {
+ if (nls_disk) {
+ size = nls_disk->char2uni(src, srclen, &ch);
+ if (size <= 0) {
+ ch = '?';
+ size = 1;
+ }
+ src += size;
+ srclen -= size;
+ } else {
+ ch = *src++;
+ srclen--;
+ }
+ if (ch == '/')
+ ch = ':';
+ size = nls_io->uni2char(ch, dst, dstlen);
+ if (size < 0) {
+ if (size == -ENAMETOOLONG)
+ goto out;
+ *dst = '?';
+ size = 1;
+ }
+ dst += size;
+ dstlen -= size;
+ }
+ } else {
+ char ch;
+
+ while (--srclen >= 0)
+ *dst++ = (ch = *src++) == '/' ? ':' : ch;
+ }
+out:
+ return dst - out;
+}
+
+/*
+ * hfs_asc2mac()
+ *
+ * Given an ASCII string (not null-terminated) and its length,
+ * generate the corresponding filename in the Macintosh character set
+ * using the 'trivial' name-mangling scheme, returning the length of
+ * the mangled filename. Note that the output string is not NULL
+ * terminated.
+ *
+ * This routine is a inverse to hfs_mac2triv().
+ * A ':' is replaced by a '/'.
+ */
+void hfs_asc2mac(struct super_block *sb, struct hfs_name *out, const struct qstr *in)
+{
+ struct nls_table *nls_disk = HFS_SB(sb)->nls_disk;
+ struct nls_table *nls_io = HFS_SB(sb)->nls_io;
+ const char *src;
+ char *dst;
+ int srclen, dstlen, size;
+
+ src = in->name;
+ srclen = in->len;
+ dst = out->name;
+ dstlen = HFS_NAMELEN;
+ if (nls_io) {
+ wchar_t ch;
+
+ while (srclen > 0 && dstlen > 0) {
+ size = nls_io->char2uni(src, srclen, &ch);
+ if (size < 0) {
+ ch = '?';
+ size = 1;
+ }
+ src += size;
+ srclen -= size;
+ if (ch == ':')
+ ch = '/';
+ if (nls_disk) {
+ size = nls_disk->uni2char(ch, dst, dstlen);
+ if (size < 0) {
+ if (size == -ENAMETOOLONG)
+ goto out;
+ *dst = '?';
+ size = 1;
+ }
+ dst += size;
+ dstlen -= size;
+ } else {
+ *dst++ = ch > 0xff ? '?' : ch;
+ dstlen--;
+ }
+ }
+ } else {
+ char ch;
+
+ if (dstlen > srclen)
+ dstlen = srclen;
+ while (--dstlen >= 0)
+ *dst++ = (ch = *src++) == ':' ? '/' : ch;
+ }
+out:
+ out->len = dst - (char *)out->name;
+ dstlen = HFS_NAMELEN - out->len;
+ while (--dstlen >= 0)
+ *dst++ = 0;
+}