]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
nfs/localio: backfill missing partial read support for misaligned DIO
authorMike Snitzer <snitzer@kernel.org>
Mon, 27 Oct 2025 13:08:33 +0000 (09:08 -0400)
committerAnna Schumaker <anna.schumaker@oracle.com>
Mon, 10 Nov 2025 15:32:28 +0000 (10:32 -0500)
Misaligned DIO read can be split into 3 IOs, must handle potential for
short read from each component IO (follows same pattern used for
handling partial writes, except upper layer read code handles advancing
offset before retry).

Fixes: c817248fc831 ("nfs/localio: add proper O_DIRECT support for READ and WRITE")
Signed-off-by: Mike Snitzer <snitzer@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
fs/nfs/localio.c

index 647fa19b0479ec93f2056d1b3f10a92ae5f7a356..9c205f8b5e59c4aa9e369e6d1b9733c58ade1fbe 100644 (file)
@@ -414,7 +414,7 @@ nfs_local_iters_setup_dio(struct nfs_local_kiocb *iocb, int rw,
        /* Setup misaligned end?
         * If so, the end is purposely setup to be issued using buffered IO
         * before the middle (which will use DIO, if DIO-aligned, with AIO).
-        * This creates problems if/when the end results in a partial write.
+        * This creates problems if/when the end results in short read or write.
         * So must save index and length of end to handle this corner case.
         */
        if (local_dio->end_len) {
@@ -580,8 +580,9 @@ static void nfs_local_read_done(struct nfs_local_kiocb *iocb)
         */
        hdr->res.replen = 0;
 
-       if (hdr->res.count != hdr->args.count ||
-           hdr->args.offset + hdr->res.count >= i_size_read(file_inode(filp)))
+       /* nfs_readpage_result() handles short read */
+
+       if (hdr->args.offset + hdr->res.count >= i_size_read(file_inode(filp)))
                hdr->res.eof = true;
 
        dprintk("%s: read %ld bytes eof %d.\n", __func__,
@@ -620,6 +621,7 @@ static void nfs_local_call_read(struct work_struct *work)
                container_of(work, struct nfs_local_kiocb, work);
        struct file *filp = iocb->kiocb.ki_filp;
        const struct cred *save_cred;
+       bool force_done = false;
        ssize_t status;
        int n_iters;
 
@@ -637,7 +639,21 @@ static void nfs_local_call_read(struct work_struct *work)
                iocb->kiocb.ki_pos = iocb->offset[i];
                status = filp->f_op->read_iter(&iocb->kiocb, &iocb->iters[i]);
                if (status != -EIOCBQUEUED) {
-                       if (nfs_local_pgio_done(iocb, status, false)) {
+                       if (unlikely(status >= 0 && status < iocb->iters[i].count)) {
+                               /* partial read */
+                               if (i == iocb->end_iter_index) {
+                                       /* Must not account DIO partial end, otherwise (due
+                                        * to end being issued before middle): the partial
+                                        * read accounting in nfs_local_read_done()
+                                        * would incorrectly advance hdr->args.offset
+                                        */
+                                       status = 0;
+                               } else {
+                                       /* Partial read at start or middle, force done */
+                                       force_done = true;
+                               }
+                       }
+                       if (nfs_local_pgio_done(iocb, status, force_done)) {
                                nfs_local_read_iocb_done(iocb);
                                break;
                        }