coda_namecache.c revision 1.27
1/*	$NetBSD: coda_namecache.c,v 1.27 2020/04/13 19:23:17 ad 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.27 2020/04/13 19:23:17 ad 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#include <coda/coda_subr.h>
92
93/*
94 * Declaration of the name cache data structure.
95 */
96
97int 	coda_nc_use = 1;			 /* Indicate use of CODA Name Cache */
98
99int	coda_nc_size = CODA_NC_CACHESIZE;	 /* size of the cache */
100int	coda_nc_hashsize = CODA_NC_HASHSIZE; /* size of the primary hash */
101
102struct 	coda_cache *coda_nc_heap;	/* pointer to the cache entries */
103struct	coda_hash  *coda_nc_hash;	/* hash table of cfscache pointers */
104struct	coda_lru   coda_nc_lru;		/* head of lru chain */
105
106struct coda_nc_statistics coda_nc_stat;	/* Keep various stats */
107
108/*
109 * for testing purposes
110 */
111int coda_nc_debug = 0;
112
113/*
114 * Entry points for the CODA Name Cache
115 */
116static struct coda_cache *
117coda_nc_find(struct cnode *dcp, const char *name, int namelen,
118	kauth_cred_t cred, int hash);
119static void
120coda_nc_remove(struct coda_cache *cncp, enum dc_status dcstat);
121
122/*
123 * Initialize the cache, the LRU structure and the Hash structure(s)
124 */
125
126#define TOTAL_CACHE_SIZE 	(sizeof(struct coda_cache) * coda_nc_size)
127#define TOTAL_HASH_SIZE 	(sizeof(struct coda_hash)  * coda_nc_hashsize)
128
129int coda_nc_initialized = 0;      /* Initially the cache has not been initialized */
130
131void
132coda_nc_init(void)
133{
134    int i;
135
136    /* zero the statistics structure */
137
138    memset(&coda_nc_stat, 0, (sizeof(struct coda_nc_statistics)));
139
140#ifdef	CODA_VERBOSE
141    printf("CODA NAME CACHE: CACHE %d, HASH TBL %d\n", CODA_NC_CACHESIZE, CODA_NC_HASHSIZE);
142#endif
143    CODA_ALLOC(coda_nc_heap, struct coda_cache *, TOTAL_CACHE_SIZE);
144    CODA_ALLOC(coda_nc_hash, struct coda_hash *, TOTAL_HASH_SIZE);
145
146    memset(coda_nc_heap, 0, TOTAL_CACHE_SIZE);
147    memset(coda_nc_hash, 0, TOTAL_HASH_SIZE);
148
149    TAILQ_INIT(&coda_nc_lru.head);
150
151    for (i=0; i < coda_nc_size; i++) {	/* initialize the heap */
152	TAILQ_INSERT_HEAD(&coda_nc_lru.head, &coda_nc_heap[i], lru);
153    }
154
155    for (i=0; i < coda_nc_hashsize; i++) {	/* initialize the hashtable */
156	LIST_INIT(&coda_nc_hash[i].head);
157    }
158
159    coda_nc_initialized++;
160}
161
162/*
163 * Auxillary routines -- shouldn't be entry points
164 */
165
166static struct coda_cache *
167coda_nc_find(struct cnode *dcp, const char *name, int namelen,
168	kauth_cred_t cred, int hash)
169{
170	/*
171	 * hash to find the appropriate bucket, look through the chain
172	 * for the right entry (especially right cred, unless cred == 0)
173	 */
174	struct coda_cache *cncp;
175	int count = 1;
176
177	CODA_NC_DEBUG(CODA_NC_FIND,
178		myprintf(("coda_nc_find(dcp %p, name %s, len %d, cred %p, hash %d\n",
179			dcp, name, namelen, cred, hash));)
180
181	LIST_FOREACH(cncp, &coda_nc_hash[hash].head, hash)
182	{
183
184	    if ((CODA_NAMEMATCH(cncp, name, namelen, dcp)) &&
185		((cred == 0) || (cncp->cred == cred)))
186	    {
187		/* compare cr_uid instead */
188		coda_nc_stat.Search_len += count;
189		return(cncp);
190	    }
191#ifdef	DEBUG
192	    else if (CODA_NAMEMATCH(cncp, name, namelen, dcp)) {
193	    	printf("coda_nc_find: name %s, new cred = %p, cred = %p\n",
194			name, cred, cncp->cred);
195		printf("nref %d, nuid %d, ngid %d // oref %d, ocred %d, ogid %d\n",
196			kauth_cred_getrefcnt(cred),
197			kauth_cred_geteuid(cred),
198			kauth_cred_getegid(cred),
199			kauth_cred_getrefcnt(cncp->cred),
200			kauth_cred_geteuid(cncp->cred),
201			kauth_cred_getegid(cncp->cred));
202		coda_print_cred(cred);
203		coda_print_cred(cncp->cred);
204	    }
205#endif
206	    count++;
207	}
208
209	return((struct coda_cache *)0);
210}
211
212/*
213 * Enter a new (dir cnode, name) pair into the cache, updating the
214 * LRU and Hash as needed.
215 */
216void
217coda_nc_enter(struct cnode *dcp, const char *name, int namelen,
218	kauth_cred_t cred, struct cnode *cp)
219{
220    struct coda_cache *cncp;
221    int hash;
222
223    if (coda_nc_use == 0)			/* Cache is off */
224	return;
225
226    CODA_NC_DEBUG(CODA_NC_ENTER,
227		myprintf(("Enter: dcp %p cp %p name %s cred %p \n",
228		       dcp, cp, name, cred)); )
229
230    if (namelen > CODA_NC_NAMELEN) {
231	CODA_NC_DEBUG(CODA_NC_ENTER,
232		    myprintf(("long name enter %s\n",name));)
233	    coda_nc_stat.long_name_enters++;	/* record stats */
234	return;
235    }
236
237    hash = CODA_NC_HASH(name, namelen, dcp);
238    cncp = coda_nc_find(dcp, name, namelen, cred, hash);
239    if (cncp != (struct coda_cache *) 0) {
240	coda_nc_stat.dbl_enters++;		/* duplicate entry */
241	return;
242    }
243
244    coda_nc_stat.enters++;		/* record the enters statistic */
245
246    /* Grab the next element in the lru chain */
247    cncp = TAILQ_FIRST(&coda_nc_lru.head);
248    TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
249
250    if (CODA_NC_VALID(cncp)) {
251	/* Seems really ugly, but we have to decrement the appropriate
252	   hash bucket length here, so we have to find the hash bucket
253	   */
254	coda_nc_hash[CODA_NC_HASH(cncp->name, cncp->namelen, cncp->dcp)].length--;
255
256	coda_nc_stat.lru_rm++;	/* zapped a valid entry */
257	LIST_REMOVE(cncp, hash);
258	vrele(CTOV(cncp->dcp));
259	vrele(CTOV(cncp->cp));
260	kauth_cred_free(cncp->cred);
261    }
262
263    /*
264     * Put a hold on the current vnodes and fill in the cache entry.
265     */
266    vref(CTOV(cp));
267    vref(CTOV(dcp));
268    kauth_cred_hold(cred);
269    cncp->dcp = dcp;
270    cncp->cp = cp;
271    cncp->namelen = namelen;
272    cncp->cred = cred;
273
274    memcpy(cncp->name, name, (unsigned)namelen);
275
276    /* Insert into the lru and hash chains. */
277    TAILQ_INSERT_TAIL(&coda_nc_lru.head, cncp, lru);
278    LIST_INSERT_HEAD(&coda_nc_hash[hash].head, cncp, hash);
279    coda_nc_hash[hash].length++;                      /* Used for tuning */
280
281    CODA_NC_DEBUG(CODA_NC_PRINTCODA_NC, print_coda_nc(); )
282}
283
284/*
285 * Find the (dir cnode, name) pair in the cache, if its cred
286 * matches the input, return it, otherwise return 0
287 */
288struct cnode *
289coda_nc_lookup(struct cnode *dcp, const char *name, int namelen,
290	kauth_cred_t cred)
291{
292	int hash;
293	struct coda_cache *cncp;
294
295	if (coda_nc_use == 0)			/* Cache is off */
296		return((struct cnode *) 0);
297
298	if (namelen > CODA_NC_NAMELEN) {
299	        CODA_NC_DEBUG(CODA_NC_LOOKUP,
300			    myprintf(("long name lookup %s\n",name));)
301		coda_nc_stat.long_name_lookups++;		/* record stats */
302		return((struct cnode *) 0);
303	}
304
305	/* Use the hash function to locate the starting point,
306	   then the search routine to go down the list looking for
307	   the correct cred.
308 	 */
309
310	hash = CODA_NC_HASH(name, namelen, dcp);
311	cncp = coda_nc_find(dcp, name, namelen, cred, hash);
312	if (cncp == (struct coda_cache *) 0) {
313		coda_nc_stat.misses++;			/* record miss */
314		return((struct cnode *) 0);
315	}
316
317	coda_nc_stat.hits++;
318
319	/* put this entry at the end of the LRU */
320	TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
321	TAILQ_INSERT_TAIL(&coda_nc_lru.head, cncp, lru);
322
323	/* move it to the front of the hash chain */
324	/* don't need to change the hash bucket length */
325	LIST_REMOVE(cncp, hash);
326	LIST_INSERT_HEAD(&coda_nc_hash[hash].head, cncp, hash);
327
328	CODA_NC_DEBUG(CODA_NC_LOOKUP,
329		printf("lookup: dcp %p, name %s, cred %p = cp %p\n",
330			dcp, name, cred, cncp->cp); )
331
332	return(cncp->cp);
333}
334
335static void
336coda_nc_remove(struct coda_cache *cncp, enum dc_status dcstat)
337{
338	/*
339	 * remove an entry -- vrele(cncp->dcp, cp), crfree(cred),
340	 * remove it from its hash chain, and
341	 * place it at the head of the lru list.
342	 */
343        CODA_NC_DEBUG(CODA_NC_REMOVE,
344		    myprintf(("coda_nc_remove %s from parent %s\n",
345			      cncp->name, coda_f2s(&cncp->dcp->c_fid))); )
346
347
348	LIST_REMOVE(cncp, hash);
349	memset(&cncp->hash, 0, sizeof(cncp->hash));
350
351	if ((dcstat == IS_DOWNCALL) && (vrefcnt(CTOV(cncp->dcp)) == 1)) {
352		cncp->dcp->c_flags |= C_PURGING;
353	}
354	vrele(CTOV(cncp->dcp));
355
356	if ((dcstat == IS_DOWNCALL) && (vrefcnt(CTOV(cncp->cp)) == 1)) {
357		cncp->cp->c_flags |= C_PURGING;
358	}
359	vrele(CTOV(cncp->cp));
360
361	kauth_cred_free(cncp->cred);
362	memset(DATA_PART(cncp), 0, DATA_SIZE);
363
364	/* move the null entry to the front for reuse */
365	TAILQ_REMOVE(&coda_nc_lru.head, cncp, lru);
366	TAILQ_INSERT_HEAD(&coda_nc_lru.head, cncp, lru);
367}
368
369/*
370 * Remove all entries with a parent which has the input fid.
371 */
372void
373coda_nc_zapParentfid(CodaFid *fid, enum dc_status dcstat)
374{
375	/* To get to a specific fid, we might either have another hashing
376	   function or do a sequential search through the cache for the
377	   appropriate entries. The later may be acceptable since I don't
378	   think callbacks or whatever Case 1 covers are frequent occurrences.
379	 */
380	struct coda_cache *cncp, *ncncp;
381	int i;
382
383	if (coda_nc_use == 0)			/* Cache is off */
384		return;
385
386	CODA_NC_DEBUG(CODA_NC_ZAPPFID,
387		myprintf(("ZapParent: fid %s\n", coda_f2s(fid))); )
388
389	coda_nc_stat.zapPfids++;
390
391	for (i = 0; i < coda_nc_hashsize; i++) {
392
393		/*
394		 * Need to save the hash_next pointer in case we remove the
395		 * entry. remove causes hash_next to point to itself.
396		 */
397
398		ncncp = LIST_FIRST(&coda_nc_hash[i].head);
399		while ((cncp = ncncp) != NULL) {
400			ncncp = LIST_NEXT(cncp, hash);
401
402			if (coda_fid_eq(&(cncp->dcp->c_fid), fid)) {
403			        coda_nc_hash[i].length--;      /* Used for tuning */
404				coda_nc_remove(cncp, dcstat);
405			}
406		}
407	}
408}
409
410/*
411 * Remove all entries which have the same fid as the input
412 */
413void
414coda_nc_zapfid(CodaFid *fid, enum dc_status dcstat)
415{
416	/* See comment for zapParentfid. This routine will be used
417	   if attributes are being cached.
418	 */
419	struct coda_cache *cncp, *ncncp;
420	int i;
421
422	if (coda_nc_use == 0)			/* Cache is off */
423		return;
424
425	CODA_NC_DEBUG(CODA_NC_ZAPFID,
426		myprintf(("Zapfid: fid %s\n", coda_f2s(fid))); )
427
428	coda_nc_stat.zapFids++;
429
430	for (i = 0; i < coda_nc_hashsize; i++) {
431
432		ncncp = LIST_FIRST(&coda_nc_hash[i].head);
433		while ((cncp = ncncp) != NULL) {
434			ncncp = LIST_NEXT(cncp, hash);
435
436			if (coda_fid_eq(&cncp->cp->c_fid, fid)) {
437			        coda_nc_hash[i].length--;     /* Used for tuning */
438				coda_nc_remove(cncp, dcstat);
439			}
440		}
441	}
442}
443
444/*
445 * Remove all entries which match the fid and the cred
446 */
447void
448coda_nc_zapvnode(CodaFid *fid, kauth_cred_t cred,
449    enum dc_status dcstat)
450{
451	/* See comment for zapfid. I don't think that one would ever
452	   want to zap a file with a specific cred from the kernel.
453	   We'll leave this one unimplemented.
454	 */
455	if (coda_nc_use == 0)			/* Cache is off */
456		return;
457
458	CODA_NC_DEBUG(CODA_NC_ZAPVNODE,
459		myprintf(("Zapvnode: fid %s cred %p\n",
460			  coda_f2s(fid), cred)); )
461}
462
463/*
464 * Remove all entries which have the (dir vnode, name) pair
465 */
466void
467coda_nc_zapfile(struct cnode *dcp, const char *name, int namelen)
468{
469	/* use the hash function to locate the file, then zap all
470 	   entries of it regardless of the cred.
471	 */
472	struct coda_cache *cncp;
473	int hash;
474
475	if (coda_nc_use == 0)			/* Cache is off */
476		return;
477
478	CODA_NC_DEBUG(CODA_NC_ZAPFILE,
479		myprintf(("Zapfile: dcp %p name %s \n",
480			  dcp, name)); )
481
482	if (namelen > CODA_NC_NAMELEN) {
483		coda_nc_stat.long_remove++;		/* record stats */
484		return;
485	}
486
487	coda_nc_stat.zapFile++;
488
489	hash = CODA_NC_HASH(name, namelen, dcp);
490	cncp = coda_nc_find(dcp, name, namelen, 0, hash);
491
492	while (cncp) {
493	  coda_nc_hash[hash].length--;                 /* Used for tuning */
494/* 1.3 */
495	  coda_nc_remove(cncp, NOT_DOWNCALL);
496	  cncp = coda_nc_find(dcp, name, namelen, 0, hash);
497	}
498}
499
500/*
501 * Remove all the entries for a particular user. Used when tokens expire.
502 * A user is determined by his/her effective user id (id_uid).
503 */
504void
505coda_nc_purge_user(uid_t uid, enum dc_status dcstat)
506{
507	/*
508	 * I think the best approach is to go through the entire cache
509	 * via HASH or whatever and zap all entries which match the
510	 * input cred. Or just flush the whole cache.  It might be
511	 * best to go through on basis of LRU since cache will almost
512	 * always be full and LRU is more straightforward.
513	 */
514
515	struct coda_cache *cncp, *ncncp;
516	int hash;
517
518	if (coda_nc_use == 0)			/* Cache is off */
519		return;
520
521	CODA_NC_DEBUG(CODA_NC_PURGEUSER,
522		myprintf(("ZapDude: uid %x\n", uid)); )
523	coda_nc_stat.zapUsers++;
524
525	ncncp = TAILQ_FIRST(&coda_nc_lru.head);
526	while ((cncp = ncncp) != NULL) {
527		ncncp = TAILQ_NEXT(cncp, lru);
528
529		if ((CODA_NC_VALID(cncp)) &&
530		   (kauth_cred_geteuid(cncp->cred) == uid)) {
531		        /* Seems really ugly, but we have to decrement the appropriate
532			   hash bucket length here, so we have to find the hash bucket
533			   */
534		        hash = CODA_NC_HASH(cncp->name, cncp->namelen, cncp->dcp);
535			coda_nc_hash[hash].length--;     /* For performance tuning */
536
537			coda_nc_remove(cncp, dcstat);
538		}
539	}
540}
541
542/*
543 * Flush the entire name cache. In response to a flush of the Venus cache.
544 */
545void
546coda_nc_flush(enum dc_status dcstat)
547{
548	/* One option is to deallocate the current name cache and
549	   call init to start again. Or just deallocate, then rebuild.
550	   Or again, we could just go through the array and zero the
551	   appropriate fields.
552	 */
553
554	/*
555	 * Go through the whole lru chain and kill everything as we go.
556	 * I don't use remove since that would rebuild the lru chain
557	 * as it went and that seemed unneccesary.
558	 */
559	struct coda_cache *cncp;
560	int i;
561
562	if (coda_nc_use == 0)			/* Cache is off */
563		return;
564
565	coda_nc_stat.Flushes++;
566
567	TAILQ_FOREACH(cncp, &coda_nc_lru.head, lru) {
568		if (CODA_NC_VALID(cncp)) {	/* only zero valid nodes */
569			LIST_REMOVE(cncp, hash);
570			memset(&cncp->hash, 0, sizeof(cncp->hash));
571
572			if ((dcstat == IS_DOWNCALL)
573			    && (vrefcnt(CTOV(cncp->dcp)) == 1))
574			{
575				cncp->dcp->c_flags |= C_PURGING;
576			}
577			vrele(CTOV(cncp->dcp));
578
579			if (CTOV(cncp->cp)->v_iflag & VI_TEXT) {
580			    if (coda_vmflush(cncp->cp))
581				CODADEBUG(CODA_FLUSH,
582					myprintf(("coda_nc_flush: %s busy\n",
583						coda_f2s(&cncp->cp->c_fid))); )
584			}
585
586			if ((dcstat == IS_DOWNCALL)
587			    && (vrefcnt(CTOV(cncp->cp)) == 1))
588			{
589				cncp->cp->c_flags |= C_PURGING;
590			}
591			vrele(CTOV(cncp->cp));
592
593			kauth_cred_free(cncp->cred);
594			memset(DATA_PART(cncp), 0, DATA_SIZE);
595		}
596	}
597
598	for (i = 0; i < coda_nc_hashsize; i++)
599	  coda_nc_hash[i].length = 0;
600}
601
602/*
603 * Debugging routines
604 */
605
606/*
607 * This routine should print out all the hash chains to the console.
608 */
609void
610print_coda_nc(void)
611{
612	int hash;
613	struct coda_cache *cncp;
614
615	for (hash = 0; hash < coda_nc_hashsize; hash++) {
616		myprintf(("\nhash %d\n",hash));
617
618		LIST_FOREACH(cncp, &coda_nc_hash[hash].head, hash) {
619			myprintf(("cp %p dcp %p cred %p name %s\n",
620				  cncp->cp, cncp->dcp,
621				  cncp->cred, cncp->name));
622		     }
623	}
624}
625
626void
627coda_nc_gather_stats(void)
628{
629    int i, xmax = 0, sum = 0, temp, zeros = 0, ave, n;
630
631	for (i = 0; i < coda_nc_hashsize; i++) {
632	  if (coda_nc_hash[i].length) {
633	    sum += coda_nc_hash[i].length;
634	  } else {
635	    zeros++;
636	  }
637
638	  if (coda_nc_hash[i].length > xmax)
639	    xmax = coda_nc_hash[i].length;
640	}
641
642	/*
643	 * When computing the Arithmetic mean, only count slots which
644	 * are not empty in the distribution.
645	 */
646        coda_nc_stat.Sum_bucket_len = sum;
647        coda_nc_stat.Num_zero_len = zeros;
648        coda_nc_stat.Max_bucket_len = xmax;
649
650	if ((n = coda_nc_hashsize - zeros) > 0)
651	  ave = sum / n;
652	else
653	  ave = 0;
654
655	sum = 0;
656	for (i = 0; i < coda_nc_hashsize; i++) {
657	  if (coda_nc_hash[i].length) {
658	    temp = coda_nc_hash[i].length - ave;
659	    sum += temp * temp;
660	  }
661	}
662        coda_nc_stat.Sum2_bucket_len = sum;
663}
664
665/*
666 * The purpose of this routine is to allow the hash and cache sizes to be
667 * changed dynamically. This should only be used in controlled environments,
668 * it makes no effort to lock other users from accessing the cache while it
669 * is in an improper state (except by turning the cache off).
670 */
671int
672coda_nc_resize(int hashsize, int heapsize, enum dc_status dcstat)
673{
674    if ((hashsize % 2) || (heapsize % 2)) { /* Illegal hash or cache sizes */
675	return(EINVAL);
676    }
677
678    coda_nc_use = 0;                       /* Turn the cache off */
679
680    coda_nc_flush(dcstat);                 /* free any cnodes in the cache */
681
682    /* WARNING: free must happen *before* size is reset */
683    CODA_FREE(coda_nc_heap,TOTAL_CACHE_SIZE);
684    CODA_FREE(coda_nc_hash,TOTAL_HASH_SIZE);
685
686    coda_nc_hashsize = hashsize;
687    coda_nc_size = heapsize;
688
689    coda_nc_init();                        /* Set up a cache with the new size */
690
691    coda_nc_use = 1;                       /* Turn the cache back on */
692    return(0);
693}
694
695char coda_nc_name_buf[CODA_MAXNAMLEN+1];
696
697void
698coda_nc_name(struct cnode *cp)
699{
700	struct coda_cache *cncp;
701	int i;
702
703	if (coda_nc_use == 0)			/* Cache is off */
704		return;
705
706	for (i = 0; i < coda_nc_hashsize; i++) {
707
708		LIST_FOREACH(cncp, &coda_nc_hash[i].head, hash) {
709			if (cncp->cp == cp) {
710				memcpy(coda_nc_name_buf, cncp->name, cncp->namelen);
711				coda_nc_name_buf[cncp->namelen] = 0;
712				printf(" is %s (%p,%p)@%p",
713					coda_nc_name_buf, cncp->cp, cncp->dcp, cncp);
714			}
715
716		}
717	}
718}
719