#ifndef _BLURB_
#define _BLURB_
/*

            Coda: an Experimental Distributed File System
                             Release 3.1

          Copyright (c) 1987-1995 Carnegie Mellon University
                         All Rights Reserved

Permission  to  use, copy, modify and distribute this software and its
documentation is hereby granted,  provided  that  both  the  copyright
notice  and  this  permission  notice  appear  in  all  copies  of the
software, derivative works or  modified  versions,  and  any  portions
thereof, and that both notices appear in supporting documentation, and
that credit is given to Carnegie Mellon University  in  all  documents
and publicity pertaining to direct or indirect use of this code or its
derivatives.

CODA IS AN EXPERIMENTAL SOFTWARE SYSTEM AND IS  KNOWN  TO  HAVE  BUGS,
SOME  OF  WHICH MAY HAVE SERIOUS CONSEQUENCES.  CARNEGIE MELLON ALLOWS
FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION.   CARNEGIE  MELLON
DISCLAIMS  ANY  LIABILITY  OF  ANY  KIND  FOR  ANY  DAMAGES WHATSOEVER
RESULTING DIRECTLY OR INDIRECTLY FROM THE USE OF THIS SOFTWARE  OR  OF
ANY DERIVATIVE WORK.

Carnegie  Mellon  encourages  users  of  this  software  to return any
improvements or extensions that  they  make,  and  to  grant  Carnegie
Mellon the rights to redistribute these changes without encumbrance.
*/

static char *rcsid = "$Header: cfs_subr.c,v 3.4.2.1 95/10/11 10:03:33 raiff Exp $";
#endif /*_BLURB_*/

/* 
 * Mach Operating System
 * 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.
 */

/*
 * HISTORY
 * $Log:	cfs_subr.c,v $
 * Revision 3.4.2.1  95/10/11  10:03:33  raiff
 * Branch for release beta-11Oct1995_34901
 * 
 * Revision 3.4  95/10/09  19:25:00  satya
 * Reblurbed with new CMU and IBM notices for SOSP-15 CD-ROM
 * 
 * Revision 3.3  95/09/17  13:47:44  dcs
 * Changed some printfs to be more informative
 * 
 * Revision 3.2  95/07/27  15:44:10  lily
 * Removed cfsnc_replace, replaced it with a cfs_find, unhash, and
 * rehash.  This fixes a cnode leak and a bug in which the fid is
 * not actually replaced.  (cfs_namecache.c, cfsnc.h, cfs_subr.c)
 * 
 * Revision 3.1  95/06/08  16:03:21  satya
 * *** empty log message ***
 * 
 * Revision 3.2.3.1  95/05/11  11:13:52  raiff
 * Branch for release beta-11May1995_36561
 * 
 * Revision 3.2  95/03/07  00:32:55  dcs
 * cfscall was returning wrong error code if venus died.
 * 
 * Revision 3.1  95/03/04  19:07:58  bnoble
 * Bump to major revision 3 to prepare for NetBSD port
 * 
 * Revision 2.8  1995/03/03  17:00:04  dcs
 * Fixed kernel bug involving sleep and upcalls. Basically if you killed
 * a job waiting on venus, the venus upcall queues got trashed. Depending
 * on luck, you could kill the kernel or not.
 * (mods to cfs_subr.c and cfs_mach.d)
 *
 * Revision 2.7  95/03/02  22:45:21  dcs
 * Sun4 compatibility
 * 
 * Revision 2.6  95/02/17  16:25:17  dcs
 * These versions represent several changes:
 * 1. Allow venus to restart even if outstanding references exist.
 * 2. Have only one ctlvp per client, as opposed to one per mounted cfs device.d
 * 3. Allow ody_expand to return many members, not just one.
 * 
 * Revision 2.5  94/11/09  15:56:26  dcs
 * Had the thread sleeping on the wrong thing!
 * 
 * Revision 2.4  94/10/14  09:57:57  dcs
 * Made changes 'cause sun4s have braindead compilers
 * 
 * Revision 2.3  94/10/12  16:46:26  dcs
 * Cleaned kernel/venus interface by removing XDR junk, plus
 * so cleanup to allow this code to be more easily ported.
 * 
 * Revision 1.2  92/10/27  17:58:22  lily
 * merge kernel/latest and alpha/src/cfs
 * 
 * Revision 2.4  92/09/30  14:16:26  mja
 * 	Incorporated Dave Steere's fix for the GNU-Emacs bug.
 * 	Also, included his cfs_flush routine in place of the former cfsnc_flush.
 * 	[91/02/07            jjk]
 * 
 * 	Added contributors blurb.
 * 	[90/12/13            jjk]
 * 
 * 	Hack to allow users to keep coda venus calls uninterruptible. THis
 * 	basically prevents the Gnu-emacs bug from appearing, in which a call
 * 	was being interrupted, and return EINTR, but gnu didn't check for the
 * 	error and figured the file was buggered.
 * 	[90/12/09            dcs]
 * 
 * Revision 2.3  90/08/10  10:23:20  mrt
 * 	Removed include of vm/vm_page.h as it no longer exists.
 * 	[90/08/10            mrt]
 * 
 * Revision 2.2  90/07/05  11:26:35  mrt
 * 	Initialize name cache on first call to vcopen.
 * 	[90/05/23            dcs]
 * 
 * 	Created for the Coda File System.
 * 	[90/05/23            dcs]
 * 
 * Revision 1.5  90/05/31  17:01:35  dcs
 * Prepare for merge with facilities kernel.
 * 
 * Revision 1.2  90/03/19  15:56:25  dcs
 * Initialize name cache on first call to vcopen.
 * 
 * Revision 1.1  90/03/15  10:43:26  jjk
 * Initial revision
 * 
 */ 

/* @(#)cfs_subr.c	1.5 87/09/14 3.2/4.3CFSSRC */

#include "cfs.h"
#if	NMACH_CFS

#ifdef SUN4C
struct cnode *cfs_alloc();
struct cnode *cfs_find();
extern struct fs *igetfs();
#else
struct cnode *cfs_alloc(void);
struct cnode *cfs_find(ViceFid *fid);
extern struct fs *igetfs(dev_t);
#endif SUN4C

/* God this kills me. Isn't there a better way of going about this? - DCS*/
char pass_process_info;

/*
 * Statistics
 */
struct {
	int	ncalls;			/* client requests */
	int	nbadcalls;		/* upcall failures */
	int	reqs[CFS_NCALLS];	/* count of each request */
} cfs_clstat;


/* Key question: whether to sleep interuptably or uninteruptably when waiting for Venus.
/* The former seems better (cause you can ^C a job), but then GNU-EMACS completion
 * breaks. Use tsleep with no timeout, and no longjmp happens. But, when sleeping
 * "uninterruptibly", we don't get told if it returns abnormally (e.g. kill -9).
 */

/* If you want this to be interruptible, set this to > PZERO */
int cfscall_sleep = PZERO - 1;

int /* How do I hate compiling for sun4s, let me count the ways...*/
cfscall(mntinfo, inSize, outSize, buffer) 
     struct cfs_mntinfo *mntinfo; int inSize; int *outSize; caddr_t buffer;
{
	struct vcomm *vcp;
	struct vmsg vm;
	int error;

	if (mntinfo == NULL) {
	    /* Unlikely, but could be a race condition with a dying warden */
	    return ENODEV;
	}

	vcp = &(mntinfo->mi_vcomm);
	
	cfs_clstat.ncalls++;
	cfs_clstat.reqs[((struct inputArgs *)buffer)->opcode]++;

	if (!VC_OPEN(vcp))
	    return(ENODEV);

	/* Format the request message. */
	vm.vm_data = buffer;
	vm.vm_flags = 0;
	vm.vm_inSize = inSize;
	vm.vm_outSize = *outSize ? *outSize : inSize; /* |buffer| >= inSize */
	vm.vm_opcode = ((struct inputArgs *)buffer)->opcode;
	vm.vm_unique = ++vcp->vc_seq;
	if (cfsdebug)
	    printf("Doing a call for %d.%d\n", vm.vm_opcode, vm.vm_unique);
	
	/* Fill in the common input args. */
	((struct inputArgs *)buffer)->unique = vm.vm_unique;

	/* Append msg to request queue and poke Venus. */
	INSQUE(vm.vm_chain, vcp->vc_requests);
	SELWAKEUP(vcp->vc_selproc);

	/* We can be interrupted while we wait for Venus to process our request.
	 * If the interrupt occurs before Venus has read the request, we dequeue
	 * and return. If it occurs after the read but before the reply, we
	 * dequeue, send a signal message, and return. If it occurs after
	 * the reply we ignore it. In no case do we want to restart the syscall.
	 * If it was interrupted by a venus shutdown (vcclose), return ENODEV.
	 */

	SLEEP(&vm.vm_sleep, (cfscall_sleep));	/* Ignore return, We have to check anyway */

	if (VC_OPEN(vcp)) {	/* Venus is still alive */
	    if (vm.vm_flags & VM_WRITE) { 	/* Op went through, interrupt or not... */
		error = 0;
		*outSize = vm.vm_outSize;
	    }

	    else if (!(vm.vm_flags & VM_READ)) {  /* Interrupted before venus read it. */
		if (cfsdebug)
		    printf("interrupted before read: intr = %x, %x, op = %d.%d, flags = %x\n",
			   u.u_procp->p_sig, u.u_procp->p_cursig,
			   vm.vm_opcode, vm.vm_unique, vm.vm_flags);
		REMQUE(vm.vm_chain);
		error = EINTR;
	    }
	    
	    else { 	/* (!(vm.vm_flags & VM_WRITE)) means interrupted after upcall started */
		/* Interrupted after start of upcall, send venus a signal */
		struct inputArgs *dog;
		struct vmsg *svmp;
		
		if (cfsdebug)
		    printf("Sending Venus a signal: intr = %x, %x, op = %d.%d, flags = %x\n",
			   u.u_procp->p_sig, u.u_procp->p_cursig,
			   vm.vm_opcode, vm.vm_unique, vm.vm_flags);
		
		REMQUE(vm.vm_chain);
		error = EINTR;
		
		svmp = (struct vmsg *)kalloc((u_int)sizeof(struct vmsg));
		
		svmp->vm_data = (char *)kalloc(VC_IN_NO_DATA);
		dog = (struct inputArgs *)svmp->vm_data;
		
		svmp->vm_flags = 0;
		dog->opcode = svmp->vm_opcode = CFS_SIGNAL;
		dog->unique = svmp->vm_unique = vm.vm_unique;
		svmp->vm_inSize = VC_IN_NO_DATA;
		svmp->vm_outSize = VC_IN_NO_DATA;
		
		if (cfsdebug)
		    printf("cfscall: enqueing signal msg (%d, %d)\n",
			   svmp->vm_opcode, svmp->vm_unique);
		
		INSQUE(svmp->vm_chain, vcp->vc_requests);   /* insert at head of queue! */
		SELWAKEUP(vcp->vc_selproc);
	    }
	}

	else {	/* If venus died (!VC_OPEN(vcp)) */
	    if (cfsdebug)
		printf("vcclose woke op %d.%d flags %d\n",
		       vm.vm_opcode, vm.vm_unique, vm.vm_flags);
	    
	    if (!(vm.vm_flags & VM_WRITE))
		error = ENODEV;
	}

	return(error);
}


#define ODD(vnode)        ((vnode) & 0x1)

/*
 * There are 6 cases where invalidations occur. The semantics of each is listed here.
 * CFS_FLUSH     -- flush all entries from the name cache and the cnode cache.
 * CFS_PURGEUSER -- flush all entries from the name cache for a specific user
 *                  This call is a result of token expiration.
 *
 * The next two are the result of callbacks on a file or directory.
 * CFS_ZAPDIR    -- flush the attributes for the dir from its cnode.
 *                  Zap all children of this directory from the namecache.
 * CFS_ZAPFILE   -- flush the attributes for a file.
 *
 * The fifth is a result of Venus detecting an inconsistent file.
 * CFS_PURGEFID  -- flush the attribute for the file
 *                  If it is a dir (odd vnode), purge its children from the namecache
 *                  remove the file from the namecache.
 * The sixth allows Venus to replace local fids with global ones during reintegration.
 * CFS_REPLACE	 -- replace one ViceFid with another throughout the name cache
 */

int handleDownCall(opcode, out) /* How do I hate compiling for sun4s, let me count the ways...*/
     int opcode; struct outputArgs *out;
{
    int error;
    
    /* Handle invalidate requests. */
    switch (opcode) {
      case CFS_FLUSH : {
	  cfs_flush();
	  
	  CFSDEBUG(CFS_FLUSH,cfs_testflush();)    /* print remaining cnodes */
	      return(0);
      }
	
      case CFS_PURGEUSER : {
	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_PURGEUSER]++;
	  
	  cfsnc_purge_user(&out->d.cfs_purgeuser.cred);
	  return(0);
      }
	
      case CFS_ZAPFILE : {
	  struct cnode *cp;
	  int error = 0;
	  
	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_ZAPFILE]++;
	  
	  cp = cfs_find(&out->d.cfs_zapfile.CodaFid);
	  if (cp != NULL) {
	      VN_HOLD(CTOV(cp));
	      
	      cp->c_flags &= ~C_VATTR;
	      if (CTOV(cp)->v_flag & VTEXT)
		  error = cfs_vmflush(cp);
	      
	      CFSDEBUG(CFS_ZAPFILE, printf("zapfile: fid = (%x.%x.%x), refcnt = %d, error = %d\n",cp->c_fid.Volume, cp->c_fid.Vnode, cp->c_fid.Unique, CTOV(cp)->v_count - 1, error);)
		  
		  VN_RELE(CTOV(cp));
	  }
	  
	  return(error);
      }
	
      case CFS_ZAPDIR : {
	  struct cnode *cp;
	  
	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_ZAPDIR]++;
	  
	  cp = cfs_find(&out->d.cfs_zapdir.CodaFid);
	  if (cp != NULL) {
	      VN_HOLD(CTOV(cp));
	      
	      cp->c_flags &= ~C_VATTR;
	      cfsnc_zapParentfid(&out->d.cfs_zapdir.CodaFid);     
	      
	      CFSDEBUG(CFS_ZAPDIR, printf("zapdir: fid = (%x.%x.%x), refcnt = %d\n",cp->c_fid.Volume, cp->c_fid.Vnode, cp->c_fid.Unique, CTOV(cp)->v_count - 1);)
		  
		  VN_RELE(CTOV(cp));
	  }
	  
	  return(0);
      }
	
      case CFS_ZAPVNODE : {
	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_ZAPVNODE]++;
	  
	  cfsnc_zapvnode(&out->d.cfs_zapvnode.VFid, &out->d.cfs_zapvnode.cred);
	  return(0);
      }	
	
      case CFS_PURGEFID : {
	  struct cnode *cp;
	  int error = 0;
	  
	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_PURGEFID]++;
	  
	  cp = cfs_find(&out->d.cfs_purgefid.CodaFid);
	  if (cp != NULL) {
	      VN_HOLD(CTOV(cp));
	      
	      if (ODD(out->d.cfs_purgefid.CodaFid.Vnode)) { /* Vnode is a directory */
		  cfsnc_zapParentfid(&out->d.cfs_purgefid.CodaFid);     
	      }
	      
	      cp->c_flags &= ~C_VATTR;
	      cfsnc_zapfid(&out->d.cfs_purgefid.CodaFid);
	      if (!(ODD(out->d.cfs_purgefid.CodaFid.Vnode)) && (CTOV(cp)->v_flag & VTEXT))
		  
		  error = cfs_vmflush(cp);
	      
	      CFSDEBUG(CFS_PURGEFID, printf("purgefid: fid = (%x.%x.%x), refcnt = %d, error = %d\n",cp->c_fid.Volume, cp->c_fid.Vnode, cp->c_fid.Unique, CTOV(cp)->v_count - 1, error);)
		  
		  VN_RELE(CTOV(cp));
	  }
	  return(error);
      }

      case CFS_REPLACE : {
	  struct cnode *cp = NULL;

	  cfs_clstat.ncalls++;
	  cfs_clstat.reqs[CFS_REPLACE]++;
	  
	  cp = cfs_find(&out->d.cfs_replace.OldFid);
	  if (cp != NULL) { 
	      /* remove the cnode from the hash table, replace the fid, and reinsert */
	      VN_HOLD(CTOV(cp));
	      cfs_unsave(cp);
	      cp->c_fid = out->d.cfs_replace.NewFid;
	      cfs_save(cp);

	      CFSDEBUG(CFS_REPLACE, printf("replace: oldfid = (%x.%x.%x), newfid = (%x.%x.%x), cp = 0x%x\n",
					   out->d.cfs_replace.OldFid.Volume,
					   out->d.cfs_replace.OldFid.Vnode,
					   out->d.cfs_replace.OldFid.Unique,
					   cp->c_fid.Volume, cp->c_fid.Vnode, 
					   cp->c_fid.Unique, cp);)
	      VN_RELE(CTOV(cp));
	  }
	  return (0);
      }			   
    }
}

struct cnode *cfs_freelist = NULL;
int cfs_reuse = 0;
int cfs_new = 0;
int cfs_active = 0;

/*
 * Return a vnode for the given fid.
 * If no cnode exists for this fid create one and put it
 * in a table hashed by fid.Volume and fid.Vnode.  If the cnode for
 * this fid is already in the table return it (ref count is
 * incremented by cfs_find.  The cnode will be flushed from the
 * table when cfs_inactive calls cfs_unsave.
 */
struct vnode *
makecfsnode(fid, vfsp, type) /* How do I hate compiling for sun4s, let me count the ways...*/
     ViceFid *fid; struct vfs *vfsp; short type;
{
	struct cnode *cp;

	if ((cp = cfs_find(fid)) == NULL) {
	    struct vnode *vp;
	    struct vm_info *vitmp;

	    cp = cfs_alloc();
	    cp->c_fid = *fid;

	    vp = CTOV(cp);
	    vitmp = vp->v_vm_info;
	    VN_INIT(vp, vfsp, type, 0);
	    vp->v_vm_info = vitmp;
	    vp->v_vm_info->pager = MEMORY_OBJECT_NULL;
	    vp->v_type = ITYPE_CFS;

	    cfs_save(cp);

	    /* Otherwise vfsp is 0 */
	    if (!IS_CTL_FID(fid))
		((struct cfs_mntinfo *)(vfsp->vfs_data))->mi_refct++;
	} else {
	    VN_HOLD(CTOV(cp));
	}

        return(CTOV(cp));
}

/*
 * Cnode lookup stuff.
 * These routines maintain a table of cnodes hashed by fid so
 * that the cnode for a fid can be found if it already exists.
 * NOTE: CFS_CACHESIZE must be a power of 2 for cfshash to work!
 */

#define CFS_CACHESIZE 512
struct cnode *cfs_cache[CFS_CACHESIZE];

#define cfshash(fid) \
    (((fid)->Volume + (fid)->Vnode) & (CFS_CACHESIZE-1))

/*
 * cfs_kill is called as a side effect to vcopen. To prevent any cnodes left around
 * from an earlier run of a venus or warden from causing problems with the new
 * instance, mark any outstanding cnodes as dying. Future operations on these cnodes
 * should fail (excepting cfs_inactive of course!). Since multiple venii/wardens can
 * be running, only kill the cnodes for a particular entry in the cfs_mnttbl. -- DCS 12/1/94
 */

cfs_kill(whoIam)
     struct vfs *whoIam;
{
    int hash, count = 0;
    struct cnode *cp;

    /* This is slightly overkill, but should work. Eventually it'd be nice to only flush
     * those entries from the namecache that reference a vnode in this vfs.
     */
    cfsnc_flush();
    
    for (hash = 0; hash < CFS_CACHESIZE; hash++) {
	for (cp = cfs_cache[hash]; cp != NULL; cp = CNODE_NEXT(cp)) {
	    if (CTOV(cp)->v_vfsp == whoIam) {
		count++;
		cp->c_flags |= C_DYING;
		CFSDEBUG(CFS_FLUSH, printf("kill: live cnode fid %x.%x.%x count %d flags %d\n",
					   (cp->c_fid).Volume,(cp->c_fid).Vnode,(cp->c_fid).Unique,
					   CTOV(cp)->v_count, cp->c_flags); );
	    }
	}
    }
    return count;
}

/*
 * There are two reasons why a cnode may be in use, it may be in the name cache
 * or it may be executing.
 */
cfs_flush()
{
    int hash;
    struct cnode *cp;
    
    cfs_clstat.ncalls++;
    cfs_clstat.reqs[CFS_FLUSH]++;
    
    cfsnc_flush();			    /* flush files from the name cache */

    for (hash = 0; hash < CFS_CACHESIZE; hash++) {
	for (cp = cfs_cache[hash]; cp != NULL; cp = CNODE_NEXT(cp)) {  
	    if (!ODD(cp->c_fid.Vnode)) /* only files can be executed */
		cfs_vmflush(cp);
	}
    }
}

/*
 * As a debugging measure, print out any cnodes that lived through a name cache flush.
 */
cfs_testflush()
{
        int hash;
	struct cnode *cp;

	for (hash = 0; hash < CFS_CACHESIZE; hash++) {
	  for (cp = cfs_cache[hash];
	       cp != NULL;
	       cp = CNODE_NEXT(cp)) {  
	    printf("Testflush: live cnode fid %x.%x.%x count %d\n",
		   (cp->c_fid).Volume,(cp->c_fid).Vnode,(cp->c_fid).Unique, CTOV(cp)->v_count);
	  }
	}
}

/*
 * Put a cnode in the hash table
 */
cfs_save(cp) /* How do I hate compiling for sun4s, let me count the ways...*/
     struct cnode *cp;
{
	CTOV(cp)->v_next = CTOV(cfs_cache[cfshash(&cp->c_fid)]);
	cfs_cache[cfshash(&cp->c_fid)] = cp;
}

/*
 * Remove a cnode from the hash table
 */
cfs_unsave(cp) /* How do I hate compiling for sun4s, let me count the ways...*/
     struct cnode *cp;
{
	struct cnode *ptr;
	struct cnode *ptrprev = NULL;
	 
	ptr = cfs_cache[cfshash(&cp->c_fid)]; 
	while (ptr != NULL) { 
		if (ptr == cp) { 
			if (ptrprev == NULL) {
				cfs_cache[cfshash(&cp->c_fid)] = CNODE_NEXT(ptr);
			} else {
				CTOV(ptrprev)->v_next = CTOV(CNODE_NEXT(ptr));
			}
			CTOV(cp)->v_next = (struct vnode *)NULL;
			
			return; 
		}	
		ptrprev = ptr;
		ptr = CNODE_NEXT(ptr);
	}	
}

/*
 * Allocate a cnode.
 */
struct cnode *
cfs_alloc()
{
    struct cnode *cp;
    struct vm_info *vitmp;

    if (cfs_freelist) {
	cp = cfs_freelist;
	cfs_freelist = CNODE_NEXT(cp);
	cfs_reuse++;
    }
    else {
	cp = (struct cnode *)kalloc((u_int)sizeof(struct cnode));
	CTOV(cp)->v_vm_info = VM_INFO_NULL;
	vm_info_init(CTOV(cp));
	cfs_new++;
    }
    vitmp = CTOV(cp)->v_vm_info;
    CN_INIT(cp);
    CTOV(cp)->v_vm_info = vitmp;

    return(cp);
}

/*
 * Deallocate a cnode.
 */
cfs_free(cp) /* How do I hate compiling for sun4s, let me count the ways...*/
     register struct cnode *cp;
{
	CTOV(cp)->v_next = CTOV(cfs_freelist);
	cfs_freelist = cp;
}

/*
 * Lookup a cnode by fid. If the cnode is dying, it is bogus so skip it.
 * NOTE: this allows multiple cnodes with same fid -- dcs 1/25/95
 */
struct cnode *
cfs_find(fid) 
     ViceFid *fid;
{
    struct cnode *cp;

    cp = cfs_cache[cfshash(fid)];
    while (cp) {
	if ((cp->c_fid.Vnode == fid->Vnode) &&
	    (cp->c_fid.Volume == fid->Volume) &&
	    (cp->c_fid.Unique == fid->Unique) &&
	    (!IS_DYING(cp)))
	    {
		cfs_active++;
		return(cp); 
	    }		    
	cp = CNODE_NEXT(cp);
    }
    return(NULL);
}

cfs_iget(dev, ino, ipp) /* How do I hate compiling for sun4s, let me count the ways...*/
	 dev_t dev; ino_t ino; struct inode **ipp;
{
    /* This is like VFS_VGET() or igetinode()! */
    struct fs *fs = igetfs(dev);
    if (fs == NULL) {
	printf("cfs_iget: igetfs(%d) returns NULL\n", dev);
	return(ENXIO);
    }
    *ipp = iget(dev, fs, ino);
    if (*ipp == NULL) {
	printf("cfs_iget: iget(%d, %x, %d) returns NULL\n", dev, fs, ino);
	return(ENOENT);
    }
    return(0);
}

cfs_vmflush(cp) /* How do I hate compiling for sun4s, let me count the ways...*/
     struct cnode *cp;
{
    /* Unset <device, inode> so that page_read doesn't try to use (possibly) invalid cache file. */
    cp->c_device = 0;
    cp->c_inode = 0;

    return(inode_uncache_try(VTOI(CTOV(cp))) ? 0 : ETXTBSY);
}

/*
 * Utilities used by both client and server
 * Standard levels:
 * 0) no debugging
 * 1) hard failures
 * 2) soft failures
 * 3) current test software
 * 4) main procedure entry points
 * 5) main procedure exit points
 * 6) utility procedure entry points
 * 7) utility procedure exit points
 * 8) obscure procedure entry points
 * 9) obscure procedure exit points
 * 10) random stuff
 * 11) all <= 1
 * 12) all <= 2
 * 13) all <= 3
 * ...
 */

print_vattr( attr )
	struct vattr *attr;
{
	printf("getattr: mode %d uid %d gid %d fsid %d rdev %d\n",
		(int)attr->va_mode, (int)attr->va_uid,
		(int)attr->va_gid, (int)attr->va_fsid, (int)attr->va_rdev);

	printf("	nodeid %d nlink %d size %d blocksize %d blocks %d\n",
		(int)attr->va_nodeid, (int)attr->va_nlink, (int)attr->va_size,
		(int)attr->va_blocksize,(int)attr->va_blocks);

	printf("	atime sec %d usec %d",
		(int)attr->va_atime.tv_sec, (int)attr->va_atime.tv_usec);
	printf("	mtime sec %d usec %d",
		(int)attr->va_mtime.tv_sec, (int)attr->va_mtime.tv_usec);
	printf("	ctime sec %d usec %d\n",
		(int)attr->va_ctime.tv_sec, (int)attr->va_ctime.tv_usec);
}

#endif	/* NMACH_CFS */
