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
next prev 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®