xref: /netbsd-src/sys/coda/coda_namecache.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: coda_namecache.c,v 1.24 2009/04/18 14:58:02 tsutsui Exp $	*/
2 
3 /*
4  *
5  *             Coda: an Experimental Distributed File System
6  *                              Release 3.1
7  *
8  *           Copyright (c) 1987-1998 Carnegie Mellon University
9  *                          All Rights Reserved
10  *
11  * Permission  to  use, copy, modify and distribute this software and its
12  * documentation is hereby granted,  provided  that  both  the  copyright
13  * notice  and  this  permission  notice  appear  in  all  copies  of the
14  * software, derivative works or  modified  versions,  and  any  portions
15  * thereof, and that both notices appear in supporting documentation, and
16  * that credit is given to Carnegie Mellon University  in  all  documents
17  * and publicity pertaining to direct or indirect use of this code or its
18  * derivatives.
19  *
20  * CODA IS AN EXPERIMENTAL SOFTWARE SYSTEM AND IS  KNOWN  TO  HAVE  BUGS,
21  * SOME  OF  WHICH MAY HAVE SERIOUS CONSEQUENCES.  CARNEGIE MELLON ALLOWS
22  * FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION.   CARNEGIE  MELLON
23  * DISCLAIMS  ANY  LIABILITY  OF  ANY  KIND  FOR  ANY  DAMAGES WHATSOEVER
24  * RESULTING DIRECTLY OR INDIRECTLY FROM THE USE OF THIS SOFTWARE  OR  OF
25  * ANY DERIVATIVE WORK.
26  *
27  * Carnegie  Mellon  encourages  users  of  this  software  to return any
28  * improvements or extensions that  they  make,  and  to  grant  Carnegie
29  * Mellon the rights to redistribute these changes without encumbrance.
30  *
31  * 	@(#) coda/coda_namecache.c,v 1.1.1.1 1998/08/29 21:26:45 rvb Exp $
32  */
33 
34 /*
35  * Mach Operating System
36  * Copyright (c) 1990 Carnegie-Mellon University
37  * Copyright (c) 1989 Carnegie-Mellon University
38  * All rights reserved.  The CMU software License Agreement specifies
39  * the terms and conditions for use and redistribution.
40  */
41 
42 /*
43  * This code was written for the Coda file system at Carnegie Mellon University.
44  * Contributers include David Steere, James Kistler, and M. Satyanarayanan.
45  */
46 
47 /*
48  * This module contains the routines to implement the CODA name cache. The
49  * purpose of this cache is to reduce the cost of translating pathnames
50  * into Vice FIDs. Each entry in the cache contains the name of the file,
51  * the vnode (FID) of the parent directory, and the cred structure of the
52  * user accessing the file.
53  *
54  * The first time a file is accessed, it is looked up by the local Venus
55  * which first insures that the user has access to the file. In addition
56  * we are guaranteed that Venus will invalidate any name cache entries in
57  * case the user no longer should be able to access the file. For these
58  * reasons we do not need to keep access list information as well as a
59  * cred structure for each entry.
60  *
61  * The table can be accessed through the routines cnc_init(), cnc_enter(),
62  * cnc_lookup(), cnc_rmfidcred(), cnc_rmfid(), cnc_rmcred(), and cnc_purge().
63  * There are several other routines which aid in the implementation of the
64  * hash table.
65  */
66 
67 /*
68  * NOTES: rvb@cs
69  * 1.	The name cache holds a reference to every vnode in it.  Hence files can not be
70  *	 closed or made inactive until they are released.
71  * 2.	coda_nc_name(cp) was added to get a name for a cnode pointer for debugging.
72  * 3.	coda_nc_find() has debug code to detect when entries are stored with different
73  *	 credentials.  We don't understand yet, if/how entries are NOT EQ but still
74  *	 EQUAL
75  * 4.	I wonder if this name cache could be replace by the vnode name cache.
76  *	The latter has no zapping functions, so probably not.
77  */
78 
79 #include <sys/cdefs.h>
80 __KERNEL_RCSID(0, "$NetBSD: coda_namecache.c,v 1.24 2009/04/18 14:58:02 tsutsui Exp $");
81 
82 #include <sys/param.h>
83 #include <sys/errno.h>
84 #include <sys/malloc.h>
85 #include <sys/select.h>
86 #include <sys/kauth.h>
87 
88 #include <coda/coda.h>
89 #include <coda/cnode.h>
90 #include <coda/coda_namecache.h>
91 
92 #ifdef	DEBUG
93 #include <coda/coda_vnops.h>
94 #endif
95 
96 /*
97  * Declaration of the name cache data structure.
98  */
99 
100 int 	coda_nc_use = 1;			 /* Indicate use of CODA Name Cache */
101 
102 int	coda_nc_size = CODA_NC_CACHESIZE;	 /* size of the cache */
103 int	coda_nc_hashsize = CODA_NC_HASHSIZE; /* size of the primary hash */
104 
105 struct 	coda_cache *coda_nc_heap;	/* pointer to the cache entries */
106 struct	coda_hash  *coda_nc_hash;	/* hash table of cfscache pointers */
107 struct	coda_lru   coda_nc_lru;		/* head of lru chain */
108 
109 struct coda_nc_statistics coda_nc_stat;	/* Keep various stats */
110 
111 /*
112  * for testing purposes
113  */
114 int coda_nc_debug = 0;
115 
116 /*
117  * Entry points for the CODA Name Cache
118  */
119 static struct coda_cache *
120 coda_nc_find(struct cnode *dcp, const char *name, int namelen,
121 	kauth_cred_t cred, int hash);
122 static void
123 coda_nc_remove(struct coda_cache *cncp, enum dc_status dcstat);
124 
125 /*
126  * Initialize the cache, the LRU structure and the Hash structure(s)
127  */
128 
129 #define TOTAL_CACHE_SIZE 	(sizeof(struct coda_cache) * coda_nc_size)
130 #define TOTAL_HASH_SIZE 	(sizeof(struct coda_hash)  * coda_nc_hashsize)
131 
132 int coda_nc_initialized = 0;      /* Initially the cache has not been initialized */
133 
134 void
135 coda_nc_init(void)
136 {
137     int i;
138 
139     /* zero the statistics structure */
140 
141     memset(&coda_nc_stat, 0, (sizeof(struct coda_nc_statistics)));
142 
143 #ifdef	CODA_VERBOSE
144     printf("CODA NAME CACHE: CACHE %d, HASH TBL %d\n", CODA_NC_CACHESIZE, CODA_NC_HASHSIZE);
145 #endif
146     CODA_ALLOC(coda_nc_heap, struct coda_cache *, TOTAL_CACHE_SIZE);
147     CODA_ALLOC(coda_nc_hash, struct coda_hash *, TOTAL_HASH_SIZE);
148 
149     memset(coda_nc_heap, 0, TOTAL_CACHE_SIZE);
150     memset(coda_nc_hash, 0, TOTAL_HASH_SIZE);
151 
152     TAILQ_INIT(&coda_nc_lru.head);
153 
154     for (i=0; i < coda_nc_size; i++) {	/* initialize the heap */
155 	TAILQ_INSERT_HEAD(&coda_nc_lru.head, &coda_nc_heap[i], lru);
156     }
157 
158     for (i=0; i < coda_nc_hashsize; i++) {	/* initialize the hashtable */
159 	LIST_INIT(&coda_nc_hash[i].head);
160     }
161 
162     coda_nc_initialized++;
163 }
164 
165 /*
166  * Auxillary routines -- shouldn't be entry points
167  */
168 
169 static struct coda_cache *
170 coda_nc_find(struct cnode *dcp, const char *name, int namelen,
171 	kauth_cred_t cred, int hash)
172 {
173 	/*
174 	 * hash to find the appropriate bucket, look through the chain
175 	 * for the right entry (especially right cred, unless cred == 0)
176 	 */
177 	struct coda_cache *cncp;
178 	int count = 1;
179 
180 	CODA_NC_DEBUG(CODA_NC_FIND,
181 		myprintf(("coda_nc_find(dcp %p, name %s, len %d, cred %p, hash %d\n",
182 			dcp, name, namelen, cred, hash));)
183 
184 	LIST_FOREACH(cncp, &coda_nc_hash[hash].head, hash)
185 	{
186 
187 	    if ((CODA_NAMEMATCH(cncp, name, namelen, dcp)) &&
188 		((cred == 0) || (cncp->cred == cred)))
189 	    {
190 		/* compare cr_uid instead */
191 		coda_nc_stat.Search_len += count;
192 		return(cncp);
193 	    }
194 #ifdef	DEBUG
195 	    else if (CODA_NAMEMATCH(cncp, name, namelen, dcp)) {
196 	    	printf("coda_nc_find: name %s, new cred = %p, cred = %p\n",
197 			name, cred, cncp->cred);
198 		printf("nref %d, nuid %d, ngid %d // oref %d, ocred %d, ogid %d\n",
199 			kauth_cred_getrefcnt(cred),
200 			kauth_cred_geteuid(cred),
201 			kauth_cred_getegid(cred),
202 			kauth_cred_getrefcnt(cncp->cred),
203 			kauth_cred_geteuid(cncp->cred),
204 			kauth_cred_getegid(cncp->cred));
205 		print_cred(cred);
206 		print_cred(cncp->cred);
207 	    }
208 #endif
209 	    count++;
210 	}
211 
212 	return((struct coda_cache *)0);
213 }
214 
215 /*
216  * Enter a new (dir cnode, name) pair into the cache, updating the
217  * LRU and Hash as needed.
218  */
219 void
220 coda_nc_enter(struct cnode *dcp, const char *name, int namelen,
221 	kauth_cred_t cred, struct cnode *cp)
222 {
223     struct coda_cache *cncp;
224     int hash;
225 
226     if (coda_nc_use == 0)			/* Cache is off */
227 	return;
228 
229     CODA_NC_DEBUG(CODA_NC_ENTER,
230 		myprintf(("Enter: dcp %p cp %p name %s cred %p \n",
231 		       dcp, cp, name, cred)); )
232 
233     if (namelen > CODA_NC_NAMELEN) {
234 	CODA_NC_DEBUG(CODA_NC_ENTER,
235 		    myprintf(("long name enter %s\n",name));)
236 	    coda_nc_stat.long_name_enters++;	/* record stats */
237 	return;
238     }
239 
240     hash = CODA_NC_HASH(name, namelen, dcp);
241     cncp = coda_nc_find(dcp, name, namelen, cred, hash);
242     if (cncp != (struct coda_cache *) 0) {
243 	coda_nc_stat.dbl_enters++;		/* duplicate entry */
244 	return;
245     }
246 
247     coda_nc_stat.enters++;		/* record the enters statistic */
248 
249     /* Grab the next element in the lru chain */
250     cncp = TAILQ_FIRST(&coda_nc_lru.head);
251     TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
252 
253     if (CODA_NC_VALID(cncp)) {
254 	/* Seems really ugly, but we have to decrement the appropriate
255 	   hash bucket length here, so we have to find the hash bucket
256 	   */
257 	coda_nc_hash[CODA_NC_HASH(cncp->name, cncp->namelen, cncp->dcp)].length--;
258 
259 	coda_nc_stat.lru_rm++;	/* zapped a valid entry */
260 	LIST_REMOVE(cncp, hash);
261 	vrele(CTOV(cncp->dcp));
262 	vrele(CTOV(cncp->cp));
263 	kauth_cred_free(cncp->cred);
264     }
265 
266     /*
267      * Put a hold on the current vnodes and fill in the cache entry.
268      */
269     vref(CTOV(cp));
270     vref(CTOV(dcp));
271     kauth_cred_hold(cred);
272     cncp->dcp = dcp;
273     cncp->cp = cp;
274     cncp->namelen = namelen;
275     cncp->cred = cred;
276 
277     memcpy(cncp->name, name, (unsigned)namelen);
278 
279     /* Insert into the lru and hash chains. */
280     TAILQ_INSERT_TAIL(&coda_nc_lru.head, cncp, lru);
281     LIST_INSERT_HEAD(&coda_nc_hash[hash].head, cncp, hash);
282     coda_nc_hash[hash].length++;                      /* Used for tuning */
283 
284     CODA_NC_DEBUG(CODA_NC_PRINTCODA_NC, print_coda_nc(); )
285 }
286 
287 /*
288  * Find the (dir cnode, name) pair in the cache, if it's cred
289  * matches the input, return it, otherwise return 0
290  */
291 struct cnode *
292 coda_nc_lookup(struct cnode *dcp, const char *name, int namelen,
293 	kauth_cred_t cred)
294 {
295 	int hash;
296 	struct coda_cache *cncp;
297 
298 	if (coda_nc_use == 0)			/* Cache is off */
299 		return((struct cnode *) 0);
300 
301 	if (namelen > CODA_NC_NAMELEN) {
302 	        CODA_NC_DEBUG(CODA_NC_LOOKUP,
303 			    myprintf(("long name lookup %s\n",name));)
304 		coda_nc_stat.long_name_lookups++;		/* record stats */
305 		return((struct cnode *) 0);
306 	}
307 
308 	/* Use the hash function to locate the starting point,
309 	   then the search routine to go down the list looking for
310 	   the correct cred.
311  	 */
312 
313 	hash = CODA_NC_HASH(name, namelen, dcp);
314 	cncp = coda_nc_find(dcp, name, namelen, cred, hash);
315 	if (cncp == (struct coda_cache *) 0) {
316 		coda_nc_stat.misses++;			/* record miss */
317 		return((struct cnode *) 0);
318 	}
319 
320 	coda_nc_stat.hits++;
321 
322 	/* put this entry at the end of the LRU */
323 	TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
324 	TAILQ_INSERT_TAIL(&coda_nc_lru.head, cncp, lru);
325 
326 	/* move it to the front of the hash chain */
327 	/* don't need to change the hash bucket length */
328 	LIST_REMOVE(cncp, hash);
329 	LIST_INSERT_HEAD(&coda_nc_hash[hash].head, cncp, hash);
330 
331 	CODA_NC_DEBUG(CODA_NC_LOOKUP,
332 		printf("lookup: dcp %p, name %s, cred %p = cp %p\n",
333 			dcp, name, cred, cncp->cp); )
334 
335 	return(cncp->cp);
336 }
337 
338 static void
339 coda_nc_remove(struct coda_cache *cncp, enum dc_status dcstat)
340 {
341 	/*
342 	 * remove an entry -- vrele(cncp->dcp, cp), crfree(cred),
343 	 * remove it from it's hash chain, and
344 	 * place it at the head of the lru list.
345 	 */
346         CODA_NC_DEBUG(CODA_NC_REMOVE,
347 		    myprintf(("coda_nc_remove %s from parent %s\n",
348 			      cncp->name, coda_f2s(&cncp->dcp->c_fid))); )
349 
350 
351 	LIST_REMOVE(cncp, hash);
352 	memset(&cncp->hash, 0, sizeof(cncp->hash));
353 
354 	if ((dcstat == IS_DOWNCALL) && (CTOV(cncp->dcp)->v_usecount == 1)) {
355 		cncp->dcp->c_flags |= C_PURGING;
356 	}
357 	vrele(CTOV(cncp->dcp));
358 
359 	if ((dcstat == IS_DOWNCALL) && (CTOV(cncp->cp)->v_usecount == 1)) {
360 		cncp->cp->c_flags |= C_PURGING;
361 	}
362 	vrele(CTOV(cncp->cp));
363 
364 	kauth_cred_free(cncp->cred);
365 	memset(DATA_PART(cncp), 0, DATA_SIZE);
366 
367 	/* move the null entry to the front for reuse */
368 	TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
369 	TAILQ_INSERT_HEAD(&coda_nc_lru.head, cncp, lru);
370 }
371 
372 /*
373  * Remove all entries with a parent which has the input fid.
374  */
375 void
376 coda_nc_zapParentfid(CodaFid *fid, enum dc_status dcstat)
377 {
378 	/* To get to a specific fid, we might either have another hashing
379 	   function or do a sequential search through the cache for the
380 	   appropriate entries. The later may be acceptable since I don't
381 	   think callbacks or whatever Case 1 covers are frequent occurrences.
382 	 */
383 	struct coda_cache *cncp, *ncncp;
384 	int i;
385 
386 	if (coda_nc_use == 0)			/* Cache is off */
387 		return;
388 
389 	CODA_NC_DEBUG(CODA_NC_ZAPPFID,
390 		myprintf(("ZapParent: fid %s\n", coda_f2s(fid))); )
391 
392 	coda_nc_stat.zapPfids++;
393 
394 	for (i = 0; i < coda_nc_hashsize; i++) {
395 
396 		/*
397 		 * Need to save the hash_next pointer in case we remove the
398 		 * entry. remove causes hash_next to point to itself.
399 		 */
400 
401 		ncncp = LIST_FIRST(&coda_nc_hash[i].head);
402 		while ((cncp = ncncp) != NULL) {
403 			ncncp = LIST_NEXT(cncp, hash);
404 
405 			if (coda_fid_eq(&(cncp->dcp->c_fid), fid)) {
406 			        coda_nc_hash[i].length--;      /* Used for tuning */
407 				coda_nc_remove(cncp, dcstat);
408 			}
409 		}
410 	}
411 }
412 
413 /*
414  * Remove all entries which have the same fid as the input
415  */
416 void
417 coda_nc_zapfid(CodaFid *fid, enum dc_status dcstat)
418 {
419 	/* See comment for zapParentfid. This routine will be used
420 	   if attributes are being cached.
421 	 */
422 	struct coda_cache *cncp, *ncncp;
423 	int i;
424 
425 	if (coda_nc_use == 0)			/* Cache is off */
426 		return;
427 
428 	CODA_NC_DEBUG(CODA_NC_ZAPFID,
429 		myprintf(("Zapfid: fid %s\n", coda_f2s(fid))); )
430 
431 	coda_nc_stat.zapFids++;
432 
433 	for (i = 0; i < coda_nc_hashsize; i++) {
434 
435 		ncncp = LIST_FIRST(&coda_nc_hash[i].head);
436 		while ((cncp = ncncp) != NULL) {
437 			ncncp = LIST_NEXT(cncp, hash);
438 
439 			if (coda_fid_eq(&cncp->cp->c_fid, fid)) {
440 			        coda_nc_hash[i].length--;     /* Used for tuning */
441 				coda_nc_remove(cncp, dcstat);
442 			}
443 		}
444 	}
445 }
446 
447 /*
448  * Remove all entries which match the fid and the cred
449  */
450 void
451 coda_nc_zapvnode(CodaFid *fid, kauth_cred_t cred,
452     enum dc_status dcstat)
453 {
454 	/* See comment for zapfid. I don't think that one would ever
455 	   want to zap a file with a specific cred from the kernel.
456 	   We'll leave this one unimplemented.
457 	 */
458 	if (coda_nc_use == 0)			/* Cache is off */
459 		return;
460 
461 	CODA_NC_DEBUG(CODA_NC_ZAPVNODE,
462 		myprintf(("Zapvnode: fid %s cred %p\n",
463 			  coda_f2s(fid), cred)); )
464 }
465 
466 /*
467  * Remove all entries which have the (dir vnode, name) pair
468  */
469 void
470 coda_nc_zapfile(struct cnode *dcp, const char *name, int namelen)
471 {
472 	/* use the hash function to locate the file, then zap all
473  	   entries of it regardless of the cred.
474 	 */
475 	struct coda_cache *cncp;
476 	int hash;
477 
478 	if (coda_nc_use == 0)			/* Cache is off */
479 		return;
480 
481 	CODA_NC_DEBUG(CODA_NC_ZAPFILE,
482 		myprintf(("Zapfile: dcp %p name %s \n",
483 			  dcp, name)); )
484 
485 	if (namelen > CODA_NC_NAMELEN) {
486 		coda_nc_stat.long_remove++;		/* record stats */
487 		return;
488 	}
489 
490 	coda_nc_stat.zapFile++;
491 
492 	hash = CODA_NC_HASH(name, namelen, dcp);
493 	cncp = coda_nc_find(dcp, name, namelen, 0, hash);
494 
495 	while (cncp) {
496 	  coda_nc_hash[hash].length--;                 /* Used for tuning */
497 /* 1.3 */
498 	  coda_nc_remove(cncp, NOT_DOWNCALL);
499 	  cncp = coda_nc_find(dcp, name, namelen, 0, hash);
500 	}
501 }
502 
503 /*
504  * Remove all the entries for a particular user. Used when tokens expire.
505  * A user is determined by his/her effective user id (id_uid).
506  */
507 void
508 coda_nc_purge_user(uid_t uid, enum dc_status dcstat)
509 {
510 	/*
511 	 * I think the best approach is to go through the entire cache
512 	 * via HASH or whatever and zap all entries which match the
513 	 * input cred. Or just flush the whole cache.  It might be
514 	 * best to go through on basis of LRU since cache will almost
515 	 * always be full and LRU is more straightforward.
516 	 */
517 
518 	struct coda_cache *cncp, *ncncp;
519 	int hash;
520 
521 	if (coda_nc_use == 0)			/* Cache is off */
522 		return;
523 
524 	CODA_NC_DEBUG(CODA_NC_PURGEUSER,
525 		myprintf(("ZapDude: uid %x\n", uid)); )
526 	coda_nc_stat.zapUsers++;
527 
528 	ncncp = TAILQ_FIRST(&coda_nc_lru.head);
529 	while ((cncp = ncncp) != NULL) {
530 		ncncp = TAILQ_NEXT(cncp, lru);
531 
532 		if ((CODA_NC_VALID(cncp)) &&
533 		   (kauth_cred_geteuid(cncp->cred) == uid)) {
534 		        /* Seems really ugly, but we have to decrement the appropriate
535 			   hash bucket length here, so we have to find the hash bucket
536 			   */
537 		        hash = CODA_NC_HASH(cncp->name, cncp->namelen, cncp->dcp);
538 			coda_nc_hash[hash].length--;     /* For performance tuning */
539 
540 			coda_nc_remove(cncp, dcstat);
541 		}
542 	}
543 }
544 
545 /*
546  * Flush the entire name cache. In response to a flush of the Venus cache.
547  */
548 void
549 coda_nc_flush(enum dc_status dcstat)
550 {
551 	/* One option is to deallocate the current name cache and
552 	   call init to start again. Or just deallocate, then rebuild.
553 	   Or again, we could just go through the array and zero the
554 	   appropriate fields.
555 	 */
556 
557 	/*
558 	 * Go through the whole lru chain and kill everything as we go.
559 	 * I don't use remove since that would rebuild the lru chain
560 	 * as it went and that seemed unneccesary.
561 	 */
562 	struct coda_cache *cncp;
563 	int i;
564 
565 	if (coda_nc_use == 0)			/* Cache is off */
566 		return;
567 
568 	coda_nc_stat.Flushes++;
569 
570 	TAILQ_FOREACH(cncp, &coda_nc_lru.head, lru) {
571 		if (CODA_NC_VALID(cncp)) {	/* only zero valid nodes */
572 			LIST_REMOVE(cncp, hash);
573 			memset(&cncp->hash, 0, sizeof(cncp->hash));
574 
575 			if ((dcstat == IS_DOWNCALL)
576 			    && (CTOV(cncp->dcp)->v_usecount == 1))
577 			{
578 				cncp->dcp->c_flags |= C_PURGING;
579 			}
580 			vrele(CTOV(cncp->dcp));
581 
582 			if (CTOV(cncp->cp)->v_iflag & VI_TEXT) {
583 			    if (coda_vmflush(cncp->cp))
584 				CODADEBUG(CODA_FLUSH,
585 					myprintf(("coda_nc_flush: %s busy\n",
586 						coda_f2s(&cncp->cp->c_fid))); )
587 			}
588 
589 			if ((dcstat == IS_DOWNCALL)
590 			    && (CTOV(cncp->cp)->v_usecount == 1))
591 			{
592 				cncp->cp->c_flags |= C_PURGING;
593 			}
594 			vrele(CTOV(cncp->cp));
595 
596 			kauth_cred_free(cncp->cred);
597 			memset(DATA_PART(cncp), 0, DATA_SIZE);
598 		}
599 	}
600 
601 	for (i = 0; i < coda_nc_hashsize; i++)
602 	  coda_nc_hash[i].length = 0;
603 }
604 
605 /*
606  * Debugging routines
607  */
608 
609 /*
610  * This routine should print out all the hash chains to the console.
611  */
612 void
613 print_coda_nc(void)
614 {
615 	int hash;
616 	struct coda_cache *cncp;
617 
618 	for (hash = 0; hash < coda_nc_hashsize; hash++) {
619 		myprintf(("\nhash %d\n",hash));
620 
621 		LIST_FOREACH(cncp, &coda_nc_hash[hash].head, hash) {
622 			myprintf(("cp %p dcp %p cred %p name %s\n",
623 				  cncp->cp, cncp->dcp,
624 				  cncp->cred, cncp->name));
625 		     }
626 	}
627 }
628 
629 void
630 coda_nc_gather_stats(void)
631 {
632     int i, xmax = 0, sum = 0, temp, zeros = 0, ave, n;
633 
634 	for (i = 0; i < coda_nc_hashsize; i++) {
635 	  if (coda_nc_hash[i].length) {
636 	    sum += coda_nc_hash[i].length;
637 	  } else {
638 	    zeros++;
639 	  }
640 
641 	  if (coda_nc_hash[i].length > xmax)
642 	    xmax = coda_nc_hash[i].length;
643 	}
644 
645 	/*
646 	 * When computing the Arithmetic mean, only count slots which
647 	 * are not empty in the distribution.
648 	 */
649         coda_nc_stat.Sum_bucket_len = sum;
650         coda_nc_stat.Num_zero_len = zeros;
651         coda_nc_stat.Max_bucket_len = xmax;
652 
653 	if ((n = coda_nc_hashsize - zeros) > 0)
654 	  ave = sum / n;
655 	else
656 	  ave = 0;
657 
658 	sum = 0;
659 	for (i = 0; i < coda_nc_hashsize; i++) {
660 	  if (coda_nc_hash[i].length) {
661 	    temp = coda_nc_hash[i].length - ave;
662 	    sum += temp * temp;
663 	  }
664 	}
665         coda_nc_stat.Sum2_bucket_len = sum;
666 }
667 
668 /*
669  * The purpose of this routine is to allow the hash and cache sizes to be
670  * changed dynamically. This should only be used in controlled environments,
671  * it makes no effort to lock other users from accessing the cache while it
672  * is in an improper state (except by turning the cache off).
673  */
674 int
675 coda_nc_resize(int hashsize, int heapsize, enum dc_status dcstat)
676 {
677     if ((hashsize % 2) || (heapsize % 2)) { /* Illegal hash or cache sizes */
678 	return(EINVAL);
679     }
680 
681     coda_nc_use = 0;                       /* Turn the cache off */
682 
683     coda_nc_flush(dcstat);                 /* free any cnodes in the cache */
684 
685     /* WARNING: free must happen *before* size is reset */
686     CODA_FREE(coda_nc_heap,TOTAL_CACHE_SIZE);
687     CODA_FREE(coda_nc_hash,TOTAL_HASH_SIZE);
688 
689     coda_nc_hashsize = hashsize;
690     coda_nc_size = heapsize;
691 
692     coda_nc_init();                        /* Set up a cache with the new size */
693 
694     coda_nc_use = 1;                       /* Turn the cache back on */
695     return(0);
696 }
697 
698 char coda_nc_name_buf[CODA_MAXNAMLEN+1];
699 
700 void
701 coda_nc_name(struct cnode *cp)
702 {
703 	struct coda_cache *cncp;
704 	int i;
705 
706 	if (coda_nc_use == 0)			/* Cache is off */
707 		return;
708 
709 	for (i = 0; i < coda_nc_hashsize; i++) {
710 
711 		LIST_FOREACH(cncp, &coda_nc_hash[i].head, hash) {
712 			if (cncp->cp == cp) {
713 				memcpy(coda_nc_name_buf, cncp->name, cncp->namelen);
714 				coda_nc_name_buf[cncp->namelen] = 0;
715 				printf(" is %s (%p,%p)@%p",
716 					coda_nc_name_buf, cncp->cp, cncp->dcp, cncp);
717 			}
718 
719 		}
720 	}
721 }
722