1 /*	$OpenBSD: vfs_cache.c,v 1.21 2007/04/19 09:25:33 pedro Exp $	*/
2 /*	$NetBSD: vfs_cache.c,v 1.13 1996/02/04 02:18:09 christos Exp $	*/
3 
4 /*
5  * Copyright (c) 1989, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  *	@(#)vfs_cache.c	8.3 (Berkeley) 8/22/94
33  */
34 
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/time.h>
38 #include <sys/mount.h>
39 #include <sys/vnode.h>
40 #include <sys/namei.h>
41 #include <sys/errno.h>
42 #include <sys/malloc.h>
43 #include <sys/pool.h>
44 #include <sys/hash.h>
45 
46 /*
47  * TODO: namecache access should really be locked.
48  */
49 
50 /*
51  * Name caching works as follows:
52  *
53  * Names found by directory scans are retained in a cache
54  * for future reference.  It is managed LRU, so frequently
55  * used names will hang around.  Cache is indexed by hash value
56  * obtained from (vp, name) where vp refers to the directory
57  * containing name.
58  *
59  * For simplicity (and economy of storage), names longer than
60  * a maximum length of NCHNAMLEN are not cached; they occur
61  * infrequently in any case, and are almost never of interest.
62  *
63  * Upon reaching the last segment of a path, if the reference
64  * is for DELETE, or NOCACHE is set (rewrite), and the
65  * name is located in the cache, it will be dropped.
66  */
67 
68 /*
69  * Structures associated with name caching.
70  */
71 LIST_HEAD(nchashhead, namecache) *nchashtbl;
72 u_long	nchash;				/* size of hash table - 1 */
73 long	numcache;			/* number of cache entries allocated */
74 TAILQ_HEAD(, namecache) nclruhead;		/* LRU chain */
75 struct	nchstats nchstats;		/* cache effectiveness statistics */
76 
77 LIST_HEAD(ncvhashhead, namecache) *ncvhashtbl;
78 u_long  ncvhash;                        /* size of hash table - 1 */
79 
80 #define NCHASH(dvp, cnp) \
81 	hash32_buf(&(dvp)->v_id, sizeof((dvp)->v_id), (cnp)->cn_hash) & nchash
82 
83 #define NCVHASH(vp) (vp)->v_id & ncvhash
84 
85 int doingcache = 1;			/* 1 => enable the cache */
86 
87 struct pool nch_pool;
88 
89 u_long nextvnodeid;
90 
91 /*
92  * Look for a the name in the cache. We don't do this
93  * if the segment name is long, simply so the cache can avoid
94  * holding long names (which would either waste space, or
95  * add greatly to the complexity).
96  *
97  * Lookup is called with ni_dvp pointing to the directory to search,
98  * ni_ptr pointing to the name of the entry being sought, ni_namelen
99  * tells the length of the name, and ni_hash contains a hash of
100  * the name. If the lookup succeeds, the vnode is returned in ni_vp
101  * and a status of 0 is returned. If the locking fails for whatever
102  * reason, the vnode is unlocked and the error is returned to caller.
103  * If the lookup determines that the name does not exist (negative caching),
104  * a status of ENOENT is returned. If the lookup fails, a status of -1
105  * is returned.
106  */
107 int
cache_lookup(struct vnode * dvp,struct vnode ** vpp,struct componentname * cnp)108 cache_lookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp)
109 {
110 	struct namecache *ncp;
111 	struct nchashhead *ncpp;
112 	struct vnode *vp;
113 	struct proc *p = curproc;
114 	u_long vpid;
115 	int error;
116 
117 	*vpp = NULL;
118 
119 	if (!doingcache) {
120 		cnp->cn_flags &= ~MAKEENTRY;
121 		return (-1);
122 	}
123 	if (cnp->cn_namelen > NCHNAMLEN) {
124 		nchstats.ncs_long++;
125 		cnp->cn_flags &= ~MAKEENTRY;
126 		return (-1);
127 	}
128 
129 	ncpp = &nchashtbl[NCHASH(dvp, cnp)];
130 	LIST_FOREACH(ncp, ncpp, nc_hash) {
131 		if (ncp->nc_dvp == dvp &&
132 		    ncp->nc_dvpid == dvp->v_id &&
133 		    ncp->nc_nlen == cnp->cn_namelen &&
134 		    !memcmp(ncp->nc_name, cnp->cn_nameptr, (u_int)ncp->nc_nlen))
135 			break;
136 	}
137 	if (ncp == NULL) {
138 		nchstats.ncs_miss++;
139 		return (-1);
140 	}
141 	if ((cnp->cn_flags & MAKEENTRY) == 0) {
142 		nchstats.ncs_badhits++;
143 		goto remove;
144 	} else if (ncp->nc_vp == NULL) {
145 		if (cnp->cn_nameiop != CREATE ||
146 		    (cnp->cn_flags & ISLASTCN) == 0) {
147 			nchstats.ncs_neghits++;
148 			/*
149 			 * Move this slot to end of LRU chain,
150 			 * if not already there.
151 			 */
152 			if (TAILQ_NEXT(ncp, nc_lru) != NULL) {
153 				TAILQ_REMOVE(&nclruhead, ncp, nc_lru);
154 				TAILQ_INSERT_TAIL(&nclruhead, ncp, nc_lru);
155 			}
156 			return (ENOENT);
157 		} else {
158 			nchstats.ncs_badhits++;
159 			goto remove;
160 		}
161 	} else if (ncp->nc_vpid != ncp->nc_vp->v_id) {
162 		nchstats.ncs_falsehits++;
163 		goto remove;
164 	}
165 
166 	vp = ncp->nc_vp;
167 	vpid = vp->v_id;
168 	if (vp == dvp) {	/* lookup on "." */
169 		VREF(dvp);
170 		error = 0;
171 	} else if (cnp->cn_flags & ISDOTDOT) {
172 		VOP_UNLOCK(dvp, 0, p);
173 		cnp->cn_flags |= PDIRUNLOCK;
174 		error = vget(vp, LK_EXCLUSIVE, p);
175 		/*
176 		 * If the above vget() succeeded and both LOCKPARENT and
177 		 * ISLASTCN is set, lock the directory vnode as well.
178 		 */
179 		if (!error && (~cnp->cn_flags & (LOCKPARENT|ISLASTCN)) == 0) {
180 			if ((error = vn_lock(dvp, LK_EXCLUSIVE, p)) != 0) {
181 				vput(vp);
182 				return (error);
183 			}
184 			cnp->cn_flags &= ~PDIRUNLOCK;
185 		}
186 	} else {
187 		error = vget(vp, LK_EXCLUSIVE, p);
188 		/*
189 		 * If the above vget() failed or either of LOCKPARENT or
190 		 * ISLASTCN is set, unlock the directory vnode.
191 		 */
192 		if (error || (~cnp->cn_flags & (LOCKPARENT|ISLASTCN)) != 0) {
193 			VOP_UNLOCK(dvp, 0, p);
194 			cnp->cn_flags |= PDIRUNLOCK;
195 		}
196 	}
197 
198 	/*
199 	 * Check that the lock succeeded, and that the capability number did
200 	 * not change while we were waiting for the lock.
201 	 */
202 	if (error || vpid != vp->v_id) {
203 		if (!error) {
204 			vput(vp);
205 			nchstats.ncs_falsehits++;
206 		} else
207 			nchstats.ncs_badhits++;
208 		/*
209 		 * The parent needs to be locked when we return to VOP_LOOKUP().
210 		 * The `.' case here should be extremely rare (if it can happen
211 		 * at all), so we don't bother optimizing out the unlock/relock.
212 		 */
213 		if (vp == dvp || error ||
214 		    (~cnp->cn_flags & (LOCKPARENT|ISLASTCN)) != 0) {
215 			if ((error = vn_lock(dvp, LK_EXCLUSIVE, p)) != 0)
216 				return (error);
217 			cnp->cn_flags &= ~PDIRUNLOCK;
218 		}
219 		return (-1);
220 	}
221 
222 	nchstats.ncs_goodhits++;
223 	/*
224 	 * Move this slot to end of LRU chain, if not already there.
225 	 */
226 	if (TAILQ_NEXT(ncp, nc_lru) != NULL) {
227 		TAILQ_REMOVE(&nclruhead, ncp, nc_lru);
228 		TAILQ_INSERT_TAIL(&nclruhead, ncp, nc_lru);
229 	}
230 	*vpp = vp;
231 	return (0);
232 
233 remove:
234 	/*
235 	 * Last component and we are renaming or deleting,
236 	 * the cache entry is invalid, or otherwise don't
237 	 * want cache entry to exist.
238 	 */
239 	TAILQ_REMOVE(&nclruhead, ncp, nc_lru);
240 	LIST_REMOVE(ncp, nc_hash);
241 	ncp->nc_hash.le_prev = NULL;
242 
243 	if (ncp->nc_vhash.le_prev != NULL) {
244 		LIST_REMOVE(ncp, nc_vhash);
245 		ncp->nc_vhash.le_prev = NULL;
246 	}
247 
248 	pool_put(&nch_pool, ncp);
249 	numcache--;
250 	return (-1);
251 }
252 
253 /*
254  * Scan cache looking for name of directory entry pointing at vp.
255  *
256  * Fill in dvpp.
257  *
258  * If bufp is non-NULL, also place the name in the buffer which starts
259  * at bufp, immediately before *bpp, and move bpp backwards to point
260  * at the start of it.  (Yes, this is a little baroque, but it's done
261  * this way to cater to the whims of getcwd).
262  *
263  * Returns 0 on success, -1 on cache miss, positive errno on failure.
264  *
265  * TODO: should we return *dvpp locked?
266  */
267 
268 int
cache_revlookup(struct vnode * vp,struct vnode ** dvpp,char ** bpp,char * bufp)269 cache_revlookup(struct vnode *vp, struct vnode **dvpp, char **bpp, char *bufp)
270 {
271 	struct namecache *ncp;
272 	struct vnode *dvp;
273 	struct ncvhashhead *nvcpp;
274 	char *bp;
275 
276 	if (!doingcache)
277 		goto out;
278 
279 	nvcpp = &ncvhashtbl[NCVHASH(vp)];
280 
281 	LIST_FOREACH(ncp, nvcpp, nc_vhash) {
282 		if (ncp->nc_vp == vp &&
283 		    ncp->nc_vpid == vp->v_id &&
284 		    (dvp = ncp->nc_dvp) != NULL &&
285 		    /* avoid pesky '.' entries.. */
286 		    dvp != vp && ncp->nc_dvpid == dvp->v_id) {
287 
288 #ifdef DIAGNOSTIC
289 			if (ncp->nc_nlen == 1 &&
290 			    ncp->nc_name[0] == '.')
291 				panic("cache_revlookup: found entry for .");
292 
293 			if (ncp->nc_nlen == 2 &&
294 			    ncp->nc_name[0] == '.' &&
295 			    ncp->nc_name[1] == '.')
296 				panic("cache_revlookup: found entry for ..");
297 #endif
298 			nchstats.ncs_revhits++;
299 
300 			if (bufp != NULL) {
301 				bp = *bpp;
302 				bp -= ncp->nc_nlen;
303 				if (bp <= bufp) {
304 					*dvpp = NULL;
305 					return (ERANGE);
306 				}
307 				memcpy(bp, ncp->nc_name, ncp->nc_nlen);
308 				*bpp = bp;
309 			}
310 
311 			*dvpp = dvp;
312 
313 			/*
314 			 * XXX: Should we vget() here to have more
315 			 * consistent semantics with cache_lookup()?
316 			 *
317 			 * For MP safety it might be necessary to do
318 			 * this here, while also protecting hash
319 			 * tables themselves to provide some sort of
320 			 * sane inter locking.
321 			 */
322 			return (0);
323 		}
324 	}
325 	nchstats.ncs_revmiss++;
326 
327  out:
328 	*dvpp = NULL;
329 	return (-1);
330 }
331 
332 /*
333  * Add an entry to the cache
334  */
335 void
cache_enter(struct vnode * dvp,struct vnode * vp,struct componentname * cnp)336 cache_enter(struct vnode *dvp, struct vnode *vp, struct componentname *cnp)
337 {
338 	struct namecache *ncp;
339 	struct nchashhead *ncpp;
340 	struct ncvhashhead *nvcpp;
341 
342 	if (!doingcache || cnp->cn_namelen > NCHNAMLEN)
343 		return;
344 
345 	/*
346 	 * Free the cache slot at head of lru chain.
347 	 */
348 	if (numcache < desiredvnodes) {
349 		ncp = pool_get(&nch_pool, PR_WAITOK);
350 		bzero((char *)ncp, sizeof *ncp);
351 		numcache++;
352 	} else if ((ncp = TAILQ_FIRST(&nclruhead)) != NULL) {
353 		TAILQ_REMOVE(&nclruhead, ncp, nc_lru);
354 		if (ncp->nc_hash.le_prev != NULL) {
355 			LIST_REMOVE(ncp, nc_hash);
356 			ncp->nc_hash.le_prev = NULL;
357 		}
358 		if (ncp->nc_vhash.le_prev != NULL) {
359 			LIST_REMOVE(ncp, nc_vhash);
360 			ncp->nc_vhash.le_prev = NULL;
361 		}
362 	} else
363 		return;
364 	/* grab the vnode we just found */
365 	ncp->nc_vp = vp;
366 	if (vp)
367 		ncp->nc_vpid = vp->v_id;
368 	/* fill in cache info */
369 	ncp->nc_dvp = dvp;
370 	ncp->nc_dvpid = dvp->v_id;
371 	ncp->nc_nlen = cnp->cn_namelen;
372 	bcopy(cnp->cn_nameptr, ncp->nc_name, (unsigned)ncp->nc_nlen);
373 	TAILQ_INSERT_TAIL(&nclruhead, ncp, nc_lru);
374 	ncpp = &nchashtbl[NCHASH(dvp, cnp)];
375 	LIST_INSERT_HEAD(ncpp, ncp, nc_hash);
376 
377 	/*
378 	 * Create reverse-cache entries (used in getcwd) for
379 	 * directories.
380 	 */
381 
382 	ncp->nc_vhash.le_prev = NULL;
383 	ncp->nc_vhash.le_next = NULL;
384 
385 	if (vp && vp != dvp && vp->v_type == VDIR &&
386 	    (ncp->nc_nlen > 2 ||
387 		(ncp->nc_nlen > 1 && ncp->nc_name[1] != '.') ||
388 		(ncp->nc_nlen > 0 && ncp->nc_name[0] != '.'))) {
389 		nvcpp = &ncvhashtbl[NCVHASH(vp)];
390 		LIST_INSERT_HEAD(nvcpp, ncp, nc_vhash);
391 	}
392 }
393 
394 /*
395  * Name cache initialization, from vfs_init() when we are booting
396  */
397 void
nchinit(void)398 nchinit(void)
399 {
400 
401 	TAILQ_INIT(&nclruhead);
402 	nchashtbl = hashinit(desiredvnodes, M_CACHE, M_WAITOK, &nchash);
403 	ncvhashtbl = hashinit(desiredvnodes/8, M_CACHE, M_WAITOK, &ncvhash);
404 	pool_init(&nch_pool, sizeof(struct namecache), 0, 0, 0, "nchpl",
405 	    &pool_allocator_nointr);
406 }
407 
408 /*
409  * Cache flush, a particular vnode; called when a vnode is renamed to
410  * hide entries that would now be invalid
411  */
412 void
cache_purge(struct vnode * vp)413 cache_purge(struct vnode *vp)
414 {
415 	struct namecache *ncp;
416 	struct nchashhead *ncpp;
417 
418 	vp->v_id = ++nextvnodeid;
419 	if (nextvnodeid != 0)
420 		return;
421 	for (ncpp = &nchashtbl[nchash]; ncpp >= nchashtbl; ncpp--) {
422 		LIST_FOREACH(ncp, ncpp, nc_hash) {
423 			ncp->nc_vpid = 0;
424 			ncp->nc_dvpid = 0;
425 		}
426 	}
427 	vp->v_id = ++nextvnodeid;
428 }
429 
430 /*
431  * Cache flush, a whole filesystem; called when filesys is umounted to
432  * remove entries that would now be invalid
433  */
434 void
cache_purgevfs(struct mount * mp)435 cache_purgevfs(struct mount *mp)
436 {
437 	struct namecache *ncp, *nxtcp;
438 
439 	for (ncp = TAILQ_FIRST(&nclruhead); ncp != TAILQ_END(&nclruhead);
440 	    ncp = nxtcp) {
441 		nxtcp = TAILQ_NEXT(ncp, nc_lru);
442 		if (ncp->nc_dvp == NULL || ncp->nc_dvp->v_mount != mp)
443 			continue;
444 		/* free the resources we had */
445 		ncp->nc_vp = NULL;
446 		ncp->nc_dvp = NULL;
447 		TAILQ_REMOVE(&nclruhead, ncp, nc_lru);
448 		if (ncp->nc_hash.le_prev != NULL) {
449 			LIST_REMOVE(ncp, nc_hash);
450 			ncp->nc_hash.le_prev = NULL;
451 		}
452 		if (ncp->nc_vhash.le_prev != NULL) {
453 			LIST_REMOVE(ncp, nc_vhash);
454 			ncp->nc_vhash.le_prev = NULL;
455 		}
456 		pool_put(&nch_pool, ncp);
457 		numcache--;
458 	}
459 }
460