Module Name:    src
Committed By:   ad
Date:           Thu Jan 16 16:45:31 UTC 2020

Modified Files:
        src/sys/kern [ad-namecache]: vfs_lookup.c

Log Message:
Push the vnode locking in namei() about as far back as it will go.


To generate a diff of this commit:
cvs rdiff -u -r1.212 -r1.212.4.1 src/sys/kern/vfs_lookup.c

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

Modified files:

Index: src/sys/kern/vfs_lookup.c
diff -u src/sys/kern/vfs_lookup.c:1.212 src/sys/kern/vfs_lookup.c:1.212.4.1
--- src/sys/kern/vfs_lookup.c:1.212	Thu Jul 18 09:39:40 2019
+++ src/sys/kern/vfs_lookup.c	Thu Jan 16 16:45:30 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: vfs_lookup.c,v 1.212 2019/07/18 09:39:40 hannken Exp $	*/
+/*	$NetBSD: vfs_lookup.c,v 1.212.4.1 2020/01/16 16:45:30 ad Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1989, 1993
@@ -37,7 +37,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: vfs_lookup.c,v 1.212 2019/07/18 09:39:40 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: vfs_lookup.c,v 1.212.4.1 2020/01/16 16:45:30 ad Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_magiclinks.h"
@@ -710,8 +710,6 @@ namei_start(struct namei_state *state, i
 		return ENOTDIR;
 	}
 
-	vn_lock(startdir, LK_EXCLUSIVE | LK_RETRY);
-
 	*startdir_ret = startdir;
 	return 0;
 }
@@ -749,15 +747,17 @@ namei_follow(struct namei_state *state, 
 	size_t linklen;
 	int error;
 
-	KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
-	KASSERT(VOP_ISLOCKED(foundobj) == LK_EXCLUSIVE);
 	if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
 		return ELOOP;
 	}
+
+	vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
 	if (foundobj->v_mount->mnt_flag & MNT_SYMPERM) {
 		error = VOP_ACCESS(foundobj, VEXEC, cnp->cn_cred);
-		if (error != 0)
+		if (error != 0) {
+			VOP_UNLOCK(foundobj);
 			return error;
+		}
 	}
 
 	/* FUTURE: fix this to not use a second buffer */
@@ -771,6 +771,7 @@ namei_follow(struct namei_state *state, 
 	auio.uio_resid = MAXPATHLEN;
 	UIO_SETUP_SYSSPACE(&auio);
 	error = VOP_READLINK(foundobj, &auio, cnp->cn_cred);
+	VOP_UNLOCK(foundobj);
 	if (error) {
 		PNBUF_PUT(cp);
 		return error;
@@ -807,14 +808,11 @@ namei_follow(struct namei_state *state, 
 	/* we're now starting from the beginning of the buffer again */
 	cnp->cn_nameptr = ndp->ni_pnbuf;
 
-	/* must unlock this before relocking searchdir */
-	VOP_UNLOCK(foundobj);
-
 	/*
 	 * Check if root directory should replace current directory.
 	 */
 	if (ndp->ni_pnbuf[0] == '/') {
-		vput(searchdir);
+		vrele(searchdir);
 		/* Keep absolute symbolic links inside emulation root */
 		searchdir = ndp->ni_erootdir;
 		if (searchdir == NULL ||
@@ -825,7 +823,6 @@ namei_follow(struct namei_state *state, 
 			searchdir = ndp->ni_rootdir;
 		}
 		vref(searchdir);
-		vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
 		while (cnp->cn_nameptr[0] == '/') {
 			cnp->cn_nameptr++;
 			ndp->ni_pathlen--;
@@ -833,7 +830,6 @@ namei_follow(struct namei_state *state, 
 	}
 
 	*newsearchdir_ret = searchdir;
-	KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
 	return 0;
 }
 
@@ -933,19 +929,20 @@ static int
 lookup_once(struct namei_state *state,
 	    struct vnode *searchdir,
 	    struct vnode **newsearchdir_ret,
-	    struct vnode **foundobj_ret)
+	    struct vnode **foundobj_ret,
+	    bool *newsearchdir_locked_ret)
 {
 	struct vnode *tmpvn;		/* scratch vnode */
 	struct vnode *foundobj;		/* result */
 	struct mount *mp;		/* mount table entry */
 	struct lwp *l = curlwp;
+	bool searchdir_locked = false;
 	int error;
 
 	struct componentname *cnp = state->cnp;
 	struct nameidata *ndp = state->ndp;
 
 	KASSERT(cnp == &ndp->ni_cnd);
-	KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
 	*newsearchdir_ret = searchdir;
 
 	/*
@@ -977,9 +974,7 @@ lookup_once(struct namei_state *state,
 			if (ndp->ni_rootdir != rootvnode) {
 				int retval;
 
-				VOP_UNLOCK(searchdir);
 				retval = vn_isunder(searchdir, ndp->ni_rootdir, l);
-				vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
 				if (!retval) {
 				    /* Oops! We got out of jail! */
 				    log(LOG_WARNING,
@@ -988,12 +983,11 @@ lookup_once(struct namei_state *state,
 					p->p_pid, kauth_cred_geteuid(l->l_cred),
 					p->p_comm);
 				    /* Put us at the jail root. */
-				    vput(searchdir);
+				    vrele(searchdir);
 				    searchdir = NULL;
 				    foundobj = ndp->ni_rootdir;
 				    vref(foundobj);
 				    vref(foundobj);
-				    vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
 				    *newsearchdir_ret = foundobj;
 				    *foundobj_ret = foundobj;
 				    error = 0;
@@ -1006,18 +1000,19 @@ lookup_once(struct namei_state *state,
 			tmpvn = searchdir;
 			searchdir = searchdir->v_mount->mnt_vnodecovered;
 			vref(searchdir);
-			vput(tmpvn);
-			vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+			vrele(tmpvn);
 			*newsearchdir_ret = searchdir;
 		}
 	}
 
 	/*
 	 * We now have a segment name to search for, and a directory to search.
-	 * Our vnode state here is that "searchdir" is held and locked.
+	 * Our vnode state here is that "searchdir" is held.
 	 */
 unionlookup:
 	foundobj = NULL;
+	vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+	searchdir_locked = true;
 	error = VOP_LOOKUP(searchdir, &foundobj, cnp);
 
 	if (error != 0) {
@@ -1034,7 +1029,7 @@ unionlookup:
 			searchdir = searchdir->v_mount->mnt_vnodecovered;
 			vref(searchdir);
 			vput(tmpvn);
-			vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+			searchdir_locked = false;
 			*newsearchdir_ret = searchdir;
 			goto unionlookup;
 		}
@@ -1089,84 +1084,99 @@ unionlookup:
 	}
 
 	/*
-	 * "searchdir" is locked and held, "foundobj" is held,
-	 * they may be the same vnode.
-	 */
-	if (searchdir != foundobj) {
-		if (cnp->cn_flags & ISDOTDOT)
-			VOP_UNLOCK(searchdir);
-		error = vn_lock(foundobj, LK_EXCLUSIVE);
-		if (cnp->cn_flags & ISDOTDOT)
-			vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
-		if (error != 0) {
-			vrele(foundobj);
-			goto done;
-		}
-	}
-
-	/*
-	 * Check to see if the vnode has been mounted on;
-	 * if so find the root of the mounted file system.
+	 * Do an unlocked check to see if the vnode has been mounted on; if
+	 * so find the root of the mounted file system.
 	 */
 	KASSERT(searchdir != NULL);
-	while (foundobj->v_type == VDIR &&
-	       (mp = foundobj->v_mountedhere) != NULL &&
-	       (cnp->cn_flags & NOCROSSMOUNT) == 0) {
+	if (foundobj->v_type == VDIR && foundobj->v_mountedhere != NULL &&
+	    (cnp->cn_flags & NOCROSSMOUNT) == 0) {
+		/*
+		 * "searchdir" is locked and held, "foundobj" is held,
+		 * they may be the same vnode.
+		 */
+		if (searchdir != foundobj) {
+			if (vn_lock(foundobj, LK_EXCLUSIVE | LK_NOWAIT) != 0) {
+				if (cnp->cn_flags & ISDOTDOT)
+					VOP_UNLOCK(searchdir);
+				error = vn_lock(foundobj, LK_EXCLUSIVE);
+				if (cnp->cn_flags & ISDOTDOT)
+					vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+				if (error != 0) {
+					vrele(foundobj);
+					goto done;
+				}
+			}
+		}
+		while (foundobj->v_type == VDIR &&
+		       (mp = foundobj->v_mountedhere) != NULL &&
+		       (cnp->cn_flags & NOCROSSMOUNT) == 0) {
 
-		KASSERT(searchdir != foundobj);
+			KASSERT(searchdir != foundobj);
 
-		error = vfs_busy(mp);
-		if (error != 0) {
-			vput(foundobj);
-			goto done;
-		}
-		if (searchdir != NULL) {
-			VOP_UNLOCK(searchdir);
-		}
-		vput(foundobj);
-		error = VFS_ROOT(mp, &foundobj);
-		vfs_unbusy(mp);
-		if (error) {
+			error = vfs_busy(mp);
+			if (error != 0) {
+				vput(foundobj);
+				goto done;
+			}
 			if (searchdir != NULL) {
+				VOP_UNLOCK(searchdir);
+				searchdir_locked = false;
+			}
+			vput(foundobj);
+			error = VFS_ROOT(mp, &foundobj);
+			vfs_unbusy(mp);
+			if (error) {
+				if (searchdir != NULL) {
+					vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+					searchdir_locked = true;
+				}
+				goto done;
+			}
+			/*
+			 * Avoid locking vnodes from two filesystems because
+			 * it's prone to deadlock, e.g. when using puffs.
+			 * Also, it isn't a good idea to propagate slowness of
+			 * a filesystem up to the root directory. For now,
+			 * only handle the common case, where foundobj is
+			 * VDIR.
+			 *
+			 * In this case set searchdir to null to avoid using
+			 * it again. It is not correct to set searchdir ==
+			 * foundobj here as that will confuse the caller.
+			 * (See PR 47040.)
+			 */
+			if (searchdir == NULL) {
+				/* already been here once; do nothing further */
+			} else if (foundobj->v_type == VDIR) {
+				vrele(searchdir);
+				*newsearchdir_ret = searchdir = NULL;
+			} else {
+				VOP_UNLOCK(foundobj);
 				vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
+				vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
+				searchdir_locked = true;
 			}
-			goto done;
 		}
-		/*
-		 * Avoid locking vnodes from two filesystems because
-		 * it's prone to deadlock, e.g. when using puffs.
-		 * Also, it isn't a good idea to propagate slowness of
-		 * a filesystem up to the root directory. For now,
-		 * only handle the common case, where foundobj is
-		 * VDIR.
-		 *
-		 * In this case set searchdir to null to avoid using
-		 * it again. It is not correct to set searchdir ==
-		 * foundobj here as that will confuse the caller.
-		 * (See PR 40740.)
-		 */
-		if (searchdir == NULL) {
-			/* already been here once; do nothing further */
-		} else if (foundobj->v_type == VDIR) {
-			vrele(searchdir);
-			*newsearchdir_ret = searchdir = NULL;
-		} else {
-			VOP_UNLOCK(foundobj);
-			vn_lock(searchdir, LK_EXCLUSIVE | LK_RETRY);
-			vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
+		KASSERT(searchdir != foundobj);
+		VOP_UNLOCK(foundobj);
+	}
+
+	/* Unlock, unless the caller needs the parent locked. */
+	if (searchdir != NULL) {
+		KASSERT(searchdir_locked);
+		if ((cnp->cn_flags & (ISLASTCN | LOCKPARENT)) !=
+		    (ISLASTCN | LOCKPARENT)) {
+		    	VOP_UNLOCK(searchdir);
+		    	searchdir_locked = false;
 		}
+	} else {
+		KASSERT(!searchdir_locked);
 	}
 
 	*foundobj_ret = foundobj;
 	error = 0;
 done:
-	KASSERT(*newsearchdir_ret == NULL ||
-		VOP_ISLOCKED(*newsearchdir_ret) == LK_EXCLUSIVE);
-	/*
-	 * *foundobj_ret is valid only if error == 0.
-	 */
-	KASSERT(error != 0 || *foundobj_ret == NULL ||
-	    VOP_ISLOCKED(*foundobj_ret) == LK_EXCLUSIVE);
+	*newsearchdir_locked_ret = searchdir_locked;
 	return error;
 }
 
@@ -1183,6 +1193,7 @@ namei_oneroot(struct namei_state *state,
 	struct nameidata *ndp = state->ndp;
 	struct componentname *cnp = state->cnp;
 	struct vnode *searchdir, *foundobj;
+	bool searchdir_locked = false;
 	int error;
 
 	error = namei_start(state, isnfsd, &searchdir);
@@ -1223,7 +1234,7 @@ namei_oneroot(struct namei_state *state,
 
 	for (;;) {
 		KASSERT(searchdir != NULL);
-		KASSERT(VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
+		KASSERT(!searchdir_locked);
 
 		/*
 		 * If the directory we're on is unmounted, bail out.
@@ -1231,7 +1242,7 @@ namei_oneroot(struct namei_state *state,
 		 * XXX: yes it should... but how?
 		 */
 		if (searchdir->v_mount == NULL) {
-			vput(searchdir);
+			vrele(searchdir);
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
 			return (ENOENT);
@@ -1250,17 +1261,23 @@ namei_oneroot(struct namei_state *state,
 
 		error = lookup_parsepath(state);
 		if (error) {
-			vput(searchdir);
+			vrele(searchdir);
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
 			state->attempt_retry = 1;
 			return (error);
 		}
 
-		error = lookup_once(state, searchdir, &searchdir, &foundobj);
+		error = lookup_once(state, searchdir, &searchdir, &foundobj,
+		    &searchdir_locked);
 		if (error) {
 			if (searchdir != NULL) {
-				vput(searchdir);
+				if (searchdir_locked) {
+					searchdir_locked = false;
+					vput(searchdir);
+				} else {
+					vrele(searchdir);
+				}
 			}
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
@@ -1297,6 +1314,11 @@ namei_oneroot(struct namei_state *state,
 		 * them again.
 		 */
 		if (namei_atsymlink(state, foundobj)) {
+			/* Don't need searchdir locked any more. */
+			if (searchdir_locked) {
+				searchdir_locked = false;
+				VOP_UNLOCK(searchdir);
+			}
 			ndp->ni_pathlen += state->slashes;
 			ndp->ni_next -= state->slashes;
 			if (neverfollow) {
@@ -1338,14 +1360,13 @@ namei_oneroot(struct namei_state *state,
 			if (error) {
 				KASSERT(searchdir != foundobj);
 				if (searchdir != NULL) {
-					vput(searchdir);
+					vrele(searchdir);
 				}
-				vput(foundobj);
+				vrele(foundobj);
 				ndp->ni_dvp = NULL;
 				ndp->ni_vp = NULL;
 				return error;
 			}
-			/* namei_follow unlocks it (ugh) so rele, not put */
 			vrele(foundobj);
 			foundobj = NULL;
 
@@ -1376,9 +1397,16 @@ namei_oneroot(struct namei_state *state,
 		    (cnp->cn_flags & REQUIREDIR)) {
 			KASSERT(foundobj != searchdir);
 			if (searchdir) {
-				vput(searchdir);
+				if (searchdir_locked) {
+					searchdir_locked = false;
+					vput(searchdir);
+				} else {
+					vrele(searchdir);
+				}
+			} else {
+				KASSERT(!searchdir_locked);
 			}
-			vput(foundobj);
+			vrele(foundobj);
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
 			state->attempt_retry = 1;
@@ -1396,15 +1424,21 @@ namei_oneroot(struct namei_state *state,
 		 * Continue with the next component.
 		 */
 		cnp->cn_nameptr = ndp->ni_next;
-		if (searchdir == foundobj) {
-			vrele(searchdir);
-		} else if (searchdir != NULL) {
-			vput(searchdir);
+		if (searchdir != NULL) {
+			if (searchdir_locked) {
+				searchdir_locked = false;
+				vput(searchdir);
+			} else {
+				vrele(searchdir);
+			}
 		}
 		searchdir = foundobj;
 		foundobj = NULL;
 	}
 
+	KASSERT((cnp->cn_flags & LOCKPARENT) == 0 || searchdir == NULL ||
+	    VOP_ISLOCKED(searchdir) == LK_EXCLUSIVE);
+
  skiploop:
 
 	if (foundobj != NULL) {
@@ -1417,16 +1451,15 @@ namei_oneroot(struct namei_state *state,
 			 * forever.  So convert it to the real root.
 			 */
 			if (searchdir != NULL) {
-				if (searchdir == foundobj)
-					vrele(searchdir);
-				else
-					vput(searchdir);
+				if ((cnp->cn_flags & LOCKPARENT) != 0) {
+					VOP_UNLOCK(searchdir);
+				}
+				vrele(searchdir);
 				searchdir = NULL;
 			}
-			vput(foundobj);
+			vrele(foundobj);
 			foundobj = ndp->ni_rootdir;
 			vref(foundobj);
-			vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
 		}
 
 		/*
@@ -1439,9 +1472,12 @@ namei_oneroot(struct namei_state *state,
 		    (searchdir == NULL ||
 		     searchdir->v_mount != foundobj->v_mount)) {
 			if (searchdir) {
-				vput(searchdir);
+				if ((cnp->cn_flags & LOCKPARENT) != 0) {
+					VOP_UNLOCK(searchdir);
+				}
+				vrele(searchdir);
 			}
-			vput(foundobj);
+			vrele(foundobj);
 			foundobj = NULL;
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
@@ -1466,21 +1502,23 @@ namei_oneroot(struct namei_state *state,
 		if (state->rdonly &&
 		    (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) {
 			if (searchdir) {
-				if (foundobj != searchdir) {
-					vput(searchdir);
-				} else {
-					vrele(searchdir);
+				if ((cnp->cn_flags & LOCKPARENT) != 0) {
+					VOP_UNLOCK(searchdir);
 				}
+				vrele(searchdir);
 				searchdir = NULL;
 			}
-			vput(foundobj);
+			vrele(foundobj);
 			foundobj = NULL;
 			ndp->ni_dvp = NULL;
 			ndp->ni_vp = NULL;
 			state->attempt_retry = 1;
 			return EROFS;
 		}
-		if ((cnp->cn_flags & LOCKLEAF) == 0) {
+
+		/* Lock the leaf node if requested. */
+		if ((cnp->cn_flags & (LOCKLEAF | LOCKPARENT)) == LOCKPARENT &&
+		    searchdir == foundobj) {
 			/*
 			 * Note: if LOCKPARENT but not LOCKLEAF is
 			 * set, and searchdir == foundobj, this code
@@ -1492,7 +1530,11 @@ namei_oneroot(struct namei_state *state,
 			 * that uses this combination "knows" this, so
 			 * it can't be safely changed. Feh. XXX
 			 */
-			VOP_UNLOCK(foundobj);
+		    	VOP_UNLOCK(searchdir);
+		} else if ((cnp->cn_flags & LOCKLEAF) != 0 &&
+		    (searchdir != foundobj ||
+		    (cnp->cn_flags & LOCKPARENT) == 0)) {
+			vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
 		}
 	}
 
@@ -1504,11 +1546,7 @@ namei_oneroot(struct namei_state *state,
 	 * If LOCKPARENT is not set, the parent directory isn't returned.
 	 */
 	if ((cnp->cn_flags & LOCKPARENT) == 0 && searchdir != NULL) {
-		if (searchdir == foundobj) {
-			vrele(searchdir);
-		} else {
-			vput(searchdir);
-		}
+		vrele(searchdir);
 		searchdir = NULL;
 	}
 
@@ -1645,11 +1683,11 @@ static int
 do_lookup_for_nfsd_index(struct namei_state *state)
 {
 	int error = 0;
-
 	struct componentname *cnp = state->cnp;
 	struct nameidata *ndp = state->ndp;
 	struct vnode *startdir;
 	struct vnode *foundobj;
+	bool startdir_locked;
 	const char *cp;			/* pointer into pathname argument */
 
 	KASSERT(cnp == &ndp->ni_cnd);
@@ -1682,12 +1720,17 @@ do_lookup_for_nfsd_index(struct namei_st
 	 * own reference to it to avoid consuming the caller's.
 	 */
 	vref(startdir);
-	vn_lock(startdir, LK_EXCLUSIVE | LK_RETRY);
-	error = lookup_once(state, startdir, &startdir, &foundobj);
+	error = lookup_once(state, startdir, &startdir, &foundobj,
+	    &startdir_locked);
+	KASSERT(!startdir_locked);
 	if (error == 0 && startdir == foundobj) {
 		vrele(startdir);
 	} else if (startdir != NULL) {
-		vput(startdir);
+		if (startdir_locked) {
+			vput(startdir);
+		} else {
+			vrele(startdir);
+		}
 	}
 	if (error) {
 		goto bad;
@@ -1698,9 +1741,8 @@ do_lookup_for_nfsd_index(struct namei_st
 		return 0;
 	}
 
-	KASSERT((cnp->cn_flags & LOCKPARENT) == 0);
-	if ((cnp->cn_flags & LOCKLEAF) == 0) {
-		VOP_UNLOCK(foundobj);
+	if ((cnp->cn_flags & LOCKLEAF) != 0) {
+		vn_lock(foundobj, LK_EXCLUSIVE | LK_RETRY);
 	}
 	return (0);
 

Reply via email to