patch-2.1.70 linux/fs/coda/namecache.c

Next file: linux/fs/coda/pioctl.c
Previous file: linux/fs/coda/inode.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.69/linux/fs/coda/namecache.c linux/fs/coda/namecache.c
@@ -0,0 +1,838 @@
+/* 
+ * Mach Operating System
+ * Copyright (c) 1990 Carnegie-Mellon University
+ * Copyright (c) 1989 Carnegie-Mellon University
+ * All rights reserved.  The CMU software License Agreement specifies
+ * the terms and conditions for use and redistribution.
+ */
+
+/*
+ * This code was written for the Coda file system at Carnegie Mellon University.
+ * Contributers include David Steere, James Kistler, and M. Satyanarayanan.
+ */
+
+
+/*
+ * This module contains the routines to implement the CFS name cache. The
+ * purpose of this cache is to reduce the cost of translating pathnames 
+ * into Vice FIDs. Each entry in the cache contains the name of the file,
+ * the vnode (FID) of the parent directory, and the cred structure of the
+ * user accessing the file.
+ *
+ * The first time a file is accessed, it is looked up by the local Venus
+ * which first insures that the user has access to the file. In addition
+ * we are guaranteed that Venus will invalidate any name cache entries in
+ * case the user no longer should be able to access the file. For these
+ * reasons we do not need to keep access list information as well as a
+ * cred structure for each entry.
+ *
+ * The table can be accessed through the routines cnc_init(), cnc_enter(),
+ * cnc_lookup(), cnc_rmfidcred(), cnc_rmfid(), cnc_rmcred(), and cnc_purge().
+ * There are several other routines which aid in the implementation of the
+ * hash table.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/errno.h>
+#include <linux/locks.h>
+#include <asm/segment.h>
+#include <linux/string.h>
+
+#include <linux/coda.h>
+#include <linux/coda_linux.h>
+#include <linux/coda_cnode.h>
+#include <linux/coda_namecache.h>
+
+
+int cfsnc_use;
+
+static struct cfscache * cfsnc_find(struct cnode *dcp, const char * name, int namelen, int hash);
+static void cfsnc_remove(struct cfscache *cncp);
+static inline int  nchash(const char *, int, struct cnode *);
+static inline int ncmatch(struct cfscache *, const char *, int, 
+                          struct cnode *);
+static inline void hashins(struct cfscache *a, struct cfscache *pred);
+static inline void hashrem(struct cfscache *a);
+static inline void hashnull(struct cfscache *);
+static inline void lrurem(struct cfscache *a);
+static inline void lruins(struct cfscache *a, struct cfscache *pred);
+static void cfsnc_gather_stats(void);
+
+
+/* externals */
+extern int coda_fideq(ViceFid *fid1, ViceFid *fid2);
+extern int coda_debug;
+extern int coda_print_entry;
+extern struct super_block *coda_super_block;
+
+
+
+/* 
+ * Declaration of the name cache data structure.
+ */
+
+int 	cfsnc_use = 0;			 /* Indicate use of CFS Name Cache */
+int	cfsnc_size = CFSNC_CACHESIZE;	 /* size of the cache */
+int	cfsnc_hashsize = CFSNC_HASHSIZE; /* size of the primary hash */
+int     cfsnc_flushme = 0;
+int     cfsnc_procsize = 0;
+static  int cfsnc_force = 0;
+
+struct cfshash {
+	struct cfscache *hash_next, *hash_prev;
+	int              length;
+};
+
+struct cfslruhead {
+        struct cfscache *dummy1, *dummy2;
+        struct cfscache *lru_next, *lru_prev;
+};
+
+struct 	cfscache *cfsncheap;	/* pointer to the cache entries */
+struct	cfshash  *cfsnchash;	/* hash table of cfscache pointers */
+struct	cfslruhead  cfsnc_lru;	/* head of lru chain; prev = lru */
+
+struct cfsnc_statistics cfsnc_stat;	/* Keep various stats */
+
+#define TOTAL_CACHE_SIZE 	(sizeof(struct cfscache) * cfsnc_size)
+#define TOTAL_HASH_SIZE 	(sizeof(struct cfshash)  * cfsnc_hashsize)
+int cfsnc_initialized = 0;      /* Initially the cache has not been initialized */
+
+/* 
+ * for testing purposes
+ */
+int cfsnc_debug = 1;
+
+
+/*
+ * Auxillary routines -- shouldn't be entry points
+ */
+
+
+/*
+ * Hash function for the primary hash.
+ * First try -- (first + last letters + length + (int)cp) mod size
+ * 2nd try -- same, except dir fid.vnode instead of cp
+ */
+static inline int  
+nchash(const char *name, int namelen, struct cnode *cp)
+{
+    return ((name[0] + name[namelen-1] + 
+             namelen + (int)(cp)) & (cfsnc_hashsize-1));   
+}
+
+/* matching function */
+static inline int ncmatch(struct cfscache *cp, const char *name, int namelen,
+                          struct cnode *dcp)
+{
+    return 	((namelen == cp->namelen) && (dcp == cp->dcp) && 
+		 (memcmp(cp->name,name,namelen) == 0));
+}
+
+/* insert  a  behind  pred */
+static inline void hashins(struct cfscache *a, struct cfscache *pred)
+{
+	a->hash_next = pred->hash_next;
+	pred->hash_next->hash_prev= a;
+	pred->hash_next = a;
+	a->hash_prev = pred;
+}
+
+static inline void hashrem(struct cfscache *a) 
+{
+	a->hash_prev->hash_next = a->hash_next;
+	a->hash_next->hash_prev = a->hash_prev;
+}
+
+static inline void hashnull(struct cfscache *elem) {
+	elem->hash_next = elem;
+	elem->hash_prev = elem;
+}
+
+static inline void lrurem(struct cfscache *a) 
+{
+	a->lru_prev->lru_next = a->lru_next;
+	a->lru_next->lru_prev = a->lru_prev;
+}
+
+static inline void lruins(struct cfscache *a, struct cfscache *pred)
+{
+	pred->lru_next->lru_prev= a;
+	a->lru_next = pred->lru_next;
+	
+	a->lru_prev = pred;
+	pred->lru_next = a;
+}
+
+static struct cfscache *
+cfsnc_find(struct cnode *dcp, const char * name, int namelen, int hash)
+{
+	/* 
+	 * hash to find the appropriate bucket, look through the chain
+	 * for the right entry 
+	 */
+	register struct cfscache *cncp;
+	int count = 1;
+
+	CDEBUG(D_CACHE, "dcp 0x%x, name %s, len %d, hash %d\n",
+			   (int)dcp, name, namelen, hash);
+
+	for (cncp  = cfsnchash[hash].hash_next; 
+	     cncp != (struct cfscache *)&cfsnchash[hash];
+	     cncp  = cncp->hash_next, count++) 
+	{
+
+	    if (ncmatch(cncp, name, namelen, dcp))
+	    { 
+		cfsnc_stat.Search_len += count;
+		CDEBUG(D_CACHE, "dcp 0x%x,found.\n", (int) dcp);
+		return(cncp);
+			
+	    }
+	}
+	CDEBUG(D_CACHE, "dcp 0x%x,not found.\n", (int) dcp);
+	return((struct cfscache *)0);
+}
+
+static void
+cfsnc_remove(struct cfscache *cncp)
+{
+	/* 
+	 * remove an entry -- VN_RELE(cncp->dcp, cp), crfree(cred),
+	 * remove it from it's hash chain, and
+	 * place it at the head of the lru list.
+	 */
+    CDEBUG(D_CACHE, "remove %s from parent %lx.%lx.%lx\n",
+           cncp->name, (cncp->dcp)->c_fid.Volume,
+           (cncp->dcp)->c_fid.Vnode, (cncp->dcp)->c_fid.Unique);
+
+  	hashrem(cncp);
+	hashnull(cncp);		/* have it be a null chain */
+
+	/* VN_RELE(CTOV(cncp->dcp));  */
+	iput(CTOI(cncp->cp)); 
+	/* crfree(cncp->cred);  */
+
+	memset(DATA_PART(cncp), 0 ,DATA_SIZE);
+	cncp->cp = NULL;
+	cncp->dcp = (struct cnode *) 0;
+
+	/* Put the null entry just after the least-recently-used entry */
+	lrurem(cncp);
+	lruins(cncp, cfsnc_lru.lru_prev);
+}
+
+
+/*
+ * Entry points for the CFS Name Cache
+ */
+
+/*  
+ * Initialize the cache, the LRU structure and the Hash structure(s)
+ */
+void
+cfsnc_init(void)
+{
+    register int i;
+
+    /* zero the statistics structure */
+    cfsnc_procsize =  10000 * cfsnc_hashsize + cfsnc_size;
+    memset(&cfsnc_stat, 0, (sizeof(struct cfsnc_statistics)));
+    
+    CODA_ALLOC(cfsncheap, struct cfscache *, TOTAL_CACHE_SIZE);
+    CODA_ALLOC(cfsnchash, struct cfshash *, TOTAL_HASH_SIZE);
+    
+    cfsnc_lru.lru_next = cfsnc_lru.lru_prev = (struct cfscache *)&cfsnc_lru; 
+    
+    /* initialize the heap */
+    for (i=0; i < cfsnc_size; i++) {	
+	lruins(&cfsncheap[i], (struct cfscache *) &cfsnc_lru);
+	hashnull(&cfsncheap[i]);
+	cfsncheap[i].cp = cfsncheap[i].dcp = (struct cnode *)0;
+    }
+    
+    for (i=0; i < cfsnc_hashsize; i++) {	/* initialize the hashtable */
+	hashnull((struct cfscache *)&cfsnchash[i]);
+	cfsnchash[i].length=0;  /* bucket length */
+    }
+    
+    cfsnc_initialized = 1;
+    CDEBUG(D_CACHE, "cfsnc_initialized is now 1.\n");
+}
+
+/*
+ * Enter a new (dir cnode, name) pair into the cache, updating the
+ * LRU and Hash as needed.
+ */
+
+void
+cfsnc_enter(struct cnode *dcp, register const char *name, int namelen, struct cnode *cp)
+{
+    register struct cfscache *cncp;
+    register int hash;
+    
+    if (cfsnc_use == 0)			/* Cache is off */
+	return;
+    
+    CDEBUG(D_CACHE, "dcp 0x%x cp 0x%x name %s, ind 0x%x \n",
+	   (int)dcp, (int)cp, name, (int)cp->c_vnode); 
+	
+    if (namelen > CFSNC_NAMELEN) {
+        CDEBUG(D_CACHE, "long name enter %s\n",name);
+	    cfsnc_stat.long_name_enters++;	/* record stats */
+	return;
+    }
+    
+    hash = nchash(name, namelen, dcp);
+	CDEBUG(D_CACHE, "Calling find with name %s, dcp %d, hash %d\n",
+	       name, (int) dcp, (int) hash);
+
+    cncp = cfsnc_find(dcp, name, namelen, hash);
+    if (cncp != (struct cfscache *) 0) {	
+	printk("cfsnc_enter: Duplicate cache entry; tell Peter.\n");
+	cfsnc_stat.dbl_enters++;		/* duplicate entry */
+	return;
+    }
+    
+    cfsnc_stat.enters++;		/* record the enters statistic */
+    
+    /* Grab the lru element in the lru chain */
+    cncp = cfsnc_lru.lru_prev;
+    
+    lrurem(cncp);	/* remove it from the lists */
+    
+    /* if cncp is on hash list remove it */
+    if ( cncp->dcp != (struct cnode *) 0 ) {
+	/* We have to decrement the appropriate hash bucket length
+	   here, so we have to find the hash bucket */
+	cfsnchash[nchash(cncp->name, cncp->namelen, cncp->dcp)].length--;
+	cfsnc_stat.lru_rm++;	/* zapped a valid entry */
+	hashrem(cncp);
+	iput(CTOI(cncp->cp));
+	/* VN_RELE(CTOV(cncp->dcp));  */
+	/* crfree(cncp->cred); */
+    }
+    /*
+     * Put a hold on the current vnodes and fill in the cache entry.
+     */
+    iget((CTOI(cp))->i_sb, CTOI(cp)->i_ino);
+    /* VN_HOLD(CTOV(dcp)); */
+    /* XXXX crhold(cred); */
+    cncp->dcp = dcp;
+    cncp->cp = cp;
+    cncp->namelen = namelen;
+    /* cncp->cred = cred; */
+    
+    memcpy(cncp->name, name, (unsigned)namelen);
+    
+    /* Insert into the lru and hash chains. */
+    
+    lruins(cncp, (struct cfscache *) &cfsnc_lru);
+    hashins(cncp, (struct cfscache *)&cfsnchash[hash]);
+    cfsnchash[hash].length++;                      /* Used for tuning */
+    CDEBUG(D_CACHE, "Entering:\n");
+    coda_print_ce(cncp);
+}
+
+/*
+ * Find the (dir cnode, name) pair in the cache, if it's cred
+ * matches the input, return it, otherwise return 0
+ */
+
+struct cnode *
+cfsnc_lookup(struct cnode *dcp, register const char *name, int namelen)
+{
+	register int hash;
+	register struct cfscache *cncp;
+        /* this should go into a callback funcntion for /proc/sys
+           don't know how at the moment? */  
+	if (cfsnc_flushme == 1) {
+		cfsnc_flush();
+		cfsnc_flushme = 0;
+	}
+	
+	if (cfsnc_procsize != 10000*cfsnc_hashsize + cfsnc_size ) {
+		int hsh = cfsnc_procsize/10000;
+		int siz = cfsnc_procsize%10000;
+		int rc;
+		
+		if ( (hsh > 1) && (siz > 2) ) {
+			rc = cfsnc_resize(hsh, siz);
+			if ( !rc ) {
+				printk("Coda:cache size (hash,size) (%d,%d)\n",
+					hsh, siz);
+			} else {
+				printk("Coda: cache resize failed\n");
+			}
+		}
+	}
+
+	if (cfsnc_use == 0)			/* Cache is off */
+		return((struct cnode *) 0);
+
+	if (namelen > CFSNC_NAMELEN) {
+        CDEBUG(D_CACHE,"long name lookup %s\n",name);
+		cfsnc_stat.long_name_lookups++;		/* record stats */
+		return((struct cnode *) 0);
+	}
+
+	/* Use the hash function to locate the starting point,
+	   then the search routine to go down the list looking for
+	   the correct cred.
+ 	 */
+
+	hash = nchash(name, namelen, dcp);
+	CDEBUG(D_CACHE, "Calling find with name %s, dcp %d, hash %d\n",
+	       name, (int) dcp, (int) hash);
+	cncp = cfsnc_find(dcp, name, namelen, hash);
+	if (cncp == (struct cfscache *) 0) {
+		cfsnc_stat.misses++;			/* record miss */
+		return((struct cnode *) 0);
+	}
+
+	cfsnc_stat.hits++;
+
+	/* put this entry at the mru end of the LRU */
+	lrurem(cncp);
+	lruins(cncp, (struct cfscache *) &cfsnc_lru);
+
+	/* move it to the front of the hash chain */
+	/* don't need to change the hash bucket length */
+	hashrem(cncp);
+	hashins(cncp, (struct cfscache *) &cfsnchash[hash]);
+
+	CDEBUG(D_CACHE, "lookup: dcp 0x%x, name %s,  cp 0x%x\n",
+           (int) dcp, name,  (int) cncp->cp);
+
+	return(cncp->cp);
+}
+
+/*
+ * Remove all entries with a parent which has the input fid.
+ */
+
+void
+cfsnc_zapParentfid(ViceFid *fid)
+{
+	/* To get to a specific fid, we might either have another hashing
+	   function or do a sequential search through the cache for the
+	   appropriate entries. The later may be acceptable since I don't
+	   think callbacks or whatever Case 1 covers are frequent occurences.
+	 */
+	register struct cfscache *cncp, *ncncp;
+	register int i;
+
+	if (cfsnc_use == 0)			/* Cache is off */
+		return;
+
+	CDEBUG(D_CACHE, " fid 0x%lx, 0x%lx, 0x%lx \n",
+           fid->Volume, fid->Vnode, fid->Unique);
+
+	cfsnc_stat.zapPfids++;
+
+	for (i = 0; i < cfsnc_hashsize; i++) {
+
+		/*
+		 * Need to save the hash_next pointer in case we remove the
+		 * entry. remove causes hash_next to point to itself.
+		 */
+
+		for (cncp = cfsnchash[i].hash_next; 
+		     cncp != (struct cfscache *) &cfsnchash[i];
+		     cncp = ncncp) {
+			ncncp = cncp->hash_next;
+			if ( coda_fideq(&cncp->dcp->c_fid, fid) ) {
+			        cfsnchash[i].length--;   /* Used for tuning */
+				cfsnc_remove(cncp); 
+			    }
+		     }
+	}
+}
+
+/*
+ * Remove all entries which have the same fid as the input
+ */
+void
+cfsnc_zapfid(ViceFid *fid)
+{
+	/* See comment for zapParentfid. This routine will be used
+	   if attributes are being cached. 
+	 */
+	register struct cfscache *cncp, *ncncp;
+	register int i;
+
+	if (cfsnc_use == 0)			/* Cache is off */
+		return;
+
+	CDEBUG(D_CACHE, "Zapfid: fid 0x%lx, 0x%lx, 0x%lx \n",
+           fid->Volume, fid->Vnode, fid->Unique);
+
+	cfsnc_stat.zapFids++;
+
+	for (i = 0; i < cfsnc_hashsize; i++) {
+		for (cncp = cfsnchash[i].hash_next; 
+		     cncp != (struct cfscache *) &cfsnchash[i];
+		     cncp = ncncp) {
+			ncncp = cncp->hash_next;
+			if (coda_fideq(&(cncp->cp->c_fid), fid)) {
+			        CDEBUG(D_CACHE, "Found cncp: name %s\n", cncp->name);
+			        cfsnchash[i].length--;   /* Used for tuning */
+				cfsnc_remove(cncp); 
+			    }
+		     }
+	}
+}
+
+
+/*
+ * Remove all entries which have the (dir vnode, name) pair
+ */
+void
+cfsnc_zapfile(struct cnode *dcp, register const char *name, int length)
+{
+	/* use the hash function to locate the file, then zap all
+ 	   entries of it regardless of the cred.
+	 */
+	register struct cfscache *cncp;
+	int hash;
+
+	if (cfsnc_use == 0)			/* Cache is off */
+		return;
+
+	CDEBUG(D_CACHE,"Zapfile: dcp 0x%x name %s \n",
+           (int) dcp, name);
+
+	if (length > CFSNC_NAMELEN) {
+		cfsnc_stat.long_remove++;		/* record stats */
+		return;
+	}
+
+	cfsnc_stat.zapFile++;
+
+	hash = nchash(name, length,  dcp);
+    /* remove entries: remember they might exist for more than a 
+       single cred */
+	while ( (cncp = cfsnc_find(dcp, name, length, hash)) != NULL ) {
+	  cfsnchash[hash].length--;       
+	  cfsnc_remove(cncp);
+	}
+}
+
+/* 
+ * Remove all the entries for a particular user. Used when tokens expire.
+ * A user is determined by his/her effective user id (id_uid).
+ */
+
+void
+cfsnc_purge_user(struct CodaCred *cred)
+{
+	/* I think the best approach is to go through the entire cache
+	   via HASH or whatever and zap all entries which match the
+	   input cred. Or just flush the whole cache.
+	   It might be best to go through on basis of LRU since cache
+	   will almost always be full and LRU is more straightforward.
+	 */
+
+	register struct cfscache *cncp;
+	int hash;
+
+	if (cfsnc_use == 0)			/* Cache is off */
+		return;
+
+	CDEBUG(D_CACHE,"ZapDude: uid %ld\n",cred->cr_uid);
+	cfsnc_stat.zapUsers++;
+
+	for (cncp = cfsnc_lru.lru_next;
+	     cncp != (struct cfscache *) &cfsnc_lru;
+	     cncp = cncp->lru_next) {
+
+		if ((CFSNC_VALID(cncp)) &&
+		   ((cncp->cred)->cr_uid == cred->cr_uid)) {
+		        /* Seems really ugly, but we have to decrement the appropriate
+			   hash bucket length here, so we have to find the hash bucket
+			   */
+		        hash = nchash(cncp->name, cncp->namelen, cncp->dcp);
+			cfsnchash[hash].length--;     /* For performance tuning */
+
+			cfsnc_remove(cncp); 
+		}
+	}
+}
+
+/*
+ * Flush the entire name cache. In response to a flush of the Venus cache.
+ */
+
+void
+cfsnc_flush(void)
+{
+	/* One option is to deallocate the current name cache and
+	   call init to start again. Or just deallocate, then rebuild.
+	   Or again, we could just go through the array and zero the 
+	   appropriate fields. 
+	 */
+	
+	/* 
+	 * Go through the whole lru chain and kill everything as we go.
+	 * I don't use remove since that would rebuild the lru chain
+	 * as it went and that seemed unneccesary.
+	 */
+	register struct cfscache *cncp;
+	int i;
+
+	if ((cfsnc_use == 0 || cfsnc_initialized == 0) && (cfsnc_force == 0) )
+		return;
+
+	cfsnc_stat.Flushes++;
+
+	for (cncp = cfsnc_lru.lru_next;
+	     cncp != (struct cfscache *) &cfsnc_lru;
+	     cncp = cncp->lru_next) {
+		if ( cncp->cp ) {
+			hashrem(cncp);	/* only zero valid nodes */
+			hashnull(cncp);
+			iput(CTOI(cncp->cp));  
+			/* crfree(cncp->cred);  */
+			memset(DATA_PART(cncp), 0, DATA_SIZE);
+		}
+	}
+
+	for (i = 0; i < cfsnc_hashsize; i++)
+	  cfsnchash[i].length = 0;
+}
+
+/*
+ * This routine replaces a ViceFid in the name cache with another.
+ * It is added to allow Venus during reintegration to replace 
+ * locally allocated temp fids while disconnected with global fids 
+ * even when the reference count on those fids are not zero.
+ */
+void
+cfsnc_replace(ViceFid *f1, ViceFid *f2)
+{
+        /* 
+	 * Replace f1 with f2 throughout the name cache
+	 */
+	int hash;
+	register struct cfscache *cncp;
+
+	CDEBUG(D_CACHE, 
+    "cfsnc_replace fid_1 = (%lx.%lx.%lx) and fid_2 = (%lx.%lx.%lx)\n",
+           f1->Volume, f1->Vnode, f1->Unique, 
+           f2->Volume, f2->Vnode, f2->Unique);
+
+	for (hash = 0; hash < cfsnc_hashsize; hash++) {
+		for (cncp = cfsnchash[hash].hash_next; 
+		     cncp != (struct cfscache *) &cfsnchash[hash];
+		     cncp = cncp->hash_next) {
+		        if (!memcmp(&cncp->cp->c_fid, f1, sizeof(ViceFid))) {
+			    memcpy(&cncp->cp->c_fid, f2, sizeof(ViceFid));
+			    continue; 	/* no need to check cncp->dcp now */
+			}
+		        if (!memcmp(&cncp->dcp->c_fid, f1, sizeof(ViceFid)))
+			    memcpy(&cncp->dcp->c_fid, f2, sizeof(ViceFid));
+		     }
+	}
+}
+
+/*
+ * Debugging routines
+ */
+
+/* 
+ * This routine should print out all the hash chains to the console.
+ */
+
+void
+print_cfsnc(void)
+{
+	int hash;
+	register struct cfscache *cncp;
+
+	for (hash = 0; hash < cfsnc_hashsize; hash++) {
+		printk("\nhash %d\n",hash);
+
+		for (cncp = cfsnchash[hash].hash_next; 
+		     cncp != (struct cfscache *)&cfsnchash[hash];
+		     cncp = cncp->hash_next) {
+			printk("cp 0x%x dcp 0x%x cred 0x%x name %s ino %d count %d dev %d\n",
+				  (int)cncp->cp, (int)cncp->dcp,
+				  (int)cncp->cred, cncp->name, CTOI(cncp->cp)->i_count, CTOI(cncp->cp)->i_count, CTOI(cncp->cp)->i_dev);
+		     }
+	}
+}
+
+int
+cfsnc_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+        int hash;
+        int len=0;
+        off_t pos=0;
+        off_t begin;
+	struct cfscache *cncp;
+        char tmpbuf[80];
+
+        if (offset < 80) 
+              len += sprintf(buffer, "%-79s\n",
+      "hash  len   volume    vnode   unique             name        ino       pino ct");
+	if ( !cfsnc_initialized ) {
+		*start = buffer;
+		return len;
+	}
+        pos = 80;
+	for (hash = 0; hash < cfsnc_hashsize; hash++) {
+		for (cncp = cfsnchash[hash].hash_next; 
+		     cncp != (struct cfscache *)&cfsnchash[hash];
+		     cncp = cncp->hash_next) {
+		 	pos += 80;
+		 	if (pos < offset)
+                                continue;
+			sprintf(tmpbuf, "%4d  %3d %8x %8x %8x %16s %10ld %10ld %2d", 
+				hash, cfsnchash[hash].length, (int) cncp->cp->c_fid.Volume, 
+				(int) cncp->cp->c_fid.Vnode, (int) cncp->cp->c_fid.Unique , cncp->name, 
+				CTOI(cncp->cp)->i_ino, 
+				CTOI(cncp->dcp)->i_ino, 
+				CTOI(cncp->cp)->i_count);
+                     	len += sprintf(buffer+len, "%-79s\n", tmpbuf);
+                     	if(len >= length)
+                                break;
+                	}
+               	if(len>= length)
+                       	break;
+		}
+        begin = len - (pos - offset);
+        *start = buffer + begin;
+        len -= begin;
+        if(len>length)
+                len = length;
+        return len;
+} 
+
+int
+cfsnc_nc_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+        int len=0;
+        off_t begin;
+	
+	cfsnc_gather_stats();
+
+	/* this works as long as we are below 1024 characters! */    
+    len += sprintf(buffer,"Coda minicache statistics\n\n");
+    len += sprintf(buffer+len, "cfsnc_hits : %d\n", cfsnc_stat.hits);
+    len += sprintf(buffer+len, "cfsnc_misses : %d\n", cfsnc_stat.misses);
+    len += sprintf(buffer+len, "cfsnc_enters : %d\n", cfsnc_stat.enters);
+    len += sprintf(buffer+len, "cfsnc_dbl_enters : %d\n", cfsnc_stat.dbl_enters);
+    len += sprintf(buffer+len, "cfsnc_long_name_enters : %d\n", cfsnc_stat.long_name_enters);
+    len += sprintf(buffer+len, "cfsnc_long_name_lookups : %d\n", cfsnc_stat.long_name_lookups);
+    len += sprintf(buffer+len, "cfsnc_long_remove : %d\n", cfsnc_stat.long_remove);
+    len += sprintf(buffer+len, "cfsnc_lru_rm : %d\n", cfsnc_stat.lru_rm);
+    len += sprintf(buffer+len, "cfsnc_zapPfids : %d\n", cfsnc_stat.zapPfids);
+    len += sprintf(buffer+len, "cfsnc_zapFids : %d\n", cfsnc_stat.zapFids);
+    len += sprintf(buffer+len, "cfsnc_zapFile : %d\n", cfsnc_stat.zapFile);
+    len += sprintf(buffer+len, "cfsnc_zapUsers : %d\n", cfsnc_stat.zapUsers);
+    len += sprintf(buffer+len, "cfsnc_Flushes : %d\n", cfsnc_stat.Flushes);
+    len += sprintf(buffer+len, "cfsnc_SumLen : %d\n", cfsnc_stat.Sum_bucket_len);
+    len += sprintf(buffer+len, "cfsnc_Sum2Len : %d\n", cfsnc_stat.Sum2_bucket_len);
+    len += sprintf(buffer+len,  "cfsnc_# 0 len : %d\n", cfsnc_stat.Num_zero_len);
+    len += sprintf(buffer+len,  "cfsnc_MaxLen : %d\n", cfsnc_stat.Max_bucket_len);
+    len += sprintf(buffer+len,  "cfsnc_SearchLen : %d\n", cfsnc_stat.Search_len);
+       	begin =  offset;
+       	*start = buffer + begin;
+       	len -= begin;
+
+        if(len>length)
+                len = length;
+	if (len< 0)
+		len = 0;
+        return len;
+} 
+
+
+
+void
+coda_print_ce(struct cfscache *ce) 
+{
+CDEBUG(D_CACHE, "cp 0x%x, dcp 0x%x, name %s, inod 0x%x, ino %d, count %d, dev %d\n",
+        (int)ce->cp, (int)ce->dcp, ce->name, (int)CTOI(ce->cp),(int)CTOI(ce->cp)->i_ino,  CTOI(ce->cp)->i_count, CTOI(ce->cp)->i_dev);
+}
+
+static void
+cfsnc_gather_stats(void)
+{
+    int i, max = 0, sum = 0, temp, zeros = 0, ave, n;
+
+	for (i = 0; i < cfsnc_hashsize; i++) {
+	  if (cfsnchash[i].length) {
+	    sum += cfsnchash[i].length;
+	  } else {
+	    zeros++;
+	  }
+
+	  if (cfsnchash[i].length > max)
+	    max = cfsnchash[i].length;
+	}
+
+/*
+ * When computing the Arithmetic mean, only count slots which 
+ * are not empty in the distribution.
+ */
+        cfsnc_stat.Sum_bucket_len = sum;
+        cfsnc_stat.Num_zero_len = zeros;
+        cfsnc_stat.Max_bucket_len = max;
+
+	if ((n = cfsnc_hashsize - zeros) > 0) 
+	  ave = sum / n;
+	else
+	  ave = 0;
+
+	sum = 0;
+	for (i = 0; i < cfsnc_hashsize; i++) {
+	  if (cfsnchash[i].length) {
+	    temp = cfsnchash[i].length - ave;
+	    sum += temp * temp;
+	  }
+	}
+        cfsnc_stat.Sum2_bucket_len = sum;
+}
+
+/*
+ * The purpose of this routine is to allow the hash and cache sizes to be
+ * changed dynamically. This should only be used in controlled environments,
+ * it makes no effort to lock other users from accessing the cache while it
+ * is in an improper state (except by turning the cache off).
+ */
+int
+cfsnc_resize(int hashsize, int heapsize)
+{
+    if ( !cfsnc_use )
+	    return 0;
+
+    if ((hashsize % 2) || (heapsize % 2)) { /* Illegal hash or cache sizes */
+	    return(EINVAL);
+    }                 
+    
+    cfsnc_use = 0;                       /* Turn the cache off */
+    cfsnc_force = 1;                     /* otherwise we can't flush */
+    
+    cfsnc_flush();                       /* free any cnodes in the cache */
+    cfsnc_force = 0;
+    
+    /* WARNING: free must happen *before* size is reset */
+    CODA_FREE(cfsncheap,TOTAL_CACHE_SIZE);
+    CODA_FREE(cfsnchash,TOTAL_HASH_SIZE);
+    
+    cfsnc_hashsize = hashsize;
+    cfsnc_size = heapsize;
+    
+    cfsnc_init();                        /* Set up a cache with the new size */
+    
+    cfsnc_use = 1;                       /* Turn the cache back on */
+    return(0);
+}
+
+
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov