Module Name:    src
Committed By:   riastradh
Date:           Fri Mar 27 17:27:56 UTC 2015

Modified Files:
        src/sys/ufs/chfs: chfs_vnops.c
        src/sys/ufs/ext2fs: ext2fs_extern.h ext2fs_lookup.c ext2fs_readwrite.c
            ext2fs_rename.c ext2fs_vfsops.c ext2fs_vnops.c
        src/sys/ufs/ffs: ffs_extern.h ffs_vfsops.c
        src/sys/ufs/lfs: lfs_extern.h lfs_rename.c lfs_vnops.c ulfs_extern.h
            ulfs_lookup.c ulfs_readwrite.c ulfs_vnops.c
        src/sys/ufs/ufs: ufs_extern.h ufs_lookup.c ufs_readwrite.c ufs_rename.c
            ufs_vnops.c ufsmount.h

Log Message:
Disentangle buffer-cached I/O from page-cached I/O in UFS.

Page-cached I/O is used for regular files, and is initiated by VFS
users such as userland and NFS.

Buffer-cached I/O is used for directories and symlinks, and is issued
only internally by UFS.

New UFS routine ufs_bufio replaces vn_rdwr for internal use.
ufs_bufio is implemented by new UFS operations uo_bufrd/uo_bufwr,
which sit in ufs_readwrite.c alongside the VOP_READ/VOP_WRITE
implementations.

I preserved the code as much as possible and will leave further
simplification for future commits.  I kept the ulfs_readwrite.c
copypasta close to ufs_readwrite.c in case we ever want to merge them
back; likewise ext2fs_readwrite.c.

No externally visible semantic change.  All atf fs tests still pass.


To generate a diff of this commit:
cvs rdiff -u -r1.24 -r1.25 src/sys/ufs/chfs/chfs_vnops.c
cvs rdiff -u -r1.47 -r1.48 src/sys/ufs/ext2fs/ext2fs_extern.h
cvs rdiff -u -r1.77 -r1.78 src/sys/ufs/ext2fs/ext2fs_lookup.c
cvs rdiff -u -r1.66 -r1.67 src/sys/ufs/ext2fs/ext2fs_readwrite.c
cvs rdiff -u -r1.7 -r1.8 src/sys/ufs/ext2fs/ext2fs_rename.c
cvs rdiff -u -r1.191 -r1.192 src/sys/ufs/ext2fs/ext2fs_vfsops.c
cvs rdiff -u -r1.115 -r1.116 src/sys/ufs/ext2fs/ext2fs_vnops.c
cvs rdiff -u -r1.81 -r1.82 src/sys/ufs/ffs/ffs_extern.h
cvs rdiff -u -r1.325 -r1.326 src/sys/ufs/ffs/ffs_vfsops.c
cvs rdiff -u -r1.101 -r1.102 src/sys/ufs/lfs/lfs_extern.h
cvs rdiff -u -r1.7 -r1.8 src/sys/ufs/lfs/lfs_rename.c \
    src/sys/ufs/lfs/ulfs_readwrite.c
cvs rdiff -u -r1.269 -r1.270 src/sys/ufs/lfs/lfs_vnops.c
cvs rdiff -u -r1.13 -r1.14 src/sys/ufs/lfs/ulfs_extern.h
cvs rdiff -u -r1.21 -r1.22 src/sys/ufs/lfs/ulfs_lookup.c \
    src/sys/ufs/lfs/ulfs_vnops.c
cvs rdiff -u -r1.78 -r1.79 src/sys/ufs/ufs/ufs_extern.h
cvs rdiff -u -r1.132 -r1.133 src/sys/ufs/ufs/ufs_lookup.c
cvs rdiff -u -r1.107 -r1.108 src/sys/ufs/ufs/ufs_readwrite.c
cvs rdiff -u -r1.11 -r1.12 src/sys/ufs/ufs/ufs_rename.c
cvs rdiff -u -r1.225 -r1.226 src/sys/ufs/ufs/ufs_vnops.c
cvs rdiff -u -r1.42 -r1.43 src/sys/ufs/ufs/ufsmount.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/ufs/chfs/chfs_vnops.c
diff -u src/sys/ufs/chfs/chfs_vnops.c:1.24 src/sys/ufs/chfs/chfs_vnops.c:1.25
--- src/sys/ufs/chfs/chfs_vnops.c:1.24	Sun Jan 11 17:29:57 2015
+++ src/sys/ufs/chfs/chfs_vnops.c	Fri Mar 27 17:27:55 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: chfs_vnops.c,v 1.24 2015/01/11 17:29:57 hannken Exp $	*/
+/*	$NetBSD: chfs_vnops.c,v 1.25 2015/03/27 17:27:55 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2010 Department of Software Engineering,
@@ -1310,9 +1310,8 @@ chfs_symlink(void *v)
 
 		uvm_vnp_setsize(vp, len);
 	} else {
-		err = vn_rdwr(UIO_WRITE, vp, target, len, (off_t)0,
-		    UIO_SYSSPACE, IO_NODELOCKED, cnp->cn_cred,
-		    (size_t *)0, NULL);
+		err = ufs_bufio(UIO_WRITE, vp, target, len, (off_t)0,
+		    IO_NODELOCKED, cnp->cn_cred, (size_t *)0, NULL);
 	}
 
 out:
@@ -1454,7 +1453,7 @@ chfs_readlink(void *v)
 		return (0);
 	}
 
-	return (VOP_READ(vp, uio, 0, cred));
+	return (UFS_BUFRD(vp, uio, 0, cred));
 }
 
 /* --------------------------------------------------------------------- */

Index: src/sys/ufs/ext2fs/ext2fs_extern.h
diff -u src/sys/ufs/ext2fs/ext2fs_extern.h:1.47 src/sys/ufs/ext2fs/ext2fs_extern.h:1.48
--- src/sys/ufs/ext2fs/ext2fs_extern.h:1.47	Sun May 25 14:07:19 2014
+++ src/sys/ufs/ext2fs/ext2fs_extern.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_extern.h,v 1.47 2014/05/25 14:07:19 hannken Exp $	*/
+/*	$NetBSD: ext2fs_extern.h,v 1.48 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993, 1994
@@ -147,6 +147,8 @@ void ext2fs_set_inode_guid(struct inode 
 /* ext2fs_readwrite.c */
 int ext2fs_read(void *);
 int ext2fs_write(void *);
+int ext2fs_bufrd(struct vnode *, struct uio *, int, kauth_cred_t);
+int ext2fs_bufwr(struct vnode *, struct uio *, int, kauth_cred_t);
 
 /* ext2fs_vnops.c */
 int ext2fs_create(void *);

Index: src/sys/ufs/ext2fs/ext2fs_lookup.c
diff -u src/sys/ufs/ext2fs/ext2fs_lookup.c:1.77 src/sys/ufs/ext2fs/ext2fs_lookup.c:1.78
--- src/sys/ufs/ext2fs/ext2fs_lookup.c:1.77	Tue Jun  3 19:30:29 2014
+++ src/sys/ufs/ext2fs/ext2fs_lookup.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_lookup.c,v 1.77 2014/06/03 19:30:29 joerg Exp $	*/
+/*	$NetBSD: ext2fs_lookup.c,v 1.78 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*
  * Modified for NetBSD 1.2E
@@ -48,7 +48,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_lookup.c,v 1.77 2014/06/03 19:30:29 joerg Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_lookup.c,v 1.78 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -180,7 +180,7 @@ ext2fs_readdir(void *v)
 	}
 	aiov.iov_base = dirbuf;
 
-	error = VOP_READ(ap->a_vp, &auio, 0, ap->a_cred);
+	error = UFS_BUFRD(ap->a_vp, &auio, 0, ap->a_cred);
 	if (error == 0) {
 		readcnt = e2fs_count - auio.uio_resid;
 		for (dp = (struct ext2fs_direct *)dirbuf;
@@ -952,8 +952,8 @@ ext2fs_dirempty(struct inode *ip, ino_t 
 #define	MINDIRSIZ (sizeof (struct ext2fs_dirtemplate) / 2)
 
 	for (off = 0; off < ext2fs_size(ip); off += fs2h16(dp->e2d_reclen)) {
-		error = vn_rdwr(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ, off,
-		   UIO_SYSSPACE, IO_NODELOCKED, cred, &count, NULL);
+		error = ufs_bufio(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ,
+		    off, IO_NODELOCKED, cred, &count, NULL);
 		/*
 		 * Since we read MINDIRSIZ, residual must
 		 * be 0 unless we're at end of file.

Index: src/sys/ufs/ext2fs/ext2fs_readwrite.c
diff -u src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.66 src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.67
--- src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.66	Sun Nov  9 18:23:28 2014
+++ src/sys/ufs/ext2fs/ext2fs_readwrite.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_readwrite.c,v 1.66 2014/11/09 18:23:28 maxv Exp $	*/
+/*	$NetBSD: ext2fs_readwrite.c,v 1.67 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1993
@@ -60,7 +60,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_readwrite.c,v 1.66 2014/11/09 18:23:28 maxv Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_readwrite.c,v 1.67 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -97,13 +97,9 @@ ext2fs_read(void *v)
 	struct vnode *vp;
 	struct inode *ip;
 	struct uio *uio;
-	struct m_ext2fs *fs;
-	struct buf *bp;
 	struct ufsmount *ump;
 	vsize_t bytelen;
-	daddr_t lbn, nextlbn;
-	off_t bytesinfile;
-	long size, xfersize, blkoffset;
+	int advice;
 	int error;
 
 	vp = ap->a_vp;
@@ -123,7 +119,10 @@ ext2fs_read(void *v)
 	} else if (vp->v_type != VREG && vp->v_type != VDIR)
 		panic("%s: type %d", "ext2fs_read", vp->v_type);
 #endif
-	fs = ip->i_e2fs;
+	/* XXX Eliminate me by refusing directory reads from userland.  */
+	if (vp->v_type == VDIR)
+		return ext2fs_bufrd(vp, uio, ap->a_ioflag, ap->a_cred);
+
 	if ((uint64_t)uio->uio_offset > ump->um_maxfilesize)
 		return (EFBIG);
 	if (uio->uio_resid == 0)
@@ -131,22 +130,64 @@ ext2fs_read(void *v)
 	if (uio->uio_offset >= ext2fs_size(ip))
 		goto out;
 
-	if (vp->v_type == VREG) {
-		const int advice = IO_ADV_DECODE(ap->a_ioflag);
-
-		while (uio->uio_resid > 0) {
-			bytelen = MIN(ext2fs_size(ip) - uio->uio_offset,
+	KASSERT(vp->v_type == VREG);
+	advice = IO_ADV_DECODE(ap->a_ioflag);
+	while (uio->uio_resid > 0) {
+		bytelen = MIN(ext2fs_size(ip) - uio->uio_offset,
 			    uio->uio_resid);
-			if (bytelen == 0)
-				break;
+		if (bytelen == 0)
+			break;
 
-			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
-			    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
-			if (error)
-				break;
-		}
-		goto out;
+		error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
+		    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
+		if (error)
+			break;
+	}
+
+out:
+	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
+		ip->i_flag |= IN_ACCESS;
+		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC)
+			error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
 	}
+	return (error);
+}
+
+/*
+ * UFS op for reading via the buffer cache
+ */
+int
+ext2fs_bufrd(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	struct ufsmount *ump;
+	struct m_ext2fs *fs;
+	struct buf *bp;
+	off_t bytesinfile;
+	daddr_t lbn, nextlbn;
+	long size, xfersize, blkoffset;
+	int error;
+
+	KASSERT(uio->uio_rw == UIO_READ);
+	KASSERT(VOP_ISLOCKED(vp));
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
+
+	ip = VTOI(vp);
+	ump = ip->i_ump;
+	fs = ip->i_e2fs;
+	error = 0;
+
+	KASSERT(vp->v_type != VLNK ||
+	    ext2fs_size(ip) >= ump->um_maxsymlinklen);
+	KASSERT(vp->v_type != VLNK || ump->um_maxsymlinklen != 0 ||
+	    ext2fs_nblock(ip) != 0);
+
+	if (uio->uio_offset > ump->um_maxfilesize)
+		return EFBIG;
+	if (uio->uio_resid == 0)
+		return 0;
+	if (uio->uio_offset >= ext2fs_size(ip))
+		goto out;
 
 	for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
 		bytesinfile = ext2fs_size(ip) - uio->uio_offset;
@@ -196,7 +237,7 @@ ext2fs_read(void *v)
 out:
 	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
 		ip->i_flag |= IN_ACCESS;
-		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC)
+		if ((ioflag & IO_SYNC) == IO_SYNC)
 			error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
 	}
 	return (error);
@@ -218,11 +259,9 @@ ext2fs_write(void *v)
 	struct uio *uio;
 	struct inode *ip;
 	struct m_ext2fs *fs;
-	struct buf *bp;
 	struct ufsmount *ump;
-	daddr_t lbn;
 	off_t osize;
-	int blkoffset, error, flags, ioflag, resid, xfersize;
+	int blkoffset, error, ioflag, resid;
 	vsize_t bytelen;
 	off_t oldoff = 0;					/* XXX */
 	bool async;
@@ -271,58 +310,124 @@ ext2fs_write(void *v)
 	resid = uio->uio_resid;
 	osize = ext2fs_size(ip);
 
-	if (vp->v_type == VREG) {
-		while (uio->uio_resid > 0) {
-			oldoff = uio->uio_offset;
-			blkoffset = ext2_blkoff(fs, uio->uio_offset);
-			bytelen = MIN(fs->e2fs_bsize - blkoffset,
-			    uio->uio_resid);
+	KASSERT(vp->v_type == VREG);
+	while (uio->uio_resid > 0) {
+		oldoff = uio->uio_offset;
+		blkoffset = ext2_blkoff(fs, uio->uio_offset);
+		bytelen = MIN(fs->e2fs_bsize - blkoffset, uio->uio_resid);
 
-			if (vp->v_size < oldoff + bytelen) {
-				uvm_vnp_setwritesize(vp, oldoff + bytelen);
-			}
-			error = ufs_balloc_range(vp, uio->uio_offset,
-			    bytelen, ap->a_cred, 0);
-			if (error)
-				break;
-			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
-			    UBC_WRITE | UBC_UNMAP_FLAG(vp));
-			if (error)
-				break;
+		if (vp->v_size < oldoff + bytelen) {
+			uvm_vnp_setwritesize(vp, oldoff + bytelen);
+		}
+		error = ufs_balloc_range(vp, uio->uio_offset, bytelen,
+		    ap->a_cred, 0);
+		if (error)
+			break;
+		error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
+		    UBC_WRITE | UBC_UNMAP_FLAG(vp));
+		if (error)
+			break;
+
+		/*
+		 * update UVM's notion of the size now that we've
+		 * copied the data into the vnode's pages.
+		 */
 
-			/*
-			 * update UVM's notion of the size now that we've
-			 * copied the data into the vnode's pages.
-			 */
-
-			if (vp->v_size < uio->uio_offset) {
-				uvm_vnp_setsize(vp, uio->uio_offset);
-				extended = 1;
-			}
-
-			/*
-			 * flush what we just wrote if necessary.
-			 * XXXUBC simplistic async flushing.
-			 */
-
-			if (!async && oldoff >> 16 != uio->uio_offset >> 16) {
-				mutex_enter(vp->v_interlock);
-				error = VOP_PUTPAGES(vp, (oldoff >> 16) << 16,
-				    (uio->uio_offset >> 16) << 16,
-				    PGO_CLEANIT | PGO_LAZY);
-			}
+		if (vp->v_size < uio->uio_offset) {
+			uvm_vnp_setsize(vp, uio->uio_offset);
+			extended = 1;
 		}
-		if (error == 0 && ioflag & IO_SYNC) {
+
+		/*
+		 * flush what we just wrote if necessary.
+		 * XXXUBC simplistic async flushing.
+		 */
+
+		if (!async && oldoff >> 16 != uio->uio_offset >> 16) {
 			mutex_enter(vp->v_interlock);
-			error = VOP_PUTPAGES(vp, trunc_page(oldoff),
-			    round_page(ext2_blkroundup(fs, uio->uio_offset)),
-			    PGO_CLEANIT | PGO_SYNCIO);
+			error = VOP_PUTPAGES(vp, (oldoff >> 16) << 16,
+			    (uio->uio_offset >> 16) << 16,
+			    PGO_CLEANIT | PGO_LAZY);
 		}
+	}
+	if (error == 0 && ioflag & IO_SYNC) {
+		mutex_enter(vp->v_interlock);
+		error = VOP_PUTPAGES(vp, trunc_page(oldoff),
+		    round_page(ext2_blkroundup(fs, uio->uio_offset)),
+		    PGO_CLEANIT | PGO_SYNCIO);
+	}
 
-		goto out;
+	/*
+	 * If we successfully wrote any data, and we are not the superuser
+	 * we clear the setuid and setgid bits as a precaution against
+	 * tampering.
+	 */
+	ip->i_flag |= IN_CHANGE | IN_UPDATE;
+	if (vp->v_mount->mnt_flag & MNT_RELATIME)
+		ip->i_flag |= IN_ACCESS;
+	if (resid > uio->uio_resid && ap->a_cred) {
+		if (ip->i_e2fs_mode & ISUID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0)
+				ip->i_e2fs_mode &= ISUID;
+		}
+
+		if (ip->i_e2fs_mode & ISGID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0)
+				ip->i_e2fs_mode &= ~ISGID;
+		}
 	}
+	if (resid > uio->uio_resid)
+		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
+	if (error) {
+		(void) ext2fs_truncate(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		uio->uio_offset -= resid - uio->uio_resid;
+		uio->uio_resid = resid;
+	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC)
+		error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
+	KASSERT(vp->v_size == ext2fs_size(ip));
+	return (error);
+}
+
+/*
+ * UFS op for writing via the buffer cache
+ */
+int
+ext2fs_bufwr(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	struct ufsmount *ump;
+	struct m_ext2fs *fs;
+	struct buf *bp;
+	int flags;
+	off_t osize;
+	daddr_t lbn;
+	int resid, blkoffset, xfersize;
+	int extended = 0;
+	int error;
+
+	KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
+	KASSERT(vp->v_type != VDIR || ISSET(ioflag, IO_SYNC));
+	KASSERT(uio->uio_rw == UIO_WRITE);
+
+	ip = VTOI(vp);
+	ump = ip->i_ump;
+	fs = ip->i_e2fs;
+	error = 0;
+
+	if (uio->uio_offset < 0 ||
+	    uio->uio_resid > ump->um_maxfilesize ||
+	    uio->uio_offset > (ump->um_maxfilesize - uio->uio_resid))
+		return EFBIG;
+	if (uio->uio_resid == 0)
+		return 0;
 
 	flags = ioflag & IO_SYNC ? B_SYNC : 0;
+	resid = uio->uio_resid;
+	osize = ext2fs_size(ip);
+
 	for (error = 0; uio->uio_resid > 0;) {
 		lbn = ext2_lblkno(fs, uio->uio_offset);
 		blkoffset = ext2_blkoff(fs, uio->uio_offset);
@@ -331,8 +436,8 @@ ext2fs_write(void *v)
 			flags |= B_CLRBUF;
 		else
 			flags &= ~B_CLRBUF;
-		error = ext2fs_balloc(ip,
-		    lbn, blkoffset + xfersize, ap->a_cred, &bp, flags);
+		error = ext2fs_balloc(ip, lbn, blkoffset + xfersize, cred, &bp,
+		    flags);
 		if (error)
 			break;
 		if (ext2fs_size(ip) < uio->uio_offset + xfersize) {
@@ -367,20 +472,18 @@ ext2fs_write(void *v)
 	 * we clear the setuid and setgid bits as a precaution against
 	 * tampering.
 	 */
-
-out:
 	ip->i_flag |= IN_CHANGE | IN_UPDATE;
 	if (vp->v_mount->mnt_flag & MNT_RELATIME)
 		ip->i_flag |= IN_ACCESS;
-	if (resid > uio->uio_resid && ap->a_cred) {
+	if (resid > uio->uio_resid && cred) {
 		if (ip->i_e2fs_mode & ISUID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0)
 				ip->i_e2fs_mode &= ISUID;
 		}
 
 		if (ip->i_e2fs_mode & ISGID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0)
 				ip->i_e2fs_mode &= ~ISGID;
 		}
@@ -388,7 +491,7 @@ out:
 	if (resid > uio->uio_resid)
 		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error) {
-		(void) ext2fs_truncate(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		(void) ext2fs_truncate(vp, osize, ioflag & IO_SYNC, cred);
 		uio->uio_offset -= resid - uio->uio_resid;
 		uio->uio_resid = resid;
 	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC)

Index: src/sys/ufs/ext2fs/ext2fs_rename.c
diff -u src/sys/ufs/ext2fs/ext2fs_rename.c:1.7 src/sys/ufs/ext2fs/ext2fs_rename.c:1.8
--- src/sys/ufs/ext2fs/ext2fs_rename.c:1.7	Sun May 25 13:46:58 2014
+++ src/sys/ufs/ext2fs/ext2fs_rename.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_rename.c,v 1.7 2014/05/25 13:46:58 hannken Exp $	*/
+/*	$NetBSD: ext2fs_rename.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_rename.c,v 1.7 2014/05/25 13:46:58 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_rename.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/buf.h>
@@ -892,8 +892,8 @@ ext2fs_read_dotdot(struct vnode *vp, kau
 	KASSERT(ino_ret != NULL);
 	KASSERT(vp->v_type == VDIR);
 
-	error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
-	    UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL);
+	error = ufs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
+	    IO_NODELOCKED, cred, NULL, NULL);
 	if (error)
 		return error;
 
@@ -924,8 +924,8 @@ ext2fs_rename_replace_dotdot(struct vnod
 	VTOI(fdvp)->i_e2fs_nlink--;
 	VTOI(fdvp)->i_flag |= IN_CHANGE;
 
-	error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
-	    UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL);
+	error = ufs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
+	    IO_NODELOCKED, cred, NULL, NULL);
 	if (error)
 		return error;
 
@@ -944,8 +944,8 @@ ext2fs_rename_replace_dotdot(struct vnod
 
 	dirbuf.dotdot_ino = h2fs32(VTOI(tdvp)->i_number);
 	/* XXX WTF?  Why not check error?  */
-	(void)vn_rdwr(UIO_WRITE, vp, &dirbuf, sizeof dirbuf, (off_t)0,
-	    UIO_SYSSPACE, (IO_NODELOCKED | IO_SYNC), cred, NULL, NULL);
+	(void)ufs_bufio(UIO_WRITE, vp, &dirbuf, sizeof dirbuf, (off_t)0,
+	    (IO_NODELOCKED | IO_SYNC), cred, NULL, NULL);
 
 	return 0;
 }

Index: src/sys/ufs/ext2fs/ext2fs_vfsops.c
diff -u src/sys/ufs/ext2fs/ext2fs_vfsops.c:1.191 src/sys/ufs/ext2fs/ext2fs_vfsops.c:1.192
--- src/sys/ufs/ext2fs/ext2fs_vfsops.c:1.191	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ext2fs/ext2fs_vfsops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_vfsops.c,v 1.191 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ext2fs_vfsops.c,v 1.192 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1989, 1991, 1993, 1994
@@ -60,7 +60,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_vfsops.c,v 1.191 2015/03/17 09:39:29 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_vfsops.c,v 1.192 2015/03/27 17:27:56 riastradh Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_compat_netbsd.h"
@@ -156,6 +156,8 @@ static const struct genfs_ops ext2fs_gen
 static const struct ufs_ops ext2fs_ufsops = {
 	.uo_itimes = ext2fs_itimes,
 	.uo_update = ext2fs_update,
+	.uo_bufrd = ext2fs_bufrd,
+	.uo_bufwr = ext2fs_bufwr,
 };
 
 /* Fill in the inode uid/gid from ext2 halves.  */

Index: src/sys/ufs/ext2fs/ext2fs_vnops.c
diff -u src/sys/ufs/ext2fs/ext2fs_vnops.c:1.115 src/sys/ufs/ext2fs/ext2fs_vnops.c:1.116
--- src/sys/ufs/ext2fs/ext2fs_vnops.c:1.115	Sun Nov  9 18:23:28 2014
+++ src/sys/ufs/ext2fs/ext2fs_vnops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_vnops.c,v 1.115 2014/11/09 18:23:28 maxv Exp $	*/
+/*	$NetBSD: ext2fs_vnops.c,v 1.116 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1989, 1993
@@ -65,7 +65,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_vnops.c,v 1.115 2014/11/09 18:23:28 maxv Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_vnops.c,v 1.116 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -727,9 +727,9 @@ ext2fs_mkdir(void *v)
 		dirtemplate.dotdot_type = EXT2_FT_DIR;
 	}
 	dirtemplate.dotdot_name[0] = dirtemplate.dotdot_name[1] = '.';
-	error = vn_rdwr(UIO_WRITE, tvp, (void *)&dirtemplate,
-	    sizeof (dirtemplate), (off_t)0, UIO_SYSSPACE,
-	    IO_NODELOCKED|IO_SYNC, cnp->cn_cred, (size_t *)0, NULL);
+	error = ufs_bufio(UIO_WRITE, tvp, (void *)&dirtemplate,
+	    sizeof (dirtemplate), (off_t)0, IO_NODELOCKED|IO_SYNC,
+	    cnp->cn_cred, (size_t *)0, NULL);
 	if (error) {
 		dp->i_e2fs_nlink--;
 		dp->i_flag |= IN_CHANGE;
@@ -895,9 +895,8 @@ ext2fs_symlink(void *v)
 			ip->i_flag |= IN_ACCESS;
 		uvm_vnp_setsize(vp, len);
 	} else
-		error = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
-		    UIO_SYSSPACE, IO_NODELOCKED, ap->a_cnp->cn_cred,
-		    (size_t *)0, NULL);
+		error = ufs_bufio(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
+		    IO_NODELOCKED, ap->a_cnp->cn_cred, (size_t *)0, NULL);
 bad:
 	VOP_UNLOCK(vp);
 	if (error)
@@ -927,7 +926,7 @@ ext2fs_readlink(void *v)
 		uiomove((char *)ip->i_din.e2fs_din->e2di_shortlink, isize, ap->a_uio);
 		return (0);
 	}
-	return (VOP_READ(vp, ap->a_uio, 0, ap->a_cred));
+	return (UFS_BUFRD(vp, ap->a_uio, 0, ap->a_cred));
 }
 
 /*

Index: src/sys/ufs/ffs/ffs_extern.h
diff -u src/sys/ufs/ffs/ffs_extern.h:1.81 src/sys/ufs/ffs/ffs_extern.h:1.82
--- src/sys/ufs/ffs/ffs_extern.h:1.81	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ffs/ffs_extern.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ffs_extern.h,v 1.81 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ffs_extern.h,v 1.82 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993, 1994
@@ -127,6 +127,10 @@ int	ffs_cgupdate(struct ufsmount *, int)
 /* ffs_vnops.c */
 int	ffs_read(void *);
 int	ffs_write(void *);
+int	ffs_bufio(enum uio_rw, struct vnode *, void *, size_t, off_t, int,
+	    kauth_cred_t, size_t *, struct lwp *);
+int	ffs_bufrd(struct vnode *, struct uio *, int, kauth_cred_t);
+int	ffs_bufwr(struct vnode *, struct uio *, int, kauth_cred_t);
 int	ffs_fsync(void *);
 int	ffs_spec_fsync(void *);
 int	ffs_reclaim(void *);

Index: src/sys/ufs/ffs/ffs_vfsops.c
diff -u src/sys/ufs/ffs/ffs_vfsops.c:1.325 src/sys/ufs/ffs/ffs_vfsops.c:1.326
--- src/sys/ufs/ffs/ffs_vfsops.c:1.325	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ffs/ffs_vfsops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ffs_vfsops.c,v 1.325 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ffs_vfsops.c,v 1.326 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2009 The NetBSD Foundation, Inc.
@@ -61,7 +61,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ffs_vfsops.c,v 1.325 2015/03/17 09:39:29 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ffs_vfsops.c,v 1.326 2015/03/27 17:27:56 riastradh Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_ffs.h"
@@ -182,6 +182,8 @@ static const struct ufs_ops ffs_ufsops =
 	.uo_truncate = ffs_truncate,
 	.uo_balloc = ffs_balloc,
 	.uo_snapgone = ffs_snapgone,
+	.uo_bufrd = ffs_bufrd,
+	.uo_bufwr = ffs_bufwr,
 };
 
 static int

Index: src/sys/ufs/lfs/lfs_extern.h
diff -u src/sys/ufs/lfs/lfs_extern.h:1.101 src/sys/ufs/lfs/lfs_extern.h:1.102
--- src/sys/ufs/lfs/lfs_extern.h:1.101	Tue Mar 18 18:20:44 2014
+++ src/sys/ufs/lfs/lfs_extern.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: lfs_extern.h,v 1.101 2014/03/18 18:20:44 riastradh Exp $	*/
+/*	$NetBSD: lfs_extern.h,v 1.102 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc.
@@ -272,6 +272,9 @@ int lfs_write	 (void *);
 int lfs_getpages (void *);
 int lfs_putpages (void *);
 
+int lfs_bufrd(struct vnode *, struct uio *, int, kauth_cred_t);
+int lfs_bufwr(struct vnode *, struct uio *, int, kauth_cred_t);
+
 extern int lfs_mount_type;
 extern int (**lfs_vnodeop_p)(void *);
 extern int (**lfs_specop_p)(void *);

Index: src/sys/ufs/lfs/lfs_rename.c
diff -u src/sys/ufs/lfs/lfs_rename.c:1.7 src/sys/ufs/lfs/lfs_rename.c:1.8
--- src/sys/ufs/lfs/lfs_rename.c:1.7	Sat May 17 07:08:35 2014
+++ src/sys/ufs/lfs/lfs_rename.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: lfs_rename.c,v 1.7 2014/05/17 07:08:35 dholland Exp $	*/
+/*	$NetBSD: lfs_rename.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $	*/
 /*  from NetBSD: ufs_rename.c,v 1.6 2013/01/22 09:39:18 dholland Exp  */
 
 /*-
@@ -89,7 +89,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: lfs_rename.c,v 1.7 2014/05/17 07:08:35 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: lfs_rename.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -629,8 +629,8 @@ ulfs_read_dotdot(struct vnode *vp, kauth
 	KASSERT(ino_ret != NULL);
 	KASSERT(vp->v_type == VDIR);
 
-	error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
-	    UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL);
+	error = ulfs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
+	    IO_NODELOCKED, cred, NULL, NULL);
 	if (error)
 		return error;
 
Index: src/sys/ufs/lfs/ulfs_readwrite.c
diff -u src/sys/ufs/lfs/ulfs_readwrite.c:1.7 src/sys/ufs/lfs/ulfs_readwrite.c:1.8
--- src/sys/ufs/lfs/ulfs_readwrite.c:1.7	Thu Oct 17 21:01:08 2013
+++ src/sys/ufs/lfs/ulfs_readwrite.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_readwrite.c,v 1.7 2013/10/17 21:01:08 christos Exp $	*/
+/*	$NetBSD: ulfs_readwrite.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $	*/
 /*  from NetBSD: ufs_readwrite.c,v 1.105 2013/01/22 09:39:18 dholland Exp  */
 
 /*-
@@ -33,7 +33,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: ulfs_readwrite.c,v 1.7 2013/10/17 21:01:08 christos Exp $");
+__KERNEL_RCSID(1, "$NetBSD: ulfs_readwrite.c,v 1.8 2015/03/27 17:27:56 riastradh Exp $");
 
 #ifdef LFS_READWRITE
 #define	FS			struct lfs
@@ -42,6 +42,8 @@ __KERNEL_RCSID(1, "$NetBSD: ulfs_readwri
 #define	READ_S			"lfs_read"
 #define	WRITE			lfs_write
 #define	WRITE_S			"lfs_write"
+#define	BUFRD			lfs_bufrd
+#define	BUFWR			lfs_bufwr
 #define	fs_bsize		lfs_bsize
 #define	fs_bmask		lfs_bmask
 #else
@@ -51,6 +53,8 @@ __KERNEL_RCSID(1, "$NetBSD: ulfs_readwri
 #define	READ_S			"ffs_read"
 #define	WRITE			ffs_write
 #define	WRITE_S			"ffs_write"
+#define	BUFRD			ffs_bufrd
+#define	BUFWR			ffs_bufwr
 #endif
 
 /*
@@ -69,14 +73,9 @@ READ(void *v)
 	struct vnode *vp;
 	struct inode *ip;
 	struct uio *uio;
-	struct buf *bp;
 	FS *fs;
 	vsize_t bytelen;
-	daddr_t lbn, nextlbn;
-	off_t bytesinfile;
-	long size, xfersize, blkoffset;
-	int error, ioflag;
-	bool usepc = false;
+	int error, ioflag, advice;
 
 	vp = ap->a_vp;
 	ip = VTOI(vp);
@@ -89,13 +88,17 @@ READ(void *v)
 	if (uio->uio_rw != UIO_READ)
 		panic("%s: mode", READ_S);
 
-	if (vp->v_type == VLNK) {
-		if (ip->i_size < fs->um_maxsymlinklen ||
-		    (fs->um_maxsymlinklen == 0 && DIP(ip, blocks) == 0))
-			panic("%s: short symlink", READ_S);
-	} else if (vp->v_type != VREG && vp->v_type != VDIR)
+	if (vp->v_type != VREG && vp->v_type != VDIR)
 		panic("%s: type %d", READ_S, vp->v_type);
 #endif
+	/* XXX Eliminate me by refusing directory reads from userland.  */
+	if (vp->v_type == VDIR)
+		return BUFRD(vp, uio, ioflag, ap->a_cred);
+#ifdef LFS_READWRITE
+	/* XXX Eliminate me by using ufs_bufio in lfs.  */
+	if (vp->v_type == VREG && ip->i_number == LFS_IFILE_INUM)
+		return BUFRD(vp, uio, ioflag, ap->a_cred);
+#endif
 	if ((u_int64_t)uio->uio_offset > fs->um_maxfilesize)
 		return (EFBIG);
 	if (uio->uio_resid == 0)
@@ -111,30 +114,76 @@ READ(void *v)
 	if (uio->uio_offset >= ip->i_size)
 		goto out;
 
-#ifdef LFS_READWRITE
-	usepc = (vp->v_type == VREG && ip->i_number != LFS_IFILE_INUM);
-#else /* !LFS_READWRITE */
-	usepc = vp->v_type == VREG;
-#endif /* !LFS_READWRITE */
-	if (usepc) {
-		const int advice = IO_ADV_DECODE(ap->a_ioflag);
+	KASSERT(vp->v_type == VREG);
+	advice = IO_ADV_DECODE(ap->a_ioflag);
+	while (uio->uio_resid > 0) {
+		if (ioflag & IO_DIRECT) {
+			genfs_directio(vp, uio, ioflag);
+		}
+		bytelen = MIN(ip->i_size - uio->uio_offset, uio->uio_resid);
+		if (bytelen == 0)
+			break;
+		error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
+		    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
+		if (error)
+			break;
+	}
 
-		while (uio->uio_resid > 0) {
-			if (ioflag & IO_DIRECT) {
-				genfs_directio(vp, uio, ioflag);
-			}
-			bytelen = MIN(ip->i_size - uio->uio_offset,
-			    uio->uio_resid);
-			if (bytelen == 0)
-				break;
-			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
-			    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
-			if (error)
-				break;
+ out:
+	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
+		ip->i_flag |= IN_ACCESS;
+		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC) {
+			error = lfs_update(vp, NULL, NULL, UPDATE_WAIT);
 		}
-		goto out;
 	}
 
+	fstrans_done(vp->v_mount);
+	return (error);
+}
+
+/*
+ * UFS op for reading via the buffer cache
+ */
+int
+BUFRD(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	FS *fs;
+	struct buf *bp;
+	daddr_t lbn, nextlbn;
+	off_t bytesinfile;
+	long size, xfersize, blkoffset;
+	int error;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK ||
+	    vp->v_type == VREG);
+	KASSERT(uio->uio_rw == UIO_READ);
+
+	ip = VTOI(vp);
+	fs = ip->I_FS;
+	error = 0;
+
+	KASSERT(vp->v_type != VLNK || ip->i_size < fs->um_maxsymlinklen);
+	KASSERT(vp->v_type != VLNK || fs->um_maxsymlinklen != 0 ||
+	    DIP(ip, blocks) == 0);
+	KASSERT(vp->v_type != VREG || vp == fs->lfs_ivnode);
+	KASSERT(vp->v_type != VREG || ip->i_number == LFS_IFILE_INUM);
+
+	if (uio->uio_offset > fs->um_maxfilesize)
+		return EFBIG;
+	if (uio->uio_resid == 0)
+		return 0;
+
+#ifndef LFS_READWRITE
+	KASSERT(!ISSET(ip->i_flags, (SF_SNAPSHOT | SF_SNAPINVAL)));
+#endif
+
+	fstrans_start(vp->v_mount, FSTRANS_SHARED);
+
+	if (uio->uio_offset >= ip->i_size)
+		goto out;
+
 	for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
 		bytesinfile = ip->i_size - uio->uio_offset;
 		if (bytesinfile <= 0)
@@ -180,7 +229,7 @@ READ(void *v)
  out:
 	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
 		ip->i_flag |= IN_ACCESS;
-		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC) {
+		if ((ioflag & IO_SYNC) == IO_SYNC) {
 			error = lfs_update(vp, NULL, NULL, UPDATE_WAIT);
 		}
 	}
@@ -205,19 +254,13 @@ WRITE(void *v)
 	struct uio *uio;
 	struct inode *ip;
 	FS *fs;
-	struct buf *bp;
 	kauth_cred_t cred;
-	daddr_t lbn;
 	off_t osize, origoff, oldoff, preallocoff, endallocoff, nsize;
-	int blkoffset, error, flags, ioflag, resid, size, xfersize;
+	int blkoffset, error, flags, ioflag, resid;
 	int aflag;
 	int extended=0;
 	vsize_t bytelen;
 	bool async;
-	bool usepc = false;
-#ifdef LFS_READWRITE
-	bool need_unreserve = false;
-#endif
 
 	cred = ap->a_cred;
 	ioflag = ap->a_ioflag;
@@ -237,12 +280,6 @@ WRITE(void *v)
 			uio->uio_offset = ip->i_size;
 		if ((ip->i_flags & APPEND) && uio->uio_offset != ip->i_size)
 			return (EPERM);
-		/* FALLTHROUGH */
-	case VLNK:
-		break;
-	case VDIR:
-		if ((ioflag & IO_SYNC) == 0)
-			panic("%s: nonsync dir write", WRITE_S);
 		break;
 	default:
 		panic("%s: type", WRITE_S);
@@ -270,15 +307,13 @@ WRITE(void *v)
 	osize = ip->i_size;
 	error = 0;
 
-	usepc = vp->v_type == VREG;
+	KASSERT(vp->v_type == VREG);
 
 #ifdef LFS_READWRITE
 	async = true;
 	lfs_availwait(fs, lfs_btofsb(fs, uio->uio_resid));
 	lfs_check(vp, LFS_UNUSED_LBN, 0);
 #endif /* !LFS_READWRITE */
-	if (!usepc)
-		goto bcache;
 
 	preallocoff = round_page(lfs_blkroundup(fs, MAX(osize, uio->uio_offset)));
 	aflag = ioflag & IO_SYNC ? B_SYNC : 0;
@@ -412,9 +447,105 @@ WRITE(void *v)
 		    round_page(lfs_blkroundup(fs, uio->uio_offset)),
 		    PGO_CLEANIT | PGO_SYNCIO | PGO_JOURNALLOCKED);
 	}
-	goto out;
 
- bcache:
+	/*
+	 * If we successfully wrote any data, and we are not the superuser
+	 * we clear the setuid and setgid bits as a precaution against
+	 * tampering.
+	 */
+out:
+	ip->i_flag |= IN_CHANGE | IN_UPDATE;
+	if (vp->v_mount->mnt_flag & MNT_RELATIME)
+		ip->i_flag |= IN_ACCESS;
+	if (resid > uio->uio_resid && ap->a_cred) {
+		if (ip->i_mode & ISUID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0) {
+				ip->i_mode &= ~ISUID;
+				DIP_ASSIGN(ip, mode, ip->i_mode);
+			}
+		}
+
+		if (ip->i_mode & ISGID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0) {
+				ip->i_mode &= ~ISGID;
+				DIP_ASSIGN(ip, mode, ip->i_mode);
+			}
+		}
+	}
+	if (resid > uio->uio_resid)
+		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
+	if (error) {
+		(void) lfs_truncate(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		uio->uio_offset -= resid - uio->uio_resid;
+		uio->uio_resid = resid;
+	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC) {
+		error = lfs_update(vp, NULL, NULL, UPDATE_WAIT);
+	} else {
+		/* nothing */
+	}
+	KASSERT(vp->v_size == ip->i_size);
+	fstrans_done(vp->v_mount);
+
+	return (error);
+}
+
+/*
+ * UFS op for writing via the buffer cache
+ */
+int
+BUFWR(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	FS *fs;
+	int flags;
+	struct buf *bp;
+	off_t osize, origoff;
+	int resid, xfersize, size, blkoffset;
+	daddr_t lbn;
+	int extended=0;
+	int error;
+#ifdef LFS_READWRITE
+	bool need_unreserve = false;
+#endif
+
+	KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
+	KASSERT(vp->v_type != VDIR || ISSET(ioflag, IO_SYNC));
+	KASSERT(uio->uio_rw == UIO_WRITE);
+
+	ip = VTOI(vp);
+	fs = ip->I_FS;
+
+	KASSERT(vp->v_size == ip->i_size);
+
+	if (uio->uio_offset < 0 ||
+	    uio->uio_resid > fs->um_maxfilesize ||
+	    uio->uio_offset > (fs->um_maxfilesize - uio->uio_resid))
+		return EFBIG;
+#ifdef LFS_READWRITE
+	KASSERT(vp != fs->lfs_ivnode);
+#endif
+	if (uio->uio_resid == 0)
+		return 0;
+
+	fstrans_start(vp->v_mount, FSTRANS_SHARED);
+
+	flags = ioflag & IO_SYNC ? B_SYNC : 0;
+	origoff = uio->uio_offset;
+	resid = uio->uio_resid;
+	osize = ip->i_size;
+	error = 0;
+
+	KASSERT(vp->v_type != VREG);
+
+#ifdef LFS_READWRITE
+	lfs_availwait(fs, lfs_btofsb(fs, uio->uio_resid));
+	lfs_check(vp, LFS_UNUSED_LBN, 0);
+#endif /* !LFS_READWRITE */
+
+	/* XXX Should never have cached pages here.  */
 	mutex_enter(vp->v_interlock);
 	VOP_PUTPAGES(vp, trunc_page(origoff), round_page(origoff + resid),
 	    PGO_CLEANIT | PGO_FREE | PGO_SYNCIO | PGO_JOURNALLOCKED);
@@ -434,8 +565,8 @@ WRITE(void *v)
 			break;
 		need_unreserve = true;
 #endif
-		error = lfs_balloc(vp, uio->uio_offset, xfersize,
-		    ap->a_cred, flags, &bp);
+		error = lfs_balloc(vp, uio->uio_offset, xfersize, cred, flags,
+		    &bp);
 
 		if (error)
 			break;
@@ -488,13 +619,12 @@ WRITE(void *v)
 	 * we clear the setuid and setgid bits as a precaution against
 	 * tampering.
 	 */
-out:
 	ip->i_flag |= IN_CHANGE | IN_UPDATE;
 	if (vp->v_mount->mnt_flag & MNT_RELATIME)
 		ip->i_flag |= IN_ACCESS;
-	if (resid > uio->uio_resid && ap->a_cred) {
+	if (resid > uio->uio_resid && cred) {
 		if (ip->i_mode & ISUID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0) {
 				ip->i_mode &= ~ISUID;
 				DIP_ASSIGN(ip, mode, ip->i_mode);
@@ -502,7 +632,7 @@ out:
 		}
 
 		if (ip->i_mode & ISGID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0) {
 				ip->i_mode &= ~ISGID;
 				DIP_ASSIGN(ip, mode, ip->i_mode);
@@ -512,7 +642,7 @@ out:
 	if (resid > uio->uio_resid)
 		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error) {
-		(void) lfs_truncate(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		(void) lfs_truncate(vp, osize, ioflag & IO_SYNC, cred);
 		uio->uio_offset -= resid - uio->uio_resid;
 		uio->uio_resid = resid;
 	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC) {

Index: src/sys/ufs/lfs/lfs_vnops.c
diff -u src/sys/ufs/lfs/lfs_vnops.c:1.269 src/sys/ufs/lfs/lfs_vnops.c:1.270
--- src/sys/ufs/lfs/lfs_vnops.c:1.269	Fri Jul 25 08:20:53 2014
+++ src/sys/ufs/lfs/lfs_vnops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: lfs_vnops.c,v 1.269 2014/07/25 08:20:53 dholland Exp $	*/
+/*	$NetBSD: lfs_vnops.c,v 1.270 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc.
@@ -125,7 +125,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: lfs_vnops.c,v 1.269 2014/07/25 08:20:53 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: lfs_vnops.c,v 1.270 2015/03/27 17:27:56 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_compat_netbsd.h"
@@ -691,9 +691,9 @@ lfs_symlink(void *v)
 		if ((*vpp)->v_mount->mnt_flag & MNT_RELATIME)
 			ip->i_flag |= IN_ACCESS;
 	} else {
-		error = vn_rdwr(UIO_WRITE, *vpp, ap->a_target, len, (off_t)0,
-		    UIO_SYSSPACE, IO_NODELOCKED | IO_JOURNALLOCKED,
-		    ap->a_cnp->cn_cred, NULL, NULL);
+		error = ulfs_bufio(UIO_WRITE, *vpp, ap->a_target, len, (off_t)0,
+		    IO_NODELOCKED | IO_JOURNALLOCKED, ap->a_cnp->cn_cred, NULL,
+		    NULL);
 	}
 
 	VOP_UNLOCK(*vpp);

Index: src/sys/ufs/lfs/ulfs_extern.h
diff -u src/sys/ufs/lfs/ulfs_extern.h:1.13 src/sys/ufs/lfs/ulfs_extern.h:1.14
--- src/sys/ufs/lfs/ulfs_extern.h:1.13	Sun May 25 13:49:13 2014
+++ src/sys/ufs/lfs/ulfs_extern.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_extern.h,v 1.13 2014/05/25 13:49:13 hannken Exp $	*/
+/*	$NetBSD: ulfs_extern.h,v 1.14 2015/03/27 17:27:56 riastradh Exp $	*/
 /*  from NetBSD: ufs_extern.h,v 1.72 2012/05/09 00:21:18 riastradh Exp  */
 
 /*-
@@ -170,6 +170,8 @@ int	ulfs_makeinode(int, struct vnode *, 
 		      struct vnode **, struct componentname *);
 int	ulfs_gop_alloc(struct vnode *, off_t, off_t, int, kauth_cred_t);
 void	ulfs_gop_markupdate(struct vnode *, int);
+int	ulfs_bufio(enum uio_rw, struct vnode *, void *, size_t, off_t, int,
+	    kauth_cred_t, size_t *, struct lwp *);
 
 /*
  * Snapshot function prototypes.

Index: src/sys/ufs/lfs/ulfs_lookup.c
diff -u src/sys/ufs/lfs/ulfs_lookup.c:1.21 src/sys/ufs/lfs/ulfs_lookup.c:1.22
--- src/sys/ufs/lfs/ulfs_lookup.c:1.21	Tue Jun  3 19:30:30 2014
+++ src/sys/ufs/lfs/ulfs_lookup.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_lookup.c,v 1.21 2014/06/03 19:30:30 joerg Exp $	*/
+/*	$NetBSD: ulfs_lookup.c,v 1.22 2015/03/27 17:27:56 riastradh Exp $	*/
 /*  from NetBSD: ufs_lookup.c,v 1.122 2013/01/22 09:39:18 dholland Exp  */
 
 /*
@@ -38,7 +38,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ulfs_lookup.c,v 1.21 2014/06/03 19:30:30 joerg Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ulfs_lookup.c,v 1.22 2015/03/27 17:27:56 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_lfs.h"
@@ -1216,8 +1216,8 @@ ulfs_dirempty(struct inode *ip, ino_t pa
 
 	for (off = 0; off < ip->i_size;
 	    off += ulfs_rw16(dp->d_reclen, needswap)) {
-		error = vn_rdwr(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ, off,
-		   UIO_SYSSPACE, IO_NODELOCKED, cred, &count, NULL);
+		error = ulfs_bufio(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ,
+		    off, IO_NODELOCKED, cred, &count, NULL);
 		/*
 		 * Since we read MINDIRSIZ, residual must
 		 * be 0 unless we're at end of file.
Index: src/sys/ufs/lfs/ulfs_vnops.c
diff -u src/sys/ufs/lfs/ulfs_vnops.c:1.21 src/sys/ufs/lfs/ulfs_vnops.c:1.22
--- src/sys/ufs/lfs/ulfs_vnops.c:1.21	Sat May 17 07:09:09 2014
+++ src/sys/ufs/lfs/ulfs_vnops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_vnops.c,v 1.21 2014/05/17 07:09:09 dholland Exp $	*/
+/*	$NetBSD: ulfs_vnops.c,v 1.22 2015/03/27 17:27:56 riastradh Exp $	*/
 /*  from NetBSD: ufs_vnops.c,v 1.213 2013/06/08 05:47:02 kardel Exp  */
 
 /*-
@@ -67,7 +67,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ulfs_vnops.c,v 1.21 2014/05/17 07:09:09 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ulfs_vnops.c,v 1.22 2015/03/27 17:27:56 riastradh Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_lfs.h"
@@ -934,7 +934,7 @@ ulfs_readlink(void *v)
 		uiomove((char *)SHORTLINK(ip), isize, ap->a_uio);
 		return (0);
 	}
-	return (VOP_READ(vp, ap->a_uio, 0, ap->a_cred));
+	return (lfs_bufrd(vp, ap->a_uio, 0, ap->a_cred));
 }
 
 /*
@@ -1320,3 +1320,49 @@ ulfs_gop_markupdate(struct vnode *vp, in
 		ip->i_flag |= mask;
 	}
 }
+
+int
+ulfs_bufio(enum uio_rw rw, struct vnode *vp, void *buf, size_t len, off_t off,
+    int ioflg, kauth_cred_t cred, size_t *aresid, struct lwp *l)
+{
+	struct iovec iov;
+	struct uio uio;
+	int error;
+
+	/* XXX Remove me -- all callers should be locked.  */
+	if (!ISSET(ioflg, IO_NODELOCKED)) {
+		if (rw == UIO_READ)
+			vn_lock(vp, LK_SHARED | LK_RETRY);
+		else /* UIO_WRITE */
+			vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
+	}
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	uio.uio_iov = &iov;
+	uio.uio_iovcnt = 1;
+	uio.uio_resid = len;
+	uio.uio_offset = off;
+	uio.uio_rw = rw;
+	UIO_SETUP_SYSSPACE(&uio);
+
+	switch (rw) {
+	case UIO_READ:
+		error = lfs_bufrd(vp, &uio, ioflg, cred);
+		break;
+	case UIO_WRITE:
+		error = lfs_bufwr(vp, &uio, ioflg, cred);
+		break;
+	default:
+		panic("invalid uio rw: %d", (int)rw);
+	}
+
+	if (aresid)
+		*aresid = uio.uio_resid;
+	else if (uio.uio_resid && error == 0)
+		error = EIO;
+
+	if (!ISSET(ioflg, IO_NODELOCKED))
+		VOP_UNLOCK(vp);
+	return error;
+}

Index: src/sys/ufs/ufs/ufs_extern.h
diff -u src/sys/ufs/ufs/ufs_extern.h:1.78 src/sys/ufs/ufs/ufs_extern.h:1.79
--- src/sys/ufs/ufs/ufs_extern.h:1.78	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ufs/ufs_extern.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_extern.h,v 1.78 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ufs_extern.h,v 1.79 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993, 1994
@@ -186,6 +186,8 @@ void	ufs_vinit(struct mount *, int (**)(
 		  int (**)(void *), struct vnode **);
 int	ufs_gop_alloc(struct vnode *, off_t, off_t, int, kauth_cred_t);
 void	ufs_gop_markupdate(struct vnode *, int);
+int	ufs_bufio(enum uio_rw, struct vnode *, void *, size_t, off_t, int,
+	    kauth_cred_t, size_t *, struct lwp *);
 
 __END_DECLS
 

Index: src/sys/ufs/ufs/ufs_lookup.c
diff -u src/sys/ufs/ufs/ufs_lookup.c:1.132 src/sys/ufs/ufs/ufs_lookup.c:1.133
--- src/sys/ufs/ufs/ufs_lookup.c:1.132	Tue Jun  3 19:30:30 2014
+++ src/sys/ufs/ufs/ufs_lookup.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_lookup.c,v 1.132 2014/06/03 19:30:30 joerg Exp $	*/
+/*	$NetBSD: ufs_lookup.c,v 1.133 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1989, 1993
@@ -37,7 +37,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ufs_lookup.c,v 1.132 2014/06/03 19:30:30 joerg Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_lookup.c,v 1.133 2015/03/27 17:27:56 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_ffs.h"
@@ -1182,8 +1182,8 @@ ufs_dirempty(struct inode *ip, ino_t par
 
 	for (off = 0; off < ip->i_size;
 	    off += ufs_rw16(dp->d_reclen, needswap)) {
-		error = vn_rdwr(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ, off,
-		   UIO_SYSSPACE, IO_NODELOCKED, cred, &count, NULL);
+		error = ufs_bufio(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ,
+		    off, IO_NODELOCKED, cred, &count, NULL);
 		/*
 		 * Since we read MINDIRSIZ, residual must
 		 * be 0 unless we're at end of file.

Index: src/sys/ufs/ufs/ufs_readwrite.c
diff -u src/sys/ufs/ufs/ufs_readwrite.c:1.107 src/sys/ufs/ufs/ufs_readwrite.c:1.108
--- src/sys/ufs/ufs/ufs_readwrite.c:1.107	Sun Jun 23 07:28:37 2013
+++ src/sys/ufs/ufs/ufs_readwrite.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_readwrite.c,v 1.107 2013/06/23 07:28:37 dholland Exp $	*/
+/*	$NetBSD: ufs_readwrite.c,v 1.108 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1993
@@ -32,7 +32,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: ufs_readwrite.c,v 1.107 2013/06/23 07:28:37 dholland Exp $");
+__KERNEL_RCSID(1, "$NetBSD: ufs_readwrite.c,v 1.108 2015/03/27 17:27:56 riastradh Exp $");
 
 #ifdef LFS_READWRITE
 #define	FS			struct lfs
@@ -41,6 +41,8 @@ __KERNEL_RCSID(1, "$NetBSD: ufs_readwrit
 #define	READ_S			"lfs_read"
 #define	WRITE			lfs_write
 #define	WRITE_S			"lfs_write"
+#define	BUFRD			lfs_bufrd
+#define	BUFWR			lfs_bufwr
 #define	fs_bsize		lfs_bsize
 #define	fs_bmask		lfs_bmask
 #define	UFS_WAPBL_BEGIN(mp)	0
@@ -58,6 +60,8 @@ __KERNEL_RCSID(1, "$NetBSD: ufs_readwrit
 #define	READ_S			"ffs_read"
 #define	WRITE			ffs_write
 #define	WRITE_S			"ffs_write"
+#define	BUFRD			ffs_bufrd
+#define	BUFWR			ffs_bufwr
 #define ufs_blkoff		ffs_blkoff
 #define ufs_blksize		ffs_blksize
 #define ufs_lblkno		ffs_lblkno
@@ -82,14 +86,8 @@ READ(void *v)
 	struct inode *ip;
 	struct uio *uio;
 	struct ufsmount *ump;
-	struct buf *bp;
-	FS *fs;
 	vsize_t bytelen;
-	daddr_t lbn, nextlbn;
-	off_t bytesinfile;
-	long size, xfersize, blkoffset;
-	int error, ioflag;
-	bool usepc = false;
+	int error, ioflag, advice;
 
 	vp = ap->a_vp;
 	ip = VTOI(vp);
@@ -102,14 +100,17 @@ READ(void *v)
 	if (uio->uio_rw != UIO_READ)
 		panic("%s: mode", READ_S);
 
-	if (vp->v_type == VLNK) {
-		if (ip->i_size < ump->um_maxsymlinklen ||
-		    (ump->um_maxsymlinklen == 0 && DIP(ip, blocks) == 0))
-			panic("%s: short symlink", READ_S);
-	} else if (vp->v_type != VREG && vp->v_type != VDIR)
+	if (vp->v_type != VREG && vp->v_type != VDIR)
 		panic("%s: type %d", READ_S, vp->v_type);
 #endif
-	fs = ip->I_FS;
+	/* XXX Eliminate me by refusing directory reads from userland.  */
+	if (vp->v_type == VDIR)
+		return BUFRD(vp, uio, ioflag, ap->a_cred);
+#ifdef LFS_READWRITE
+	/* XXX Eliminate me by using ufs_bufio in lfs.  */
+	if (vp->v_type == VREG && ip->i_number == LFS_IFILE_INUM)
+		return BUFRD(vp, uio, ioflag, ap->a_cred);
+#endif
 	if ((u_int64_t)uio->uio_offset > ump->um_maxfilesize)
 		return (EFBIG);
 	if (uio->uio_resid == 0)
@@ -125,30 +126,81 @@ READ(void *v)
 	if (uio->uio_offset >= ip->i_size)
 		goto out;
 
-#ifdef LFS_READWRITE
-	usepc = (vp->v_type == VREG && ip->i_number != LFS_IFILE_INUM);
-#else /* !LFS_READWRITE */
-	usepc = vp->v_type == VREG;
-#endif /* !LFS_READWRITE */
-	if (usepc) {
-		const int advice = IO_ADV_DECODE(ap->a_ioflag);
+	KASSERT(vp->v_type == VREG);
+	advice = IO_ADV_DECODE(ap->a_ioflag);
+	while (uio->uio_resid > 0) {
+		if (ioflag & IO_DIRECT) {
+			genfs_directio(vp, uio, ioflag);
+		}
+		bytelen = MIN(ip->i_size - uio->uio_offset, uio->uio_resid);
+		if (bytelen == 0)
+			break;
+		error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
+		    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
+		if (error)
+			break;
+	}
 
-		while (uio->uio_resid > 0) {
-			if (ioflag & IO_DIRECT) {
-				genfs_directio(vp, uio, ioflag);
+ out:
+	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
+		ip->i_flag |= IN_ACCESS;
+		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC) {
+			error = UFS_WAPBL_BEGIN(vp->v_mount);
+			if (error) {
+				fstrans_done(vp->v_mount);
+				return error;
 			}
-			bytelen = MIN(ip->i_size - uio->uio_offset,
-			    uio->uio_resid);
-			if (bytelen == 0)
-				break;
-			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
-			    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
-			if (error)
-				break;
+			error = UFS_UPDATE(vp, NULL, NULL, UPDATE_WAIT);
+			UFS_WAPBL_END(vp->v_mount);
 		}
-		goto out;
 	}
 
+	fstrans_done(vp->v_mount);
+	return (error);
+}
+
+/*
+ * UFS op for reading via the buffer cache
+ */
+int
+BUFRD(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	struct ufsmount *ump;
+	FS *fs;
+	struct buf *bp;
+	daddr_t lbn, nextlbn;
+	off_t bytesinfile;
+	long size, xfersize, blkoffset;
+	int error;
+
+	KASSERT(VOP_ISLOCKED(vp));
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
+	KASSERT(uio->uio_rw == UIO_READ);
+
+	ip = VTOI(vp);
+	ump = ip->i_ump;
+	fs = ip->I_FS;
+	error = 0;
+
+	KASSERT(vp->v_type != VLNK || ip->i_size >= ump->um_maxsymlinklen);
+	KASSERT(vp->v_type != VLNK || ump->um_maxsymlinklen != 0 ||
+	    DIP(ip, blocks) == 0);
+
+	if (uio->uio_offset > ump->um_maxfilesize)
+		return EFBIG;
+	if (uio->uio_resid == 0)
+		return 0;
+
+#ifndef LFS_READWRITE
+	KASSERT(!ISSET(ip->i_flags, (SF_SNAPSHOT | SF_SNAPINVAL)));
+#endif
+
+	fstrans_start(vp->v_mount, FSTRANS_SHARED);
+
+	if (uio->uio_offset >= ip->i_size)
+		goto out;
+
 	for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
 		bytesinfile = ip->i_size - uio->uio_offset;
 		if (bytesinfile <= 0)
@@ -194,7 +246,7 @@ READ(void *v)
  out:
 	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
 		ip->i_flag |= IN_ACCESS;
-		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC) {
+		if ((ioflag & IO_SYNC) == IO_SYNC) {
 			error = UFS_WAPBL_BEGIN(vp->v_mount);
 			if (error) {
 				fstrans_done(vp->v_mount);
@@ -225,19 +277,13 @@ WRITE(void *v)
 	struct uio *uio;
 	struct inode *ip;
 	FS *fs;
-	struct buf *bp;
 	kauth_cred_t cred;
-	daddr_t lbn;
 	off_t osize, origoff, oldoff, preallocoff, endallocoff, nsize;
-	int blkoffset, error, flags, ioflag, resid, size, xfersize;
+	int blkoffset, error, flags, ioflag, resid;
 	int aflag;
 	int extended=0;
 	vsize_t bytelen;
 	bool async;
-	bool usepc = false;
-#ifdef LFS_READWRITE
-	bool need_unreserve = false;
-#endif
 	struct ufsmount *ump;
 
 	cred = ap->a_cred;
@@ -259,12 +305,6 @@ WRITE(void *v)
 			uio->uio_offset = ip->i_size;
 		if ((ip->i_flags & APPEND) && uio->uio_offset != ip->i_size)
 			return (EPERM);
-		/* FALLTHROUGH */
-	case VLNK:
-		break;
-	case VDIR:
-		if ((ioflag & IO_SYNC) == 0)
-			panic("%s: nonsync dir write", WRITE_S);
 		break;
 	default:
 		panic("%s: type", WRITE_S);
@@ -292,7 +332,7 @@ WRITE(void *v)
 	osize = ip->i_size;
 	error = 0;
 
-	usepc = vp->v_type == VREG;
+	KASSERT(vp->v_type == VREG);
 
 	if ((ioflag & IO_JOURNALLOCKED) == 0) {
 		error = UFS_WAPBL_BEGIN(vp->v_mount);
@@ -307,8 +347,6 @@ WRITE(void *v)
 	lfs_availwait(fs, btofsb(fs, uio->uio_resid));
 	lfs_check(vp, LFS_UNUSED_LBN, 0);
 #endif /* !LFS_READWRITE */
-	if (!usepc)
-		goto bcache;
 
 	preallocoff = round_page(ufs_blkroundup(fs, MAX(osize, uio->uio_offset)));
 	aflag = ioflag & IO_SYNC ? B_SYNC : 0;
@@ -440,9 +478,110 @@ WRITE(void *v)
 		    round_page(ufs_blkroundup(fs, uio->uio_offset)),
 		    PGO_CLEANIT | PGO_SYNCIO | PGO_JOURNALLOCKED);
 	}
-	goto out;
 
- bcache:
+	/*
+	 * If we successfully wrote any data, and we are not the superuser
+	 * we clear the setuid and setgid bits as a precaution against
+	 * tampering.
+	 */
+out:
+	ip->i_flag |= IN_CHANGE | IN_UPDATE;
+	if (vp->v_mount->mnt_flag & MNT_RELATIME)
+		ip->i_flag |= IN_ACCESS;
+	if (resid > uio->uio_resid && ap->a_cred) {
+		if (ip->i_mode & ISUID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0) {
+				ip->i_mode &= ~ISUID;
+				DIP_ASSIGN(ip, mode, ip->i_mode);
+			}
+		}
+
+		if (ip->i_mode & ISGID) {
+			if (kauth_authorize_vnode(ap->a_cred,
+			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0) {
+				ip->i_mode &= ~ISGID;
+				DIP_ASSIGN(ip, mode, ip->i_mode);
+			}
+		}
+	}
+	if (resid > uio->uio_resid)
+		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
+	if (error) {
+		(void) UFS_TRUNCATE(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		uio->uio_offset -= resid - uio->uio_resid;
+		uio->uio_resid = resid;
+	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC)
+		error = UFS_UPDATE(vp, NULL, NULL, UPDATE_WAIT);
+	else
+		UFS_WAPBL_UPDATE(vp, NULL, NULL, 0);
+	KASSERT(vp->v_size == ip->i_size);
+	if ((ioflag & IO_JOURNALLOCKED) == 0)
+		UFS_WAPBL_END(vp->v_mount);
+	fstrans_done(vp->v_mount);
+
+	return (error);
+}
+
+/*
+ * UFS op for writing via the buffer cache
+ */
+int
+BUFWR(struct vnode *vp, struct uio *uio, int ioflag, kauth_cred_t cred)
+{
+	struct inode *ip;
+	struct ufsmount *ump;
+	FS *fs;
+	int flags;
+	struct buf *bp;
+	off_t osize, origoff;
+	int resid, xfersize, size, blkoffset;
+	daddr_t lbn;
+	int extended=0;
+	int error;
+#ifdef LFS_READWRITE
+	bool need_unreserve = false;
+#endif
+
+	KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
+	KASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
+	KASSERT(vp->v_type != VDIR || ISSET(ioflag, IO_SYNC));
+	KASSERT(uio->uio_rw == UIO_WRITE);
+
+	ip = VTOI(vp);
+	ump = ip->i_ump;
+	fs = ip->I_FS;
+
+	KASSERT(vp->v_size == ip->i_size);
+
+	if (uio->uio_offset < 0 ||
+	    uio->uio_resid > ump->um_maxfilesize ||
+	    uio->uio_offset > (ump->um_maxfilesize - uio->uio_resid))
+		return EFBIG;
+#ifdef LFS_READWRITE
+	KASSERT(vp != fs->lfs_ivnode);
+#endif
+	if (uio->uio_resid == 0)
+		return 0;
+
+	fstrans_start(vp->v_mount, FSTRANS_SHARED);
+
+	flags = ioflag & IO_SYNC ? B_SYNC : 0;
+	origoff = uio->uio_offset;
+	resid = uio->uio_resid;
+	osize = ip->i_size;
+	error = 0;
+
+	KASSERT(vp->v_type != VREG);
+	KASSERT(ISSET(ioflag, IO_JOURNALLOCKED));
+	UFS_WAPBL_JLOCK_ASSERT(vp->v_mount);
+
+#ifdef LFS_READWRITE
+	lfs_availwait(fs, btofsb(fs, uio->uio_resid));
+	lfs_check(vp, LFS_UNUSED_LBN, 0);
+#endif /* !LFS_READWRITE */
+
+	/* XXX Should never have pages cached here.  */
 	mutex_enter(vp->v_interlock);
 	VOP_PUTPAGES(vp, trunc_page(origoff), round_page(origoff + resid),
 	    PGO_CLEANIT | PGO_FREE | PGO_SYNCIO | PGO_JOURNALLOCKED);
@@ -462,8 +601,8 @@ WRITE(void *v)
 			break;
 		need_unreserve = true;
 #endif
-		error = UFS_BALLOC(vp, uio->uio_offset, xfersize,
-		    ap->a_cred, flags, &bp);
+		error = UFS_BALLOC(vp, uio->uio_offset, xfersize, cred, flags,
+		    &bp);
 
 		if (error)
 			break;
@@ -516,13 +655,12 @@ WRITE(void *v)
 	 * we clear the setuid and setgid bits as a precaution against
 	 * tampering.
 	 */
-out:
 	ip->i_flag |= IN_CHANGE | IN_UPDATE;
 	if (vp->v_mount->mnt_flag & MNT_RELATIME)
 		ip->i_flag |= IN_ACCESS;
-	if (resid > uio->uio_resid && ap->a_cred) {
+	if (resid > uio->uio_resid && cred) {
 		if (ip->i_mode & ISUID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0) {
 				ip->i_mode &= ~ISUID;
 				DIP_ASSIGN(ip, mode, ip->i_mode);
@@ -530,7 +668,7 @@ out:
 		}
 
 		if (ip->i_mode & ISGID) {
-			if (kauth_authorize_vnode(ap->a_cred,
+			if (kauth_authorize_vnode(cred,
 			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0) {
 				ip->i_mode &= ~ISGID;
 				DIP_ASSIGN(ip, mode, ip->i_mode);
@@ -540,7 +678,7 @@ out:
 	if (resid > uio->uio_resid)
 		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error) {
-		(void) UFS_TRUNCATE(vp, osize, ioflag & IO_SYNC, ap->a_cred);
+		(void) UFS_TRUNCATE(vp, osize, ioflag & IO_SYNC, cred);
 		uio->uio_offset -= resid - uio->uio_resid;
 		uio->uio_resid = resid;
 	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC)

Index: src/sys/ufs/ufs/ufs_rename.c
diff -u src/sys/ufs/ufs/ufs_rename.c:1.11 src/sys/ufs/ufs/ufs_rename.c:1.12
--- src/sys/ufs/ufs/ufs_rename.c:1.11	Sun May 25 13:45:39 2014
+++ src/sys/ufs/ufs/ufs_rename.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_rename.c,v 1.11 2014/05/25 13:45:39 hannken Exp $	*/
+/*	$NetBSD: ufs_rename.c,v 1.12 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ufs_rename.c,v 1.11 2014/05/25 13:45:39 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_rename.c,v 1.12 2015/03/27 17:27:56 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/buf.h>
@@ -874,8 +874,8 @@ ufs_read_dotdot(struct vnode *vp, kauth_
 	KASSERT(ino_ret != NULL);
 	KASSERT(vp->v_type == VDIR);
 
-	error = vn_rdwr(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
-	    UIO_SYSSPACE, IO_NODELOCKED, cred, NULL, NULL);
+	error = ufs_bufio(UIO_READ, vp, &dirbuf, sizeof dirbuf, (off_t)0,
+	    IO_NODELOCKED, cred, NULL, NULL);
 	if (error)
 		return error;
 

Index: src/sys/ufs/ufs/ufs_vnops.c
diff -u src/sys/ufs/ufs/ufs_vnops.c:1.225 src/sys/ufs/ufs/ufs_vnops.c:1.226
--- src/sys/ufs/ufs/ufs_vnops.c:1.225	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ufs/ufs_vnops.c	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_vnops.c,v 1.225 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ufs_vnops.c,v 1.226 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2008 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.225 2015/03/17 09:39:29 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.226 2015/03/27 17:27:56 riastradh Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_ffs.h"
@@ -1210,9 +1210,9 @@ ufs_symlink(void *v)
 			ip->i_flag |= IN_ACCESS;
 		UFS_WAPBL_UPDATE(vp, NULL, NULL, 0);
 	} else
-		error = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
-		    UIO_SYSSPACE, IO_NODELOCKED | IO_JOURNALLOCKED,
-		    ap->a_cnp->cn_cred, NULL, NULL);
+		error = ufs_bufio(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
+		    IO_NODELOCKED | IO_JOURNALLOCKED, ap->a_cnp->cn_cred, NULL,
+		    NULL);
 	UFS_WAPBL_END1(ap->a_dvp->v_mount, ap->a_dvp);
 	VOP_UNLOCK(vp);
 	if (error)
@@ -1279,7 +1279,7 @@ ufs_readdir(void *v)
 	cdbuf = kmem_alloc(cdbufsz, KM_SLEEP);
 	aiov.iov_base = cdbuf;
 	aiov.iov_len = rcount;
-	error = VOP_READ(vp, &auio, 0, ap->a_cred);
+	error = UFS_BUFRD(vp, &auio, 0, ap->a_cred);
 	if (error != 0) {
 		kmem_free(cdbuf, cdbufsz);
 		return error;
@@ -1399,7 +1399,7 @@ ufs_readlink(void *v)
 		uiomove((char *)SHORTLINK(ip), isize, ap->a_uio);
 		return (0);
 	}
-	return (VOP_READ(vp, ap->a_uio, 0, ap->a_cred));
+	return (UFS_BUFRD(vp, ap->a_uio, 0, ap->a_cred));
 }
 
 /*
@@ -1887,3 +1887,49 @@ ufs_gop_markupdate(struct vnode *vp, int
 		ip->i_flag |= mask;
 	}
 }
+
+int
+ufs_bufio(enum uio_rw rw, struct vnode *vp, void *buf, size_t len, off_t off,
+    int ioflg, kauth_cred_t cred, size_t *aresid, struct lwp *l)
+{
+	struct iovec iov;
+	struct uio uio;
+	int error;
+
+	/* XXX Remove me -- all callers should be locked.  */
+	if (!ISSET(ioflg, IO_NODELOCKED)) {
+		if (rw == UIO_READ)
+			vn_lock(vp, LK_SHARED | LK_RETRY);
+		else /* UIO_WRITE */
+			vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
+	}
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	uio.uio_iov = &iov;
+	uio.uio_iovcnt = 1;
+	uio.uio_resid = len;
+	uio.uio_offset = off;
+	uio.uio_rw = rw;
+	UIO_SETUP_SYSSPACE(&uio);
+
+	switch (rw) {
+	case UIO_READ:
+		error = UFS_BUFRD(vp, &uio, ioflg, cred);
+		break;
+	case UIO_WRITE:
+		error = UFS_BUFWR(vp, &uio, ioflg, cred);
+		break;
+	default:
+		panic("invalid uio rw: %d", (int)rw);
+	}
+
+	if (aresid)
+		*aresid = uio.uio_resid;
+	else if (uio.uio_resid && error == 0)
+		error = EIO;
+
+	if (!ISSET(ioflg, IO_NODELOCKED))
+		VOP_UNLOCK(vp);
+	return error;
+}

Index: src/sys/ufs/ufs/ufsmount.h
diff -u src/sys/ufs/ufs/ufsmount.h:1.42 src/sys/ufs/ufs/ufsmount.h:1.43
--- src/sys/ufs/ufs/ufsmount.h:1.42	Tue Mar 17 09:39:29 2015
+++ src/sys/ufs/ufs/ufsmount.h	Fri Mar 27 17:27:56 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufsmount.h,v 1.42 2015/03/17 09:39:29 hannken Exp $	*/
+/*	$NetBSD: ufsmount.h,v 1.43 2015/03/27 17:27:56 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1989, 1993
@@ -137,6 +137,8 @@ struct ufs_ops {
 	int (*uo_balloc)(struct vnode *, off_t, int, kauth_cred_t, int,
 	    struct buf **);
         void (*uo_snapgone)(struct vnode *);
+	int (*uo_bufrd)(struct vnode *, struct uio *, int, kauth_cred_t);
+	int (*uo_bufwr)(struct vnode *, struct uio *, int, kauth_cred_t);
 };
 
 #define	UFS_OPS(vp)	(VFSTOUFS((vp)->v_mount)->um_ops)
@@ -151,6 +153,10 @@ struct ufs_ops {
 	(*UFS_OPS(vp)->uo_balloc)((vp), (off), (size), (cr), (flags), (bpp))
 #define	UFS_SNAPGONE(vp) \
 	(*UFS_OPS(vp)->uo_snapgone)((vp))
+#define	UFS_BUFRD(vp, uio, ioflag, cred) \
+	(*UFS_OPS(vp)->uo_bufrd)((vp), (uio), (ioflag), (cred))
+#define	UFS_BUFWR(vp, uio, ioflag, cred) \
+	(*UFS_OPS(vp)->uo_bufwr)((vp), (uio), (ioflag), (cred))
 
 /* UFS-specific flags */
 #define UFS_NEEDSWAP	0x01	/* filesystem metadata need byte-swapping */

Reply via email to