The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap
@ 2026-07-27  6:17 Heming Zhao
  2026-07-27  6:17 ` [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature Heming Zhao
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Heming Zhao @ 2026-07-27  6:17 UTC (permalink / raw)
  To: joseph.qi, mark, jlbec, hch; +Cc: Heming Zhao, ocfs2-devel, linux-kernel

This patch set migrates ocfs2 Direct I/O (DIO) from buffer_head mode
to iomap mode. And the code base is commit 58717b2a1365d.

Changelog:
v2:
- Merge Joseph's code and ideas from [1]:
  - Add FMODE_CAN_ODIRECT to f_mode in ocfs2_file_open()
  - Change iomap_ops and iomap_dio_ops API declarations from ocfs2.h to
    aops.h
  - Switch from iomap_dio_rw to __iomap_dio_rw + iomap_dio_complete
  - Change "struct iomap_dio_ops" from read/write style to PR/EX locking
    style. With this approach, we completely eliminate the need to define
    any iocb->ki_flags bits.
- Remove alignment code from ocfs2_should_use_dio(), because iomap internally
  already handles this.
- Add patch 4 to remove unused functions and the CONFIG_LEGACY_DIRECT_IO
  Kconfig option.
- Add "Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>"

[1]:
https://gitee.com/anolis/storage-cloud-kernel/commit/ea3fe75e2060118ba093e90b1cd34fa382914b29


v1:
https://lore.kernel.org/ocfs2-devel/20260724052559.15409-1-heming.zhao@suse.com/T/#t

--------------
# test report

This patch set has been tested on 7.2.0 kernel with the following test
cases.

## ltp

runltp -f dio -d /mnt/ocfs2/

The test results: passed

## xfstests

./check -g quick -T -b -s ocfs2 -e generic/081 \
-e generic/266 -e generic/272 -e generic/281 \
-e generic/338 -e generic/347 -e generic/361 \
-e generic/628 -e generic/629 -e generic/648 -e generic/650 

The test results are the same before and after the patch set.

Failures: generic/003 generic/322 generic/329 generic/331 generic/370
generic/376 generic/420 generic/424 generic/448 generic/449 generic/467
generic/477 generic/510 generic/513 generic/537 generic/552 generic/563
generic/578 generic/607 generic/620 generic/630 generic/755 generic/777
generic/784
Failed 24 of 632 tests

## fio on single node

fio --direct=1 --iodepth=32 --rw=write --ioengine=libaio --bs=128k --size=1g \
--numjobs=4 --runtime=100 --group_reporting --filename=/mnt/ocfs2/write.1g --name=tst
- before patch : write: IOPS=18.8k, BW=4708MiB/s (4937MB/s)(4096MiB/870msec)
- applied patch: write: IOPS=39.6k, BW=4953MiB/s (5193MB/s)(4096MiB/827msec)
- overwrite (don't remove write.1g, run above fio command) speed seems same. 

fio --direct=1 --iodepth=32 --rw=read --ioengine=libaio --bs=128k --size=1g \
--numjobs=4 --runtime=100 --group_reporting --filename=/mnt/ocfs2/write.1g --name=tst
- before patch : read: IOPS=52.4k, BW=6554MiB/s (6872MB/s)(4096MiB/625msec)
- applied patch: read: IOPS=52.2k, BW=6522MiB/s (6839MB/s)(4096MiB/628msec)

fio --direct=1 --iodepth=32 --rw=randwrite --ioengine=libaio --bs=4k --size=1g \
--numjobs=4 --runtime=100 --group_reporting --filename=/mnt/ocfs2/write.1g --name=tst
- before patch
  - creating write: IOPS=20.9k, BW=81.7MiB/s (85.6MB/s)(4096MiB/50159msec)
  - overwriting write.1g: write: IOPS=281k, BW=1097MiB/s (1151MB/s)(4096MiB/3733msec)
- applied patch
  - creating write: IOPS=21.0k, BW=82.0MiB/s (86.0MB/s)(4096MiB/49938msec)
  - overwriting write.1g: write: IOPS=329k, BW=1286MiB/s (1348MB/s)(4096MiB/3185msec) 

fio --direct=1 --iodepth=32 --rw=randread --ioengine=libaio --bs=4k --size=1g \
--numjobs=4 --runtime=100 --group_reporting --filename=/mnt/ocfs2/write.1g --name=tst
- before patch : read: IOPS=372k, BW=1452MiB/s (1522MB/s)(4096MiB/2821msec)
- applied patch: read: IOPS=369k, BW=1441MiB/s (1511MB/s)(4096MiB/2842msec)


## fio on two nodes

### write in different folder

node1 /root/fio/diff/ocfs2-w.sh:
> fio --direct=1 --iodepth=32 --rw=write --ioengine=libaio --bs=128k --size=2g \
> --numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/node1/write.2g --name=tst

node2 /root/fio/diff/ocfs2-w.sh:
> fio --direct=1 --iodepth=32 --rw=write --ioengine=libaio --bs=128k --size=2g \
> --numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/node2/write.2g --name=tst


node1 /root/fio/diff/ocfs2-randw.sh:
> fio --direct=1 --iodepth=32 --rw=randwrite --ioengine=libaio --bs=4k --size=2g \
> --numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/node1/write.2g --name=tst

node2 /root/fio/diff/ocfs2-randw.sh:
> fio --direct=1 --iodepth=32 --rw=randwrite --ioengine=libaio --bs=4k --size=2g \
> --numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/node2/write.2g --name=tst

run on both node, 
pdsh -w ssh:tw2510-[1-2] /root/fio/diff/ocfs2-w.sh
- before patch : write: IOPS=19.3k, BW=2417MiB/s (2534MB/s)(4096MiB/1695msec)
- applied patch: write: IOPS=20.8k, BW=2599MiB/s (2725MB/s)(4096MiB/1576msec)
pdsh -w ssh:tw2510-[1-2] /root/fio/diff/ocfs2-randw.sh
- before patch : write: IOPS=3742, BW=14.6MiB/s (15.3MB/s)(1462MiB/100007msec)
- applied patch: write: IOPS=3765, BW=14.7MiB/s (15.4MB/s)(1471MiB/100010msec)


### write in same file

node1 & node2: (/root/fio/share/ocfs2-w.sh)
fio --direct=1 --iodepth=32 --rw=write --ioengine=libaio --bs=128k --size=2g \
--numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/share/write.2g --name=tst

node1 & node2: (/root/fio/share/ocfs2-randw.sh)
fio --direct=1 --iodepth=32 --rw=randwrite --ioengine=libaio --bs=4k --size=2g \
--numjobs=2 --runtime=100 --group_reporting --filename=/mnt/ocfs2/share/write.2g --name=tst

run on both node
pdsh -w ssh:tw2510-[1-2] /root/fio/share/ocfs2-w.sh
- before patch : write: IOPS=2052, BW=257MiB/s (269MB/s)(4096MiB/15967msec)
- applied patch: write: IOPS=2558, BW=320MiB/s (335MB/s)(4096MiB/12808msec)
pdsh -w ssh:tw2510-[1-2] /root/fio/share/ocfs2-randw.sh
- before patch : write: IOPS=3588, BW=14.0MiB/s (14.7MB/s)(1402MiB/100001msec)
- applied patch: write: IOPS=3590, BW=14.0MiB/s (14.7MB/s)(1403MiB/100001msec)

Heming Zhao (4):
  ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature
  ocfs2: switch dio read path from buffer_head to iomap
  ocfs2: switch dio write path from buffer_head to iomap
  ocfs2: remove legacy blockdev direct-IO path APIs

 fs/ocfs2/Kconfig          |   2 +-
 fs/ocfs2/aops.c           | 650 ++++++++++++++++++++++++--------------
 fs/ocfs2/aops.h           |  37 +--
 fs/ocfs2/buffer_head_io.c |  19 +-
 fs/ocfs2/file.c           | 161 ++++++++--
 fs/ocfs2/ocfs2.h          |  45 ++-
 fs/ocfs2/ocfs2_fs.h       |   3 +
 7 files changed, 596 insertions(+), 321 deletions(-)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature
  2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
@ 2026-07-27  6:17 ` Heming Zhao
  2026-07-28  5:41   ` Joseph Qi
  2026-07-27  6:17 ` [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap Heming Zhao
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 14+ messages in thread
From: Heming Zhao @ 2026-07-27  6:17 UTC (permalink / raw)
  To: joseph.qi, mark, jlbec, hch; +Cc: Heming Zhao, ocfs2-devel, linux-kernel

As part of migrating OCFS2 DIO read/write code paths towards the modern
and high-performant iomap framework, this patch introduces an iomap API
to replace old high-overhead VFS buffer_head structure paths.

This patch establishes the foundational block mapping routines required by
subsequent iomap integration patches. The implementation draws
inspiration from ext4_map_blocks().

Signed-off-by: Heming Zhao <heming.zhao@suse.com>
---
 fs/ocfs2/aops.c           | 94 +++++++++++++++++++++++++++++++++++++++
 fs/ocfs2/aops.h           |  2 +
 fs/ocfs2/buffer_head_io.c | 12 -----
 fs/ocfs2/ocfs2.h          | 45 ++++++++++++++++++-
 4 files changed, 140 insertions(+), 13 deletions(-)

diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
index 4acdbb70882c..08df5e3b5196 100644
--- a/fs/ocfs2/aops.c
+++ b/fs/ocfs2/aops.c
@@ -126,6 +126,100 @@ static int ocfs2_lock_get_block(struct inode *inode, sector_t iblock,
 	return ret;
 }
 
+int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
+		    int flags)
+{
+	int err = 0;
+	unsigned int ext_flags;
+	u64 max_blocks = map->len;
+	u64 p_blkno, count, past_eof;
+	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+	int create = flags & OCFS2_GET_BLOCKS_CREATE;
+
+	if (OCFS2_I(inode)->ip_flags & OCFS2_INODE_SYSTEM_FILE)
+		mlog(ML_NOTICE, "map_block on system inode 0x%p (%llu)\n",
+		     inode, inode->i_ino);
+
+	if (S_ISLNK(inode->i_mode)) {
+		/*
+		 * TODO: refer ocfs2_get_block() to handle
+		 * ocfs2_read_folio in the future
+		 */
+		mlog(ML_NOTICE, "map_block on S_ISLNK file, node 0x%p (%llu)\n",
+		     inode, inode->i_ino);
+		dump_stack();
+		goto bail;
+	}
+
+	err = ocfs2_extent_map_get_blocks(inode, map->lblk, &p_blkno, &count,
+					  &ext_flags);
+	if (err) {
+		mlog(ML_ERROR, "get_blocks() failed, inode: 0x%p, "
+		     "block: %llu\n", inode, map->lblk);
+		goto bail;
+	}
+
+	if (max_blocks < count)
+		count = max_blocks;
+
+	map->pblk = p_blkno;
+	map->len = count;
+
+	/*
+	 * ocfs2 never allocates in this function - the only time we
+	 * need to use MAP_NEW is when we're extending i_size on a file
+	 * system which doesn't support holes, in which case MAP_NEW
+	 * allows __block_write_begin() to zero.
+	 *
+	 * If we see this on a sparse file system, then a truncate has
+	 * raced us and removed the cluster. In this case, we clear
+	 * the buffers dirty and uptodate bits and let the buffer code
+	 * ignore it as a hole.
+	 */
+	if (create && map->pblk == 0 && ocfs2_sparse_alloc(osb)) {
+		map->flags &= ~(OCFS2_MAP_DIRTY | OCFS2_MAP_UPTODATE);
+		goto bail;
+	}
+
+	if (p_blkno) {
+		if (ext_flags & OCFS2_EXT_UNWRITTEN) {
+			map->flags |= OCFS2_MAP_UNWRITTEN;
+		} else if (!(ext_flags & OCFS2_EXT_UNWRITTEN)) {
+			/* Treat the unwritten extent as a hole for zeroing purposes. */
+			map->flags |= OCFS2_MAP_MAPPED;
+		} else {
+			/* nothing to do */
+		}
+	}
+
+	if (!ocfs2_sparse_alloc(osb)) {
+		if (map->pblk == 0) {
+			err = -EIO;
+			mlog(ML_ERROR,
+			     "iblock = %llu p_blkno = %llu blkno=(%llu)\n",
+			     (unsigned long long)map->lblk,
+			     (unsigned long long)map->pblk,
+			     (unsigned long long)OCFS2_I(inode)->ip_blkno);
+			mlog(ML_ERROR, "Size %llu, clusters %u\n",
+			     (unsigned long long)i_size_read(inode),
+			     OCFS2_I(inode)->ip_clusters);
+			dump_stack();
+			goto bail;
+		}
+	}
+
+	past_eof = ocfs2_blocks_for_bytes(inode->i_sb, i_size_read(inode));
+
+	if (create && (map->lblk >= past_eof))
+		map->flags |= OCFS2_MAP_NEW;
+
+bail:
+	if (err < 0)
+		return -EIO;
+	else
+		return map->len;
+}
+
 int ocfs2_get_block(struct inode *inode, sector_t iblock,
 		    struct buffer_head *bh_result, int create)
 {
diff --git a/fs/ocfs2/aops.h b/fs/ocfs2/aops.h
index 114efc9111e4..8dd6edd7c1a1 100644
--- a/fs/ocfs2/aops.h
+++ b/fs/ocfs2/aops.h
@@ -42,6 +42,8 @@ int ocfs2_size_fits_inline_data(struct buffer_head *di_bh, u64 new_size);
 
 int ocfs2_get_block(struct inode *inode, sector_t iblock,
 		    struct buffer_head *bh_result, int create);
+int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
+		    int flags);
 /* all ocfs2_dio_end_io()'s fault */
 #define ocfs2_iocb_is_rw_locked(iocb) \
 	test_bit(0, (unsigned long *)&iocb->private)
diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c
index 7bfe377af2df..493f2209cca5 100644
--- a/fs/ocfs2/buffer_head_io.c
+++ b/fs/ocfs2/buffer_head_io.c
@@ -23,18 +23,6 @@
 #include "buffer_head_io.h"
 #include "ocfs2_trace.h"
 
-/*
- * Bits on bh->b_state used by ocfs2.
- *
- * These MUST be after the JBD2 bits.  Hence, we use BH_JBDPrivateStart.
- */
-enum ocfs2_state_bits {
-	BH_NeedsValidate = BH_JBDPrivateStart,
-};
-
-/* Expand the magic b_state functions */
-BUFFER_FNS(NeedsValidate, needs_validate);
-
 int ocfs2_write_block(struct ocfs2_super *osb, struct buffer_head *bh,
 		      struct ocfs2_caching_info *ci)
 {
diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
index 62cad6522c7a..095f7ae5dded 100644
--- a/fs/ocfs2/ocfs2.h
+++ b/fs/ocfs2/ocfs2.h
@@ -509,7 +509,50 @@ struct ocfs2_super
 	struct ocfs2_filecheck_sysfs_entry osb_fc_ent;
 };
 
-#define OCFS2_SB(sb)	    ((struct ocfs2_super *)(sb)->s_fs_info)
+/*
+ * Bits on bh->b_state used by ocfs2.
+ *
+ * These MUST be after the JBD2 bits.  Hence, we use BH_JBDPrivateStart.
+ */
+enum ocfs2_state_bits {
+	BH_NeedsValidate = BH_JBDPrivateStart,
+};
+
+/* Expand the magic b_state functions */
+BUFFER_FNS(NeedsValidate, needs_validate);
+
+/*
+ * Logical to physical block mapping, used by ocfs2_map_blocks()
+ *
+ * This structure is used to pass requests into ocfs2_map_blocks() as
+ * well as to store the information returned by ocfs2_map_blocks().  It
+ * takes less room on the stack than a struct buffer_head.
+ */
+#define OCFS2_MAP_NEW			BIT(BH_New)
+#define OCFS2_MAP_MAPPED		BIT(BH_Mapped)
+#define OCFS2_MAP_UNWRITTEN		BIT(BH_Unwritten)
+/* useless? #define OCFS2_MAP_BOUNDARY		BIT(BH_Boundary) */
+/* useless? #define OCFS2_MAP_DELAYED		BIT(BH_Delay) */
+#define OCFS2_MAP_DIRTY			BIT(BH_Dirty)
+#define OCFS2_MAP_UPTODATE		BIT(BH_Uptodate)
+#define OCFS2_MAP_NEEDS_VALIDATE	BIT(BH_NeedsValidate)
+#define OCFS2_MAP_DEFER_COMPLETION	BIT(BH_Defer_Completion)
+#define OCFS2_MAP_FLAGS		(OCFS2_MAP_NEW | OCFS2_MAP_MAPPED |\
+				 OCFS2_MAP_DIRTY | OCFS2_MAP_UPTODATE |\
+				 OCFS2_MAP_NEEDS_VALIDATE |\
+				 OCFS2_MAP_DEFER_COMPLETION)
+
+struct ocfs2_map_block {
+	u64 pblk; /* physical block# */
+	u64 lblk; /* logical block# */
+	u64 len;  /* number of block */
+	unsigned int flags;
+};
+
+/* Flags used by ocfs2_map_blocks() */
+#define OCFS2_GET_BLOCKS_CREATE	(0x0001)
+
+#define OCFS2_SB(sb)	((struct ocfs2_super *)(sb)->s_fs_info)
 
 /* Useful typedef for passing around journal access functions */
 typedef int (*ocfs2_journal_access_func)(handle_t *handle,
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap
  2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
  2026-07-27  6:17 ` [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature Heming Zhao
@ 2026-07-27  6:17 ` Heming Zhao
  2026-07-28  4:16   ` Christoph Hellwig
  2026-07-28  5:50   ` Joseph Qi
  2026-07-27  6:17 ` [RFC PATCH v2 3/4] ocfs2: switch dio write " Heming Zhao
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 14+ messages in thread
From: Heming Zhao @ 2026-07-27  6:17 UTC (permalink / raw)
  To: joseph.qi, mark, jlbec, hch; +Cc: Heming Zhao, ocfs2-devel, linux-kernel

This patch migrates the DIO read path from the legacy buffer_head
infrastructure to the modern and more efficient iomap framework.

The rw cluster-lock level is not stashed in iocb->private: iomap DIO owns
that field (it stores a bio there for polled I/O and unconditionally
clears it before calling ->end_io), so any side-channel packed into
iocb->private is corrupted and would trip the end_io lock-state check.
Instead the unlock level is encoded by which iomap_dio_ops is passed -
ocfs2_iomap_dio_ops_pr (PRMODE) or ocfs2_iomap_dio_ops_ex (EXMODE) - and
the *_iter() functions use the split __iomap_dio_rw() + iomap_dio_complete()
API so they can determine unambiguously, from the return value, whether the
completion handler already dropped the lock (real I/O, sync or -EIOCBQUEUED)
or whether the caller must drop it (no I/O issued, or buffered fallback).

Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Heming Zhao <heming.zhao@suse.com>
---
 fs/ocfs2/Kconfig    |   1 +
 fs/ocfs2/aops.c     | 113 ++++++++++++++++++++++++++++++++++++++++++++
 fs/ocfs2/file.c     |  57 ++++++++++++++++++----
 fs/ocfs2/ocfs2.h    |   3 ++
 fs/ocfs2/ocfs2_fs.h |   3 ++
 5 files changed, 167 insertions(+), 10 deletions(-)

diff --git a/fs/ocfs2/Kconfig b/fs/ocfs2/Kconfig
index 2514d36cbe01..bf1678a5eb01 100644
--- a/fs/ocfs2/Kconfig
+++ b/fs/ocfs2/Kconfig
@@ -7,6 +7,7 @@ config OCFS2_FS
 	select CRC32
 	select QUOTA
 	select QUOTA_TREE
+	select FS_IOMAP
 	select FS_POSIX_ACL
 	select LEGACY_DIRECT_IO
 	help
diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
index 08df5e3b5196..12f5f2e3530a 100644
--- a/fs/ocfs2/aops.c
+++ b/fs/ocfs2/aops.c
@@ -4,6 +4,7 @@
  */
 
 #include <linux/fs.h>
+#include <linux/iomap.h>
 #include <linux/slab.h>
 #include <linux/highmem.h>
 #include <linux/pagemap.h>
@@ -2556,6 +2557,118 @@ static ssize_t ocfs2_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
 				    ocfs2_dio_end_io, 0);
 }
 
+static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+		unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
+{
+	int ret;
+	struct ocfs2_map_block map;
+	struct ocfs2_inode_info *oi = OCFS2_I(inode);
+	u8 blkbits = inode->i_blkbits;
+
+	if ((offset >> blkbits) > OCFS2_MAX_LOGICAL_BLOCK)
+		return -EINVAL;
+
+	/*
+	 * Calculate the first and last logical blocks respectively.
+	 */
+	map.lblk = offset >> blkbits;
+	map.len = min_t(loff_t, (offset + length - 1) >> blkbits,
+			OCFS2_MAX_LOGICAL_BLOCK) - map.lblk + 1;
+	map.flags = 0;
+
+	if (flags & IOMAP_WRITE) {
+		/* todo */
+	} else {
+		down_read(&oi->ip_alloc_sem);
+		ret = ocfs2_map_blocks(inode, &map, 0);
+		up_read(&oi->ip_alloc_sem);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Before returning to iomap, let's ensure the allocated mapping
+	 * covers the entire requested length for atomic writes.
+	 */
+	if (flags & IOMAP_ATOMIC) {
+		if (map.len < (length >> blkbits)) {
+			WARN_ON_ONCE(1);
+			return -EINVAL;
+		}
+	}
+
+	iomap->bdev = inode->i_sb->s_bdev;
+	iomap->offset = (u64)map.lblk << blkbits;
+	iomap->length = (u64)map.len << blkbits;
+
+	if (map.pblk == 0) {
+		iomap->type = IOMAP_HOLE;
+		iomap->addr = IOMAP_NULL_ADDR;
+	} else {
+		if (map.flags & OCFS2_MAP_UNWRITTEN) {
+			iomap->type = IOMAP_UNWRITTEN;
+		} else if (map.flags & OCFS2_MAP_MAPPED) {
+			iomap->type = IOMAP_MAPPED;
+		} else {
+			WARN_ON_ONCE(1);
+			return -EIO;
+		}
+		iomap->addr = (sector_t)map.pblk << blkbits;
+	}
+
+	if (map.flags & OCFS2_MAP_NEW)
+		iomap->flags |= IOMAP_F_NEW;
+
+	return 0;
+}
+
+const struct iomap_ops ocfs2_iomap_ops = {
+	.iomap_begin	= ocfs2_iomap_begin,
+};
+
+/*
+ * Direct I/O completion. Like the old ocfs2_dio_end_io(), we use the rw_lock
+ * DLM lock to protect io on one node from truncation on another, and drop it
+ * here once the io has completed. iomap_dio_complete() always calls this,
+ * including on the buffered-fallback (-ENOTBLK) path, so the rw_lock taken in
+ * ocfs2_file_{read,write}_iter() is released exactly once.
+ */
+static int ocfs2_iomap_dio_end_io(struct kiocb *iocb, ssize_t size, int error,
+				  int level)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+
+	/*
+	 * iomap_dio_complete() calls us even when no I/O was issued because the
+	 * mapping bounced us back to buffered I/O (reported as size == 0 and
+	 * error == 0). In that case leave the rw_lock held so
+	 * ocfs2_file_{read,write}_iter() keeps it for the buffered retry and
+	 * releases it in its own cleanup path.
+	 */
+	if (size == 0 && error == 0)
+		return 0;
+
+	ocfs2_rw_unlock(inode, level);
+	return error;
+}
+
+static int ocfs2_dio_end_io_r_pr(struct kiocb *iocb, ssize_t size,
+				int error, unsigned int flags)
+{
+	if (error)
+		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld errno:%d",
+				 (long long)size, error);
+
+	error = ocfs2_iomap_dio_end_io(iocb, size, error, 0);
+
+bail:
+	return error;
+}
+
+const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr = {
+	.end_io = ocfs2_dio_end_io_r_pr,
+};
 const struct address_space_operations ocfs2_aops = {
 	.dirty_folio		= block_dirty_folio,
 	.read_folio		= ocfs2_read_folio,
diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
index d6e977ba6565..a0b3883216bc 100644
--- a/fs/ocfs2/file.c
+++ b/fs/ocfs2/file.c
@@ -9,6 +9,7 @@
 
 #include <linux/capability.h>
 #include <linux/fs.h>
+#include <linux/iomap.h>
 #include <linux/types.h>
 #include <linux/slab.h>
 #include <linux/highmem.h>
@@ -2374,6 +2375,19 @@ static int ocfs2_prepare_inode_for_write(struct file *file,
 	return ret;
 }
 
+static bool ocfs2_should_use_dio(struct kiocb *iocb, struct iov_iter *iter,
+				struct inode *inode)
+{
+	/*
+	 * Fallback to buffered I/O if we see an inode without
+	 * extents.
+	 */
+	if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL)
+		return false;
+
+	return true;
+}
+
 static ssize_t ocfs2_file_write_iter(struct kiocb *iocb,
 				    struct iov_iter *from)
 {
@@ -2548,7 +2562,6 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
 			filp->f_path.dentry->d_name.name,
 			to->nr_segs);	/* GRRRRR */
 
-
 	if (!inode) {
 		ret = -EINVAL;
 		mlog_errno(ret);
@@ -2558,7 +2571,8 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
 	if (!direct_io && nowait)
 		return -EOPNOTSUPP;
 
-	ocfs2_iocb_init_rw_locked(iocb);
+	if (!iov_iter_count(to))
+		return 0; /* skip atime */
 
 	/*
 	 * buffered reads protect themselves in ->read_folio().  O_DIRECT reads
@@ -2576,8 +2590,6 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
 			goto bail;
 		}
 		rw_level = 0;
-		/* communicate with ocfs2_dio_end_io */
-		ocfs2_iocb_set_rw_locked(iocb, rw_level);
 	}
 
 	/*
@@ -2598,17 +2610,42 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
 	}
 	ocfs2_inode_unlock(inode, lock_level);
 
-	ret = generic_file_read_iter(iocb, to);
+	if (direct_io && ocfs2_should_use_dio(iocb, to, inode)) {
+		struct iomap_dio *dio;
+
+		dio = __iomap_dio_rw(iocb, to, &ocfs2_iomap_ops,
+				&ocfs2_iomap_dio_ops_r_pr, 0, NULL, 0);
+		if (dio == NULL) {
+			/* No I/O issued; rw_lock still held. */
+			ret = 0;
+		} else if (IS_ERR(dio)) {
+			ret = PTR_ERR(dio);
+			if (ret == -EIOCBQUEUED)
+				rw_level = -1;
+		} else {
+			ret = iomap_dio_complete(dio);
+			if (ret != 0)
+				rw_level = -1;
+		}
+		/*
+		 * A 0 result means the mapping bounced us back to buffered I/O
+		 * (e.g. inline data); the rw_lock is still held. Clear
+		 * IOCB_DIRECT so generic_file_read_iter() takes the buffered
+		 * path rather than re-entering direct I/O.
+		 */
+		if (ret == 0) {
+			iocb->ki_flags &= ~IOCB_DIRECT;
+			ret = generic_file_read_iter(iocb, to);
+		}
+	} else {
+		iocb->ki_flags &= ~IOCB_DIRECT;
+		ret = generic_file_read_iter(iocb, to);
+	}
 	trace_generic_file_read_iter_ret(ret);
 
 	/* buffered aio wouldn't have proper lock coverage today */
 	BUG_ON(ret == -EIOCBQUEUED && !direct_io);
 
-	/* see ocfs2_file_write_iter */
-	if (ret == -EIOCBQUEUED || !ocfs2_iocb_is_rw_locked(iocb)) {
-		rw_level = -1;
-	}
-
 bail:
 	if (rw_level != -1)
 		ocfs2_rw_unlock(inode, rw_level);
diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
index 095f7ae5dded..a775c1869293 100644
--- a/fs/ocfs2/ocfs2.h
+++ b/fs/ocfs2/ocfs2.h
@@ -549,6 +549,9 @@ struct ocfs2_map_block {
 	unsigned int flags;
 };
 
+extern const struct iomap_ops ocfs2_iomap_ops;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
+
 /* Flags used by ocfs2_map_blocks() */
 #define OCFS2_GET_BLOCKS_CREATE	(0x0001)
 
diff --git a/fs/ocfs2/ocfs2_fs.h b/fs/ocfs2/ocfs2_fs.h
index c501eb3cdcda..000bd014acbd 100644
--- a/fs/ocfs2/ocfs2_fs.h
+++ b/fs/ocfs2/ocfs2_fs.h
@@ -314,6 +314,9 @@
  */
 #define OCFS2_CLUSTER_O2CB_GLOBAL_HEARTBEAT	(0x01)
 
+/* Max logical block we can support */
+#define OCFS2_MAX_LOGICAL_BLOCK	(0xFFFFFFFE)
+
 struct ocfs2_system_inode_info {
 	char	*si_name;
 	int	si_iflags;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC PATCH v2 3/4] ocfs2: switch dio write path from buffer_head to iomap
  2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
  2026-07-27  6:17 ` [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature Heming Zhao
  2026-07-27  6:17 ` [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap Heming Zhao
@ 2026-07-27  6:17 ` Heming Zhao
  2026-07-28  4:18   ` Christoph Hellwig
  2026-07-28  6:07   ` Joseph Qi
  2026-07-27  6:18 ` [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs Heming Zhao
  2026-07-28  4:11 ` [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Christoph Hellwig
  4 siblings, 2 replies; 14+ messages in thread
From: Heming Zhao @ 2026-07-27  6:17 UTC (permalink / raw)
  To: joseph.qi, mark, jlbec, hch; +Cc: Heming Zhao, ocfs2-devel, linux-kernel

This patch converts OCFS2's DIO write path from the legacy
buffer_head infrastructure to the modern iomap framework.

Key modifications and designs are as follows:

1. Dynamic Context Allocation:
   Refactor 'struct ocfs2_write_ctxt' to use a flexible array 'w_desc[]'
   instead of a fixed-size array. Dynamically allocate the context based on
   the write length ('w_clen') in 'ocfs2_alloc_write_ctxt()'. This prevents
   static limits overflow and optimizes kernel heap memory utilization.

2. Robust Mapping and Limits:
   Introduce 'ocfs2_dio_wr_map_blocks()' to allocate and map direct write
   blocks. Implement a 1 MiB cap ('OCFS2_DIO_WR_MAX_MAX_BYTES') per mapping
   call to restrict allocation granularity, preventing JBD2 transaction
   credit exhaustion during huge asynchronous sequential writes.

3. Reliable Completion Work and Fallback:
   - Implement 'ocfs2_iomap_dio_end_io_write()' to handle metadata completion.
     It converts UNWRITTEN extents, updates inode size (EOF), and deletes the
     inode from the orphan directory if it was appended.
   - Implement 'ocfs2_dio_write_end_io()' to finalize the dio lifecycle and
     release cluster locks safely.
   - Intercept '-ENOTBLK' errors from 'iomap_dio_rw()' caused by page cache
     invalidation failures (due to mmap/buffered collisions). Gracefully clear
     the error, strip the IOCB_DIRECT flag, and fall back to buffered write

4. Moved ocfs2_add_inode_to_orphan():
   - moved ocfs2_add_inode_to_orphan() from ocfs2_dio_wr_map_blocks() to
     ocfs2_file_write_iter().

5. Uncertain logic in code
   For the following code block in ocfs2_dio_wr_map_blocks():
   ```
     if (extend) {
             if (ocfs2_sparse_alloc(osb))
                     ret = ocfs2_zero_tail(inode, di_bh, pos);
             else
                     ret = ocfs2_expand_nonsparse_inode(inode, di_bh, pos,
                                                         map_len, NULL);
             if (ret < 0) {
                     mlog_errno(ret);
                     goto unlock;
             }
     }
   ```
   I am not completely certain whether it is called only once per
   ocfs2_file_write_iter(), but I believe calling it multiple times will
   not introduce any side effects. Furthermore, testing across various
   scenarios showed no instances of multiple calls.

Assisted-by: Gemini:gemini-3.5-flash
Assisted-by: Claude:claude-sonnet-4-5
Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Heming Zhao <heming.zhao@suse.com>
---
 fs/ocfs2/aops.c           | 394 ++++++++++++++++++++++++++++++++++++--
 fs/ocfs2/buffer_head_io.c |   7 +-
 fs/ocfs2/file.c           |  91 +++++++--
 fs/ocfs2/ocfs2.h          |   2 +
 4 files changed, 459 insertions(+), 35 deletions(-)

diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
index 12f5f2e3530a..9a079436c9c0 100644
--- a/fs/ocfs2/aops.c
+++ b/fs/ocfs2/aops.c
@@ -764,6 +764,8 @@ int ocfs2_map_folio_blocks(struct folio *folio, u64 *p_blkno,
 #define OCFS2_MAX_CTXT_PAGES	(OCFS2_MAX_CLUSTERSIZE / PAGE_SIZE)
 #endif
 
+#define OCFS2_DIO_WR_MAX_MAP_BYTES	(1U << 20)	/* 1 MiB */
+
 #define OCFS2_MAX_CLUSTERS_PER_PAGE	(PAGE_SIZE / OCFS2_MIN_CLUSTERSIZE)
 
 struct ocfs2_unwritten_extent {
@@ -799,8 +801,6 @@ struct ocfs2_write_ctxt {
 	/* Type of caller. Must be one of buffer, mmap, direct.  */
 	ocfs2_write_type_t		w_type;
 
-	struct ocfs2_write_cluster_desc	w_desc[OCFS2_MAX_CLUSTERS_PER_PAGE];
-
 	/*
 	 * This is true if page_size > cluster_size.
 	 *
@@ -850,6 +850,8 @@ struct ocfs2_write_ctxt {
 
 	struct list_head		w_unwritten_list;
 	unsigned int			w_unwritten_count;
+
+	struct ocfs2_write_cluster_desc	w_desc[];
 };
 
 void ocfs2_unlock_and_free_folios(struct folio **folios, int num_folios)
@@ -917,17 +919,24 @@ static int ocfs2_alloc_write_ctxt(struct ocfs2_write_ctxt **wcp,
 				  unsigned len, ocfs2_write_type_t type,
 				  struct buffer_head *di_bh)
 {
-	u32 cend;
+	u32 w_cpos, cend, w_clen;
 	struct ocfs2_write_ctxt *wc;
+	size_t alloc_size;
+
+	w_cpos = pos >> osb->s_clustersize_bits;
+	cend = (pos + len - 1) >> osb->s_clustersize_bits;
+	w_clen = cend - w_cpos + 1;
+
+	alloc_size = sizeof(struct ocfs2_write_ctxt) +
+		w_clen * sizeof(struct ocfs2_write_cluster_desc);
 
-	wc = kzalloc_obj(struct ocfs2_write_ctxt, GFP_NOFS);
+	wc = kzalloc(alloc_size, GFP_NOFS);
 	if (!wc)
 		return -ENOMEM;
 
-	wc->w_cpos = pos >> osb->s_clustersize_bits;
+	wc->w_cpos = w_cpos;
 	wc->w_first_new_cpos = UINT_MAX;
-	cend = (pos + len - 1) >> osb->s_clustersize_bits;
-	wc->w_clen = cend - wc->w_cpos + 1;
+	wc->w_clen = w_clen;
 	get_bh(di_bh);
 	wc->w_di_bh = di_bh;
 	wc->w_type = type;
@@ -2218,7 +2227,7 @@ static int ocfs2_dio_wr_get_block(struct inode *inode, sector_t iblock,
 	struct ocfs2_write_cluster_desc *desc = NULL;
 	struct ocfs2_dio_write_ctxt *dwc = NULL;
 	struct buffer_head *di_bh = NULL;
-	u64 p_blkno;
+	u64 p_blkno = 0;
 	unsigned int i_blkbits = inode->i_sb->s_blocksize_bits;
 	loff_t pos = iblock << i_blkbits;
 	sector_t endblk = (i_size_read(inode) - 1) >> i_blkbits;
@@ -2358,6 +2367,148 @@ static int ocfs2_dio_wr_get_block(struct inode *inode, sector_t iblock,
 	return ret;
 }
 
+/* copy from ocfs2_dio_wr_get_block */
+static int ocfs2_dio_wr_map_blocks(struct inode *inode,
+			       struct ocfs2_map_block *map, int create)
+{
+	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+	struct ocfs2_inode_info *oi = OCFS2_I(inode);
+	struct ocfs2_write_ctxt *wc;
+	struct ocfs2_write_cluster_desc *desc = NULL;
+	struct buffer_head *di_bh = NULL;
+	u64 p_blkno;
+	sector_t iblock = map->lblk;
+	unsigned int i_blkbits = inode->i_sb->s_blocksize_bits;
+	loff_t pos = iblock << i_blkbits;
+	sector_t endblk = (i_size_read(inode) - 1) >> i_blkbits;
+	unsigned int total_len = map->len << i_blkbits;
+	unsigned int map_len;
+	unsigned int cont_clusters = 1;
+	int ret = 0, extend = 0, idx;
+
+	/*
+	 * map->len is count in ocfs2_iomap_begin according to write
+	 * "pos" and "end", we need map twice to return different buffer state:
+	 * 1. area in file size, not set NEW;
+	 * 2. area out file size, set  NEW.
+	 *
+	 *		   iblock    endblk
+	 * |--------|---------|---------|---------
+	 * |<-------area in file------->|
+	 */
+	if ((iblock <= endblk) &&
+	    ((iblock + ((total_len - 1) >> i_blkbits)) > endblk))
+		total_len = (endblk - iblock + 1) << i_blkbits;
+
+	/*
+	 * Bound how much we allocate/prepare (and journal) per call. map->len
+	 * below still reports only the physically-contiguous prefix to iomap;
+	 * iomap calls us again for the remainder.
+	 */
+	map_len = min(total_len, OCFS2_DIO_WR_MAX_MAP_BYTES);
+
+	mlog(0, "get block of %llu at %llu:%u req %u\n",
+			inode->i_ino, pos, map_len, total_len);
+
+	/*
+	 * Because we need to change file size in ocfs2_iomap_dio_end_io_write(),
+	 * or we may need to add it to orphan dir. So can not fall to fast path
+	 * while file size will be changed.
+	 */
+	if (pos + total_len <= i_size_read(inode)) {
+		/* This is the fast path for re-write. */
+		ret = ocfs2_map_blocks(inode, map, OCFS2_GET_BLOCKS_CREATE);
+		if ((map->flags & OCFS2_MAP_MAPPED) &&
+		    !(map->flags & OCFS2_MAP_NEW) && ret == 0)
+			goto out;
+
+		/* Clear state set by ocfs2_map_blocks. */
+		map->flags = 0;
+	} else {
+		extend = 1;
+	}
+
+	ret = ocfs2_inode_lock(inode, &di_bh, 1);
+	if (ret) {
+		mlog_errno(ret);
+		goto out;
+	}
+
+	down_write(&oi->ip_alloc_sem);
+
+	if (extend) {
+		if (ocfs2_sparse_alloc(osb))
+			ret = ocfs2_zero_tail(inode, di_bh, pos);
+		else
+			ret = ocfs2_expand_nonsparse_inode(inode, di_bh, pos,
+							   map_len, NULL);
+		if (ret < 0) {
+			mlog_errno(ret);
+			goto unlock;
+		}
+	}
+
+	ret = ocfs2_write_begin_nolock(inode->i_mapping, pos, map_len,
+				       OCFS2_WRITE_DIRECT, NULL,
+				       (void **)&wc, di_bh, NULL);
+	if (ret) {
+		mlog_errno(ret);
+		goto unlock;
+	}
+
+	desc = &wc->w_desc[0];
+
+	p_blkno = ocfs2_clusters_to_blocks(inode->i_sb, desc->c_phys);
+	BUG_ON(p_blkno == 0);
+	p_blkno += iblock & (u64)(ocfs2_clusters_to_blocks(inode->i_sb, 1) - 1);
+
+	/*
+	 * Calculate physically contiguous clusters by checking descriptor arrays.
+	 * This strictly prevents map->len from expanding past non-contiguous physical bounds.
+	 */
+	for (idx = 1; idx < wc->w_clen; idx++) {
+		if (wc->w_desc[idx].c_phys == wc->w_desc[idx - 1].c_phys + 1 &&
+		    wc->w_desc[idx].c_needs_zero == wc->w_desc[idx - 1].c_needs_zero) {
+			cont_clusters++;
+		} else {
+			break;
+		}
+	}
+	map->pblk = p_blkno;
+	/*
+	 * Only report the mapping as UNWRITTEN when the cluster is newly
+	 * allocated or was already unwritten (desc->c_needs_zero). Reporting
+	 * an already-written cluster as UNWRITTEN makes the iomap direct-io
+	 * core zero the sub-block head/tail padding, which destroys existing
+	 * data when a sub-block write lands inside a written cluster.
+	 */
+	map->flags |= OCFS2_MAP_MAPPED;
+	if (desc->c_needs_zero)
+		map->flags |= OCFS2_MAP_UNWRITTEN;
+	map->len = cont_clusters * ocfs2_clusters_to_blocks(inode->i_sb, 1);
+
+	if (desc->c_needs_zero)
+		map->flags |= OCFS2_MAP_NEW;
+
+	if (iblock > endblk)
+		map->flags |= OCFS2_MAP_NEW;
+
+	map->flags |= OCFS2_MAP_DEFER_COMPLETION;
+
+	ocfs2_free_unwritten_list(inode, &wc->w_unwritten_list);
+	ret = ocfs2_write_end_nolock(inode->i_mapping, pos, map_len, map_len, wc);
+	BUG_ON(ret != map_len);
+	ret = 0;
+
+unlock:
+	up_write(&oi->ip_alloc_sem);
+	ocfs2_inode_unlock(inode, 1);
+	brelse(di_bh);
+
+out:
+	return ret;
+}
+
 static int ocfs2_dio_end_io_write(struct inode *inode,
 				  struct ocfs2_dio_write_ctxt *dwc,
 				  loff_t offset,
@@ -2557,6 +2708,12 @@ static ssize_t ocfs2_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
 				    ocfs2_dio_end_io, 0);
 }
 
+static int ocfs2_iomap_alloc(struct inode *inode, struct ocfs2_map_block *map,
+			    unsigned int flags)
+{
+	return ocfs2_dio_wr_map_blocks(inode, map, 1);
+}
+
 static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 		unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
 {
@@ -2564,6 +2721,7 @@ static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 	struct ocfs2_map_block map;
 	struct ocfs2_inode_info *oi = OCFS2_I(inode);
 	u8 blkbits = inode->i_blkbits;
+	unsigned int orig_mlen;
 
 	if ((offset >> blkbits) > OCFS2_MAX_LOGICAL_BLOCK)
 		return -EINVAL;
@@ -2575,9 +2733,32 @@ static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 	map.len = min_t(loff_t, (offset + length - 1) >> blkbits,
 			OCFS2_MAX_LOGICAL_BLOCK) - map.lblk + 1;
 	map.flags = 0;
+	orig_mlen = map.len;
 
 	if (flags & IOMAP_WRITE) {
-		/* todo */
+		/*
+		 * We check here if the blocks are already allocated, then we
+		 * don't need to start a journal txn and we can directly return
+		 * the mapping information. This could boost performance
+		 * especially in multi-threaded overwrite requests.
+		 */
+		if (offset + length <= i_size_read(inode)) {
+			down_read(&oi->ip_alloc_sem);
+			ret = ocfs2_map_blocks(inode, &map, 0);
+			up_read(&oi->ip_alloc_sem);
+			/*
+			 * For atomic writes the entire requested length should
+			 * be mapped.
+			 */
+			if (map.flags & OCFS2_MAP_MAPPED) {
+				if ((!(flags & IOMAP_ATOMIC) && ret > 0) ||
+				   (flags & IOMAP_ATOMIC && ret >= orig_mlen)) {
+					goto out;
+				}
+			}
+			map.len = orig_mlen;
+		}
+		ret = ocfs2_iomap_alloc(inode, &map, flags);
 	} else {
 		down_read(&oi->ip_alloc_sem);
 		ret = ocfs2_map_blocks(inode, &map, 0);
@@ -2587,6 +2768,7 @@ static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
 	if (ret < 0)
 		return ret;
 
+out:
 	/*
 	 * Before returning to iomap, let's ensure the allocated mapping
 	 * covers the entire requested length for atomic writes.
@@ -2653,8 +2835,8 @@ static int ocfs2_iomap_dio_end_io(struct kiocb *iocb, ssize_t size, int error,
 	return error;
 }
 
-static int ocfs2_dio_end_io_r_pr(struct kiocb *iocb, ssize_t size,
-				int error, unsigned int flags)
+static int ocfs2_iomap_dio_end_io_r_pr(struct kiocb *iocb, ssize_t size,
+					int error, unsigned int flags)
 {
 	if (error)
 		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld errno:%d",
@@ -2662,13 +2844,199 @@ static int ocfs2_dio_end_io_r_pr(struct kiocb *iocb, ssize_t size,
 
 	error = ocfs2_iomap_dio_end_io(iocb, size, error, 0);
 
-bail:
 	return error;
 }
 
 const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr = {
-	.end_io = ocfs2_dio_end_io_r_pr,
+	.end_io = ocfs2_iomap_dio_end_io_r_pr,
+};
+
+/* copy from ocfs2_dio_end_io_write */
+static int ocfs2_iomap_dio_end_io_write(struct inode *inode,
+				  loff_t offset,
+				  ssize_t bytes)
+{
+	struct ocfs2_cached_dealloc_ctxt dealloc;
+	struct ocfs2_extent_tree et;
+	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+	struct ocfs2_inode_info *oi = OCFS2_I(inode);
+	struct buffer_head *di_bh = NULL;
+	struct ocfs2_dinode *di;
+	struct ocfs2_alloc_context *data_ac = NULL;
+	struct ocfs2_alloc_context *meta_ac = NULL;
+	handle_t *handle = NULL;
+	loff_t end = offset + bytes;
+	int ret = 0, credits = 0;
+	struct ocfs2_map_block map;
+	unsigned int blkbits = inode->i_blkbits;
+	unsigned int max_blocks;
+	unsigned int ue_cpos = 0, ue_phys = 0, ue_len = 0;
+	unsigned int curr_lblk, end_lblk;
+
+	map.lblk = offset >> blkbits;
+	max_blocks = (bytes + offset) >> osb->s_clustersize_bits;
+
+	ocfs2_init_dealloc_ctxt(&dealloc);
+
+	ret = ocfs2_inode_lock(inode, &di_bh, 1);
+	if (ret < 0) {
+		mlog_errno(ret);
+		goto out;
+	}
+
+	down_write(&oi->ip_alloc_sem);
+
+	di = (struct ocfs2_dinode *)di_bh->b_data;
+
+	ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode), di_bh);
+
+	/* Attach dealloc with extent tree in case that we may reuse extents
+	 * which are already unlinked from current extent tree due to extent
+	 * rotation and merging.
+	 */
+	et.et_dealloc = &dealloc;
+
+	ret = ocfs2_lock_allocators(inode, &et, 0, (bytes >> osb->s_clustersize_bits)*2,
+				    &data_ac, &meta_ac);
+	if (ret) {
+		mlog_errno(ret);
+		goto unlock;
+	}
+
+	credits = ocfs2_calc_extend_credits(inode->i_sb, &di->id2.i_list);
+
+	handle = ocfs2_start_trans(osb, credits);
+	if (IS_ERR(handle)) {
+		ret = PTR_ERR(handle);
+		mlog_errno(ret);
+		goto unlock;
+	}
+	ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), di_bh,
+				      OCFS2_JOURNAL_ACCESS_WRITE);
+	if (ret) {
+		mlog_errno(ret);
+		goto commit;
+	}
+
+	curr_lblk = offset >> blkbits;
+	/*
+	 * Round the end up so the final partial block (sub-block direct I/O)
+	 * is included; otherwise the last, partially-written cluster is left
+	 * unwritten and reads back as zero.
+	 */
+	end_lblk = (offset + bytes + (1 << blkbits) - 1) >> blkbits;
+	while (ret >= 0 && curr_lblk < end_lblk) {
+		memset(&map, 0, sizeof(map));
+		map.lblk += curr_lblk;
+		map.len = end_lblk - curr_lblk;
+
+		ret = ocfs2_assure_trans_credits(handle, credits);
+		if (ret < 0) {
+			mlog_errno(ret);
+			break;
+		}
+
+		ret = ocfs2_map_blocks(inode, &map, 0);
+		if (ret < 0) {
+			mlog_errno(ret);
+			break;
+		}
+		if (ret == 0)
+			break;
+
+		if (map.flags & OCFS2_MAP_UNWRITTEN) {
+			ue_cpos = ocfs2_blocks_to_clusters(inode->i_sb, map.lblk);
+			ue_phys = ocfs2_blocks_to_clusters(inode->i_sb, map.pblk);
+			ue_len = ocfs2_clusters_for_blocks(inode->i_sb,
+						map.lblk + map.len) - ue_cpos;
+
+			ret = ocfs2_mark_extent_written(inode, &et, handle,
+					ue_cpos, ue_len,
+					ue_phys,
+					meta_ac, &dealloc);
+			if (ret < 0) {
+				mlog_errno(ret);
+				break;
+			}
+		}
+		curr_lblk += map.len;
+	}
+
+	if (end > i_size_read(inode)) {
+		ret = ocfs2_set_inode_size(handle, inode, di_bh, end);
+		if (ret < 0)
+			mlog_errno(ret);
+	}
+
+commit:
+	ocfs2_commit_trans(osb, handle);
+
+unlock:
+	up_write(&oi->ip_alloc_sem);
+
+	if (data_ac)
+		ocfs2_free_alloc_context(data_ac);
+	if (meta_ac)
+		ocfs2_free_alloc_context(meta_ac);
+
+	down_write(&oi->ip_alloc_sem);
+	if (!ret && (di->i_flags & cpu_to_le32(OCFS2_DIO_ORPHANED_FL))) {
+		ret = ocfs2_del_inode_from_orphan(osb, inode, di_bh, 0, 0);
+		if (ret < 0)
+			mlog_errno(ret);
+	}
+	up_write(&oi->ip_alloc_sem);
+	ocfs2_inode_unlock(inode, 1);
+	brelse(di_bh);
+
+out:
+	ocfs2_run_deallocs(osb, &dealloc);
+	return ret;
+}
+
+static int ocfs2_dio_write_end_io(struct kiocb *iocb, ssize_t size,
+			int error, unsigned int flags, int level)
+{
+	struct inode *inode = file_inode(iocb->ki_filp);
+	loff_t offset = iocb->ki_pos;
+	int ret = 0;
+
+	if (error)
+		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld errno:%d",
+				 (long long)size, error);
+
+	if (size && ((flags & IOMAP_DIO_UNWRITTEN) ||
+		    (offset + size > i_size_read(inode)))) {
+		ret = ocfs2_iomap_dio_end_io_write(inode, offset, size);
+		if (ret < 0)
+			mlog_errno(ret);
+	}
+
+	error = ocfs2_iomap_dio_end_io(iocb, size, error, level);
+
+	return error;
+}
+
+static int ocfs2_iomap_dio_end_io_w_pr(struct kiocb *iocb, ssize_t size,
+				     int error, unsigned int flags)
+{
+	return ocfs2_dio_write_end_io(iocb, size, error, flags, 0);
+}
+
+static int ocfs2_iomap_dio_end_io_w_ex(struct kiocb *iocb, ssize_t size,
+				     int error, unsigned int flags)
+{
+	return ocfs2_dio_write_end_io(iocb, size, error, flags, 1);
+}
+
+const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr = {
+	.end_io = ocfs2_iomap_dio_end_io_w_pr,
+};
+
+const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex = {
+	.end_io = ocfs2_iomap_dio_end_io_w_ex,
 };
+
 const struct address_space_operations ocfs2_aops = {
 	.dirty_folio		= block_dirty_folio,
 	.read_folio		= ocfs2_read_folio,
diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c
index 493f2209cca5..03906ab737ec 100644
--- a/fs/ocfs2/buffer_head_io.c
+++ b/fs/ocfs2/buffer_head_io.c
@@ -332,6 +332,8 @@ int ocfs2_read_blocks(struct ocfs2_caching_info *ci, u64 block, int nr,
 						wait_on_buffer(bh);
 					put_bh(bh);
 					bhs[i] = NULL;
+				} else if (bh && buffer_uptodate(bh)) {
+					clear_buffer_uptodate(bh);
 				}
 				continue;
 			}
@@ -360,11 +362,8 @@ int ocfs2_read_blocks(struct ocfs2_caching_info *ci, u64 block, int nr,
 				BUG_ON(buffer_jbd(bh));
 				clear_buffer_needs_validate(bh);
 				status = validate(sb, bh);
-				if (status) {
-					if (buffer_uptodate(bh))
-						clear_buffer_uptodate(bh);
+				if (status)
 					goto read_failure;
-				}
 			}
 		}
 
diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
index a0b3883216bc..2d78f2863acf 100644
--- a/fs/ocfs2/file.c
+++ b/fs/ocfs2/file.c
@@ -48,6 +48,7 @@
 #include "quota.h"
 #include "refcounttree.h"
 #include "ocfs2_trace.h"
+#include "namei.h"
 
 #include "buffer_head_io.h"
 
@@ -2378,6 +2379,8 @@ static int ocfs2_prepare_inode_for_write(struct file *file,
 static bool ocfs2_should_use_dio(struct kiocb *iocb, struct iov_iter *iter,
 				struct inode *inode)
 {
+	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
+
 	/*
 	 * Fallback to buffered I/O if we see an inode without
 	 * extents.
@@ -2385,6 +2388,11 @@ static bool ocfs2_should_use_dio(struct kiocb *iocb, struct iov_iter *iter,
 	if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL)
 		return false;
 
+	/* Fallback to buffered I/O if we do not support append dio. */
+	if (iocb->ki_pos + iter->count > i_size_read(inode) &&
+	    !ocfs2_supports_append_dio(osb))
+		return false;
+
 	return true;
 }
 
@@ -2405,6 +2413,8 @@ static ssize_t ocfs2_file_write_iter(struct kiocb *iocb,
 			i_size_read(inode) ? 1 : 0);
 	int direct_io = iocb->ki_flags & IOCB_DIRECT ? 1 : 0;
 	int nowait = iocb->ki_flags & IOCB_NOWAIT ? 1 : 0;
+	int dio_flags = 0;
+	ssize_t buffered;
 
 	trace_ocfs2_file_write_iter(inode, file, file->f_path.dentry,
 		(unsigned long long)OCFS2_I(inode)->ip_blkno,
@@ -2424,8 +2434,6 @@ static ssize_t ocfs2_file_write_iter(struct kiocb *iocb,
 	} else
 		inode_lock(inode);
 
-	ocfs2_iocb_init_rw_locked(iocb);
-
 	/*
 	 * Concurrent O_DIRECT writes are allowed with
 	 * mount_option "coherency=buffered".
@@ -2489,26 +2497,73 @@ static ssize_t ocfs2_file_write_iter(struct kiocb *iocb,
 		saved_ki_complete = xchg(&iocb->ki_complete, NULL);
 	}
 
-	/* communicate with ocfs2_dio_end_io */
-	ocfs2_iocb_set_rw_locked(iocb, rw_level);
+	if (direct_io && ocfs2_should_use_dio(iocb, from, inode)) {
+		if (ocfs2_clusters_for_bytes(inode->i_sb, iocb->ki_pos + count) >
+		    ocfs2_clusters_for_bytes(inode->i_sb, i_size_read(inode))) {
+			/*
+			 * when we are going to alloc extents beyond file size, add the
+			 * inode to orphan dir, so we can recall those spaces when
+			 * system crashed during write.
+			 */
+			ret = ocfs2_add_inode_to_orphan(osb, inode);
+			if (ret < 0) {
+				mlog_errno(ret);
+				goto out;
+			}
+			dio_flags = IOMAP_DIO_FORCE_WAIT;
+		}
+		const struct iomap_dio_ops *dops = rw_level ?
+			&ocfs2_iomap_dio_ops_w_ex : &ocfs2_iomap_dio_ops_w_pr;
+		struct iomap_dio *dio;
+
+		dio = __iomap_dio_rw(iocb, from, &ocfs2_iomap_ops,
+				     dops, dio_flags, NULL, 0);
+		if (dio == NULL) {
+			/* No I/O issued; rw_lock still held. */
+			written = 0;
+		} else if (IS_ERR(dio)) {
+			written = PTR_ERR(dio);
+			/*
+			 * -EIOCBQUEUED: async I/O; end_io will drop the lock on
+			 * completion. Any other error means no I/O started and
+			 * end_io was not called, so the cleanup path below drops
+			 * the lock.
+			 */
+			if (written == -EIOCBQUEUED)
+				rw_level = -1;
+		} else {
+			/*
+			 * Synchronous completion. iomap_dio_complete() runs
+			 * end_io, which drops the lock unless the mapping
+			 * bounced us back to buffered I/O (returns 0, lock
+			 * retained).
+			 */
+			written = iomap_dio_complete(dio);
+			if (written != 0)
+				rw_level = -1;
+		}
+		/*
+		 * iomap_dio_rw() returns -ENOTBLK when it could not invalidate
+		 * the page cache for the DIO range (e.g. racing buffered/mmap
+		 * I/O on the same file, as exercised by generic/095). Fall back
+		 * to a buffered write for the remaining data, the same way
+		 * ext4/xfs do, instead of leaking -ENOTBLK to userspace.
+		 */
+		if (written == -ENOTBLK)
+			written = 0;
+		if (written >= 0 && iov_iter_count(from)) {
+			iocb->ki_flags &= ~IOCB_DIRECT;
+			buffered = __generic_file_write_iter(iocb, from);
+			written = direct_write_fallback(iocb, from, written, buffered);
+		}
+	} else {
+		iocb->ki_flags &= ~IOCB_DIRECT;
+		written = __generic_file_write_iter(iocb, from);
+	}
 
-	written = __generic_file_write_iter(iocb, from);
 	/* buffered aio wouldn't have proper lock coverage today */
 	BUG_ON(written == -EIOCBQUEUED && !direct_io);
 
-	/*
-	 * deep in g_f_a_w_n()->ocfs2_direct_IO we pass in a ocfs2_dio_end_io
-	 * function pointer which is called when o_direct io completes so that
-	 * it can unlock our rw lock.
-	 * Unfortunately there are error cases which call end_io and others
-	 * that don't.  so we don't have to unlock the rw_lock if either an
-	 * async dio is going to do it in the future or an end_io after an
-	 * error has already done it.
-	 */
-	if ((written == -EIOCBQUEUED) || (!ocfs2_iocb_is_rw_locked(iocb))) {
-		rw_level = -1;
-	}
-
 	if (unlikely(written <= 0))
 		goto out;
 
diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
index a775c1869293..b228b10b5e71 100644
--- a/fs/ocfs2/ocfs2.h
+++ b/fs/ocfs2/ocfs2.h
@@ -551,6 +551,8 @@ struct ocfs2_map_block {
 
 extern const struct iomap_ops ocfs2_iomap_ops;
 extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex;
 
 /* Flags used by ocfs2_map_blocks() */
 #define OCFS2_GET_BLOCKS_CREATE	(0x0001)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs
  2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
                   ` (2 preceding siblings ...)
  2026-07-27  6:17 ` [RFC PATCH v2 3/4] ocfs2: switch dio write " Heming Zhao
@ 2026-07-27  6:18 ` Heming Zhao
  2026-07-28  4:19   ` Christoph Hellwig
  2026-07-28  6:11   ` Joseph Qi
  2026-07-28  4:11 ` [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Christoph Hellwig
  4 siblings, 2 replies; 14+ messages in thread
From: Heming Zhao @ 2026-07-27  6:18 UTC (permalink / raw)
  To: joseph.qi, mark, jlbec, hch; +Cc: Heming Zhao, ocfs2-devel, linux-kernel

Since the DIO path already migrated from buffer_head mode (by using
a_ops->direct_IO method) to iomap mode, the VFS no longer sets
FMODE_CAN_ODIRECT automatically, so ocfs2_file_open() sets it
explicitly.

The now-unused blockdev get_block/end_io machinery
(ocfs2_lock_get_block(), ocfs2_dio_wr_get_block(), ocfs2_dio_end_io(),
ocfs2_dio_end_io_write(), and the ocfs2_dio_write_ctxt helpers) is removed,
and OCFS2_FS Kconfig removes LEGACY_DIRECT_IO.

Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Signed-off-by: Heming Zhao <heming.zhao@suse.com>
---
 fs/ocfs2/Kconfig |   1 -
 fs/ocfs2/aops.c  | 411 -----------------------------------------------
 fs/ocfs2/aops.h  |  35 +---
 fs/ocfs2/file.c  |  13 +-
 fs/ocfs2/ocfs2.h |   5 -
 5 files changed, 16 insertions(+), 449 deletions(-)

diff --git a/fs/ocfs2/Kconfig b/fs/ocfs2/Kconfig
index bf1678a5eb01..74bc2008ef0b 100644
--- a/fs/ocfs2/Kconfig
+++ b/fs/ocfs2/Kconfig
@@ -9,7 +9,6 @@ config OCFS2_FS
 	select QUOTA_TREE
 	select FS_IOMAP
 	select FS_POSIX_ACL
-	select LEGACY_DIRECT_IO
 	help
 	  OCFS2 is a general purpose extent based shared disk cluster file
 	  system with many similarities to ext3. It supports 64 bit inode
diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
index 9a079436c9c0..3e4a7043074a 100644
--- a/fs/ocfs2/aops.c
+++ b/fs/ocfs2/aops.c
@@ -114,19 +114,6 @@ static int ocfs2_symlink_get_block(struct inode *inode, sector_t iblock,
 	return err;
 }
 
-static int ocfs2_lock_get_block(struct inode *inode, sector_t iblock,
-		    struct buffer_head *bh_result, int create)
-{
-	int ret = 0;
-	struct ocfs2_inode_info *oi = OCFS2_I(inode);
-
-	down_read(&oi->ip_alloc_sem);
-	ret = ocfs2_get_block(inode, iblock, bh_result, create);
-	up_read(&oi->ip_alloc_sem);
-
-	return ret;
-}
-
 int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
 		    int flags)
 {
@@ -2170,204 +2157,6 @@ static int ocfs2_write_end(const struct kiocb *iocb,
 	return ret;
 }
 
-struct ocfs2_dio_write_ctxt {
-	struct list_head	dw_zero_list;
-	unsigned		dw_zero_count;
-	int			dw_orphaned;
-	pid_t			dw_writer_pid;
-};
-
-static struct ocfs2_dio_write_ctxt *
-ocfs2_dio_alloc_write_ctx(struct buffer_head *bh, int *alloc)
-{
-	struct ocfs2_dio_write_ctxt *dwc = NULL;
-
-	if (bh->b_private)
-		return bh->b_private;
-
-	dwc = kmalloc_obj(struct ocfs2_dio_write_ctxt, GFP_NOFS);
-	if (dwc == NULL)
-		return NULL;
-	INIT_LIST_HEAD(&dwc->dw_zero_list);
-	dwc->dw_zero_count = 0;
-	dwc->dw_orphaned = 0;
-	dwc->dw_writer_pid = task_pid_nr(current);
-	bh->b_private = dwc;
-	*alloc = 1;
-
-	return dwc;
-}
-
-static void ocfs2_dio_free_write_ctx(struct inode *inode,
-				     struct ocfs2_dio_write_ctxt *dwc)
-{
-	ocfs2_free_unwritten_list(inode, &dwc->dw_zero_list);
-	kfree(dwc);
-}
-
-/*
- * TODO: Make this into a generic get_blocks function.
- *
- * From do_direct_io in direct-io.c:
- *  "So what we do is to permit the ->get_blocks function to populate
- *   bh.b_size with the size of IO which is permitted at this offset and
- *   this i_blkbits."
- *
- * This function is called directly from get_more_blocks in direct-io.c.
- *
- * called like this: dio->get_blocks(dio->inode, fs_startblk,
- * 					fs_count, map_bh, dio->rw == WRITE);
- */
-static int ocfs2_dio_wr_get_block(struct inode *inode, sector_t iblock,
-			       struct buffer_head *bh_result, int create)
-{
-	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
-	struct ocfs2_inode_info *oi = OCFS2_I(inode);
-	struct ocfs2_write_ctxt *wc;
-	struct ocfs2_write_cluster_desc *desc = NULL;
-	struct ocfs2_dio_write_ctxt *dwc = NULL;
-	struct buffer_head *di_bh = NULL;
-	u64 p_blkno = 0;
-	unsigned int i_blkbits = inode->i_sb->s_blocksize_bits;
-	loff_t pos = iblock << i_blkbits;
-	sector_t endblk = (i_size_read(inode) - 1) >> i_blkbits;
-	unsigned len, total_len = bh_result->b_size;
-	int ret = 0, first_get_block = 0;
-
-	len = osb->s_clustersize - (pos & (osb->s_clustersize - 1));
-	len = min(total_len, len);
-
-	/*
-	 * bh_result->b_size is count in get_more_blocks according to write
-	 * "pos" and "end", we need map twice to return different buffer state:
-	 * 1. area in file size, not set NEW;
-	 * 2. area out file size, set  NEW.
-	 *
-	 *		   iblock    endblk
-	 * |--------|---------|---------|---------
-	 * |<-------area in file------->|
-	 */
-
-	if ((iblock <= endblk) &&
-	    ((iblock + ((len - 1) >> i_blkbits)) > endblk))
-		len = (endblk - iblock + 1) << i_blkbits;
-
-	mlog(0, "get block of %llu at %llu:%u req %u\n",
-			inode->i_ino, pos, len, total_len);
-
-	/*
-	 * Because we need to change file size in ocfs2_dio_end_io_write(), or
-	 * we may need to add it to orphan dir. So can not fall to fast path
-	 * while file size will be changed.
-	 */
-	if (pos + total_len <= i_size_read(inode)) {
-
-		/* This is the fast path for re-write. */
-		ret = ocfs2_lock_get_block(inode, iblock, bh_result, create);
-		if (buffer_mapped(bh_result) &&
-		    !buffer_new(bh_result) &&
-		    ret == 0)
-			goto out;
-
-		/* Clear state set by ocfs2_get_block. */
-		bh_result->b_state = 0;
-	}
-
-	dwc = ocfs2_dio_alloc_write_ctx(bh_result, &first_get_block);
-	if (unlikely(dwc == NULL)) {
-		ret = -ENOMEM;
-		mlog_errno(ret);
-		goto out;
-	}
-
-	if (ocfs2_clusters_for_bytes(inode->i_sb, pos + total_len) >
-	    ocfs2_clusters_for_bytes(inode->i_sb, i_size_read(inode)) &&
-	    !dwc->dw_orphaned) {
-		/*
-		 * when we are going to alloc extents beyond file size, add the
-		 * inode to orphan dir, so we can recall those spaces when
-		 * system crashed during write.
-		 */
-		ret = ocfs2_add_inode_to_orphan(osb, inode);
-		if (ret < 0) {
-			mlog_errno(ret);
-			goto out;
-		}
-		dwc->dw_orphaned = 1;
-	}
-
-	ret = ocfs2_inode_lock(inode, &di_bh, 1);
-	if (ret) {
-		mlog_errno(ret);
-		goto out;
-	}
-
-	down_write(&oi->ip_alloc_sem);
-
-	if (first_get_block) {
-		if (ocfs2_sparse_alloc(osb))
-			ret = ocfs2_zero_tail(inode, di_bh, pos);
-		else
-			ret = ocfs2_expand_nonsparse_inode(inode, di_bh, pos,
-							   total_len, NULL);
-		if (ret < 0) {
-			mlog_errno(ret);
-			goto unlock;
-		}
-	}
-
-	ret = ocfs2_write_begin_nolock(inode->i_mapping, pos, len,
-				       OCFS2_WRITE_DIRECT, NULL,
-				       (void **)&wc, di_bh, NULL);
-	if (ret) {
-		mlog_errno(ret);
-		goto unlock;
-	}
-
-	desc = &wc->w_desc[0];
-
-	p_blkno = ocfs2_clusters_to_blocks(inode->i_sb, desc->c_phys);
-	BUG_ON(p_blkno == 0);
-	p_blkno += iblock & (u64)(ocfs2_clusters_to_blocks(inode->i_sb, 1) - 1);
-
-	map_bh(bh_result, inode->i_sb, p_blkno);
-	bh_result->b_size = len;
-	if (desc->c_needs_zero)
-		set_buffer_new(bh_result);
-
-	if (iblock > endblk)
-		set_buffer_new(bh_result);
-
-	/* May sleep in end_io. It should not happen in a irq context. So defer
-	 * it to dio work queue. */
-	set_buffer_defer_completion(bh_result);
-
-	if (!list_empty(&wc->w_unwritten_list)) {
-		struct ocfs2_unwritten_extent *ue = NULL;
-
-		ue = list_first_entry(&wc->w_unwritten_list,
-				      struct ocfs2_unwritten_extent,
-				      ue_node);
-		BUG_ON(ue->ue_cpos != desc->c_cpos);
-		/* The physical address may be 0, fill it. */
-		ue->ue_phys = desc->c_phys;
-
-		list_splice_tail_init(&wc->w_unwritten_list, &dwc->dw_zero_list);
-		dwc->dw_zero_count += wc->w_unwritten_count;
-	}
-
-	ret = ocfs2_write_end_nolock(inode->i_mapping, pos, len, len, wc);
-	BUG_ON(ret != len);
-	ret = 0;
-unlock:
-	up_write(&oi->ip_alloc_sem);
-	ocfs2_inode_unlock(inode, 1);
-	brelse(di_bh);
-out:
-	return ret;
-}
-
-/* copy from ocfs2_dio_wr_get_block */
 static int ocfs2_dio_wr_map_blocks(struct inode *inode,
 			       struct ocfs2_map_block *map, int create)
 {
@@ -2509,205 +2298,6 @@ static int ocfs2_dio_wr_map_blocks(struct inode *inode,
 	return ret;
 }
 
-static int ocfs2_dio_end_io_write(struct inode *inode,
-				  struct ocfs2_dio_write_ctxt *dwc,
-				  loff_t offset,
-				  ssize_t bytes)
-{
-	struct ocfs2_cached_dealloc_ctxt dealloc;
-	struct ocfs2_extent_tree et;
-	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
-	struct ocfs2_inode_info *oi = OCFS2_I(inode);
-	struct ocfs2_unwritten_extent *ue = NULL;
-	struct buffer_head *di_bh = NULL;
-	struct ocfs2_dinode *di;
-	struct ocfs2_alloc_context *data_ac = NULL;
-	struct ocfs2_alloc_context *meta_ac = NULL;
-	handle_t *handle = NULL;
-	loff_t end = offset + bytes;
-	int ret = 0, credits = 0, batch = 0;
-
-	ocfs2_init_dealloc_ctxt(&dealloc);
-
-	/* We do clear unwritten, delete orphan, change i_size here. If neither
-	 * of these happen, we can skip all this. */
-	if (list_empty(&dwc->dw_zero_list) &&
-	    end <= i_size_read(inode) &&
-	    !dwc->dw_orphaned)
-		goto out;
-
-	ret = ocfs2_inode_lock(inode, &di_bh, 1);
-	if (ret < 0) {
-		mlog_errno(ret);
-		goto out;
-	}
-
-	down_write(&oi->ip_alloc_sem);
-	di = (struct ocfs2_dinode *)di_bh->b_data;
-
-	ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode), di_bh);
-
-	/* Attach dealloc with extent tree in case that we may reuse extents
-	 * which are already unlinked from current extent tree due to extent
-	 * rotation and merging.
-	 */
-	et.et_dealloc = &dealloc;
-
-	ret = ocfs2_lock_allocators(inode, &et, 0, dwc->dw_zero_count*2,
-				    &data_ac, &meta_ac);
-	if (ret) {
-		mlog_errno(ret);
-		goto unlock;
-	}
-
-	credits = ocfs2_calc_extend_credits(inode->i_sb, &di->id2.i_list);
-
-	list_for_each_entry(ue, &dwc->dw_zero_list, ue_node) {
-		if (!handle) {
-			handle = ocfs2_start_trans(osb, credits);
-			if (IS_ERR(handle)) {
-				ret = PTR_ERR(handle);
-				mlog_errno(ret);
-				goto unlock;
-			}
-			ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), di_bh,
-					OCFS2_JOURNAL_ACCESS_WRITE);
-			if (ret) {
-				mlog_errno(ret);
-				goto commit;
-			}
-		}
-		ret = ocfs2_assure_trans_credits(handle, credits);
-		if (ret < 0) {
-			mlog_errno(ret);
-			goto commit;
-		}
-		ret = ocfs2_mark_extent_written(inode, &et, handle,
-						ue->ue_cpos, 1,
-						ue->ue_phys,
-						meta_ac, &dealloc);
-		if (ret < 0) {
-			mlog_errno(ret);
-			goto commit;
-		}
-
-		if (++batch == OCFS2_DIO_MARK_EXTENT_BATCH) {
-			ocfs2_commit_trans(osb, handle);
-			handle = NULL;
-			batch = 0;
-		}
-	}
-
-	if (end > i_size_read(inode)) {
-		if (!handle) {
-			handle = ocfs2_start_trans(osb, credits);
-			if (IS_ERR(handle)) {
-				ret = PTR_ERR(handle);
-				mlog_errno(ret);
-				goto unlock;
-			}
-		}
-		ret = ocfs2_set_inode_size(handle, inode, di_bh, end);
-		if (ret < 0)
-			mlog_errno(ret);
-	}
-
-commit:
-	if (handle)
-		ocfs2_commit_trans(osb, handle);
-unlock:
-	up_write(&oi->ip_alloc_sem);
-
-	if (data_ac) {
-		ocfs2_free_alloc_context(data_ac);
-		data_ac = NULL;
-	}
-	if (meta_ac) {
-		ocfs2_free_alloc_context(meta_ac);
-		meta_ac = NULL;
-	}
-
-	/* everything looks good, let's start the cleanup */
-	if (!ret && dwc->dw_orphaned) {
-		BUG_ON(dwc->dw_writer_pid != task_pid_nr(current));
-
-		ret = ocfs2_del_inode_from_orphan(osb, inode, di_bh, 0, 0);
-		if (ret < 0)
-			mlog_errno(ret);
-	}
-	ocfs2_inode_unlock(inode, 1);
-	brelse(di_bh);
-out:
-	ocfs2_run_deallocs(osb, &dealloc);
-	ocfs2_dio_free_write_ctx(inode, dwc);
-
-	return ret;
-}
-
-/*
- * ocfs2_dio_end_io is called by the dio core when a dio is finished.  We're
- * particularly interested in the aio/dio case.  We use the rw_lock DLM lock
- * to protect io on one node from truncation on another.
- */
-static int ocfs2_dio_end_io(struct kiocb *iocb,
-			    loff_t offset,
-			    ssize_t bytes,
-			    void *private)
-{
-	struct inode *inode = file_inode(iocb->ki_filp);
-	int level;
-	int ret = 0;
-
-	/* this io's submitter should not have unlocked this before we could */
-	BUG_ON(!ocfs2_iocb_is_rw_locked(iocb));
-
-	if (bytes <= 0)
-		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld",
-				 (long long)bytes);
-	if (private) {
-		if (bytes > 0)
-			ret = ocfs2_dio_end_io_write(inode, private, offset,
-						     bytes);
-		else
-			ocfs2_dio_free_write_ctx(inode, private);
-	}
-
-	ocfs2_iocb_clear_rw_locked(iocb);
-
-	level = ocfs2_iocb_rw_locked_level(iocb);
-	ocfs2_rw_unlock(inode, level);
-	return ret;
-}
-
-static ssize_t ocfs2_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
-{
-	struct file *file = iocb->ki_filp;
-	struct inode *inode = file->f_mapping->host;
-	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
-	get_block_t *get_block;
-
-	/*
-	 * Fallback to buffered I/O if we see an inode without
-	 * extents.
-	 */
-	if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL)
-		return 0;
-
-	/* Fallback to buffered I/O if we do not support append dio. */
-	if (iocb->ki_pos + iter->count > i_size_read(inode) &&
-	    !ocfs2_supports_append_dio(osb))
-		return 0;
-
-	if (iov_iter_rw(iter) == READ)
-		get_block = ocfs2_lock_get_block;
-	else
-		get_block = ocfs2_dio_wr_get_block;
-
-	return __blockdev_direct_IO(iocb, inode, inode->i_sb->s_bdev,
-				    iter, get_block,
-				    ocfs2_dio_end_io, 0);
-}
-
 static int ocfs2_iomap_alloc(struct inode *inode, struct ocfs2_map_block *map,
 			    unsigned int flags)
 {
@@ -3045,7 +2635,6 @@ const struct address_space_operations ocfs2_aops = {
 	.write_begin		= ocfs2_write_begin,
 	.write_end		= ocfs2_write_end,
 	.bmap			= ocfs2_bmap,
-	.direct_IO		= ocfs2_direct_IO,
 	.invalidate_folio	= block_invalidate_folio,
 	.release_folio		= ocfs2_release_folio,
 	.migrate_folio		= buffer_migrate_folio,
diff --git a/fs/ocfs2/aops.h b/fs/ocfs2/aops.h
index 8dd6edd7c1a1..484b25d749d7 100644
--- a/fs/ocfs2/aops.h
+++ b/fs/ocfs2/aops.h
@@ -7,6 +7,12 @@
 #define OCFS2_AOPS_H
 
 #include <linux/fs.h>
+#include <linux/iomap.h>
+
+extern const struct iomap_ops ocfs2_iomap_ops;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr;
+extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex;
 
 int ocfs2_map_folio_blocks(struct folio *folio, u64 *p_blkno,
 			  struct inode *inode, unsigned int from,
@@ -44,34 +50,5 @@ int ocfs2_get_block(struct inode *inode, sector_t iblock,
 		    struct buffer_head *bh_result, int create);
 int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
 		    int flags);
-/* all ocfs2_dio_end_io()'s fault */
-#define ocfs2_iocb_is_rw_locked(iocb) \
-	test_bit(0, (unsigned long *)&iocb->private)
-static inline void ocfs2_iocb_set_rw_locked(struct kiocb *iocb, int level)
-{
-	set_bit(0, (unsigned long *)&iocb->private);
-	if (level)
-		set_bit(1, (unsigned long *)&iocb->private);
-	else
-		clear_bit(1, (unsigned long *)&iocb->private);
-}
-
-/*
- * Using a named enum representing lock types in terms of #N bit stored in
- * iocb->private, which is going to be used for communication between
- * ocfs2_dio_end_io() and ocfs2_file_write/read_iter().
- */
-enum ocfs2_iocb_lock_bits {
-	OCFS2_IOCB_RW_LOCK = 0,
-	OCFS2_IOCB_RW_LOCK_LEVEL,
-	OCFS2_IOCB_NUM_LOCKS
-};
-
-#define ocfs2_iocb_init_rw_locked(iocb) \
-	(iocb->private = NULL)
-#define ocfs2_iocb_clear_rw_locked(iocb) \
-	clear_bit(OCFS2_IOCB_RW_LOCK, (unsigned long *)&iocb->private)
-#define ocfs2_iocb_rw_locked_level(iocb) \
-	test_bit(OCFS2_IOCB_RW_LOCK_LEVEL, (unsigned long *)&iocb->private)
 
 #endif /* OCFS2_FILE_H */
diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
index 2d78f2863acf..a54f92633582 100644
--- a/fs/ocfs2/file.c
+++ b/fs/ocfs2/file.c
@@ -92,6 +92,13 @@ static int ocfs2_file_open(struct inode *inode, struct file *file)
 			      file->f_path.dentry->d_name.len,
 			      file->f_path.dentry->d_name.name, mode);
 
+	/*
+	 * Direct I/O is served through iomap_dio_rw() from
+	 * ocfs2_file_{read,write}_iter() rather than an a_ops->direct_IO
+	 * method, so advertise O_DIRECT capability explicitly here.
+	 */
+	file->f_mode |= FMODE_CAN_ODIRECT;
+
 	if (file->f_mode & FMODE_WRITE) {
 		status = dquot_initialize(inode);
 		if (status)
@@ -1171,9 +1178,9 @@ int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	size_change = S_ISREG(inode->i_mode) && attr->ia_valid & ATTR_SIZE;
 	if (size_change) {
 		/*
-		 * Here we should wait dio to finish before inode lock
-		 * to avoid a deadlock between ocfs2_setattr() and
-		 * ocfs2_dio_end_io_write()
+		 * Here we should wait for in-flight direct I/O to finish
+		 * before taking the inode lock, to avoid a deadlock between
+		 * ocfs2_setattr() and direct I/O completion.
 		 */
 		inode_dio_wait(inode);
 
diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
index b228b10b5e71..095f7ae5dded 100644
--- a/fs/ocfs2/ocfs2.h
+++ b/fs/ocfs2/ocfs2.h
@@ -549,11 +549,6 @@ struct ocfs2_map_block {
 	unsigned int flags;
 };
 
-extern const struct iomap_ops ocfs2_iomap_ops;
-extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
-extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr;
-extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex;
-
 /* Flags used by ocfs2_map_blocks() */
 #define OCFS2_GET_BLOCKS_CREATE	(0x0001)
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap
  2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
                   ` (3 preceding siblings ...)
  2026-07-27  6:18 ` [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs Heming Zhao
@ 2026-07-28  4:11 ` Christoph Hellwig
  4 siblings, 0 replies; 14+ messages in thread
From: Christoph Hellwig @ 2026-07-28  4:11 UTC (permalink / raw)
  To: Heming Zhao; +Cc: joseph.qi, mark, jlbec, hch, ocfs2-devel, linux-kernel

s/migrates/migrate/


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap
  2026-07-27  6:17 ` [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap Heming Zhao
@ 2026-07-28  4:16   ` Christoph Hellwig
  2026-07-28  5:50   ` Joseph Qi
  1 sibling, 0 replies; 14+ messages in thread
From: Christoph Hellwig @ 2026-07-28  4:16 UTC (permalink / raw)
  To: Heming Zhao; +Cc: joseph.qi, mark, jlbec, hch, ocfs2-devel, linux-kernel

On Mon, Jul 27, 2026 at 02:17:58PM +0800, Heming Zhao wrote:
> This patch migrates the DIO read path from the legacy buffer_head

Please never start commit messages with "This patch", that context
is implied already.

> The rw cluster-lock level is not stashed in iocb->private: iomap DIO owns

Explaining what you don't do is always a bit odd, I'd start with what
you do, and only really mention what not later if really needed.

> that field (it stores a bio there for polled I/O and unconditionally
> clears it before calling ->end_io), so any side-channel packed into
> iocb->private is corrupted and would trip the end_io lock-state check.
> Instead the unlock level is encoded by which iomap_dio_ops is passed -
> ocfs2_iomap_dio_ops_pr (PRMODE) or ocfs2_iomap_dio_ops_ex (EXMODE) - and
> the *_iter() functions use the split __iomap_dio_rw() + iomap_dio_complete()
> API so they can determine unambiguously, from the return value, whether the
> completion handler already dropped the lock (real I/O, sync or -EIOCBQUEUED)
> or whether the caller must drop it (no I/O issued, or buffered fallback).

.. and a lot of this just seems to be about the locking, but very little
about the iomap changes.

> +	if (flags & IOMAP_WRITE) {
> +		/* todo */

should this be a WARN_ON for now?

> +	if (direct_io && ocfs2_should_use_dio(iocb, to, inode)) {
> +		struct iomap_dio *dio;
> +
> +		dio = __iomap_dio_rw(iocb, to, &ocfs2_iomap_ops,
> +				&ocfs2_iomap_dio_ops_r_pr, 0, NULL, 0);
> +		if (dio == NULL) {
> +			/* No I/O issued; rw_lock still held. */
> +			ret = 0;
> +		} else if (IS_ERR(dio)) {
> +			ret = PTR_ERR(dio);
> +			if (ret == -EIOCBQUEUED)
> +				rw_level = -1;
> +		} else {
> +			ret = iomap_dio_complete(dio);
> +			if (ret != 0)
> +				rw_level = -1;
> +		}

What information is missing if you use plain iomap_dio_rw here?

> +		/*
> +		 * A 0 result means the mapping bounced us back to buffered I/O
> +		 * (e.g. inline data); the rw_lock is still held. Clear
> +		 * IOCB_DIRECT so generic_file_read_iter() takes the buffered
> +		 * path rather than re-entering direct I/O.
> +		 */
> +		if (ret == 0) {
> +			iocb->ki_flags &= ~IOCB_DIRECT;
> +			ret = generic_file_read_iter(iocb, to);
> +		}
> +	} else {
> +		iocb->ki_flags &= ~IOCB_DIRECT;
> +		ret = generic_file_read_iter(iocb, to);
> +	}

Can you share this code somehow?


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 3/4] ocfs2: switch dio write path from buffer_head to iomap
  2026-07-27  6:17 ` [RFC PATCH v2 3/4] ocfs2: switch dio write " Heming Zhao
@ 2026-07-28  4:18   ` Christoph Hellwig
  2026-07-28  6:07   ` Joseph Qi
  1 sibling, 0 replies; 14+ messages in thread
From: Christoph Hellwig @ 2026-07-28  4:18 UTC (permalink / raw)
  To: Heming Zhao; +Cc: joseph.qi, mark, jlbec, hch, ocfs2-devel, linux-kernel

On Mon, Jul 27, 2026 at 02:17:59PM +0800, Heming Zhao wrote:
> This patch converts OCFS2's DIO write path from the legacy
> buffer_head infrastructure to the modern iomap framework.
> 
> Key modifications and designs are as follows:
> 
> 1. Dynamic Context Allocation:

This reads like complete AI slop.  Please turn this into a coherent
commit message without the lists and exaggerations.

I'll try to read that before reading the commit, because it kinda
implies a lack of understing by anyone what actually happens.

So please try to understand what you're doing or had your tool do,
then write it down and re-review if the code makes sense based
on that.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs
  2026-07-27  6:18 ` [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs Heming Zhao
@ 2026-07-28  4:19   ` Christoph Hellwig
  2026-07-28  4:25     ` Heming Zhao
  2026-07-28  6:11   ` Joseph Qi
  1 sibling, 1 reply; 14+ messages in thread
From: Christoph Hellwig @ 2026-07-28  4:19 UTC (permalink / raw)
  To: Heming Zhao; +Cc: joseph.qi, mark, jlbec, hch, ocfs2-devel, linux-kernel

After this __blockdev_direct_IO is unused except to implement
blockdev_direct_IO.  So please fold blockdev_direct_IO into
__blockdev_direct_IO and remove all the code that is now unused

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs
  2026-07-28  4:19   ` Christoph Hellwig
@ 2026-07-28  4:25     ` Heming Zhao
  0 siblings, 0 replies; 14+ messages in thread
From: Heming Zhao @ 2026-07-28  4:25 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: joseph.qi, mark, jlbec, ocfs2-devel, linux-kernel

On 7/28/26 12:19, Christoph Hellwig wrote:
> After this __blockdev_direct_IO is unused except to implement
> blockdev_direct_IO.  So please fold blockdev_direct_IO into
> __blockdev_direct_IO and remove all the code that is now unused


Thanks for all your kind review comments.
I will carefully revise the patch set before sending v3.

- Heming

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature
  2026-07-27  6:17 ` [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature Heming Zhao
@ 2026-07-28  5:41   ` Joseph Qi
  0 siblings, 0 replies; 14+ messages in thread
From: Joseph Qi @ 2026-07-28  5:41 UTC (permalink / raw)
  To: Heming Zhao; +Cc: mark, jlbec, hch, ocfs2-devel, linux-kernel



On 7/27/26 2:17 PM, Heming Zhao wrote:
> As part of migrating OCFS2 DIO read/write code paths towards the modern
> and high-performant iomap framework, this patch introduces an iomap API
> to replace old high-overhead VFS buffer_head structure paths.
> 
> This patch establishes the foundational block mapping routines required by
> subsequent iomap integration patches. The implementation draws
> inspiration from ext4_map_blocks().
> 
> Signed-off-by: Heming Zhao <heming.zhao@suse.com>
> ---
>  fs/ocfs2/aops.c           | 94 +++++++++++++++++++++++++++++++++++++++
>  fs/ocfs2/aops.h           |  2 +
>  fs/ocfs2/buffer_head_io.c | 12 -----
>  fs/ocfs2/ocfs2.h          | 45 ++++++++++++++++++-
>  4 files changed, 140 insertions(+), 13 deletions(-)
> 
> diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
> index 4acdbb70882c..08df5e3b5196 100644
> --- a/fs/ocfs2/aops.c
> +++ b/fs/ocfs2/aops.c
> @@ -126,6 +126,100 @@ static int ocfs2_lock_get_block(struct inode *inode, sector_t iblock,
>  	return ret;
>  }
>  
> +int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
> +		    int flags)
> +{
> +	int err = 0;
> +	unsigned int ext_flags;
> +	u64 max_blocks = map->len;
> +	u64 p_blkno, count, past_eof;
> +	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
> +	int create = flags & OCFS2_GET_BLOCKS_CREATE;
> +
> +	if (OCFS2_I(inode)->ip_flags & OCFS2_INODE_SYSTEM_FILE)
> +		mlog(ML_NOTICE, "map_block on system inode 0x%p (%llu)\n",
> +		     inode, inode->i_ino);
> +
> +	if (S_ISLNK(inode->i_mode)) {
> +		/*
> +		 * TODO: refer ocfs2_get_block() to handle
> +		 * ocfs2_read_folio in the future
> +		 */
> +		mlog(ML_NOTICE, "map_block on S_ISLNK file, node 0x%p (%llu)\n",
> +		     inode, inode->i_ino);
> +		dump_stack();

Here mlog and dump_stack() for debug?
Why not gate behind ML_DEBUG and WARN_ON_ONCE()?

> +		goto bail;
> +	}
> +
> +	err = ocfs2_extent_map_get_blocks(inode, map->lblk, &p_blkno, &count,
> +					  &ext_flags);
> +	if (err) {
> +		mlog(ML_ERROR, "get_blocks() failed, inode: 0x%p, "
> +		     "block: %llu\n", inode, map->lblk);
> +		goto bail;
> +	}
> +
> +	if (max_blocks < count)
> +		count = max_blocks;
> +
> +	map->pblk = p_blkno;
> +	map->len = count;
> +
> +	/*
> +	 * ocfs2 never allocates in this function - the only time we
> +	 * need to use MAP_NEW is when we're extending i_size on a file
> +	 * system which doesn't support holes, in which case MAP_NEW
> +	 * allows __block_write_begin() to zero.
> +	 *
> +	 * If we see this on a sparse file system, then a truncate has
> +	 * raced us and removed the cluster. In this case, we clear
> +	 * the buffers dirty and uptodate bits and let the buffer code
> +	 * ignore it as a hole.
> +	 */
> +	if (create && map->pblk == 0 && ocfs2_sparse_alloc(osb)) {
> +		map->flags &= ~(OCFS2_MAP_DIRTY | OCFS2_MAP_UPTODATE);
> +		goto bail;
> +	}
> +
> +	if (p_blkno) {
> +		if (ext_flags & OCFS2_EXT_UNWRITTEN) {
> +			map->flags |= OCFS2_MAP_UNWRITTEN;
> +		} else if (!(ext_flags & OCFS2_EXT_UNWRITTEN)) {
> +			/* Treat the unwritten extent as a hole for zeroing purposes. */
> +			map->flags |= OCFS2_MAP_MAPPED;
> +		} else {
> +			/* nothing to do */
> +		}
> +	}

It looks odd here. Can simplify to:

if (ext_flags & OCFS2_EXT_UNWRITTEN)
	map->flags |= OCFS2_MAP_UNWRITTEN;
else
	map->flags |= OCFS2_MAP_MAPPED;

> +
> +	if (!ocfs2_sparse_alloc(osb)) {
> +		if (map->pblk == 0) {
> +			err = -EIO;
> +			mlog(ML_ERROR,
> +			     "iblock = %llu p_blkno = %llu blkno=(%llu)\n",
> +			     (unsigned long long)map->lblk,
> +			     (unsigned long long)map->pblk,
> +			     (unsigned long long)OCFS2_I(inode)->ip_blkno);
> +			mlog(ML_ERROR, "Size %llu, clusters %u\n",
> +			     (unsigned long long)i_size_read(inode),
> +			     OCFS2_I(inode)->ip_clusters);
> +			dump_stack();
> +			goto bail;
> +		}
> +	}
> +
> +	past_eof = ocfs2_blocks_for_bytes(inode->i_sb, i_size_read(inode));
> +
> +	if (create && (map->lblk >= past_eof))
> +		map->flags |= OCFS2_MAP_NEW;
> +
> +bail:
> +	if (err < 0)
> +		return -EIO;

Why swallow the error code?
I'd rather propagate the actual error.

> +	else
> +		return map->len;
> +}
> +
>  int ocfs2_get_block(struct inode *inode, sector_t iblock,
>  		    struct buffer_head *bh_result, int create)
>  {
> diff --git a/fs/ocfs2/aops.h b/fs/ocfs2/aops.h
> index 114efc9111e4..8dd6edd7c1a1 100644
> --- a/fs/ocfs2/aops.h
> +++ b/fs/ocfs2/aops.h
> @@ -42,6 +42,8 @@ int ocfs2_size_fits_inline_data(struct buffer_head *di_bh, u64 new_size);
>  
>  int ocfs2_get_block(struct inode *inode, sector_t iblock,
>  		    struct buffer_head *bh_result, int create);
> +int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
> +		    int flags);
>  /* all ocfs2_dio_end_io()'s fault */
>  #define ocfs2_iocb_is_rw_locked(iocb) \
>  	test_bit(0, (unsigned long *)&iocb->private)
> diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c
> index 7bfe377af2df..493f2209cca5 100644
> --- a/fs/ocfs2/buffer_head_io.c
> +++ b/fs/ocfs2/buffer_head_io.c
> @@ -23,18 +23,6 @@
>  #include "buffer_head_io.h"
>  #include "ocfs2_trace.h"
>  
> -/*
> - * Bits on bh->b_state used by ocfs2.
> - *
> - * These MUST be after the JBD2 bits.  Hence, we use BH_JBDPrivateStart.
> - */
> -enum ocfs2_state_bits {
> -	BH_NeedsValidate = BH_JBDPrivateStart,
> -};
> -
> -/* Expand the magic b_state functions */
> -BUFFER_FNS(NeedsValidate, needs_validate);
> -
>  int ocfs2_write_block(struct ocfs2_super *osb, struct buffer_head *bh,
>  		      struct ocfs2_caching_info *ci)
>  {
> diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
> index 62cad6522c7a..095f7ae5dded 100644
> --- a/fs/ocfs2/ocfs2.h
> +++ b/fs/ocfs2/ocfs2.h
> @@ -509,7 +509,50 @@ struct ocfs2_super
>  	struct ocfs2_filecheck_sysfs_entry osb_fc_ent;
>  };
>  
> -#define OCFS2_SB(sb)	    ((struct ocfs2_super *)(sb)->s_fs_info)
> +/*
> + * Bits on bh->b_state used by ocfs2.
> + *
> + * These MUST be after the JBD2 bits.  Hence, we use BH_JBDPrivateStart.
> + */
> +enum ocfs2_state_bits {
> +	BH_NeedsValidate = BH_JBDPrivateStart,
> +};
> +
> +/* Expand the magic b_state functions */
> +BUFFER_FNS(NeedsValidate, needs_validate);
> +
> +/*
> + * Logical to physical block mapping, used by ocfs2_map_blocks()
> + *
> + * This structure is used to pass requests into ocfs2_map_blocks() as
> + * well as to store the information returned by ocfs2_map_blocks().  It
> + * takes less room on the stack than a struct buffer_head.
> + */
> +#define OCFS2_MAP_NEW			BIT(BH_New)
> +#define OCFS2_MAP_MAPPED		BIT(BH_Mapped)
> +#define OCFS2_MAP_UNWRITTEN		BIT(BH_Unwritten)
> +/* useless? #define OCFS2_MAP_BOUNDARY		BIT(BH_Boundary) */
> +/* useless? #define OCFS2_MAP_DELAYED		BIT(BH_Delay) */
> +#define OCFS2_MAP_DIRTY			BIT(BH_Dirty)
> +#define OCFS2_MAP_UPTODATE		BIT(BH_Uptodate)
> +#define OCFS2_MAP_NEEDS_VALIDATE	BIT(BH_NeedsValidate)
> +#define OCFS2_MAP_DEFER_COMPLETION	BIT(BH_Defer_Completion)
> +#define OCFS2_MAP_FLAGS		(OCFS2_MAP_NEW | OCFS2_MAP_MAPPED |\
> +				 OCFS2_MAP_DIRTY | OCFS2_MAP_UPTODATE |\
> +				 OCFS2_MAP_NEEDS_VALIDATE |\
> +				 OCFS2_MAP_DEFER_COMPLETION)
> +
> +struct ocfs2_map_block {
> +	u64 pblk; /* physical block# */
> +	u64 lblk; /* logical block# */
> +	u64 len;  /* number of block */
> +	unsigned int flags;
> +};
> +
> +/* Flags used by ocfs2_map_blocks() */
> +#define OCFS2_GET_BLOCKS_CREATE	(0x0001)
> +
> +#define OCFS2_SB(sb)	((struct ocfs2_super *)(sb)->s_fs_info)
>  
>  /* Useful typedef for passing around journal access functions */
>  typedef int (*ocfs2_journal_access_func)(handle_t *handle,


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap
  2026-07-27  6:17 ` [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap Heming Zhao
  2026-07-28  4:16   ` Christoph Hellwig
@ 2026-07-28  5:50   ` Joseph Qi
  1 sibling, 0 replies; 14+ messages in thread
From: Joseph Qi @ 2026-07-28  5:50 UTC (permalink / raw)
  To: Heming Zhao; +Cc: mark, jlbec, hch, ocfs2-devel, linux-kernel



On 7/27/26 2:17 PM, Heming Zhao wrote:
> This patch migrates the DIO read path from the legacy buffer_head
> infrastructure to the modern and more efficient iomap framework.
> 
> The rw cluster-lock level is not stashed in iocb->private: iomap DIO owns
> that field (it stores a bio there for polled I/O and unconditionally
> clears it before calling ->end_io), so any side-channel packed into
> iocb->private is corrupted and would trip the end_io lock-state check.
> Instead the unlock level is encoded by which iomap_dio_ops is passed -
> ocfs2_iomap_dio_ops_pr (PRMODE) or ocfs2_iomap_dio_ops_ex (EXMODE) - and
> the *_iter() functions use the split __iomap_dio_rw() + iomap_dio_complete()
> API so they can determine unambiguously, from the return value, whether the
> completion handler already dropped the lock (real I/O, sync or -EIOCBQUEUED)
> or whether the caller must drop it (no I/O issued, or buffered fallback).
> 
> Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Heming Zhao <heming.zhao@suse.com>
> ---
>  fs/ocfs2/Kconfig    |   1 +
>  fs/ocfs2/aops.c     | 113 ++++++++++++++++++++++++++++++++++++++++++++
>  fs/ocfs2/file.c     |  57 ++++++++++++++++++----
>  fs/ocfs2/ocfs2.h    |   3 ++
>  fs/ocfs2/ocfs2_fs.h |   3 ++
>  5 files changed, 167 insertions(+), 10 deletions(-)
> 
> diff --git a/fs/ocfs2/Kconfig b/fs/ocfs2/Kconfig
> index 2514d36cbe01..bf1678a5eb01 100644
> --- a/fs/ocfs2/Kconfig
> +++ b/fs/ocfs2/Kconfig
> @@ -7,6 +7,7 @@ config OCFS2_FS
>  	select CRC32
>  	select QUOTA
>  	select QUOTA_TREE
> +	select FS_IOMAP
>  	select FS_POSIX_ACL
>  	select LEGACY_DIRECT_IO
>  	help
> diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
> index 08df5e3b5196..12f5f2e3530a 100644
> --- a/fs/ocfs2/aops.c
> +++ b/fs/ocfs2/aops.c
> @@ -4,6 +4,7 @@
>   */
>  
>  #include <linux/fs.h>
> +#include <linux/iomap.h>
>  #include <linux/slab.h>
>  #include <linux/highmem.h>
>  #include <linux/pagemap.h>
> @@ -2556,6 +2557,118 @@ static ssize_t ocfs2_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
>  				    ocfs2_dio_end_io, 0);
>  }
>  
> +static int ocfs2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> +		unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
> +{
> +	int ret;
> +	struct ocfs2_map_block map;
> +	struct ocfs2_inode_info *oi = OCFS2_I(inode);
> +	u8 blkbits = inode->i_blkbits;
> +
> +	if ((offset >> blkbits) > OCFS2_MAX_LOGICAL_BLOCK)
> +		return -EINVAL;
> +
> +	/*
> +	 * Calculate the first and last logical blocks respectively.
> +	 */
> +	map.lblk = offset >> blkbits;
> +	map.len = min_t(loff_t, (offset + length - 1) >> blkbits,
> +			OCFS2_MAX_LOGICAL_BLOCK) - map.lblk + 1;
> +	map.flags = 0;
> +
> +	if (flags & IOMAP_WRITE) {
> +		/* todo */

This makes the series non-bisectable since ret is uninitialized.
So return -EOPNOTSUPP instead.

BTW, 'TODO' is preferred.

> +	} else {
> +		down_read(&oi->ip_alloc_sem);
> +		ret = ocfs2_map_blocks(inode, &map, 0);
> +		up_read(&oi->ip_alloc_sem);
> +	}
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	/*
> +	 * Before returning to iomap, let's ensure the allocated mapping
> +	 * covers the entire requested length for atomic writes.
> +	 */
> +	if (flags & IOMAP_ATOMIC) {
> +		if (map.len < (length >> blkbits)) {
> +			WARN_ON_ONCE(1);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	iomap->bdev = inode->i_sb->s_bdev;
> +	iomap->offset = (u64)map.lblk << blkbits;
> +	iomap->length = (u64)map.len << blkbits;
> +
> +	if (map.pblk == 0) {
> +		iomap->type = IOMAP_HOLE;
> +		iomap->addr = IOMAP_NULL_ADDR;
> +	} else {
> +		if (map.flags & OCFS2_MAP_UNWRITTEN) {
> +			iomap->type = IOMAP_UNWRITTEN;
> +		} else if (map.flags & OCFS2_MAP_MAPPED) {
> +			iomap->type = IOMAP_MAPPED;
> +		} else {
> +			WARN_ON_ONCE(1);
> +			return -EIO;
> +		}
> +		iomap->addr = (sector_t)map.pblk << blkbits;
> +	}
> +
> +	if (map.flags & OCFS2_MAP_NEW)
> +		iomap->flags |= IOMAP_F_NEW;
> +
> +	return 0;
> +}
> +
> +const struct iomap_ops ocfs2_iomap_ops = {
> +	.iomap_begin	= ocfs2_iomap_begin,
> +};
> +
> +/*
> + * Direct I/O completion. Like the old ocfs2_dio_end_io(), we use the rw_lock
> + * DLM lock to protect io on one node from truncation on another, and drop it
> + * here once the io has completed. iomap_dio_complete() always calls this,
> + * including on the buffered-fallback (-ENOTBLK) path, so the rw_lock taken in
> + * ocfs2_file_{read,write}_iter() is released exactly once.
> + */
> +static int ocfs2_iomap_dio_end_io(struct kiocb *iocb, ssize_t size, int error,
> +				  int level)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +
> +	/*
> +	 * iomap_dio_complete() calls us even when no I/O was issued because the

This looks incorrect.
If __iomap_dio_rw() returns NULL, iomap_dio_complete() is never called.

> +	 * mapping bounced us back to buffered I/O (reported as size == 0 and
> +	 * error == 0). In that case leave the rw_lock held so
> +	 * ocfs2_file_{read,write}_iter() keeps it for the buffered retry and
> +	 * releases it in its own cleanup path.
> +	 */
> +	if (size == 0 && error == 0)
> +		return 0;
> +
> +	ocfs2_rw_unlock(inode, level);
> +	return error;
> +}
> +
> +static int ocfs2_dio_end_io_r_pr(struct kiocb *iocb, ssize_t size,
> +				int error, unsigned int flags)
> +{
> +	if (error)
> +		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld errno:%d",
> +				 (long long)size, error);
> +
> +	error = ocfs2_iomap_dio_end_io(iocb, size, error, 0);
> +
> +bail:

Seems it is a dead label.

> +	return error;
> +}
> +
> +const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr = {
> +	.end_io = ocfs2_dio_end_io_r_pr,
> +};
>  const struct address_space_operations ocfs2_aops = {
>  	.dirty_folio		= block_dirty_folio,
>  	.read_folio		= ocfs2_read_folio,
> diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
> index d6e977ba6565..a0b3883216bc 100644
> --- a/fs/ocfs2/file.c
> +++ b/fs/ocfs2/file.c
> @@ -9,6 +9,7 @@
>  
>  #include <linux/capability.h>
>  #include <linux/fs.h>
> +#include <linux/iomap.h>
>  #include <linux/types.h>
>  #include <linux/slab.h>
>  #include <linux/highmem.h>
> @@ -2374,6 +2375,19 @@ static int ocfs2_prepare_inode_for_write(struct file *file,
>  	return ret;
>  }
>  
> +static bool ocfs2_should_use_dio(struct kiocb *iocb, struct iov_iter *iter,
> +				struct inode *inode)
> +{
> +	/*
> +	 * Fallback to buffered I/O if we see an inode without
> +	 * extents.
> +	 */
> +	if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL)
> +		return false;
> +
> +	return true;
> +}
> +
>  static ssize_t ocfs2_file_write_iter(struct kiocb *iocb,
>  				    struct iov_iter *from)
>  {
> @@ -2548,7 +2562,6 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
>  			filp->f_path.dentry->d_name.name,
>  			to->nr_segs);	/* GRRRRR */
>  
> -
>  	if (!inode) {
>  		ret = -EINVAL;
>  		mlog_errno(ret);
> @@ -2558,7 +2571,8 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
>  	if (!direct_io && nowait)
>  		return -EOPNOTSUPP;
>  
> -	ocfs2_iocb_init_rw_locked(iocb);
> +	if (!iov_iter_count(to))
> +		return 0; /* skip atime */
>  
>  	/*
>  	 * buffered reads protect themselves in ->read_folio().  O_DIRECT reads
> @@ -2576,8 +2590,6 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
>  			goto bail;
>  		}
>  		rw_level = 0;
> -		/* communicate with ocfs2_dio_end_io */
> -		ocfs2_iocb_set_rw_locked(iocb, rw_level);
>  	}
>  
>  	/*
> @@ -2598,17 +2610,42 @@ static ssize_t ocfs2_file_read_iter(struct kiocb *iocb,
>  	}
>  	ocfs2_inode_unlock(inode, lock_level);
>  
> -	ret = generic_file_read_iter(iocb, to);
> +	if (direct_io && ocfs2_should_use_dio(iocb, to, inode)) {
> +		struct iomap_dio *dio;
> +
> +		dio = __iomap_dio_rw(iocb, to, &ocfs2_iomap_ops,
> +				&ocfs2_iomap_dio_ops_r_pr, 0, NULL, 0);
> +		if (dio == NULL) {
> +			/* No I/O issued; rw_lock still held. */
> +			ret = 0;
> +		} else if (IS_ERR(dio)) {
> +			ret = PTR_ERR(dio);
> +			if (ret == -EIOCBQUEUED)
> +				rw_level = -1;
> +		} else {
> +			ret = iomap_dio_complete(dio);
> +			if (ret != 0)
> +				rw_level = -1;
> +		}
> +		/*
> +		 * A 0 result means the mapping bounced us back to buffered I/O
> +		 * (e.g. inline data); the rw_lock is still held. Clear
> +		 * IOCB_DIRECT so generic_file_read_iter() takes the buffered
> +		 * path rather than re-entering direct I/O.
> +		 */
> +		if (ret == 0) {
> +			iocb->ki_flags &= ~IOCB_DIRECT;
> +			ret = generic_file_read_iter(iocb, to);
> +		}
> +	} else {
> +		iocb->ki_flags &= ~IOCB_DIRECT;
> +		ret = generic_file_read_iter(iocb, to);
> +	}
>  	trace_generic_file_read_iter_ret(ret);
>  
>  	/* buffered aio wouldn't have proper lock coverage today */
>  	BUG_ON(ret == -EIOCBQUEUED && !direct_io);
>  
> -	/* see ocfs2_file_write_iter */
> -	if (ret == -EIOCBQUEUED || !ocfs2_iocb_is_rw_locked(iocb)) {
> -		rw_level = -1;
> -	}
> -
>  bail:
>  	if (rw_level != -1)
>  		ocfs2_rw_unlock(inode, rw_level);
> diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
> index 095f7ae5dded..a775c1869293 100644
> --- a/fs/ocfs2/ocfs2.h
> +++ b/fs/ocfs2/ocfs2.h
> @@ -549,6 +549,9 @@ struct ocfs2_map_block {
>  	unsigned int flags;
>  };
>  
> +extern const struct iomap_ops ocfs2_iomap_ops;
> +extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
> +
>  /* Flags used by ocfs2_map_blocks() */
>  #define OCFS2_GET_BLOCKS_CREATE	(0x0001)
>  
> diff --git a/fs/ocfs2/ocfs2_fs.h b/fs/ocfs2/ocfs2_fs.h
> index c501eb3cdcda..000bd014acbd 100644
> --- a/fs/ocfs2/ocfs2_fs.h
> +++ b/fs/ocfs2/ocfs2_fs.h
> @@ -314,6 +314,9 @@
>   */
>  #define OCFS2_CLUSTER_O2CB_GLOBAL_HEARTBEAT	(0x01)
>  
> +/* Max logical block we can support */
> +#define OCFS2_MAX_LOGICAL_BLOCK	(0xFFFFFFFE)
> +
>  struct ocfs2_system_inode_info {
>  	char	*si_name;
>  	int	si_iflags;


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 3/4] ocfs2: switch dio write path from buffer_head to iomap
  2026-07-27  6:17 ` [RFC PATCH v2 3/4] ocfs2: switch dio write " Heming Zhao
  2026-07-28  4:18   ` Christoph Hellwig
@ 2026-07-28  6:07   ` Joseph Qi
  1 sibling, 0 replies; 14+ messages in thread
From: Joseph Qi @ 2026-07-28  6:07 UTC (permalink / raw)
  To: Heming Zhao; +Cc: mark, jlbec, hch, ocfs2-devel, linux-kernel



On 7/27/26 2:17 PM, Heming Zhao wrote:
> This patch converts OCFS2's DIO write path from the legacy
> buffer_head infrastructure to the modern iomap framework.
> 
> Key modifications and designs are as follows:
> 
> 1. Dynamic Context Allocation:
>    Refactor 'struct ocfs2_write_ctxt' to use a flexible array 'w_desc[]'
>    instead of a fixed-size array. Dynamically allocate the context based on
>    the write length ('w_clen') in 'ocfs2_alloc_write_ctxt()'. This prevents
>    static limits overflow and optimizes kernel heap memory utilization.
> 
> 2. Robust Mapping and Limits:
>    Introduce 'ocfs2_dio_wr_map_blocks()' to allocate and map direct write
>    blocks. Implement a 1 MiB cap ('OCFS2_DIO_WR_MAX_MAX_BYTES') per mapping
>    call to restrict allocation granularity, preventing JBD2 transaction
>    credit exhaustion during huge asynchronous sequential writes.
> 
> 3. Reliable Completion Work and Fallback:
>    - Implement 'ocfs2_iomap_dio_end_io_write()' to handle metadata completion.
>      It converts UNWRITTEN extents, updates inode size (EOF), and deletes the
>      inode from the orphan directory if it was appended.
>    - Implement 'ocfs2_dio_write_end_io()' to finalize the dio lifecycle and
>      release cluster locks safely.
>    - Intercept '-ENOTBLK' errors from 'iomap_dio_rw()' caused by page cache
>      invalidation failures (due to mmap/buffered collisions). Gracefully clear
>      the error, strip the IOCB_DIRECT flag, and fall back to buffered write
> 
> 4. Moved ocfs2_add_inode_to_orphan():
>    - moved ocfs2_add_inode_to_orphan() from ocfs2_dio_wr_map_blocks() to
>      ocfs2_file_write_iter().
> 
> 5. Uncertain logic in code
>    For the following code block in ocfs2_dio_wr_map_blocks():
>    ```
>      if (extend) {
>              if (ocfs2_sparse_alloc(osb))
>                      ret = ocfs2_zero_tail(inode, di_bh, pos);
>              else
>                      ret = ocfs2_expand_nonsparse_inode(inode, di_bh, pos,
>                                                          map_len, NULL);
>              if (ret < 0) {
>                      mlog_errno(ret);
>                      goto unlock;
>              }
>      }
>    ```
>    I am not completely certain whether it is called only once per
>    ocfs2_file_write_iter(), but I believe calling it multiple times will
>    not introduce any side effects. Furthermore, testing across various
>    scenarios showed no instances of multiple calls.
> 
> Assisted-by: Gemini:gemini-3.5-flash
> Assisted-by: Claude:claude-sonnet-4-5
> Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Heming Zhao <heming.zhao@suse.com>
> ---
>  fs/ocfs2/aops.c           | 394 ++++++++++++++++++++++++++++++++++++--
>  fs/ocfs2/buffer_head_io.c |   7 +-
>  fs/ocfs2/file.c           |  91 +++++++--
>  fs/ocfs2/ocfs2.h          |   2 +
>  4 files changed, 459 insertions(+), 35 deletions(-)
> 
> diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
> index 12f5f2e3530a..9a079436c9c0 100644
> --- a/fs/ocfs2/aops.c
> +++ b/fs/ocfs2/aops.c

...

> +
> +	ocfs2_free_unwritten_list(inode, &wc->w_unwritten_list);
> +	ret = ocfs2_write_end_nolock(inode->i_mapping, pos, map_len, map_len, wc);
> +	BUG_ON(ret != map_len);

Under memory pressure, folio allocation may fail. In this case,
ocfs2_write_end_nolock() can return a short count.
So we must handle this case gracefully.

> +	ret = 0;
> +
> +unlock:
> +	up_write(&oi->ip_alloc_sem);
> +	ocfs2_inode_unlock(inode, 1);
> +	brelse(di_bh);
> +
> +out:
> +	return ret;
> +}
> +
>  static int ocfs2_dio_end_io_write(struct inode *inode,
>  				  struct ocfs2_dio_write_ctxt *dwc,
>  				  loff_t offset,

...

> +static int ocfs2_iomap_dio_end_io_write(struct inode *inode,
> +				  loff_t offset,
> +				  ssize_t bytes)
> +{
> +	struct ocfs2_cached_dealloc_ctxt dealloc;
> +	struct ocfs2_extent_tree et;
> +	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
> +	struct ocfs2_inode_info *oi = OCFS2_I(inode);
> +	struct buffer_head *di_bh = NULL;
> +	struct ocfs2_dinode *di;
> +	struct ocfs2_alloc_context *data_ac = NULL;
> +	struct ocfs2_alloc_context *meta_ac = NULL;
> +	handle_t *handle = NULL;
> +	loff_t end = offset + bytes;
> +	int ret = 0, credits = 0;
> +	struct ocfs2_map_block map;
> +	unsigned int blkbits = inode->i_blkbits;
> +	unsigned int max_blocks;
> +	unsigned int ue_cpos = 0, ue_phys = 0, ue_len = 0;
> +	unsigned int curr_lblk, end_lblk;
> +
> +	map.lblk = offset >> blkbits;
> +	max_blocks = (bytes + offset) >> osb->s_clustersize_bits;

Seems unused.

...

> +	curr_lblk = offset >> blkbits;
> +	/*
> +	 * Round the end up so the final partial block (sub-block direct I/O)
> +	 * is included; otherwise the last, partially-written cluster is left
> +	 * unwritten and reads back as zero.
> +	 */
> +	end_lblk = (offset + bytes + (1 << blkbits) - 1) >> blkbits;
> +	while (ret >= 0 && curr_lblk < end_lblk) {
> +		memset(&map, 0, sizeof(map));
> +		map.lblk += curr_lblk;

Since map is memset just now, so here we can use "map.lblk = curr_lblk"
directly.

> +		map.len = end_lblk - curr_lblk;
> +
> +		ret = ocfs2_assure_trans_credits(handle, credits);
> +		if (ret < 0) {
> +			mlog_errno(ret);
> +			break;
> +		}
> +

...

> +
> +static int ocfs2_dio_write_end_io(struct kiocb *iocb, ssize_t size,
> +			int error, unsigned int flags, int level)
> +{
> +	struct inode *inode = file_inode(iocb->ki_filp);
> +	loff_t offset = iocb->ki_pos;
> +	int ret = 0;
> +
> +	if (error)
> +		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld errno:%d",
> +				 (long long)size, error);
> +
> +	if (size && ((flags & IOMAP_DIO_UNWRITTEN) ||
> +		    (offset + size > i_size_read(inode)))) {

Is it safe to do i_size_read() in case async dio completion?

...


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs
  2026-07-27  6:18 ` [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs Heming Zhao
  2026-07-28  4:19   ` Christoph Hellwig
@ 2026-07-28  6:11   ` Joseph Qi
  1 sibling, 0 replies; 14+ messages in thread
From: Joseph Qi @ 2026-07-28  6:11 UTC (permalink / raw)
  To: Heming Zhao; +Cc: mark, jlbec, hch, ocfs2-devel, linux-kernel



On 7/27/26 2:18 PM, Heming Zhao wrote:
> Since the DIO path already migrated from buffer_head mode (by using
> a_ops->direct_IO method) to iomap mode, the VFS no longer sets
> FMODE_CAN_ODIRECT automatically, so ocfs2_file_open() sets it
> explicitly.
> 
> The now-unused blockdev get_block/end_io machinery
> (ocfs2_lock_get_block(), ocfs2_dio_wr_get_block(), ocfs2_dio_end_io(),
> ocfs2_dio_end_io_write(), and the ocfs2_dio_write_ctxt helpers) is removed,
> and OCFS2_FS Kconfig removes LEGACY_DIRECT_IO.
> 
> Co-developed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> Signed-off-by: Heming Zhao <heming.zhao@suse.com>
> ---
>  fs/ocfs2/Kconfig |   1 -
>  fs/ocfs2/aops.c  | 411 -----------------------------------------------
>  fs/ocfs2/aops.h  |  35 +---
>  fs/ocfs2/file.c  |  13 +-
>  fs/ocfs2/ocfs2.h |   5 -
>  5 files changed, 16 insertions(+), 449 deletions(-)
> 
> diff --git a/fs/ocfs2/Kconfig b/fs/ocfs2/Kconfig
> index bf1678a5eb01..74bc2008ef0b 100644
> --- a/fs/ocfs2/Kconfig
> +++ b/fs/ocfs2/Kconfig
> @@ -9,7 +9,6 @@ config OCFS2_FS
>  	select QUOTA_TREE
>  	select FS_IOMAP
>  	select FS_POSIX_ACL
> -	select LEGACY_DIRECT_IO
>  	help
>  	  OCFS2 is a general purpose extent based shared disk cluster file
>  	  system with many similarities to ext3. It supports 64 bit inode
> diff --git a/fs/ocfs2/aops.c b/fs/ocfs2/aops.c
> index 9a079436c9c0..3e4a7043074a 100644
> --- a/fs/ocfs2/aops.c
> +++ b/fs/ocfs2/aops.c
> @@ -114,19 +114,6 @@ static int ocfs2_symlink_get_block(struct inode *inode, sector_t iblock,
>  	return err;
>  }
>  
> -static int ocfs2_lock_get_block(struct inode *inode, sector_t iblock,
> -		    struct buffer_head *bh_result, int create)
> -{
> -	int ret = 0;
> -	struct ocfs2_inode_info *oi = OCFS2_I(inode);
> -
> -	down_read(&oi->ip_alloc_sem);
> -	ret = ocfs2_get_block(inode, iblock, bh_result, create);
> -	up_read(&oi->ip_alloc_sem);
> -
> -	return ret;
> -}
> -
>  int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
>  		    int flags)
>  {
> @@ -2170,204 +2157,6 @@ static int ocfs2_write_end(const struct kiocb *iocb,
>  	return ret;
>  }
>  
> -struct ocfs2_dio_write_ctxt {
> -	struct list_head	dw_zero_list;
> -	unsigned		dw_zero_count;
> -	int			dw_orphaned;
> -	pid_t			dw_writer_pid;
> -};
> -
> -static struct ocfs2_dio_write_ctxt *
> -ocfs2_dio_alloc_write_ctx(struct buffer_head *bh, int *alloc)
> -{
> -	struct ocfs2_dio_write_ctxt *dwc = NULL;
> -
> -	if (bh->b_private)
> -		return bh->b_private;
> -
> -	dwc = kmalloc_obj(struct ocfs2_dio_write_ctxt, GFP_NOFS);
> -	if (dwc == NULL)
> -		return NULL;
> -	INIT_LIST_HEAD(&dwc->dw_zero_list);
> -	dwc->dw_zero_count = 0;
> -	dwc->dw_orphaned = 0;
> -	dwc->dw_writer_pid = task_pid_nr(current);
> -	bh->b_private = dwc;
> -	*alloc = 1;
> -
> -	return dwc;
> -}
> -
> -static void ocfs2_dio_free_write_ctx(struct inode *inode,
> -				     struct ocfs2_dio_write_ctxt *dwc)
> -{
> -	ocfs2_free_unwritten_list(inode, &dwc->dw_zero_list);
> -	kfree(dwc);
> -}
> -
> -/*
> - * TODO: Make this into a generic get_blocks function.
> - *
> - * From do_direct_io in direct-io.c:
> - *  "So what we do is to permit the ->get_blocks function to populate
> - *   bh.b_size with the size of IO which is permitted at this offset and
> - *   this i_blkbits."
> - *
> - * This function is called directly from get_more_blocks in direct-io.c.
> - *
> - * called like this: dio->get_blocks(dio->inode, fs_startblk,
> - * 					fs_count, map_bh, dio->rw == WRITE);
> - */
> -static int ocfs2_dio_wr_get_block(struct inode *inode, sector_t iblock,
> -			       struct buffer_head *bh_result, int create)
> -{
> -	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
> -	struct ocfs2_inode_info *oi = OCFS2_I(inode);
> -	struct ocfs2_write_ctxt *wc;
> -	struct ocfs2_write_cluster_desc *desc = NULL;
> -	struct ocfs2_dio_write_ctxt *dwc = NULL;
> -	struct buffer_head *di_bh = NULL;
> -	u64 p_blkno = 0;
> -	unsigned int i_blkbits = inode->i_sb->s_blocksize_bits;
> -	loff_t pos = iblock << i_blkbits;
> -	sector_t endblk = (i_size_read(inode) - 1) >> i_blkbits;
> -	unsigned len, total_len = bh_result->b_size;
> -	int ret = 0, first_get_block = 0;
> -
> -	len = osb->s_clustersize - (pos & (osb->s_clustersize - 1));
> -	len = min(total_len, len);
> -
> -	/*
> -	 * bh_result->b_size is count in get_more_blocks according to write
> -	 * "pos" and "end", we need map twice to return different buffer state:
> -	 * 1. area in file size, not set NEW;
> -	 * 2. area out file size, set  NEW.
> -	 *
> -	 *		   iblock    endblk
> -	 * |--------|---------|---------|---------
> -	 * |<-------area in file------->|
> -	 */
> -
> -	if ((iblock <= endblk) &&
> -	    ((iblock + ((len - 1) >> i_blkbits)) > endblk))
> -		len = (endblk - iblock + 1) << i_blkbits;
> -
> -	mlog(0, "get block of %llu at %llu:%u req %u\n",
> -			inode->i_ino, pos, len, total_len);
> -
> -	/*
> -	 * Because we need to change file size in ocfs2_dio_end_io_write(), or
> -	 * we may need to add it to orphan dir. So can not fall to fast path
> -	 * while file size will be changed.
> -	 */
> -	if (pos + total_len <= i_size_read(inode)) {
> -
> -		/* This is the fast path for re-write. */
> -		ret = ocfs2_lock_get_block(inode, iblock, bh_result, create);
> -		if (buffer_mapped(bh_result) &&
> -		    !buffer_new(bh_result) &&
> -		    ret == 0)
> -			goto out;
> -
> -		/* Clear state set by ocfs2_get_block. */
> -		bh_result->b_state = 0;
> -	}
> -
> -	dwc = ocfs2_dio_alloc_write_ctx(bh_result, &first_get_block);
> -	if (unlikely(dwc == NULL)) {
> -		ret = -ENOMEM;
> -		mlog_errno(ret);
> -		goto out;
> -	}
> -
> -	if (ocfs2_clusters_for_bytes(inode->i_sb, pos + total_len) >
> -	    ocfs2_clusters_for_bytes(inode->i_sb, i_size_read(inode)) &&
> -	    !dwc->dw_orphaned) {
> -		/*
> -		 * when we are going to alloc extents beyond file size, add the
> -		 * inode to orphan dir, so we can recall those spaces when
> -		 * system crashed during write.
> -		 */
> -		ret = ocfs2_add_inode_to_orphan(osb, inode);
> -		if (ret < 0) {
> -			mlog_errno(ret);
> -			goto out;
> -		}
> -		dwc->dw_orphaned = 1;
> -	}
> -
> -	ret = ocfs2_inode_lock(inode, &di_bh, 1);
> -	if (ret) {
> -		mlog_errno(ret);
> -		goto out;
> -	}
> -
> -	down_write(&oi->ip_alloc_sem);
> -
> -	if (first_get_block) {
> -		if (ocfs2_sparse_alloc(osb))
> -			ret = ocfs2_zero_tail(inode, di_bh, pos);
> -		else
> -			ret = ocfs2_expand_nonsparse_inode(inode, di_bh, pos,
> -							   total_len, NULL);
> -		if (ret < 0) {
> -			mlog_errno(ret);
> -			goto unlock;
> -		}
> -	}
> -
> -	ret = ocfs2_write_begin_nolock(inode->i_mapping, pos, len,
> -				       OCFS2_WRITE_DIRECT, NULL,
> -				       (void **)&wc, di_bh, NULL);
> -	if (ret) {
> -		mlog_errno(ret);
> -		goto unlock;
> -	}
> -
> -	desc = &wc->w_desc[0];
> -
> -	p_blkno = ocfs2_clusters_to_blocks(inode->i_sb, desc->c_phys);
> -	BUG_ON(p_blkno == 0);
> -	p_blkno += iblock & (u64)(ocfs2_clusters_to_blocks(inode->i_sb, 1) - 1);
> -
> -	map_bh(bh_result, inode->i_sb, p_blkno);
> -	bh_result->b_size = len;
> -	if (desc->c_needs_zero)
> -		set_buffer_new(bh_result);
> -
> -	if (iblock > endblk)
> -		set_buffer_new(bh_result);
> -
> -	/* May sleep in end_io. It should not happen in a irq context. So defer
> -	 * it to dio work queue. */
> -	set_buffer_defer_completion(bh_result);
> -
> -	if (!list_empty(&wc->w_unwritten_list)) {
> -		struct ocfs2_unwritten_extent *ue = NULL;
> -
> -		ue = list_first_entry(&wc->w_unwritten_list,
> -				      struct ocfs2_unwritten_extent,
> -				      ue_node);
> -		BUG_ON(ue->ue_cpos != desc->c_cpos);
> -		/* The physical address may be 0, fill it. */
> -		ue->ue_phys = desc->c_phys;
> -
> -		list_splice_tail_init(&wc->w_unwritten_list, &dwc->dw_zero_list);
> -		dwc->dw_zero_count += wc->w_unwritten_count;
> -	}
> -
> -	ret = ocfs2_write_end_nolock(inode->i_mapping, pos, len, len, wc);
> -	BUG_ON(ret != len);
> -	ret = 0;
> -unlock:
> -	up_write(&oi->ip_alloc_sem);
> -	ocfs2_inode_unlock(inode, 1);
> -	brelse(di_bh);
> -out:
> -	return ret;
> -}
> -
> -/* copy from ocfs2_dio_wr_get_block */
>  static int ocfs2_dio_wr_map_blocks(struct inode *inode,
>  			       struct ocfs2_map_block *map, int create)
>  {
> @@ -2509,205 +2298,6 @@ static int ocfs2_dio_wr_map_blocks(struct inode *inode,
>  	return ret;
>  }
>  
> -static int ocfs2_dio_end_io_write(struct inode *inode,
> -				  struct ocfs2_dio_write_ctxt *dwc,
> -				  loff_t offset,
> -				  ssize_t bytes)
> -{
> -	struct ocfs2_cached_dealloc_ctxt dealloc;
> -	struct ocfs2_extent_tree et;
> -	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
> -	struct ocfs2_inode_info *oi = OCFS2_I(inode);
> -	struct ocfs2_unwritten_extent *ue = NULL;
> -	struct buffer_head *di_bh = NULL;
> -	struct ocfs2_dinode *di;
> -	struct ocfs2_alloc_context *data_ac = NULL;
> -	struct ocfs2_alloc_context *meta_ac = NULL;
> -	handle_t *handle = NULL;
> -	loff_t end = offset + bytes;
> -	int ret = 0, credits = 0, batch = 0;
> -
> -	ocfs2_init_dealloc_ctxt(&dealloc);
> -
> -	/* We do clear unwritten, delete orphan, change i_size here. If neither
> -	 * of these happen, we can skip all this. */
> -	if (list_empty(&dwc->dw_zero_list) &&
> -	    end <= i_size_read(inode) &&
> -	    !dwc->dw_orphaned)
> -		goto out;
> -
> -	ret = ocfs2_inode_lock(inode, &di_bh, 1);
> -	if (ret < 0) {
> -		mlog_errno(ret);
> -		goto out;
> -	}
> -
> -	down_write(&oi->ip_alloc_sem);
> -	di = (struct ocfs2_dinode *)di_bh->b_data;
> -
> -	ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode), di_bh);
> -
> -	/* Attach dealloc with extent tree in case that we may reuse extents
> -	 * which are already unlinked from current extent tree due to extent
> -	 * rotation and merging.
> -	 */
> -	et.et_dealloc = &dealloc;
> -
> -	ret = ocfs2_lock_allocators(inode, &et, 0, dwc->dw_zero_count*2,
> -				    &data_ac, &meta_ac);
> -	if (ret) {
> -		mlog_errno(ret);
> -		goto unlock;
> -	}
> -
> -	credits = ocfs2_calc_extend_credits(inode->i_sb, &di->id2.i_list);
> -
> -	list_for_each_entry(ue, &dwc->dw_zero_list, ue_node) {
> -		if (!handle) {
> -			handle = ocfs2_start_trans(osb, credits);
> -			if (IS_ERR(handle)) {
> -				ret = PTR_ERR(handle);
> -				mlog_errno(ret);
> -				goto unlock;
> -			}
> -			ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), di_bh,
> -					OCFS2_JOURNAL_ACCESS_WRITE);
> -			if (ret) {
> -				mlog_errno(ret);
> -				goto commit;
> -			}
> -		}
> -		ret = ocfs2_assure_trans_credits(handle, credits);
> -		if (ret < 0) {
> -			mlog_errno(ret);
> -			goto commit;
> -		}
> -		ret = ocfs2_mark_extent_written(inode, &et, handle,
> -						ue->ue_cpos, 1,
> -						ue->ue_phys,
> -						meta_ac, &dealloc);
> -		if (ret < 0) {
> -			mlog_errno(ret);
> -			goto commit;
> -		}
> -
> -		if (++batch == OCFS2_DIO_MARK_EXTENT_BATCH) {
> -			ocfs2_commit_trans(osb, handle);
> -			handle = NULL;
> -			batch = 0;
> -		}
> -	}
> -
> -	if (end > i_size_read(inode)) {
> -		if (!handle) {
> -			handle = ocfs2_start_trans(osb, credits);
> -			if (IS_ERR(handle)) {
> -				ret = PTR_ERR(handle);
> -				mlog_errno(ret);
> -				goto unlock;
> -			}
> -		}
> -		ret = ocfs2_set_inode_size(handle, inode, di_bh, end);
> -		if (ret < 0)
> -			mlog_errno(ret);
> -	}
> -
> -commit:
> -	if (handle)
> -		ocfs2_commit_trans(osb, handle);
> -unlock:
> -	up_write(&oi->ip_alloc_sem);
> -
> -	if (data_ac) {
> -		ocfs2_free_alloc_context(data_ac);
> -		data_ac = NULL;
> -	}
> -	if (meta_ac) {
> -		ocfs2_free_alloc_context(meta_ac);
> -		meta_ac = NULL;
> -	}
> -
> -	/* everything looks good, let's start the cleanup */
> -	if (!ret && dwc->dw_orphaned) {
> -		BUG_ON(dwc->dw_writer_pid != task_pid_nr(current));
> -
> -		ret = ocfs2_del_inode_from_orphan(osb, inode, di_bh, 0, 0);
> -		if (ret < 0)
> -			mlog_errno(ret);
> -	}
> -	ocfs2_inode_unlock(inode, 1);
> -	brelse(di_bh);
> -out:
> -	ocfs2_run_deallocs(osb, &dealloc);
> -	ocfs2_dio_free_write_ctx(inode, dwc);
> -
> -	return ret;
> -}
> -
> -/*
> - * ocfs2_dio_end_io is called by the dio core when a dio is finished.  We're
> - * particularly interested in the aio/dio case.  We use the rw_lock DLM lock
> - * to protect io on one node from truncation on another.
> - */
> -static int ocfs2_dio_end_io(struct kiocb *iocb,
> -			    loff_t offset,
> -			    ssize_t bytes,
> -			    void *private)
> -{
> -	struct inode *inode = file_inode(iocb->ki_filp);
> -	int level;
> -	int ret = 0;
> -
> -	/* this io's submitter should not have unlocked this before we could */
> -	BUG_ON(!ocfs2_iocb_is_rw_locked(iocb));
> -
> -	if (bytes <= 0)
> -		mlog_ratelimited(ML_ERROR, "Direct IO failed, bytes = %lld",
> -				 (long long)bytes);
> -	if (private) {
> -		if (bytes > 0)
> -			ret = ocfs2_dio_end_io_write(inode, private, offset,
> -						     bytes);
> -		else
> -			ocfs2_dio_free_write_ctx(inode, private);
> -	}
> -
> -	ocfs2_iocb_clear_rw_locked(iocb);
> -
> -	level = ocfs2_iocb_rw_locked_level(iocb);
> -	ocfs2_rw_unlock(inode, level);
> -	return ret;
> -}
> -
> -static ssize_t ocfs2_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
> -{
> -	struct file *file = iocb->ki_filp;
> -	struct inode *inode = file->f_mapping->host;
> -	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
> -	get_block_t *get_block;
> -
> -	/*
> -	 * Fallback to buffered I/O if we see an inode without
> -	 * extents.
> -	 */
> -	if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL)
> -		return 0;
> -
> -	/* Fallback to buffered I/O if we do not support append dio. */
> -	if (iocb->ki_pos + iter->count > i_size_read(inode) &&
> -	    !ocfs2_supports_append_dio(osb))
> -		return 0;
> -
> -	if (iov_iter_rw(iter) == READ)
> -		get_block = ocfs2_lock_get_block;
> -	else
> -		get_block = ocfs2_dio_wr_get_block;
> -
> -	return __blockdev_direct_IO(iocb, inode, inode->i_sb->s_bdev,
> -				    iter, get_block,
> -				    ocfs2_dio_end_io, 0);
> -}
> -
>  static int ocfs2_iomap_alloc(struct inode *inode, struct ocfs2_map_block *map,
>  			    unsigned int flags)
>  {
> @@ -3045,7 +2635,6 @@ const struct address_space_operations ocfs2_aops = {
>  	.write_begin		= ocfs2_write_begin,
>  	.write_end		= ocfs2_write_end,
>  	.bmap			= ocfs2_bmap,
> -	.direct_IO		= ocfs2_direct_IO,
>  	.invalidate_folio	= block_invalidate_folio,
>  	.release_folio		= ocfs2_release_folio,
>  	.migrate_folio		= buffer_migrate_folio,
> diff --git a/fs/ocfs2/aops.h b/fs/ocfs2/aops.h
> index 8dd6edd7c1a1..484b25d749d7 100644
> --- a/fs/ocfs2/aops.h
> +++ b/fs/ocfs2/aops.h
> @@ -7,6 +7,12 @@
>  #define OCFS2_AOPS_H
>  
>  #include <linux/fs.h>
> +#include <linux/iomap.h>
> +
> +extern const struct iomap_ops ocfs2_iomap_ops;
> +extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
> +extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr;
> +extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex;
>  
>  int ocfs2_map_folio_blocks(struct folio *folio, u64 *p_blkno,
>  			  struct inode *inode, unsigned int from,
> @@ -44,34 +50,5 @@ int ocfs2_get_block(struct inode *inode, sector_t iblock,
>  		    struct buffer_head *bh_result, int create);
>  int ocfs2_map_blocks(struct inode *inode, struct ocfs2_map_block *map,
>  		    int flags);
> -/* all ocfs2_dio_end_io()'s fault */
> -#define ocfs2_iocb_is_rw_locked(iocb) \
> -	test_bit(0, (unsigned long *)&iocb->private)
> -static inline void ocfs2_iocb_set_rw_locked(struct kiocb *iocb, int level)
> -{
> -	set_bit(0, (unsigned long *)&iocb->private);
> -	if (level)
> -		set_bit(1, (unsigned long *)&iocb->private);
> -	else
> -		clear_bit(1, (unsigned long *)&iocb->private);
> -}
> -
> -/*
> - * Using a named enum representing lock types in terms of #N bit stored in
> - * iocb->private, which is going to be used for communication between
> - * ocfs2_dio_end_io() and ocfs2_file_write/read_iter().
> - */
> -enum ocfs2_iocb_lock_bits {
> -	OCFS2_IOCB_RW_LOCK = 0,
> -	OCFS2_IOCB_RW_LOCK_LEVEL,
> -	OCFS2_IOCB_NUM_LOCKS
> -};
> -
> -#define ocfs2_iocb_init_rw_locked(iocb) \
> -	(iocb->private = NULL)
> -#define ocfs2_iocb_clear_rw_locked(iocb) \
> -	clear_bit(OCFS2_IOCB_RW_LOCK, (unsigned long *)&iocb->private)
> -#define ocfs2_iocb_rw_locked_level(iocb) \
> -	test_bit(OCFS2_IOCB_RW_LOCK_LEVEL, (unsigned long *)&iocb->private)
>  
>  #endif /* OCFS2_FILE_H */
> diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
> index 2d78f2863acf..a54f92633582 100644
> --- a/fs/ocfs2/file.c
> +++ b/fs/ocfs2/file.c
> @@ -92,6 +92,13 @@ static int ocfs2_file_open(struct inode *inode, struct file *file)
>  			      file->f_path.dentry->d_name.len,
>  			      file->f_path.dentry->d_name.name, mode);
>  
> +	/*
> +	 * Direct I/O is served through iomap_dio_rw() from
> +	 * ocfs2_file_{read,write}_iter() rather than an a_ops->direct_IO
> +	 * method, so advertise O_DIRECT capability explicitly here.
> +	 */
> +	file->f_mode |= FMODE_CAN_ODIRECT;

This unconditionally set may include inodes with inline data.
Though it will handle the fallback in ocfs2_should_use_dio(), it looks odd
and slightly misleading to userspace. 

> +
>  	if (file->f_mode & FMODE_WRITE) {
>  		status = dquot_initialize(inode);
>  		if (status)
> @@ -1171,9 +1178,9 @@ int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
>  	size_change = S_ISREG(inode->i_mode) && attr->ia_valid & ATTR_SIZE;
>  	if (size_change) {
>  		/*
> -		 * Here we should wait dio to finish before inode lock
> -		 * to avoid a deadlock between ocfs2_setattr() and
> -		 * ocfs2_dio_end_io_write()
> +		 * Here we should wait for in-flight direct I/O to finish
> +		 * before taking the inode lock, to avoid a deadlock between
> +		 * ocfs2_setattr() and direct I/O completion.
>  		 */
>  		inode_dio_wait(inode);
>  
> diff --git a/fs/ocfs2/ocfs2.h b/fs/ocfs2/ocfs2.h
> index b228b10b5e71..095f7ae5dded 100644
> --- a/fs/ocfs2/ocfs2.h
> +++ b/fs/ocfs2/ocfs2.h
> @@ -549,11 +549,6 @@ struct ocfs2_map_block {
>  	unsigned int flags;
>  };
>  
> -extern const struct iomap_ops ocfs2_iomap_ops;
> -extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_r_pr;
> -extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_pr;
> -extern const struct iomap_dio_ops ocfs2_iomap_dio_ops_w_ex;
> -
>  /* Flags used by ocfs2_map_blocks() */
>  #define OCFS2_GET_BLOCKS_CREATE	(0x0001)
>  


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2026-07-28  6:11 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27  6:17 [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Heming Zhao
2026-07-27  6:17 ` [RFC PATCH v2 1/4] ocfs2: Add new ocfs2_map_blocks() to introduce iomap feature Heming Zhao
2026-07-28  5:41   ` Joseph Qi
2026-07-27  6:17 ` [RFC PATCH v2 2/4] ocfs2: switch dio read path from buffer_head to iomap Heming Zhao
2026-07-28  4:16   ` Christoph Hellwig
2026-07-28  5:50   ` Joseph Qi
2026-07-27  6:17 ` [RFC PATCH v2 3/4] ocfs2: switch dio write " Heming Zhao
2026-07-28  4:18   ` Christoph Hellwig
2026-07-28  6:07   ` Joseph Qi
2026-07-27  6:18 ` [RFC PATCH v2 4/4] ocfs2: remove legacy blockdev direct-IO path APIs Heming Zhao
2026-07-28  4:19   ` Christoph Hellwig
2026-07-28  4:25     ` Heming Zhao
2026-07-28  6:11   ` Joseph Qi
2026-07-28  4:11 ` [RFC PATCH v2 0/4] migrates ocfs2 DIO from buffer_head to iomap Christoph Hellwig

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox