Author: kib
Date: Wed Jun  6 16:30:16 2012
New Revision: 236687
URL: http://svn.freebsd.org/changeset/base/236687

Log:
  Improve handling of uiomove(9) errors for the NFS client.
  
  Do not brelse() the buffer unconditionally with BIO_ERROR set if
  uiomove() failed. The brelse() treats most buffers with BIO_ERROR as
  B_INVAL, dropping their content.  Instead, if the write request
  covered the whole buffer, remember the cached state and brelse() with
  BIO_ERROR set only if the buffer was not cached previously.
  
  Update the buffer dirtyoff/dirtyend based on the progress recorded by
  uiomove() in passed struct uio, even in the presence of
  error. Otherwise, usermode could see changed data in the backed pages,
  but later the buffer is destroyed without write-back.
  
  If uiomove() failed for IO_UNIT request, try to truncate the vnode
  back to the pre-write state, and rewind the progress in passed uio
  accordingly, following the FFS behaviour.
  
  Reviewed by:  rmacklem (some time ago)
  Tested by:    pho
  MFC after:    1 month

Modified:
  head/sys/fs/nfsclient/nfs_clbio.c

Modified: head/sys/fs/nfsclient/nfs_clbio.c
==============================================================================
--- head/sys/fs/nfsclient/nfs_clbio.c   Wed Jun  6 16:26:55 2012        
(r236686)
+++ head/sys/fs/nfsclient/nfs_clbio.c   Wed Jun  6 16:30:16 2012        
(r236687)
@@ -897,8 +897,9 @@ ncl_write(struct vop_write_args *ap)
        struct nfsmount *nmp = VFSTONFS(vp->v_mount);
        daddr_t lbn;
        int bcount;
-       int n, on, error = 0;
-       off_t tmp_off;
+       int bp_cached, n, on, error = 0;
+       size_t orig_resid, local_resid;
+       off_t orig_size, tmp_off;
 
        KASSERT(uio->uio_rw == UIO_WRITE, ("ncl_write mode"));
        KASSERT(uio->uio_segflg != UIO_USERSPACE || uio->uio_td == curthread,
@@ -950,6 +951,11 @@ flush_and_restart:
                        mtx_unlock(&np->n_mtx);
        }
 
+       orig_resid = uio->uio_resid;
+       mtx_lock(&np->n_mtx);
+       orig_size = np->n_size;
+       mtx_unlock(&np->n_mtx);
+
        /*
         * If IO_APPEND then load uio_offset.  We restart here if we cannot
         * get the append lock.
@@ -1127,7 +1133,10 @@ again:
                 * normally.
                 */
 
+               bp_cached = 1;
                if (on == 0 && n == bcount) {
+                       if ((bp->b_flags & B_CACHE) == 0)
+                               bp_cached = 0;
                        bp->b_flags |= B_CACHE;
                        bp->b_flags &= ~B_INVAL;
                        bp->b_ioflags &= ~BIO_ERROR;
@@ -1193,8 +1202,24 @@ again:
                        goto again;
                }
 
+               local_resid = uio->uio_resid;
                error = uiomove((char *)bp->b_data + on, n, uio);
 
+               if (error != 0 && !bp_cached) {
+                       /*
+                        * This block has no other content then what
+                        * possibly was written by the faulty uiomove.
+                        * Release it, forgetting the data pages, to
+                        * prevent the leak of uninitialized data to
+                        * usermode.
+                        */
+                       bp->b_ioflags |= BIO_ERROR;
+                       brelse(bp);
+                       uio->uio_offset -= local_resid - uio->uio_resid;
+                       uio->uio_resid = local_resid;
+                       break;
+               }
+
                /*
                 * Since this block is being modified, it must be written
                 * again and not just committed.  Since write clustering does
@@ -1203,17 +1228,18 @@ again:
                 */
                bp->b_flags &= ~(B_NEEDCOMMIT | B_CLUSTEROK);
 
-               if (error) {
-                       bp->b_ioflags |= BIO_ERROR;
-                       brelse(bp);
-                       break;
-               }
+               /*
+                * Get the partial update on the progress made from
+                * uiomove, if an error occured.
+                */
+               if (error != 0)
+                       n = local_resid - uio->uio_resid;
 
                /*
                 * Only update dirtyoff/dirtyend if not a degenerate
                 * condition.
                 */
-               if (n) {
+               if (n > 0) {
                        if (bp->b_dirtyend > 0) {
                                bp->b_dirtyoff = min(on, bp->b_dirtyoff);
                                bp->b_dirtyend = max((on + n), bp->b_dirtyend);
@@ -1242,8 +1268,22 @@ again:
                } else {
                        bdwrite(bp);
                }
+
+               if (error != 0)
+                       break;
        } while (uio->uio_resid > 0 && n > 0);
 
+       if (error != 0) {
+               if (ioflag & IO_UNIT) {
+                       VATTR_NULL(&vattr);
+                       vattr.va_size = orig_size;
+                       /* IO_SYNC is handled implicitely */
+                       (void)VOP_SETATTR(vp, &vattr, cred);
+                       uio->uio_offset -= orig_resid - uio->uio_resid;
+                       uio->uio_resid = orig_resid;
+               }
+       }
+
        return (error);
 }
 
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to