/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:cache.c 12.3$ */
/* $ACIS:cache.c 12.3$ */
/* $Source: /ibm/acis/usr/sys/afs/RCS/cache.c,v $ */

#ifndef lint
static char *rcsid = "$Header:cache.c 12.3$";
#endif

#include "../h/types.h"
#include "../h/param.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../h/socket.h"
#include "../h/socketvar.h"
#include "../h/protosw.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/file.h"
#include "../h/uio.h"
#include "../h/vfs.h"
#include "../h/vnode.h"
#include "../ufs/inode.h"
#include "../netinet/in.h"
#include "../h/mbuf.h"
#include "../rpc/types.h"
#include "../rpc/xdr.h"
#include "../h/stat.h"

#include "../afs/osi.h"
#define RFTP_INTERNALS 1
#include "../afs/r.h"
#include "../afs/rftp.h"

#include "../afs/lock.h"
#include "../afs/volerrors.h"
#include "../afsint/rvice.h"
#include "../afsint/rvaux.h"
#include "../afs/afs.h"

extern char *afs_GetMariner();
extern struct vfs *afs_globalVFS;
extern struct rftp_server *afs_rftpServer;	/* rftp server for afs */

/* convenient release macro for use when afs_PutDCache would cause deadlock on afs_xdcache lock */
#define	lockedPutDCache(ad) ((ad)->refCount--)

struct osi_dev cacheDev;		    /* cache device */ 
struct lock afs_xvcache;		    /* lock: alloc new stat cache entries */
long afs_mariner = 0;
struct lock afs_xdcache;		    /* lock: alloc new disk cache entries */
struct lock afs_xvcb;
static long cacheCounter=0;		    /* number of disk cache entries */
static long rftpSession = 1;		    /* rftp session counter */
static long cacheInfoModTime=0;
struct vcache *freeVCList = 0;		    /* free list for stat cache entries */
short freeDCList = NULLIDX;		    /* free list for disk cache entries */
long  freeDCCount = 0;			    /* count of elts in freeDCList */
struct dcache *freeDSList = 0;		    /* free list for disk slots */
long cacheInode;
long volumeInode;
struct afs_q VLRU, DLRU;

short afs_dvhashTable[DVHASHSIZE];	/*Data cache hash table*/
short afs_dchashTable[DCHASHSIZE];	/* Data cache hash table*/
struct dcache **afs_indexTable;		/*pointers to dcache entries */
long *afs_indexTimes;			/*Dcache entry Access times */
char *afs_indexFlags;			/* only one: is there data there? */
long afs_indexCounter=0;		/* fake time for marking index entries */
long afs_cacheFiles;			/* size of afs_indexTable */
long afs_cacheBlocks;			/* 1K blocks in cache */
long afs_origCacheBlocks;		/* from boot */
long afs_cacheStats;			/* stat entries in cache */
struct vcache *afs_vhashTable[VCSIZE];	/* stat cache hash table */
static struct vcache *afs_vcache_array;
static struct dcache *afs_dcache_array;
long afs_blocksUsed;			/* number of blocks in use */
long afs_reusedFiles = 0;		/* number of files reused */

/* Initialization order is important.  Must first call afs_CacheInit.
  Next must call cache file and volume file initialization routines.
  Next must call individual cache entry initialization routines.
  
  In this routine, astatSize is the number of stat cache (vnode) entries to allocate.
  afiles is the number of disk files to allocate to the cache
  ablocks is the max number of 1024 byte units that all of the files in the cache may occupy
  
  This routine should only be called at initialization time, since it reclaims no resources
  and doesn't sufficiently synchronize with other processes.
*/

#define	DDSIZE	    100		/* max # of struct dcache's resident at any time */

cache_cleanup() {
    bzero(&cacheDev, sizeof(cacheDev));
    bzero(&afs_xvcache,sizeof(afs_xvcache));
    afs_mariner = 0;
    bzero(&afs_xdcache,sizeof(afs_xdcache));
    bzero(&afs_xvcb, sizeof(afs_xvcb));
    cacheCounter=0;
    rftpSession = 1;
    cacheInfoModTime=0;
    freeVCList = 0;
    freeDCList = NULLIDX;
    freeDCCount = 0;
    freeDSList = 0;
    long cacheInode=0;
    long volumeInode=0;
    bzero(&VLRU, sizeof(VLRU));
    bzero(&DLRU, sizeof(DLRU));
    bzero(afs_dvhashTable, sizeof(afs_dvhashTable));
    bzero(afs_dchashTable, sizeof(afs_dchashTable));
    afs_indexTable=0;
    afs_indexTimes=0;
    afs_indexFlags=0;
    afs_indexCounter=0;
    afs_cacheFiles=0;
    afs_cacheBlocks=0;
    afs_origCacheBlocks=0;
    afs_cacheStats=0;
    bzero(afs_vhashTable, sizeof(afs_vhashTable));
    afs_vcache_array = 0;
    afs_dcache_array = 0;
    afs_blocksUsed=0;
    long afs_reusedFiles = 0;
}

afs_CheckSize(aextra)
    register long aextra; {
    register long counter;
    counter = 0;

    if (afs_cacheBlocks > afs_blocksUsed + aextra) return 0;
    aextra += 300; /*  do a bunch extra */
    while (afs_cacheBlocks <= afs_blocksUsed + aextra) {
	/* we're using more than we should */
	ObtainWriteLock(&afs_xdcache);
	afs_GetDownD(5,	1);	/* need space */
	ReleaseWriteLock(&afs_xdcache);
	if (++counter >	100) break;	/* safety? */
    }
    return 0;
}

/* make adjustment for the new size in the disk cache entry */
afs_AdjustSize(adc, anewSize)
    register struct dcache *adc;
    register long anewSize; {
    register long oldSize;

    oldSize = adc->f.chunkBytes;
    adc->f.chunkBytes = anewSize;
    adc->f.states |= DEntryMod;
    oldSize = ((oldSize-1)|1023)+1;	    /* round up both sizes */
    anewSize = ((anewSize-1)|1023)+1;
    afs_blocksUsed += ((anewSize - oldSize) >> 10);
}

afs_FreeCache()
	{
	register long i;

	/* first free the hash chains */
	for (i=0; i < VCSIZE; i++) {
		afs_vhashTable[i] = 0;
	}
	/* free the memory */
	osi_Free(afs_vcache_array,afs_cacheStats * sizeof(struct vcache));
	afs_vcache_array = 0;
	osi_Free(afs_dcache_array,DDSIZE * sizeof(struct dcache));
	afs_dcache_array = 0;
	osi_Free(afs_indexTable, afs_cacheFiles * sizeof(struct dcache *));
	afs_indexTable = 0;
	osi_Free(afs_indexTimes, afs_cacheFiles * sizeof(long));
	afs_indexTimes = 0;
	osi_Free(afs_indexFlags, afs_cacheFiles * sizeof(char));
	afs_indexFlags = 0;

	afs_cacheFiles = 0;
	afs_cacheStats = 0;
	afs_origCacheBlocks = afs_cacheBlocks = 0;
	afs_blocksUsed = 0;
}

afs_CacheInit(astatSize, afiles, ablocks)
    long afiles;
    long astatSize, ablocks; {
    register struct vcache *tvp;
    register struct dcache *tdp;
    register long i;

    printf("Starting afs cache scan...");
    Lock_Init(&afs_xvcache);
    Lock_Init(&afs_xdcache);
    Lock_Init(&afs_xvcb);

    /* initialize hash tables */
    for(i=0;i<DVHASHSIZE;i++) afs_dvhashTable[i] = NULLIDX;
    for(i=0;i<DCHASHSIZE;i++) afs_dchashTable[i] = NULLIDX;

    /* Allocate and thread the struct vcache entries */
    tvp = (struct vcache *) osi_Alloc(astatSize * sizeof(struct vcache));
    afs_vcache_array=freeVCList = (struct vcache *) &(tvp[0]);
    for(i=0; i < astatSize-1; i++)
	tvp[i].lruq.next = (struct afs_q *) (&tvp[i+1]);
    tvp[astatSize-1].lruq.next = (struct afs_q *) 0;

    /* Allocate and zero the pointer array to the dcache entries */
    afs_indexTable = (struct dcache **)
      osi_Alloc(sizeof(struct dcache *) * afiles);
    bzero(afs_indexTable, sizeof(struct dcache *) * afiles);
    afs_indexTimes = (long *) osi_Alloc(afiles * sizeof(long));
    bzero(afs_indexTimes, afiles * sizeof(long));
    afs_indexFlags = (char *) osi_Alloc(afiles * sizeof(char));
    bzero(afs_indexFlags, afiles * sizeof(char));

    /* Allocate and thread the struct dcache entries themselves */
    tdp = (struct dcache *) osi_Alloc(DDSIZE * sizeof(struct dcache));
    bzero(tdp, DDSIZE * sizeof(struct dcache));
    afs_dcache_array = freeDSList = &tdp[0];
    for(i=0; i < DDSIZE-1; i++)
	tdp[i].lruq.next = (struct afs_q *) (&tdp[i+1]);
    tdp[DDSIZE-1].lruq.next = (struct afs_q *) 0;
    afs_cacheFiles = afiles;
    afs_cacheStats = astatSize;
    afs_origCacheBlocks = afs_cacheBlocks = ablocks;
    afs_blocksUsed = 0;
    QInit(&VLRU);
    QInit(&DLRU);
    return 0;
}

/* This routine is responsible for moving at least one, (up to anumber) entries from the LRU queue
      to the free queue.  Anumber is just a hint, but this routine must (if possible) move at least
      one entry, or its caller will panic.
      
      This routine must be called with afs_xdcache write-locked.
*/
#define	MAXATONCE   8		/* max we can obtain at once */
afs_GetDownD(anumber, aneedSpace)
    int	aneedSpace;		/* true if we really need space, not slots */
    int anumber; {
    register struct dcache *tdc;
    register long i, j;
    long vtime;
    short victims[MAXATONCE];
    long victimTimes[MAXATONCE];    /* youngest (largest LRU time) first */
    long maxVictim;		    /* youngest (largest LRU time) victim */
    short victimPtr;		    /* next free item in victim arrays */

    if (CheckLock(&afs_xdcache) != -1) panic("getdownd nolock");
    afs_dp("new call to getdownd (%d) needspace %d\n", anumber, aneedSpace);
    /* bounds check parameter */
    if (anumber	> MAXATONCE) anumber = MAXATONCE;   /* all we can do */

    /* decrement anumber first for all dudes in free list */
    if (!aneedSpace) {
	anumber -= freeDCCount;
	if (anumber <= 0) return;	/* enough already free */
    }

    /* find oldest entries for reclamation */
    victimPtr = 0;
    maxVictim = 0;
    /* select victims from access time array */
    for(i=0;i < afs_cacheFiles; i++) {
	/* check to see if data not yet written, or if already in free list */
	if (afs_indexFlags[i] &	(IFDataMod | IFFree)) continue;
	tdc = afs_indexTable[i];
	vtime = afs_indexTimes[i];
	if (aneedSpace) {
	    /* if we need data, we may have whole cache in active region */
	    if (tdc) {
		/* active guy, but may be necessary to use */
		if (tdc->refCount != 0)	continue;   /* can not use this one */
		vtime =	afs_indexCounter+1;	    /* try not to use this guy */
	    }
	    else {
		/* only on disk, don't bother with guys sans data */
		if ((afs_indexFlags[i] & IFEverUsed) == 0) continue;
	    }
	}
	else {
	    /* if we only need slots, we need not consider the active files at all */
	    if (tdc) continue;	/* don't reclaim ones in use */
	}
	if (victimPtr < anumber) {
	    /* if there's at least one free victim slot left */
	    victims[victimPtr] = i;
	    victimTimes[victimPtr] = vtime;
	    if (vtime > maxVictim) maxVictim = vtime;
	    victimPtr++;
	}
	else if (vtime < maxVictim) {
	    /* we're older than youngest victim, so we replace at least one victim */
	    /* find youngest (largest LRU) victim */
	    for(j=0;j<anumber;j++) if (victimTimes[j] == maxVictim) break;
	    if (j == anumber) panic("getdownd local");
	    victims[j] = i;
	    victimTimes[j] = vtime;
	    /* recompute maxVictim */
	    maxVictim = 0;
	    for(j=0;j < victimPtr; j++) if (maxVictim < victimTimes[j])
		maxVictim = victimTimes[j];
	}
    }

    /* now really reclaim the victims */
    for(i=0;i<victimPtr;i++) {
	tdc = afs_GetDSlot(victims[i], 0);	    /* q is first elt in dcache entry */
	afs_FlushDCache(tdc);		/* flush this dude from the data cache */
	tdc->refCount--;		/* put it back */
	anumber--;
    }
    return;
}

/* this routine must be called with the afs_xdcache lock held (in write mode) */
afs_FlushDCache(adc)
    register struct dcache *adc; {
    register struct dcache *udc;
    register long i;
    register short us;
    struct osi_file *tfile;
    struct dcache tmpdc;

    /* we know this guy's in the LRUQ.  We'll move dude into DCQ below */
    afs_dp("flushing %x (slot %d) bytes: %d\n", adc, adc->index, adc->f.chunkBytes);

    DZap(&adc->f.inode);
    /* if this guy is in the hash table, pull him out */
    if (adc->f.fid.Fid.Volume != 0) {
	/* remove entry from first hash chains */
	i = DCHash(&adc->f.fid, adc->f.chunk);
	us = afs_dchashTable[i];
	if (us == adc->index) {
	    /* first dude in the list */
	    afs_dchashTable[i] = adc->f.hcNextp;
	}
	else {
	    /* somewhere on the chain */
	    while (us != NULLIDX) {
		/*
		 * Supply a temporary dcache structure to use in looking up the
		 * next slot in case it's not already in memory- we're here because
		     * there's a shortage of them! */
		udc = afs_GetDSlot(us, &tmpdc);
		if (udc->f.hcNextp == adc->index) {
		    /* found item pointing at the one to delete */
		    udc->f.hcNextp = adc->f.hcNextp;
		    afs_WriteDCache(udc, 1);
		    lockedPutDCache(udc); /* fix refCount*/
		    break;
		}
		us = udc->f.hcNextp;
		lockedPutDCache(udc);
	    }
	    if (us == NULLIDX) panic("dcache hc");
	}

	/* remove entry from *other* hash chain */
	i = DVHash(&adc->f.fid);
	us = afs_dvhashTable[i];
	if (us == adc->index) {
	    /* first dude in the list */
	    afs_dvhashTable[i] = adc->f.hvNextp;
	}
	else {
	    /* somewhere on the chain */
	    while (us != NULLIDX) {
		/*
		 * Same as above: don't ask the slot lookup to grab an in-memory
		     * dcache structure - we can't spare one. */
		udc = afs_GetDSlot(us, &tmpdc);
		if (udc->f.hvNextp == adc->index) {
		    /* found item pointing at the one to delete */
		    udc->f.hvNextp = adc->f.hvNextp;
		    afs_WriteDCache(udc, 1);
		    lockedPutDCache(udc); /* fix refCount */
		    break;
		}
		us = udc->f.hvNextp;
		lockedPutDCache(udc);
	    }
	    if (us == NULLIDX) panic("dcache hv");
	}
    }

    /* format the entry to look like it has no associated file any more */
    adc->f.fid.Fid.Volume = 0;	/* invalid */
    
    /* free its space */
    tfile = osi_UFSOpen(&cacheDev, adc->f.inode);
    if (!tfile) panic("flushdcache truncate");
    osi_Truncate(tfile, 0);
    afs_AdjustSize(adc,	0);	/* fix up size */
    osi_Close(tfile);

    /* finally put the entry in the free list */
    adc->f.hvNextp = freeDCList;
    freeDCList = adc->index;
    freeDCCount++;
    /* dude will be used by next time appears in afs_indexTable */
    afs_indexFlags[adc->index] |= (IFFree|IFEverUsed);
    adc->f.states |= DEntryMod;
}

afs_GetDownDSlot(anumber)
    int anumber; {
    register struct afs_q *tq, *nq;
    register struct dcache *tdc;
    register long ix;

    if (CheckLock(&afs_xdcache) != -1) panic("getdowndslot nolock");
    /* decrement anumber first for all dudes in free list */
    for(tdc = freeDSList; tdc; tdc = (struct dcache *)tdc->lruq.next) anumber--;
    if (anumber	<= 0) return;	/* enough already free */

    for(tq = DLRU.prev; tq != &DLRU && anumber > 0; tq = nq) {
	tdc = (struct dcache *)	tq;	/* q is first elt in dcache entry */
	nq = QPrev(tq);	/* in case we remove it */
	if (tdc->refCount == 0) {
	    if ((ix=tdc->index) == NULLIDX) panic("getdowndslot");

	    /* pull the entry out of the lruq and put it on the free list */
	    QRemove(&tdc->lruq);

	    /* write-through if modified */
	    if (tdc->f.states & DEntryMod) {
		tdc->f.states &= ~DEntryMod;
		afs_WriteDCache(tdc, 1);
	    }

	    /* finally put the entry in the free list */
	    afs_indexTable[ix] = (struct dcache *) 0;
	    afs_indexTimes[ix] = afs_indexCounter++;
	    tdc->index = NULLIDX;
	    tdc->lruq.next = (struct afs_q *) freeDSList;
	    freeDSList = tdc;
	    anumber--;
	}
    }
}

/* This routine is responsible for moving at least one, (up to anumber) entries from the LRU queue
      to the free queue.  Anumber is just a hint, but this routine must (if possible) move at least
      one entry, or its caller will panic.
      
      This routine must be called with afs_xvcache write-locked.
*/
afs_GetDownV(anumber)
    int anumber; {
    register struct afs_q *tq;
    struct afs_q *uq;
    register struct vcache *tvc;

    if (CheckLock(&afs_xvcache) != -1) panic("getdownv lock");
    /* decrement anumber first for all dudes in free list */
    for(tvc = freeVCList; tvc; tvc = (struct vcache *) (tvc->lruq.next)) anumber--;
    if (anumber	<= 0) return;	/* enough already free */

    for(tq = VLRU.prev; tq != &VLRU && anumber > 0; tq = uq) {
	tvc = QTOV(tq);
	uq = QPrev(tq);
	if (tvc->vrefCount == 0) {
	    afs_FlushVCache(tvc);
	    anumber--;

	}
    }
    return;
}

/* this routine must be called with the afs_xvcache lock held for writing */
afs_FlushVCache(avc)
    register struct vcache *avc; {
    register long i;
    register struct vcache **uvc, *wvc;

    if (avc->vrefCount != 0) return EBUSY;
    /* pull the entry out of the lruq and put it on the free list */
    QRemove(&avc->lruq);
    /* remove entry from the hash chain */
    i = VCHash(&avc->fid);
    uvc = &afs_vhashTable[i];
    for(wvc = *uvc; wvc; uvc = &wvc->hnext, wvc = *uvc) {
	if (avc == wvc) {
	    *uvc = avc->hnext;
	    break;
	}
    }
    if (!wvc) panic("flushvcache");	/* not in correct hash bucket */
    if (avc->mvid) osi_Free(avc->mvid, sizeof(struct VenusFid));
    if (avc->linkData) osi_Free(avc->linkData, strlen(avc->linkData)+1);
    /* put the entry in the free list and free the callback */
    avc->lruq.next = (struct afs_q *) freeVCList;
    freeVCList = avc;
    if ((avc->states & CRO) == 0 && avc->callback) {
	afs_QueueVCB(&avc->fid);
    }
    return 0;
}

afs_QueueVCB(afid)
    register struct VenusFid *afid; {
    register struct server *ts;
    register struct conn *tc;
    struct volume *tv;
    register long code;
    register struct ViceFid *tfid;
    int foundFlag;
    struct vrequest treq;
#ifdef	NINTERFACE
    struct AFSCallBack CallBacks_Array[1];
#endif

    tv = afs_GetVolume(afid, (struct vrequest *)0);
    if (!tv) return EINVAL;
    /* otherwise, serverHost[0] is the server of interest */
    ts = tv->serverHost[0];
    afs_PutVolume(tv);
    if (!ts) return EINVAL;

    /* now obtain vcb lock, purge queued delete callbacks if need be, and add
	this one, if it isn't already in the list */
    ObtainWriteLock(&afs_xvcb);
    if (ts->vcbCount >= VCBS) {
	afs_InitReq(&treq, &osi_cred);
	do {
	    tc = afs_ConnByHost(ts, ts->cell->cell, &treq, 0);
#ifdef	NINTERFACE
	    if (tc) code = AFS_GiveUpCallBacks(tc->id, (struct AFSFid *) ts->vcbs, ts->vcbCount, CallBacks_Array, ts->vcbCount);
#else
	    if (tc) code = RViceRemoveCallBackBulk(tc->id, ts->vcbs, ts->vcbCount);
#endif
	    else code = -1;
	} while(afs_Analyze(tc, code, 0, &treq));
	/* code doesn't matter, server may have done the work */
	ts->vcbCount = 0;
    }
    foundFlag = 0;
    tfid = ts->vcbs;
    for(code = 0; code < ts->vcbCount; tfid++, code++) {
	if (afid->Fid.Volume == tfid->Volume && afid->Fid.Unique == tfid->Unique && afid->Fid.Vnode == tfid->Vnode) {
	    foundFlag = 1;
	    break;
	}
    }
    if (!foundFlag) ts->vcbs[ts->vcbCount++] = afid->Fid;
    ReleaseWriteLock(&afs_xvcb);
}

afs_RemoveVCB(afid)
    register struct VenusFid *afid; {
    register int i;
    struct volume *tv;
    register struct server *ts;
    register struct ViceFid *tfid;

    tv = afs_GetVolume(afid, 0);
    if (!tv) return EINVAL;
    /* otherwise, serverHost[0] is the server of interest */
    ts = tv->serverHost[0];
    afs_PutVolume(tv);
    if (!ts) return EINVAL;

    ObtainWriteLock(&afs_xvcb);
    tfid = ts->vcbs;
    for(i=0; i<ts->vcbCount; i++,tfid++) {
	if (afid->Fid.Volume == tfid->Volume && afid->Fid.Unique == tfid->Unique && afid->Fid.Vnode == tfid->Vnode) {
	    tfid->Volume = 0;
	    break;
	}
    }
    ReleaseWriteLock(&afs_xvcb);
}

/* This routine is responsible for allocating a new cache entry from the free list.  It formats
    the cache entry and inserts it into the appropriate hash tables.  It must be called with
    afs_xvcache write-locked so as to prevent several processes from trying to create
    a new cache entry simultaneously.
    
    The afid parameter is the file id of the file whose cache entry is being created.
*/
struct vcache *afs_NewVCache(afid, ahost)
    long ahost;	    /* host to create callback from */
    register struct VenusFid *afid; {
    register struct vcache *tvc;
    register long i;

    /* pull out a free cache entry */
    if (!freeVCList) afs_GetDownV(5);
    if (!freeVCList) {
	/* none free, making one is better than a panic */
	tvc = (struct vcache *) osi_Alloc(sizeof (struct vcache));
    }
    else {
	tvc = freeVCList;   /* take from free list */
	freeVCList = (struct vcache *) (tvc->lruq.next);
    }
    bzero(&tvc->v, sizeof(struct vnode));
    tvc->parentVnode = 0;
    tvc->mvid = (struct VenusFid *) 0;
    tvc->linkData = (char *) 0;
    tvc->cbExpires = 0;
    tvc->opens = 0;
    tvc->execsOrWriters = 0;
    tvc->flockCount = 0;
    tvc->anyAccess = 0;
    tvc->states = 0;
    tvc->fid = *afid;
    tvc->vrefCount = 1;	/* us */
    i = VCHash(afid);
    tvc->hnext = afs_vhashTable[i];
    afs_vhashTable[i] = tvc;
    QAdd(&VLRU, &tvc->lruq);				/* put in lruq */
    Lock_Init(&tvc->lock);
    for(i=0;i<CPSIZE;i++) tvc->randomUid[i] = -1;
    tvc->callback = ahost;	/* to minimize chance that clear request is lost */
    tvc->chunkShift = 30;
    /* initialize vnode data, note vrefCount is v.v_count */
    tvc->v.v_op = afs_ops;
    if (afid->Fid.Vnode == 1 && afid->Fid.Unique == 1)
	tvc->mvstat = 2;
    else
	tvc->mvstat = 0;
    if (afs_globalVFS == (struct vfs *) 0) panic("afs globalvfs");
    vSetVfsp(tvc, afs_globalVFS);
    vSetType(tvc, VREG);
#ifdef	vax
    tvc->v.g_dev = afs_globalVFS->vfs_data->m_dev;
#endif
    return tvc;
}

afs_KeepFlocksAlive() {
    register struct vcache *tvc;
    register int i;
    register struct conn *tc;
    register long code;
    struct vrequest treq;

    afs_InitReq(&treq, &osi_cred);
    ObtainWriteLock(&afs_xvcache);
    for(i=0;i<VCSIZE;i++) {
	for(tvc = afs_vhashTable[i]; tvc; tvc=tvc->hnext) {
	    /* if this entry has an flock, send a keep-alive call out */
	    if (tvc->flockCount != 0) {
		/* what we should probably do is bump the vrefCount count,
		    release the xvcache lock, lock the entry, and then make the
		    call, undoing all of this later, but perhaps that's overdoing
		    things a bit.  Nevertheless, this does hold the xvcache lock
		    for quite a while, which is almost certainly an error */
		tvc->vrefCount++;
		ReleaseWriteLock(&afs_xvcache);
		ObtainWriteLock(&tvc->lock);
		do {
		    tc = afs_Conn(&tvc->fid, &treq);
#ifdef	NINTERFACE
		    if (tc) code = AFS_ExtendLock(tc->id, &tvc->fid.Fid);
#else
		    if (tc) code = RViceExtendLock(tc->id, &tvc->fid.Fid);
#endif
		    else code = -1;
		} while(afs_Analyze(tc, code, &tvc->fid, &treq));
		ReleaseWriteLock(&tvc->lock);
		ObtainWriteLock(&afs_xvcache);
		tvc->vrefCount--;   /* our tvc ptr is still good until now */
	    }
	}
    }
    ReleaseWriteLock(&afs_xvcache);
}

/* make sure a cache entry is up-to-date, status-wise */
int afs_VerifyVCache(avc, areq)
    register struct vrequest *areq;
    register struct vcache *avc; {
    register struct vcache *tvc;
    ObtainReadLock(&avc->lock);
    /* first convert an expired callback into a non-callback */
    if (avc->callback && avc->cbExpires <= osi_Time())
	avc->callback = 0;
    /* check if we're all done */
    if ((avc->states & CStatd) && ((avc->states & CRO) || avc->callback)) {
	ReleaseReadLock(&avc->lock);
	return 0;
    }
    ReleaseReadLock(&avc->lock);
    /* otherwise we must fetch the status info */
    tvc = afs_GetVCache(&avc->fid, areq);
    if (!tvc) return ENOENT;
    afs_PutVCache(tvc);	    /* put back; caller has already incremented vrefCount */
    return 0;
}

/* This function takes a fid and a vrequest structure, and is responsible for storing
    the status information *only* back to the server.  This routine must be called
    with a read lock held on the item */
afs_WriteVCache(avc, astatus, areq)
    register struct vcache *avc;
    register struct ViceStatus *astatus;
    struct vrequest *areq; {
    register long code;
    register struct conn *tc;
#ifdef	NINTERFACE
    struct AFSStoreStatus InStatus;
    struct AFSFetchStatus OutStatus;
#else
    struct BBS optData, accessList;
    struct BD bulk;

    /* store the file */
    optData.SeqLen = optData.MaxSeqLen = 0;
    optData.SeqBody = (char *) 1;
    accessList.SeqLen = accessList.MaxSeqLen = 0;
    accessList.SeqBody = (char *) 1;
#endif
    do {
	tc = afs_Conn(&avc->fid, areq);
	if (tc) {
#ifdef	NINTERFACE
	    ViceStToAFSStoreSt(astatus, &InStatus);
	    code = AFS_StoreStatus(tc->id, (struct AFSFid *) &avc->fid.Fid, &InStatus, &OutStatus);
	    AFSFetchStToViceSt(&OutStatus, astatus);
#else
	    code = RViceStoreP(tc->id, &avc->fid.Fid, StoreStatus, &accessList,
			       &optData, &bulk, astatus, astatus->Length);
#endif
	}
	else code = -1;
    } while (afs_Analyze(tc, code, &avc->fid, areq));
    if (code == 0) {
	/* success, do the changes locally */
	afs_SimpleVStat(avc, astatus);
	/* now update the date, too.  SimpleVStat didn't do this, since it thought
	    we were doing this after fetching new status over a file being written. */
	avc->m.Date = astatus->Date;
    }
    else {
	/* failure, set up to check with server next time */
	avc->states &= ~CStatd;	    /* turn off stat valid flag */
    }
    return code;
}

/* This function takes a file id and a Venus request structure, and is responsible
  for fetching the status information associated with the file.
  
      The file is identified by afid.
  
      The user whose authentication tokens will be used is specified by areq.
  
      The cache entry is returned with an increased vrefCount field.  The entry must be
      discarded by calling afs_PutVCache when you are through using the pointer to the
      cache entry.

      You should not hold any locks when calling this function, except locks on other vcache entries.
      If you lock more than one vcache entry simultaneously, you should lock them in this order:

      1.  Lock all files first, then directories.
  
      2.  Within a particular type, lock entries in Fid.Vnode order.
  
      This locking hierarchy is convenient because it allows locking of a parent dir cache entry, given a
      file (to check its access control list).  It also allows renames to be handled easily by locking
      directories in a constant order.

*/
struct vcache *afs_GetVCache(afid, areq)
    register struct VenusFid *afid;
    struct vrequest *areq; {
    register long code, i;
    register struct conn *tc;
    register struct vcache *tvc;
#ifdef	NINTERFACE
    struct AFSFetchStatus OutStatus;
    struct AFSCallBack CallBack;
#else
    struct BBS optData, accessList;
    struct ViceFid bidfid;
    struct BD bulk;
#endif
    struct volume *tvp;
    struct ViceStatus status;


    ObtainWriteLock(&afs_xvcache);
    i = VCHash(afid);
    for(tvc = afs_vhashTable[i]; tvc; tvc = tvc->hnext) {
	if (tvc->fid.Fid.Unique == afid->Fid.Unique &&
	    tvc->fid.Fid.Volume == afid->Fid.Volume &&
	    tvc->fid.Cell == afid->Cell && tvc->fid.Fid.Vnode == afid->Fid.Vnode) {
	    break;
	}
    }
    if (!tvc) {
	/* no cache entry, better grab one */
	tvc = afs_NewVCache(afid, 0);
    }
    else {
	tvc->vrefCount++;
	QRemove(&tvc->lruq);		/* move to lruq head */
	QAdd(&VLRU, &tvc->lruq);
    }
    ReleaseWriteLock(&afs_xvcache);
    ObtainReadLock(&tvc->lock);
    /* first convert an expired callback into a non-callback */
    if (tvc->callback && tvc->cbExpires <= osi_Time())
	tvc->callback = 0;
    /* check if we're all done */
    if ((tvc->states & CStatd) && ((tvc->states & CRO) || tvc->callback)) {
	ReleaseReadLock(&tvc->lock);
	return tvc;
    }
    ReleaseReadLock(&tvc->lock);
    ObtainWriteLock(&tvc->lock);
    for(i=0;i<CPSIZE;i++) tvc->randomUid[i] = -1;
    tvp	= afs_GetVolume(afid, areq);	    /* copy useful per-volume info */
    if (tvp) {
	if (tvp->states & VRO) tvc->states |= CRO;
	/* now copy ".." entry back out of volume structure, if necessary */
	if (tvc->mvstat == 2  && tvp->dotdot.Fid.Volume != 0) {
	    if (!tvc->mvid) tvc->mvid = (struct VenusFid *) osi_Alloc(sizeof(struct VenusFid));
	    *tvc->mvid = tvp->dotdot;
	}
	afs_PutVolume(tvp);
    }
    /* stat the file */
#ifndef	NINTERFACE
    optData.SeqLen = optData.MaxSeqLen = 0;
    optData.SeqBody = (char *) 1;
    accessList.SeqLen = accessList.MaxSeqLen = 0;
    accessList.SeqBody = (char *) 1;
    bidfid.Volume = 0;
#endif
    afs_RemoveVCB(afid);
    do {
	tc = afs_Conn(afid, areq);
	if (tc) {
	    tvc->callback = tc->server->host;
#ifdef	NINTERFACE
	    i = osi_Time();
	    code = AFS_FetchStatus(tc->id, (struct AFSFid *) &afid->Fid, &OutStatus, &CallBack);
	    AFSFetchStToViceSt(&OutStatus, &status);
	    AFSCallBackStToViceSt(&CallBack, &status);
	    status.CallBackTime += i;
#else
	    code = RViceFetchP(tc->id, &afid->Fid, &bidfid, FetchNoData, &bulk,
			       &accessList, &optData, &status);
#endif
	    if (code == 0 && status.CallBackTime != 0) {
		tvc->cbExpires = status.CallBackTime;
	    }
	    else tvc->callback = 0;
	}
	else code = -1;
    } while (afs_Analyze(tc, code, afid, areq));
    if (code) {
	ReleaseWriteLock(&tvc->lock);
	ObtainWriteLock(&afs_xvcache);
	tvc->vrefCount--;
	ReleaseWriteLock(&afs_xvcache);
	return (struct vcache *) 0;
    }
    tvc->states |= CStatd;
    afs_ProcessVStat(tvc, &status);
    ReleaseWriteLock(&tvc->lock);
    return tvc;
}

/*
  This function is called to decrement the reference count on a cache entry.
*/

afs_PutVCache(avc)
    register struct vcache *avc; {
    ObtainWriteLock(&afs_xvcache);	/* can we use read lock here? */
    avc->vrefCount--;
    ReleaseWriteLock(&afs_xvcache);
}

/* find a vcache entry.  Must be called with the afs_xvcache lock at least
    held at read level.
*/
struct vcache *afs_FindVCache(afid)
    register struct VenusFid *afid; {
    register struct vcache *tvc;
    register long i;

    i = VCHash(afid);
    for(tvc = afs_vhashTable[i]; tvc; tvc = tvc->hnext) {
	if (tvc->fid.Fid.Unique == afid->Fid.Unique &&
	    tvc->fid.Fid.Volume == afid->Fid.Volume &&
	    tvc->fid.Cell == afid->Cell && tvc->fid.Fid.Vnode == afid->Fid.Vnode) {
	    break;
	}
    }
    if (tvc) {
	tvc->vrefCount++;
	QRemove(&tvc->lruq);
	QAdd(&VLRU, &tvc->lruq);
    }
    return tvc;
}

/* This function is called to decrement the reference count on a disk cache entry */
afs_PutDCache(ad)
    register struct dcache *ad; {
    ObtainWriteLock(&afs_xdcache);
    if (ad->refCount <= 0) panic("putdcache");
    --ad->refCount;
    ReleaseWriteLock(&afs_xdcache);
    return 0;
}

/* try to discard all data associated with this file from the cache */
afs_TryToSmush(avc)
    register struct vcache *avc; {
    register struct dcache *tdc;
    register int index;
    tdc = afs_FindDCache(avc, 0);
    if (tdc) {
	index = tdc->index;
	ObtainWriteLock(&afs_xdcache);
	lockedPutDCache(tdc);	/* undo FindDCache's increment */
	/* if data is back on server, and refCount is 0 then we are
	    only user of this dude, and we can discard it */
	afs_dp("Considering smushing %x\n", avc);
	if ((afs_indexFlags[index] & IFDataMod) == 0 && tdc->refCount == 0) {
	    /* can discard this dude, for sure */
	    afs_FlushDCache(tdc);
	    afs_dp("Smushed %x\n", avc);
	}
	ReleaseWriteLock(&afs_xdcache);
    }
}

struct dcache *afs_FindDCache(avc, abyte)
    register struct vcache *avc;    /*Held*/
    long abyte; {
    long chunk;
    register long i, index;
    register struct dcache *tdc;

    chunk = (abyte >> avc->chunkShift);
#ifdef notdef
    /* compute but don't use yet */
    offset = abyte & ((1<<avc->chunkShift)-1);
#endif

    /*
         * Hash on the [fid, chunk] and get the corresponding dcache index after
         * write-locking the dcache.
         */
    i = DCHash(&avc->fid, chunk);
    ObtainWriteLock(&afs_xdcache);
    for(index = afs_dchashTable[i]; index != NULLIDX;) {
	tdc = afs_GetDSlot(index, (struct dcache *)0);
	if (!FidCmp(&tdc->f.fid, &avc->fid) && chunk == tdc->f.chunk) {
	    break;  /* leaving refCount high for caller */
	}
	index = tdc->f.hcNextp;
	lockedPutDCache(tdc);
    }
    ReleaseWriteLock(&afs_xdcache);
    if (index != NULLIDX) {
	afs_indexTimes[tdc->index] = afs_indexCounter++;
	return tdc;
    }
    else return (struct dcache *) 0;
}

/* routine called on writes; tell people waiting for data that more has arrived */
static int CacheWriteProc(afile, acode)
    register struct osi_file *afile;
    register long acode; {
    register struct dcache *tdc;
    tdc = (struct dcache *) osi_GetFileRock(afile);
    if (acode >= 0) {
	tdc->validPos += acode;
    }
    if (tdc->f.states & DWaiting) {
	tdc->f.states &= ~DWaiting;
	osi_Wakeup(&tdc->validPos);
    }
}

/* This function is called to obtain a reference to data stored in the disk
  cache.  Passed in are an unlocked vcache entry, the byte position in the
  file desired and a Venus request structure identifying the requesting user.
  
  This function is responsible for locating a chunk of data containing the desired
  byte and returning a reference to the disk cache entry, with its reference
  count incremented.  In addition, *aoffset is set to the offset within
  the chunk where the request byte resides, and *alen is set to the
  number of bytes of data after the desired byte (including the desired byte)
  which can be read from this chunk.
  
  Flags are 1->set locks, 2->return after creating entry.

*/
struct dcache *afs_GetDCache(avc, abyte, areq, aoffset, alen, aflags)
    register struct vcache *avc;    /*Held*/
    long abyte;
    int	aflags;			    /* should we set locks? */
    long *aoffset, *alen;	    /*Return values*/
    register struct vrequest *areq; {

    register long i, code;
    char *piggyBank;
    int setLocks;
    long chunk;
    long localSession;
    long index;
    struct BD bdesc;
    struct BBS optData, dummyBS;
#ifdef	NINTERFACE
    long Segment = 0; /* Not used yet */
    struct AFSFetchStatus OutStatus;
    struct AFSCallBack CallBack;
#else
    struct ViceFid bidFid;
#endif
    register struct dcache *tdc;
    struct ViceStatus vstat;
    struct osi_file *file;
    struct rftp_conn *rftpHandle;
    register struct conn *tc;

    /*
         * Determine the chunk number and offset within the chunk corresponding to the
         * desired byte.
         */
    chunk = (abyte >> avc->chunkShift);
#ifdef notdef
    /* don't really compute until know how to use */
    offset = abyte & ((1<<avc->chunkShift)-1);
#endif

    /*
         * Hash on the [fid, chunk] and get the corresponding dcache index after
         * write-locking the dcache.
         */
    i = DCHash(&avc->fid, chunk);
    setLocks = aflags & 1;
    ObtainWriteLock(&afs_xdcache);
    for(index = afs_dchashTable[i]; index != NULLIDX;) {
	tdc = afs_GetDSlot(index, (struct dcache *)0);
	if (!FidCmp(&tdc->f.fid, &avc->fid) && chunk == tdc->f.chunk) {
	    ReleaseWriteLock(&afs_xdcache);
	    break;  /* leaving refCount high for caller */
	}
	index = tdc->f.hcNextp;
	lockedPutDCache(tdc);
    }

    /*
     * If we didn't find the entry, we'll create one.
         */
    if (index == NULLIDX) {
	afs_dp("getdcache failed to find %x.%d\n", avc, chunk);
	if (freeDCList == NULLIDX) afs_GetDownD(5, 0);	/* just need slots */
	if (freeDCList == NULLIDX) panic("getdcache");
	afs_indexFlags[freeDCList] &= ~IFFree;
	tdc = afs_GetDSlot(freeDCList, 0);
	freeDCList = tdc->f.hvNextp;
	freeDCCount--;

	/*
	  * Fill in the newly-allocated dcache record.
	  */
	tdc->f.fid = avc->fid;
	tdc->f.versionNo = -1;	    /* invalid value */
	tdc->f.chunk = chunk;
	if (tdc->lruq.prev == &tdc->lruq) panic("lruq 1");
	/* now add to the two hash chains */
	tdc->f.hcNextp = afs_dchashTable[i];	/* i still set from above DCHash call */
	afs_dchashTable[i] = tdc->index;
	i = DVHash(&avc->fid);
	tdc->f.hvNextp = afs_dvhashTable[i];
	afs_dvhashTable[i] = tdc->index;
	tdc->f.states = DEntryMod;
	ReleaseWriteLock(&afs_xdcache);
    }

    /* here we check for 0 length fetch */
    if ((chunk << avc->chunkShift) >= avc->m.Length) {
	/* no data in file to read at this position */
	if (avc->m.DataVersion != tdc->f.versionNo) {
	    if (setLocks) ObtainWriteLock(&avc->lock);
	    file = osi_UFSOpen(&cacheDev, tdc->f.inode);
	    if (!file) panic("getdcache open0");
	    osi_Truncate(file, 0);
	    afs_AdjustSize(tdc, 0);
	    osi_Close(file);
	    tdc->f.versionNo = avc->m.DataVersion;
	    tdc->f.states |= DEntryMod;
	    if (setLocks) ReleaseWriteLock(&avc->lock);
	    afs_dp("faking 0 byte entry\n");
	}
    }

    /*
         * Here we have the unlocked entry in tdc, with its refCount incremented.
         * We must read in the whole chunk iff the version number doesn't match.
         */

    if (aflags & 2) {
	/* don't need data, just a unique dcache entry */
	afs_indexTimes[tdc->index] = afs_indexCounter++;
	return tdc;	/* check if we're done */
    }

    if (setLocks) ObtainReadLock(&avc->lock);
    if (avc->m.DataVersion != tdc->f.versionNo) {
	if (setLocks) {
	    ReleaseReadLock(&avc->lock);
	    ObtainWriteLock(&avc->lock);
	}
	/* watch for standard race condition */
	if (avc->m.DataVersion == tdc->f.versionNo) {
	    if (setLocks) ReleaseWriteLock(&avc->lock);
	    goto done;
	}
	if (avc->m.Length > tdc->f.chunkBytes) {
	    /* pre-reserve space for file */
	    afs_AdjustSize(tdc,	avc->m.Length);	/* changes chunkBytes */
	}
	if (afs_mariner) afs_MarinerLog("fetch$Fetching", avc);
	/* right now, we only have one tool, and it's a hammer.  So, we fetch the whole file. */
	DZap(&tdc->f.inode);	/* pages in cache may be old */
	file = osi_UFSOpen(&cacheDev, tdc->f.inode);
	if (!file) panic("getdcache open");
	osi_SetFileProc(file, CacheWriteProc);
	osi_SetFileRock(file, (char *) tdc);
	piggyBank = osi_AllocSendSpace();
	afs_RemoveVCB(&avc->fid);
	tdc->f.states |= DWriting;
	tdc->f.states |= DFetching; /* merge if lasts long */
	if (tdc->f.states & DFetchReq) {
	    tdc->f.states &= ~DFetchReq;
	    osi_Wakeup(&tdc->validPos);
	}
	tdc->validPos = tdc->f.chunk << 10;	    /* last valid position in this chunk */
	do {
	    bdesc.host = 0;
	    bdesc.portal = ntohs(htons(3536)); /* lookup */
	    localSession = rftpSession++;   /* no context swaps between use and bump */
	    bdesc.session = localSession;
	    optData.MaxSeqLen = PIGGYSIZE;
	    optData.SeqLen = 0;
	    optData.SeqBody = piggyBank;
#ifndef	NINTERFACE
	    dummyBS.SeqLen = dummyBS.MaxSeqLen = 0;
	    dummyBS.SeqBody = (char *) 1;   /* don't allocate space */
	    bidFid.Volume = 0;
#endif
	    tc = afs_Conn(&avc->fid, areq);
	    if (tc) {
		avc->callback = tc->server->host;
		rftpHandle = (struct rftp_conn *) rftp_GetFile(afs_rftpServer, file, tc->server->host, htons(2002), localSession, 0);
		ConvertWToSLock(&avc->lock);
#ifdef	NINTERFACE
		i = osi_Time();
		code = AFS_FetchData(tc->id, (struct AFSFid *) &avc->fid.Fid, &bdesc, Segment, &optData, &OutStatus, &CallBack);
		AFSFetchStToViceSt(&OutStatus, &vstat);
		AFSCallBackStToViceSt(&CallBack, &vstat);
		vstat.CallBackTime += i;
#else
		code = RViceFetchP(tc->id, &avc->fid.Fid, &bidFid, FetchData, &bdesc,
				   &dummyBS, &optData, &vstat);
#endif
		UpgradeSToWLock(&avc->lock);
	    }
	    else {
		code = -1;
		rftpHandle = (struct rftp_conn *) 0;
	    }
	    if (code == 0) {
		if (vstat.Length <= PIGGYSIZE) {
		    /*We're currently at file offset 0, thanks to the open above*/
		    code = osi_Write(file, piggyBank, vstat.Length);
		    if (code != optData.SeqLen) code = EIO;
		    else code = 0;
		    rftp_FreeConnection(rftpHandle);
		}
		else code = rftp_GetWait(rftpHandle);
		if (vstat.CallBackTime == 0) avc->callback = 0;
		else avc->cbExpires = vstat.CallBackTime;
		osi_Truncate(file, vstat.Length);   /* prune it */
	    }
	    else {
		if (rftpHandle) rftp_FreeConnection(rftpHandle);
	    }
	} while(afs_Analyze(tc, code, &avc->fid, areq));
	tdc->f.states &= ~DFetching;
	if (tdc->f.states & DWaiting) {
	    tdc->f.states &= ~DWaiting;
	    osi_Wakeup(&tdc->validPos);
	}
	osi_FreeSendSpace(piggyBank);
	if (avc->execsOrWriters == 0) tdc->f.states &= ~DWriting;

	/* now, if code != 0, we have an error and should punt */
	if (code) {
	    osi_Truncate(file, 0);	/* discard old data */
	    afs_AdjustSize(tdc, 0);
	    osi_Close(file);
	    tdc->f.versionNo = -1;	/* invalid value */
	    tdc->refCount--;
	    if (setLocks) ReleaseWriteLock(&avc->lock);
	    return (struct dcache *) 0;
	}

	/* otherwise we copy in the just-fetched info */
	osi_Close(file);
	afs_AdjustSize(tdc, vstat.Length);  /* new size */
	afs_ProcessVStat(avc, &vstat);	/* copy appropriate fields into vcache */
	tdc->f.versionNo = vstat.DataVersion;
	tdc->f.states |= DEntryMod;
	/* if data ever existed for this vnode, and this is a text object, do some clearing.  Now, you'd think you need only do the flush when VTEXT is on, but VTEXT is turned off when the text object is freed, while pages are left lying around in memory marked with this vnode.  If we would reactivate (create a new text object from) this vnode, we could easily stumble upon some of these old pages in pagein.  So, we always flush these guys.  Sun has a wonderful lack of useful invariants in this system. */
	if (avc->states & CDataMod) {
	    avc->states	&= ~CDataMod;	/* do this first, in case of race */
	    osi_FlushText(avc);
	}
	if (setLocks) ReleaseWriteLock(&avc->lock);
    }
    else {
	if (setLocks) ReleaseReadLock(&avc->lock);
    }

done:
    /* fixup lru info */
    afs_indexTimes[tdc->index] = afs_indexCounter++;

    /* return the data */
    *aoffset = abyte - (tdc->f.chunk << 10);
    *alen = (tdc->f.chunkBytes - *aoffset);
    return tdc;
}

/* simple copy of stat info into cache. */
afs_SimpleVStat(avc, astat)
    register struct vcache *avc;
    register struct ViceStatus *astat; {
    if (avc->execsOrWriters <= 0) {
	/* if writing the file, don't fetch over this value */
	avc->m.Length = astat->Length;
	avc->m.Date = astat->Date;
    }
    avc->m.Owner = astat->Owner;
    avc->m.Mode = astat->Mode;
    if (vType(avc) == VREG) {
	avc->m.Mode |= S_IFREG;
    }
    else if (vType(avc) == VDIR) {
	avc->m.Mode |= S_IFDIR;
    }
    else if (vType(avc) == VLNK) {
	avc->m.Mode |= S_IFLNK;
	if ((avc->m.Mode & 0111) == 0) avc->mvstat = 1;
    }
}

/* copy astat block into vcache info; must be called under a write lock */
afs_ProcessVStat(avc, astat)
    register struct vcache *avc;
    register struct ViceStatus *astat; {
    if (avc->execsOrWriters <= 0) {
	/* if writing the file, don't fetch over these values */
	avc->m.Length = astat->Length;
	avc->m.Date = astat->Date;
    }
    avc->m.DataVersion = astat->DataVersion;
    avc->m.Owner = astat->Owner;
    avc->m.Mode = astat->Mode;
    avc->m.LinkCount = astat->LinkCount;
    if (astat->VnodeType == File) {
	vSetType(avc, VREG);
	avc->m.Mode |= S_IFREG;
    }
    else if (astat->VnodeType == Directory) {
	vSetType(avc, VDIR);
	avc->m.Mode |= S_IFDIR;
    }
    else if (astat->VnodeType == SymbolicLink) {
	vSetType(avc, VLNK);
	avc->m.Mode |= S_IFLNK;
	if ((avc->m.Mode & 0111) == 0) avc->mvstat = 1;
    }
    avc->anyAccess = astat->AnyAccess;
}

/* This function is called only during initialization, and is passed one parameter,
  a file name.  This file is declared to be the volume info storage file for
  the Andrew file system.  It must be already truncated to 0 length.
  
  Warning: data will be written to this file over time by the Andrew file system.

*/
afs_InitVolumeInfo (afile)
    register char *afile; {
    register long code;
    register struct osi_file *tfile;
    struct vnode *filevp;

    code = gop_lookupname(afile, AFS_UIOSYS, 0, (struct vnode *) 0, &filevp);
    if (code) return ENOENT;
    volumeInode = VTOI(filevp)->i_number;
    VN_RELE(filevp);
    tfile = osi_UFSOpen(&cacheDev, volumeInode);
    osi_Truncate(tfile, 0);
    osi_Close(tfile);
    return 0;
}

/* This function is called only during initialization, and is passed one parameter,
  a file name.  This file is assumed to be the cache info file for venus, and
  will be used as such.  This file should *not* be truncated to 0 length; its
  contents describe what data is really in the cache.
  
  Warning: data will be written to this file over time by the Andrew file system.

*/
afs_InitCacheInfo (afile)
    register char *afile; {
    register long code;
    struct osi_stat tstat;
    register struct osi_file *tfile;
    struct vnode *filevp;

    code = gop_lookupname(afile, AFS_UIOSYS, 0, (struct vnode *) 0, &filevp);
    if (code) return ENOENT;
    cacheInode = VTOI(filevp)->i_number;
    cacheDev.dev = VTOI(filevp)->i_dev;
    dirp_SetCacheDev(&cacheDev);	/* tell dir package where cache is */
    VN_RELE(filevp);
    tfile = osi_UFSOpen(&cacheDev, cacheInode);
    if (!tfile) panic("initcacheinfo");
    osi_Stat(tfile, &tstat);
    cacheInfoModTime = tstat.mtime;
    osi_Close(tfile);
    return 0;
}

/* This function is called only during initialization.  It is passed one parameter:
  a file name of a file in the cache.
  
  The file specified will be written to be the Andrew file system.
*/
int afs_InitCacheFile(afile, ainode)
    long ainode;
    char *afile; {
    register long code;
    struct vnode *filevp;
    long index;
    int fileIsBad;
    struct osi_file *tfile;
    struct osi_stat tstat;
    register struct dcache *tdc;

    index = cacheCounter;
    if (index >= afs_cacheFiles) return EINVAL;

    ObtainWriteLock(&afs_xdcache);
    tdc = afs_GetDSlot(index, (struct dcache *)0);
    ReleaseWriteLock(&afs_xdcache);
    /* note, leaves newly-formatted cache entry in LRUQ */
    if (!tdc) {
	VN_RELE(filevp);
	panic("initcachefile 1");
    }

    if (afile) {
	code = gop_lookupname(afile, AFS_UIOSYS, 0, (struct vnode *) 0, &filevp);
	if (code) {
	    afs_PutDCache(tdc);
	    return code;
	}
	/* otherwise we have a VN_HOLD on filevp.  Get the useful info out and return.
	 we make use here of the fact that the cache is in the UFS file system,
	 and just record the inode number. */
	tdc->f.inode = VTOI(filevp)->i_number;
	VN_RELE(filevp);
    }
    else {
	tdc->f.inode = ainode;
    }
    tdc->f.chunkBytes = 0;
    fileIsBad = 0;
    if ((tdc->f.states & DWriting) || tdc->f.fid.Fid.Volume == 0) fileIsBad = 1;
    tfile = osi_UFSOpen(&cacheDev, tdc->f.inode);
    if (!tfile) panic("initcachefile open");
    code = osi_Stat(tfile, &tstat);
    if (code) panic("initcachefile stat");
    /* if file changed within T (120?) seconds of cache info file, it's probably bad.  In addition, if
	slot changed within last T seconds, the cache info file may be incorrectly identified, and so
	slot may be bad. */
    if (cacheInfoModTime < tstat.mtime + 120) fileIsBad = 1;
    if (cacheInfoModTime < tdc->f.modTime + 120) fileIsBad = 1;
    if (fileIsBad) {
	tdc->f.fid.Fid.Volume =	0;  /* not in the hash table */
	if (tstat.size != 0)
	    osi_Truncate(tfile, 0);
	/* put entry in free cache slot list */
	tdc->f.hvNextp = freeDCList;
	tdc->f.chunkBytes = 0;
	freeDCList = index;
	freeDCCount++;
	afs_indexFlags[index] |= IFFree;
    }
    else {
	/* we must put this entry in the appropriate hash tables */
	code = DCHash(&tdc->f.fid, tdc->f.chunk);
	tdc->f.hcNextp = afs_dchashTable[code];	/* i still set from above DCHash call */
	afs_dchashTable[code] = tdc->index;
	code = DVHash(&tdc->f.fid);
	tdc->f.hvNextp = afs_dvhashTable[code];
	afs_dvhashTable[code] = tdc->index;
	tdc->f.chunkBytes = 0;		    /* assumed old size */
	afs_AdjustSize(tdc, tstat.size);    /* adjust to new size */
	if (tstat.size > 0) afs_indexFlags[index] |= IFEverUsed;    /* has nontrivial amt. of data */
	afs_reusedFiles++;
	/* initialize index times to file's mod times; init indexCounter to max thereof */
	afs_indexTimes[index] = tstat.atime;
	if (afs_indexCounter < tstat.atime) afs_indexCounter = tstat.atime;
    }
    osi_Close(tfile);
    tdc->f.states &= ~(DWriting|DEntryMod);
    afs_WriteDCache(tdc, 0);	    /* don't set f.modTime; we're just cleaning up */
    afs_PutDCache(tdc);
    cacheCounter++;
    return 0;
}

/* This function is responsible for ensuring the cache info file is
    up-to-date.
*/
afs_WriteThroughDSlots() {
    register struct dcache *tdc;
    register long i;
    
    ObtainWriteLock(&afs_xdcache);
    for(i=0;i<afs_cacheFiles;i++) {
	tdc = afs_indexTable[i];
	if (tdc && (tdc->f.states & DEntryMod)) {
	    tdc->f.states &= ~DEntryMod;
	    afs_WriteDCache(tdc, 1);
	}
    }
    ReleaseWriteLock(&afs_xdcache);
}

/* This function is responsible for returning a particular dcache entry, named by its slot
    index.  If the entry is already present, it is returned, otherwise the contents are read
    from the CacheInfo file.  If the caller has supplied us with a pointer to an in-memory
    dcache structure, we place the info there.  Otherwise, we allocate a dcache entry from
    the free list and use it.

    We return the address of the in-memory copy of the file record.  This entry's refCount
    field has been incremented; use afs_PutDCache() to release it.
    
    This function must be called with the afs_xdcache lock write-locked.
*/
struct dcache *afs_GetDSlot(aslot, tmpdc)
    register long aslot;
    register struct dcache *tmpdc; {

    register long code;
    register struct dcache *tdc;
    register struct osi_file *tfile;

    if (CheckLock(&afs_xdcache) != -1) panic("getdslot nolock");
    if (aslot < 0 || aslot >= afs_cacheFiles) panic("getdslot slot");
    tdc = afs_indexTable[aslot];
    if (tdc) {
	QRemove(&tdc->lruq);	    /* move to queue head */
	QAdd(&DLRU, &tdc->lruq);
	tdc->refCount++;
	return tdc;
    }
    /* otherwise we should read it in from the cache file */
    tfile = osi_UFSOpen(&cacheDev, cacheInode);
    if (!tfile) panic("CacheInfo");

    /*
         * If we weren't passed an in-memory region to place the file info, we have
         * to allocate one.
	 */
    if (tmpdc == (struct dcache *)0) {
	if (!freeDSList) afs_GetDownDSlot(5);
	if (!freeDSList) panic("getdslot 17");
	tdc = freeDSList;
	freeDSList = (struct dcache *) tdc->lruq.next;
	tdc->f.states &= ~DEntryMod;	/* up-to-date, not in free q */
	QAdd(&DLRU, &tdc->lruq);
	if (tdc->lruq.prev == &tdc->lruq) panic("lruq 3");
    }
    else {
	tdc = tmpdc;
	tdc->f.states = 0;
    }

    /*
      * Seek to the aslot'th entry and read it in.
      */
    osi_Seek(tfile, sizeof(struct fcache) * aslot);
    code = osi_Read(tfile, (char *)(&tdc->f), sizeof(struct fcache));
    if (code != sizeof(struct fcache)) {
	tdc->f.fid.Cell = 0;
	tdc->f.fid.Fid.Volume = 0;
	tdc->f.chunk = -1;
	tdc->f.versionNo = -1;
	tdc->f.hcNextp = tdc->f.hvNextp = tdc->f.chunkNextp = NULLIDX;
	tdc->f.states |= DEntryMod;
    }
    tdc->refCount = 1;
    tdc->index = aslot;

    /*
         * If we didn't read into a temporary dcache region, update the slot pointer table.
         */
    if (tmpdc == (struct dcache *)0)
	afs_indexTable[aslot] = tdc;
    osi_Close(tfile);
    return tdc;
}

/* This function is called to write a particular dcache entry back to its home in the CacheInfo file.
  It has one parameter, the dcache entry.  The reference count is not changed.
  
  This function must be called with the afs_xdcache lock at least read-locked.

*/
int afs_WriteDCache(adc, atime)
    int atime;
    register struct dcache *adc; {
    register struct osi_file *tfile;
    register long code;

    if (atime) adc->f.modTime =	osi_Time();
    tfile = osi_UFSOpen(&cacheDev, cacheInode);
    if (!tfile) panic("cacheinfo 2");

    /*
         * Seek to the right dcache slot and write the in-memory image out to disk.
         */
    osi_Seek(tfile, sizeof(struct fcache) * adc->index);
    code = osi_Write(tfile, (char *)(&adc->f), sizeof(struct fcache));
    osi_Close(tfile);
    if (code != sizeof(struct fcache)) return EIO;
    return 0;
}

/* this function is called under a shared lock to write a dcache entry back
  to the server.
*/

int afs_StoreDCache(avc, adc, areq)
    register struct vcache *avc;
    struct vrequest *areq;
    register struct dcache *adc; {
    register struct conn *tc;
    char *piggyBank;
    register long code;
    struct rftp_conn *rftpHandle;
    struct BD BDesc;
    struct BBS OptionalData;
    struct CBS dummyAcl;
#ifdef	NINTERFACE
    struct AFSStoreStatus InStatus;
    struct AFSFetchStatus OutStatus;
    long Segment = 0;	    /* Whole file for now */
#endif
    long localSession;
    struct ViceStatus newStatus;
    struct osi_file *tfile;

    /* store the file */
    if (afs_mariner) afs_MarinerLog("store$Storing", avc);
    afs_dp("storing file %x\n", avc);
    dummyAcl.SeqLen = 0;
    dummyAcl.SeqBody = (char *) 1;
    piggyBank = osi_AllocSendSpace();
    do {
	tc = afs_Conn(&avc->fid, areq);
	if (tc) {
	    /* The writes done before a store back will clear setuid-ness in cache file. */
	    tfile = osi_UFSOpen(&cacheDev, adc->f.inode);	/* So fchmod below works */
	    if (!tfile) panic("bad inode in store");
	    localSession = rftpSession++;   /* no context swaps between use and incr */
	    rftpHandle = (struct rftp_conn *) rftp_SendFile(afs_rftpServer, tfile, tc->server->host, htons(2002), localSession, 0);
	    BDesc.host = 0;
	    BDesc.portal = ntohs(htons(3536));
	    BDesc.session = localSession;
	    newStatus.Owner = avc->m.Owner;
	    newStatus.Mode = avc->m.Mode;
	    newStatus.Date = avc->m.Date;
	    if (adc->f.chunkBytes <= PIGGYSIZE) {
		OptionalData.MaxSeqLen = PIGGYSIZE;
		OptionalData.SeqBody = piggyBank;
		OptionalData.SeqLen = osi_Read(tfile, piggyBank, PIGGYSIZE);
	    }
	    else {
		OptionalData.MaxSeqLen = 0;
		OptionalData.SeqLen = 0;
	    }
#ifdef	NINTERFACE
	    ViceStToAFSStoreSt(&newStatus, &InStatus);
	    code = AFS_StoreData(tc->id, (struct AFSFid *) &avc->fid.Fid, &InStatus, Segment, adc->f.chunkBytes, &BDesc, &OptionalData, &OutStatus);
	    AFSFetchStToViceSt(&OutStatus, &newStatus);
#else
	    code = RViceStoreP(tc->id, &avc->fid.Fid, StoreStatusData,
			       &dummyAcl, &OptionalData, &BDesc, &newStatus, adc->f.chunkBytes);
#endif
	    if (code == 0) {
		if (OptionalData.MaxSeqLen != 0) {
		    /* did piggyback call */
		    rftp_FreeConnection(rftpHandle);
		    code = 0;
		}
		/* we don't look at return code, since we don't care if dally failed,
		    and other side will notice if data doesn't get through.
		*/
		else rftp_SendWait(rftpHandle);
	    }
	    else rftp_FreeConnection(rftpHandle);
	    osi_Close(tfile);
	}
	else code = -1;
    } while (afs_Analyze(tc, code, &avc->fid, areq));
    osi_FreeSendSpace(piggyBank);
    /* now copy stuff back out */
    UpgradeSToWLock(&avc->lock);    /* keep out others for a while */
    if (code == 0) {
	afs_ProcessVStat(avc, &newStatus);
	adc->f.versionNo = avc->m.DataVersion;
	afs_indexFlags[adc->index] &= ~IFDataMod;
    }
    else {
	adc->f.versionNo = -1;
    }
    adc->f.states |= DEntryMod;
    ConvertWToSLock(&avc->lock);
    afs_dp("store done\n");
    return code;
}
