Short write in nfsd becomes a full write to the client
authorDavid Shaw <dshaw@jabberwocky.com>
Fri, 6 Mar 2009 01:16:14 +0000 (20:16 -0500)
committerJ. Bruce Fields <bfields@citi.umich.edu>
Wed, 18 Mar 2009 21:38:40 +0000 (17:38 -0400)
If a filesystem being written to via NFS returns a short write count
(as opposed to an error) to nfsd, nfsd treats that as a success for
the entire write, rather than the short count that actually succeeded.

For example, given a 8192 byte write, if the underlying filesystem
only writes 4096 bytes, nfsd will ack back to the nfs client that all
8192 bytes were written.  The nfs client does have retry logic for
short writes, but this is never called as the client is told the
complete write succeeded.

There are probably other ways it could happen, but in my case it
happened with a fuse (filesystem in userspace) filesystem which can
rather easily have a partial write.

Here is a patch to properly return the short write count to the
client.

Signed-off-by: David Shaw <dshaw@jabberwocky.com>
Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
fs/nfsd/nfs3proc.c
fs/nfsd/nfs4proc.c
fs/nfsd/nfsproc.c
fs/nfsd/vfs.c
include/linux/nfsd/nfsd.h

index 579ce8c69daab35ff20481231f179eefb18b5db9..7c9fe838f038c2a72bfc3cad37698edc6e946656 100644 (file)
@@ -203,6 +203,7 @@ nfsd3_proc_write(struct svc_rqst *rqstp, struct nfsd3_writeargs *argp,
                                         struct nfsd3_writeres  *resp)
 {
        __be32  nfserr;
+       unsigned long cnt = argp->len;
 
        dprintk("nfsd: WRITE(3)    %s %d bytes at %ld%s\n",
                                SVCFH_fmt(&argp->fh),
@@ -215,9 +216,9 @@ nfsd3_proc_write(struct svc_rqst *rqstp, struct nfsd3_writeargs *argp,
        nfserr = nfsd_write(rqstp, &resp->fh, NULL,
                                   argp->offset,
                                   rqstp->rq_vec, argp->vlen,
-                                  argp->len,
+                                  &cnt,
                                   &resp->committed);
-       resp->count = argp->count;
+       resp->count = cnt;
        RETURN_STATUS(nfserr);
 }
 
index 77f584f69dfef15d8a6e2eabf35a09530ae31712..283d77a47120fe20a2940a7fa6eb02e5004e9855 100644 (file)
@@ -682,6 +682,7 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        struct file *filp = NULL;
        u32 *p;
        __be32 status = nfs_ok;
+       unsigned long cnt;
 
        /* no need to check permission - this will be done in nfsd_write() */
 
@@ -700,7 +701,7 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                return status;
        }
 
-       write->wr_bytes_written = write->wr_buflen;
+       cnt = write->wr_buflen;
        write->wr_how_written = write->wr_stable_how;
        p = (u32 *)write->wr_verifier.data;
        *p++ = nfssvc_boot.tv_sec;
@@ -708,10 +709,12 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 
        status =  nfsd_write(rqstp, &cstate->current_fh, filp,
                             write->wr_offset, rqstp->rq_vec, write->wr_vlen,
-                            write->wr_buflen, &write->wr_how_written);
+                            &cnt, &write->wr_how_written);
        if (filp)
                fput(filp);
 
+       write->wr_bytes_written = cnt;
+
        if (status == nfserr_symlink)
                status = nfserr_inval;
        return status;
index 6f7f26351227f250625430a75268594efc0206ab..e298e260b5f13d10433da9d66bd87c3dee2d0f1f 100644 (file)
@@ -180,6 +180,7 @@ nfsd_proc_write(struct svc_rqst *rqstp, struct nfsd_writeargs *argp,
 {
        __be32  nfserr;
        int     stable = 1;
+       unsigned long cnt = argp->len;
 
        dprintk("nfsd: WRITE    %s %d bytes at %d\n",
                SVCFH_fmt(&argp->fh),
@@ -188,7 +189,7 @@ nfsd_proc_write(struct svc_rqst *rqstp, struct nfsd_writeargs *argp,
        nfserr = nfsd_write(rqstp, fh_copy(&resp->fh, &argp->fh), NULL,
                                   argp->offset,
                                   rqstp->rq_vec, argp->vlen,
-                                  argp->len,
+                                  &cnt,
                                   &stable);
        return nfsd_return_attrs(nfserr, resp);
 }
index 0c076293155dd662804aa8535f17956678331c92..54404d73080919cffdf7300485b14855ee5432ef 100644 (file)
@@ -960,7 +960,7 @@ static void kill_suid(struct dentry *dentry)
 static __be32
 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
                                loff_t offset, struct kvec *vec, int vlen,
-                               unsigned long cnt, int *stablep)
+                               unsigned long *cnt, int *stablep)
 {
        struct svc_export       *exp;
        struct dentry           *dentry;
@@ -974,7 +974,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
        err = nfserr_perm;
 
        if ((fhp->fh_export->ex_flags & NFSEXP_MSNFS) &&
-               (!lock_may_write(file->f_path.dentry->d_inode, offset, cnt)))
+               (!lock_may_write(file->f_path.dentry->d_inode, offset, *cnt)))
                goto out;
 #endif
 
@@ -1006,7 +1006,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
        host_err = vfs_writev(file, (struct iovec __user *)vec, vlen, &offset);
        set_fs(oldfs);
        if (host_err >= 0) {
-               nfsdstats.io_write += cnt;
+               nfsdstats.io_write += host_err;
                fsnotify_modify(file->f_path.dentry);
        }
 
@@ -1051,9 +1051,10 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
        }
 
        dprintk("nfsd: write complete host_err=%d\n", host_err);
-       if (host_err >= 0)
+       if (host_err >= 0) {
                err = 0;
-       else 
+               *cnt = host_err;
+       } else
                err = nfserrno(host_err);
 out:
        return err;
@@ -1095,7 +1096,7 @@ out:
  */
 __be32
 nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
-               loff_t offset, struct kvec *vec, int vlen, unsigned long cnt,
+               loff_t offset, struct kvec *vec, int vlen, unsigned long *cnt,
                int *stablep)
 {
        __be32                  err = 0;
index 16f7b403d9c108140db3d6897991ef0e957f0d6b..54beda12d26bb2b5faebbb8cfdc13ec72e2a2b76 100644 (file)
@@ -105,7 +105,7 @@ void                nfsd_close(struct file *);
 __be32                 nfsd_read(struct svc_rqst *, struct svc_fh *, struct file *,
                                loff_t, struct kvec *, int, unsigned long *);
 __be32                 nfsd_write(struct svc_rqst *, struct svc_fh *,struct file *,
-                               loff_t, struct kvec *,int, unsigned long, int *);
+                               loff_t, struct kvec *,int, unsigned long *, int *);
 __be32         nfsd_readlink(struct svc_rqst *, struct svc_fh *,
                                char *, int *);
 __be32         nfsd_symlink(struct svc_rqst *, struct svc_fh *,