mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jan Prusakowski <jprusakowski@google.com>
To: Jaegeuk Kim <jaegeuk@kernel.org>, Chao Yu <chao@kernel.org>
Cc: linux-f2fs-devel@lists.sourceforge.net,
	linux-kernel@vger.kernel.org,
	 Jan Prusakowski <jprusakowski@google.com>,
	Eric Biggers <ebiggers@google.com>,
	 Yonggil Song <yonggil.song@samsung.com>,
	Hans Holmberg <hans.holmberg@wdc.com>
Subject: [PATCH RESEND 4/9] f2fs: propagate errors from buffered fallback of O_DIRECT writes
Date: Thu, 12 Nov 2026 14:33:50 +0000	[thread overview]
Message-ID: <20261112143355.2923939-5-jprusakowski@google.com> (raw)
In-Reply-To: <20261112143355.2923939-1-jprusakowski@google.com>

f2fs falls back to buffered I/O for O_DIRECT writes in two places:
f2fs_file_write_iter() when f2fs_should_use_dio() rejects direct I/O
(e.g. compressed files or unaligned requests), and f2fs_dio_write_iter()
for the remainder of a partial direct write. In both cases the data is
then written back and dropped from the page cache by
f2fs_flush_buffered_write() to preserve O_DIRECT semantics, but errors
are lost:

- f2fs_flush_buffered_write() ignores the result of
  filemap_write_and_wait_range(), so a write that fails at writeback
  time (e.g. on compressed files with dm-error in generic/250 and
  generic/252) is reported to the user as fully successful.

- In f2fs_dio_write_iter(), if nothing was written by direct I/O and the
  buffered write fails (e.g. with -ENOSPC in generic/299), the function
  returns 0 instead of the error.

Follow direct_write_fallback(): if the buffered write fails, return the
number of bytes written by direct I/O, or the error if there were none.
If writeback fails, reset ki_pos to the start of the buffered part,
since we don't know how much of it reached the disk, and return the
direct I/O byte count or the error.

For f2fs_file_write_iter(), flush before generic_write_sync() and while
still holding the inode lock, so the error can be returned and the range
can't be re-dirtied by another writer in the meantime.

Writing a part of a compressed cluster marks the whole cluster dirty
(set_cluster_dirty()), so flush and invalidate whole clusters for
compressed files; otherwise dirty pages outside the written range stay
in the page cache.

Also don't fall back to buffered I/O in f2fs_dio_write_iter() for
IOCB_NOWAIT, where f2fs_buffered_write_iter() would just return
-EOPNOTSUPP. Return -EAGAIN instead if nothing was written, so that the
caller retries in a blocking context.

Fixes: a1e09b03e6f5 ("f2fs: use iomap for direct I/O")
Fixes: 92318f20d703 ("f2fs: preserve direct write semantics when buffering is forced")
Assisted-by: LLM
Signed-off-by: Jan Prusakowski <jprusakowski@google.com>
---
 fs/f2fs/file.c | 77 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 61 insertions(+), 16 deletions(-)

diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 05061254c3b2..b67782043396 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -5643,17 +5643,32 @@ static const struct iomap_dio_ops f2fs_iomap_dio_write_ops = {
 	.submit_io	= f2fs_dio_write_submit_io,
 };
 
-static void f2fs_flush_buffered_write(struct address_space *mapping,
-				      loff_t start_pos, loff_t end_pos)
+/*
+ * Write back and drop the page cache of a buffered fallback of an O_DIRECT
+ * write.  Writing a part of a compressed cluster dirties the whole cluster,
+ * so extend the range to cluster boundaries for compressed files.
+ */
+static int f2fs_flush_buffered_write(struct address_space *mapping,
+				     loff_t start_pos, loff_t end_pos)
 {
+	struct inode *inode = mapping->host;
 	int ret;
 
+	if (f2fs_compressed_file(inode)) {
+		loff_t cluster_bytes =
+			(loff_t)F2FS_I(inode)->i_cluster_size << PAGE_SHIFT;
+
+		start_pos = round_down(start_pos, cluster_bytes);
+		end_pos = round_up(end_pos + 1, cluster_bytes) - 1;
+	}
+
 	ret = filemap_write_and_wait_range(mapping, start_pos, end_pos);
 	if (ret < 0)
-		return;
+		return ret;
 	invalidate_mapping_pages(mapping,
 				 start_pos >> PAGE_SHIFT,
 				 end_pos >> PAGE_SHIFT);
+	return 0;
 }
 
 static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
@@ -5740,6 +5755,13 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
 		ssize_t ret2;
 		loff_t bufio_start_pos = iocb->ki_pos;
 
+		/* A buffered write can block, so don't fall back for NOWAIT. */
+		if (iocb->ki_flags & IOCB_NOWAIT) {
+			if (!ret)
+				ret = -EAGAIN;
+			goto out;
+		}
+
 		/*
 		 * The direct write was partial, so we need to fall back to a
 		 * buffered write for the remainder.
@@ -5748,8 +5770,15 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
 		ret2 = f2fs_buffered_write_iter(iocb, from);
 		if (iov_iter_count(from))
 			f2fs_write_failed(inode, iocb->ki_pos);
-		if (ret2 < 0)
+		/*
+		 * Return the number of bytes written by direct I/O, or the
+		 * error if there were none, same as direct_write_fallback().
+		 */
+		if (ret2 < 0) {
+			if (!ret)
+				ret = ret2;
 			goto out;
+		}
 
 		/*
 		 * Ensure that the pagecache pages are written to disk and
@@ -5757,12 +5786,20 @@ static ssize_t f2fs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from,
 		 */
 		if (ret2 > 0) {
 			loff_t bufio_end_pos = bufio_start_pos + ret2 - 1;
+			int err;
+
+			err = f2fs_flush_buffered_write(file->f_mapping,
+							bufio_start_pos,
+							bufio_end_pos);
+			if (err) {
+				/* We don't know how much of it was written. */
+				iocb->ki_pos = bufio_start_pos;
+				if (!ret)
+					ret = err;
+				goto out;
+			}
 
 			ret += ret2;
-
-			f2fs_flush_buffered_write(file->f_mapping,
-						  bufio_start_pos,
-						  bufio_end_pos);
 		}
 	} else {
 		/* iomap_dio_rw() already handled the generic_write_sync(). */
@@ -5841,6 +5878,22 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 			f2fs_buffered_write_iter(iocb, from);
 
 		trace_f2fs_datawrite_end(inode, orig_pos, ret);
+
+		/*
+		 * If buffered IO was forced, flush and drop the data from
+		 * the page cache to preserve O_DIRECT semantics.
+		 */
+		if (ret > 0 && !dio && (iocb->ki_flags & IOCB_DIRECT)) {
+			int err;
+
+			err = f2fs_flush_buffered_write(iocb->ki_filp->f_mapping,
+							bufio_start_pos,
+							bufio_start_pos + ret - 1);
+			if (err) {
+				iocb->ki_pos = bufio_start_pos;
+				ret = err;
+			}
+		}
 	}
 
 	/* Don't leave any preallocated blocks around past i_size. */
@@ -5864,14 +5917,6 @@ static ssize_t f2fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	if (ret > 0 && may_need_sync)
 		ret = generic_write_sync(iocb, ret);
 
-	/* If buffered IO was forced, flush and drop the data from
-	 * the page cache to preserve O_DIRECT semantics
-	 */
-	if (ret > 0 && !dio && (iocb->ki_flags & IOCB_DIRECT))
-		f2fs_flush_buffered_write(iocb->ki_filp->f_mapping,
-					  bufio_start_pos,
-					  bufio_start_pos + ret - 1);
-
 	return ret;
 }
 
-- 
2.56.0.567.gdb80ae8cd5-goog


  parent reply	other threads:[~2026-11-12 14:38 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-12 14:33 [PATCH RESEND 0/9] f2fs: fix stale data exposure on failed " Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 1/9] f2fs: compress: avoid sleeping in atomic context in f2fs_decompress_cluster() Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 2/9] f2fs: compress: abort on read error in prepare_compress_overwrite() Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 3/9] f2fs: keep old block address when out-of-place write fails to allocate Jan Prusakowski
2026-11-12 14:33 ` Jan Prusakowski [this message]
2026-11-12 14:33 ` [PATCH RESEND 5/9] f2fs: truncate blocks allocated past EOF by a failed direct write Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 6/9] f2fs: roll back failed block updates of O_DIRECT buffered fallback Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 7/9] f2fs: roll back failed block updates of direct writes Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 8/9] f2fs: compress: fix cleanup when a cluster write fails to be submitted Jan Prusakowski
2026-11-12 14:33 ` [PATCH RESEND 9/9] f2fs: compress: roll back failed cluster writes of O_DIRECT buffered fallback Jan Prusakowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261112143355.2923939-5-jprusakowski@google.com \
    --to=jprusakowski@google.com \
    --cc=chao@kernel.org \
    --cc=ebiggers@google.com \
    --cc=hans.holmberg@wdc.com \
    --cc=jaegeuk@kernel.org \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=yonggil.song@samsung.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®