summaryrefslogtreecommitdiffstats
path: root/fs/hfs
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /fs/hfs
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
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..1b331108d
--- /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, GFP_KERNEL);
+ 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;
+}