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

#ifndef lint
static char *rcsid = "$Header:resource.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 "../afs/osi.h"
#define RFTP_INTERNALS 1
#define R_INTERNALS 1
#include "../afs/r.h"
#include "../afs/rftp.h"

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

/* This file contains routines used for dealing with users, volumes, cells
 * and connections.  They are related, unfortunately, as follows:
 *
 * 1.  A unix uid and a cell map
 * to a viceid and a set of tokens for authenticating as that vice user.
 *
 * 2.  A vice fid (including cell) maps to the set of servers providing service
 * for the cell and volume of the file named by the fid.
 *
 * 3.  A viceid and server maps to a particular connection that can be used
 * for authenticating as the particular user to that file server.
 *
 * The most-often performed operation starts with a Unix uid and a vice fid,
 * and iterates through all the connections to the servers that both provide
 * service for the file named by the fid and are authenticated as the uid
 * pair in the specified cell (the authentication is expressed as a cell, viceid
 * pair).
 *
 * Note that afs_rlock is the parent lock for cells, users, servers and volumes.
 * See large comment in lock.h about how parent locking really works.
 */

struct server *afs_FindServer ();
extern char *afs_cv2string();
extern long rftp_window;
extern char *afs_GetMariner();

extern long afs_waitForever;
extern short afs_waitForeverCount;
static long fvTable[NFENTRIES];
long afs_debug = 0;	    /* general console debug flag */
extern long volumeInode;
extern long afs_setTime;
long afs_setTimeHost=0;	    /* last host we used for time */
struct osi_dev cacheDev;		    /* cache device */ 
long afs_volCounter = 1;    /* for allocating volume indices */
struct cell *afs_cells = 0;
struct unixuser *afs_users[NUSERS];
struct server *afs_servers[NSERVERS];
struct volume *afs_volumes[NVOLS];
extern struct lock afs_xvcache;	/* lock for cache */
struct lock afs_xconn;	    /* allocation lock for new things */
struct lock afs_xvolume;    /* allocation lock for volumes */
struct lock afs_xuser;	    /* allocation lock for unixusers */
struct lock afs_xcell;	    /* allocation lock for cells */
struct lock afs_xserver;    /* allocation lock for servers */
struct r_server	*afs_server;	/* server for andrew file system */
struct volume *afs_freeVolList;
struct rftp_server *afs_rftpServer;	/* rftp server for afs */
extern struct vcache *afs_vhashTable[];
extern struct dcache **afs_indexTable;
extern char *afs_indexFlags;
long afs_marinerHost;
unsigned short afs_marinerPort;
long afs_logFileInode = 0;

static cellCounter = 1;

resource_cleanup() {
    bzero(fvTable, sizeof(fvTable));
    afs_setTimeHost=0;
    bzero(&cacheDev, sizeof(cacheDev));
    afs_volCounter = 1;
    afs_cells = 0;
    bzero(afs_users, sizeof(afs_users));
    bzero(afs_servers, sizeof(afs_servers));
    bzero(afs_volumes, sizeof(afs_volumes));
    bzero(&afs_xconn, sizeof(afs_xconn));
    bzero(&afs_xvolume, sizeof(afs_xvolume));
    bzero(&afs_xuser, sizeof(afs_xuser));
    bzero(&afs_xcell, sizeof(afs_xcell));
    bzero(&afs_xserver, sizeof(afs_xserver));
    afs_server = 0;
    afs_freeVolList = 0;
    afs_rftpServer = 0;
    bzero(afs_vhashTable, sizeof(afs_vhashTable));
    afs_indexTable = 0;
    afs_marinerHost = 0;
    afs_marinerPort = 0;
    afs_logFileInode = 0;
    cellCounter = 1;
}

static char mbuffer[100];
afs_MarinerLog(astring, avc)
    register struct vcache *avc;
    register char *astring; {
    register long code;
    struct sockaddr_in taddr;
    register char *tp, *up;

    taddr.sin_family = AF_INET;
    taddr.sin_addr.s_addr = afs_marinerHost;
    taddr.sin_port = htons(2106);
    tp = mbuffer;
    strcpy(tp, astring);
    tp += strlen(astring);
    *tp++ = ' ';
    up = afs_GetMariner(avc);
    strcpy(tp, up);
    tp += strlen(up);
    *tp++ = '\n';
    /* note, console doesn't want a terminating null */
    code = osi_NetSend(afs_server->socket, &taddr, mbuffer, tp-mbuffer);
    if (code) printf("afs_mariner code %d\n", code);
}

/* called with afs_xuser, afs_xserver and afs_xconn locks held, to delete appropriate
    conn structures for au */
static RemoveUserConns(au)
    register struct unixuser *au; {
    register int i;
    register struct server *ts;
    register struct conn *tc, **lc;
    for(i=0;i<NSERVERS;i++) {
	for(ts = afs_servers[i]; ts; ts=ts->next) {
	    lc = &ts->conns;
	    for(tc = *lc; tc; lc = &tc->next, tc = *lc) {
		if (tc->user == au && tc->refCount == 0) {
		    *lc = tc->next;
		    r_FreeConnection(tc->id);
		    osi_Free(tc, sizeof(struct conn));
		    break;  /* at most one instance per server */
		}
	    }
	}
    }
}

/* called from afs_Daemon to garbage collect unixusers no longer using system, and their R conns */
afs_GCUserData() {
    register struct unixuser *tu, **lu, *nu;
    register int i;
    long now, delFlag;

    /* obtain locks in valid order */
    ObtainWriteLock(&afs_xuser);
    ObtainWriteLock(&afs_xserver);
    ObtainWriteLock(&afs_xconn);
    now = osi_Time();
    for(i=0;i<NUSERS;i++) {
	for(lu = &afs_users[i], tu = *lu; tu; tu = nu) {
	    delFlag = 0;	/* should we delete this dude? */
	    /* don't garbage collect users in use now (refCount) or root (uid 0).  We don't
		GC root so that the code to automatically notice if a server has come back
		up has a connection to use.  Alternatively, we could create a missing conn
		if we need it in afs_CheckServers.
	    */
	    if (tu->refCount == 0 && tu->uid != 0) {
		if (tu->states & UHasTokens) {
		    if (tu->tokenTime < now - TOKENTIMEOUT) delFlag = 1;
		}
		else {
		    if (tu->tokenTime < now - NOTOKTIMEOUT) delFlag = 1;
		}
	    }
	    nu = tu->next;
	    if (delFlag) {
		*lu = tu->next;
		RemoveUserConns(tu);
		osi_Free(tu, sizeof(struct unixuser));
	    }
	    else {
		lu = &tu->next;
	    }
	}
    }
    ReleaseWriteLock(&afs_xuser);
    ReleaseWriteLock(&afs_xserver);
    ReleaseWriteLock(&afs_xconn);
}

/* called by unmount to clear all the user tokens */
afs_Clear_Users() {
    register struct unixuser *tu, **lu, *nu;
    register int i;

    /* obtain locks in valid order */
    ObtainWriteLock(&afs_xuser);
    ObtainWriteLock(&afs_xserver);
    ObtainWriteLock(&afs_xconn);
    for(i=0;i<NUSERS;i++) {
	for(lu = &afs_users[i], tu = *lu; tu; tu = nu) {
	    nu = tu->next;
	    *lu = tu->next;
	    RemoveUserConns(tu);
	    osi_Free(tu, sizeof(struct unixuser));
	}
    }
    ReleaseWriteLock(&afs_xuser);
    ReleaseWriteLock(&afs_xserver);
    ReleaseWriteLock(&afs_xconn);
}

afs_FinalizeReq(areq)
    register struct vrequest *areq; {
    if (areq->initd) return;
    areq->busyCount = 0;
    areq->accessError = 0;
    areq->volumeError = 0;
    areq->networkError = 0;
    areq->initd = 1;
}

afs_CheckCode(acode, areq)
    register long acode;
    register struct vrequest *areq; {
    if (acode) afs_dp("returning code %d\n", acode);
    if (!areq || !areq->initd) return acode;
    if (areq->networkError) return ETIMEDOUT;
    if (acode == 0) return 0;
    if (areq->accessError) return EACCES;
    if (areq->volumeError) return ENODEV;
    return acode;
}

/* sleep for atime seconds */
static VSleep(atime)
    register long atime; {
    osi_NetWait(0, atime * 1000, 0);
}

#define	CVBS	20
char *afs_cv2string(aval)
    register unsigned long aval; {
    static char tbuffer[CVBS];
    register char *tp;
    register int  i;
    int any;
    
    any = 0;
    tp = &tbuffer[CVBS];
    *(--tp) = 0;
    while (aval != 0) {
	i = aval % 10;
	*(--tp) = '0' + i;
	aval /= 10;
	any = 1;
    }
    if (!any) *(--tp) = '0';
    return tp;
}

/*
 * All of the vol cache routines must be called with the afs_xvolume
 * lock held in exclusive mode, since they use static variables.
 * In addition, we don't want two people adding the same volume
 * at the same time.
 */

static struct fvolume staticFVolume;
static long FVIndex = -1;
struct fvolume *afs_GetVolCache(aslot)
    register long aslot; {
    register struct osi_file *tfile;
    register long code;
    if (FVIndex != aslot) {
	tfile = osi_UFSOpen(&cacheDev, volumeInode);
	if (!tfile) panic("open volumeinode");
	osi_Seek(tfile, sizeof(struct fvolume) * aslot);
	code = osi_Read(tfile, &staticFVolume, sizeof(struct fvolume));
	if (code != sizeof(struct fvolume)) panic("read volumeinfo");
	osi_Close(tfile);
	FVIndex = aslot;
    }
    return &staticFVolume;
}

struct fvolume *afs_FindVolCache(acell, avol)
    register long avol;
    register long acell; {
    register long i;
    register struct fvolume *tf;
    i = FVHash(acell,avol);
    for(i=fvTable[i]; i!=0; i=tf->next) {
	tf = afs_GetVolCache(i);
	if (tf->cell == acell && tf->volume == avol) return tf;
    }
    return (struct fvolume *) 0;
}

afs_WriteVolCache() {
    register struct osi_file *tfile;
    register long code;

    if (FVIndex < 0 || FVIndex >= afs_volCounter) panic("volumeinfo alloc");
    tfile = osi_UFSOpen(&cacheDev, volumeInode);
    if (!tfile) panic("open volumeinode");
    osi_Seek(tfile, sizeof(struct fvolume) * FVIndex);
    code = osi_Write(tfile, &staticFVolume, sizeof(struct fvolume));
    if (code != sizeof(struct fvolume)) panic("write volumeinfo");
    osi_Close(tfile);    
    return 0;
}

/* routine to get a slot from the volume list--must be called under afs_xvolume
    lock (write-locked). */
struct volume *afs_GetVolSlot() {
    register struct volume *tv, **lv;
    register long i;
    long bestTime;
    struct volume *bestVp, **bestLp;

    if (!afs_freeVolList) {
	/* get free slot */
	bestTime = 0x7fffffff;
	bestVp = 0;
	bestLp = 0;
	for(i=0;i<NVOLS;i++) {
	    lv = &afs_volumes[i];
	    for(tv = *lv; tv; lv = &tv->next, tv = *lv) {
		if (tv->refCount == 0) {    /* is this one available? */
		    if (tv->accessTime < bestTime) {	/* best one available? */
			bestTime = tv->accessTime;
			bestLp = lv;
			bestVp = tv;
		    }
		}
	    }
	}
	if (!bestVp) panic("getvolslot none");
	tv = bestVp;
	*bestLp = tv->next;
	if (tv->name) osi_Free(tv->name, strlen(tv->name)+1);
	tv->name = (char *) 0;
	/* now write out volume structure to file */
	if (tv->vtix < 0) {
	    tv->vtix = afs_volCounter++;
	    /* now put on hash chain */
	    i = FVHash(tv->cell, tv->volume);
	    staticFVolume.next = fvTable[i];
	    fvTable[i]=tv->vtix;
	}
	else {
	    /* haul the guy in from disk so we don't overwrite hash table next chain*/
	    afs_GetVolCache(tv->vtix);
	}
	FVIndex = tv->vtix;
	staticFVolume.volume = tv->volume;
	staticFVolume.cell = tv->cell;
	staticFVolume.mtpoint = tv->mtpoint;
	staticFVolume.dotdot = tv->dotdot;
	afs_WriteVolCache();
    }
    else {
	tv = afs_freeVolList;
	afs_freeVolList = tv->next;
    }
    return tv;
}

/* reset volume name to volume id mapping cache */
afs_CheckVolumeNames() {
    register long i;
    register struct volume *tv;
    register struct vcache *tvc;

    ObtainReadLock(&afs_xvolume);
    for(i=0;i<NVOLS;i++) {
	for(tv = afs_volumes[i]; tv; tv=tv->next) {
	    ObtainWriteLock(&tv->lock);
	    tv->states |= VRecheck;
	    if (tv->name) {
		osi_Free(tv->name, strlen(tv->name)+1);
		tv->name = (char *) 0;
	    }
	    ReleaseWriteLock(&tv->lock);
	}
    }
    ReleaseReadLock(&afs_xvolume);

    /* next ensure all mt points are re-evaluated */
    ObtainWriteLock(&afs_xvcache);
    for(i=0;i<VCSIZE;i++) {
	for(tvc = afs_vhashTable[i]; tvc; tvc=tvc->hnext) {
	    tvc->states &= ~CMValid;
	}
    }
    ReleaseWriteLock(&afs_xvcache);
}

static ServerDown(aserver)
    register struct server *aserver; {
    unsigned host_id;

    bcopy(&aserver->host, &host_id, sizeof host_id);
    printf("afs: Lost contact with server %d.%d.%d.%d\n", host_id >> 24,
		(host_id >> 16) & 0xff, (host_id >> 8) & 0xff, host_id & 0xff);
    aserver->isDown = 1;
}

/* return true if we have any callback promises from this server */
static HaveCallBacksFrom(aserver)
struct server *aserver; {
    long thost;
    register long now;
    register int i;
    register struct vcache *tvc;

    now = osi_Time();       /* for checking for expired callbacks */
/*    thost = aserver->host;  /* host we're looking for */
    bcopy(&aserver->host, &thost, sizeof thost);
    for(i=0;i<VCSIZE;i++) { /* for all guys in the hash table */
        for(tvc = afs_vhashTable[i]; tvc; tvc=tvc->hnext) {
            /* check to see if this entry has an unexpired callback promise
                from the required host */
            if (thost == tvc->callback && tvc->cbExpires >= now)
                return 1;
        }
    }
    return 0;
}

#define MINCHANGE 2
/* check down servers (if adown), or running servers (if !adown) */
afs_CheckServers(adown)
int     adown; {
    struct vrequest treq;
    register struct server *ts;
    register struct conn *tc;
    register long i;
    register long code;
    long start, end, delta;
    struct timeval tv;

    afs_InitReq(&treq, &osi_cred);
    ObtainReadLock(&afs_xserver);
    for(i=0;i<NSERVERS;i++) {
        for(ts = afs_servers[i]; ts; ts=ts->next) {

            /* see if this server is the type we're checking this time */
            if (adown != ts->isDown) continue;
            /* get a connection, even if host is down; bumps conn ref count */
            if (ts->cell) tc = afs_ConnByHost(ts, ts->cell->cell, &treq, 1);
            else continue;
            /* can't do anything without a connection */
            if (!tc) continue;

            if (ts->isDown || HaveCallBacksFrom(ts)) {
                if (ts->isDown) r_SetRetries(tc->id, 2);
                start = osi_Time();     /* time the gettimeofday call */
#ifdef  notdef
                code = AFS_GetTime(tc->id, &tv.tv_sec, &tv.tv_usec);
#else
                code = RViceGetTime(tc->id, &tv.tv_sec, &tv.tv_usec);
#endif
                end = osi_Time();
                /* if we're supposed to set the time, and the call worked quickly
             (same second response) and this is the host we use for the time
                 and the time is really different, then really set the time */
                if (code == 0 && start == end && afs_setTime != 0 &&
                    (afs_setTimeHost == 0 || tc->server->host ==
			afs_setTimeHost)) {
                    /* set the time */
                    delta = end - tv.tv_sec;   /* how many secs fast we are */
                    /* see if clock has changed enough to make it worthwhile */
                    if (delta >= MINCHANGE || delta <= -MINCHANGE) {
                        osi_SetTime(&tv);
                        if (delta > 0)
                            printf("afs: setting clock back %d seconds.\n", delta);
                        else
                            printf("afs: setting clock ahead %d seconds.\n", -delta);
                    }
/*                    afs_setTimeHost = tc->server->host;         */
		    bcopy(&tc->server->host, &afs_setTimeHost, sizeof(afs_setTimeHost));
                }
                r_ResetRetries(tc->id);
                if (code >= 0 && ts->isDown) {
		    unsigned host_id;
		    bcopy(&ts->host, &host_id, sizeof host_id);
                    /* server back up */
                    printf("afs: Server %d.%d.%d.%d back up\n",  host_id >> 24,
		(host_id >> 16) & 0xff, (host_id >> 8) & 0xff, host_id & 0xff);
                    ts->isDown = 0;
                    if (afs_waitForeverCount) {
                        osi_Wakeup(&afs_waitForever);
                    }
                }
                else if (code < 0 && !ts->isDown) {
                    /* server crashed */
                    ServerDown(ts);
                }
            }

            afs_PutConn(tc);    /* done with it now */
        }   /* for each server loop */
    }       /* for each server hash bucket loop */
    ReleaseReadLock(&afs_xserver);
}

int afs_ResetAccessCache(auser)
    register struct unixuser *auser; {
    register int i, j;
    register struct vcache *tvc;
    ObtainWriteLock(&afs_xvcache);
    for(i=0;i<VCSIZE;i++) {
	for(tvc=afs_vhashTable[i]; tvc; tvc=tvc->hnext) {
	    for(j=0;j<CPSIZE;j++) {
		/* really should do this under cache write lock, but that.
		    is hard to under locking hierarchy */
		if (tvc->randomUid[j] == auser->uid)
		    tvc->randomUid[j] = -1;
	    }
	}
    }
    ReleaseWriteLock(&afs_xvcache);
}

/* ensure all connections make use of new tokens.  Discard incorrectly-cached access info. */
int afs_ResetUserConns (auser)
    register struct unixuser *auser; {
    register int i;
    register struct server *ts;
    register struct conn *tc;

    ObtainWriteLock(&afs_xserver);
    ObtainWriteLock(&afs_xconn);
    for(i=0;i<NSERVERS;i++) {
	for(ts = afs_servers[i]; ts; ts=ts->next) {
	    for (tc = ts->conns; tc; tc=tc->next) {
		if (tc->user == auser)
		    tc->forceConnectFS = 1;
	    }
	}
    }
    ReleaseWriteLock(&afs_xconn);
    ReleaseWriteLock(&afs_xserver);
    afs_ResetAccessCache(auser);
}

/* warning: probably shouldn't bump refCount without afs_xuser lock */
struct unixuser *afs_FindUser(auid, acell)
    long acell;
    register long auid; {
    register struct unixuser *tu;
    register long i;
    i = UHash(auid);
    for(tu = afs_users[i]; tu; tu = tu->next) {
	if (tu->uid == auid && tu->cell == acell) {
	    tu->refCount++;
	    return tu;
	}
    }
    return (struct unixuser *) 0;
}

afs_GetKeys(awho, astp, alen, actp, aconn)
    register long awho;
    register char **astp;
    long *alen;
    register char **actp;
    struct r_connection *aconn; {
    /* Find the keys for this user */
    long host;
    register struct unixuser *tu;
    struct server *ts;

    host = r_PeerHost(aconn);
    ts = afs_FindServer(host);
    afs_dp("in getkeys, looking for user %x, host %x\n", awho, host);
    if (!ts) return -1;
    tu = afs_FindUser(awho, ts->cell->cell);  /* find this vice user on this host */
    if (!tu || !(tu->states & UHasTokens)) {
	/* generic fake tokens, not useful if vice is running authenticated */
	if (tu)	awho = tu->uid;			/* use uid, not pag */
	strcpy(*astp, "UID=");
	strcpy((*astp) + 4, afs_cv2string(awho));
	*alen = strlen(*astp)+1;
    }
    else {
	/* use the real token */
	*astp = (char *) &tu->st;
	*actp = (char *) tu->ct.HandShakeKey;
	*alen = sizeof(struct SecretToken);
    }
    if (tu) afs_PutUser(tu);
    return 0;
}

afs_ResourceInit() {
    register long i;
    register struct volume *tv;

    Lock_Init(&afs_xconn);
    Lock_Init(&afs_xuser);
    Lock_Init(&afs_xvolume);
    Lock_Init(&afs_xcell);
    Lock_Init(&afs_xserver);
    r_GetKeys = afs_GetKeys;
    r_nRetries = 30;
    afs_server = r_NewServer(htons(3535));

    /* create volume list structure */
    tv = (struct volume *) osi_Alloc(MAXVOLS * sizeof(struct volume));
    for(i=0;i<MAXVOLS-1;i++) tv[i].next = &tv[i+1];
    tv[MAXVOLS-1].next = (struct volume *) 0;
    afs_freeVolList = tv;
    for(i=0;i<NFENTRIES;i++)
	fvTable[i] = 0;

    osi_Wakeup(&afs_server);	/* wakeup anyone waiting for it */
    r_SocketListener(afs_server);
}

afs_RFTPInit() {
    rftp_window	= 5;		/* faster doesn't work in CMU's environment */
    afs_rftpServer = rftp_NewServer(htons(3536));
    osi_Wakeup(&afs_rftpServer);
    rftp_SocketListener(afs_rftpServer);
    rftp_KillServer(htons(3536));
}

struct cell *afs_GetCellByName(acellName)
    register char *acellName; {
    register struct cell *tc;
    ObtainWriteLock(&afs_xcell);
    for(tc = afs_cells; tc; tc=tc->next) {
	if (!strcmp(tc->cellName, acellName)) {
	    ReleaseWriteLock(&afs_xcell);
	    return tc;
	}
    }
    ReleaseWriteLock(&afs_xcell);
    return (struct cell *) 0;
}

struct cell *afs_GetCell(acell)
    register long acell; {
    register struct cell *tc;
    ObtainWriteLock(&afs_xcell);
    for(tc = afs_cells; tc; tc=tc->next) {
	if (tc->cell == acell) {
	    ReleaseWriteLock(&afs_xcell);
	    return tc;
	}
    }
    ReleaseWriteLock(&afs_xcell);
    return (struct cell *) 0;
}

struct cell *afs_NewCell(acellName, acellHosts)
    char *acellName;
    register long *acellHosts; {
    register struct cell *tc;
    register long i, temp;
    struct server *ts;

    ObtainWriteLock(&afs_xcell);
    for(tc = afs_cells; tc; tc = tc->next) {
	if (!strcmp(tc->cellName, acellName)) {
	    break;
	}
    }
    if (!tc) {
	tc = (struct cell *) osi_Alloc(sizeof(struct cell));
	tc->next = afs_cells;
	afs_cells = tc;
	tc->cellName = (char *) osi_Alloc(strlen(acellName)+1);
	strcpy(tc->cellName, acellName);
	tc->cell = cellCounter++;
	tc->states = 0;
    }
    bzero(tc->cellHosts, sizeof(tc->cellHosts));
    for(i=0; i<MAXHOSTS; i++) {
	temp = acellHosts[i];
	if (temp == 0) break;
	ts = tc->cellHosts[i] = afs_GetServer(temp, 0);
	ts->cell = tc;
    }
    ReleaseWriteLock(&afs_xcell);
    return tc;
}

struct unixuser *afs_GetUser(auid, acell)
    long acell;
    register long auid; {
    register struct unixuser *tu;
    register long i;
    i = UHash(auid);
    ObtainWriteLock(&afs_xuser);
    for(tu = afs_users[i]; tu; tu = tu->next) {
	if (tu->uid == auid && tu->cell == acell) {
	    tu->refCount++;
	    ReleaseWriteLock(&afs_xuser);
	    return tu;
	}
    }
    tu = (struct unixuser *) osi_Alloc(sizeof(struct unixuser));
    tu->next = afs_users[i];
    afs_users[i] = tu;
    tu->uid = auid;
    tu->cell = acell;
    tu->vid = UNDEFVID;
    tu->states = 0;
    tu->primary = 0;
    tu->refCount = 1;
    tu->tokenTime = osi_Time();
    ReleaseWriteLock(&afs_xuser);
    return tu;
}

afs_PutUser(au)
    register struct unixuser *au; {
    --au->refCount;
}

/* set the primary flag on a unixuser structure, ensuring that exactly one dude has
    the flag set at any time for a particular unix uid. */
afs_SetPrimary(au, aflag)
    register struct unixuser *au;
    register int aflag; {
    register struct unixuser *tu;
    register int i;
    struct unixuser *pu;

    i = UHash(au->uid);
    pu = (struct unixuser *) 0;
    ObtainWriteLock(&afs_xuser);
    /* see if anyone is this uid's primary cell yet; recording in pu the corresponding user */
    for(tu=afs_users[i]; tu; tu=tu->next) {
	if (tu->uid == au->uid && tu->primary) pu = tu;
    }
    if (pu && !(pu->states & UHasTokens)) {
	/* primary user has unlogged, don't treat him as primary any longer;
	    note that we want to treat him as primary until now, so that
	    people see a primary identity until now. */
	pu->primary = 0;
	pu = (struct unixuser *) 0;
    }
    if (aflag == 1) {
	/* setting au to be primary */
	if (pu) pu->primary = 0;
	au->primary = 1;
    }
    else if (aflag == 0) {
	/* we don't know if we're supposed to be primary or not */
	if (!pu || au == pu) {
	    au->primary = 1;
	}
	else au->primary = 0;
    }
    ReleaseWriteLock(&afs_xuser);
}

/* note that areq may be null, in which case we don't bother to set any
    request status information.
*/
struct volume *afs_GetVolume(afid, areq)
    struct vrequest *areq;
    register struct VenusFid *afid; {
    register struct volume *tv;
    register long i;
    char tbuffer[50];
    i = VHash(afid->Fid.Volume);
    ObtainWriteLock(&afs_xvolume);
    for(tv = afs_volumes[i]; tv; tv=tv->next) {
	if(tv->volume == afid->Fid.Volume && tv->cell == afid->Cell
	   && (tv->states & VRecheck) == 0) {
	    tv->refCount++;
	    ReleaseWriteLock(&afs_xvolume);
	    return tv;
	}
    }
    strcpy(tbuffer, afs_cv2string(afid->Fid.Volume));
    ReleaseWriteLock(&afs_xvolume);
    return afs_GetVolumeByName(tbuffer, afid->Cell, 0, areq);
}

struct volume *afs_GetVolumeByName(aname, acell, agood, areq)
    struct vrequest *areq;
    long acell;
    int agood;
    register char *aname; {
    register long code, i;
    register struct volume *tv;
    struct VolumeInfo volInfo;
    struct cell *tcell;
    long *p;
    struct fvolume *tf;
    register struct conn *tconn;
    struct vrequest treq;

    /* allow null request if we don't care about ENODEV/ETIMEDOUT distinction */
    if (!areq) {
	areq = &treq;
	afs_InitReq(&treq, &osi_cred);
    }

    ObtainWriteLock(&afs_xvolume);
    for(code=0;code<NVOLS;code++) {
	for(tv = afs_volumes[code]; tv; tv=tv->next) {
	    if (tv->name && !strcmp(aname,tv->name) && tv->cell == acell && (tv->states&VRecheck) == 0) {
		tv->refCount++;
		ReleaseWriteLock(&afs_xvolume);
		return tv;
	    }
	}
    }
    /* here we don't think we'll find one, so we make the call
	and then see if we have the appropriate volume. */
    ReleaseWriteLock(&afs_xvolume);
    tcell = afs_GetCell(acell);
    if (!tcell) {
	return (struct volume *) 0;
    }
    afs_dp("getvolumebyname about to look for volume %s\n", aname);
    do {
	tconn = afs_ConnByMHosts(tcell->cellHosts, tcell->cell, areq);
#ifdef	NINTERFACE
	if (tconn) code = AFS_GetVolumeInfo(tconn->id, aname, &volInfo);
#else
	if (tconn) code = RViceGetVolumeInfo(tconn->id, aname, &volInfo);
#endif
	else code = -1;
    } while (afs_Analyze(tconn, code, (struct ViceFid *) 0, areq));
    if (code) {
	return (struct volume *) 0;
    }
    /* otherwise we create the entry and insert this info */
    code = volInfo.Vid;	    /* volume number */
    i = VHash(code);
    ObtainWriteLock(&afs_xvolume);
    for(tv = afs_volumes[i]; tv; tv=tv->next) {
	if (tv->volume == code) {
	    break;
	}
    }
    if (!tv) {
	tv = afs_GetVolSlot();
	bzero(tv, sizeof(struct volume));
	tv->cell = tcell->cell;
	Lock_Init(&tv->lock);
	tv->volume = code;
	tv->next = afs_volumes[i];	/* thread into list */
	afs_volumes[i] = tv;
	tf = afs_FindVolCache(tv->cell, code);
	if (tf) {
	    tv->vtix = FVIndex;
	    tv->mtpoint = tf->mtpoint;
	    tv->dotdot = tf->dotdot;
	}
	else tv->vtix = -1;
    }
    p = (long *) &volInfo.Type0;
    tv->rwVol = p[readwriteVolume];
    tv->roVol = p[readonlyVolume];
    tv->backVol = p[backupVolume];
    if (volInfo.Type != RWVOL) tv->states |= VRO;
    if (volInfo.Type == BACKVOL) tv->states|= VBackup;
    tv->refCount++;
    tv->states &= ~VRecheck;	    /* just checked it */
    tv->accessTime = osi_Time();
    ReleaseWriteLock(&afs_xvolume);
    ObtainWriteLock(&tv->lock);
    InstallVolumeInfo(tv, &volInfo, acell);
    if (agood) {
	if (!tv->name) {
	    tv->name = osi_Alloc(strlen(aname) + 1);
	    strcpy(tv->name, aname);
	}
    }
    ReleaseWriteLock(&tv->lock);
    return tv;
}

/* call this with the volume structure locked */
InstallVolumeInfo(av, avi, acell)
    register struct volume *av;
    register struct VolumeInfo *avi; {
    register int i;
    register struct server *ts;
    register unsigned long *servers;
    servers = &avi->Server0;
    if (avi->ServerCount > MAXHOSTS) panic("InstallVolumeInfo");
    for(i=0;i<avi->ServerCount; i++) {
	ts = afs_GetServer(htonl(*servers), acell);
	av->serverHost[i] = ts;
	servers++;
    }
}

afs_PutVolume(av)
    register struct volume *av; {
    av->refCount--;
}

struct server *afs_FindServer (aserver)
    register long aserver; {
    register struct server *ts;
    register int i;
    i = SHash(aserver);
    for(ts = afs_servers[i]; ts; ts=ts->next) {
	if (ts->host == aserver) {
	    break;
	}
    }
    return ts;
}

struct server *afs_GetServer (aserver, acell)
    long acell;
    long aserver; {
    register struct server *ts;
    register int i;
    i = SHash(aserver);
    ObtainWriteLock(&afs_xserver);
    for(ts = afs_servers[i]; ts; ts=ts->next) {
	if (ts->host == aserver) {
	    ReleaseWriteLock(&afs_xserver);
	    return ts;
	}
    }
    ts = (struct server *) osi_Alloc(sizeof(struct server));
    bzero(ts, sizeof(struct server));
    if (acell) ts->cell = afs_GetCell(acell);
/*    ts->host = aserver;    */
    bcopy(&aserver, &ts->host, sizeof aserver);
    ts->next = afs_servers[i];
    afs_servers[i] = ts;
    ReleaseWriteLock(&afs_xserver);
    return ts;
}

struct conn *afs_Conn (afid, areq)
    register struct VenusFid *afid;
    register struct vrequest *areq; {
    register long cell;
    register struct volume *tv;
    register struct conn *tconn;

    cell = afid->Cell;
    tv = afs_GetVolume(afid, areq);
    if (!tv) {
	return (struct conn *) 0;
    }
    tconn = afs_ConnByMHosts(tv->serverHost, cell, areq);
    afs_PutVolume(tv);
    return tconn;
}

/* forceConnectFS is set whenever we must recompute the connection. UTokensBad
    is true only if we know that the tokens are bad.  We thus clear this flag
    when we get a new set of tokens.
    Having force... true and UTokensBad true simultaneously means that the tokens
    went bad and we're supposed to create a new, unauthenticated, connection.
*/
struct conn *afs_ConnByHost(aserver, acell, areq, aforce)
    register struct server *aserver;
    long acell;
    register struct vrequest *areq;
    int aforce; {
    register struct unixuser *tu;
    register struct conn *tc;
    struct conn *uc;
    int maxTag;

    if (aserver->isDown	&& !aforce) return (struct conn	*) 0;	/* known down */
    tu = afs_GetUser(areq->uid, acell);
    if (tu->states & UTokensBad) {
	/* tokens are bad, use user 0 instead */
	afs_PutUser(tu);
	tu = afs_GetUser(0, acell);
    }
    ObtainSharedLock(&afs_xconn);
    maxTag = -1;    /* biggest tag seen so far */
    uc = (struct conn *) 0;	/* conn when all are busy and can't make new one */
    for(tc = aserver->conns; tc; tc=tc->next) {
	if (tc->user == tu) {
	    /* if conn is not busy, we can use it */
	    if (!r_Busy(tc->id)) break;
	    uc = tc;	/* remember someone in case we're desperate */
	    if (tc->tag	> maxTag) maxTag = tc->tag;	/* compute largest tag seen */
	}
    }
    if (!tc && maxTag >= MAXMULTI-1) tc = uc;	/* too many conns, don't make new one */
    if (!tc) {
	UpgradeSToWLock(&afs_xconn);
	tc = (struct conn *) osi_Alloc(sizeof(struct conn));
	Lock_Init(&tc->lock);
	tc->user = tu;
	tc->tag = maxTag+1;
	tc->server = aserver;
	tc->refCount = 0;   /* bumped below */
	tc->forceConnectFS = 1;
	tc->id = (struct r_connection *) 0;
	tc->next = aserver->conns;
	aserver->conns = tc;
	ConvertWToSLock(&afs_xconn);
    }
    tc->refCount++;
    ReleaseSharedLock(&afs_xconn);

    if (tc->forceConnectFS) {
	ObtainWriteLock(&tc->lock);
	if (tc->id) r_FreeConnection(tc->id);
	tc->id = r_NewConn(afs_server, aserver->host, htons(2003));
	if (tu->vid != UNDEFVID) {
	    r_SetAuth(tc->id, tu->uid);
	}
	tc->forceConnectFS = 0;	/* apparently we're appropriately connected now */
	ReleaseWriteLock(&tc->lock);
    }
    afs_PutUser(tu);
    return tc;
}

struct conn *afs_ConnByMHosts(ahosts, acell, areq)
    register struct server *ahosts[];
    long acell;
    register struct vrequest *areq; {
    register long i;
    register struct conn *tconn;
    register struct server *ts;
    
    /* try to find any connection from the set */
    while (1) {
	for(i=0;i<MAXHOSTS;i++) {
	    if ((ts = ahosts[i]) == (struct server *) 0) break;
	    tconn = afs_ConnByHost(ts, acell, areq, 0);
	    if (tconn) return tconn;
	}
	if (afs_waitForever == 0) return (struct conn *) 0;
	/* otherwise, we wait for a server to come back */
	printf("afsd: waiting for a file server to come back up.\n");
	afs_waitForeverCount++;
	osi_Sleep(&afs_waitForever);
	afs_waitForeverCount--;
    }
}

/* you better have a write lock on the volume before calling this one */
afs_ResetVolumeInfo(tv)
    register struct volume *tv; {
    tv->states |= VRecheck;
    if (tv->name) osi_Free(tv->name, strlen(tv->name)+1);
    tv->name = (char *) 0;
}

afs_PutConn(ac)
    register struct conn *ac; {
    ac->refCount--;
}

/* the connection should be held (by refCount) (afs_Conn locks it).  It will
    be released when this routine is done. */
int afs_Analyze(aconn, acode, afid, areq)
    register struct conn *aconn;
    long acode;
    register struct vrequest *areq;
    struct VenusFid *afid; {
    long *pp;
    register long i, code;
    struct server *tsp;
    struct VolumeInfo volInfo;
    register struct volume *tvp;
    long returnCode;
    char tname[20];

    afs_dp("analyzing conn %x, code %d, for user %d\n", aconn, acode, areq->uid);

    if (!aconn) {
	afs_FinalizeReq(areq);
	areq->networkError = 1;
	return 0;				/* Fail if no connection. */
    }

    /* Find server associated with this connection. */
    tsp = aconn->server;

    /* If network troubles, mark server as having bogued out again. */
    if (acode < 0)
	ServerDown(tsp);

    if (acode == 0) {
	afs_PutConn(aconn);
	return 0;
    }
    afs_FinalizeReq(areq);
    if (acode == VBUSY) {
        printf("afs: Waiting for busy volume %u\n", (afid? afid->Fid.Volume : 0));
	if (areq->busyCount++ > 20) returnCode = 0;
	else {
	    VSleep(20);	    /* poll periodically */
	    returnCode = 1;
	}
    }
    else if (acode == VICETOKENDEAD) {	/* 1235 */
	struct unixuser *tu;
	tu = afs_FindUser(areq->uid, tsp->cell->cell);
	if (tu)
	    printf("afs: Tokens for user of AFS id %d have expired\n", tu->vid);
	else /* The else case shouldn't be possible and should probably be replaced by a panic? */
	    printf("afs: Tokens for user %d have expired\n", areq->uid);
	aconn->forceConnectFS = 0;		/* don't check until new tokens set */
	aconn->user->states |= UTokensBad;
	returnCode = 1;				/* Try again (as root). */
    }
    /* Check for access violation. */
    else if (acode == EACCES) {
	/* should mark access error in non-existent per-user global structure */
	areq->accessError = 1;
	returnCode = 0;
    }
    /* Check for bad volume data base / missing volume. */
    else if (acode == VNOVOL) {
	/* should set something so ENODEV can be returned by caller */
	areq->volumeError = 1;
        returnCode = 0;
    }
    /* Check for moving volume. */
    else if (acode == VMOVED) {
	returnCode = 0;	/* default */
        if (afid) {
	    strcpy(tname, afs_cv2string(afid->Fid.Volume));
#ifdef	NINTERFACE
	    code = AFS_GetVolumeInfo(aconn->id, tname, &volInfo);
#else
	    code = RViceGetVolumeInfo(aconn->id, tname, &volInfo);
#endif
	    printf("afs: Volume %u moved\n", afid->Fid.Volume);
	    if (code) {
		afs_dp("afs: can't find my way home (code %d)\n", code);
	    }
	    else {
		tvp = afs_GetVolume(afid, (struct vrequest *) 0);
		if (tvp) {
		    for(i=0;i<MAXHOSTS;i++) tvp->serverHost[i] = (struct server *) 0;
		    pp = (long *) (&volInfo.Server0);
		    for(i=0;i<volInfo.ServerCount;i++) {
			tvp->serverHost[i] = afs_GetServer(htonl(*pp), afid->Cell);
			pp++;
		    }
		    VSleep(1);	/* Better safe than sorry. */
		    returnCode = 1;	/* try again */
		}
	    }
	}
    }
    else if (acode >= 0) returnCode = 0;		/* Other random Vice error. */

    if (acode < 0) {
	/* If we get here, code < 0 and we have network/Server troubles.
	 * areq->networkError is not set here, since we always
	 * retry in case there is another server.  However, if we find
	 * no connection (aconn == 0) we set the networkError flag.
	 */
	tsp->isDown = 1;
	VSleep(1);		/* Just a hack for desperate times. */
	returnCode = 1;
    }
    
    /* now unlock the connection and return */
    afs_PutConn(aconn);
    return returnCode;
}

afs_SetLogFile(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 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. */
    afs_logFileInode = VTOI(filevp)->i_number;
    VN_RELE(filevp);
    tfile = osi_UFSOpen(&cacheDev, afs_logFileInode);
    osi_Truncate(tfile, 0);
    osi_Close(tfile);
    return 0;
}

int LogFileInUse = 0;
struct osi_file *Logtfile;

StartLogFile() {
    if (LogFileInUse)
	osi_Close(Logtfile);
    Logtfile = osi_UFSOpen(&cacheDev, afs_logFileInode);
    if (!Logtfile) panic("open LogFile");
    osi_Truncate(Logtfile, 0);	    /* to start afresh */
    LogFileInUse = 1;
}

EndLogFile() {
    if (!LogFileInUse)
	return;	    /* Should we complain here? */
    LogFileInUse = 0;
    osi_Close(Logtfile);
}

afs_dp(a,b,c,d,e,f,g,h,i,j)
long a,b,c,d,e,f,g,h,i,j; {
    if (afs_debug)
	if (LogFileInUse)
	    fprint(a,b,c,d,e,f,g,h,i,j);
	else
	    printf(a,b,c,d,e,f,g,h,i,j);
}

fprint(fmt, x1)
char *fmt;
unsigned x1;
{
    fprf(fmt, &x1);
}

fprf(fmt, adx)
	register char *fmt;
	register u_int *adx;
{
	register int b, c, i;
	char *s;
	int any;

loop:
	while ((c = *fmt++) != '%') {
		if(c == '\0')
			return;
		puttofile(c);
	}
again:
	c = *fmt++;
	/* THIS CODE IS VAX DEPENDENT IN HANDLING %l? AND %c */
	switch (c) {

	case 'l':
		goto again;
	case 'x': case 'X':
		b = 16;
		goto number;
	case 'd': case 'D':
	case 'u':		/* what a joke */
		b = 10;
		goto number;
	case 'o': case 'O':
		b = 8;
number:
		fprintn((u_long)*adx, b);
		break;
	case 'c':
		b = *adx;
		for (i = 24; i >= 0; i -= 8)
			if (c = (b >> i) & 0x7f)
				puttofile(c);
		break;
	case 'b':
		b = *adx++;
		s = (char *)*adx;
		fprintn((u_long)b, *s++);
		any = 0;
		if (b) {
			puttofile('<');
			while (i = *s++) {
				if (b & (1 << (i-1))) {
					if (any)
						puttofile(',');
					any = 1;
					for (; (c = *s) > 32; s++)
						puttofile(c);
				} else
					for (; *s > 32; s++)
						;
			}
			if (any)
				puttofile('>');
		}
		break;

	case 's':
		s = (char *)*adx;
		while (c = *s++)
			puttofile(c);
		break;

	case '%':
		puttofile('%');
		break;
	}
	adx++;
	goto loop;
}

/*
 * Printn prints a number n in base b.
 * We don't use recursion to avoid deep kernel stacks.
 */
fprintn(n, b)
	u_long n;
{
	char prbuf[11];
	register char *cp;

	if (b == 10 && (int)n < 0) {
		puttofile('-');
		n = (unsigned)(-(int)n);
	}
	cp = prbuf;
	do {
		*cp++ = "0123456789abcdef"[n%b];
		n /= b;
	} while (n);
	do
		puttofile(*--cp);
	while (cp > prbuf);
}

puttofile(c)
register int c;
{
    static int bufindex = 0;
    static char filebuffer[4096];
    int code;

    if (c == '\n') {
	filebuffer[bufindex++] = '\r';
	filebuffer[bufindex++] = c;
	code = osi_Write(Logtfile, filebuffer, bufindex);
	if (code != bufindex) printf("puttofile: osi_write"); /* the printf should be replaced by panic! */
	bufindex = 0;
	return;
    }
    filebuffer[bufindex++] = c;
}

/* run everywhere, checking locks */
afs_CheckLocks() {
    register int i;
    printf("Looking for locked data structures.\n");
    printf("conn %x, volume %x, user %x, cell %x, server %x\n",
	    afs_xconn, afs_xvolume, afs_xuser, afs_xcell, afs_xserver);
    {
	register struct vcache *tvc;
	for(i=0;i<VCSIZE;i++) {
	    for(tvc = afs_vhashTable[i]; tvc; tvc=tvc->hnext) {
		if (tvc->vrefCount)
		    printf("Stat cache entry at %x is held\n", tvc);
		if (CheckLock(&tvc->lock))
		    printf("Stat entry at %x is locked\n", tvc);
	    }
	}
    }
    {
	register struct dcache *tdc;
	for(i=0;i<afs_cacheFiles;i++) {
	    tdc = afs_indexTable[i];
	    if (tdc) {
		if (tdc->refCount)
		    printf("Disk entry %d at %x is held\n", i, tdc);
	    }
	    if (afs_indexFlags[i] & IFDataMod)
		printf("Disk entry %d at %x has IFDataMod flag set.\n", i, tdc);
	}
    }
    {
	register struct server *ts;
	register struct conn *tc;
	for(i=0;i<NSERVERS;i++) {
	    for(ts = afs_servers[i]; ts; ts=ts->next) {
		if (ts->isDown)
		    printf("Server entry for host %x (at %x) is marked down\n", ts->host, ts);
		for(tc = ts->conns; tc; tc=tc->next) {
		    if (CheckLock(&tc->lock))
			printf("conn at %x (server %x) is locked\n", tc, ts->host);
		    if (tc->refCount)
			printf("conn at %x (server %x) is held\n", tc, ts->host);
		}
	    }
	}
    }
    {
	register struct volume *tv;
	for(i=0;i<NVOLS;i++) {
	    for(tv = afs_volumes[i]; tv; tv=tv->next) {
		if (CheckLock(&tv->lock))
		    printf("volume at %x is locked\n", tv);
		if (tv->refCount)
		    printf("volume at %x is held\n", tv);
	    }
	}
    }
    {
	register struct unixuser *tu;
	for(i=0;i<NUSERS;i++) {
	    for(tu = afs_users[i]; tu; tu=tu->next) {
		if (tu->refCount) printf("user at %x is held\n", tu);
	    }
	}
    }
}
