kernel - Add new bufcache/VM consolidated API, fsx fixes for NFS
authorMatthew Dillon <dillon@apollo.backplane.com>
Tue, 19 Jan 2010 22:45:46 +0000 (14:45 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Tue, 19 Jan 2010 23:00:13 +0000 (15:00 -0800)
* Add kern/vfs_vm.c with a new API for vtruncbuf() and vnode_pager_setsize()
  called nvtruncbuf(), nvextendbuf(), and nvnode_pager_setsize().  This
  API solves numerous problems with data coherency between the VM and
  buffer cache subsystems.

  Generally speaking what this API does is allow the VM pages backing the
  buffer straddling EOF in a file to remain valid instead of invalidating
  them.  Take NFS for example with 32K buffers and, say, a 16385 byte
  file.  The NFS buffer cache buffer is backed by 8 x 4K VM pages but
  the actual file only requires 5 x 4K pages.  This API keeps all 8 VM
  pages valid.

  This API also handles zeroing out portions of the buffer after truncation
  and zero-extending portions of the buffer after a file extension.

  NFS has been migrated to the new API.  HAMMER will soon follow.  UFS and
  EXT2FS are harder due to their far more complex buffer cache sizing
  operations (due to their fragment vs full-sized block handling).

* Remodel the NFS client to use the new API.  This allows NFS to consolidate
  all truncation and extension operations into nfs_meta_setsize(), including
  all code which previously had to deal with special buffer cache / VM
  cases related to truncation and extension.

* Fix a bug in kern/vfs_bio.c where NFS buffers requiring the clearing
  of B_NEEDCOMMIT failed to also clear B_CLUSTEROK, leading to occassional
  attempts by NFS to issue RPCs larger than the NFS I/O block size (resulting
  in a panic).

* NFS now uses vop_stdgetpages() and vop_stdputpages().  The NFS-specific
  nfs_getpages() and nfs_putpages() has been removed.  Remove a vinvalbuf()
  in the nfs_bioread() code on remote-directory modification which was
  deadlocking getpages.  This needs more work.

* Simplify the local-vs-remote modification tests in NFS.  This needs more
  work.  What was happening, generally, was that the larger number of
  RPCs inflight allowed by the NFS client was creating too much confusion
  in the attribute feedback in the RPC replies, causing the NFS client to
  lose track of the file's actual size during heavy modifying operations
  (aka fsx tests).

sys/conf/files
sys/kern/vfs_bio.c
sys/kern/vfs_vm.c [new file with mode: 0644]
sys/sys/vnode.h
sys/vfs/nfs/nfs.h
sys/vfs/nfs/nfs_bio.c
sys/vfs/nfs/nfs_subs.c
sys/vfs/nfs/nfs_vnops.c
sys/vfs/nfs/nfsnode.h
sys/vm/vm_extern.h

index 5137fdb..c2efd66 100644 (file)
@@ -771,6 +771,7 @@ kern/vfs_jops.c             standard
 kern/vfs_lookup.c      standard
 kern/vfs_nlookup.c     standard
 kern/vfs_subr.c                standard
+kern/vfs_vm.c          standard
 kern/vfs_lock.c                standard
 kern/vfs_mount.c       standard
 kern/vfs_rangelock.c   standard
index 5b6a55d..19f0f8c 100644 (file)
@@ -3927,6 +3927,9 @@ vfs_clean_one_page(struct buf *bp, int pageno, vm_page_t m)
         * NFS if B_NEEDCOMMIT is also set).  So for the purposes of clearing
         * B_NEEDCOMMIT we only test the dirty bits covered by the buffer.
         * This also saves some console spam.
+        *
+        * When clearing B_NEEDCOMMIT we must also clear B_CLUSTEROK,
+        * NFS can handle huge commits but not huge writes.
         */
        vm_page_test_dirty(m);
        if (m->dirty) {
@@ -3937,7 +3940,7 @@ vfs_clean_one_page(struct buf *bp, int pageno, vm_page_t m)
                                "loff=%jx,%d flgs=%08x clr B_NEEDCOMMIT\n",
                                bp, (intmax_t)bp->b_loffset, bp->b_bcount,
                                bp->b_flags);
-                       bp->b_flags &= ~B_NEEDCOMMIT;
+                       bp->b_flags &= ~(B_NEEDCOMMIT | B_CLUSTEROK);
                }
                if (bp->b_dirtyoff > soff - xoff)
                        bp->b_dirtyoff = soff - xoff;
diff --git a/sys/kern/vfs_vm.c b/sys/kern/vfs_vm.c
new file mode 100644 (file)
index 0000000..5cae130
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2010 The DragonFly Project.  All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Implements new VFS/VM coherency functions.  For conforming VFSs
+ * we treat the backing VM object slightly differently.  Instead of
+ * maintaining a number of pages to exactly fit the size of the file
+ * we instead maintain pages to fit the entire contents of the last
+ * buffer cache buffer used by the file.
+ *
+ * For VFSs like NFS and HAMMER which use (generally speaking) fixed
+ * sized buffers this greatly reduces the complexity of VFS/VM interactions.
+ *
+ * Truncations no longer invalidate pages covered by the buffer cache
+ * beyond the file EOF which still fit within the file's last buffer.
+ * We simply unmap them and do not allow userland to fault them in.
+ *
+ * The VFS is no longer responsible for zero-filling buffers during a
+ * truncation, the last buffer will be automatically zero-filled by
+ * nvtruncbuf().
+ *
+ * This code is intended to (eventually) replace vtruncbuf() and
+ * vnode_pager_setsize().
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/buf.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mount.h>
+#include <sys/proc.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/unistd.h>
+#include <sys/vmmeter.h>
+#include <sys/vnode.h>
+
+#include <machine/limits.h>
+
+#include <vm/vm.h>
+#include <vm/vm_object.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_kern.h>
+#include <vm/pmap.h>
+#include <vm/vm_map.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+#include <vm/vnode_pager.h>
+#include <vm/vm_zone.h>
+
+#include <sys/buf2.h>
+#include <sys/thread2.h>
+#include <sys/sysref2.h>
+#include <sys/mplock2.h>
+
+static int nvtruncbuf_bp_trunc_cmp(struct buf *bp, void *data);
+static int nvtruncbuf_bp_trunc(struct buf *bp, void *data);
+static int nvtruncbuf_bp_metasync_cmp(struct buf *bp, void *data);
+static int nvtruncbuf_bp_metasync(struct buf *bp, void *data);
+
+/*
+ * Truncate a file's buffer and pages to a specified length. The
+ * byte-granular length of the file is specified along with the block
+ * size of the buffer containing that offset.
+ *
+ * If the last buffer straddles the length its contents will be zero-filled
+ * as appropriate.  All buffers and pages after the last buffer will be
+ * destroyed.  The last buffer itself will be destroyed only if the length
+ * is exactly aligned with it.
+ */
+int
+nvtruncbuf(struct vnode *vp, off_t length, int blksize)
+{
+       off_t truncloffset;
+       off_t truncboffset;
+       const char *filename;
+       lwkt_tokref vlock;
+       struct buf *bp;
+       int count;
+       int boff;
+       int error;
+
+       /*
+        * Round up to the *next* block, then destroy the buffers in question.
+        * Since we are only removing some of the buffers we must rely on the
+        * scan count to determine whether a loop is necessary.
+        *
+        * Destroy any pages beyond the last buffer.
+        */
+       boff = (int)(length % blksize);
+       if (boff)
+               truncloffset = length + (blksize - boff);
+       else
+               truncloffset = length;
+
+       lwkt_gettoken(&vlock, &vp->v_token);
+       do {
+               count = RB_SCAN(buf_rb_tree, &vp->v_rbclean_tree,
+                               nvtruncbuf_bp_trunc_cmp,
+                               nvtruncbuf_bp_trunc, &truncloffset);
+               count += RB_SCAN(buf_rb_tree, &vp->v_rbdirty_tree,
+                               nvtruncbuf_bp_trunc_cmp,
+                               nvtruncbuf_bp_trunc, &truncloffset);
+       } while(count);
+
+       nvnode_pager_setsize(vp, length, blksize);
+
+       /*
+        * Zero-fill the area beyond the file EOF that still fits within
+        * the last buffer.  Even though we are modifying the contents
+        * of a buffer we are doing so beyond the file EOF and it doesn't
+        * count as a real modification.
+        *
+        * The VFS is responsible for dealing with the actual truncation.
+        */
+       if (boff) {
+               truncboffset = length - boff;
+               error = bread(vp, truncboffset, blksize, &bp);
+               if (error == 0) {
+                       bzero(bp->b_data + boff, blksize - boff);
+                       if (bp->b_flags & B_DELWRI) {
+                               if (bp->b_dirtyoff > boff)
+                                       bp->b_dirtyoff = boff;
+                               if (bp->b_dirtyend > boff)
+                                       bp->b_dirtyend = boff;
+                       }
+                       bqrelse(bp);
+               }
+       } else {
+               error = 0;
+       }
+
+       /*
+        * For safety, fsync any remaining metadata if the file is not being
+        * truncated to 0.  Since the metadata does not represent the entire
+        * dirty list we have to rely on the hit count to ensure that we get
+        * all of it.
+        */
+       if (length > 0) {
+               do {
+                       count = RB_SCAN(buf_rb_tree, &vp->v_rbdirty_tree,
+                                       nvtruncbuf_bp_metasync_cmp,
+                                       nvtruncbuf_bp_metasync, vp);
+               } while (count);
+       }
+
+       /*
+        * It is possible to have in-progress I/O from buffers that were
+        * not part of the truncation.  This should not happen if we
+        * are truncating to 0-length.
+        */
+       bio_track_wait(&vp->v_track_write, 0, 0);
+
+       /*
+        * Debugging only
+        */
+       spin_lock_wr(&vp->v_spinlock);
+       filename = TAILQ_FIRST(&vp->v_namecache) ?
+                  TAILQ_FIRST(&vp->v_namecache)->nc_name : "?";
+       spin_unlock_wr(&vp->v_spinlock);
+
+       /*
+        * Make sure no buffers were instantiated while we were trying
+        * to clean out the remaining VM pages.  This could occur due
+        * to busy dirty VM pages being flushed out to disk.
+        */
+       do {
+               count = RB_SCAN(buf_rb_tree, &vp->v_rbclean_tree,
+                               nvtruncbuf_bp_trunc_cmp,
+                               nvtruncbuf_bp_trunc, &truncloffset);
+               count += RB_SCAN(buf_rb_tree, &vp->v_rbdirty_tree,
+                               nvtruncbuf_bp_trunc_cmp,
+                               nvtruncbuf_bp_trunc, &truncloffset);
+               if (count) {
+                       kprintf("Warning: vtruncbuf():  Had to re-clean %d "
+                              "left over buffers in %s\n", count, filename);
+               }
+       } while(count);
+
+       lwkt_reltoken(&vlock);
+
+       return (error);
+}
+
+/*
+ * The callback buffer is beyond the new file EOF and must be destroyed.
+ * Note that the compare function must conform to the RB_SCAN's requirements.
+ */
+static
+int
+nvtruncbuf_bp_trunc_cmp(struct buf *bp, void *data)
+{
+       if (bp->b_loffset >= *(off_t *)data)
+               return(0);
+       return(-1);
+}
+
+static
+int
+nvtruncbuf_bp_trunc(struct buf *bp, void *data)
+{
+       /*
+        * Do not try to use a buffer we cannot immediately lock, but sleep
+        * anyway to prevent a livelock.  The code will loop until all buffers
+        * can be acted upon.
+        */
+       if (BUF_LOCK(bp, LK_EXCLUSIVE | LK_NOWAIT)) {
+               if (BUF_LOCK(bp, LK_EXCLUSIVE|LK_SLEEPFAIL) == 0)
+                       BUF_UNLOCK(bp);
+       } else {
+               bremfree(bp);
+               bp->b_flags |= (B_INVAL | B_RELBUF | B_NOCACHE);
+               brelse(bp);
+       }
+       return(1);
+}
+
+/*
+ * Fsync all meta-data after truncating a file to be non-zero.  Only metadata
+ * blocks (with a negative loffset) are scanned.
+ * Note that the compare function must conform to the RB_SCAN's requirements.
+ */
+static int
+nvtruncbuf_bp_metasync_cmp(struct buf *bp, void *data)
+{
+       if (bp->b_loffset < 0)
+               return(0);
+       return(1);
+}
+
+static int
+nvtruncbuf_bp_metasync(struct buf *bp, void *data)
+{
+       struct vnode *vp = data;
+
+       if (bp->b_flags & B_DELWRI) {
+               /*
+                * Do not try to use a buffer we cannot immediately lock,
+                * but sleep anyway to prevent a livelock.  The code will
+                * loop until all buffers can be acted upon.
+                */
+               if (BUF_LOCK(bp, LK_EXCLUSIVE | LK_NOWAIT)) {
+                       if (BUF_LOCK(bp, LK_EXCLUSIVE|LK_SLEEPFAIL) == 0)
+                               BUF_UNLOCK(bp);
+               } else {
+                       bremfree(bp);
+                       if (bp->b_vp == vp)
+                               bawrite(bp);
+                       else
+                               bwrite(bp);
+               }
+               return(1);
+       } else {
+               return(0);
+       }
+}
+
+/*
+ * Extend a file's buffer and pages to a new, larger size.  Note that the
+ * blocksize passed is for the buffer covering the old file size, NOT the
+ * new file size.
+ *
+ * To make this explicit we require the old length to passed even though
+ * we can acquire it from vp->v_filesize.
+ *
+ * If the caller intends to immediately write into the newly extended
+ * space pass trivial == 1.  If trivial is 0 the original buffer will be
+ * zero-filled as necessary to clean out any junk in the extended space.
+ *
+ * NOTE: We do not zero-fill to the end of the buffer or page to remove
+ *      mmap cruft since userland can just re-cruft it.  Filesystems are
+ *      responsible for zero-filling extra space beyond the file EOF during
+ *      strategy write functions, or zero-filling junk areas on read.
+ */
+int
+nvextendbuf(struct vnode *vp, off_t olength, off_t nlength,
+           int oblksize, int nblksize, int trivial)
+{
+       off_t truncboffset;
+       struct buf *bp;
+       int boff;
+       int error;
+
+       error = 0;
+       nvnode_pager_setsize(vp, nlength, nblksize);
+       if (trivial == 0) {
+               boff = (int)(olength % oblksize);
+               truncboffset = olength - boff;
+
+               if (boff) {
+                       error = bread(vp, truncboffset, oblksize, &bp);
+                       if (error == 0) {
+                               bzero(bp->b_data + boff, oblksize - boff);
+                               bqrelse(bp);
+                       }
+               }
+       }
+       return (error);
+}
+
+/*
+ * Set vp->v_filesize and vp->v_object->size, destroy pages beyond
+ * the last buffer when truncating.
+ *
+ * This function does not do any zeroing or invalidating of partially
+ * overlapping pages.  Zeroing is the responsibility of nvtruncbuf().
+ * However, it does unmap VM pages from the user address space on a
+ * page-granular (verses buffer cache granular) basis.
+ */
+void
+nvnode_pager_setsize(struct vnode *vp, off_t length, int blksize)
+{
+       vm_pindex_t nobjsize;
+       vm_pindex_t oobjsize;
+       vm_pindex_t pi;
+       vm_object_t object;
+       vm_page_t m;
+       off_t truncboffset;
+       int boff;
+
+       /*
+        * Degenerate conditions
+        */
+       if ((object = vp->v_object) == NULL)
+               return;
+       if (length == vp->v_filesize)
+               return;
+
+       /*
+        * Calculate the size of the VM object, coverage includes
+        * the buffer straddling EOF.  If EOF is buffer-aligned
+        * we don't bother.
+        *
+        * Buffers do not have to be page-aligned.  Make sure
+        * nobjsize is beyond the last page of the buffer.
+        */
+       boff = (int)(length % blksize);
+       truncboffset = length - boff;
+       oobjsize = object->size;
+       if (boff)
+               nobjsize = OFF_TO_IDX(truncboffset + blksize + PAGE_MASK);
+       else
+               nobjsize = OFF_TO_IDX(truncboffset + PAGE_MASK);
+       object->size = nobjsize;
+
+       if (length < vp->v_filesize) {
+               /*
+                * File has shrunk, toss any cached pages beyond
+                * the end of the buffer (blksize aligned) for the
+                * new EOF.
+                */
+               vp->v_filesize = length;
+               if (nobjsize < oobjsize) {
+                       vm_object_page_remove(object, nobjsize, oobjsize,
+                                             FALSE);
+               }
+
+               /*
+                * Unmap any pages (page aligned) beyond the new EOF.
+                * The pages remain part of the (last) buffer and are not
+                * invalidated.
+                */
+               pi = OFF_TO_IDX(length + PAGE_MASK);
+               while (pi < nobjsize) {
+                       do {
+                               m = vm_page_lookup(object, pi);
+                       } while (m && vm_page_sleep_busy(m, TRUE, "vsetsz"));
+                       if (m) {
+                               vm_page_busy(m);
+                               vm_page_protect(m, VM_PROT_NONE);
+                               vm_page_wakeup(m);
+                       }
+                       ++pi;
+               }
+       } else {
+               /*
+                * File has expanded.
+                */
+               vp->v_filesize = length;
+       }
+}
index cdb0fa6..1d1c8de 100644 (file)
@@ -505,6 +505,11 @@ void       vgone_vxlocked (struct vnode *vp);
 int    vrevoke (struct vnode *vp, struct ucred *cred);
 int    vinvalbuf (struct vnode *vp, int save, int slpflag, int slptimeo);
 int    vtruncbuf (struct vnode *vp, off_t length, int blksize);
+void   vnode_pager_setsize (struct vnode *, vm_ooffset_t);
+int    nvtruncbuf (struct vnode *vp, off_t length, int blksize);
+void   nvnode_pager_setsize (struct vnode *vp, off_t length, int blksize);
+int    nvextendbuf(struct vnode *vp, off_t olength, off_t nlength,
+               int oblksize, int nblksize, int trivial);
 int    vfsync(struct vnode *vp, int waitfor, int passes,
                int (*checkdef)(struct buf *),
                int (*waitoutput)(struct vnode *, struct thread *));
index 56ec4c0..5e71021 100644 (file)
@@ -783,8 +783,8 @@ int nfsrv_write (struct nfsrv_descript *nfsd, struct nfssvc_sock *slp,
                         struct thread *td, struct mbuf **mrq);
 void   nfsrv_rcv (struct socket *so, void *arg, int waitflag);
 void   nfsrv_slpderef (struct nfssvc_sock *slp);
-struct buf *nfs_meta_setsize (struct vnode *vp, struct thread *td,
-                       off_t nbase, int boff, int bytes);
+int    nfs_meta_setsize (struct vnode *vp, struct thread *td,
+                       off_t nbase, int trivial);
 int    nfs_clientd(struct nfsmount *nmp, struct ucred *cred,
                        struct nfsd_cargs *ncd, int flag, caddr_t argp,
                        struct thread *td);
index 96ff2f8..364891d 100644 (file)
@@ -79,270 +79,6 @@ static void nfs_readrpc_bio_done(nfsm_info_t info);
 static void nfs_writerpc_bio_done(nfsm_info_t info);
 static void nfs_commitrpc_bio_done(nfsm_info_t info);
 
-/*
- * Vnode op for VM getpages.
- *
- * nfs_getpages(struct vnode *a_vp, vm_page_t *a_m, int a_count,
- *             int a_reqpage, vm_ooffset_t a_offset)
- */
-int
-nfs_getpages(struct vop_getpages_args *ap)
-{
-       struct thread *td = curthread;          /* XXX */
-       int i, error, nextoff, size, toff, count, npages;
-       struct uio uio;
-       struct iovec iov;
-       char *kva;
-       struct vnode *vp;
-       struct nfsmount *nmp;
-       vm_page_t *pages;
-       vm_page_t m;
-       struct msf_buf *msf;
-
-       vp = ap->a_vp;
-       nmp = VFSTONFS(vp->v_mount);
-       pages = ap->a_m;
-       count = ap->a_count;
-
-       if (vp->v_object == NULL) {
-               kprintf("nfs_getpages: called with non-merged cache vnode??\n");
-               return VM_PAGER_ERROR;
-       }
-
-       if ((nmp->nm_flag & NFSMNT_NFSV3) != 0 &&
-           (nmp->nm_state & NFSSTA_GOTFSINFO) == 0)
-               (void)nfs_fsinfo(nmp, vp, td);
-
-       npages = btoc(count);
-
-       /*
-        * NOTE that partially valid pages may occur in cases other
-        * then file EOF, such as when a file is partially written and
-        * ftruncate()-extended to a larger size.   It is also possible
-        * for the valid bits to be set on garbage beyond the file EOF and
-        * clear in the area before EOF (e.g. m->valid == 0xfc), which can
-        * occur due to vtruncbuf() and the buffer cache's handling of
-        * pages which 'straddle' buffers or when b_bufsize is not a 
-        * multiple of PAGE_SIZE.... the buffer cache cannot normally
-        * clear the extra bits.  This kind of situation occurs when you
-        * make a small write() (m->valid == 0x03) and then mmap() and
-        * fault in the buffer(m->valid = 0xFF).  When NFS flushes the
-        * buffer (vinvalbuf() m->valid = 0xFC) we are left with a mess.
-        *
-        * This is combined with the possibility that the pages are partially
-        * dirty or that there is a buffer backing the pages that is dirty
-        * (even if m->dirty is 0).
-        *
-        * To solve this problem several hacks have been made:  (1) NFS
-        * guarentees that the IO block size is a multiple of PAGE_SIZE and
-        * (2) The buffer cache, when invalidating an NFS buffer, will
-        * disregard the buffer's fragmentory b_bufsize and invalidate
-        * the whole page rather then just the piece the buffer owns.
-        *
-        * This allows us to assume that a partially valid page found here
-        * is fully valid (vm_fault will zero'd out areas of the page not
-        * marked as valid).
-        */
-       m = pages[ap->a_reqpage];
-       if (m->valid != 0) {
-               for (i = 0; i < npages; ++i) {
-                       if (i != ap->a_reqpage)
-                               vnode_pager_freepage(pages[i]);
-               }
-               return(0);
-       }
-
-       /*
-        * Use an MSF_BUF as a medium to retrieve data from the pages.
-        */
-       msf_map_pagelist(&msf, pages, npages, 0);
-       KKASSERT(msf);
-       kva = msf_buf_kva(msf);
-
-       iov.iov_base = kva;
-       iov.iov_len = count;
-       uio.uio_iov = &iov;
-       uio.uio_iovcnt = 1;
-       uio.uio_offset = IDX_TO_OFF(pages[0]->pindex);
-       uio.uio_resid = count;
-       uio.uio_segflg = UIO_SYSSPACE;
-       uio.uio_rw = UIO_READ;
-       uio.uio_td = td;
-
-       error = nfs_readrpc_uio(vp, &uio);
-       msf_buf_free(msf);
-
-       if (error && ((int)uio.uio_resid == count)) {
-               kprintf("nfs_getpages: error %d\n", error);
-               for (i = 0; i < npages; ++i) {
-                       if (i != ap->a_reqpage)
-                               vnode_pager_freepage(pages[i]);
-               }
-               return VM_PAGER_ERROR;
-       }
-
-       /*
-        * Calculate the number of bytes read and validate only that number
-        * of bytes.  Note that due to pending writes, size may be 0.  This
-        * does not mean that the remaining data is invalid!
-        */
-
-       size = count - (int)uio.uio_resid;
-
-       for (i = 0, toff = 0; i < npages; i++, toff = nextoff) {
-               nextoff = toff + PAGE_SIZE;
-               m = pages[i];
-
-               m->flags &= ~PG_ZERO;
-
-               /*
-                * NOTE: vm_page_undirty/clear_dirty etc do not clear the
-                *       pmap modified bit.
-                */
-               if (nextoff <= size) {
-                       /*
-                        * Read operation filled an entire page
-                        */
-                       m->valid = VM_PAGE_BITS_ALL;
-                       vm_page_undirty(m);
-               } else if (size > toff) {
-                       /*
-                        * Read operation filled a partial page.
-                        */
-                       m->valid = 0;
-                       vm_page_set_valid(m, 0, size - toff);
-                       vm_page_clear_dirty_end_nonincl(m, 0, size - toff);
-                       /* handled by vm_fault now        */
-                       /* vm_page_zero_invalid(m, TRUE); */
-               } else {
-                       /*
-                        * Read operation was short.  If no error occured
-                        * we may have hit a zero-fill section.   We simply
-                        * leave valid set to 0.
-                        */
-                       ;
-               }
-               if (i != ap->a_reqpage) {
-                       /*
-                        * Whether or not to leave the page activated is up in
-                        * the air, but we should put the page on a page queue
-                        * somewhere (it already is in the object).  Result:
-                        * It appears that emperical results show that
-                        * deactivating pages is best.
-                        */
-
-                       /*
-                        * Just in case someone was asking for this page we
-                        * now tell them that it is ok to use.
-                        */
-                       if (!error) {
-                               if (m->flags & PG_WANTED)
-                                       vm_page_activate(m);
-                               else
-                                       vm_page_deactivate(m);
-                               vm_page_wakeup(m);
-                       } else {
-                               vnode_pager_freepage(m);
-                       }
-               }
-       }
-       return 0;
-}
-
-/*
- * Vnode op for VM putpages.
- *
- * The pmap modified bit was cleared prior to the putpages and probably
- * couldn't get set again until after our I/O completed, since the page
- * should not be mapped.  But don't count on it.  The m->dirty bits must
- * be completely cleared when we finish even if the count is truncated.
- *
- * nfs_putpages(struct vnode *a_vp, vm_page_t *a_m, int a_count, int a_sync,
- *             int *a_rtvals, vm_ooffset_t a_offset)
- */
-int
-nfs_putpages(struct vop_putpages_args *ap)
-{
-       struct thread *td = curthread;
-       struct uio uio;
-       struct iovec iov;
-       char *kva;
-       int iomode, must_commit, i, error, npages, count;
-       off_t offset;
-       int *rtvals;
-       struct vnode *vp;
-       struct nfsmount *nmp;
-       struct nfsnode *np;
-       vm_page_t *pages;
-       struct msf_buf *msf;
-
-       vp = ap->a_vp;
-       np = VTONFS(vp);
-       nmp = VFSTONFS(vp->v_mount);
-       pages = ap->a_m;
-       count = ap->a_count;
-       rtvals = ap->a_rtvals;
-       npages = btoc(count);
-       offset = IDX_TO_OFF(pages[0]->pindex);
-
-       if ((nmp->nm_flag & NFSMNT_NFSV3) != 0 &&
-           (nmp->nm_state & NFSSTA_GOTFSINFO) == 0)
-               (void)nfs_fsinfo(nmp, vp, td);
-
-       for (i = 0; i < npages; i++) {
-               rtvals[i] = VM_PAGER_AGAIN;
-       }
-
-       /*
-        * When putting pages, do not extend file past EOF.
-        */
-
-       if (offset + count > np->n_size) {
-               count = np->n_size - offset;
-               if (count < 0)
-                       count = 0;
-       }
-
-       /*
-        * Use an MSF_BUF as a medium to retrieve data from the pages.
-        */
-       msf_map_pagelist(&msf, pages, npages, 0);
-       KKASSERT(msf);
-       kva = msf_buf_kva(msf);
-
-       iov.iov_base = kva;
-       iov.iov_len = count;
-       uio.uio_iov = &iov;
-       uio.uio_iovcnt = 1;
-       uio.uio_offset = offset;
-       uio.uio_resid = (size_t)count;
-       uio.uio_segflg = UIO_SYSSPACE;
-       uio.uio_rw = UIO_WRITE;
-       uio.uio_td = td;
-
-       if ((ap->a_sync & VM_PAGER_PUT_SYNC) == 0)
-           iomode = NFSV3WRITE_UNSTABLE;
-       else
-           iomode = NFSV3WRITE_FILESYNC;
-
-       error = nfs_writerpc_uio(vp, &uio, &iomode, &must_commit);
-
-       msf_buf_free(msf);
-
-       if (error == 0) {
-               int nwritten;
-
-               nwritten = round_page(count - (int)uio.uio_resid) / PAGE_SIZE;
-               for (i = 0; i < nwritten; i++) {
-                       rtvals[i] = VM_PAGER_OK;
-                       vm_page_undirty(pages[i]);
-               }
-               if (must_commit)
-                       nfs_clearcommit(vp->v_mount);
-       }
-       return rtvals[0];
-}
-
 /*
  * Vnode op for read using bio
  */
@@ -410,13 +146,19 @@ nfs_bioread(struct vnode *vp, struct uio *uio, int ioflag)
        error = VOP_GETATTR(vp, &vattr);
        if (error)
                return (error);
+
+       /*
+        * This can deadlock getpages/putpages for regular
+        * files.  Only do it for directories.
+        */
        if (np->n_flag & NRMODIFIED) {
-               if (vp->v_type == VDIR)
+               if (vp->v_type == VDIR) {
                        nfs_invaldir(vp);
-               error = nfs_vinvalbuf(vp, V_SAVE, 1);
-               if (error)
-                       return (error);
-               np->n_flag &= ~NRMODIFIED;
+                       error = nfs_vinvalbuf(vp, V_SAVE, 1);
+                       if (error)
+                               return (error);
+                       np->n_flag &= ~NRMODIFIED;
+               }
        }
 
        /*
@@ -755,6 +497,7 @@ nfs_write(struct vop_write_args *ap)
        int haverslock = 0;
        int bcount;
        int biosize;
+       int trivial;
 
 #ifdef DIAGNOSTIC
        if (uio->uio_rw != UIO_WRITE)
@@ -840,7 +583,7 @@ restart:
         * Maybe this should be above the vnode op call, but so long as
         * file servers have no limits, i don't think it matters
         */
-       if (td->td_proc && uio->uio_offset + uio->uio_resid >
+       if (td && td->td_proc && uio->uio_offset + uio->uio_resid >
              td->td_proc->p_rlimit[RLIMIT_FSIZE].rlim_cur) {
                lwpsignal(td->td_proc, td->td_lwp, SIGXFSZ);
                if (haverslock)
@@ -863,10 +606,12 @@ again:
                 */
                if (uio->uio_offset + bytes > np->n_size) {
                        np->n_flag |= NLMODIFIED;
-                       bp = nfs_meta_setsize(vp, td, loffset, boff, bytes);
-               } else {
-                       bp = nfs_getcacheblk(vp, loffset, biosize, td);
+                       trivial = (uio->uio_segflg != UIO_NOCOPY &&
+                                  uio->uio_offset <= np->n_size);
+                       nfs_meta_setsize(vp, td, uio->uio_offset + bytes,
+                                        trivial);
                }
+               bp = nfs_getcacheblk(vp, loffset, biosize, td);
                if (bp == NULL) {
                        error = EINTR;
                        break;
@@ -1486,94 +1231,31 @@ nfs_doio(struct vnode *vp, struct bio *bio, struct thread *td)
 }
 
 /*
- * Used to aid in handling ftruncate() and non-trivial write-extend
- * operations on the NFS client side.  Note that trivial write-extend
- * operations (appending with no write hole) are handled by nfs_write()
- * directly to avoid silly flushes.
+ * Handle all truncation, write-extend, and ftruncate()-extend operations
+ * on the NFS lcient side.
  *
- * Truncation creates a number of special problems for NFS.  We have to
- * throw away VM pages and buffer cache buffers that are beyond EOF, and
- * we have to properly handle VM pages or (potentially dirty) buffers
- * that straddle the truncation point.
- *
- * File extension no longer has an issue now that the buffer size is
- * fixed.  When extending the intended overwrite area is specified
- * by (boff, bytes).  This function uses the parameters to determine
- * what areas must be zerod.  If there are no gaps we set B_CACHE.
+ * We use the new API in kern/vfs_vm.c to perform these operations in a
+ * VM-friendly way.  With this API VM pages are properly zerod and pages
+ * still mapped into the buffer straddling EOF are not invalidated.
  */
-struct buf *
-nfs_meta_setsize(struct vnode *vp, struct thread *td, off_t nbase,
-                int boff, int bytes)
+int
+nfs_meta_setsize(struct vnode *vp, struct thread *td, off_t nsize, int trivial)
 {
-
        struct nfsnode *np = VTONFS(vp);
-       off_t osize = np->n_size;
-       off_t nsize;
+       off_t osize;
        int biosize = vp->v_mount->mnt_stat.f_iosize;
-       int error = 0;
-       struct buf *bp;
+       int error;
 
-       nsize = nbase + boff + bytes;
+       osize = np->n_size;
        np->n_size = nsize;
 
        if (nsize < osize) {
-               /*
-                * vtruncbuf() doesn't get the buffer overlapping the 
-                * truncation point, but it will invalidate pages in
-                * that buffer and zero the appropriate byte range in
-                * the page straddling EOF.
-                */
-               error = vtruncbuf(vp, nsize, biosize);
-
-               /*
-                * NFS doesn't do a good job tracking changes in the EOF
-                * so it may not revisit the buffer if the file is extended.
-                *
-                * After truncating just clear B_CACHE on the buffer
-                * straddling EOF.  If the buffer is dirty then clean
-                * out the portion beyond the file EOF.
-                */
-               if (error) {
-                       bp = NULL;
-               } else {
-                       bp = nfs_getcacheblk(vp, nbase, biosize, td);
-                       if (bp->b_flags & B_DELWRI) {
-                               if (bp->b_dirtyoff > bp->b_bcount)
-                                       bp->b_dirtyoff = bp->b_bcount;
-                               if (bp->b_dirtyend > bp->b_bcount)
-                                       bp->b_dirtyend = bp->b_bcount;
-                               boff = (int)nsize & (biosize - 1);
-                               bzero(bp->b_data + boff, biosize - boff);
-                       } else if (nsize != nbase) {
-                               boff = (int)nsize & (biosize - 1);
-                               bzero(bp->b_data + boff, biosize - boff);
-                       }
-               }
+               error = nvtruncbuf(vp, nsize, biosize);
        } else {
-               /*
-                * The newly expanded portions of the buffer should already
-                * be zero'd out if B_CACHE is set.  If B_CACHE is not
-                * set and the buffer is beyond osize we can safely zero it
-                * and set B_CACHE to avoid issuing unnecessary degenerate
-                * read rpcs.
-                *
-                * Don't do this if the caller is going to overwrite the
-                * entire buffer anyway (and also don't set B_CACHE!).
-                * This allows the caller to optimize the operation.
-                */
-               KKASSERT(nsize >= 0);
-               vnode_pager_setsize(vp, (vm_ooffset_t)nsize);
-
-               bp = nfs_getcacheblk(vp, nbase, biosize, td);
-               if ((bp->b_flags & B_CACHE) == 0 && nbase >= osize &&
-                   !(boff == 0 && bytes == biosize)
-               ) {
-                       bzero(bp->b_data, biosize);
-                       bp->b_flags |= B_CACHE;
-                       bp->b_flags &= ~(B_ERROR | B_INVAL);
-               }
+               error = nvextendbuf(vp, osize, nsize,
+                                   biosize, biosize, trivial);
        }
-       return(bp);
+       return(error);
 }
 
 /*
index 01a1c14..48d4d56 100644 (file)
@@ -786,6 +786,15 @@ nfs_loadattrcache(struct vnode *vp, struct mbuf **mdp, caddr_t *dposp,
        np->n_attrstamp = time_second;
        if (vap->va_size != np->n_size) {
                if (vap->va_type == VREG) {
+                       /*
+                        * Get rid of all the junk we had before and just
+                        * set NRMODIFIED if NLMODIFIED is 0.  Depend on
+                        * occassionally flushing our dirty buffers to
+                        * clear both the NLMODIFIED and NRMODIFIED flags.
+                        */
+                       if ((np->n_flag & NLMODIFIED) == 0)
+                               np->n_flag |= NRMODIFIED;
+#if 0
                        if ((lattr_flags & NFS_LATTR_NOSHRINK) && 
                            vap->va_size < np->n_size) {
                                /*
@@ -839,7 +848,8 @@ nfs_loadattrcache(struct vnode *vp, struct mbuf **mdp, caddr_t *dposp,
                                np->n_size = vap->va_size;
                                np->n_flag |= NRMODIFIED;
                        }
-                       vnode_pager_setsize(vp, np->n_size);
+                       nvnode_pager_setsize(vp, np->n_size);
+#endif
                } else {
                        np->n_size = vap->va_size;
                }
@@ -929,15 +939,9 @@ nfs_getattrcache(struct vnode *vp, struct vattr *vaper)
         */
        if (vap->va_size != np->n_size) {
                if (vap->va_type == VREG) {
-                       if (np->n_flag & NLMODIFIED) {
-                               if (vap->va_size < np->n_size)
-                                       vap->va_size = np->n_size;
-                               else
-                                       np->n_size = vap->va_size;
-                       } else {
-                               np->n_size = vap->va_size;
-                       }
-                       vnode_pager_setsize(vp, np->n_size);
+                       if (np->n_flag & NLMODIFIED)
+                               vap->va_size = np->n_size;
+                       nfs_meta_setsize(vp, curthread, vap->va_size, 0);
                } else {
                        np->n_size = vap->va_size;
                }
index 92f2f16..24f7b2d 100644 (file)
@@ -140,8 +140,8 @@ struct vop_ops nfsv2_vnode_vops = {
        .vop_old_create =       nfs_create,
        .vop_fsync =            nfs_fsync,
        .vop_getattr =          nfs_getattr,
-       .vop_getpages =         nfs_getpages,
-       .vop_putpages =         nfs_putpages,
+       .vop_getpages =         vop_stdgetpages,
+       .vop_putpages =         vop_stdputpages,
        .vop_inactive =         nfs_inactive,
        .vop_old_link =         nfs_link,
        .vop_old_lookup =       nfs_lookup,
@@ -677,7 +677,6 @@ nfs_setattr(struct vop_setattr_args *ap)
        struct vnode *vp = ap->a_vp;
        struct nfsnode *np = VTONFS(vp);
        struct vattr *vap = ap->a_vap;
-       struct buf *bp;
        int biosize = vp->v_mount->mnt_stat.f_iosize;
        int error = 0;
        int boff;
@@ -730,40 +729,19 @@ nfs_setattr(struct vop_setattr_args *ap)
                        if (vp->v_mount->mnt_flag & MNT_RDONLY)
                                return (EROFS);
 
-                       /*
-                        * This is nasty.  The RPCs we send to flush pending
-                        * data often return attribute information which is
-                        * cached via a callback to nfs_loadattrcache(), which
-                        * has the effect of changing our notion of the file
-                        * size.  Due to flushed appends and other operations
-                        * the file size can be set to virtually anything, 
-                        * including values that do not match either the old
-                        * or intended file size.
-                        *
-                        * When this condition is detected we must loop to
-                        * try the operation again.  Hopefully no more
-                        * flushing is required on the loop so it works the
-                        * second time around.  THIS CASE ALMOST ALWAYS
-                        * HAPPENS!
-                        */
                        tsize = np->n_size;
 again:
                        boff = (int)vap->va_size & (biosize - 1);
-                       bp = nfs_meta_setsize(vp, td, vap->va_size - boff,
-                                             boff, 0);
-                       if (bp) {
-                               error = 0;
-                               brelse(bp);
-                       } else {
-                               error = EINTR;
-                       }
+                       error = nfs_meta_setsize(vp, td, vap->va_size, 0);
 
+#if 0
                        if (np->n_flag & NLMODIFIED) {
                            if (vap->va_size == 0)
                                error = nfs_vinvalbuf(vp, 0, 1);
                            else
                                error = nfs_vinvalbuf(vp, V_SAVE, 1);
                        }
+#endif
                        /*
                         * note: this loop case almost always happens at 
                         * least once per truncation.
@@ -811,8 +789,7 @@ again:
        }
        if (error && vap->va_size != VNOVAL) {
                np->n_size = np->n_vattr.va_size = tsize;
-               boff = (int)np->n_size & (biosize - 1);
-               vnode_pager_setsize(vp, np->n_size);
+               nfs_meta_setsize(vp, td, np->n_size, 0);
        }
        return (error);
 }
index 721a416..08324f3 100644 (file)
@@ -219,8 +219,6 @@ nfs_vpcred(struct vnode *vp, int ndflag)
 /*
  * Prototypes for NFS vnode operations
  */
-int    nfs_getpages (struct vop_getpages_args *);
-int    nfs_putpages (struct vop_putpages_args *);
 int    nfs_write (struct vop_write_args *);
 int    nfs_inactive (struct vop_inactive_args *);
 int    nfs_reclaim (struct vop_reclaim_args *);
index 4729729..0c452e3 100644 (file)
@@ -107,7 +107,6 @@ struct vmspace *vmspace_fork (struct vmspace *);
 void vmspace_exec (struct proc *, struct vmspace *);
 void vmspace_unshare (struct proc *);
 void vmspace_exitfree (struct proc *);
-void vnode_pager_setsize (struct vnode *, vm_ooffset_t);
 void vslock (caddr_t, u_int);
 void vsunlock (caddr_t, u_int);
 void vm_object_print (/* db_expr_t */ long, boolean_t, /* db_expr_t */ long,