]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
NFS/localio: Handle short writes by retrying
authorTrond Myklebust <trond.myklebust@hammerspace.com>
Sat, 3 Jan 2026 17:14:59 +0000 (12:14 -0500)
committerAnna Schumaker <anna.schumaker@oracle.com>
Thu, 22 Jan 2026 15:51:10 +0000 (10:51 -0500)
The current code for handling short writes in localio just truncates the
I/O and then sets an error. While that is close to how the ordinary NFS
code behaves, it does mean there is a chance the data that got written
is lost because it isn't persisted.
To fix this, change localio so that the upper layers can direct the
behaviour to persist any unstable data by rewriting it, and then
continuing writing until an ENOSPC is hit.

Fixes: 70ba381e1a43 ("nfs: add LOCALIO support")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Reviewed-by: Mike Snitzer <snitzer@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/localio.c

index 41fbcb3f9167e9aab25abc6fa4b31d61466bbf6d..00bbac6c9fe408af83a42cafb24f527a63b32dc2 100644 (file)
@@ -58,6 +58,11 @@ struct nfs_local_fsync_ctx {
 static bool localio_enabled __read_mostly = true;
 module_param(localio_enabled, bool, 0644);
 
+static int nfs_local_do_read(struct nfs_local_kiocb *iocb,
+                            const struct rpc_call_ops *call_ops);
+static int nfs_local_do_write(struct nfs_local_kiocb *iocb,
+                             const struct rpc_call_ops *call_ops);
+
 static inline bool nfs_client_is_local(const struct nfs_client *clp)
 {
        return !!rcu_access_pointer(clp->cl_uuid.net);
@@ -542,13 +547,50 @@ nfs_local_iocb_release(struct nfs_local_kiocb *iocb)
        nfs_local_iocb_free(iocb);
 }
 
-static void
-nfs_local_pgio_release(struct nfs_local_kiocb *iocb)
+static void nfs_local_pgio_restart(struct nfs_local_kiocb *iocb,
+                                  struct nfs_pgio_header *hdr)
+{
+       int status = 0;
+
+       iocb->kiocb.ki_pos = hdr->args.offset;
+       iocb->kiocb.ki_flags &= ~(IOCB_DSYNC | IOCB_SYNC | IOCB_DIRECT);
+       iocb->kiocb.ki_complete = NULL;
+       iocb->aio_complete_work = NULL;
+       iocb->end_iter_index = -1;
+
+       switch (hdr->rw_mode) {
+       case FMODE_READ:
+               nfs_local_iters_init(iocb, ITER_DEST);
+               status = nfs_local_do_read(iocb, hdr->task.tk_ops);
+               break;
+       case FMODE_WRITE:
+               nfs_local_iters_init(iocb, ITER_SOURCE);
+               status = nfs_local_do_write(iocb, hdr->task.tk_ops);
+               break;
+       default:
+               status = -EOPNOTSUPP;
+       }
+
+       if (status != 0) {
+               nfs_local_iocb_release(iocb);
+               hdr->task.tk_status = status;
+               nfs_local_hdr_release(hdr, hdr->task.tk_ops);
+       }
+}
+
+static void nfs_local_pgio_release(struct nfs_local_kiocb *iocb)
 {
        struct nfs_pgio_header *hdr = iocb->hdr;
+       struct rpc_task *task = &hdr->task;
+
+       task->tk_action = NULL;
+       task->tk_ops->rpc_call_done(task, hdr);
 
-       nfs_local_iocb_release(iocb);
-       nfs_local_hdr_release(hdr, hdr->task.tk_ops);
+       if (task->tk_action == NULL) {
+               nfs_local_iocb_release(iocb);
+               task->tk_ops->rpc_release(hdr);
+       } else
+               nfs_local_pgio_restart(iocb, hdr);
 }
 
 /*
@@ -773,19 +815,7 @@ static void nfs_local_write_done(struct nfs_local_kiocb *iocb)
                pr_info_ratelimited("nfs: Unexpected direct I/O write alignment failure\n");
        }
 
-       /* Handle short writes as if they are ENOSPC */
-       status = hdr->res.count;
-       if (status > 0 && status < hdr->args.count) {
-               hdr->mds_offset += status;
-               hdr->args.offset += status;
-               hdr->args.pgbase += status;
-               hdr->args.count -= status;
-               nfs_set_pgio_error(hdr, -ENOSPC, hdr->args.offset);
-               status = -ENOSPC;
-               /* record -ENOSPC in terms of nfs_local_pgio_done */
-               (void) nfs_local_pgio_done(iocb, status, true);
-       }
-       if (hdr->task.tk_status < 0)
+       if (status < 0)
                nfs_reset_boot_verifier(hdr->inode);
 }