| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/26 23:23 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_file_fsync",
"hfsplus_map_extent",
"hfsplus_iomap_cont_expand",
"hfsplus_iomap_swap_activate",
"hfsplus_get_block"
],
"KMSANReasoning": "The patch series converts the hfsplus filesystem from legacy direct I/O and buffered I/O to the modern iomap infrastructure. It introduces new file operations, iomap callbacks, and modifies extent mapping logic. The changes primarily involve standard VFS and iomap API usage, such as `iomap_dio_rw`, `iomap_file_buffered_write`, and `iomap_zero_range`. File expansions are properly zeroed using `iomap_zero_range`, and newly allocated blocks are flagged with `IOMAP_F_NEW`, which the iomap core handles to prevent uninitialized disk data exposure. There are no new structures copied to user space, no new ioctls, and no complex data structures introduced that could leak uninitialized kernel memory. Any potential bugs introduced by these changes would likely be out-of-bounds accesses, use-after-free, or logic errors, which are well-covered by standard KASAN and other default sanitizers. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false,
"Reasoning": "The patch converts the HFS+ filesystem to use the iomap infrastructure for file I/O, replacing the legacy direct I/O and buffer head based paths. It introduces new iomap operations for reading, writing, and seeking, and modifies extent mapping and block allocation logic. These are significant functional changes to core filesystem logic that are reachable and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/26 23:23 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit de51045c005433a7cda50aa6d0ce099e31f15d7f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 26 23:23:05 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig\nindex ca8401cb6954d..b4432c64db3be 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 f2a9ae697e811..3ddea69a9c698 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 1b3af8c87cadb..61c49cca4a7a2 100644\n--- a/fs/hfsplus/bitmap.c\n+++ b/fs/hfsplus/bitmap.c\n@@ -39,6 +39,8 @@ 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+\tfolio_wait_stable(page_folio(page));\n \tpptr = kmap_local_page(page);\n \tcurr = pptr + (offset \u0026 (PAGE_CACHE_BITS - 1)) / 32;\n \ti = offset % 32;\n@@ -75,6 +77,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 +87,8 @@ 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\tfolio_wait_stable(page_folio(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 +103,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 +136,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 +144,8 @@ 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\tfolio_wait_stable(page_folio(page));\n \t\tpptr = kmap_local_page(page);\n \t\tcurr = pptr;\n \t\tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -152,6 +163,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 +197,8 @@ 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+\tfolio_wait_stable(page_folio(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 +230,12 @@ 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\tfolio_wait_stable(page_folio(page));\n \t\tpptr = kmap_local_page(page);\n \t\tcurr = pptr;\n \t\tend = pptr + PAGE_CACHE_BITS / 32;\n@@ -232,6 +249,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 813e68b8ecd65..8f7f560cec642 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 0000000000000..82678488fb054\n--- /dev/null\n+++ b/fs/hfsplus/file.c\n@@ -0,0 +1,307 @@\n+// SPDX-License-Identifier: GPL-2.0\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 ec04b82ad9278..8166abb22397b 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@@ -430,12 +446,19 @@ int hfsplus_rename_cat(u32 cnid, struct inode *src_dir, const struct qstr *src_n\n extern const struct inode_operations hfsplus_dir_inode_operations;\n extern const struct file_operations hfsplus_dir_operations;\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 /* extents.c */\n int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,\n \t\t\tconst hfsplus_btree_key *k2);\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 +466,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,8 +486,6 @@ 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);\ndiff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c\nindex 1c57c873f05d6..08731ec0cecd3 100644\n--- a/fs/hfsplus/inode.c\n+++ b/fs/hfsplus/inode.c\n@@ -18,15 +18,12 @@\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-\n-static int hfsplus_read_folio(struct file *file, struct folio *folio)\n-{\n-\treturn block_read_full_folio(folio, hfsplus_get_block);\n-}\n+#include \"iomap.h\"\n \n static void hfsplus_write_failed(struct address_space *mapping, loff_t to)\n {\n@@ -120,67 +117,13 @@ 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+static int hfsplus_btree_read_folio(struct file *file, struct folio *folio)\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+\treturn block_read_full_folio(folio, hfsplus_get_block);\n }\n \n-static int hfsplus_writepages(struct address_space *mapping,\n-\t\t\t struct writeback_control *wbc)\n+static int hfsplus_btree_writepages(struct address_space *mapping,\n+\t\t\t\t struct writeback_control *wbc)\n {\n \treturn mpage_writepages(mapping, wbc, hfsplus_get_block);\n }\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_btree_read_folio,\n+\t.writepages\t= hfsplus_btree_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,70 @@ 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+static int hfsplus_symlink_read_folio(struct file *file, struct folio *folio)\n+{\n+\treturn block_read_full_folio(folio, hfsplus_get_block);\n+}\n+\n+static int hfsplus_symlink_writepages(struct address_space *mapping,\n+\t\t\t\t struct writeback_control *wbc)\n+{\n+\treturn mpage_writepages(mapping, wbc, hfsplus_get_block);\n+}\n+\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_symlink_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_symlink_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 +263,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 +277,14 @@ 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\tloff_t old_size = inode-\u003ei_size;\n+\n+\t\t\ti_size_write(inode, attr-\u003eia_size);\n+\t\t\terror = hfsplus_iomap_cont_expand(inode, attr-\u003eia_size);\n+\t\t\tif (error) {\n+\t\t\t\ti_size_write(inode, old_size);\n \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 +323,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 +344,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 +390,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 +531,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 0000000000000..1e6dfa693526e\n--- /dev/null\n+++ b/fs/hfsplus/iomap.c\n@@ -0,0 +1,189 @@\n+// SPDX-License-Identifier: GPL-2.0\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+\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+const struct iomap_dio_ops hfsplus_write_dio_ops = {\n+\t.end_io\t\t= iomap_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 0000000000000..dac07a9d25f8d\n--- /dev/null\n+++ b/fs/hfsplus/iomap.h\n@@ -0,0 +1,18 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\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/fs/hfsplus/super.c b/fs/hfsplus/super.c\nindex 5777e31de45ac..459ca6f3b8147 100644\n--- a/fs/hfsplus/super.c\n+++ b/fs/hfsplus/super.c\n@@ -571,6 +571,7 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)\n \t\tgoto out_close_attr_tree;\n \t}\n \tsbi-\u003ealloc_file = inode;\n+\tmapping_set_stable_writes(inode-\u003ei_mapping);\n \n \t/* Load the root directory */\n \troot = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);\ndiff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h\nindex 45fb4c9ff9f5e..1417e19d94bce 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 {\ndiff --git a/include/linux/iomap.h b/include/linux/iomap.h\nindex 56b43d594e6e2..50fe6036b01ac 100644\n--- a/include/linux/iomap.h\n+++ b/include/linux/iomap.h\n@@ -560,6 +560,26 @@ struct iomap_dio_ops {\n \tstruct bio_set *bio_set;\n };\n \n+/*\n+ * Direct I/O completion handler\n+ */\n+static inline\n+int iomap_dio_end_io(struct kiocb *iocb, ssize_t size,\n+\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 /*\n * Wait for the I/O to complete in iomap_dio_rw even if the kiocb is not\n * synchronous.\n"
} |
| 2/1 | 2026/08/26 23:23 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_file_fsync",
"hfsplus_map_extent",
"hfsplus_iomap_cont_expand",
"hfsplus_iomap_swap_activate",
"hfsplus_get_block"
],
"Reasoning": "The patch converts the HFS+ filesystem to use the iomap infrastructure for file I/O, replacing the legacy direct I/O and buffer head based paths. It introduces new iomap operations for reading, writing, and seeking, and modifies extent mapping and block allocation logic. These are significant functional changes to core filesystem logic that are reachable and should be fuzzed.",
"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). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. 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. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 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. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. 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 de51045c005433a7cda50aa6d0ce099e31f15d7f
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 26 23:23:05 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig
index ca8401cb6954d..b4432c64db3be 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 f2a9ae697e811..3ddea69a9c698 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 1b3af8c87cadb..61c49cca4a7a2 100644
--- a/fs/hfsplus/bitmap.c
+++ b/fs/hfsplus/bitmap.c
@@ -39,6 +39,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
i = offset % 32;
@@ -75,6 +77,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 +87,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
curr = pptr = kmap_local_page(page);
if ((size ^ offset) / PAGE_CACHE_BITS)
end = pptr + PAGE_CACHE_BITS / 32;
@@ -98,6 +103,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 +136,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 +144,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -152,6 +163,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 +197,8 @@ 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);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -216,9 +230,12 @@ 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);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -232,6 +249,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 813e68b8ecd65..8f7f560cec642 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 0000000000000..82678488fb054
--- /dev/null
+++ b/fs/hfsplus/file.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 ec04b82ad9278..8166abb22397b 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;
@@ -430,12 +446,19 @@ int hfsplus_rename_cat(u32 cnid, struct inode *src_dir, const struct qstr *src_n
extern const struct inode_operations hfsplus_dir_inode_operations;
extern const struct file_operations hfsplus_dir_operations;
+/* 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);
+
/* extents.c */
int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
const hfsplus_btree_key *k2);
int hfsplus_ext_write_extent(struct inode *inode);
int hfsplus_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
+int hfsplus_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 +466,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,8 +486,6 @@ 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);
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 1c57c873f05d6..08731ec0cecd3 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -18,15 +18,12 @@
#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"
-
-static int hfsplus_read_folio(struct file *file, struct folio *folio)
-{
- return block_read_full_folio(folio, hfsplus_get_block);
-}
+#include "iomap.h"
static void hfsplus_write_failed(struct address_space *mapping, loff_t to)
{
@@ -120,67 +117,13 @@ 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)
+static int hfsplus_btree_read_folio(struct file *file, struct folio *folio)
{
- 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;
+ return block_read_full_folio(folio, hfsplus_get_block);
}
-static int hfsplus_writepages(struct address_space *mapping,
- struct writeback_control *wbc)
+static int hfsplus_btree_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_btree_read_folio,
+ .writepages = hfsplus_btree_writepages,
.write_begin = hfsplus_write_begin,
.write_end = generic_write_end,
.migrate_folio = buffer_migrate_folio,
@@ -197,18 +140,70 @@ const struct address_space_operations hfsplus_btree_aops = {
.release_folio = hfsplus_release_folio,
};
-const struct address_space_operations hfsplus_aops = {
+static int hfsplus_symlink_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, hfsplus_get_block);
+}
+
+static int hfsplus_symlink_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, hfsplus_get_block);
+}
+
+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_symlink_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_symlink_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 +263,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 +277,14 @@ 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)
+ 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;
+ }
}
truncate_setsize(inode, attr->ia_size);
hfsplus_file_truncate(inode);
@@ -353,86 +323,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 +344,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 +390,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 +531,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 0000000000000..1e6dfa693526e
--- /dev/null
+++ b/fs/hfsplus/iomap.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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,
+};
+
+const struct iomap_dio_ops hfsplus_write_dio_ops = {
+ .end_io = iomap_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 0000000000000..dac07a9d25f8d
--- /dev/null
+++ b/fs/hfsplus/iomap.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 5777e31de45ac..459ca6f3b8147 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -571,6 +571,7 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
goto out_close_attr_tree;
}
sbi->alloc_file = inode;
+ mapping_set_stable_writes(inode->i_mapping);
/* Load the root directory */
root = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);
diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
index 45fb4c9ff9f5e..1417e19d94bce 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 {
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 56b43d594e6e2..50fe6036b01ac 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -560,6 +560,26 @@ struct iomap_dio_ops {
struct bio_set *bio_set;
};
+/*
+ * Direct I/O completion handler
+ */
+static inline
+int iomap_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;
+}
+
/*
* Wait for the I/O to complete in iomap_dio_rw even if the kiocb is not
* synchronous.
|
| 3/2 | 2026/08/26 23:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18959 output: 67 thoughts: 620 Thoughts: **My Assessment of the HFS+ iomap Conversion Patch** Okay, so I've been digging into this HFS+ patch, and it looks like a fairly comprehensive overhaul. The core idea is to migrate the HFS+ filesystem's I/O path to utilize the iomap infrastructure, which, as we know, can offer significant performance and flexibility advantages. The fact that it's replacing `LEGACY_DIRECT_IO` with `FS_IOMAP` in `Kconfig` pretty much confirms this major architectural shift. This isn't just a minor tweak; this is a functional transformation. We're talking about new files (`file.c`, `iomap.c`) and modifications across the board. The patch touches the core filesystem components: `bitmap.c` (page locking and write stability), `extents.c` (with the new `hfsplus_map_extent` and updates to block-related functions), `inode.c` (integrating iomap for address space operations, separating btree and symlink aops). And, of course, the addition of `iomap.c` and `iomap.h` with all the iomap-specific operations. This definitely warrants some serious fuzzing and testing. Given the scope of these changes, I need to focus on the key areas. I'm thinking I should start by really scrutinizing the interactions of functions like `hfsplus_file_write_iter`, `hfsplus_file_read_iter`, `hfsplus_file_llseek`, and `hfsplus_file_fsync` since they're the direct interaction with the new I/O path. I'll also want to understand the extent mapping (`hfsplus_map_extent`) and the iomap continuation (`hfsplus_iomap_cont_expand`, `__hfsplus_iomap_begin`, `hfsplus_write_iomap_end`, `hfsplus_writeback_range`). These are crucial for the iomap's correct operation. I should verify the required configs are met, like `CONFIG_HFSPLUS_FS`. I'll run some `grepper` searches to verify the specific function names inside of `fs/hfsplus/file.c` and `fs/hfsplus/iomap.c` to see how the changes are implemented and how they interact. This is where the rubber meets the road. |
| 4/2 | 2026/08/26 23:23 | tool | grepper |
0mArgs:{
"Expression": "^(int|ssize_t|loff_t|static int|static ssize_t|static loff_t) [a-zA-Z0-9_]+\\(",
"PathPrefix": "fs/hfsplus/file.c"
}Results: {
"Output": "fs/hfsplus/file.c-14-\nfs/hfsplus/file.c:15:static int hfsplus_file_open(struct inode *inode, struct file *file)\nfs/hfsplus/file.c-16-{\n--\nfs/hfsplus/file.c-25-\nfs/hfsplus/file.c:26:static int hfsplus_file_release(struct inode *inode, struct file *file)\nfs/hfsplus/file.c-27-{\n--\nfs/hfsplus/file.c-44-\nfs/hfsplus/file.c:45:int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end,\nfs/hfsplus/file.c-46-\t\t int datasync)\n--\nfs/hfsplus/file.c-127- */\nfs/hfsplus/file.c:128:static ssize_t hfsplus_fallback_buffered_write(struct kiocb *iocb,\nfs/hfsplus/file.c-129-\t\t\t\t\t\tstruct iov_iter *from)\n--\nfs/hfsplus/file.c-155-\nfs/hfsplus/file.c:156:static ssize_t hfsplus_dio_write_iter(struct kiocb *iocb,\nfs/hfsplus/file.c-157-\t\t\t\t\tstruct iov_iter *from)\n--\nfs/hfsplus/file.c-181-\nfs/hfsplus/file.c:182:static ssize_t hfsplus_file_write_iter(struct kiocb *iocb,\nfs/hfsplus/file.c-183-\t\t\t\t\tstruct iov_iter *iter)\n--\nfs/hfsplus/file.c-249-\nfs/hfsplus/file.c:250:static ssize_t hfsplus_file_read_iter(struct kiocb *iocb,\nfs/hfsplus/file.c-251-\t\t\t\t\tstruct iov_iter *iter)\n--\nfs/hfsplus/file.c-270-\nfs/hfsplus/file.c:271:static loff_t hfsplus_file_llseek(struct file *file, loff_t offset, int whence)\nfs/hfsplus/file.c-272-{\n"
} |
| 5/2 | 2026/08/26 23:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20782 output: 69 thoughts: 17 Thoughts: Here's a summary of my immediate thought process, rendered as a first-person account, assuming I'm the expert: **Navigating the File System Landscape** Okay, so I need to dive into the core of how HFS+ deals with I/O mapping. That's a critical area, especially when looking at performance bottlenecks or potential data corruption issues. The first place I'm going to head is `fs/hfsplus/iomap.c`. That file is the likely repository for the functions handling logical to physical block translation, extent management, and the overall mechanics of how HFS+ arranges data on the disk. This is where the rubber meets the road, so to speak, in terms of file system interactions with the underlying storage. Let's see what secrets it holds. |
| 6/2 | 2026/08/26 23:23 | tool | grepper |
0mArgs:{
"Expression": "^(int|ssize_t|loff_t|static int|static ssize_t|static loff_t) [a-zA-Z0-9_]+\\(",
"PathPrefix": "fs/hfsplus/iomap.c"
}Results: {
"Output": "fs/hfsplus/iomap.c-12-\nfs/hfsplus/iomap.c:13:static int __hfsplus_iomap_begin(struct inode *inode, loff_t offset,\nfs/hfsplus/iomap.c-14-\t\t\t\t loff_t length, unsigned int flags,\n--\nfs/hfsplus/iomap.c-73-\nfs/hfsplus/iomap.c:74:static int hfsplus_iomap_begin(struct inode *inode, loff_t offset,\nfs/hfsplus/iomap.c-75-\t\t\t\tloff_t length, unsigned int flags,\n--\nfs/hfsplus/iomap.c-82-\nfs/hfsplus/iomap.c:83:static int hfsplus_write_iomap_begin(struct inode *inode, loff_t offset,\nfs/hfsplus/iomap.c-84-\t\t\t\t loff_t length, unsigned int flags,\n--\nfs/hfsplus/iomap.c=92=const struct iomap_ops hfsplus_iomap_ops = {\n--\nfs/hfsplus/iomap.c-101- */\nfs/hfsplus/iomap.c:102:static int hfsplus_write_iomap_end(struct inode *inode, loff_t pos,\nfs/hfsplus/iomap.c-103-\t\t\t\t loff_t length, ssize_t written,\n--\nfs/hfsplus/iomap.c=129=const struct iomap_ops hfsplus_write_iomap_ops = {\n--\nfs/hfsplus/iomap.c-139- */\nfs/hfsplus/iomap.c:140:int hfsplus_iomap_cont_expand(struct inode *inode, loff_t size)\nfs/hfsplus/iomap.c-141-{\n--\nfs/hfsplus/iomap.c-157- */\nfs/hfsplus/iomap.c:158:static ssize_t hfsplus_writeback_range(struct iomap_writepage_ctx *wpc,\nfs/hfsplus/iomap.c-159-\t\t\t\t\tstruct folio *folio, u64 offset,\n--\nfs/hfsplus/iomap.c=181=const struct iomap_dio_ops hfsplus_write_dio_ops = {\n--\nfs/hfsplus/iomap.c-184-\nfs/hfsplus/iomap.c:185:int hfsplus_iomap_swap_activate(struct swap_info_struct *sis,\nfs/hfsplus/iomap.c-186-\t\t\t\t struct file *file, sector_t *span)\n"
} |
| 7/2 | 2026/08/26 23:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21761 output: 77 thoughts: 16 Thoughts: Here's my take on the thought process, as if it were my own: **Diving into `fs/hfsplus/extents.c`** Okay, time to dig into the HFS+ extent management code. Let's see... the `fs/hfsplus/extents.c` file is probably the right place to start. Extents are fundamental to how HFS+ organizes data, so this is where the core logic for allocating, deallocating, and managing those blocks would reside. I'm expecting to find functions related to things like mapping logical block numbers to physical ones, handling fragmented files, and possibly dealing with allocation bitmaps if HFS+ uses one. I'll need to refresh my memory on the HFS+ data structures to understand how extents are stored on disk. Let's see what we can find. |
| 8/2 | 2026/08/26 23:23 | tool | grepper |
0mArgs:{
"Expression": "^(int|ssize_t|loff_t|static int|static ssize_t|static loff_t|u32|static u32) [a-zA-Z0-9_]+\\(",
"PathPrefix": "fs/hfsplus/extents.c"
}Results: {
"Output": "fs/hfsplus/extents.c-20-/* Compare two extents keys, returns 0 on same, pos/neg for difference */\nfs/hfsplus/extents.c:21:int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,\nfs/hfsplus/extents.c-22-\t\t\tconst hfsplus_btree_key *k2)\n--\nfs/hfsplus/extents.c=42=static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,\n--\nfs/hfsplus/extents.c-58- */\nfs/hfsplus/extents.c:59:static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off,\nfs/hfsplus/extents.c-60-\t\t\t\t u32 *dblock)\n--\nfs/hfsplus/extents.c-77-\nfs/hfsplus/extents.c:78:static int hfsplus_ext_block_count(struct hfsplus_extent *ext)\nfs/hfsplus/extents.c-79-{\n--\nfs/hfsplus/extents.c-87-\nfs/hfsplus/extents.c:88:static u32 hfsplus_ext_lastblock(struct hfsplus_extent *ext)\nfs/hfsplus/extents.c-89-{\n--\nfs/hfsplus/extents.c-98-\nfs/hfsplus/extents.c:99:static int __hfsplus_ext_write_extent(struct inode *inode,\nfs/hfsplus/extents.c-100-\t\tstruct hfs_find_data *fd)\n--\nfs/hfsplus/extents.c-142-\nfs/hfsplus/extents.c:143:static int hfsplus_ext_write_extent_locked(struct inode *inode)\nfs/hfsplus/extents.c-144-{\n--\nfs/hfsplus/extents.c-158-\nfs/hfsplus/extents.c:159:int hfsplus_ext_write_extent(struct inode *inode)\nfs/hfsplus/extents.c-160-{\n--\nfs/hfsplus/extents.c=191=static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd,\n--\nfs/hfsplus/extents.c-219-\nfs/hfsplus/extents.c:220:static int hfsplus_ext_read_extent(struct inode *inode, u32 block)\nfs/hfsplus/extents.c-221-{\n--\nfs/hfsplus/extents.c-252- */\nfs/hfsplus/extents.c:253:int hfsplus_map_extent(struct inode *inode, u32 ablock, int create,\nfs/hfsplus/extents.c-254-\t\t\tu32 *dblock, u32 *max_blocks, bool *balloc)\n--\nfs/hfsplus/extents.c-307-/* Get a block at iblock for inode, possibly allocating if create */\nfs/hfsplus/extents.c:308:int hfsplus_get_block(struct inode *inode, sector_t iblock,\nfs/hfsplus/extents.c-309-\t\t struct buffer_head *bh_result, int create)\n--\nfs/hfsplus/extents.c=352=static void hfsplus_dump_extent(struct hfsplus_extent *extent)\n--\nfs/hfsplus/extents.c-363-\nfs/hfsplus/extents.c:364:static int hfsplus_add_extent(struct hfsplus_extent *extent, u32 offset,\nfs/hfsplus/extents.c-365-\t\t\t u32 alloc_block, u32 block_count)\n--\nfs/hfsplus/extents.c-391-\nfs/hfsplus/extents.c:392:static int hfsplus_free_extents(struct super_block *sb,\nfs/hfsplus/extents.c-393-\t\t\t\tstruct hfsplus_extent *extent,\n--\nfs/hfsplus/extents.c-445-\nfs/hfsplus/extents.c:446:int hfsplus_free_fork(struct super_block *sb, u32 cnid,\nfs/hfsplus/extents.c-447-\t\tstruct hfsplus_fork_raw *fork, int type)\n--\nfs/hfsplus/extents.c-490-\nfs/hfsplus/extents.c:491:int hfsplus_file_extend(struct inode *inode, bool zeroout)\nfs/hfsplus/extents.c-492-{\n"
} |
| 9/2 | 2026/08/26 23:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23293 output: 34 thoughts: 183 Thoughts: **My Current Line of Reasoning** Okay, so I need to zero in on the core HFS+ file system functions for this investigation. Based on what I'm seeing, I should start with these: `hfsplus_file_fsync`, `hfsplus_file_write_iter`, `hfsplus_file_read_iter`, `hfsplus_file_llseek`, `hfsplus_map_extent`, and `hfsplus_iomap_cont_expand`. Those seem like the key building blocks for understanding file I/O and extent mapping within the HFS+ context. Now, let me just quickly glance at `hfsplus_iomap_swap_activate`. It's non-static, so it's probably worth a look, though I'll need to prioritize. I'll also check out `hfsplus_get_block` since it has been updated, and that could be interesting. On the other hand, `hfsplus_ext_find_block` was also updated but it's static, so it may not be as critical to the larger picture. I'll have to consider this later. Finally, I'll need to reference the `fs/hfsplus/Kconfig` file. That will reveal any dependencies or configuration options that could influence how these functions behave. I can't get the full picture without that context. |
| 10/2 | 2026/08/26 23:23 | tool | read-file |
0mArgs:{
"File": "fs/hfsplus/Kconfig",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\tconfig HFSPLUS_FS\n 3:\t\ttristate \"Apple Extended HFS file system support\"\n 4:\t\tdepends on BLOCK\n 5:\t\tselect BUFFER_HEAD\n 6:\t\tselect NLS\n 7:\t\tselect NLS_UTF8\n 8:\t\tselect FS_IOMAP\n 9:\t\thelp\n 10:\t\t If you say Y here, you will be able to mount extended format\n 11:\t\t Macintosh-formatted hard drive partitions with full read-write access.\n 12:\t\n 13:\t\t This file system is often called HFS+ and was introduced with\n 14:\t\t MacOS 8. It includes all Mac specific filesystem data such as\n 15:\t\t data forks and creator codes, but it also has several UNIX\n 16:\t\t style features such as file ownership and permissions.\n 17:\t\n 18:\tconfig HFSPLUS_KUNIT_TEST\n 19:\t\ttristate \"KUnit tests for HFS+ filesystem\" if !KUNIT_ALL_TESTS\n 20:\t\tdepends on HFSPLUS_FS \u0026\u0026 KUNIT\n"
} |
| 11/2 | 2026/08/26 23:23 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24112 output: 173 thoughts: 1039 Thoughts: **Analyzing HFS+ I/O Operations for Fuzzing** Alright, given the `HFSPLUS_FS` config, I need to identify key functions for fuzzing after this HFS+ iomap conversion. The goal is to focus on entry points for file I/O operations and related extent/block management. First, I reviewed the candidate functions: `hfsplus_file_fsync`, `hfsplus_file_write_iter`, `hfsplus_file_read_iter`, `hfsplus_file_llseek`, `hfsplus_map_extent`, `hfsplus_iomap_cont_expand`, and `hfsplus_iomap_swap_activate`. My initial thought was to include `hfsplus_file_write_iter` even though it's static, since it's assigned to `hfsplus_file_operations.write_iter` and represents a critical entry point. However, the instruction explicitly prefers non-static, non-inlined entry points. Therefore, I'm focusing on the non-static candidates. `hfsplus_file_write_iter` is tempting, but static, and I need to prioritize according to the instructions. Thus, `hfsplus_file_fsync`, `hfsplus_map_extent`, `hfsplus_iomap_cont_expand`, `hfsplus_iomap_swap_activate`, and `hfsplus_get_block` are definitely in scope. I made sure to re-check all the code. I verified that those functions are indeed non-static, by looking through `fs/hfsplus/file.c`, `fs/hfsplus/iomap.c`, and `fs/hfsplus/extents.c`. I confirmed that the definitions of `hfsplus_file_fsync`, `hfsplus_map_extent`, `hfsplus_iomap_cont_expand`, `hfsplus_iomap_swap_activate`, and `hfsplus_get_block` are exactly as I expect. Based on the information, this patch represents a significant architectural shift with iomap, which touches on the core file system, block allocation, extent mapping, and I/O handling, and therefore, these functions are all excellent fuzzing targets to cover key entrypoints for file operations. |
| 12/2 | 2026/08/26 23:24 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_file_fsync",
"hfsplus_map_extent",
"hfsplus_iomap_cont_expand",
"hfsplus_iomap_swap_activate",
"hfsplus_get_block"
],
"Reasoning": "The patch converts the HFS+ filesystem to use the iomap infrastructure for file I/O, replacing the legacy direct I/O and buffer head based paths. It introduces new iomap operations for reading, writing, and seeking, and modifies extent mapping and block allocation logic. These are significant functional changes to core filesystem logic that are reachable and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"HFSPLUS_FS"
],
"FocusSymbols": [
"hfsplus_file_fsync",
"hfsplus_map_extent",
"hfsplus_iomap_cont_expand",
"hfsplus_iomap_swap_activate",
"hfsplus_get_block"
],
"Reasoning": "The patch converts the HFS+ filesystem to use the iomap infrastructure for file I/O, replacing the legacy direct I/O and buffer head based paths. It introduces new iomap operations for reading, writing, and seeking, and modifies extent mapping and block allocation logic. These are significant functional changes to core filesystem logic that are reachable and should be fuzzed.",
"WorthFuzzing": true
} |
| 13/1 | 2026/08/26 23:24 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 14/2 | 2026/08/26 23:24 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series converts the hfsplus filesystem from legacy direct I/O and buffered I/O to the modern iomap infrastructure. It introduces new file operations, iomap callbacks, and modifies extent mapping logic. The changes primarily involve standard VFS and iomap API usage, such as `iomap_dio_rw`, `iomap_file_buffered_write`, and `iomap_zero_range`. File expansions are properly zeroed using `iomap_zero_range`, and newly allocated blocks are flagged with `IOMAP_F_NEW`, which the iomap core handles to prevent uninitialized disk data exposure. There are no new structures copied to user space, no new ioctls, and no complex data structures introduced that could leak uninitialized kernel memory. Any potential bugs introduced by these changes would likely be out-of-bounds accesses, use-after-free, or logic errors, which are well-covered by standard KASAN and other default sanitizers. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 de51045c005433a7cda50aa6d0ce099e31f15d7f
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 26 23:23:05 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/hfsplus/Kconfig b/fs/hfsplus/Kconfig
index ca8401cb6954d..b4432c64db3be 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 f2a9ae697e811..3ddea69a9c698 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 1b3af8c87cadb..61c49cca4a7a2 100644
--- a/fs/hfsplus/bitmap.c
+++ b/fs/hfsplus/bitmap.c
@@ -39,6 +39,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
i = offset % 32;
@@ -75,6 +77,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 +87,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
curr = pptr = kmap_local_page(page);
if ((size ^ offset) / PAGE_CACHE_BITS)
end = pptr + PAGE_CACHE_BITS / 32;
@@ -98,6 +103,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 +136,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 +144,8 @@ int hfsplus_block_allocate(struct super_block *sb, u32 size,
start = size;
goto out;
}
+ lock_page(page);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -152,6 +163,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 +197,8 @@ 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);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr + (offset & (PAGE_CACHE_BITS - 1)) / 32;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -216,9 +230,12 @@ 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);
+ folio_wait_stable(page_folio(page));
pptr = kmap_local_page(page);
curr = pptr;
end = pptr + PAGE_CACHE_BITS / 32;
@@ -232,6 +249,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 813e68b8ecd65..8f7f560cec642 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 0000000000000..82678488fb054
--- /dev/null
+++ b/fs/hfsplus/file.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 ec04b82ad9278..8166abb22397b 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;
@@ -430,12 +446,19 @@ int hfsplus_rename_cat(u32 cnid, struct inode *src_dir, const struct qstr *src_n
extern const struct inode_operations hfsplus_dir_inode_operations;
extern const struct file_operations hfsplus_dir_operations;
+/* 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);
+
/* extents.c */
int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
const hfsplus_btree_key *k2);
int hfsplus_ext_write_extent(struct inode *inode);
int hfsplus_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create);
+int hfsplus_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 +466,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,8 +486,6 @@ 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);
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 1c57c873f05d6..08731ec0cecd3 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -18,15 +18,12 @@
#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"
-
-static int hfsplus_read_folio(struct file *file, struct folio *folio)
-{
- return block_read_full_folio(folio, hfsplus_get_block);
-}
+#include "iomap.h"
static void hfsplus_write_failed(struct address_space *mapping, loff_t to)
{
@@ -120,67 +117,13 @@ 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)
+static int hfsplus_btree_read_folio(struct file *file, struct folio *folio)
{
- 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;
+ return block_read_full_folio(folio, hfsplus_get_block);
}
-static int hfsplus_writepages(struct address_space *mapping,
- struct writeback_control *wbc)
+static int hfsplus_btree_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_btree_read_folio,
+ .writepages = hfsplus_btree_writepages,
.write_begin = hfsplus_write_begin,
.write_end = generic_write_end,
.migrate_folio = buffer_migrate_folio,
@@ -197,18 +140,70 @@ const struct address_space_operations hfsplus_btree_aops = {
.release_folio = hfsplus_release_folio,
};
-const struct address_space_operations hfsplus_aops = {
+static int hfsplus_symlink_read_folio(struct file *file, struct folio *folio)
+{
+ return block_read_full_folio(folio, hfsplus_get_block);
+}
+
+static int hfsplus_symlink_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ return mpage_writepages(mapping, wbc, hfsplus_get_block);
+}
+
+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_symlink_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_symlink_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 +263,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 +277,14 @@ 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)
+ 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;
+ }
}
truncate_setsize(inode, attr->ia_size);
hfsplus_file_truncate(inode);
@@ -353,86 +323,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 +344,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 +390,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 +531,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 0000000000000..1e6dfa693526e
--- /dev/null
+++ b/fs/hfsplus/iomap.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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,
+};
+
+const struct iomap_dio_ops hfsplus_write_dio_ops = {
+ .end_io = iomap_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 0000000000000..dac07a9d25f8d
--- /dev/null
+++ b/fs/hfsplus/iomap.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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/fs/hfsplus/super.c b/fs/hfsplus/super.c
index 5777e31de45ac..459ca6f3b8147 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -571,6 +571,7 @@ static int hfsplus_fill_super(struct super_block *sb, struct fs_context *fc)
goto out_close_attr_tree;
}
sbi->alloc_file = inode;
+ mapping_set_stable_writes(inode->i_mapping);
/* Load the root directory */
root = hfsplus_iget(sb, HFSPLUS_ROOT_CNID);
diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
index 45fb4c9ff9f5e..1417e19d94bce 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 {
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 56b43d594e6e2..50fe6036b01ac 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -560,6 +560,26 @@ struct iomap_dio_ops {
struct bio_set *bio_set;
};
+/*
+ * Direct I/O completion handler
+ */
+static inline
+int iomap_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;
+}
+
/*
* Wait for the I/O to complete in iomap_dio_rw even if the kiocb is not
* synchronous.
|
| 15/3 | 2026/08/26 23:24 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18956 output: 246 thoughts: 1463 Thoughts: **Assessing the HFS+ Filesystem I/O Patch Series for Potential Uninitialized Memory Exposure** Okay, so I'm looking at this patch series that's refactoring the `hfsplus` filesystem. The primary change is the shift from `LEGACY_DIRECT_IO` to `iomap` for file I/O operations. They've introduced new files: `fs/hfsplus/file.c`, `fs/hfsplus/iomap.c`, and `fs/hfsplus/iomap.h`, and they've modified `hfsplus_get_block` and added a `hfsplus_map_extent` function. I see they're also using `lock_page` and `folio_wait_stable` now in the block allocation/freeing routines. My primary concern, as always, is memory safety. Specifically, I want to make sure this doesn't introduce any new avenues for leaking uninitialized memory to user space. The patch seems to be replacing the old direct and buffered IO paths with `iomap`-based ones. They've updated the `hfsplus_file_operations` structure, using `iomap_dio_rw`, `iomap_file_buffered_write`, and `generic_file_read_iter` (with `iomap_dio_rw` for direct I/O). They also implement `hfsplus_iomap_ops`, and the writeback/write IO operation structures. They're implementing `begin` and `end` functions for iomap and write operations, as well as functions for writeback. The good news is that they're sticking to standard file operations: `read_iter`, `write_iter`, `llseek`, `fsync`, `open`, and `release`. The `ioctl` function remains unchanged at `hfsplus_ioctl`. They've modified `hfsplus_get_block` to use `hfsplus_map_extent`. `hfsplus_map_extent` returns `dblock` and `max_blocks`. So, I need to check how `dblock` and `max_blocks` are initialized. Alright, a deeper dive. I see they're using `iomap_zero_range` to zero-extend the backing store in `hfsplus_iomap_cont_expand`, which is great; that's exactly what I want to see. `hfsplus_write_iomap_end` is advancing `hip->phys_size` and `hfsplus_file_write_iter` and `hfsplus_file_truncate` are using `hfsplus_iomap_cont_expand` for zero-extension if the write extends past the current file size. Now, let's look at how the `iomap` address is set within `hfsplus_iomap_begin`. It's setting `iomap->addr` based on `hfsplus_ablock_to_phys_bytes` and an offset. Crucially, if a new block is being allocated, they set `IOMAP_F_NEW`. The `iomap` infrastructure itself is responsible for handling the zeroing of these `IOMAP_F_NEW` blocks, ensuring no uninitialized data leaks. This is precisely what I expect. So, the changes appear to be standard VFS/`iomap` conversions. No custom data structures copied to user space, no new system calls, no new netlink attributes, and no new ioctls. I think standard KASAN and other bug detectors are perfectly capable of catching out-of-bounds reads, use-after-free, and other common memory safety issues. The KMSAN tool is specifically for uninitialized memory issues. Let's double-check how `hfsplus_map_extent` works. It has a check for the `ablock` index and calls `hfsplus_ext_find_block` to locate a block. If `hfsplus_ext_find_block` finds a block it sets `*dblock` and returns a value, otherwise `*dblock` is set to 0. Then, let me make sure that dblock is initialized correctly in the iomap. The check that calls `hfsplus_map_extent` catches any error. It uses `hfsplus_map_extent(inode, ablock, may_alloc, &dblock, &max_blocks, NULL)`. If an error occurs, the process returns early. Based on my analysis, `iomap_zero_range` is used, and there is safe handling of allocated blocks. This patch uses `I |
| 16/3 | 2026/08/26 23:24 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series converts the hfsplus filesystem from legacy direct I/O and buffered I/O to the modern iomap infrastructure. It introduces new file operations, iomap callbacks, and modifies extent mapping logic. The changes primarily involve standard VFS and iomap API usage, such as `iomap_dio_rw`, `iomap_file_buffered_write`, and `iomap_zero_range`. File expansions are properly zeroed using `iomap_zero_range`, and newly allocated blocks are flagged with `IOMAP_F_NEW`, which the iomap core handles to prevent uninitialized disk data exposure. There are no new structures copied to user space, no new ioctls, and no complex data structures introduced that could leak uninitialized kernel memory. Any potential bugs introduced by these changes would likely be out-of-bounds accesses, use-after-free, or logic errors, which are well-covered by standard KASAN and other default sanitizers. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series converts the hfsplus filesystem from legacy direct I/O and buffered I/O to the modern iomap infrastructure. It introduces new file operations, iomap callbacks, and modifies extent mapping logic. The changes primarily involve standard VFS and iomap API usage, such as `iomap_dio_rw`, `iomap_file_buffered_write`, and `iomap_zero_range`. File expansions are properly zeroed using `iomap_zero_range`, and newly allocated blocks are flagged with `IOMAP_F_NEW`, which the iomap core handles to prevent uninitialized disk data exposure. There are no new structures copied to user space, no new ioctls, and no complex data structures introduced that could leak uninitialized kernel memory. Any potential bugs introduced by these changes would likely be out-of-bounds accesses, use-after-free, or logic errors, which are well-covered by standard KASAN and other default sanitizers. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|