* [PATCH netfs v1 1/2] netfs: preserve prepare_write errno on reissue
2026-10-28 18:12 [PATCH netfs v1 0/2] preserve prepare_write errno across write reissue Iván Ezequiel Rodriguez
@ 2026-10-28 18:12 ` Iván Ezequiel Rodriguez
2026-10-28 18:12 ` [PATCH netfs v1 2/2] netfs: add KUnit coverage for reissue prepare_write errno Iván Ezequiel Rodriguez
1 sibling, 0 replies; 3+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 18:12 UTC (permalink / raw)
To: David Howells, Paulo Alcantara, netfs
Cc: Shyam Prasad N, Steve French, linux-fsdevel, linux-cifs, linux-kernel
prepare_write() may call netfs_prepare_write_failed() (FAILED + errno)
before write_retry calls netfs_reissue_write(). Clearing subreq->error
to 0 for a clean I/O reissue (82e8885bd763) then makes
netfs_do_issue_write() terminate with 0. The collector copies
front->error into stream->error and mapping_set_error(), which ignores
0, so writeback failure is silent.
Skip clearing error when NETFS_SREQ_FAILED is already set;
do_issue_write() already terminates FAILED subrequests.
Fixes: 82e8885bd763 ("netfs: when subreq is marked for retry, do not check if it faced an error")
Cc: Shyam Prasad N <sprasad@microsoft.com>
Cc: Steve French <stfrench@microsoft.com>
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
fs/netfs/write_issue.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index b8237e1314d8..f23364116421 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -268,7 +268,9 @@ void netfs_reissue_write(struct netfs_io_stream *stream,
iov_iter_truncate(&subreq->io_iter, size);
subreq->retry_count++;
- subreq->error = 0;
+ /* Keep prepare_write() errno; do_issue_write() terminates FAILED. */
+ if (!test_bit(NETFS_SREQ_FAILED, &subreq->flags))
+ subreq->error = 0;
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
netfs_stat(&netfs_n_wh_retry_write_subreq);
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH netfs v1 2/2] netfs: add KUnit coverage for reissue prepare_write errno
2026-10-28 18:12 [PATCH netfs v1 0/2] preserve prepare_write errno across write reissue Iván Ezequiel Rodriguez
2026-10-28 18:12 ` [PATCH netfs v1 1/2] netfs: preserve prepare_write errno on reissue Iván Ezequiel Rodriguez
@ 2026-10-28 18:12 ` Iván Ezequiel Rodriguez
1 sibling, 0 replies; 3+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 18:12 UTC (permalink / raw)
To: David Howells, Paulo Alcantara, netfs
Cc: Shyam Prasad N, Steve French, linux-fsdevel, linux-cifs, linux-kernel
Stub netfs_write_subrequest_terminated() and call netfs_reissue_write()
with FAILED + -EIO. Unpatched trees terminate with 0; patched keep -EIO.
./tools/testing/kunit/kunit.py run --kunitconfig=fs/netfs/.kunitconfig \
netfs_reissue_write*
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
fs/netfs/.kunitconfig | 3 ++
fs/netfs/Kconfig | 9 +++++
fs/netfs/Makefile | 2 +
fs/netfs/write_collect.c | 4 ++
fs/netfs/write_reissue_kunit.c | 73 ++++++++++++++++++++++++++++++++++
5 files changed, 91 insertions(+)
create mode 100644 fs/netfs/.kunitconfig
create mode 100644 fs/netfs/write_reissue_kunit.c
diff --git a/fs/netfs/.kunitconfig b/fs/netfs/.kunitconfig
new file mode 100644
index 000000000000..c783d180ab8f
--- /dev/null
+++ b/fs/netfs/.kunitconfig
@@ -0,0 +1,3 @@
+CONFIG_KUNIT=y
+CONFIG_NETFS_SUPPORT=y
+CONFIG_NETFS_KUNIT_TESTS=y
diff --git a/fs/netfs/Kconfig b/fs/netfs/Kconfig
index d0e7b0971fa3..d796dbe4bd7c 100644
--- a/fs/netfs/Kconfig
+++ b/fs/netfs/Kconfig
@@ -33,6 +33,15 @@ config NETFS_DEBUG
management module. If this is set, the debugging output may be
enabled by setting bits in /sys/module/netfs/parameters/debug.
+config NETFS_KUNIT_TESTS
+ tristate "KUnit tests for netfs" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ select NETFS_SUPPORT
+ help
+ This builds netfs KUnit tests (e.g. write reissue errno).
+ Say N unless developing netfs.
+
config FSCACHE
bool "General filesystem local caching manager"
depends on NETFS_SUPPORT
diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile
index 54834cde7e56..fa1544b9a199 100644
--- a/fs/netfs/Makefile
+++ b/fs/netfs/Makefile
@@ -33,4 +33,6 @@ netfs-$(CONFIG_FSCACHE) += fscache_proc.o
endif
netfs-$(CONFIG_FSCACHE_STATS) += fscache_stats.o
+netfs-$(CONFIG_NETFS_KUNIT_TESTS) += write_reissue_kunit.o
+
obj-$(CONFIG_NETFS_SUPPORT) += netfs.o
diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c
index 6e8ea534230d..f3236fe494bf 100644
--- a/fs/netfs/write_collect.c
+++ b/fs/netfs/write_collect.c
@@ -11,6 +11,7 @@
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
+#include <kunit/static_stub.h>
#include "internal.h"
/* Notes made in the collector */
@@ -529,6 +530,9 @@ void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error)
struct netfs_io_subrequest *subreq = _op;
struct netfs_io_request *wreq = subreq->rreq;
+ KUNIT_STATIC_STUB_REDIRECT(netfs_write_subrequest_terminated,
+ _op, transferred_or_error);
+
_enter("%x[%x] %zd", wreq->debug_id, subreq->debug_index, transferred_or_error);
switch (subreq->source) {
diff --git a/fs/netfs/write_reissue_kunit.c b/fs/netfs/write_reissue_kunit.c
new file mode 100644
index 000000000000..fca5f7dd9265
--- /dev/null
+++ b/fs/netfs/write_reissue_kunit.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * netfs_reissue_write must not clear prepare_write() errno.
+ */
+#include <kunit/test.h>
+#include <kunit/static_stub.h>
+#include <linux/uio.h>
+#include "internal.h"
+
+struct term_capture {
+ ssize_t transferred_or_error;
+ bool called;
+};
+
+static void capture_terminated(void *_op, ssize_t transferred_or_error)
+{
+ struct netfs_io_subrequest *subreq = _op;
+ struct term_capture *cap = kunit_get_current_test()->priv;
+
+ cap->called = true;
+ cap->transferred_or_error = transferred_or_error;
+ /* Mirror production: leave subreq->error as prepare_write set it. */
+ (void)subreq;
+}
+
+/*
+ * prepare_write_failed() sets FAILED + errno, then write_retry still calls
+ * reissue_write(). Before the fix, reissue wiped error to 0 so terminated
+ * reported success and mapping_set_error(0) was a no-op.
+ */
+static void reissue_preserves_prep_error(struct kunit *test)
+{
+ struct term_capture cap = {};
+ struct netfs_io_request wreq = {};
+ struct netfs_io_stream stream = {
+ .source = NETFS_UPLOAD_TO_SERVER,
+ };
+ struct netfs_io_subrequest subreq = {
+ .rreq = &wreq,
+ .len = 1,
+ .transferred = 0,
+ .error = -EIO,
+ .source = NETFS_UPLOAD_TO_SERVER,
+ };
+ char buf[1] = { 0 };
+ struct kvec kvec = { .iov_base = buf, .iov_len = 1 };
+ struct iov_iter source;
+
+ __set_bit(NETFS_SREQ_FAILED, &subreq.flags);
+ iov_iter_kvec(&source, ITER_SOURCE, &kvec, 1, 1);
+
+ test->priv = ∩
+ kunit_activate_static_stub(test, netfs_write_subrequest_terminated,
+ capture_terminated);
+
+ netfs_reissue_write(&stream, &subreq, &source);
+
+ KUNIT_EXPECT_TRUE(test, cap.called);
+ KUNIT_EXPECT_EQ(test, cap.transferred_or_error, -EIO);
+ KUNIT_EXPECT_EQ(test, subreq.error, -EIO);
+}
+
+static struct kunit_case netfs_reissue_test_cases[] = {
+ KUNIT_CASE(reissue_preserves_prep_error),
+ {}
+};
+
+static struct kunit_suite netfs_reissue_test_suite = {
+ .name = "netfs_reissue_write",
+ .test_cases = netfs_reissue_test_cases,
+};
+
+kunit_test_suite(netfs_reissue_test_suite);
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread