Linux userland API discussions
 help / color / mirror / Atom feed
* [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
@ 2026-10-12 18:46 Stefan Metzmacher
  2026-10-21 11:04 ` Christian Brauner
  0 siblings, 1 reply; 8+ messages in thread
From: Stefan Metzmacher @ 2026-10-12 18:46 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: metze, Alexander Viro, Christian Brauner, Jan Kara, io-uring,
	linux-api

It's useful to control this per io request instead of using O_DIRECT
for the file descriptor.

E.g. the SMB2_READFLAG_READ_UNBUFFERED and SMB2_WRITEFLAG_WRITE_UNBUFFERED
are also available per io request and this allows Samba to map then
to RWF_DIRECT.

kiocb_set_rw_flags() requires FMODE_CAN_ODIRECT (same check open()
does for O_DIRECT) and returns -EOPNOTSUPP otherwise, matching the
convention already used by RWF_NOWAIT/RWF_ATOMIC/RWF_DONTCACHE just
above it in the same function for "this flag isn't supported on this file".

IOCB_DIRECT predates RWF_DIRECT used a kernel-internal value,
but now IOCB_DIRECT is aligned with RWF_DIRECT the same way
commit ce71bfea207b ("fs: align IOCB_* flags with RWF_* flags")
already aligned the other shared flags.

The man-page.git will get something like this for man/man2/readv.2:

  RWF_DIRECT (since Linux XXX)
    Provide  a  per-I/O equivalent of the O_DIRECT open(2) flag.
    This allows a single preadv2() or pwritev2() call to bypass the
    page cache without the file descriptor having been opened with
    O_DIRECT.  Unlike enabling O_DIRECT with the fcntl(2) F_SETFL
    operation, this does not change the file status flags of
    the open file description, so other I/O on the same file
    descriptor, or on any file descriptor referring to the same
    open file description (see dup(2)), is not affected.
    If the underlying file or block device does not support
    O_DIRECT, the call fails; see ERRORS.  As with O_DIRECT open(2),
    the memory buffer, the file offset, and the I/O length
    generally need to be aligned to the logical block size of
    the underlying storage.

man/man2/io_submit.2 will also get updated and liburing
in man/io_uring_prep_readv2.3 and man/io_uring_prep_writev2.3

Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jan Kara <jack@suse.cz>
Cc: linux-fsdevel@vger.kernel.org
Cc: io-uring@vger.kernel.org
Cc: linux-api@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
 include/linux/fs.h      | 10 ++++++++--
 include/uapi/linux/fs.h |  5 ++++-
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/include/linux/fs.h b/include/linux/fs.h
index 83d41d9f1130..40d013a49378 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -344,10 +344,11 @@ struct readahead_control;
 #define IOCB_ATOMIC		(__force int) RWF_ATOMIC
 #define IOCB_DONTCACHE		(__force int) RWF_DONTCACHE
 #define IOCB_NOSIGNAL		(__force int) RWF_NOSIGNAL
+#define IOCB_DIRECT		(__force int) RWF_DIRECT
 
 /* non-RWF related bits - start at 16 */
+#define IOCB_NON_RWF_MASK	(~0xffff)
 #define IOCB_EVENTFD		(1 << 16)
-#define IOCB_DIRECT		(1 << 17)
 #define IOCB_WRITE		(1 << 18)
 /* iocb->ki_waitq is valid */
 #define IOCB_WAITQ		(1 << 19)
@@ -3453,7 +3454,7 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,
 	int kiocb_flags = 0;
 
 	/* make sure there's no overlap between RWF and private IOCB flags */
-	BUILD_BUG_ON((__force int) RWF_SUPPORTED & IOCB_EVENTFD);
+	BUILD_BUG_ON((__force int) RWF_SUPPORTED & IOCB_NON_RWF_MASK);
 
 	if (!flags)
 		return 0;
@@ -3480,6 +3481,11 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,
 		if (IS_DAX(ki->ki_filp->f_mapping->host))
 			return -EOPNOTSUPP;
 	}
+	if (flags & RWF_DIRECT) {
+		/* file system must support it, same as O_DIRECT */
+		if (!(ki->ki_filp->f_mode & FMODE_CAN_ODIRECT))
+			return -EOPNOTSUPP;
+	}
 	kiocb_flags |= (__force int) (flags & RWF_SUPPORTED);
 	if (flags & RWF_SYNC)
 		kiocb_flags |= IOCB_DSYNC;
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index a46c33692aa2..3aa5fd73d391 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -451,10 +451,13 @@ typedef int __bitwise __kernel_rwf_t;
 /* prevent pipe and socket writes from raising SIGPIPE */
 #define RWF_NOSIGNAL	((__force __kernel_rwf_t)0x00000100)
 
+/* per-IO O_DIRECT */
+#define RWF_DIRECT	((__force __kernel_rwf_t)0x00000200)
+
 /* mask of flags supported by the kernel */
 #define RWF_SUPPORTED	(RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\
 			 RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
-			 RWF_DONTCACHE | RWF_NOSIGNAL)
+			 RWF_DONTCACHE | RWF_NOSIGNAL | RWF_DIRECT)
 
 #define PROCFS_IOCTL_MAGIC 'f'
 
-- 
2.43.0


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

* Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-10-12 18:46 [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io Stefan Metzmacher
@ 2026-10-21 11:04 ` Christian Brauner
  2026-11-03  8:07   ` Christoph Hellwig
  0 siblings, 1 reply; 8+ messages in thread
From: Christian Brauner @ 2026-10-21 11:04 UTC (permalink / raw)
  To: Stefan Metzmacher, Christoph Hellwig
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, io-uring, linux-api

On Mon, Oct 12, 2026 at 08:46:42PM +0200, Stefan Metzmacher wrote:
> It's useful to control this per io request instead of using O_DIRECT
> for the file descriptor.

@Christoph thoughts on this?

> E.g. the SMB2_READFLAG_READ_UNBUFFERED and SMB2_WRITEFLAG_WRITE_UNBUFFERED
> are also available per io request and this allows Samba to map then
> to RWF_DIRECT.
> 
> kiocb_set_rw_flags() requires FMODE_CAN_ODIRECT (same check open()
> does for O_DIRECT) and returns -EOPNOTSUPP otherwise, matching the
> convention already used by RWF_NOWAIT/RWF_ATOMIC/RWF_DONTCACHE just
> above it in the same function for "this flag isn't supported on this file".
> 
> IOCB_DIRECT predates RWF_DIRECT used a kernel-internal value,
> but now IOCB_DIRECT is aligned with RWF_DIRECT the same way
> commit ce71bfea207b ("fs: align IOCB_* flags with RWF_* flags")
> already aligned the other shared flags.
> 
> The man-page.git will get something like this for man/man2/readv.2:
> 
>   RWF_DIRECT (since Linux XXX)
>     Provide  a  per-I/O equivalent of the O_DIRECT open(2) flag.
>     This allows a single preadv2() or pwritev2() call to bypass the
>     page cache without the file descriptor having been opened with
>     O_DIRECT.  Unlike enabling O_DIRECT with the fcntl(2) F_SETFL
>     operation, this does not change the file status flags of
>     the open file description, so other I/O on the same file
>     descriptor, or on any file descriptor referring to the same
>     open file description (see dup(2)), is not affected.
>     If the underlying file or block device does not support
>     O_DIRECT, the call fails; see ERRORS.  As with O_DIRECT open(2),
>     the memory buffer, the file offset, and the I/O length
>     generally need to be aligned to the logical block size of
>     the underlying storage.
> 
> man/man2/io_submit.2 will also get updated and liburing
> in man/io_uring_prep_readv2.3 and man/io_uring_prep_writev2.3
> 
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: Christian Brauner <brauner@kernel.org>
> Cc: Jan Kara <jack@suse.cz>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: io-uring@vger.kernel.org
> Cc: linux-api@vger.kernel.org
> Signed-off-by: Stefan Metzmacher <metze@samba.org>
> ---
>  include/linux/fs.h      | 10 ++++++++--
>  include/uapi/linux/fs.h |  5 ++++-
>  2 files changed, 12 insertions(+), 3 deletions(-)
> 
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 83d41d9f1130..40d013a49378 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -344,10 +344,11 @@ struct readahead_control;
>  #define IOCB_ATOMIC		(__force int) RWF_ATOMIC
>  #define IOCB_DONTCACHE		(__force int) RWF_DONTCACHE
>  #define IOCB_NOSIGNAL		(__force int) RWF_NOSIGNAL
> +#define IOCB_DIRECT		(__force int) RWF_DIRECT
>  
>  /* non-RWF related bits - start at 16 */
> +#define IOCB_NON_RWF_MASK	(~0xffff)
>  #define IOCB_EVENTFD		(1 << 16)
> -#define IOCB_DIRECT		(1 << 17)
>  #define IOCB_WRITE		(1 << 18)
>  /* iocb->ki_waitq is valid */
>  #define IOCB_WAITQ		(1 << 19)
> @@ -3453,7 +3454,7 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,
>  	int kiocb_flags = 0;
>  
>  	/* make sure there's no overlap between RWF and private IOCB flags */
> -	BUILD_BUG_ON((__force int) RWF_SUPPORTED & IOCB_EVENTFD);
> +	BUILD_BUG_ON((__force int) RWF_SUPPORTED & IOCB_NON_RWF_MASK);
>  
>  	if (!flags)
>  		return 0;
> @@ -3480,6 +3481,11 @@ static inline int kiocb_set_rw_flags(struct kiocb *ki, rwf_t flags,
>  		if (IS_DAX(ki->ki_filp->f_mapping->host))
>  			return -EOPNOTSUPP;
>  	}
> +	if (flags & RWF_DIRECT) {
> +		/* file system must support it, same as O_DIRECT */
> +		if (!(ki->ki_filp->f_mode & FMODE_CAN_ODIRECT))
> +			return -EOPNOTSUPP;
> +	}
>  	kiocb_flags |= (__force int) (flags & RWF_SUPPORTED);
>  	if (flags & RWF_SYNC)
>  		kiocb_flags |= IOCB_DSYNC;
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index a46c33692aa2..3aa5fd73d391 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -451,10 +451,13 @@ typedef int __bitwise __kernel_rwf_t;
>  /* prevent pipe and socket writes from raising SIGPIPE */
>  #define RWF_NOSIGNAL	((__force __kernel_rwf_t)0x00000100)
>  
> +/* per-IO O_DIRECT */
> +#define RWF_DIRECT	((__force __kernel_rwf_t)0x00000200)
> +
>  /* mask of flags supported by the kernel */
>  #define RWF_SUPPORTED	(RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\
>  			 RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
> -			 RWF_DONTCACHE | RWF_NOSIGNAL)
> +			 RWF_DONTCACHE | RWF_NOSIGNAL | RWF_DIRECT)
>  
>  #define PROCFS_IOCTL_MAGIC 'f'
>  
> -- 
> 2.43.0
> 

-- 

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

* Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-10-21 11:04 ` Christian Brauner
@ 2026-11-03  8:07   ` Christoph Hellwig
  2026-11-03 13:06     ` Stefan Metzmacher
                       ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Christoph Hellwig @ 2026-11-03  8:07 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Stefan Metzmacher, Christoph Hellwig, linux-fsdevel,
	Alexander Viro, Jan Kara, io-uring, linux-api

On Wed, Oct 21, 2026 at 01:04:06PM +0200, Christian Brauner wrote:
> On Mon, Oct 12, 2026 at 08:46:42PM +0200, Stefan Metzmacher wrote:
> > It's useful to control this per io request instead of using O_DIRECT
> > for the file descriptor.
> 
> @Christoph thoughts on this?

Puh.  This goes right into a big discussion right now.  We've
traditionally said mixing buffered and direct I/O is madness, and
that's certainly not untrue.  On the other hand people find this
useful - nfsd is now making heavy use of it, and a few year ago
there was a paper on how the lustre backend really gained by doing
that.  So while some folks like Dave Chinner still get really angry
at the idea of mixing the modes, I feel the ship has sailed and we
need to make it work as good as possible.  For some cases like
mmap'ed I/O it will always introduce data corruption, but we'd at
least make sure there's nothing exploitable.  And the direct user
exposed flag would open lots of these exploit vectors.

There's also issues where file systems still check O_DIRECT for
behavior and not IOCB_DIRECT, and this would increase the problems
with that compared to the existing in-kernel only users.
NFS for example has quite a few O_DIRECT checks, and some of them
feel wrong for this.


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

* Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-11-03  8:07   ` Christoph Hellwig
@ 2026-11-03 13:06     ` Stefan Metzmacher
  2026-11-03 13:51     ` Paulo Alcantara
  2026-11-03 19:13     ` Andy Lutomirski
  2 siblings, 0 replies; 8+ messages in thread
From: Stefan Metzmacher @ 2026-11-03 13:06 UTC (permalink / raw)
  To: Christoph Hellwig, Christian Brauner
  Cc: linux-fsdevel, Alexander Viro, Jan Kara, io-uring, linux-api

Am 03.11.26 um 09:07 schrieb Christoph Hellwig:
> On Wed, Oct 21, 2026 at 01:04:06PM +0200, Christian Brauner wrote:
>> On Mon, Oct 12, 2026 at 08:46:42PM +0200, Stefan Metzmacher wrote:
>>> It's useful to control this per io request instead of using O_DIRECT
>>> for the file descriptor.
>>
>> @Christoph thoughts on this?
> 
> Puh.  This goes right into a big discussion right now.  We've
> traditionally said mixing buffered and direct I/O is madness, and
> that's certainly not untrue.  On the other hand people find this
> useful - nfsd is now making heavy use of it, and a few year ago
> there was a paper on how the lustre backend really gained by doing
> that.  So while some folks like Dave Chinner still get really angry
> at the idea of mixing the modes, I feel the ship has sailed and we
> need to make it work as good as possible.  For some cases like
> mmap'ed I/O it will always introduce data corruption, but we'd at
> least make sure there's nothing exploitable.  And the direct user
> exposed flag would open lots of these exploit vectors.
Userspace can already mix the modes on the same file today, by
opening it twice, once with and once without O_DIRECT, or even on a
single struct file with fcntl(F_SETFL, O_DIRECT). The patch requires
FMODE_CAN_ODIRECT in kiocb_set_rw_flags(), the same check open() and
fcntl() do for O_DIRECT, so it doesn't allow direct I/O on files
that couldn't do it before.

The use case is Samba: SMB3 clients request unbuffered I/O per
request (SMB2_READFLAG_READ_UNBUFFERED and
SMB2_WRITEFLAG_WRITE_UNBUFFERED). Samba wants to use a strategy
similar to NFSD_IO_DIRECT in nfsd: for reads the range is aligned
down and up to the STATX_DIOALIGN values and read with RWF_DIRECT,
for writes the aligned middle is written with RWF_DIRECT and the
unaligned head and tail (if any) with RWF_DONTCACHE (or plain
buffered I/O if the file system doesn't support it). Without
RWF_DIRECT Samba would have to keep a second O_DIRECT file
descriptor for every open, which only hides the mixing from the
kernel.

> There's also issues where file systems still check O_DIRECT for
> behavior and not IOCB_DIRECT, and this would increase the problems
> with that compared to the existing in-kernel only users.
> NFS for example has quite a few O_DIRECT checks, and some of them
> feel wrong for this.

Right, that's the real difference: with two opens or F_SETFL the
O_DIRECT in file->f_flags always matches IOCB_DIRECT, with RWF_DIRECT
it doesn't, and FMODE_CAN_ODIRECT alone doesn't tell whether a file
system copes with that. For some file systems FMODE_CAN_ODIRECT is
still only derived from ->direct_IO = noop_direct_IO in
do_dentry_open(), so it just means that O_DIRECT can be opened.
cifs.ko is an example: with cache=loose cifs_file_write_iter()
decides by O_DIRECT, so an RWF_DIRECT write would be written
unbuffered by netfs, but without the cache invalidation and oplock
reset cifs does for O_DIRECT.

So what about making it opt-in as a start, like RWF_DONTCACHE with
FOP_DONTCACHE? A new FOP_DIRECT_PER_IO would be set by file systems
that base their I/O decisions on IOCB_DIRECT, all others return
-EOPNOTSUPP for RWF_DIRECT. Once all file systems that set
FMODE_CAN_ODIRECT also set FOP_DIRECT_PER_IO, the flag could be
removed again and RWF_DIRECT would just depend on FMODE_CAN_ODIRECT.

I looked at the local file systems Samba typically exports in
v7.4-rc1: xfs, ext4, btrfs and f2fs (and also ext2, exfat, gfs2,
nilfs2, zonefs, ntfs and ceph) decide per I/O based on
iocb->ki_flags & IOCB_DIRECT, falling back to buffered I/O per
request where direct I/O isn't possible (e.g. ext4 for encrypted,
verity or inline data files, nilfs2 for writes) or failing it with
-EOPNOTSUPP (f2fs for atomic write files, ntfs for compressed or
encrypted files). O_DIRECT in file->f_flags is only used for
checks at open time (or the start of an f2fs atomic write), so they
could set FOP_DIRECT_PER_IO right away.

Others like cifs, nfs, 9p, overlayfs and the other backing file
users, ntfs3, erofs, ocfs2 and fuse have O_DIRECT dependencies in
their open or I/O paths, they just wouldn't set FOP_DIRECT_PER_IO
for now. Fixing them is independent of RWF_DIRECT.

Should I send a v2 that adds FOP_DIRECT_PER_IO and sets it for the
file systems listed above?

metze

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

* Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-11-03  8:07   ` Christoph Hellwig
  2026-11-03 13:06     ` Stefan Metzmacher
@ 2026-11-03 13:51     ` Paulo Alcantara
  2026-11-03 19:13     ` Andy Lutomirski
  2 siblings, 0 replies; 8+ messages in thread
From: Paulo Alcantara @ 2026-11-03 13:51 UTC (permalink / raw)
  To: Christoph Hellwig, Christian Brauner
  Cc: Stefan Metzmacher, Christoph Hellwig, linux-fsdevel,
	Alexander Viro, Jan Kara, io-uring, linux-api

Christoph Hellwig <hch@lst.de> writes:

> On Wed, Oct 21, 2026 at 01:04:06PM +0200, Christian Brauner wrote:
>> On Mon, Oct 12, 2026 at 08:46:42PM +0200, Stefan Metzmacher wrote:
>> > It's useful to control this per io request instead of using O_DIRECT
>> > for the file descriptor.
>> 
>> @Christoph thoughts on this?
>
> Puh.  This goes right into a big discussion right now.  We've
> traditionally said mixing buffered and direct I/O is madness, and
> that's certainly not untrue.  On the other hand people find this
> useful - nfsd is now making heavy use of it, and a few year ago
> there was a paper on how the lustre backend really gained by doing
> that.  So while some folks like Dave Chinner still get really angry
> at the idea of mixing the modes, I feel the ship has sailed and we
> need to make it work as good as possible.  For some cases like
> mmap'ed I/O it will always introduce data corruption, but we'd at
> least make sure there's nothing exploitable.  And the direct user
> exposed flag would open lots of these exploit vectors.

Yeah.  We also have xfstests that do mmap of O_DIRECT file descriptors,
so mixing buffered and DIO to same file.

Those particular tests fail against Windows Server due to unaligned IO.
The CIFS client ends up using an handle opened for unbuffered IO in
writeback, so the server will reject any unaligned IO to it.

It works against Samba, however, which doesn't enforce any alignment.

This could be fixed by using the per-io SMB flags that metze mentioned,
but unfortunately the client still can't use it as some servers like
Azure don't support them and will fail the requests rather than ignoring
the unsupported flags.

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

* Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-11-03  8:07   ` Christoph Hellwig
  2026-11-03 13:06     ` Stefan Metzmacher
  2026-11-03 13:51     ` Paulo Alcantara
@ 2026-11-03 19:13     ` Andy Lutomirski
  2026-11-04  7:30       ` direct I/O vs buffered I/O coherency, was: " Christoph Hellwig
  2 siblings, 1 reply; 8+ messages in thread
From: Andy Lutomirski @ 2026-11-03 19:13 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Christian Brauner, Stefan Metzmacher, linux-fsdevel,
	Alexander Viro, Jan Kara, io-uring, linux-api

On Tue, Nov 3, 2026 at 12:11 AM Christoph Hellwig <hch@lst.de> wrote:
>
> On Wed, Oct 21, 2026 at 01:04:06PM +0200, Christian Brauner wrote:
> > On Mon, Oct 12, 2026 at 08:46:42PM +0200, Stefan Metzmacher wrote:
> > > It's useful to control this per io request instead of using O_DIRECT
> > > for the file descriptor.
> >
> > @Christoph thoughts on this?
>

> For some cases like
> mmap'ed I/O it will always introduce data corruption, but we'd at
> least make sure there's nothing exploitable.  And the direct user
> exposed flag would open lots of these exploit vectors.

I feel like we've set the bar quite low here, and I wish the bar were
higher.  Why does mmap need become incoherent when someone does an
O_DIRECT write?

Brain dump follows.

More generally, I've been contemplating page cache coherence for a few
days (unrelated to this), and I have not convinced myself that there
is any sort of principled concept of coherence that Linux implements,
and the closest I can come to being confident that nothing is
seriously broken is that I've only been able to find one seriously
broken thing recently (now fixed), and, worse I don't even have a
clean definition of what would constitute "seriously broken".  (Which
means that I can't, say, task an LLM with finding serious breaks or a
moderately compelling proof that there aren't any.)

Here's a bit of a taxonomy of page cache access types that I've come
up with from poking around at the kernel source [0]:

a) Plain buffered I/O (pread, pwrite, etc) -- I'm sure there is a lot
of nuance here, but I'm generally referring to I/O, via pagecache,
done in syscall or io_uring context on behalf of a user request.

b) Access via MAP_SHARED mmap or via unbroken-cow MAP_PRIVATE read,
whether this is actual ordinary user access or one of the weirder
cases like KVM that hits it by cheating a bit and using mmu_notifier.

c) O_DIRECT: not actually page cache, but it does *logically* touch
the same data that might be in page cache.

d) Access via pinned folio.  We have reads and writes, and we have
non-longterm and longterm pins.

In my mind (and I'm sure I've missed at least as many subtleties as
I've found), (a) is straightforward in the sense that the kernel can
take appropriate locks.  And the kernel takes locks that do *not* give
strict coherence but give a weaker sort, I think -- see below.

(b) can be fully coherent in the sense that the kernel can, and
hopefully does, invalidate mappings and flush the TLB as needed and
all access via mmap respects that (I hope -- I have not even tried to
review the various mmu_notifier users, but I sincerely hope they all
do it right).

(d) is gnarly: we have the xarray lock and the folio lock and various
pin bits, but we don't appear to have a lock that is safe to hold for
any extended period of time that preserves a folio's role as the
authoritative source of the contents of the part of the mapping it
covers.  In some sense, pinning a folio is like mmapping and faulting
in that same folio, except that the kernel can zap the PTEs and
invalidate the mapping from a different thread, but it cannot zap
folio pins.

(c) is supposed to be fast, and as far as I've found, the only
technical (as opposed to performance) reason it can't be coherent is
that there is no way for an O_DIRECT access to interact correctly with
the existence of a pinned folio.

But why can't an O_DIRECT access be coherent vs mmap?  (I'm assuming
we're talking about O_DIRECT writes, not reads.)  ISTM the kernel has
two ways to do it coherently: unmap the mapping (slow) or copy the
data being written into page cache and then write it from page cache
(differently slow).  If anyone mmaps a file and O_DIRECT-writes it
while they're touching the mapping at high frequency, I'm not sure
they deserve excellent performance, and I'd rather give coherence than
high performance incoherent behavior.


Anyway, I have a sort of proposed definition for what coherence might
mean -- call this "weak coherence":

For each address in the file, there are various accesses.  They happen
in an order -- think of this like a CPU memory model, not a strictly
linearizable order (if nothing else, mmaped accesses literally follow
the CPU's memory model).  But we broaden the memory model to pretend
that it applies to the logical file, not to the physical memory. The
implementation is weakly coherent if:

- Accesses via mmap obey the CPU memory model.

- Accesses via syscall or io_uring *including fixed buffers* obey the
CPU memory model, except that the access may be broken up into smaller
accesses that happen in arbitrary order, so long as they all happen
between the submission and the completion of the request.  This
includes direct I/O.

- Accesses via longterm folio pin follow the same rules as mmap.

- Accesses via shortterm folio pin are like syscalls: they happen some
time between acquisition and release of the pin but are not
necessarily ordered at the time that they are made by the CPU.  (This
is the weak part.)

I haven't come up with a reason that this would be impossible to
implement nicely, except that longterm folio pins would need a much
stricter locking scheme: anything that wants to detach a folio from an
address_space would need to wait for the pin to be released or
actively expedite the pin's release.

Is there a hard issue with O_DIRECT that I haven't thought of?

And someone would need to go through all the short-term pins in the
tree and make sure they're all actually okay with the weak form of
coherence.

I think there's at least one example that I think is questionable:
iov_iter_extract_user_pages, as reachable from FUSE and possibly
elsewhere.  Suppose there are two FUSE filesystems, A and B. A has a
file a and B has a file b. A process mmaps b, faults it in, and does
read(a_fd, b_ptr, 1), and the FUSE kernel code takes a short-term pin
on b_ptr's folio.  Then, before A's daemon responds, B's daemon
requests invalidation of b (via fuse_notify_inval_inode), which calls
invalidate_inode_pages2_range, which removes the folio from b's page
cache. Then A's daemon responds to the read via splice from a pipe,
causing the kernel to write to the short-term pinned folio, which goes
off into the void.

Is this a bug?  If B's invalidation happened because some external
actor logically wrote to *the entire area covered by the read*, then
arguably it's not -- under my coherence model, the read syscall can be
deemed to have occurred immediately before that write and the results
simply got overwritten.  But if B's invalidation happened for a
different reason (say an external write to a non-overlapping range),
then I think this behavior is incorrect.

Similarly, if there is just one FUSE and B is an ordinary filesystem,
then userspace can do read(a_fd, b_ptr, 1) and overlap it with an
O_DIRECT write to b at, say, offset 512, size 512.  If the FS accepts
that, then the folio gets detached and the read's update goes into the
void.


That last example seems more likely to hit if RWF_DIRECT shows up and
some library is like "hey, cool, now I can use our shiny atomic direct
I/O features in what used to be a buffered I/O path".  And maybe this
isn't a *security* bug, but it is an observable case where a simple
read() call appears to succeed but does not actually update the
destination buffer.  Ouch.


Anyway, that's my brain dump.


[0] Despite the fact that I got my start doing serious kernel work as
a result of writing a memory mapped database of sorts, I've never
actually stared at this part of the code before now...


--Andy


-- 
Andy Lutomirski
AMA Capital Management, LLC

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

* direct I/O vs buffered I/O coherency, was: Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-11-03 19:13     ` Andy Lutomirski
@ 2026-11-04  7:30       ` Christoph Hellwig
  2026-11-04 17:48         ` Andy Lutomirski
  0 siblings, 1 reply; 8+ messages in thread
From: Christoph Hellwig @ 2026-11-04  7:30 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Christoph Hellwig, Christian Brauner, Stefan Metzmacher,
	linux-fsdevel, Alexander Viro, Jan Kara, io-uring, linux-api,
	willy

On Tue, Nov 03, 2026 at 11:13:32AM -0800, Andy Lutomirski wrote:
> Here's a bit of a taxonomy of page cache access types that I've come
> up with from poking around at the kernel source [0]:
> 
> a) Plain buffered I/O (pread, pwrite, etc) -- I'm sure there is a lot
> of nuance here, but I'm generally referring to I/O, via pagecache,
> done in syscall or io_uring context on behalf of a user request.
> 
> b) Access via MAP_SHARED mmap or via unbroken-cow MAP_PRIVATE read,
> whether this is actual ordinary user access or one of the weirder
> cases like KVM that hits it by cheating a bit and using mmu_notifier.

This really also needs to be split into

b1) Read-only access through MAP_SHARED mmap without PROT_WRITE
    or unbroken-cow MAP_PRIVATE read.

and


b2) Writable access through writable MAP_SHARED mmap.

> 
> c) O_DIRECT: not actually page cache, but it does *logically* touch
> the same data that might be in page cache.

As in the file side of direct I/O to make this clear.

> d) Access via pinned folio.  We have reads and writes, and we have
> non-longterm and longterm pins.

... and this includes the "remote" side of direct I/O if done to file
backed VMAs.

We also still have

e) access through get_user_pages without pins.

e) really needs to die ASAP, but various maintainers have dragged
their feed and kept it.  And of course there's still vmsplice which
kinda hardcodes this into the syscall interface.

> But why can't an O_DIRECT access be coherent vs mmap?  (I'm assuming
> we're talking about O_DIRECT writes, not reads.)  ISTM the kernel has
> two ways to do it coherently: unmap the mapping (slow) or copy the
> data being written into page cache and then write it from page cache
> (differently slow).  If anyone mmaps a file and O_DIRECT-writes it
> while they're touching the mapping at high frequency, I'm not sure
> they deserve excellent performance, and I'd rather give coherence than
> high performance incoherent behavior.

Because reads invalidate the mapping they are just as relevant.
But yes, both could fall back to some form of buffered I/O if the
relevant ranges are mapped.  And maybe that is the best thing to do.

Note that this also means we need to prevent new folio in the page cache
from being instantiated while direct I/O is pending.  For XFS which
always take i_rwsem on buffered reads that's reasonably easy, although
we'd need additional i_dio_count checks (or move to non-owner release
of i_rwsem for direct I/O).
But other file systems don't do this, and they get significantly better
performance for small cached buffered writes, so they probably do not
want to change that.

Leaving the rest in here for context.
> 
> 
> Anyway, I have a sort of proposed definition for what coherence might
> mean -- call this "weak coherence":
> 
> For each address in the file, there are various accesses.  They happen
> in an order -- think of this like a CPU memory model, not a strictly
> linearizable order (if nothing else, mmaped accesses literally follow
> the CPU's memory model).  But we broaden the memory model to pretend
> that it applies to the logical file, not to the physical memory. The
> implementation is weakly coherent if:
> 
> - Accesses via mmap obey the CPU memory model.
> 
> - Accesses via syscall or io_uring *including fixed buffers* obey the
> CPU memory model, except that the access may be broken up into smaller
> accesses that happen in arbitrary order, so long as they all happen
> between the submission and the completion of the request.  This
> includes direct I/O.
> 
> - Accesses via longterm folio pin follow the same rules as mmap.
> 
> - Accesses via shortterm folio pin are like syscalls: they happen some
> time between acquisition and release of the pin but are not
> necessarily ordered at the time that they are made by the CPU.  (This
> is the weak part.)
> 
> I haven't come up with a reason that this would be impossible to
> implement nicely, except that longterm folio pins would need a much
> stricter locking scheme: anything that wants to detach a folio from an
> address_space would need to wait for the pin to be released or
> actively expedite the pin's release.
> 
> Is there a hard issue with O_DIRECT that I haven't thought of?
> 
> And someone would need to go through all the short-term pins in the
> tree and make sure they're all actually okay with the weak form of
> coherence.
> 
> I think there's at least one example that I think is questionable:
> iov_iter_extract_user_pages, as reachable from FUSE and possibly
> elsewhere.  Suppose there are two FUSE filesystems, A and B. A has a
> file a and B has a file b. A process mmaps b, faults it in, and does
> read(a_fd, b_ptr, 1), and the FUSE kernel code takes a short-term pin
> on b_ptr's folio.  Then, before A's daemon responds, B's daemon
> requests invalidation of b (via fuse_notify_inval_inode), which calls
> invalidate_inode_pages2_range, which removes the folio from b's page
> cache. Then A's daemon responds to the read via splice from a pipe,
> causing the kernel to write to the short-term pinned folio, which goes
> off into the void.
> 
> Is this a bug?  If B's invalidation happened because some external
> actor logically wrote to *the entire area covered by the read*, then
> arguably it's not -- under my coherence model, the read syscall can be
> deemed to have occurred immediately before that write and the results
> simply got overwritten.  But if B's invalidation happened for a
> different reason (say an external write to a non-overlapping range),
> then I think this behavior is incorrect.
> 
> Similarly, if there is just one FUSE and B is an ordinary filesystem,
> then userspace can do read(a_fd, b_ptr, 1) and overlap it with an
> O_DIRECT write to b at, say, offset 512, size 512.  If the FS accepts
> that, then the folio gets detached and the read's update goes into the
> void.
> 
> 
> That last example seems more likely to hit if RWF_DIRECT shows up and
> some library is like "hey, cool, now I can use our shiny atomic direct
> I/O features in what used to be a buffered I/O path".  And maybe this
> isn't a *security* bug, but it is an observable case where a simple
> read() call appears to succeed but does not actually update the
> destination buffer.  Ouch.
> 
> 
> Anyway, that's my brain dump.
> 
> 
> [0] Despite the fact that I got my start doing serious kernel work as
> a result of writing a memory mapped database of sorts, I've never
> actually stared at this part of the code before now...
> 
> 
> --Andy
> 
> 
> -- 
> Andy Lutomirski
> AMA Capital Management, LLC
---end quoted text---

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

* Re: direct I/O vs buffered I/O coherency, was: Re: [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io
  2026-11-04  7:30       ` direct I/O vs buffered I/O coherency, was: " Christoph Hellwig
@ 2026-11-04 17:48         ` Andy Lutomirski
  0 siblings, 0 replies; 8+ messages in thread
From: Andy Lutomirski @ 2026-11-04 17:48 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Christian Brauner, Stefan Metzmacher, linux-fsdevel,
	Alexander Viro, Jan Kara, io-uring, linux-api, willy

> On Nov 3, 2026, at 11:31 PM, Christoph Hellwig <hch@lst.de> wrote:
>
> On Tue, Nov 03, 2026 at 11:13:32AM -0800, Andy Lutomirski wrote:
>> Here's a bit of a taxonomy of page cache access types that I've come
>> up with from poking around at the kernel source [0]:
>>
>> a) Plain buffered I/O (pread, pwrite, etc) -- I'm sure there is a lot
>> of nuance here, but I'm generally referring to I/O, via pagecache,
>> done in syscall or io_uring context on behalf of a user request.
>>
>> b) Access via MAP_SHARED mmap or via unbroken-cow MAP_PRIVATE read,
>> whether this is actual ordinary user access or one of the weirder
>> cases like KVM that hits it by cheating a bit and using mmu_notifier.
>
> This really also needs to be split into
>
> b1) Read-only access through MAP_SHARED mmap without PROT_WRITE
>    or unbroken-cow MAP_PRIVATE read.
>
> and
>
>
> b2) Writable access through writable MAP_SHARED mmap.

To confirm: do you mean literally just MAP_SHARED,
PROT_READ|PROT_WRITE and then reading?  Or are you specifically
referring to reads from a shared mapping in the writable state, i.e.
after mkwrite has happened?

>
>>
>> c) O_DIRECT: not actually page cache, but it does *logically* touch
>> the same data that might be in page cache.
>
> As in the file side of direct I/O to make this clear.
>
>> d) Access via pinned folio.  We have reads and writes, and we have
>> non-longterm and longterm pins.
>
> ... and this includes the "remote" side of direct I/O if done to file
> backed VMAs.
>
> We also still have
>
> e) access through get_user_pages without pins.
>
> e) really needs to die ASAP, but various maintainers have dragged
> their feed and kept it.  And of course there's still vmsplice which
> kinda hardcodes this into the syscall interface.
>
>> But why can't an O_DIRECT access be coherent vs mmap?  (I'm assuming
>> we're talking about O_DIRECT writes, not reads.)  ISTM the kernel has
>> two ways to do it coherently: unmap the mapping (slow) or copy the
>> data being written into page cache and then write it from page cache
>> (differently slow).  If anyone mmaps a file and O_DIRECT-writes it
>> while they're touching the mapping at high frequency, I'm not sure
>> they deserve excellent performance, and I'd rather give coherence than
>> high performance incoherent behavior.
>
> Because reads invalidate the mapping they are just as relevant.
> But yes, both could fall back to some form of buffered I/O if the
> relevant ranges are mapped.  And maybe that is the best thing to do.
>
> Note that this also means we need to prevent new folio in the page cache
> from being instantiated while direct I/O is pending.  For XFS which
> always take i_rwsem on buffered reads that's reasonably easy, although
> we'd need additional i_dio_count checks (or move to non-owner release
> of i_rwsem for direct I/O).
> But other file systems don't do this, and they get significantly better
> performance for small cached buffered writes, so they probably do not
> want to change that.
>
> Leaving the rest in here for context.
>>
>>
>> Anyway, I have a sort of proposed definition for what coherence might
>> mean -- call this "weak coherence":
>>
>> For each address in the file, there are various accesses.  They happen
>> in an order -- think of this like a CPU memory model, not a strictly
>> linearizable order (if nothing else, mmaped accesses literally follow
>> the CPU's memory model).  But we broaden the memory model to pretend
>> that it applies to the logical file, not to the physical memory. The
>> implementation is weakly coherent if:
>>
>> - Accesses via mmap obey the CPU memory model.
>>
>> - Accesses via syscall or io_uring *including fixed buffers* obey the
>> CPU memory model, except that the access may be broken up into smaller
>> accesses that happen in arbitrary order, so long as they all happen
>> between the submission and the completion of the request.  This
>> includes direct I/O.
>>
>> - Accesses via longterm folio pin follow the same rules as mmap.
>>
>> - Accesses via shortterm folio pin are like syscalls: they happen some
>> time between acquisition and release of the pin but are not
>> necessarily ordered at the time that they are made by the CPU.  (This
>> is the weak part.)
>>
>> I haven't come up with a reason that this would be impossible to
>> implement nicely, except that longterm folio pins would need a much
>> stricter locking scheme: anything that wants to detach a folio from an
>> address_space would need to wait for the pin to be released or
>> actively expedite the pin's release.
>>
>> Is there a hard issue with O_DIRECT that I haven't thought of?
>>
>> And someone would need to go through all the short-term pins in the
>> tree and make sure they're all actually okay with the weak form of
>> coherence.


I should add two other current glitches that I forgot to mention.
Neither of these are new discoveries.

- short term writable folio pins that overlap with writeback: these
can modify page cache without calling .mkwrite. This has been known
for a long time. An LLM did a quick audit for me and suggested that
this can result in a bad state on NFS. It did not flag checksumming
filesystems like btrfs, nor did I ask, but from old memory I can
imagine unfortunate interactions.

- hole punching completely ignores folio pins, both short term and
long term.  I have a delightful reproducer I posted on a different
thread.  It is less than straightforward to fix without adding a way
to break an existing pin unless we’re willing to start refusing to
punch a hole or perhaps to write zeros instead of actually punching
the hole.

https://lore.kernel.org/all/CALCETrVpEP=HxVqdSP_AgA=U7ignwAgjz5Vi9+Z9dZsX5DXeLw@mail.gmail.com/


>>
>> I think there's at least one example that I think is questionable:
>> iov_iter_extract_user_pages, as reachable from FUSE and possibly
>> elsewhere.  Suppose there are two FUSE filesystems, A and B. A has a
>> file a and B has a file b. A process mmaps b, faults it in, and does
>> read(a_fd, b_ptr, 1), and the FUSE kernel code takes a short-term pin
>> on b_ptr's folio.  Then, before A's daemon responds, B's daemon
>> requests invalidation of b (via fuse_notify_inval_inode), which calls
>> invalidate_inode_pages2_range, which removes the folio from b's page
>> cache. Then A's daemon responds to the read via splice from a pipe,
>> causing the kernel to write to the short-term pinned folio, which goes
>> off into the void.
>>
>> Is this a bug?  If B's invalidation happened because some external
>> actor logically wrote to *the entire area covered by the read*, then
>> arguably it's not -- under my coherence model, the read syscall can be
>> deemed to have occurred immediately before that write and the results
>> simply got overwritten.  But if B's invalidation happened for a
>> different reason (say an external write to a non-overlapping range),
>> then I think this behavior is incorrect.
>>
>> Similarly, if there is just one FUSE and B is an ordinary filesystem,
>> then userspace can do read(a_fd, b_ptr, 1) and overlap it with an
>> O_DIRECT write to b at, say, offset 512, size 512.  If the FS accepts
>> that, then the folio gets detached and the read's update goes into the
>> void.
>>
>>
>> That last example seems more likely to hit if RWF_DIRECT shows up and
>> some library is like "hey, cool, now I can use our shiny atomic direct
>> I/O features in what used to be a buffered I/O path".  And maybe this
>> isn't a *security* bug, but it is an observable case where a simple
>> read() call appears to succeed but does not actually update the
>> destination buffer.  Ouch.
>>
>>
>> Anyway, that's my brain dump.
>>
>>
>> [0] Despite the fact that I got my start doing serious kernel work as
>> a result of writing a memory mapped database of sorts, I've never
>> actually stared at this part of the code before now...
>>
>>
>> --Andy
>>
>>

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

end of thread, other threads:[~2026-11-04 17:48 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 18:46 [PATCH] fs: add RWF_DIRECT for preadv2/pwritev2/io_uring to set IOCB_DIRECT per io Stefan Metzmacher
2026-10-21 11:04 ` Christian Brauner
2026-11-03  8:07   ` Christoph Hellwig
2026-11-03 13:06     ` Stefan Metzmacher
2026-11-03 13:51     ` Paulo Alcantara
2026-11-03 19:13     ` Andy Lutomirski
2026-11-04  7:30       ` direct I/O vs buffered I/O coherency, was: " Christoph Hellwig
2026-11-04 17:48         ` Andy Lutomirski

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