| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/24 00:38 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_map_extent",
"hfsplus_iomap_begin",
"hfsplus_write_iomap_begin",
"hfsplus_write_iomap_end",
"hfsplus_iomap_cont_expand",
"hfsplus_writeback_range",
"hfsplus_file_write_dio_end_io",
"hfsplus_fallback_buffered_write",
"hfsplus_dio_write_iter",
"hfsplus_file_write_iter",
"hfsplus_file_read_iter",
"hfsplus_file_llseek",
"hfsplus_file_fsync",
"hfsplus_block_allocate",
"hfsplus_block_free"
],
"Reasoning": "The patch series converts the HFS+ filesystem to use the modern iomap infrastructure for file I/O operations (read, write, seek, truncate). This is a major functional change to the core I/O paths of the filesystem and should be heavily fuzzed to ensure correctness and prevent regressions.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/24 00:38 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 856fe76f41e0964e0b4dc27b30217003960857dd\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Jul 24 00:38:41 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig\nindex ca8401cb6954d5..b4432c64db3bec 100644\n--- a/fs/hfsplus/Kconfig\n+++ b/fs/hfsplus/Kconfig\n@@ -5,7 +5,7 @@ config HFSPLUS_FS\n \tselect BUFFER_HEAD\n \tselect NLS\n \tselect NLS_UTF8\n-\tselect LEGACY_DIRECT_IO\n+\tselect FS_IOMAP\n \thelp\n \t If you say Y here, you will be able to mount extended format\n \t Macintosh-formatted hard drive partitions with full read-write access.\ndiff --git a/fs/hfsplus/Makefile b/fs/hfsplus/Makefile\nindex f2a9ae697e8114..3ddea69a9c6983 100644\n--- a/fs/hfsplus/Makefile\n+++ b/fs/hfsplus/Makefile\n@@ -5,8 +5,9 @@\n \n obj-$(CONFIG_HFSPLUS_FS) += hfsplus.o\n \n-hfsplus-objs := super.o options.o inode.o ioctl.o extents.o catalog.o dir.o btree.o \\\n-\t\tbnode.o brec.o bfind.o tables.o unicode.o wrapper.o bitmap.o part_tbl.o \\\n+hfsplus-objs := super.o options.o inode.o file.o iomap.o ioctl.o extents.o \\\n+\t\tcatalog.o dir.o btree.o bnode.o brec.o bfind.o tables.o \\\n+\t\tunicode.o wrapper.o bitmap.o part_tbl.o \\\n \t\tattributes.o xattr.o xattr_user.o xattr_security.o xattr_trusted.o\n \n # KUnit tests\ndiff --git a/fs/hfsplus/bitmap.c b/fs/hfsplus/bitmap.c\nindex 1b3af8c87cadb5..571652fbaeda3b 100644\n--- a/fs/hfsplus/bitmap.c\n+++ b/fs/hfsplus/bitmap.c\n@@ -39,6 +39,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t\tstart = size;\n \t\tgoto out;\n \t}\n+\tlock_page(page);\n \tpptr = kmap_local_page(page);\n \tcurr = pptr + (offset \u0026 (PAGE_CACHE_BITS - 1)) / 32;\n \ti = offset % 32;\n@@ -75,6 +76,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t\t\tcurr++;\n \t\t}\n \t\tkunmap_local(pptr);\n+\t\tunlock_page(page);\n \t\toffset += PAGE_CACHE_BITS;\n \t\tif (offset \u003e= size)\n \t\t\tbreak;\n@@ -84,6 +86,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t\t\tstart = size;\n \t\t\tgoto out;\n \t\t}\n+\t\tlock_page(page);\n \t\tcurr = pptr = kmap_local_page(page);\n \t\tif ((size ^ offset) / PAGE_CACHE_BITS)\n \t\t\tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -98,6 +101,9 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \tstart = offset + (curr - pptr) * 32 + i;\n \tif (start \u003e= size) {\n \t\thfs_dbg(\"bitmap full\\n\");\n+\t\tkunmap_local(pptr);\n+\t\tunlock_page(page);\n+\t\tstart = size;\n \t\tgoto out;\n \t}\n \t/* do any partial u32 at the start */\n@@ -128,6 +134,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t\t}\n \t\tset_page_dirty(page);\n \t\tkunmap_local(pptr);\n+\t\tunlock_page(page);\n \t\toffset += PAGE_CACHE_BITS;\n \t\tpage = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,\n \t\t\t\t\t NULL);\n@@ -135,6 +142,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t\t\tstart = size;\n \t\t\tgoto out;\n \t\t}\n+\t\tlock_page(page);\n \t\tpptr = kmap_local_page(page);\n \t\tcurr = pptr;\n \t\tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -152,6 +160,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,\n \t*curr = cpu_to_be32(n);\n \tset_page_dirty(page);\n \tkunmap_local(pptr);\n+\tunlock_page(page);\n \t*max = offset + (curr - pptr) * 32 + i - start;\n \tsbi-\u003efree_blocks -= *max;\n \thfsplus_mark_mdb_dirty(sb);\n@@ -185,6 +194,7 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)\n \tpage = read_mapping_page(mapping, pnr, NULL);\n \tif (IS_ERR(page))\n \t\tgoto kaboom;\n+\tlock_page(page);\n \tpptr = kmap_local_page(page);\n \tcurr = pptr + (offset \u0026 (PAGE_CACHE_BITS - 1)) / 32;\n \tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -216,9 +226,11 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)\n \t\t\tbreak;\n \t\tset_page_dirty(page);\n \t\tkunmap_local(pptr);\n+\t\tunlock_page(page);\n \t\tpage = read_mapping_page(mapping, ++pnr, NULL);\n \t\tif (IS_ERR(page))\n \t\t\tgoto kaboom;\n+\t\tlock_page(page);\n \t\tpptr = kmap_local_page(page);\n \t\tcurr = pptr;\n \t\tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -232,6 +244,7 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)\n out:\n \tset_page_dirty(page);\n \tkunmap_local(pptr);\n+\tunlock_page(page);\n \tsbi-\u003efree_blocks += len;\n \thfsplus_mark_mdb_dirty(sb);\n \tmutex_unlock(\u0026sbi-\u003ealloc_mutex);\ndiff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c\nindex 813e68b8ecd654..8f7f560cec6427 100644\n--- a/fs/hfsplus/extents.c\n+++ b/fs/hfsplus/extents.c\n@@ -15,6 +15,7 @@\n \n #include \"hfsplus_fs.h\"\n #include \"hfsplus_raw.h\"\n+#include \"iomap.h\"\n \n /* Compare two extents keys, returns 0 on same, pos/neg for difference */\n int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,\n@@ -48,18 +49,29 @@ static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,\n \tkey-\u003eext.pad = 0;\n }\n \n-static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off)\n+/*\n+ * hfsplus_ext_find_block() - find contiguous sequence of block\n+ *\n+ * Find the disk allocation block for 'off' within an 8-entry\n+ * extent record, and the number of further allocation blocks\n+ * that are contiguous with it in the same extent entry.\n+ */\n+static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off,\n+\t\t\t\t u32 *dblock)\n {\n \tint i;\n \tu32 count;\n \n-\tfor (i = 0; i \u003c 8; ext++, i++) {\n+\tfor (i = 0; i \u003c HFSPLUS_FORK_EXTENT_COUNT; ext++, i++) {\n \t\tcount = be32_to_cpu(ext-\u003eblock_count);\n-\t\tif (off \u003c count)\n-\t\t\treturn be32_to_cpu(ext-\u003estart_block) + off;\n+\t\tif (off \u003c count) {\n+\t\t\t*dblock = be32_to_cpu(ext-\u003estart_block) + off;\n+\t\t\treturn count - off;\n+\t\t}\n \t\toff -= count;\n \t}\n \t/* panic? */\n+\t*dblock = 0;\n \treturn 0;\n }\n \n@@ -68,7 +80,7 @@ static int hfsplus_ext_block_count(struct hfsplus_extent *ext)\n \tint i;\n \tu32 count = 0;\n \n-\tfor (i = 0; i \u003c 8; ext++, i++)\n+\tfor (i = 0; i \u003c HFSPLUS_FORK_EXTENT_COUNT; ext++, i++)\n \t\tcount += be32_to_cpu(ext-\u003eblock_count);\n \treturn count;\n }\n@@ -223,37 +235,46 @@ static int hfsplus_ext_read_extent(struct inode *inode, u32 block)\n \treturn res;\n }\n \n-/* Get a block at iblock for inode, possibly allocating if create */\n-int hfsplus_get_block(struct inode *inode, sector_t iblock,\n-\t\t struct buffer_head *bh_result, int create)\n+/*\n+ * hfsplus_map_extent() - find or allocate a sequence of allocation blocks\n+ *\n+ * Looks up the allocation block at 'ablock' for inode, extending the\n+ * file (via hfsplus_file_extend(), in clump_blocks-sized chunks) when\n+ * 'create' is set and 'ablock' lies beyond the current allocation.\n+ *\n+ * On success, *dblock is the disk allocation block backing 'ablock',\n+ * and *max_blocks is the number of further allocation blocks that are\n+ * contiguous with it (i.e. the remaining length of the extent entry\n+ * that contains 'ablock'), which may be smaller than the whole file's\n+ * remaining allocation when the fork is fragmented across several\n+ * extent entries. If a new extent had to be allocated to satisfy the\n+ * request, *balloc (when non-NULL) is set to true.\n+ */\n+int hfsplus_map_extent(struct inode *inode, u32 ablock, int create,\n+\t\t\tu32 *dblock, u32 *max_blocks, bool *balloc)\n {\n-\tstruct super_block *sb = inode-\u003ei_sb;\n-\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);\n \tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n-\tint res = -EIO;\n-\tu32 ablock, dblock, mask;\n-\tsector_t sector;\n-\tint was_dirty = 0;\n+\tint was_dirty;\n+\tint res;\n \n-\t/* Convert inode block to disk allocation block */\n-\tablock = iblock \u003e\u003e sbi-\u003efs_shift;\n+\tif (balloc)\n+\t\t*balloc = false;\n \n-\tif (iblock \u003e= hip-\u003efs_blocks) {\n+\tif (ablock \u003e= hip-\u003ealloc_blocks) {\n \t\tif (!create)\n-\t\t\treturn 0;\n-\t\tif (iblock \u003e hip-\u003efs_blocks)\n \t\t\treturn -EIO;\n-\t\tif (ablock \u003e= hip-\u003ealloc_blocks) {\n-\t\t\tres = hfsplus_file_extend(inode, false);\n-\t\t\tif (res)\n-\t\t\t\treturn res;\n-\t\t}\n-\t} else\n-\t\tcreate = 0;\n+\t\tres = hfsplus_file_extend(inode, false);\n+\t\tif (res)\n+\t\t\treturn res;\n+\t\tif (balloc)\n+\t\t\t*balloc = true;\n+\t}\n \n \tif (ablock \u003c hip-\u003efirst_blocks) {\n-\t\tdblock = hfsplus_ext_find_block(hip-\u003efirst_extents, ablock);\n-\t\tgoto done;\n+\t\t*max_blocks = hfsplus_ext_find_block(hip-\u003efirst_extents,\n+\t\t\t\t\t\t ablock,\n+\t\t\t\t\t\t dblock);\n+\t\treturn 0;\n \t}\n \n \tif (inode-\u003ei_ino == HFSPLUS_EXT_CNID)\n@@ -272,11 +293,44 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,\n \t\tmutex_unlock(\u0026hip-\u003eextents_lock);\n \t\treturn -EIO;\n \t}\n-\tdblock = hfsplus_ext_find_block(hip-\u003ecached_extents,\n-\t\t\t\t\tablock - hip-\u003ecached_start);\n+\t*max_blocks = hfsplus_ext_find_block(hip-\u003ecached_extents,\n+\t\t\t\t\t ablock - hip-\u003ecached_start,\n+\t\t\t\t\t dblock);\n \tmutex_unlock(\u0026hip-\u003eextents_lock);\n \n-done:\n+\tif (was_dirty)\n+\t\tmark_inode_dirty(inode);\n+\n+\treturn 0;\n+}\n+\n+/* Get a block at iblock for inode, possibly allocating if create */\n+int hfsplus_get_block(struct inode *inode, sector_t iblock,\n+\t\t struct buffer_head *bh_result, int create)\n+{\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);\n+\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n+\tu32 ablock, dblock, mask, max_blocks;\n+\tsector_t sector;\n+\tint res;\n+\n+\t/* Convert inode block to disk allocation block */\n+\tablock = iblock \u003e\u003e sbi-\u003efs_shift;\n+\n+\tif (iblock \u003e= hip-\u003efs_blocks) {\n+\t\tif (!create)\n+\t\t\treturn 0;\n+\t\tif (iblock \u003e hip-\u003efs_blocks)\n+\t\t\treturn -EIO;\n+\t} else\n+\t\tcreate = 0;\n+\n+\tres = hfsplus_map_extent(inode, ablock, create, \u0026dblock, \u0026max_blocks,\n+\t\t\t\t NULL);\n+\tif (res)\n+\t\treturn res;\n+\n \thfs_dbg(\"ino %llu, iblock %llu - dblock %u\\n\",\n \t\tinode-\u003ei_ino, (long long)iblock, dblock);\n \n@@ -290,9 +344,8 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,\n \t\thip-\u003ephys_size += sb-\u003es_blocksize;\n \t\thip-\u003efs_blocks++;\n \t\tinode_add_bytes(inode, sb-\u003es_blocksize);\n-\t}\n-\tif (create || was_dirty)\n \t\tmark_inode_dirty(inode);\n+\t}\n \treturn 0;\n }\n \n@@ -553,20 +606,32 @@ void hfsplus_file_truncate(struct inode *inode)\n \t\tinode-\u003ei_ino, (long long)hip-\u003ephys_size, inode-\u003ei_size);\n \n \tif (inode-\u003ei_size \u003e hip-\u003ephys_size) {\n-\t\tstruct address_space *mapping = inode-\u003ei_mapping;\n-\t\tstruct folio *folio;\n-\t\tvoid *fsdata = NULL;\n-\t\tloff_t size = inode-\u003ei_size;\n+\t\tif (S_ISREG(inode-\u003ei_mode)) {\n+\t\t\tres = hfsplus_iomap_cont_expand(inode, inode-\u003ei_size);\n+\t\t\tif (res)\n+\t\t\t\treturn;\n+\n+\t\t\tmark_inode_dirty(inode);\n+\t\t} else {\n+\t\t\tstruct address_space *mapping = inode-\u003ei_mapping;\n+\t\t\tstruct folio *folio;\n+\t\t\tvoid *fsdata = NULL;\n+\n+\t\t\tres = hfsplus_write_begin(NULL, mapping,\n+\t\t\t\t\t\t inode-\u003ei_size, 0,\n+\t\t\t\t\t\t \u0026folio, \u0026fsdata);\n+\t\t\tif (res)\n+\t\t\t\treturn;\n+\n+\t\t\tres = generic_write_end(NULL, mapping,\n+\t\t\t\t\t\tinode-\u003ei_size, 0, 0,\n+\t\t\t\t\t\tfolio, fsdata);\n+\t\t\tif (res \u003c 0)\n+\t\t\t\treturn;\n+\n+\t\t\tmark_inode_dirty(inode);\n+\t\t}\n \n-\t\tres = hfsplus_write_begin(NULL, mapping, size, 0,\n-\t\t\t\t\t \u0026folio, \u0026fsdata);\n-\t\tif (res)\n-\t\t\treturn;\n-\t\tres = generic_write_end(NULL, mapping, size, 0, 0,\n-\t\t\t\t\tfolio, fsdata);\n-\t\tif (res \u003c 0)\n-\t\t\treturn;\n-\t\tmark_inode_dirty(inode);\n \t\treturn;\n \t} else if (inode-\u003ei_size == hip-\u003ephys_size)\n \t\treturn;\ndiff --git a/fs/hfsplus/file.c b/fs/hfsplus/file.c\nnew file mode 100644\nindex 00000000000000..8c92e145295ca1\n--- /dev/null\n+++ b/fs/hfsplus/file.c\n@@ -0,0 +1,309 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * linux/fs/hfsplus/file.c\n+ *\n+ * File operations: open/release/fsync and iomap-based read/write/seek\n+ */\n+\n+#include \u003clinux/fs.h\u003e\n+#include \u003clinux/uio.h\u003e\n+#include \u003clinux/mount.h\u003e\n+#include \u003clinux/iomap.h\u003e\n+\n+#include \"hfsplus_fs.h\"\n+#include \"hfsplus_raw.h\"\n+#include \"iomap.h\"\n+\n+static int hfsplus_file_open(struct inode *inode, struct file *file)\n+{\n+\tif (HFSPLUS_IS_RSRC(inode))\n+\t\tinode = HFSPLUS_I(inode)-\u003ersrc_inode;\n+\tif (!(file-\u003ef_flags \u0026 O_LARGEFILE) \u0026\u0026 i_size_read(inode) \u003e MAX_NON_LFS)\n+\t\treturn -EOVERFLOW;\n+\tatomic_inc(\u0026HFSPLUS_I(inode)-\u003eopencnt);\n+\tfile-\u003ef_mode |= FMODE_CAN_ODIRECT;\n+\treturn 0;\n+}\n+\n+static int hfsplus_file_release(struct inode *inode, struct file *file)\n+{\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\n+\tif (HFSPLUS_IS_RSRC(inode))\n+\t\tinode = HFSPLUS_I(inode)-\u003ersrc_inode;\n+\tif (atomic_dec_and_test(\u0026HFSPLUS_I(inode)-\u003eopencnt)) {\n+\t\tinode_lock(inode);\n+\t\thfsplus_file_truncate(inode);\n+\t\tif (inode-\u003ei_flags \u0026 S_DEAD) {\n+\t\t\thfsplus_delete_cat(inode-\u003ei_ino,\n+\t\t\t\t\t HFSPLUS_SB(sb)-\u003ehidden_dir, NULL);\n+\t\t\thfsplus_delete_inode(inode);\n+\t\t}\n+\t\tinode_unlock(inode);\n+\t}\n+\treturn 0;\n+}\n+\n+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,\n+\t\t int datasync)\n+{\n+\tstruct inode *inode = file-\u003ef_mapping-\u003ehost;\n+\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(inode-\u003ei_sb);\n+\tstruct hfsplus_vh *vhdr = sbi-\u003es_vhdr;\n+\tint error = 0, error2;\n+\n+\thfs_dbg(\"inode-\u003ei_ino %llu, start %llu, end %llu\\n\",\n+\t\tinode-\u003ei_ino, start, end);\n+\n+\terror = file_write_and_wait_range(file, start, end);\n+\tif (error)\n+\t\treturn error;\n+\n+\t/*\n+\t * Sync inode metadata into the catalog and extent trees.\n+\t */\n+\tsync_inode_metadata(inode, 1);\n+\n+\t/*\n+\t * And explicitly write out the btrees.\n+\t */\n+\tif (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,\n+\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))-\u003eflags)) {\n+\t\tclear_bit(HFSPLUS_I_CAT_DIRTY, \u0026hip-\u003eflags);\n+\t\terror = filemap_write_and_wait(sbi-\u003ecat_tree-\u003einode-\u003ei_mapping);\n+\t}\n+\n+\tif (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,\n+\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))-\u003eflags)) {\n+\t\tclear_bit(HFSPLUS_I_EXT_DIRTY, \u0026hip-\u003eflags);\n+\t\terror2 =\n+\t\t\tfilemap_write_and_wait(sbi-\u003eext_tree-\u003einode-\u003ei_mapping);\n+\t\tif (!error)\n+\t\t\terror = error2;\n+\t}\n+\n+\tif (sbi-\u003eattr_tree) {\n+\t\tif (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,\n+\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))-\u003eflags)) {\n+\t\t\tclear_bit(HFSPLUS_I_ATTR_DIRTY, \u0026hip-\u003eflags);\n+\t\t\terror2 =\n+\t\t\t\tfilemap_write_and_wait(\n+\t\t\t\t\t sbi-\u003eattr_tree-\u003einode-\u003ei_mapping);\n+\t\t\tif (!error)\n+\t\t\t\terror = error2;\n+\t\t}\n+\t} else {\n+\t\tif (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, \u0026hip-\u003eflags))\n+\t\t\tpr_err(\"sync non-existent attributes tree\\n\");\n+\t}\n+\n+\tif (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,\n+\t\t\t\t\u0026HFSPLUS_I(sbi-\u003ealloc_file)-\u003eflags)) {\n+\t\tclear_bit(HFSPLUS_I_ALLOC_DIRTY, \u0026hip-\u003eflags);\n+\t\terror2 = filemap_write_and_wait(sbi-\u003ealloc_file-\u003ei_mapping);\n+\t\tif (!error)\n+\t\t\terror = error2;\n+\t}\n+\n+\tmutex_lock(\u0026sbi-\u003evh_mutex);\n+\thfsplus_prepare_volume_header_for_commit(vhdr);\n+\tmutex_unlock(\u0026sbi-\u003evh_mutex);\n+\n+\terror2 = hfsplus_commit_superblock(inode-\u003ei_sb);\n+\tif (!error)\n+\t\terror = error2;\n+\n+\tif (!test_bit(HFSPLUS_SB_NOBARRIER, \u0026sbi-\u003eflags))\n+\t\tblkdev_issue_flush(inode-\u003ei_sb-\u003es_bdev);\n+\n+\treturn error;\n+}\n+\n+/*\n+ * hfsplus_fallback_buffered_write() - fall back to buffered I/O for the\n+ * tail of a write that iomap_dio_rw() could not perform directly\n+ * (unaligned tail, or no blocks could be mapped without allocation\n+ * outside the direct path).\n+ */\n+static ssize_t hfsplus_fallback_buffered_write(struct kiocb *iocb,\n+\t\t\t\t\t\tstruct iov_iter *from)\n+{\n+\tloff_t offset = iocb-\u003eki_pos, end;\n+\tssize_t written;\n+\tint ret;\n+\n+\tiocb-\u003eki_flags \u0026= ~IOCB_DIRECT;\n+\n+\twritten = iomap_file_buffered_write(iocb, from,\n+\t\t\t\t\t \u0026hfsplus_write_iomap_ops,\n+\t\t\t\t\t NULL, NULL);\n+\tif (written \u003c 0)\n+\t\treturn written;\n+\n+\tend = iocb-\u003eki_pos + written - 1;\n+\tret = filemap_write_and_wait_range(iocb-\u003eki_filp-\u003ef_mapping,\n+\t\t\t\t\t offset, end);\n+\tif (ret)\n+\t\treturn -EIO;\n+\n+\tinvalidate_mapping_pages(iocb-\u003eki_filp-\u003ef_mapping,\n+\t\t\t\t offset \u003e\u003e PAGE_SHIFT,\n+\t\t\t\t end \u003e\u003e PAGE_SHIFT);\n+\n+\treturn written;\n+}\n+\n+static ssize_t hfsplus_dio_write_iter(struct kiocb *iocb,\n+\t\t\t\t\tstruct iov_iter *from)\n+{\n+\tssize_t ret;\n+\n+\tret = iomap_dio_rw(iocb, from,\n+\t\t\t \u0026hfsplus_write_iomap_ops,\n+\t\t\t \u0026hfsplus_write_dio_ops,\n+\t\t\t 0, NULL, 0);\n+\tif (ret == -ENOTBLK)\n+\t\tret = 0;\n+\telse if (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tif (iov_iter_count(from)) {\n+\t\tssize_t written;\n+\n+\t\twritten = hfsplus_fallback_buffered_write(iocb, from);\n+\t\tif (written \u003c 0)\n+\t\t\treturn written;\n+\t\tret += written;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static ssize_t hfsplus_file_write_iter(struct kiocb *iocb,\n+\t\t\t\t\tstruct iov_iter *iter)\n+{\n+\tstruct file *file = iocb-\u003eki_filp;\n+\tstruct inode *inode = file_inode(file);\n+\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(inode-\u003ei_sb);\n+\tloff_t total_capacity;\n+\tssize_t ret;\n+\tint err;\n+\n+\tinode_lock(inode);\n+\n+\tret = generic_write_checks(iocb, iter);\n+\tif (ret \u003c= 0)\n+\t\tgoto unlock;\n+\n+\ttotal_capacity = (loff_t)sbi-\u003etotal_blocks \u003c\u003c sbi-\u003ealloc_blksz_shift;\n+\tif (iocb-\u003eki_pos \u003e= total_capacity) {\n+\t\tret = -EFBIG;\n+\t\tgoto unlock;\n+\t}\n+\n+\terr = file_modified(file);\n+\tif (err) {\n+\t\tret = err;\n+\t\tgoto unlock;\n+\t}\n+\n+\tif (iocb-\u003eki_pos \u003e i_size_read(inode)) {\n+\t\tloff_t old_size = i_size_read(inode);\n+\t\tloff_t new_size = iocb-\u003eki_pos;\n+\n+\t\tif (iocb-\u003eki_flags \u0026 IOCB_DIRECT) {\n+\t\t\tnew_size = max_t(loff_t, new_size,\n+\t\t\t\t\t HFSPLUS_I(inode)-\u003ephys_size);\n+\t\t}\n+\n+\t\ti_size_write(inode, new_size);\n+\t\terr = hfsplus_iomap_cont_expand(inode, iocb-\u003eki_pos);\n+\t\tif (err) {\n+\t\t\ti_size_write(inode, old_size);\n+\t\t\tret = err;\n+\t\t\tgoto unlock;\n+\t\t}\n+\t\tmark_inode_dirty(inode);\n+\t} else if ((iocb-\u003eki_flags \u0026 IOCB_DIRECT) \u0026\u0026\n+\t\t HFSPLUS_I(inode)-\u003ephys_size \u003e i_size_read(inode)) {\n+\t\ti_size_write(inode, HFSPLUS_I(inode)-\u003ephys_size);\n+\t\tmark_inode_dirty(inode);\n+\t}\n+\n+\tif (iocb-\u003eki_flags \u0026 IOCB_DIRECT)\n+\t\tret = hfsplus_dio_write_iter(iocb, iter);\n+\telse {\n+\t\tret = iomap_file_buffered_write(iocb, iter,\n+\t\t\t\t\t\t\u0026hfsplus_write_iomap_ops,\n+\t\t\t\t\t\tNULL, NULL);\n+\t}\n+\n+unlock:\n+\tinode_unlock(inode);\n+\n+\tif (ret \u003e 0)\n+\t\tret = generic_write_sync(iocb, ret);\n+\n+\treturn ret;\n+}\n+\n+static ssize_t hfsplus_file_read_iter(struct kiocb *iocb,\n+\t\t\t\t\tstruct iov_iter *iter)\n+{\n+\tstruct inode *inode = file_inode(iocb-\u003eki_filp);\n+\tssize_t ret;\n+\n+\tinode_lock_shared(inode);\n+\n+\tif (iocb-\u003eki_flags \u0026 IOCB_DIRECT) {\n+\t\tfile_accessed(iocb-\u003eki_filp);\n+\t\tret = iomap_dio_rw(iocb, iter,\n+\t\t\t\t \u0026hfsplus_iomap_ops,\n+\t\t\t\t NULL, 0, NULL, 0);\n+\t} else\n+\t\tret = generic_file_read_iter(iocb, iter);\n+\n+\tinode_unlock_shared(inode);\n+\n+\treturn ret;\n+}\n+\n+static loff_t hfsplus_file_llseek(struct file *file, loff_t offset, int whence)\n+{\n+\tstruct inode *inode = file-\u003ef_mapping-\u003ehost;\n+\n+\tswitch (whence) {\n+\tcase SEEK_HOLE:\n+\t\tinode_lock_shared(inode);\n+\t\toffset = iomap_seek_hole(inode, offset, \u0026hfsplus_iomap_ops);\n+\t\tinode_unlock_shared(inode);\n+\t\tbreak;\n+\tcase SEEK_DATA:\n+\t\tinode_lock_shared(inode);\n+\t\toffset = iomap_seek_data(inode, offset, \u0026hfsplus_iomap_ops);\n+\t\tinode_unlock_shared(inode);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn generic_file_llseek(file, offset, whence);\n+\t}\n+\n+\tif (offset \u003c 0)\n+\t\treturn offset;\n+\n+\treturn vfs_setpos(file, offset, inode-\u003ei_sb-\u003es_maxbytes);\n+}\n+\n+const struct file_operations hfsplus_file_operations = {\n+\t.llseek\t\t= hfsplus_file_llseek,\n+\t.read_iter\t= hfsplus_file_read_iter,\n+\t.write_iter\t= hfsplus_file_write_iter,\n+\t.mmap_prepare\t= generic_file_mmap_prepare,\n+\t.splice_read\t= filemap_splice_read,\n+\t.splice_write\t= iter_file_splice_write,\n+\t.fsync\t\t= hfsplus_file_fsync,\n+\t.open\t\t= hfsplus_file_open,\n+\t.release\t= hfsplus_file_release,\n+\t.unlocked_ioctl = hfsplus_ioctl,\n+};\ndiff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h\nindex ec04b82ad92787..b9cadbc6c6d216 100644\n--- a/fs/hfsplus/hfsplus_fs.h\n+++ b/fs/hfsplus/hfsplus_fs.h\n@@ -175,6 +175,22 @@ static inline struct hfsplus_sb_info *HFSPLUS_SB(struct super_block *sb)\n \treturn sb-\u003es_fs_info;\n }\n \n+/*\n+ * Physical byte offset of allocation block 'dblock' on the volume.\n+ */\n+static inline loff_t hfsplus_ablock_to_phys_bytes(struct super_block *sb,\n+\t\t\t\t\t\t u32 dblock)\n+{\n+\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);\n+\tloff_t phys_bytes;\n+\n+\tphys_bytes = dblock;\n+\tphys_bytes \u003c\u003c= sbi-\u003efs_shift;\n+\tphys_bytes += sbi-\u003eblockoffset;\n+\tphys_bytes \u003c\u003c= sb-\u003es_blocksize_bits;\n+\n+\treturn phys_bytes;\n+}\n \n struct hfsplus_inode_info {\n \tatomic_t opencnt;\n@@ -436,6 +452,8 @@ int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,\n int hfsplus_ext_write_extent(struct inode *inode);\n int hfsplus_get_block(struct inode *inode, sector_t iblock,\n \t\t struct buffer_head *bh_result, int create);\n+int hfsplus_map_extent(struct inode *inode, u32 ablock, int create,\n+\t\t\tu32 *dblock, u32 *max_blocks, bool *balloc);\n int hfsplus_free_fork(struct super_block *sb, u32 cnid,\n \t\t struct hfsplus_fork_raw *fork, int type);\n int hfsplus_file_extend(struct inode *inode, bool zeroout);\n@@ -443,6 +461,7 @@ void hfsplus_file_truncate(struct inode *inode);\n \n /* inode.c */\n extern const struct address_space_operations hfsplus_aops;\n+extern const struct address_space_operations hfsplus_symlink_aops;\n extern const struct address_space_operations hfsplus_btree_aops;\n extern const struct dentry_operations hfsplus_dentry_operations;\n \n@@ -462,12 +481,15 @@ int hfsplus_cat_write_inode(struct inode *inode);\n int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,\n \t\t struct kstat *stat, u32 request_mask,\n \t\t unsigned int query_flags);\n-int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,\n-\t\t int datasync);\n int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa);\n int hfsplus_fileattr_set(struct mnt_idmap *idmap,\n \t\t\t struct dentry *dentry, struct file_kattr *fa);\n \n+/* file.c */\n+extern const struct file_operations hfsplus_file_operations;\n+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,\n+\t\t int datasync);\n+\n /* ioctl.c */\n long hfsplus_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);\n \ndiff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c\nindex 1c57c873f05d61..1f16663eadb095 100644\n--- a/fs/hfsplus/inode.c\n+++ b/fs/hfsplus/inode.c\n@@ -18,12 +18,14 @@\n #include \u003clinux/cred.h\u003e\n #include \u003clinux/uio.h\u003e\n #include \u003clinux/fileattr.h\u003e\n+#include \u003clinux/iomap.h\u003e\n \n #include \"hfsplus_fs.h\"\n #include \"hfsplus_raw.h\"\n #include \"xattr.h\"\n+#include \"iomap.h\"\n \n-static int hfsplus_read_folio(struct file *file, struct folio *folio)\n+static int hfsplus_legacy_read_folio(struct file *file, struct folio *folio)\n {\n \treturn block_read_full_folio(folio, hfsplus_get_block);\n }\n@@ -120,66 +122,7 @@ static bool hfsplus_release_folio(struct folio *folio, gfp_t mask)\n \treturn res ? try_to_free_buffers(folio) : false;\n }\n \n-static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)\n-{\n-\tstruct file *file = iocb-\u003eki_filp;\n-\tstruct address_space *mapping = file-\u003ef_mapping;\n-\tstruct inode *inode = mapping-\u003ehost;\n-\tloff_t isize;\n-\tsize_t count = iov_iter_count(iter);\n-\tloff_t end = iocb-\u003eki_pos + count;\n-\tssize_t ret;\n-\n-\t/*\n-\t * The hfsplus_get_block() only allows creating the next sequential block.\n-\t * For direct writes beyond EOF, expand the file first.\n-\t */\n-\tif (iov_iter_rw(iter) == WRITE \u0026\u0026 iocb-\u003eki_pos \u003e i_size_read(inode)) {\n-\t\tloff_t start_off, end_off;\n-\t\tloff_t start_page, end_page;\n-\n-\t\tisize = i_size_read(inode);\n-\n-\t\t/*\n-\t\t * Wait for any in-flight DIO on this inode to finish before\n-\t\t * calling generic_cont_expand_simple().\n-\t\t */\n-\t\tinode_dio_wait(inode);\n-\n-\t\tret = generic_cont_expand_simple(inode, iocb-\u003eki_pos);\n-\t\tif (ret)\n-\t\t\treturn ret;\n-\n-\t\tstart_off = isize;\n-\t\tend_off = (end \u003e 0) ? end - 1 : end;\n-\n-\t\tret = filemap_write_and_wait_range(mapping, start_off, end_off);\n-\t\tif (ret)\n-\t\t\treturn ret;\n-\n-\t\tstart_page = start_off \u003e\u003e PAGE_SHIFT;\n-\t\tend_page = end_off \u003e\u003e PAGE_SHIFT;\n-\n-\t\tinvalidate_inode_pages2_range(mapping, start_page, end_page);\n-\t}\n-\n-\tret = blockdev_direct_IO(iocb, inode, iter, hfsplus_get_block);\n-\n-\t/*\n-\t * In case of error extending write may have instantiated a few\n-\t * blocks outside i_size. Trim these off again.\n-\t */\n-\tif (unlikely(iov_iter_rw(iter) == WRITE \u0026\u0026 ret \u003c 0)) {\n-\t\tisize = i_size_read(inode);\n-\n-\t\tif (end \u003e isize)\n-\t\t\thfsplus_write_failed(mapping, end);\n-\t}\n-\n-\treturn ret;\n-}\n-\n-static int hfsplus_writepages(struct address_space *mapping,\n+static int hfsplus_legacy_writepages(struct address_space *mapping,\n \t\t\t struct writeback_control *wbc)\n {\n \treturn mpage_writepages(mapping, wbc, hfsplus_get_block);\n@@ -188,8 +131,8 @@ static int hfsplus_writepages(struct address_space *mapping,\n const struct address_space_operations hfsplus_btree_aops = {\n \t.dirty_folio\t= block_dirty_folio,\n \t.invalidate_folio = block_invalidate_folio,\n-\t.read_folio\t= hfsplus_read_folio,\n-\t.writepages\t= hfsplus_writepages,\n+\t.read_folio\t= hfsplus_legacy_read_folio,\n+\t.writepages\t= hfsplus_legacy_writepages,\n \t.write_begin\t= hfsplus_write_begin,\n \t.write_end\t= generic_write_end,\n \t.migrate_folio\t= buffer_migrate_folio,\n@@ -197,18 +140,59 @@ const struct address_space_operations hfsplus_btree_aops = {\n \t.release_folio\t= hfsplus_release_folio,\n };\n \n-const struct address_space_operations hfsplus_aops = {\n+const struct address_space_operations hfsplus_symlink_aops = {\n \t.dirty_folio\t= block_dirty_folio,\n \t.invalidate_folio = block_invalidate_folio,\n-\t.read_folio\t= hfsplus_read_folio,\n+\t.read_folio\t= hfsplus_legacy_read_folio,\n \t.write_begin\t= hfsplus_write_begin,\n \t.write_end\t= generic_write_end,\n \t.bmap\t\t= hfsplus_bmap,\n-\t.direct_IO\t= hfsplus_direct_IO,\n-\t.writepages\t= hfsplus_writepages,\n+\t.writepages\t= hfsplus_legacy_writepages,\n \t.migrate_folio\t= buffer_migrate_folio,\n };\n \n+static int hfsplus_read_folio(struct file *file, struct folio *folio)\n+{\n+\tiomap_bio_read_folio(folio, \u0026hfsplus_iomap_ops);\n+\treturn 0;\n+}\n+\n+static void hfsplus_readahead(struct readahead_control *rac)\n+{\n+\tiomap_bio_readahead(rac, \u0026hfsplus_iomap_ops);\n+}\n+\n+static int hfsplus_writepages(struct address_space *mapping,\n+\t\t\t struct writeback_control *wbc)\n+{\n+\tstruct iomap_writepage_ctx wpc = {\n+\t\t.inode\t= mapping-\u003ehost,\n+\t\t.wbc\t= wbc,\n+\t\t.ops\t= \u0026hfsplus_writeback_ops,\n+\t};\n+\n+\treturn iomap_writepages(\u0026wpc);\n+}\n+\n+static sector_t hfsplus_aop_bmap(struct address_space *mapping, sector_t block)\n+{\n+\treturn iomap_bmap(mapping, block, \u0026hfsplus_iomap_ops);\n+}\n+\n+const struct address_space_operations hfsplus_aops = {\n+\t.read_folio\t\t= hfsplus_read_folio,\n+\t.readahead\t\t= hfsplus_readahead,\n+\t.writepages\t\t= hfsplus_writepages,\n+\t.dirty_folio\t\t= iomap_dirty_folio,\n+\t.bmap\t\t\t= hfsplus_aop_bmap,\n+\t.migrate_folio\t\t= filemap_migrate_folio,\n+\t.is_partially_uptodate\t= iomap_is_partially_uptodate,\n+\t.error_remove_folio\t= generic_error_remove_folio,\n+\t.release_folio\t\t= iomap_release_folio,\n+\t.invalidate_folio\t= iomap_invalidate_folio,\n+\t.swap_activate\t\t= hfsplus_iomap_swap_activate,\n+};\n+\n const struct dentry_operations hfsplus_dentry_operations = {\n \t.d_hash = hfsplus_hash_dentry,\n \t.d_compare = hfsplus_compare_dentry,\n@@ -268,35 +252,6 @@ static int hfsplus_get_perms(struct inode *inode,\n \treturn -EIO;\n }\n \n-static int hfsplus_file_open(struct inode *inode, struct file *file)\n-{\n-\tif (HFSPLUS_IS_RSRC(inode))\n-\t\tinode = HFSPLUS_I(inode)-\u003ersrc_inode;\n-\tif (!(file-\u003ef_flags \u0026 O_LARGEFILE) \u0026\u0026 i_size_read(inode) \u003e MAX_NON_LFS)\n-\t\treturn -EOVERFLOW;\n-\tatomic_inc(\u0026HFSPLUS_I(inode)-\u003eopencnt);\n-\treturn 0;\n-}\n-\n-static int hfsplus_file_release(struct inode *inode, struct file *file)\n-{\n-\tstruct super_block *sb = inode-\u003ei_sb;\n-\n-\tif (HFSPLUS_IS_RSRC(inode))\n-\t\tinode = HFSPLUS_I(inode)-\u003ersrc_inode;\n-\tif (atomic_dec_and_test(\u0026HFSPLUS_I(inode)-\u003eopencnt)) {\n-\t\tinode_lock(inode);\n-\t\thfsplus_file_truncate(inode);\n-\t\tif (inode-\u003ei_flags \u0026 S_DEAD) {\n-\t\t\thfsplus_delete_cat(inode-\u003ei_ino,\n-\t\t\t\t\t HFSPLUS_SB(sb)-\u003ehidden_dir, NULL);\n-\t\t\thfsplus_delete_inode(inode);\n-\t\t}\n-\t\tinode_unlock(inode);\n-\t}\n-\treturn 0;\n-}\n-\n static int hfsplus_setattr(struct mnt_idmap *idmap,\n \t\t\t struct dentry *dentry, struct iattr *attr)\n {\n@@ -311,10 +266,22 @@ static int hfsplus_setattr(struct mnt_idmap *idmap,\n \t attr-\u003eia_size != i_size_read(inode)) {\n \t\tinode_dio_wait(inode);\n \t\tif (attr-\u003eia_size \u003e inode-\u003ei_size) {\n-\t\t\terror = generic_cont_expand_simple(inode,\n-\t\t\t\t\t\t\t attr-\u003eia_size);\n-\t\t\tif (error)\n-\t\t\t\treturn error;\n+\t\t\tif (S_ISREG(inode-\u003ei_mode)) {\n+\t\t\t\tloff_t old_size = inode-\u003ei_size;\n+\n+\t\t\t\ti_size_write(inode, attr-\u003eia_size);\n+\t\t\t\terror = hfsplus_iomap_cont_expand(inode,\n+\t\t\t\t\t\t\t\t attr-\u003eia_size);\n+\t\t\t\tif (error) {\n+\t\t\t\t\ti_size_write(inode, old_size);\n+\t\t\t\t\treturn error;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\terror = generic_cont_expand_simple(inode,\n+\t\t\t\t\t\t\t\t attr-\u003eia_size);\n+\t\t\t\tif (error)\n+\t\t\t\t\treturn error;\n+\t\t\t}\n \t\t}\n \t\ttruncate_setsize(inode, attr-\u003eia_size);\n \t\thfsplus_file_truncate(inode);\n@@ -353,86 +320,6 @@ int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,\n \treturn 0;\n }\n \n-int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,\n-\t\t int datasync)\n-{\n-\tstruct inode *inode = file-\u003ef_mapping-\u003ehost;\n-\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n-\tstruct super_block *sb = inode-\u003ei_sb;\n-\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(inode-\u003ei_sb);\n-\tstruct hfsplus_vh *vhdr = sbi-\u003es_vhdr;\n-\tint error = 0, error2;\n-\n-\thfs_dbg(\"inode-\u003ei_ino %llu, start %llu, end %llu\\n\",\n-\t\tinode-\u003ei_ino, start, end);\n-\n-\terror = file_write_and_wait_range(file, start, end);\n-\tif (error)\n-\t\treturn error;\n-\tinode_lock(inode);\n-\n-\t/*\n-\t * Sync inode metadata into the catalog and extent trees.\n-\t */\n-\tsync_inode_metadata(inode, 1);\n-\n-\t/*\n-\t * And explicitly write out the btrees.\n-\t */\n-\tif (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,\n-\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))-\u003eflags)) {\n-\t\tclear_bit(HFSPLUS_I_CAT_DIRTY, \u0026hip-\u003eflags);\n-\t\terror = filemap_write_and_wait(sbi-\u003ecat_tree-\u003einode-\u003ei_mapping);\n-\t}\n-\n-\tif (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,\n-\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))-\u003eflags)) {\n-\t\tclear_bit(HFSPLUS_I_EXT_DIRTY, \u0026hip-\u003eflags);\n-\t\terror2 =\n-\t\t\tfilemap_write_and_wait(sbi-\u003eext_tree-\u003einode-\u003ei_mapping);\n-\t\tif (!error)\n-\t\t\terror = error2;\n-\t}\n-\n-\tif (sbi-\u003eattr_tree) {\n-\t\tif (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,\n-\t\t\t\t\u0026HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))-\u003eflags)) {\n-\t\t\tclear_bit(HFSPLUS_I_ATTR_DIRTY, \u0026hip-\u003eflags);\n-\t\t\terror2 =\n-\t\t\t\tfilemap_write_and_wait(\n-\t\t\t\t\t sbi-\u003eattr_tree-\u003einode-\u003ei_mapping);\n-\t\t\tif (!error)\n-\t\t\t\terror = error2;\n-\t\t}\n-\t} else {\n-\t\tif (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, \u0026hip-\u003eflags))\n-\t\t\tpr_err(\"sync non-existent attributes tree\\n\");\n-\t}\n-\n-\tif (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,\n-\t\t\t\t\u0026HFSPLUS_I(sbi-\u003ealloc_file)-\u003eflags)) {\n-\t\tclear_bit(HFSPLUS_I_ALLOC_DIRTY, \u0026hip-\u003eflags);\n-\t\terror2 = filemap_write_and_wait(sbi-\u003ealloc_file-\u003ei_mapping);\n-\t\tif (!error)\n-\t\t\terror = error2;\n-\t}\n-\n-\tmutex_lock(\u0026sbi-\u003evh_mutex);\n-\thfsplus_prepare_volume_header_for_commit(vhdr);\n-\tmutex_unlock(\u0026sbi-\u003evh_mutex);\n-\n-\terror2 = hfsplus_commit_superblock(inode-\u003ei_sb);\n-\tif (!error)\n-\t\terror = error2;\n-\n-\tif (!test_bit(HFSPLUS_SB_NOBARRIER, \u0026sbi-\u003eflags))\n-\t\tblkdev_issue_flush(inode-\u003ei_sb-\u003es_bdev);\n-\n-\tinode_unlock(inode);\n-\n-\treturn error;\n-}\n-\n static const struct inode_operations hfsplus_file_inode_operations = {\n \t.setattr\t= hfsplus_setattr,\n \t.getattr\t= hfsplus_getattr,\n@@ -454,19 +341,6 @@ static const struct inode_operations hfsplus_special_inode_operations = {\n \t.listxattr\t= hfsplus_listxattr,\n };\n \n-static const struct file_operations hfsplus_file_operations = {\n-\t.llseek\t\t= generic_file_llseek,\n-\t.read_iter\t= generic_file_read_iter,\n-\t.write_iter\t= generic_file_write_iter,\n-\t.mmap_prepare\t= generic_file_mmap_prepare,\n-\t.splice_read\t= filemap_splice_read,\n-\t.splice_write\t= iter_file_splice_write,\n-\t.fsync\t\t= hfsplus_file_fsync,\n-\t.open\t\t= hfsplus_file_open,\n-\t.release\t= hfsplus_file_release,\n-\t.unlocked_ioctl = hfsplus_ioctl,\n-};\n-\n struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,\n \t\t\t\tumode_t mode)\n {\n@@ -513,7 +387,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,\n \t\tsbi-\u003efile_count++;\n \t\tinode-\u003ei_op = \u0026hfsplus_symlink_inode_operations;\n \t\tinode_nohighmem(inode);\n-\t\tinode-\u003ei_mapping-\u003ea_ops = \u0026hfsplus_aops;\n+\t\tinode-\u003ei_mapping-\u003ea_ops = \u0026hfsplus_symlink_aops;\n \t\thip-\u003eclump_blocks = 1;\n \t} else if (S_ISCHR(inode-\u003ei_mode) || S_ISBLK(inode-\u003ei_mode) ||\n \t\t S_ISFIFO(inode-\u003ei_mode) || S_ISSOCK(inode-\u003ei_mode)) {\n@@ -654,7 +528,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)\n \t\t} else if (S_ISLNK(inode-\u003ei_mode)) {\n \t\t\tinode-\u003ei_op = \u0026hfsplus_symlink_inode_operations;\n \t\t\tinode_nohighmem(inode);\n-\t\t\tinode-\u003ei_mapping-\u003ea_ops = \u0026hfsplus_aops;\n+\t\t\tinode-\u003ei_mapping-\u003ea_ops = \u0026hfsplus_symlink_aops;\n \t\t} else {\n \t\t\tinode-\u003ei_op = \u0026hfsplus_special_inode_operations;\n \t\t\tinit_special_inode(inode, inode-\u003ei_mode,\ndiff --git a/fs/hfsplus/iomap.c b/fs/hfsplus/iomap.c\nnew file mode 100644\nindex 00000000000000..e00144b27c186a\n--- /dev/null\n+++ b/fs/hfsplus/iomap.c\n@@ -0,0 +1,209 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * linux/fs/hfsplus/iomap.c\n+ *\n+ * iomap callback functions for the hfsplus filesystem\n+ */\n+\n+#include \u003clinux/iomap.h\u003e\n+#include \u003clinux/pagemap.h\u003e\n+\n+#include \"hfsplus_fs.h\"\n+#include \"hfsplus_raw.h\"\n+#include \"iomap.h\"\n+\n+static int __hfsplus_iomap_begin(struct inode *inode, loff_t offset,\n+\t\t\t\t loff_t length, unsigned int flags,\n+\t\t\t\t struct iomap *iomap, bool may_alloc)\n+{\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\tstruct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);\n+\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n+\tu32 ablock, dblock, max_blocks;\n+\tloff_t ablock_offset, ablock_bytes;\n+\tloff_t block_start;\n+\tbool is_new;\n+\tint err;\n+\n+\tif (!may_alloc) {\n+\t\t/* Completely beyond EOF. Treat as hole */\n+\t\tif (i_size_read(inode) \u003c= offset) {\n+\t\t\tiomap-\u003etype = IOMAP_HOLE;\n+\t\t\tiomap-\u003eaddr = IOMAP_NULL_ADDR;\n+\t\t\tiomap-\u003eoffset = offset;\n+\t\t\tiomap-\u003elength = length;\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\t/* Clamp length if the requested range goes beyond i_size */\n+\t\tif (offset + length \u003e i_size_read(inode)) {\n+\t\t\tloff_t i_size = i_size_read(inode);\n+\t\t\tunsigned int blocksize = i_blocksize(inode);\n+\t\t\tlength = round_up(i_size, blocksize) - offset;\n+\t\t}\n+\t}\n+\n+\tablock = offset \u003e\u003e sbi-\u003ealloc_blksz_shift;\n+\n+\terr = hfsplus_map_extent(inode, ablock, may_alloc, \u0026dblock,\n+\t\t\t\t \u0026max_blocks, NULL);\n+\tif (err)\n+\t\treturn err;\n+\n+\tablock_offset = offset \u0026 (sbi-\u003ealloc_blksz - 1);\n+\tablock_bytes = (loff_t)max_blocks \u003c\u003c sbi-\u003ealloc_blksz_shift;\n+\n+\tlength = min_t(loff_t, length, ablock_bytes - ablock_offset);\n+\tblock_start = round_down(offset, i_blocksize(inode));\n+\tis_new = may_alloc \u0026\u0026 block_start \u003e= hip-\u003ephys_size;\n+\tif (may_alloc \u0026\u0026 !is_new \u0026\u0026 offset \u003c hip-\u003ephys_size)\n+\t\tlength = min_t(loff_t, length, hip-\u003ephys_size - offset);\n+\n+\tiomap-\u003ebdev = sb-\u003es_bdev;\n+\tiomap-\u003eoffset = offset;\n+\tiomap-\u003elength = length;\n+\tiomap-\u003eaddr = hfsplus_ablock_to_phys_bytes(sb, dblock) + ablock_offset;\n+\tiomap-\u003etype = IOMAP_MAPPED;\n+\tiomap-\u003eflags = IOMAP_F_MERGED;\n+\n+\tif (is_new)\n+\t\tiomap-\u003eflags |= IOMAP_F_NEW;\n+\n+\treturn 0;\n+}\n+\n+static int hfsplus_iomap_begin(struct inode *inode, loff_t offset,\n+\t\t\t\tloff_t length, unsigned int flags,\n+\t\t\t\tstruct iomap *iomap, struct iomap *srcmap)\n+{\n+\treturn __hfsplus_iomap_begin(inode,\n+\t\t\t\t offset, length, flags,\n+\t\t\t\t iomap, false);\n+}\n+\n+static int hfsplus_write_iomap_begin(struct inode *inode, loff_t offset,\n+\t\t\t\t loff_t length, unsigned int flags,\n+\t\t\t\t struct iomap *iomap, struct iomap *srcmap)\n+{\n+\treturn __hfsplus_iomap_begin(inode,\n+\t\t\t\t offset, length, flags,\n+\t\t\t\t iomap, true);\n+}\n+\n+const struct iomap_ops hfsplus_iomap_ops = {\n+\t.iomap_begin = hfsplus_iomap_begin,\n+};\n+\n+/*\n+ * hfsplus_write_iomap_end()\n+ *\n+ * Advance the allocated-and-zeroed high-water mark\n+ * (hip-\u003ephys_size / hip-\u003efs_blocks) to cover the newly written range.\n+ */\n+static int hfsplus_write_iomap_end(struct inode *inode, loff_t pos,\n+\t\t\t\t loff_t length, ssize_t written,\n+\t\t\t\t unsigned int flags, struct iomap *iomap)\n+{\n+\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\tloff_t end;\n+\tbool dirtied = false;\n+\n+\tif (!written)\n+\t\treturn 0;\n+\n+\tend = round_up(pos + written, sb-\u003es_blocksize);\n+\n+\tif (hip-\u003ephys_size \u003c end) {\n+\t\tinode_add_bytes(inode, end - hip-\u003ephys_size);\n+\t\thip-\u003ephys_size = end;\n+\t\thip-\u003efs_blocks = end \u003e\u003e sb-\u003es_blocksize_bits;\n+\t\tdirtied = true;\n+\t}\n+\n+\tif (dirtied)\n+\t\tmark_inode_dirty(inode);\n+\n+\treturn written;\n+}\n+\n+const struct iomap_ops hfsplus_write_iomap_ops = {\n+\t.iomap_begin\t= hfsplus_write_iomap_begin,\n+\t.iomap_end\t= hfsplus_write_iomap_end,\n+};\n+\n+/*\n+ * hfsplus_iomap_cont_expand()\n+ *\n+ * Zero-extend the backing store from the current phys_size up to 'size'.\n+ * Used both by hfsplus_setattr() and by hfsplus_file_truncate().\n+ */\n+int hfsplus_iomap_cont_expand(struct inode *inode, loff_t size)\n+{\n+\tstruct hfsplus_inode_info *hip = HFSPLUS_I(inode);\n+\tloff_t start = hip-\u003ephys_size;\n+\n+\tif (size \u003c= start)\n+\t\treturn 0;\n+\n+\treturn iomap_zero_range(inode, start, size - start, NULL,\n+\t\t\t\t\u0026hfsplus_write_iomap_ops, NULL, NULL);\n+}\n+\n+/*\n+ * hfsplus_writeback_range() - map folio during writeback\n+ *\n+ * Called for each folio during writeback. If the folio falls outside\n+ * the current iomap, remaps by calling __hfsplus_iomap_begin() again.\n+ */\n+static ssize_t hfsplus_writeback_range(struct iomap_writepage_ctx *wpc,\n+\t\t\t\t\tstruct folio *folio, u64 offset,\n+\t\t\t\t\tunsigned int len, u64 end_pos)\n+{\n+\tint err;\n+\n+\tif (offset \u003c wpc-\u003eiomap.offset ||\n+\t offset \u003e= wpc-\u003eiomap.offset + wpc-\u003eiomap.length) {\n+\t\terr = __hfsplus_iomap_begin(wpc-\u003einode,\n+\t\t\t\t\t offset, len, 0,\n+\t\t\t\t\t \u0026wpc-\u003eiomap, false);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn iomap_add_to_ioend(wpc, folio, offset, end_pos, len);\n+}\n+\n+const struct iomap_writeback_ops hfsplus_writeback_ops = {\n+\t.writeback_range\t= hfsplus_writeback_range,\n+\t.writeback_submit\t= iomap_ioend_writeback_submit,\n+};\n+\n+/*\n+ * hfsplus_file_write_dio_end_io() - Direct I/O write completion handler\n+ */\n+static int hfsplus_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,\n+\t\t\t\t\t int error, unsigned int flags)\n+{\n+\tstruct inode *inode = file_inode(iocb-\u003eki_filp);\n+\n+\tif (error)\n+\t\treturn error;\n+\n+\tif (size \u0026\u0026 i_size_read(inode) \u003c iocb-\u003eki_pos + size) {\n+\t\ti_size_write(inode, iocb-\u003eki_pos + size);\n+\t\tmark_inode_dirty(inode);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+const struct iomap_dio_ops hfsplus_write_dio_ops = {\n+\t.end_io\t\t= hfsplus_file_write_dio_end_io,\n+};\n+\n+int hfsplus_iomap_swap_activate(struct swap_info_struct *sis,\n+\t\t\t\t struct file *file, sector_t *span)\n+{\n+\treturn iomap_swapfile_activate(sis, file, span, \u0026hfsplus_iomap_ops);\n+}\ndiff --git a/fs/hfsplus/iomap.h b/fs/hfsplus/iomap.h\nnew file mode 100644\nindex 00000000000000..1ea3333c1e0759\n--- /dev/null\n+++ b/fs/hfsplus/iomap.h\n@@ -0,0 +1,20 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+/*\n+ * linux/fs/hfsplus/iomap.h\n+ *\n+ * iomap callback declarations for the hfsplus filesystem\n+ */\n+\n+#ifndef _LINUX_HFSPLUS_IOMAP_H\n+#define _LINUX_HFSPLUS_IOMAP_H\n+\n+extern const struct iomap_ops hfsplus_iomap_ops;\n+extern const struct iomap_ops hfsplus_write_iomap_ops;\n+extern const struct iomap_writeback_ops hfsplus_writeback_ops;\n+extern const struct iomap_dio_ops hfsplus_write_dio_ops;\n+\n+int hfsplus_iomap_cont_expand(struct inode *inode, loff_t size);\n+int hfsplus_iomap_swap_activate(struct swap_info_struct *sis,\n+\t\t\t\tstruct file *file, sector_t *span);\n+\n+#endif /* _LINUX_HFSPLUS_IOMAP_H */\ndiff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h\nindex 45fb4c9ff9f5ee..1417e19d94bcec 100644\n--- a/include/linux/hfs_common.h\n+++ b/include/linux/hfs_common.h\n@@ -171,18 +171,22 @@ enum {\n \tHFS_XATTR_NAME,\n };\n \n+#define HFS_FORK_EXTENT_COUNT\t(3)\n+\n struct hfs_extent {\n \t__be16 block;\n \t__be16 count;\n };\n-typedef struct hfs_extent hfs_extent_rec[3];\n+typedef struct hfs_extent hfs_extent_rec[HFS_FORK_EXTENT_COUNT];\n+\n+#define HFSPLUS_FORK_EXTENT_COUNT\t(8)\n \n /* A single contiguous area of a file */\n struct hfsplus_extent {\n \t__be32 start_block;\n \t__be32 block_count;\n } __packed;\n-typedef struct hfsplus_extent hfsplus_extent_rec[8];\n+typedef struct hfsplus_extent hfsplus_extent_rec[HFSPLUS_FORK_EXTENT_COUNT];\n \n /* Information for a \"Fork\" in a file */\n struct hfsplus_fork_raw {\n"
} |
| 2/1 | 2026/07/24 00:38 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_map_extent",
"hfsplus_iomap_begin",
"hfsplus_write_iomap_begin",
"hfsplus_write_iomap_end",
"hfsplus_iomap_cont_expand",
"hfsplus_writeback_range",
"hfsplus_file_write_dio_end_io",
"hfsplus_fallback_buffered_write",
"hfsplus_dio_write_iter",
"hfsplus_file_write_iter",
"hfsplus_file_read_iter",
"hfsplus_file_llseek",
"hfsplus_file_fsync",
"hfsplus_block_allocate",
"hfsplus_block_free"
],
"Reasoning": "The patch series converts the HFS+ filesystem to use the modern iomap infrastructure for file I/O operations (read, write, seek, truncate). This is a major functional change to the core I/O paths of the filesystem and should be heavily fuzzed to ensure correctness and prevent regressions.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 856fe76f41e0964e0b4dc27b30217003960857dd
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Jul 24 00:38:41 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig
index ca8401cb6954d5..b4432c64db3bec 100644
--- a/fs/hfsplus/Kconfig
+++ b/fs/hfsplus/Kconfig
@@ -5,7 +5,7 @@ config HFSPLUS_FS
select BUFFER_HEAD
select NLS
select NLS_UTF8
- select LEGACY_DIRECT_IO
+ select FS_IOMAP
help
If you say Y here, you will be able to mount extended format
Macintosh-formatted hard drive partitions with full read-write access.
diff --git a/fs/hfsplus/Makefile b/fs/hfsplus/Makefile
index f2a9ae697e8114..3ddea69a9c6983 100644
--- a/fs/hfsplus/Makefile
+++ b/fs/hfsplus/Makefile
@@ -5,8 +5,9 @@
obj-$(CONFIG_HFSPLUS_FS) += hfsplus.o
-hfsplus-objs := super.o options.o inode.o ioctl.o extents.o catalog.o dir.o btree.o \
- bnode.o brec.o bfind.o tables.o unicode.o wrapper.o bitmap.o part_tbl.o \
+hfsplus-objs := super.o options.o inode.o file.o iomap.o ioctl.o extents.o \
+ catalog.o dir.o btree.o bnode.o brec.o bfind.o tables.o \
+ unicode.o wrapper.o bitmap.o part_tbl.o \
attributes.o xattr.o xattr_user.o xattr_security.o xattr_trusted.o
# KUnit tests
diff --git a/fs/hfsplus/bitmap.c b/fs/hfsplus/bitmap.c
index 1b3af8c87cadb5..571652fbaeda3b 100644
--- a/fs/hfsplus/bitmap.c
+++ b/fs/hfsplus/bitmap.c
@@ -39,6 +39,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
i = offset % 32;
@@ -75,6 +76,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
curr++;
}
kunmap_local(pptr);
+ unlock_page(page);
offset += PAGE_CACHE_BITS;
if (offset >= size)
break;
@@ -84,6 +86,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
curr = pptr = kmap_local_page(page);
if ((size ^ offset) / PAGE_CACHE_BITS)
end = pptr + PAGE_CACHE_BITS / 32;
@@ -98,6 +101,9 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = offset + (curr - pptr) * 32 + i;
if (start >= size) {
hfs_dbg("bitmap full\n");
+ kunmap_local(pptr);
+ unlock_page(page);
+ start = size;
goto out;
}
/* do any partial u32 at the start */
@@ -128,6 +134,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
}
set_page_dirty(page);
kunmap_local(pptr);
+ unlock_page(page);
offset += PAGE_CACHE_BITS;
page = read_mapping_page(mapping, offset / PAGE_CACHE_BITS,
NULL);
@@ -135,6 +142,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -152,6 +160,7 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
*curr = cpu_to_be32(n);
set_page_dirty(page);
kunmap_local(pptr);
+ unlock_page(page);
*max = offset + (curr - pptr) * 32 + i - start;
sbi->free_blocks -= *max;
hfsplus_mark_mdb_dirty(sb);
@@ -185,6 +194,7 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)
page = read_mapping_page(mapping, pnr, NULL);
if (IS_ERR(page))
goto kaboom;
+ lock_page(page);
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -216,9 +226,11 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)
break;
set_page_dirty(page);
kunmap_local(pptr);
+ unlock_page(page);
page = read_mapping_page(mapping, ++pnr, NULL);
if (IS_ERR(page))
goto kaboom;
+ lock_page(page);
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -232,6 +244,7 @@ int hfsplus_block_free(struct super_block *sb, u32 offset, u32 count)
out:
set_page_dirty(page);
kunmap_local(pptr);
+ unlock_page(page);
sbi->free_blocks += len;
hfsplus_mark_mdb_dirty(sb);
mutex_unlock(&sbi->alloc_mutex);
diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c
index 813e68b8ecd654..8f7f560cec6427 100644
--- a/fs/hfsplus/extents.c
+++ b/fs/hfsplus/extents.c
@@ -15,6 +15,7 @@
#include "hfsplus_fs.h"
#include "hfsplus_raw.h"
+#include "iomap.h"
/* Compare two extents keys, returns 0 on same, pos/neg for difference */
int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
@@ -48,18 +49,29 @@ static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,
key->ext.pad = 0;
}
-static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off)
+/*
+ * hfsplus_ext_find_block() - find contiguous sequence of block
+ *
+ * Find the disk allocation block for 'off' within an 8-entry
+ * extent record, and the number of further allocation blocks
+ * that are contiguous with it in the same extent entry.
+ */
+static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off,
+ u32 *dblock)
{
int i;
u32 count;
- for (i = 0; i < 8; ext++, i++) {
+ for (i = 0; i < HFSPLUS_FORK_EXTENT_COUNT; ext++, i++) {
count = be32_to_cpu(ext->block_count);
- if (off < count)
- return be32_to_cpu(ext->start_block) + off;
+ if (off < count) {
+ *dblock = be32_to_cpu(ext->start_block) + off;
+ return count - off;
+ }
off -= count;
}
/* panic? */
+ *dblock = 0;
return 0;
}
@@ -68,7 +80,7 @@ static int hfsplus_ext_block_count(struct hfsplus_extent *ext)
int i;
u32 count = 0;
- for (i = 0; i < 8; ext++, i++)
+ for (i = 0; i < HFSPLUS_FORK_EXTENT_COUNT; ext++, i++)
count += be32_to_cpu(ext->block_count);
return count;
}
@@ -223,37 +235,46 @@ static int hfsplus_ext_read_extent(struct inode *inode, u32 block)
return res;
}
-/* Get a block at iblock for inode, possibly allocating if create */
-int hfsplus_get_block(struct inode *inode, sector_t iblock,
- struct buffer_head *bh_result, int create)
+/*
+ * hfsplus_map_extent() - find or allocate a sequence of allocation blocks
+ *
+ * Looks up the allocation block at 'ablock' for inode, extending the
+ * file (via hfsplus_file_extend(), in clump_blocks-sized chunks) when
+ * 'create' is set and 'ablock' lies beyond the current allocation.
+ *
+ * On success, *dblock is the disk allocation block backing 'ablock',
+ * and *max_blocks is the number of further allocation blocks that are
+ * contiguous with it (i.e. the remaining length of the extent entry
+ * that contains 'ablock'), which may be smaller than the whole file's
+ * remaining allocation when the fork is fragmented across several
+ * extent entries. If a new extent had to be allocated to satisfy the
+ * request, *balloc (when non-NULL) is set to true.
+ */
+int hfsplus_map_extent(struct inode *inode, u32 ablock, int create,
+ u32 *dblock, u32 *max_blocks, bool *balloc)
{
- struct super_block *sb = inode->i_sb;
- struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
- int res = -EIO;
- u32 ablock, dblock, mask;
- sector_t sector;
- int was_dirty = 0;
+ int was_dirty;
+ int res;
- /* Convert inode block to disk allocation block */
- ablock = iblock >> sbi->fs_shift;
+ if (balloc)
+ *balloc = false;
- if (iblock >= hip->fs_blocks) {
+ if (ablock >= hip->alloc_blocks) {
if (!create)
- return 0;
- if (iblock > hip->fs_blocks)
return -EIO;
- if (ablock >= hip->alloc_blocks) {
- res = hfsplus_file_extend(inode, false);
- if (res)
- return res;
- }
- } else
- create = 0;
+ res = hfsplus_file_extend(inode, false);
+ if (res)
+ return res;
+ if (balloc)
+ *balloc = true;
+ }
if (ablock < hip->first_blocks) {
- dblock = hfsplus_ext_find_block(hip->first_extents, ablock);
- goto done;
+ *max_blocks = hfsplus_ext_find_block(hip->first_extents,
+ ablock,
+ dblock);
+ return 0;
}
if (inode->i_ino == HFSPLUS_EXT_CNID)
@@ -272,11 +293,44 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,
mutex_unlock(&hip->extents_lock);
return -EIO;
}
- dblock = hfsplus_ext_find_block(hip->cached_extents,
- ablock - hip->cached_start);
+ *max_blocks = hfsplus_ext_find_block(hip->cached_extents,
+ ablock - hip->cached_start,
+ dblock);
mutex_unlock(&hip->extents_lock);
-done:
+ if (was_dirty)
+ mark_inode_dirty(inode);
+
+ return 0;
+}
+
+/* Get a block at iblock for inode, possibly allocating if create */
+int hfsplus_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ u32 ablock, dblock, mask, max_blocks;
+ sector_t sector;
+ int res;
+
+ /* Convert inode block to disk allocation block */
+ ablock = iblock >> sbi->fs_shift;
+
+ if (iblock >= hip->fs_blocks) {
+ if (!create)
+ return 0;
+ if (iblock > hip->fs_blocks)
+ return -EIO;
+ } else
+ create = 0;
+
+ res = hfsplus_map_extent(inode, ablock, create, &dblock, &max_blocks,
+ NULL);
+ if (res)
+ return res;
+
hfs_dbg("ino %llu, iblock %llu - dblock %u\n",
inode->i_ino, (long long)iblock, dblock);
@@ -290,9 +344,8 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,
hip->phys_size += sb->s_blocksize;
hip->fs_blocks++;
inode_add_bytes(inode, sb->s_blocksize);
- }
- if (create || was_dirty)
mark_inode_dirty(inode);
+ }
return 0;
}
@@ -553,20 +606,32 @@ void hfsplus_file_truncate(struct inode *inode)
inode->i_ino, (long long)hip->phys_size, inode->i_size);
if (inode->i_size > hip->phys_size) {
- struct address_space *mapping = inode->i_mapping;
- struct folio *folio;
- void *fsdata = NULL;
- loff_t size = inode->i_size;
+ if (S_ISREG(inode->i_mode)) {
+ res = hfsplus_iomap_cont_expand(inode, inode->i_size);
+ if (res)
+ return;
+
+ mark_inode_dirty(inode);
+ } else {
+ struct address_space *mapping = inode->i_mapping;
+ struct folio *folio;
+ void *fsdata = NULL;
+
+ res = hfsplus_write_begin(NULL, mapping,
+ inode->i_size, 0,
+ &folio, &fsdata);
+ if (res)
+ return;
+
+ res = generic_write_end(NULL, mapping,
+ inode->i_size, 0, 0,
+ folio, fsdata);
+ if (res < 0)
+ return;
+
+ mark_inode_dirty(inode);
+ }
- res = hfsplus_write_begin(NULL, mapping, size, 0,
- &folio, &fsdata);
- if (res)
- return;
- res = generic_write_end(NULL, mapping, size, 0, 0,
- folio, fsdata);
- if (res < 0)
- return;
- mark_inode_dirty(inode);
return;
} else if (inode->i_size == hip->phys_size)
return;
diff --git a/fs/hfsplus/file.c b/fs/hfsplus/file.c
new file mode 100644
index 00000000000000..8c92e145295ca1
--- /dev/null
+++ b/fs/hfsplus/file.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/file.c
+ *
+ * File operations: open/release/fsync and iomap-based read/write/seek
+ */
+
+#include <linux/fs.h>
+#include <linux/uio.h>
+#include <linux/mount.h>
+#include <linux/iomap.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+#include "iomap.h"
+
+static int hfsplus_file_open(struct inode *inode, struct file *file)
+{
+ if (HFSPLUS_IS_RSRC(inode))
+ inode = HFSPLUS_I(inode)->rsrc_inode;
+ if (!(file->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
+ return -EOVERFLOW;
+ atomic_inc(&HFSPLUS_I(inode)->opencnt);
+ file->f_mode |= FMODE_CAN_ODIRECT;
+ return 0;
+}
+
+static int hfsplus_file_release(struct inode *inode, struct file *file)
+{
+ struct super_block *sb = inode->i_sb;
+
+ if (HFSPLUS_IS_RSRC(inode))
+ inode = HFSPLUS_I(inode)->rsrc_inode;
+ if (atomic_dec_and_test(&HFSPLUS_I(inode)->opencnt)) {
+ inode_lock(inode);
+ hfsplus_file_truncate(inode);
+ if (inode->i_flags & S_DEAD) {
+ hfsplus_delete_cat(inode->i_ino,
+ HFSPLUS_SB(sb)->hidden_dir, NULL);
+ hfsplus_delete_inode(inode);
+ }
+ inode_unlock(inode);
+ }
+ return 0;
+}
+
+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
+ int datasync)
+{
+ struct inode *inode = file->f_mapping->host;
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ struct hfsplus_vh *vhdr = sbi->s_vhdr;
+ int error = 0, error2;
+
+ hfs_dbg("inode->i_ino %llu, start %llu, end %llu\n",
+ inode->i_ino, start, end);
+
+ error = file_write_and_wait_range(file, start, end);
+ if (error)
+ return error;
+
+ /*
+ * Sync inode metadata into the catalog and extent trees.
+ */
+ sync_inode_metadata(inode, 1);
+
+ /*
+ * And explicitly write out the btrees.
+ */
+ if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags);
+ error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping);
+ }
+
+ if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,
+ &HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
+ error2 =
+ filemap_write_and_wait(sbi->ext_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ }
+
+ if (sbi->attr_tree) {
+ if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,
+ &HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))->flags)) {
+ clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags);
+ error2 =
+ filemap_write_and_wait(
+ sbi->attr_tree->inode->i_mapping);
+ if (!error)
+ error = error2;
+ }
+ } else {
+ if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags))
+ pr_err("sync non-existent attributes tree\n");
+ }
+
+ if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,
+ &HFSPLUS_I(sbi->alloc_file)->flags)) {
+ clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags);
+ error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
+ if (!error)
+ error = error2;
+ }
+
+ mutex_lock(&sbi->vh_mutex);
+ hfsplus_prepare_volume_header_for_commit(vhdr);
+ mutex_unlock(&sbi->vh_mutex);
+
+ error2 = hfsplus_commit_superblock(inode->i_sb);
+ if (!error)
+ error = error2;
+
+ if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
+ blkdev_issue_flush(inode->i_sb->s_bdev);
+
+ return error;
+}
+
+/*
+ * hfsplus_fallback_buffered_write() - fall back to buffered I/O for the
+ * tail of a write that iomap_dio_rw() could not perform directly
+ * (unaligned tail, or no blocks could be mapped without allocation
+ * outside the direct path).
+ */
+static ssize_t hfsplus_fallback_buffered_write(struct kiocb *iocb,
+ struct iov_iter *from)
+{
+ loff_t offset = iocb->ki_pos, end;
+ ssize_t written;
+ int ret;
+
+ iocb->ki_flags &= ~IOCB_DIRECT;
+
+ written = iomap_file_buffered_write(iocb, from,
+ &hfsplus_write_iomap_ops,
+ NULL, NULL);
+ if (written < 0)
+ return written;
+
+ end = iocb->ki_pos + written - 1;
+ ret = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,
+ offset, end);
+ if (ret)
+ return -EIO;
+
+ invalidate_mapping_pages(iocb->ki_filp->f_mapping,
+ offset >> PAGE_SHIFT,
+ end >> PAGE_SHIFT);
+
+ return written;
+}
+
+static ssize_t hfsplus_dio_write_iter(struct kiocb *iocb,
+ struct iov_iter *from)
+{
+ ssize_t ret;
+
+ ret = iomap_dio_rw(iocb, from,
+ &hfsplus_write_iomap_ops,
+ &hfsplus_write_dio_ops,
+ 0, NULL, 0);
+ if (ret == -ENOTBLK)
+ ret = 0;
+ else if (ret < 0)
+ return ret;
+
+ if (iov_iter_count(from)) {
+ ssize_t written;
+
+ written = hfsplus_fallback_buffered_write(iocb, from);
+ if (written < 0)
+ return written;
+ ret += written;
+ }
+
+ return ret;
+}
+
+static ssize_t hfsplus_file_write_iter(struct kiocb *iocb,
+ struct iov_iter *iter)
+{
+ struct file *file = iocb->ki_filp;
+ struct inode *inode = file_inode(file);
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
+ loff_t total_capacity;
+ ssize_t ret;
+ int err;
+
+ inode_lock(inode);
+
+ ret = generic_write_checks(iocb, iter);
+ if (ret <= 0)
+ goto unlock;
+
+ total_capacity = (loff_t)sbi->total_blocks << sbi->alloc_blksz_shift;
+ if (iocb->ki_pos >= total_capacity) {
+ ret = -EFBIG;
+ goto unlock;
+ }
+
+ err = file_modified(file);
+ if (err) {
+ ret = err;
+ goto unlock;
+ }
+
+ if (iocb->ki_pos > i_size_read(inode)) {
+ loff_t old_size = i_size_read(inode);
+ loff_t new_size = iocb->ki_pos;
+
+ if (iocb->ki_flags & IOCB_DIRECT) {
+ new_size = max_t(loff_t, new_size,
+ HFSPLUS_I(inode)->phys_size);
+ }
+
+ i_size_write(inode, new_size);
+ err = hfsplus_iomap_cont_expand(inode, iocb->ki_pos);
+ if (err) {
+ i_size_write(inode, old_size);
+ ret = err;
+ goto unlock;
+ }
+ mark_inode_dirty(inode);
+ } else if ((iocb->ki_flags & IOCB_DIRECT) &&
+ HFSPLUS_I(inode)->phys_size > i_size_read(inode)) {
+ i_size_write(inode, HFSPLUS_I(inode)->phys_size);
+ mark_inode_dirty(inode);
+ }
+
+ if (iocb->ki_flags & IOCB_DIRECT)
+ ret = hfsplus_dio_write_iter(iocb, iter);
+ else {
+ ret = iomap_file_buffered_write(iocb, iter,
+ &hfsplus_write_iomap_ops,
+ NULL, NULL);
+ }
+
+unlock:
+ inode_unlock(inode);
+
+ if (ret > 0)
+ ret = generic_write_sync(iocb, ret);
+
+ return ret;
+}
+
+static ssize_t hfsplus_file_read_iter(struct kiocb *iocb,
+ struct iov_iter *iter)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+ ssize_t ret;
+
+ inode_lock_shared(inode);
+
+ if (iocb->ki_flags & IOCB_DIRECT) {
+ file_accessed(iocb->ki_filp);
+ ret = iomap_dio_rw(iocb, iter,
+ &hfsplus_iomap_ops,
+ NULL, 0, NULL, 0);
+ } else
+ ret = generic_file_read_iter(iocb, iter);
+
+ inode_unlock_shared(inode);
+
+ return ret;
+}
+
+static loff_t hfsplus_file_llseek(struct file *file, loff_t offset, int whence)
+{
+ struct inode *inode = file->f_mapping->host;
+
+ switch (whence) {
+ case SEEK_HOLE:
+ inode_lock_shared(inode);
+ offset = iomap_seek_hole(inode, offset, &hfsplus_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ case SEEK_DATA:
+ inode_lock_shared(inode);
+ offset = iomap_seek_data(inode, offset, &hfsplus_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ default:
+ return generic_file_llseek(file, offset, whence);
+ }
+
+ if (offset < 0)
+ return offset;
+
+ return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
+}
+
+const struct file_operations hfsplus_file_operations = {
+ .llseek = hfsplus_file_llseek,
+ .read_iter = hfsplus_file_read_iter,
+ .write_iter = hfsplus_file_write_iter,
+ .mmap_prepare = generic_file_mmap_prepare,
+ .splice_read = filemap_splice_read,
+ .splice_write = iter_file_splice_write,
+ .fsync = hfsplus_file_fsync,
+ .open = hfsplus_file_open,
+ .release = hfsplus_file_release,
+ .unlocked_ioctl = hfsplus_ioctl,
+};
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
index ec04b82ad92787..b9cadbc6c6d216 100644
--- a/fs/hfsplus/hfsplus_fs.h
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -175,6 +175,22 @@ static inline struct hfsplus_sb_info *HFSPLUS_SB(struct super_block *sb)
return sb->s_fs_info;
}
+/*
+ * Physical byte offset of allocation block 'dblock' on the volume.
+ */
+static inline loff_t hfsplus_ablock_to_phys_bytes(struct super_block *sb,
+ u32 dblock)
+{
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ loff_t phys_bytes;
+
+ phys_bytes = dblock;
+ phys_bytes <<= sbi->fs_shift;
+ phys_bytes += sbi->blockoffset;
+ phys_bytes <<= sb->s_blocksize_bits;
+
+ return phys_bytes;
+}
struct hfsplus_inode_info {
atomic_t opencnt;
@@ -436,6 +452,8 @@ int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
int hfsplus_ext_write_extent(struct inode *inode);
int hfsplus_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
+int hfsplus_map_extent(struct inode *inode, u32 ablock, int create,
+ u32 *dblock, u32 *max_blocks, bool *balloc);
int hfsplus_free_fork(struct super_block *sb, u32 cnid,
struct hfsplus_fork_raw *fork, int type);
int hfsplus_file_extend(struct inode *inode, bool zeroout);
@@ -443,6 +461,7 @@ void hfsplus_file_truncate(struct inode *inode);
/* inode.c */
extern const struct address_space_operations hfsplus_aops;
+extern const struct address_space_operations hfsplus_symlink_aops;
extern const struct address_space_operations hfsplus_btree_aops;
extern const struct dentry_operations hfsplus_dentry_operations;
@@ -462,12 +481,15 @@ int hfsplus_cat_write_inode(struct inode *inode);
int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int query_flags);
-int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
- int datasync);
int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
int hfsplus_fileattr_set(struct mnt_idmap *idmap,
struct dentry *dentry, struct file_kattr *fa);
+/* file.c */
+extern const struct file_operations hfsplus_file_operations;
+int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
+ int datasync);
+
/* ioctl.c */
long hfsplus_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 1c57c873f05d61..1f16663eadb095 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -18,12 +18,14 @@
#include <linux/cred.h>
#include <linux/uio.h>
#include <linux/fileattr.h>
+#include <linux/iomap.h>
#include "hfsplus_fs.h"
#include "hfsplus_raw.h"
#include "xattr.h"
+#include "iomap.h"
-static int hfsplus_read_folio(struct file *file, struct folio *folio)
+static int hfsplus_legacy_read_folio(struct file *file, struct folio *folio)
{
return block_read_full_folio(folio, hfsplus_get_block);
}
@@ -120,66 +122,7 @@ static bool hfsplus_release_folio(struct folio *folio, gfp_t mask)
return res ? try_to_free_buffers(folio) : false;
}
-static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
-{
- struct file *file = iocb->ki_filp;
- struct address_space *mapping = file->f_mapping;
- struct inode *inode = mapping->host;
- loff_t isize;
- size_t count = iov_iter_count(iter);
- loff_t end = iocb->ki_pos + count;
- ssize_t ret;
-
- /*
- * The hfsplus_get_block() only allows creating the next sequential block.
- * For direct writes beyond EOF, expand the file first.
- */
- if (iov_iter_rw(iter) == WRITE && iocb->ki_pos > i_size_read(inode)) {
- loff_t start_off, end_off;
- loff_t start_page, end_page;
-
- isize = i_size_read(inode);
-
- /*
- * Wait for any in-flight DIO on this inode to finish before
- * calling generic_cont_expand_simple().
- */
- inode_dio_wait(inode);
-
- ret = generic_cont_expand_simple(inode, iocb->ki_pos);
- if (ret)
- return ret;
-
- start_off = isize;
- end_off = (end > 0) ? end - 1 : end;
-
- ret = filemap_write_and_wait_range(mapping, start_off, end_off);
- if (ret)
- return ret;
-
- start_page = start_off >> PAGE_SHIFT;
- end_page = end_off >> PAGE_SHIFT;
-
- invalidate_inode_pages2_range(mapping, start_page, end_page);
- }
-
- ret = blockdev_direct_IO(iocb, inode, iter, hfsplus_get_block);
-
- /*
- * In case of error extending write may have instantiated a few
- * blocks outside i_size. Trim these off again.
- */
- if (unlikely(iov_iter_rw(iter) == WRITE && ret < 0)) {
- isize = i_size_read(inode);
-
- if (end > isize)
- hfsplus_write_failed(mapping, end);
- }
-
- return ret;
-}
-
-static int hfsplus_writepages(struct address_space *mapping,
+static int hfsplus_legacy_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
return mpage_writepages(mapping, wbc, hfsplus_get_block);
@@ -188,8 +131,8 @@ static int hfsplus_writepages(struct address_space *mapping,
const struct address_space_operations hfsplus_btree_aops = {
.dirty_folio = block_dirty_folio,
.invalidate_folio = block_invalidate_folio,
- .read_folio = hfsplus_read_folio,
- .writepages = hfsplus_writepages,
+ .read_folio = hfsplus_legacy_read_folio,
+ .writepages = hfsplus_legacy_writepages,
.write_begin = hfsplus_write_begin,
.write_end = generic_write_end,
.migrate_folio = buffer_migrate_folio,
@@ -197,18 +140,59 @@ const struct address_space_operations hfsplus_btree_aops = {
.release_folio = hfsplus_release_folio,
};
-const struct address_space_operations hfsplus_aops = {
+const struct address_space_operations hfsplus_symlink_aops = {
.dirty_folio = block_dirty_folio,
.invalidate_folio = block_invalidate_folio,
- .read_folio = hfsplus_read_folio,
+ .read_folio = hfsplus_legacy_read_folio,
.write_begin = hfsplus_write_begin,
.write_end = generic_write_end,
.bmap = hfsplus_bmap,
- .direct_IO = hfsplus_direct_IO,
- .writepages = hfsplus_writepages,
+ .writepages = hfsplus_legacy_writepages,
.migrate_folio = buffer_migrate_folio,
};
+static int hfsplus_read_folio(struct file *file, struct folio *folio)
+{
+ iomap_bio_read_folio(folio, &hfsplus_iomap_ops);
+ return 0;
+}
+
+static void hfsplus_readahead(struct readahead_control *rac)
+{
+ iomap_bio_readahead(rac, &hfsplus_iomap_ops);
+}
+
+static int hfsplus_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ struct iomap_writepage_ctx wpc = {
+ .inode = mapping->host,
+ .wbc = wbc,
+ .ops = &hfsplus_writeback_ops,
+ };
+
+ return iomap_writepages(&wpc);
+}
+
+static sector_t hfsplus_aop_bmap(struct address_space *mapping, sector_t block)
+{
+ return iomap_bmap(mapping, block, &hfsplus_iomap_ops);
+}
+
+const struct address_space_operations hfsplus_aops = {
+ .read_folio = hfsplus_read_folio,
+ .readahead = hfsplus_readahead,
+ .writepages = hfsplus_writepages,
+ .dirty_folio = iomap_dirty_folio,
+ .bmap = hfsplus_aop_bmap,
+ .migrate_folio = filemap_migrate_folio,
+ .is_partially_uptodate = iomap_is_partially_uptodate,
+ .error_remove_folio = generic_error_remove_folio,
+ .release_folio = iomap_release_folio,
+ .invalidate_folio = iomap_invalidate_folio,
+ .swap_activate = hfsplus_iomap_swap_activate,
+};
+
const struct dentry_operations hfsplus_dentry_operations = {
.d_hash = hfsplus_hash_dentry,
.d_compare = hfsplus_compare_dentry,
@@ -268,35 +252,6 @@ static int hfsplus_get_perms(struct inode *inode,
return -EIO;
}
-static int hfsplus_file_open(struct inode *inode, struct file *file)
-{
- if (HFSPLUS_IS_RSRC(inode))
- inode = HFSPLUS_I(inode)->rsrc_inode;
- if (!(file->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
- return -EOVERFLOW;
- atomic_inc(&HFSPLUS_I(inode)->opencnt);
- return 0;
-}
-
-static int hfsplus_file_release(struct inode *inode, struct file *file)
-{
- struct super_block *sb = inode->i_sb;
-
- if (HFSPLUS_IS_RSRC(inode))
- inode = HFSPLUS_I(inode)->rsrc_inode;
- if (atomic_dec_and_test(&HFSPLUS_I(inode)->opencnt)) {
- inode_lock(inode);
- hfsplus_file_truncate(inode);
- if (inode->i_flags & S_DEAD) {
- hfsplus_delete_cat(inode->i_ino,
- HFSPLUS_SB(sb)->hidden_dir, NULL);
- hfsplus_delete_inode(inode);
- }
- inode_unlock(inode);
- }
- return 0;
-}
-
static int hfsplus_setattr(struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *attr)
{
@@ -311,10 +266,22 @@ static int hfsplus_setattr(struct mnt_idmap *idmap,
attr->ia_size != i_size_read(inode)) {
inode_dio_wait(inode);
if (attr->ia_size > inode->i_size) {
- error = generic_cont_expand_simple(inode,
- attr->ia_size);
- if (error)
- return error;
+ if (S_ISREG(inode->i_mode)) {
+ loff_t old_size = inode->i_size;
+
+ i_size_write(inode, attr->ia_size);
+ error = hfsplus_iomap_cont_expand(inode,
+ attr->ia_size);
+ if (error) {
+ i_size_write(inode, old_size);
+ return error;
+ }
+ } else {
+ error = generic_cont_expand_simple(inode,
+ attr->ia_size);
+ if (error)
+ return error;
+ }
}
truncate_setsize(inode, attr->ia_size);
hfsplus_file_truncate(inode);
@@ -353,86 +320,6 @@ int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path,
return 0;
}
-int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,
- int datasync)
-{
- struct inode *inode = file->f_mapping->host;
- struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
- struct super_block *sb = inode->i_sb;
- struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
- struct hfsplus_vh *vhdr = sbi->s_vhdr;
- int error = 0, error2;
-
- hfs_dbg("inode->i_ino %llu, start %llu, end %llu\n",
- inode->i_ino, start, end);
-
- error = file_write_and_wait_range(file, start, end);
- if (error)
- return error;
- inode_lock(inode);
-
- /*
- * Sync inode metadata into the catalog and extent trees.
- */
- sync_inode_metadata(inode, 1);
-
- /*
- * And explicitly write out the btrees.
- */
- if (test_and_clear_bit(HFSPLUS_I_CAT_DIRTY,
- &HFSPLUS_I(HFSPLUS_CAT_TREE_I(sb))->flags)) {
- clear_bit(HFSPLUS_I_CAT_DIRTY, &hip->flags);
- error = filemap_write_and_wait(sbi->cat_tree->inode->i_mapping);
- }
-
- if (test_and_clear_bit(HFSPLUS_I_EXT_DIRTY,
- &HFSPLUS_I(HFSPLUS_EXT_TREE_I(sb))->flags)) {
- clear_bit(HFSPLUS_I_EXT_DIRTY, &hip->flags);
- error2 =
- filemap_write_and_wait(sbi->ext_tree->inode->i_mapping);
- if (!error)
- error = error2;
- }
-
- if (sbi->attr_tree) {
- if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY,
- &HFSPLUS_I(HFSPLUS_ATTR_TREE_I(sb))->flags)) {
- clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags);
- error2 =
- filemap_write_and_wait(
- sbi->attr_tree->inode->i_mapping);
- if (!error)
- error = error2;
- }
- } else {
- if (test_and_clear_bit(HFSPLUS_I_ATTR_DIRTY, &hip->flags))
- pr_err("sync non-existent attributes tree\n");
- }
-
- if (test_and_clear_bit(HFSPLUS_I_ALLOC_DIRTY,
- &HFSPLUS_I(sbi->alloc_file)->flags)) {
- clear_bit(HFSPLUS_I_ALLOC_DIRTY, &hip->flags);
- error2 = filemap_write_and_wait(sbi->alloc_file->i_mapping);
- if (!error)
- error = error2;
- }
-
- mutex_lock(&sbi->vh_mutex);
- hfsplus_prepare_volume_header_for_commit(vhdr);
- mutex_unlock(&sbi->vh_mutex);
-
- error2 = hfsplus_commit_superblock(inode->i_sb);
- if (!error)
- error = error2;
-
- if (!test_bit(HFSPLUS_SB_NOBARRIER, &sbi->flags))
- blkdev_issue_flush(inode->i_sb->s_bdev);
-
- inode_unlock(inode);
-
- return error;
-}
-
static const struct inode_operations hfsplus_file_inode_operations = {
.setattr = hfsplus_setattr,
.getattr = hfsplus_getattr,
@@ -454,19 +341,6 @@ static const struct inode_operations hfsplus_special_inode_operations = {
.listxattr = hfsplus_listxattr,
};
-static const struct file_operations hfsplus_file_operations = {
- .llseek = generic_file_llseek,
- .read_iter = generic_file_read_iter,
- .write_iter = generic_file_write_iter,
- .mmap_prepare = generic_file_mmap_prepare,
- .splice_read = filemap_splice_read,
- .splice_write = iter_file_splice_write,
- .fsync = hfsplus_file_fsync,
- .open = hfsplus_file_open,
- .release = hfsplus_file_release,
- .unlocked_ioctl = hfsplus_ioctl,
-};
-
struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,
umode_t mode)
{
@@ -513,7 +387,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,
sbi->file_count++;
inode->i_op = &hfsplus_symlink_inode_operations;
inode_nohighmem(inode);
- inode->i_mapping->a_ops = &hfsplus_aops;
+ inode->i_mapping->a_ops = &hfsplus_symlink_aops;
hip->clump_blocks = 1;
} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
@@ -654,7 +528,7 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd)
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &hfsplus_symlink_inode_operations;
inode_nohighmem(inode);
- inode->i_mapping->a_ops = &hfsplus_aops;
+ inode->i_mapping->a_ops = &hfsplus_symlink_aops;
} else {
inode->i_op = &hfsplus_special_inode_operations;
init_special_inode(inode, inode->i_mode,
diff --git a/fs/hfsplus/iomap.c b/fs/hfsplus/iomap.c
new file mode 100644
index 00000000000000..e00144b27c186a
--- /dev/null
+++ b/fs/hfsplus/iomap.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/fs/hfsplus/iomap.c
+ *
+ * iomap callback functions for the hfsplus filesystem
+ */
+
+#include <linux/iomap.h>
+#include <linux/pagemap.h>
+
+#include "hfsplus_fs.h"
+#include "hfsplus_raw.h"
+#include "iomap.h"
+
+static int __hfsplus_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, bool may_alloc)
+{
+ struct super_block *sb = inode->i_sb;
+ struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ u32 ablock, dblock, max_blocks;
+ loff_t ablock_offset, ablock_bytes;
+ loff_t block_start;
+ bool is_new;
+ int err;
+
+ if (!may_alloc) {
+ /* Completely beyond EOF. Treat as hole */
+ if (i_size_read(inode) <= offset) {
+ iomap->type = IOMAP_HOLE;
+ iomap->addr = IOMAP_NULL_ADDR;
+ iomap->offset = offset;
+ iomap->length = length;
+ return 0;
+ }
+
+ /* Clamp length if the requested range goes beyond i_size */
+ if (offset + length > i_size_read(inode)) {
+ loff_t i_size = i_size_read(inode);
+ unsigned int blocksize = i_blocksize(inode);
+ length = round_up(i_size, blocksize) - offset;
+ }
+ }
+
+ ablock = offset >> sbi->alloc_blksz_shift;
+
+ err = hfsplus_map_extent(inode, ablock, may_alloc, &dblock,
+ &max_blocks, NULL);
+ if (err)
+ return err;
+
+ ablock_offset = offset & (sbi->alloc_blksz - 1);
+ ablock_bytes = (loff_t)max_blocks << sbi->alloc_blksz_shift;
+
+ length = min_t(loff_t, length, ablock_bytes - ablock_offset);
+ block_start = round_down(offset, i_blocksize(inode));
+ is_new = may_alloc && block_start >= hip->phys_size;
+ if (may_alloc && !is_new && offset < hip->phys_size)
+ length = min_t(loff_t, length, hip->phys_size - offset);
+
+ iomap->bdev = sb->s_bdev;
+ iomap->offset = offset;
+ iomap->length = length;
+ iomap->addr = hfsplus_ablock_to_phys_bytes(sb, dblock) + ablock_offset;
+ iomap->type = IOMAP_MAPPED;
+ iomap->flags = IOMAP_F_MERGED;
+
+ if (is_new)
+ iomap->flags |= IOMAP_F_NEW;
+
+ return 0;
+}
+
+static int hfsplus_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ return __hfsplus_iomap_begin(inode,
+ offset, length, flags,
+ iomap, false);
+}
+
+static int hfsplus_write_iomap_begin(struct inode *inode, loff_t offset,
+ loff_t length, unsigned int flags,
+ struct iomap *iomap, struct iomap *srcmap)
+{
+ return __hfsplus_iomap_begin(inode,
+ offset, length, flags,
+ iomap, true);
+}
+
+const struct iomap_ops hfsplus_iomap_ops = {
+ .iomap_begin = hfsplus_iomap_begin,
+};
+
+/*
+ * hfsplus_write_iomap_end()
+ *
+ * Advance the allocated-and-zeroed high-water mark
+ * (hip->phys_size / hip->fs_blocks) to cover the newly written range.
+ */
+static int hfsplus_write_iomap_end(struct inode *inode, loff_t pos,
+ loff_t length, ssize_t written,
+ unsigned int flags, struct iomap *iomap)
+{
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ struct super_block *sb = inode->i_sb;
+ loff_t end;
+ bool dirtied = false;
+
+ if (!written)
+ return 0;
+
+ end = round_up(pos + written, sb->s_blocksize);
+
+ if (hip->phys_size < end) {
+ inode_add_bytes(inode, end - hip->phys_size);
+ hip->phys_size = end;
+ hip->fs_blocks = end >> sb->s_blocksize_bits;
+ dirtied = true;
+ }
+
+ if (dirtied)
+ mark_inode_dirty(inode);
+
+ return written;
+}
+
+const struct iomap_ops hfsplus_write_iomap_ops = {
+ .iomap_begin = hfsplus_write_iomap_begin,
+ .iomap_end = hfsplus_write_iomap_end,
+};
+
+/*
+ * hfsplus_iomap_cont_expand()
+ *
+ * Zero-extend the backing store from the current phys_size up to 'size'.
+ * Used both by hfsplus_setattr() and by hfsplus_file_truncate().
+ */
+int hfsplus_iomap_cont_expand(struct inode *inode, loff_t size)
+{
+ struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
+ loff_t start = hip->phys_size;
+
+ if (size <= start)
+ return 0;
+
+ return iomap_zero_range(inode, start, size - start, NULL,
+ &hfsplus_write_iomap_ops, NULL, NULL);
+}
+
+/*
+ * hfsplus_writeback_range() - map folio during writeback
+ *
+ * Called for each folio during writeback. If the folio falls outside
+ * the current iomap, remaps by calling __hfsplus_iomap_begin() again.
+ */
+static ssize_t hfsplus_writeback_range(struct iomap_writepage_ctx *wpc,
+ struct folio *folio, u64 offset,
+ unsigned int len, u64 end_pos)
+{
+ int err;
+
+ if (offset < wpc->iomap.offset ||
+ offset >= wpc->iomap.offset + wpc->iomap.length) {
+ err = __hfsplus_iomap_begin(wpc->inode,
+ offset, len, 0,
+ &wpc->iomap, false);
+ if (err)
+ return err;
+ }
+
+ return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+}
+
+const struct iomap_writeback_ops hfsplus_writeback_ops = {
+ .writeback_range = hfsplus_writeback_range,
+ .writeback_submit = iomap_ioend_writeback_submit,
+};
+
+/*
+ * hfsplus_file_write_dio_end_io() - Direct I/O write completion handler
+ */
+static int hfsplus_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
+ int error, unsigned int flags)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+
+ if (error)
+ return error;
+
+ if (size && i_size_read(inode) < iocb->ki_pos + size) {
+ i_size_write(inode, iocb->ki_pos + size);
+ mark_inode_dirty(inode);
+ }
+
+ return 0;
+}
+
+const struct iomap_dio_ops hfsplus_write_dio_ops = {
+ .end_io = hfsplus_file_write_dio_end_io,
+};
+
+int hfsplus_iomap_swap_activate(struct swap_info_struct *sis,
+ struct file *file, sector_t *span)
+{
+ return iomap_swapfile_activate(sis, file, span, &hfsplus_iomap_ops);
+}
diff --git a/fs/hfsplus/iomap.h b/fs/hfsplus/iomap.h
new file mode 100644
index 00000000000000..1ea3333c1e0759
--- /dev/null
+++ b/fs/hfsplus/iomap.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * linux/fs/hfsplus/iomap.h
+ *
+ * iomap callback declarations for the hfsplus filesystem
+ */
+
+#ifndef _LINUX_HFSPLUS_IOMAP_H
+#define _LINUX_HFSPLUS_IOMAP_H
+
+extern const struct iomap_ops hfsplus_iomap_ops;
+extern const struct iomap_ops hfsplus_write_iomap_ops;
+extern const struct iomap_writeback_ops hfsplus_writeback_ops;
+extern const struct iomap_dio_ops hfsplus_write_dio_ops;
+
+int hfsplus_iomap_cont_expand(struct inode *inode, loff_t size);
+int hfsplus_iomap_swap_activate(struct swap_info_struct *sis,
+ struct file *file, sector_t *span);
+
+#endif /* _LINUX_HFSPLUS_IOMAP_H */
diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
index 45fb4c9ff9f5ee..1417e19d94bcec 100644
--- a/include/linux/hfs_common.h
+++ b/include/linux/hfs_common.h
@@ -171,18 +171,22 @@ enum {
HFS_XATTR_NAME,
};
+#define HFS_FORK_EXTENT_COUNT (3)
+
struct hfs_extent {
__be16 block;
__be16 count;
};
-typedef struct hfs_extent hfs_extent_rec[3];
+typedef struct hfs_extent hfs_extent_rec[HFS_FORK_EXTENT_COUNT];
+
+#define HFSPLUS_FORK_EXTENT_COUNT (8)
/* A single contiguous area of a file */
struct hfsplus_extent {
__be32 start_block;
__be32 block_count;
} __packed;
-typedef struct hfsplus_extent hfsplus_extent_rec[8];
+typedef struct hfsplus_extent hfsplus_extent_rec[HFSPLUS_FORK_EXTENT_COUNT];
/* Information for a "Fork" in a file */
struct hfsplus_fork_raw {
|
| 3/2 | 2026/07/24 00:38 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18359 output: 280 thoughts: 1281 Thoughts: **HFS+ iomap Conversion: Fuzzing Analysis** Okay, so I've got this patch series in front of me that fundamentally refactors the HFS+ filesystem, replacing the legacy I/O with iomap. This is a big deal; we're talking core file I/O operations – read, write, seek, truncate – all being redirected through a new path. That's a huge potential for regressions, so fuzzing this is absolutely critical. It's accessible via standard syscalls, so that makes things straightforward. My initial thoughts are, let's nail down the critical functions. We've got two new files: `iomap.c` and `file.c`. `iomap.c` implements the iomap operations: `__hfsplus_iomap_begin`, `hfsplus_iomap_begin`, `hfsplus_write_iomap_begin`, `hfsplus_write_iomap_end`, `hfsplus_iomap_cont_expand`, `hfsplus_writeback_range`, `hfsplus_file_write_dio_end_io`, and `hfsplus_iomap_swap_activate`. These are the direct iomap implementations. Then there's `file.c`, which now houses the file operations. We have: `hfsplus_file_open`, `hfsplus_file_release`, `hfsplus_file_fsync`, `hfsplus_fallback_buffered_write`, `hfsplus_dio_write_iter`, `hfsplus_file_write_iter`, `hfsplus_file_read_iter`, and `hfsplus_file_llseek`. These are all crucial points for testing the new I/O flow. Beyond those, there are modifications in `extents.c`, specifically `hfsplus_ext_find_block`, `hfsplus_map_extent`, `hfsplus_get_block`, and `hfsplus_file_truncate`. `bitmap.c` also gets some attention with `hfsplus_block_allocate` and `hfsplus_block_free`, so these are also relevant, especially since allocation/deallocation can be tricky. To run this, the only config needed is `CONFIG_HFSPLUS_FS`, which is the main HFS+ config. So, let's distill that down to a list of focus symbols for fuzzing: * `hfsplus_map_extent` * `hfsplus_iomap_begin` * `hfsplus_write_iomap_begin` * `hfsplus_write_iomap_end` * `hfsplus_iomap_cont_expand` * `hfsplus_writeback_range` * `hfsplus_file_write_dio_end_io` * `hfsplus_fallback_buffered_write` * `hfsplus_dio_write_iter` * `hfsplus_file_write_iter` * `hfsplus_file_read_iter` * `hfsplus_file_llseek` * `hfsplus_file_fsync` * `hfsplus_block_allocate` * `hfsplus_block_free` Everything points to this being worth fuzzing, given the scope of the changes. We need to thoroughly test every aspect of the new iomap integration, from basic reads and writes to more complex operations like truncate, sync, and block allocation/deallocation to ensure the HFS+ filesystem remains stable and correct under the new iomap architecture. |
| 4/2 | 2026/07/24 00:38 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_map_extent",
"hfsplus_iomap_begin",
"hfsplus_write_iomap_begin",
"hfsplus_write_iomap_end",
"hfsplus_iomap_cont_expand",
"hfsplus_writeback_range",
"hfsplus_file_write_dio_end_io",
"hfsplus_fallback_buffered_write",
"hfsplus_dio_write_iter",
"hfsplus_file_write_iter",
"hfsplus_file_read_iter",
"hfsplus_file_llseek",
"hfsplus_file_fsync",
"hfsplus_block_allocate",
"hfsplus_block_free"
],
"Reasoning": "The patch series converts the HFS+ filesystem to use the modern iomap infrastructure for file I/O operations (read, write, seek, truncate). This is a major functional change to the core I/O paths of the filesystem and should be heavily fuzzed to ensure correctness and prevent regressions.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_map_extent",
"hfsplus_iomap_begin",
"hfsplus_write_iomap_begin",
"hfsplus_write_iomap_end",
"hfsplus_iomap_cont_expand",
"hfsplus_writeback_range",
"hfsplus_file_write_dio_end_io",
"hfsplus_fallback_buffered_write",
"hfsplus_dio_write_iter",
"hfsplus_file_write_iter",
"hfsplus_file_read_iter",
"hfsplus_file_llseek",
"hfsplus_file_fsync",
"hfsplus_block_allocate",
"hfsplus_block_free"
],
"Reasoning": "The patch series converts the HFS+ filesystem to use the modern iomap infrastructure for file I/O operations (read, write, seek, truncate). This is a major functional change to the core I/O paths of the filesystem and should be heavily fuzzed to ensure correctness and prevent regressions.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|