Kernel - Fix issues with vnode_pager_setsize() (effects NFS only)
authorMatthew Dillon <dillon@apollo.backplane.com>
Mon, 24 Aug 2009 22:37:34 +0000 (15:37 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Mon, 24 Aug 2009 22:37:34 +0000 (15:37 -0700)
* When truncating a file to non-zero length, if the filesystem calls
  vnode_pager_setsize() with a truncation offset that is not a
  multiple of DEV_BSIZE vnode_pager_setsize() will improperly clear
  the dirty bit for the overlapping DEV_BSIZE'd block in the VM page
  straddling the truncation point.

  This only effects NFS.  The fsx filesystem test was failing due to
  a lost dirty page after a truncation.

Reported-by: Antonio Huete Jimenez <tuxillo@quantumachine.net>
sys/kern/kern_slaballoc.c
sys/kern/kern_subr.c
sys/sys/systm.h
sys/sys/uio.h
sys/vm/vm_page.c
sys/vm/vnode_pager.c

index ab1d8d2..c621b95 100644 (file)
@@ -202,6 +202,8 @@ MALLOC_DEFINE(M_IP6NDP, "ip6ndp", "IPv6 Neighbor Discovery");
  */
 static void kmeminit(void *dummy);
 
+char *ZeroPage;
+
 SYSINIT(kmem, SI_BOOT1_ALLOCATOR, SI_ORDER_FIRST, kmeminit, NULL)
 
 #ifdef INVARIANTS
@@ -243,6 +245,8 @@ kmeminit(void *dummy)
     for (i = 0; i < arysize(weirdary); ++i)
        weirdary[i] = WEIRD_ADDR;
 
+    ZeroPage = kmem_slab_alloc(PAGE_SIZE, PAGE_SIZE, M_WAITOK|M_ZERO);
+
     if (bootverbose)
        kprintf("Slab ZoneSize set to %dKB\n", ZoneSize / 1024);
 }
index e07f91e..5968f85 100644 (file)
@@ -139,6 +139,54 @@ uiomove(caddr_t cp, size_t n, struct uio *uio)
                curproc->p_flag = (curproc->p_flag & ~P_DEADLKTREAT) | save;
        return (error);
 }
+
+/*
+ * Like uiomove() but copies zero-fill.  Only allowed for UIO_READ,
+ * for obvious reasons.
+ */
+int
+uiomovez(size_t n, struct uio *uio)
+{
+       struct iovec *iov;
+       size_t cnt;
+       int error = 0;
+
+       KASSERT(uio->uio_rw == UIO_READ, ("uiomovez: mode"));
+       KASSERT(uio->uio_segflg != UIO_USERSPACE || uio->uio_td == curthread,
+               ("uiomove proc"));
+
+       while (n > 0 && uio->uio_resid) {
+               iov = uio->uio_iov;
+               cnt = iov->iov_len;
+               if (cnt == 0) {
+                       uio->uio_iov++;
+                       uio->uio_iovcnt--;
+                       continue;
+               }
+               if (cnt > n)
+                       cnt = n;
+
+               switch (uio->uio_segflg) {
+               case UIO_USERSPACE:
+                       error = copyout(ZeroPage, iov->iov_base, cnt);
+                       if (error)
+                               break;
+                       break;
+               case UIO_SYSSPACE:
+                       bzero(iov->iov_base, cnt);
+                       break;
+               case UIO_NOCOPY:
+                       break;
+               }
+               iov->iov_base = (char *)iov->iov_base + cnt;
+               iov->iov_len -= cnt;
+               uio->uio_resid -= cnt;
+               uio->uio_offset += cnt;
+               n -= cnt;
+       }
+       return (error);
+}
+
 /*
  * Wrapper for uiomove() that validates the arguments against a known-good
  * kernel buffer.
index 2ca81fd..c568957 100644 (file)
@@ -81,6 +81,7 @@ extern u_int64_t dumplo64;    /* block number into dumpdev, start of dump */
 extern cdev_t rootdev;         /* root device */
 extern cdev_t rootdevs[2];     /* possible root devices */
 extern char *rootdevnames[2];  /* names of possible root devices */
+extern char *ZeroPage;
 
 extern int boothowto;          /* reboot flags, from console subsystem */
 extern int bootverbose;                /* nonzero to print verbose messages */
index f2f0d2a..f0de1d0 100644 (file)
@@ -95,6 +95,7 @@ struct vm_page;
 
 void   uio_yield (void);
 int    uiomove (caddr_t, size_t, struct uio *);
+int    uiomovez (size_t, struct uio *);
 int    uiomove_frombuf (void *buf, size_t buflen, struct uio *uio);
 int     uiomove_fromphys(struct vm_page *ma[], vm_offset_t offset,
                            size_t n, struct uio *uio);
index 7f5d217..e45ad19 100644 (file)
@@ -1429,6 +1429,11 @@ vm_page_bits(int base, int size)
  * of any partial chunks touched by the range.  The invalid portion of
  * such chunks will be zero'd.
  *
+ * NOTE: When truncating a buffer vnode_pager_setsize() will automatically
+ *      align base to DEV_BSIZE so as not to mark clean a partially
+ *      truncated device block.  Otherwise the dirty page status might be
+ *      lost.
+ *
  * This routine may not block.
  *
  * (base + size) must be less then or equal to PAGE_SIZE.
index c3ad39c..fb3d2b9 100644 (file)
@@ -319,10 +319,13 @@ vnode_pager_setsize(struct vnode *vp, vm_ooffset_t nsize)
                                int base = (int)nsize & PAGE_MASK;
                                int size = PAGE_SIZE - base;
                                struct sf_buf *sf;
+                               int n;
 
                                /*
                                 * Clear out partial-page garbage in case
                                 * the page has been mapped.
+                                *
+                                * This is byte aligned.
                                 */
                                vm_page_busy(m);
                                sf = sf_buf_alloc(m, SFB_CPUPRIVATE);
@@ -353,11 +356,19 @@ vnode_pager_setsize(struct vnode *vp, vm_ooffset_t nsize)
                                 * case is one of them.  If the page is still
                                 * partially dirty, make it fully dirty.
                                 *
-                                * note that we do not clear out the valid
+                                * NOTE: We do not clear out the valid
                                 * bits.  This would prevent bogus_page
                                 * replacement from working properly.
+                                *
+                                * NOTE: We do not want to clear the dirty
+                                * bit for a partial DEV_BSIZE'd truncation!
+                                * This is DEV_BSIZE aligned!
                                 */
-                               vm_page_set_validclean(m, base, size);
+                               n = ((base + DEV_BMASK) & ~DEV_BMASK) - base;
+                               base += n;
+                               size -= n;
+                               if (size > 0)
+                                       vm_page_set_validclean(m, base, size);
                                if (m->dirty != 0)
                                        m->dirty = VM_PAGE_BITS_ALL;
                                vm_page_wakeup(m);