Subject: proposal: simplifying cache_lookup() interface
To: None <tech-kern@netbsd.org>
From: Jaromir Dolecek <dolecek@ics.muni.cz>
List: tech-kern
Date: 08/07/1999 20:02:06
Hi,
while looking up through *_lookup() sources, I noted there is bunch of code
which is always the same. It's the part after cache_lookup() is called - 
the processing if error != 0.

Originally, I liked the FreeBSD approach, where cache_lookup() call
is generic VOP call and is not called explicitely at all. I discussed
it a bit with mycroft a bit and found out that is not really necessary
to do it that way - it wastes a VOP call with no good reason and even
involves a small performance hit. To remove the code duplication,
it's perfectly enough just to change cache_lookup(). If the vnode is
found in cache, cache_lookup() would do all the necessary locking.
The caller just checks whether the vnode has been found or error occured
and return if anything from those is true.

So my proposal:

Change the type of cache_lookup() to:

int
cache_lookup(dvp, vpp, cnp, found)
        struct vnode *dvp;
        struct vnode **vpp;             
        struct componentname *cnp;
        int *found;

the return value is an error number, if any occurs. *found is
set to 1 if the vnode is found in cache, 0 otherwise.

The code in the fs's lookup routine would then be changed just to

        error = cache_lookup(vdp, vpp, cnp, &found);
        if (error || found)
                return (error);

The rest is handled by cache_lookup() code.

That's basically it. Changes to cache_lookup() are suprisingly trivial,
I'm appending necessary patches. It might be possible
to optimize the added code a bit still, now it's almost plain copy
from what was in msdosfs_lookup.c. I'm also adding the diff to
msdosfs/msdosfs_lookup.c just to highlight the change in fs's code. Doing
the same for other fs's is ~trivial.

I hope it won't change some semantics, but it shouldn't AFAICS.

One remaining issue: our cache code doesn't handle
case-insensitive filesystems quite well - i.e. it won't generate
hit if the name to be found is differs from that cached in case only.
Maybe we should introduce some flags to cache_enter() so that
it would be possible to search case-insensitively optionally.
This is kind of hard doing right (thing "national character sets"),
but even beeing case-insensitive for ASCII chars would be much better
than what we (do not) have now.

The code with the patch below applied might not compile, I didn't try
it actually - just wanted to express the idea.

Best regards

Jaromir Dolecek

--- sys/namei.h.orig	Sat Aug  7 19:26:16 1999
+++ sys/namei.h	Sat Aug  7 19:29:40 1999
@@ -180,7 +180,7 @@
 int	relookup __P((struct vnode *dvp, struct vnode **vpp,
 		      struct componentname *cnp));
 void cache_purge __P((struct vnode *));
-int cache_lookup __P((struct vnode *, struct vnode **, struct componentname *));
+int cache_lookup __P((struct vnode *, struct vnode **, struct componentname *, int *));
 int cache_revlookup __P((struct vnode *, struct vnode **, char **, char *));
 void cache_enter __P((struct vnode *, struct vnode *, struct componentname *));
 void nchinit __P((void));
--- kern/vfs_cache.c.orig	Sat Aug  7 19:28:29 1999
+++ kern/vfs_cache.c	Sat Aug  7 19:27:25 1999
@@ -95,14 +95,17 @@ int doingcache = 1;			/* 1 => enable the
  * is returned. If the lookup fails, a status of zero is returned.
  */
 int
-cache_lookup(dvp, vpp, cnp)
+cache_lookup(dvp, vpp, cnp, found)
 	struct vnode *dvp;
 	struct vnode **vpp;
 	struct componentname *cnp;
+	int *found;
 {
 	register struct namecache *ncp;
 	register struct nchashhead *ncpp;
+	int vpid;
 
+	*found = 0;
 	if (!doingcache) {
 		cnp->cn_flags &= ~MAKEENTRY;
 		return (0);
@@ -156,7 +159,7 @@ cache_lookup(dvp, vpp, cnp)
 			TAILQ_INSERT_TAIL(&nclruhead, ncp, nc_lru);
 		}
 		*vpp = ncp->nc_vp;
-		return (-1);
+		goto found;
 	}
 
 	/*
@@ -172,6 +175,59 @@ cache_lookup(dvp, vpp, cnp)
 		ncp->nc_vhash.le_prev = 0;
 	}
 	TAILQ_INSERT_HEAD(&nclruhead, ncp, nc_lru);
+	return (0);
+
+    found:
+	/*
+	 * take care of necessary locking and special cases, so the caller
+	 * won't need to mess with the returned vnode at all
+	 */
+
+	/*
+	 * Get the next vnode in the path.
+	 * See comment below starting `Step through' for
+	 * an explaination of the locking protocol.
+	 */
+	dvp = *vpp;
+	vpid = dvp->v_id;
+	if (pdp == dvp) {   /* lookup on "." */
+		VREF(dvp);
+		error = 0;
+	} else if (flags & ISDOTDOT) {
+		VOP_UNLOCK(pdp, 0);
+		cnp->cn_flags |= PDIRUNLOCK;
+		error = vget(dvp, LK_EXCLUSIVE);
+		if (!error && lockparent && (flags & ISLASTCN)){
+			error = vn_lock(pdp, LK_EXCLUSIVE);
+			if (error == 0)
+				cnp->cn_flags &= ~PDIRUNLOCK;
+		}
+	} else {
+		error = vget(dvp, LK_EXCLUSIVE);
+		if (!lockparent || error || !(flags & ISLASTCN)) {
+			VOP_UNLOCK(pdp, 0);
+			cnp->cn_flags |= PDIRUNLOCK;
+		}
+	}
+
+	/*
+	 * Check that the capability number did not change
+	 * while we were waiting for the lock.
+	 */
+	if (!error) {
+		if (vpid == dvp->v_id) {
+			*found = 1;
+			return (0);
+		}
+		vput(dvp);
+		if (lockparent && pdp != dvp && (flags & ISLASTCN)) {
+			VOP_UNLOCK(pdp, 0);
+			cnp->cn_flags |= PDIRUNLOCK;
+		}
+	}
+	if ((error = vn_lock(pdp, LK_EXCLUSIVE)) != 0)
+		return (error);
+	cnp->cn_flags &= ~PDIRUNLOCK;
 	return (0);
 }
 
--- msdosfs/msdosfs_lookup.c.orig	Sat Aug  7 19:28:13 1999
+++ msdosfs/msdosfs_lookup.c	Sat Aug  7 19:27:44 1999
@@ -113,6 +113,7 @@ msdosfs_lookup(v)
 	int wincnt = 1;
 	int chksum = -1;
 	int olddos = 1;
+	int found;
 
 	cnp->cn_flags &= ~PDIRUNLOCK;
 	flags = cnp->cn_flags;
@@ -147,64 +148,9 @@ msdosfs_lookup(v)
 	 * check the name cache to see if the directory/name pair
 	 * we are looking for is known already.
 	 */
-	if ((error = cache_lookup(vdp, vpp, cnp)) != 0) {
-		int vpid;
-
-		if (error == ENOENT)
-			return (error);
-		/*
-		 * Get the next vnode in the path.
-		 * See comment below starting `Step through' for
-		 * an explaination of the locking protocol.
-		 */
-		pdp = vdp;
-		dp = VTODE(*vpp);
-		vdp = *vpp;
-		vpid = vdp->v_id;
-		if (pdp == vdp) {   /* lookup on "." */
-			VREF(vdp);
-			error = 0;
-		} else if (flags & ISDOTDOT) {
-			VOP_UNLOCK(pdp, 0);
-			cnp->cn_flags |= PDIRUNLOCK;
-			error = vget(vdp, LK_EXCLUSIVE);
-			if (!error && lockparent && (flags & ISLASTCN)){
-				error = vn_lock(pdp, LK_EXCLUSIVE);
-				if (error == 0)
-					cnp->cn_flags &= ~PDIRUNLOCK;
-			}
-		} else {
-			error = vget(vdp, LK_EXCLUSIVE);
-			if (!lockparent || error || !(flags & ISLASTCN)) {
-				VOP_UNLOCK(pdp, 0);
-				cnp->cn_flags |= PDIRUNLOCK;
-			}
-		}
-		/*
-		 * Check that the capability number did not change
-		 * while we were waiting for the lock.
-		 */
-		if (!error) {
-			if (vpid == vdp->v_id) {
-#ifdef MSDOSFS_DEBUG
-				printf("msdosfs_lookup(): cache hit, vnode %p, file %s\n",
-				       vdp, dp->de_Name);
-#endif
-				return (0);
-			}
-			vput(vdp);
-			if (lockparent && pdp != vdp && (flags & ISLASTCN)) {
-				VOP_UNLOCK(pdp, 0);
-				cnp->cn_flags |= PDIRUNLOCK;
-			}
-		}
-		if ((error = vn_lock(pdp, LK_EXCLUSIVE)) != 0)
-			return (error);
-		cnp->cn_flags &= ~PDIRUNLOCK;
-		vdp = pdp;
-		dp = VTODE(vdp);
-		*vpp = NULL;
-	}
+	error = cache_lookup(vdp, vpp, cnp, &found);
+	if (error || found)
+		return (error);
 
 	/*
 	 * If they are going after the . or .. entry in the root directory,