[PATCH 1/2] nvmet: handle short reads and writes in the file backend

From: shao.mingyin

Date: Wed Nov 11 2026 - 09:49:10 EST


From: Shao Mingyin <shao.mingyin@xxxxxxxxxx>

When the file backend falls back to its bvec mempool, large requests
are submitted in synchronous chunks. A short completion advances the
file position by the full chunk length and is eventually overwritten
with the total request length. The target reports success for an
incomplete write or returns unread buffer contents as valid data.

The common completion path has another short-I/O case: a zero-byte
return, such as a read at EOF after the backing file is truncated, is
passed to errno_to_nvme_status() and becomes success.

Stop at any incomplete chunk and convert nonnegative short completions
to -EIO in both paths. Preserve negative errors and full completions.

Fixes: d5eff33ee6f8 ("nvmet: add simple file backed ns support")
Fixes: c6aa3542e010 ("nvmet: add error log support for file backend")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Shao Mingyin <shao.mingyin@xxxxxxxxxx>
---

Tested on 7.3.0-next-20261030 (QEMU/TCG) with the loop transport and a
file-backed namespace on tmpfs (buffered_io=1).

The chunked path is only taken when the bio_vec allocation falls back
to the mempool, so failslab was used to fail that allocation, limited to
nvmet_file_execute_rw() with the stack-trace filter (times=1,
ignore-gfp-wait=N). The short completion was made deterministic by
shrinking the backing file behind nvmet's back (ns->size is only
revalidated on enable) and by leaving only 4 KiB of space in the tmpfs
for the last 64 KiB chunk of a 128 KiB write. The values below are the
status words returned by NVME_IOCTL_IO_CMD:

unpatched patched
read 512 KiB ret=0 ret=24582 (0x6006)
read 256 KiB ret=0 ret=24582
read 128 KiB ret=0 ret=24582
write 128 KiB ret=0 ret=24582

0x6006 contains NVME_SC_INTERNAL plus the Do Not Retry and More bits;
the More bit records the associated error-log entry.
Without the patch the reads report success although the tail of the
host buffer still holds the 0x5a pattern, and the write reports success
although only 68 KiB of 128 KiB reached the file. Controls: without the
injection the short read already fails with 0x6006 on both kernels
(non-chunked path), and with the injection but a full-length read both
return 0.
drivers/nvme/target/io-cmd-file.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
index 695963dd932e..e95ab1f08cff 100644
--- a/drivers/nvme/target/io-cmd-file.c
+++ b/drivers/nvme/target/io-cmd-file.c
@@ -113,7 +113,7 @@ static void nvmet_file_io_done(struct kiocb *iocb, long ret)
}

if (unlikely(ret != req->transfer_len))
- status = errno_to_nvme_status(req, ret);
+ status = errno_to_nvme_status(req, ret < 0 ? ret : -EIO);
nvmet_req_complete(req, status);
}

@@ -150,8 +150,11 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
if (unlikely(is_sync) &&
(nr_bvec - 1 == 0 || bv_cnt == NVMET_MAX_MPOOL_BVEC)) {
ret = nvmet_file_submit_bvec(req, pos, bv_cnt, len, 0);
- if (ret < 0)
+ if (ret != len) {
+ if (ret >= 0)
+ ret = -EIO;
goto complete;
+ }

pos += len;
bv_cnt = 0;