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

#ifndef lint
static char *rcsid = "$Header:afs_pioctl.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/ioctl.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"
#include "../afs/prs_fs.h"
#include "../afs/dir.h"

struct VenusFid afs_rootFid;
long afs_waitForever=0;
short afs_waitForeverCount = 0;
extern struct osi_dev cacheDev;
extern long afs_cacheBlocks;
extern struct cell *afs_cells;
extern long afs_origCacheBlocks;
extern struct unixuser *afs_users[NUSERS];
extern struct server *afs_servers[NSERVERS];
extern struct lock afs_xvcache;		    /* lock: alloc new stat cache entries */
extern struct lock afs_xserver;
extern struct lock afs_xcell;
extern struct lock afs_xconn;
extern struct lock afs_xuser;
extern struct lock afs_xdcache;		    /* lock: alloc new disk cache entries */
extern long afs_mariner, afs_marinerHost;

static int PBogus(), PSetAcl(), PGetAcl(), PSetTokens(), PGetVolumeStatus();
static int PSetVolumeStatus(), PFlush(), PNewStatMount(), PGetTokens(), PUnlog();
static int PCheckServers(), PCheckVolNames(), PCheckAuth(), PFindVolume();
static int PViceAccess(), PWaitForever(), PSetCacheSize(), PPrefetch();
static int PRemoveCallBack(), PNewCell(), PListCells(), PRemoveMount();
static int PMariner(), PGetUserCell(), PGetWSCell(), PGetFileCell(), PVenusLogging(), PNoop();

static int (*(pioctlSw[]))() = {
    PBogus,			/* 0 */
    PSetAcl,			/* 1 */
    PGetAcl,			/* 2 */
    PSetTokens,			/* 3 */
    PGetVolumeStatus,		/* 4 */
    PSetVolumeStatus,		/* 5 */
    PFlush,			/* 6 */
    PBogus,			/* 7 */
    PGetTokens,			/* 8 */
    PUnlog,			/* 9 */
    PCheckServers,		/* 10 */
    PCheckVolNames,		/* 11 */
    PCheckAuth,			/* 12 */
    PBogus,			/* 13 -- used to be quick check time */
    PFindVolume,		/* 14*/
    PPrefetch,			/* 15 -- used to be prefetch */
    PBogus,			/* 16 -- used to be testing code */
    PNoop,			/* 17 -- used to be enable group */
    PNoop,		    	/* 18 -- used to be disable group */
    PBogus,			/* 19 -- used to be list group */
    PViceAccess,		/* 20 */
    PUnlog,			/* 21 -- unlog *is* unpag in this system */
    PBogus,			/* 22 -- used to be fast getwd */
    PWaitForever,		/* 23 */
    PSetCacheSize,		/* 24 */
    PRemoveCallBack,		/* 25 -- flush only the callback */
    PNewCell,			/* 26 */
    PListCells,		    	/* 27 */
    PRemoveMount,		/* 28 -- delete mount point */
    PNewStatMount,		/* 29 -- new style mount point stat */
    PGetFileCell,		/* 30 -- get cell name for input file */
    PGetWSCell,			/* 31 -- get cell name for workstation */
    PMariner,			/* 32 - set/get mariner host */
    PGetUserCell,		/* 33 -- get cell anem for user */
    PVenusLogging,		/* 34 - Enable/Disable logging */
};

/* stolen from vice.h */
#define _AFSIOCTL(id)  ((unsigned int ) _IOW(V, id, struct afs_ioctl))
#define _VALIDAFSIOCTL(com) (com >= _AFSIOCTL(0) && com <= _AFSIOCTL(255))

afs_pioctl_cleanup() {
    bzero(&afs_rootFid, sizeof(afs_rootFid));
    afs_waitForever=0;
    afs_waitForeverCount = 0;
}

HandleIoctl(avc, afile, acom, adata)
    register struct vcache *avc;
    struct file *afile;
    register long acom;
    struct afs_ioctl *adata; {
    register long code;

    code = 0;

    switch(acom & 0xff) {
	case 1:
	    avc->states |= CSafeStore;
	    break;

	/* case 2 used to be abort store, but this is no longer provided,
	    since it is impossible to implement under normal Unix.
	*/
	
	case 3: {
	    /* return the name of the cell this file is open on */
	    register struct cell *tcell;
	    register long i;
	    
	    tcell = afs_GetCell(avc->fid.Cell);
	    if (tcell) {
		i = strlen(tcell->cellName) + 1;    /* bytes to copy out */
		if (i > adata->out_size) {
		    /* 0 means we're not interested in the output */
		    if (adata->out_size != 0) code = EFAULT;
		}
		else {
		    /* do the copy */
		    code = copyout(tcell->cellName, adata->out, i);
		}
	    }
	    else code = ENOTTY;
	}
	break;

	default:
	    code = EINVAL;
	    break;
    }
    return code;		/* so far, none implemented */
}

/* unlike most calls here, this one uses u.u_error to return error conditions,
    since this is really an intercepted chapter 2 call, rather than a vnode
    interface call.
*/
afs_xioctl () {
    struct a {
	int fd;
	int com;
	caddr_t cmarg;
    } *uap;
    register struct file *fd;
    struct vcache *tvc;
    struct afs_ioctl data;
    int ioctlDone;
    register int cmd;
    
    ioctlDone = 0;
    uap = (struct a *)u.u_ap;

    fd = getf(uap->fd);
    if (!fd) return;

    cmd = uap->com;

    /* first determine whether this is any sort of vnode */
    if (fd->f_type == DTYPE_VNODE) {
	/* good, this is a vnode; next see if it is an AFS vnode */
	tvc = (struct vcache *) fd->f_data;	/* valid, given a vnode */
	if (tvc->v.v_op == afs_ops) {
	    /* This is an AFS vnode */
	    if (((cmd >> 8) & 0xff) == 'V') {
		/* this is a VICEIOCTL call */
		u.u_error = copyin(uap->cmarg, (caddr_t) &data, sizeof (data));
		if (u.u_error)
		    return;
		u.u_error = HandleIoctl(tvc, fd, cmd, &data);
		ioctlDone = 1;
	    }
	}
    }
    if (!ioctlDone) ioctl();
    return;
}

afs_pioctl() {
    extern rmt_ioctl1();
    struct a {
	char	*path;
	int	cmd;
	caddr_t cmarg;
	int	follow;
    } *uap;
    struct afs_ioctl data;
    int com;
    struct vnode *vp;

    uap = (struct a *) u.u_ap;
    if (uap->follow) uap->follow = 1;	/* compat. with old venus */
    com = uap->cmd;
    if (! _VALIDAFSIOCTL(com)) {
	u.u_error = EINVAL;
	return;
    }
    u.u_error = copyin(uap->cmarg, (caddr_t) &data, sizeof (data));
    if (u.u_error)
    	return;
    u.u_error = gop_lookupname(uap->path, AFS_UIOUSER, uap->follow,  (struct vnode **) 0, &vp);
    if (u.u_error)
	return(u.u_error);
    if (vp) {
	if (vp->v_op == afs_ops) {
	    u.u_error = HandlePioctl(vp, com, &data, uap->follow, u.u_cred);
	    VN_RELE(vp);
	    return;
	}
	VN_RELE(vp);
	u.u_error = EINVAL;	/* Not supported on non-remote files */
    }
}

HandlePioctl(avc, acom, ablob, afollow, acred)
    register struct vcache *avc;
    long acom;
    struct ucred *acred;
    register struct afs_ioctl *ablob;
    int afollow; {
    struct vrequest treq;
    register long code;
    register long function;
    long inSize, outSize;
    char *inData, *outData;
    afs_dp("in afs_pioctl (%x), com %d\n", avc, acom);
    
    afs_InitReq(&treq, acred);
    function = acom & 0xff;
    if (function >= (sizeof(pioctlSw) / sizeof(char *))) {
	return EINVAL;	/* out of range */
    }
    inSize = ablob->in_size;
    if (inSize >= PIGGYSIZE) return E2BIG;
    inData = osi_AllocSendSpace();
    if (inSize > 0) {
	code = copyin(ablob->in, inData, inSize);
    }
    else code = 0;
    if (code) {
	osi_FreeSendSpace(inData);
	return code;
    }
    outData = osi_AllocSendSpace();
    outSize = 0;
    code = (*pioctlSw[function])(avc, function, &treq, inData, outData, inSize, &outSize);
    osi_FreeSendSpace(inData);
    if (code == 0 && ablob->out_size > 0) {
	if (outSize > ablob->out_size) outSize = ablob->out_size;
	if (outSize >= PIGGYSIZE) code = E2BIG;
	else code = copyout(outData, ablob->out, outSize);
    }
    osi_FreeSendSpace(outData);
    return afs_CheckCode(code, &treq);
}

static PSetAcl(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long code;
    struct conn *tconn;
#ifdef	NINTERFACE
    struct AFSAccessList acl;
    struct AFSStoreStatus InStatus;
    struct AFSFetchStatus OutStatus;
#else
    struct CBS acl;
    struct BBS OptionalData;
    struct ViceStatus tstat;
    struct BD BDesc;
#endif

    if ((acl.SeqLen = strlen(ain)+1) > 1000) return EINVAL;
    acl.SeqBody = ain;
#ifdef	NINTERFACE
    acl.MaxSeqLen = acl.SeqLen + 1;
#else
    OptionalData.MaxSeqLen = 0;
    OptionalData.SeqLen = 0;
#endif
    do {
	tconn = afs_Conn (&avc->fid, areq);
	if (tconn) {
#ifdef	NINTERFACE
	    code = AFS_StoreACL(tconn->id, (struct AFSFid *) &avc->fid.Fid, &InStatus, &acl, &OutStatus);
#else
	    code = RViceStoreP(tconn->id, &avc->fid.Fid, StoreNeither, &acl,
			   &OptionalData, &BDesc, &tstat, 0);
#endif
	}
	else code = -1;
    } while (afs_Analyze(tconn, code, &avc->fid, areq));
    /* now we've forgotten all of the access info */
    avc->callback = 0;
    return code;
};

static PGetAcl(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
#ifdef	NINTERFACE
    struct AFSAccessList acl;
    struct AFSFetchStatus OutStatus;
#else
    BBS acl, OptionalData;
    ViceFid bidfid;
    ViceStatus tstat;
    struct BD BDesc;
#endif
    long code;
    struct conn *tconn;

#ifndef	NINTERFACE
    bidfid.Volume = 0;
#endif
    acl.MaxSeqLen = 1000;
    acl.SeqLen = 0;
    acl.SeqBody = aout;
    do {
	tconn = afs_Conn(&avc->fid, areq);
	if (tconn) {
	    *aout = 0;
#ifdef	NINTERFACE
	    code = AFS_FetchACL(tconn->id, (struct AFSFid *) &avc->fid.Fid, &acl, &OutStatus);
#else
	    OptionalData.MaxSeqLen = 0;
	    OptionalData.SeqLen = 0;
	    OptionalData.SeqBody = (char *) 1;	/* prevent xdr malloc */
	    code = RViceFetchP(tconn->id, &avc->fid.Fid, &bidfid, FetchNoData,
			       &BDesc, &acl, &OptionalData, &tstat);
#endif
	}
	else code = -1;
    } while (afs_Analyze(tconn, code, &avc->fid, areq));
    if (code == 0) *aoutSize = (acl.SeqLen == 0 ? 1 : acl.SeqLen);
    return code;
}

static PNoop() {
    return 0;
}

static PBogus() {
    return EINVAL;
}

static PGetFileCell(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register struct cell *tcell;

    tcell = afs_GetCell(avc->fid.Cell);
    if (!tcell) return ESRCH;
    strcpy(aout, tcell->cellName);
    *aoutSize = strlen(aout) + 1;
    return 0;
}

static PGetWSCell(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register struct cell *tcell, *cellOne;

    ObtainReadLock(&afs_xcell);
    cellOne = (struct cell *) 0;
    for(tcell = afs_cells; tcell; tcell=tcell->next) {
	if (tcell->states & CPrimary) break;
	if (tcell->cell == 1) cellOne = tcell;
    }
    ReleaseReadLock(&afs_xcell);
    if (!tcell)	{	    /* no primary cell, use cell #1 */
	if (!cellOne) return ESRCH;
	tcell = cellOne;
    }
    strcpy(aout, tcell->cellName);
    *aoutSize = strlen(aout) + 1;
    return 0;
}

static PGetUserCell(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long i;
    register struct unixuser *tu;
    register struct cell *tcell;

    /* return the cell name of the primary cell for this user */
    i = UHash(areq->uid);
    ObtainWriteLock(&afs_xuser);
    for(tu = afs_users[i]; tu; tu = tu->next) {
	if (tu->uid == areq->uid && tu->primary) {
	    tu->refCount++;
	    ReleaseWriteLock(&afs_xuser);
	    break;
	}
    }
    if (tu) {
	tcell = afs_GetCell(tu->cell);
	afs_PutUser(tu);
	if (!tcell) return ESRCH;
	else {
	    strcpy(aout, tcell->cellName);
	    *aoutSize =	strlen(aout)+1;	    /* 1 for the null */
	}
    }
    else {
	ReleaseWriteLock(&afs_xuser);
	*aout = 0;
	*aoutSize = 1;
    }
    return 0;
}

static PSetTokens(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    long i;
    register long code;
    struct SecretToken secret;
    struct CBS cbsSecret;
    register struct conn *tc;
    register struct unixuser *tu;
    struct ClearToken clear;
    register struct cell *tcell;
    long flag;

    bcopy(ain, &i, sizeof(long));
    ain += sizeof(long);
    if (i != sizeof(struct SecretToken)) {
	return EINVAL;
    }
    bcopy(ain, &secret, sizeof(struct SecretToken));
    ain += sizeof(struct SecretToken);
    bcopy(ain, &i, sizeof(long));
    ain += sizeof(long);
    if (i != sizeof(struct ClearToken)) {
	return EINVAL;
    }
    bcopy(ain, &clear, sizeof(struct ClearToken));
    ain += sizeof(struct ClearToken);
    if (ainSize != 2*sizeof(long) + sizeof(struct SecretToken) + sizeof(struct ClearToken)) {
	/* still stuff left?  we've got primary flag and cell name.  Set these */
	bcopy(ain, &flag, sizeof(long));		/* primary id flag */
	ain += sizeof(long);			/* skip id field */
	/* rest is cell name, look it up */
	tcell = afs_GetCellByName(ain);
	if (tcell) {
	    i = tcell->cell;
	}
	else {
	    return ESRCH;
	}
    }
    else {
	/* default to cell 1, primary id */
	flag = 1;		/* primary id */
	i = 1;		/* cell number */
	tcell = afs_GetCell(1);
	if (!tcell) return EIO;
    }

    /* now we just set the tokens */
    tu = afs_GetUser(areq->uid,	i); /* i has the cell # */

    /* next we check the tokens */
    cbsSecret.SeqLen = sizeof(struct SecretToken);
    cbsSecret.SeqBody = (char *) &secret;
    do {
	tc = afs_ConnByMHosts(tcell->cellHosts, tcell->cell, areq);
#ifdef	NINTERFACE
	if (tc) code = AFS_CheckToken(tc->id, clear.ViceId, &cbsSecret);
#else
	if (tc) code = RViceCheckToken(tc->id, clear.ViceId, &cbsSecret);
#endif
	else code = -1;
    } while(afs_Analyze(tc, code, 0, areq));

    if (code) {
	afs_PutUser(tu);
	return code;
    }

    tu->vid = clear.ViceId;
    tu->st = secret;
    tu->ct = clear;
    tu->states |= UHasTokens;
    tu->states &= ~UTokensBad;
    afs_SetPrimary(tu, flag);
    tu->tokenTime =osi_Time();
    afs_ResetUserConns(tu);
    afs_PutUser(tu);
    return 0;
}

static PGetVolumeStatus(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    char volName[32];
    char offLineMsg[256];
    char motd[256];
    register struct conn *tc;
    register long code;
    struct VolumeStatus volstat;
    register char *cp;
    struct BBS Name, OfflineMsg, MOTD;

    Name.SeqBody = (char *) volName;
    Name.MaxSeqLen = 32;
    Name.SeqLen = 0;
    OfflineMsg.SeqBody = (char *) offLineMsg;
    OfflineMsg.MaxSeqLen = 256;
    OfflineMsg.SeqLen = 0;
    MOTD.SeqBody = (char *) motd;
    MOTD.MaxSeqLen = 256;
    MOTD.SeqLen = 0;
    do {
	tc = afs_Conn(&avc->fid, areq);
	if (tc)
#ifdef	NINTERFACE
	    code = AFS_GetVolumeStatus(tc->id, avc->fid.Fid.Volume, &volstat,
					&Name, &OfflineMsg, &MOTD);
#else
	    code = RViceGetVolumeStatus(tc->id, avc->fid.Fid.Volume, &volstat,
					&Name, &OfflineMsg, &MOTD);
#endif
	else code = -1;
    } while (afs_Analyze(tc, code, &avc->fid, areq));
    if (code) return code;
    /* Copy all this junk into msg->im_data, keeping track of the lengths. */
    cp = aout;
    bcopy(&volstat, cp, sizeof(VolumeStatus));
    cp += sizeof(VolumeStatus);
    strcpy(cp, volName);
    cp += strlen(volName)+1;
    strcpy(cp, offLineMsg);
    cp += strlen(offLineMsg)+1;
    strcpy(cp, motd);
    cp += strlen(motd)+1;
    *aoutSize = (cp - aout);
    return 0;
}

static PSetVolumeStatus(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    char volName[32];
    char offLineMsg[256];
    char motd[256];
    register struct conn *tc;
    register long code;
    struct VolumeStatus volstat;
    register char *cp;
    struct BBS Name, OfflineMsg, MOTD;

    /* Copy the junk out, using cp as a roving pointer. */
    cp = ain;
    bcopy(cp, &volstat, sizeof(VolumeStatus));
    cp += sizeof(VolumeStatus);
    strcpy(volName, cp);
    Name.SeqLen = strlen(volName)+1;
    cp += Name.SeqLen;
    strcpy(offLineMsg, cp);
    OfflineMsg.SeqLen = strlen(offLineMsg)+1;
    cp += OfflineMsg.SeqLen;
    strcpy(motd, cp);
    MOTD.SeqLen = strlen(motd)+1;
    Name.SeqBody = (char *) volName;
    Name.MaxSeqLen = 32;
    OfflineMsg.SeqBody = (char *) offLineMsg;
    OfflineMsg.MaxSeqLen = 256;
    MOTD.SeqBody = (char *) motd;
    MOTD.MaxSeqLen = 256;
    do {
	tc = afs_Conn(&avc->fid, areq);
	if (tc)
#ifdef	NINTERFACE
	    code = AFS_SetVolumeStatus(tc->id, avc->fid.Fid.Volume,
					&volstat, &Name, &OfflineMsg, &MOTD);
#else
	    code = RViceSetVolumeStatus(tc->id, avc->fid.Fid.Volume,
					&volstat, &Name, &OfflineMsg, &MOTD);
#endif
	else code = -1;
    } while (afs_Analyze(tc, code, &avc->fid, areq));
    if (code) return code;
    cp = aout;
    bcopy(&volstat, cp, sizeof(VolumeStatus));
    cp += sizeof(VolumeStatus);
    strcpy(cp, volName);
    cp += strlen(volName)+1;
    strcpy(cp, offLineMsg);
    cp += strlen(offLineMsg)+1;
    strcpy(cp, motd);
    cp += strlen(motd)+1;
    *aoutSize = cp - aout;
    return 0;
}

static PFlush(avc, afun, areq, ain, aout, ainSize, aoutSize)
    register struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register int i;
    short us;
    struct VenusFid tfid;
    register struct dcache *tdc;
    /* note how none of VCHash, DCHash or DVHash use Fid.Unique as part of
      their hashing algorithm.  Thus, this invalidation code doesn't
      move items from one bucket to another.  Don't forget to change this
      if you change the hash algorithms */
    ObtainWriteLock(&avc->lock);
    avc->states	&= ~CStatd;	/* next reference will re-stat cache entry */
    /* now find the disk cache entries */
    afs_TryToSmush(avc);
    ReleaseWriteLock(&avc->lock);
    return 0;
}

static PNewStatMount(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long code;
    register struct vcache *tvc;
    register struct dcache *tdc;
    struct VenusFid tfid;
    long offset, len;

    code = afs_VerifyVCache(avc, areq);
    if (code) return code;
    if (vType(avc) != VDIR) {
	return ENOTDIR;
    }
    tdc = afs_GetDCache(avc, 0, areq, &offset, &len, 1);
    if (!tdc) return ENOENT;
    code = dir_Lookup(&tdc->f.inode, ain, &tfid.Fid);
    if (code) {
	afs_PutDCache(tdc);
	return code;
    }
    tfid.Cell = avc->fid.Cell;
    tfid.Fid.Volume = avc->fid.Fid.Volume;
    afs_PutDCache(tdc);	    /* we're done with the data */
    tvc = afs_GetVCache(&tfid, areq);
    if (!tvc) return ENOENT;
    if (vType(tvc) != VLNK) {
	afs_PutVCache(tvc);
	return EINVAL;
    }
    ObtainWriteLock(&tvc->lock);
    code = afs_HandleLink(tvc, areq);
    if (code == 0) {
	if (tvc->linkData) {
	    /* we have the data */
	    strcpy(aout, tvc->linkData);
	    *aoutSize = strlen(tvc->linkData)+1;
	}
	else code = EIO;
    }
    ReleaseWriteLock(&tvc->lock);
    afs_PutVCache(tvc);
    return code;
}

static PGetTokens(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register struct cell *tcell;
    register long i;
    register struct unixuser *tu;
    register char *cp;
    long iterator;
    int newStyle;

    /* weird interface.  If input parameter is present, it is an integer and
	we're supposed to return the parm'th tokens for this unix uid.
	If not present, we just return tokens for cell 1.
	If counter out of bounds, return EDOM.
	If no tokens for the particular cell, return ENOTCONN.
	Also, if this mysterious parm is present, we return, along with the
	tokens, the primary cell indicator (a long 0) and the cell name
	at the end, in that order.
    */
    if (newStyle = (ainSize > 0)) {
	bcopy(ain, &iterator, sizeof(long));
    }
    i = UHash(areq->uid);
    ObtainReadLock(&afs_xuser);
    for(tu = afs_users[i]; tu; tu=tu->next) {
	if (newStyle) {
	    if (tu->uid == areq->uid && (tu->states & UHasTokens)) {
		if (iterator-- == 0) break;	/* are we done yet? */
	    }
	}
	else {
	    if (tu->uid == areq->uid && tu->cell == 1) break;
	}
    }
    if (tu) tu->refCount++;
    ReleaseReadLock(&afs_xuser);
    if (!tu) {
	return EDOM;
    }
    if ((tu->states & UHasTokens) == 0) {
	afs_PutUser(tu);
	return ENOTCONN;
    }
    /* use iterator for temp */
    cp = aout;
    iterator = sizeof(struct SecretToken);  /* send size */
    bcopy(&iterator, cp, sizeof(long));
    cp += sizeof(long);
    bcopy(&tu->st, cp, sizeof(struct SecretToken));	/* copy out st */
    cp += sizeof(struct SecretToken);
    iterator = sizeof(struct ClearToken);
    bcopy(&iterator, cp, sizeof(long));
    cp += sizeof(long);
    bcopy(&tu->ct, cp, sizeof(struct ClearToken));
    cp += sizeof(struct ClearToken);
    if (newStyle) {
	/* put out primary id and cell name, too */
	iterator = tu->primary;
	bcopy(&iterator, cp, sizeof(long));
	cp += sizeof(long);
	tcell = afs_GetCell(tu->cell);
	if (tcell) {
	    strcpy(cp, tcell->cellName);
	    cp += strlen(tcell->cellName)+1;
	}
	else *cp++ = 0;
    }
    *aoutSize = cp - aout;
    afs_PutUser(tu);
    return 0;
}

static PUnlog(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long i;
    register struct unixuser *tu;

    i = UHash(areq->uid);
    ObtainWriteLock(&afs_xuser);
    for(tu=afs_users[i]; tu; tu=tu->next) {
	if (tu->uid == areq->uid) {
	    tu->vid = UNDEFVID;
	    tu->states &= ~UHasTokens;
	    afs_ResetUserConns(tu);
	}
    }
    ReleaseWriteLock(&afs_xuser);
    return 0;
}

static PMariner(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    long newHostAddr;
    long oldHostAddr;
    
    if (afs_mariner)
	bcopy(&afs_marinerHost, &oldHostAddr, sizeof(long));
    else
	oldHostAddr = 0xffffffff;   /* disabled */
    
    bcopy(ain, &newHostAddr, sizeof(long));
    if (newHostAddr == 0xffffffff) {
	/* disable mariner operations */
	afs_mariner = 0;
    }
    else if (newHostAddr) {
	afs_mariner = 1;
	afs_marinerHost = newHostAddr;
    }
    bcopy(&oldHostAddr, aout, sizeof(long));
    *aoutSize = sizeof(long);
    return 0;
}

static PCheckServers(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register char *cp;
    register int i;
    register struct server *ts;
    afs_CheckServers(1);	/* check down servers */
    afs_CheckServers(0);	/* check up servers */
    /* now return the current down server list */
    cp = aout;
    ObtainReadLock(&afs_xserver);
    for(i=0;i<NSERVERS;i++) {
	for(ts = afs_servers[i]; ts; ts=ts->next) {
	    if (ts->isDown) {
		bcopy(&ts->host, cp, sizeof(long));
		cp += sizeof(long);
	    }
	}
    }
    ReleaseReadLock(&afs_xserver);
    *aoutSize = cp - aout;
    return 0;
}

static PCheckVolNames(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    afs_CheckRootVolume();
    afs_CheckVolumeNames();
    return 0;
}

static PCheckAuth(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register int i;
    register struct server *ts;
    register struct conn *tc;
    register struct unixuser *tu;
    long retValue;

    retValue = 0;
    tu = afs_GetUser(areq->uid,	1);	/* check local cell authentication */
    if (!tu) retValue = EACCES;
    else {
	/* we have a user */
	ObtainReadLock(&afs_xserver);
	ObtainReadLock(&afs_xconn);
	/* any tokens set? */
	if ((tu->states	& UHasTokens) == 0) retValue = EACCES;
	/* all connections in cell 1 working? */
	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 == tu && (tu->states & UTokensBad)) retValue = EACCES;
		}
	    }
	}
	ReleaseReadLock(&afs_xserver);
	ReleaseReadLock(&afs_xconn);
	afs_PutUser(tu);
    }
    bcopy(&retValue, aout, sizeof(long));
    *aoutSize = sizeof(long);
    return 0;
}

static PPrefetch(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    afs_BQueue(BOP_FETCH, avc, 0, 0, (char *) 0);
    return 0;
}

static PFindVolume(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register struct volume *tvp;
    register struct server *ts;
    register long i;
    register char *cp;
    
    tvp = afs_GetVolume(&avc->fid, areq);
    if (tvp) {
	cp = aout;
	for(i=0;i<MAXHOSTS;i++) {
	    ts = tvp->serverHost[i];
	    if (!ts) break;
	    bcopy(&ts->host, cp, sizeof(long));
	    cp += sizeof(long);
	}
	*aoutSize = cp - aout;
	afs_PutVolume(tvp);
	return 0;
    }
    return ENODEV;
}

static PViceAccess(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long code;
    long temp;
    
    bcopy(ain, &temp, sizeof(long));
    code = afs_VerifyVCache(avc, areq);
    if (code) return code;
    code = afs_AccessOK(avc,temp, areq);
    if (code) return 0;
    else return EACCES;
}

static PWaitForever(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    return EINVAL;	/* never worked */
}

static PSetCacheSize(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    long newValue;
    
    bcopy(ain, &newValue, sizeof(long));
    if (newValue == 0) afs_cacheBlocks = afs_origCacheBlocks;
    else afs_cacheBlocks = newValue;
    afs_CheckSize(0);
    return 0;
}

static PRemoveCallBack(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register struct conn *tc;
    register long code;
#ifdef NINTERFACE
    struct AFSFid * Fids_Array[2];
    struct AFSCallBack CallBacks_Array[2];
#endif

    if (avc->states & CRO) return 0;	/* read-only-ness can't change */
    ObtainWriteLock(&avc->lock);
    if (avc->callback) {
	do {
	    tc = afs_Conn(&avc->fid, areq);
#ifdef	NINTERFACE
	    Fids_Array[0] = (struct AFSFid *) &avc->fid.Fid;
	    if (tc) code = AFS_GiveUpCallBacks(tc->id, Fids_Array, 1, CallBacks_Array, 1);
#else
	    if (tc) code = RViceRemoveCallBack(tc->id, &avc->fid.Fid);
#endif
	    /* don't set code on failure since we wouldn't use it */
	} while (afs_Analyze(tc, code, &avc->fid, areq));
	avc->callback = 0;
    }
    ReleaseWriteLock(&avc->lock);
    return 0;
}

static PNewCell(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    /* create a new cell */
    long cellHosts[MAXHOSTS];	/* about 8 */
    register struct cell *tcell;
    register long code;
    
    bcopy(ain, cellHosts, MAXHOSTS*sizeof(long));
    tcell = afs_NewCell(ain + MAXHOSTS*sizeof(long), cellHosts);
    if (tcell) code = 0;
    else code = EINVAL;
    return code;
}

static PListCells(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    char *ain, *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    long whichCell;
    register struct cell *tcell;
    register long i;
    register char *cp;

    bcopy(ain, &whichCell, sizeof(long));
    ObtainReadLock(&afs_xcell);
    for(tcell=afs_cells; tcell; tcell=tcell->next) {
	if (whichCell == 0) break;
	whichCell--;
    }
    if (tcell) {
	cp = aout;
	bzero(cp, MAXHOSTS*sizeof(long));
	for(i=0;i<MAXHOSTS;i++) {
	    if (tcell->cellHosts[i] == 0) break;
	    bcopy(&tcell->cellHosts[i]->host, cp, sizeof(long));
	    cp += sizeof(long);
	}
	cp = aout + MAXHOSTS*sizeof(long);
	strcpy(cp, tcell->cellName);
	cp += strlen(tcell->cellName)+1;
	*aoutSize = cp - aout;
    }
    ReleaseReadLock(&afs_xcell);
    if (tcell) return 0;
    else return EDOM;
}

static PRemoveMount(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    register long code;
    long offset, len;
    register struct conn *tc;
    register struct dcache *tdc;
#ifdef	NINTERFACE
    struct AFSFetchStatus OutDirStatus;
#endif
    struct ViceStatus dstat;

    /* "ain" is the name of the file in this dir to remove */

    code = afs_VerifyVCache(avc, areq);
    if (code) return code;
    if (vType(avc) != VDIR) return ENOTDIR;
    tdc	= afs_GetDCache(avc, 0,	areq, &offset,	&len, 1);	/* test for error below */
    ObtainWriteLock(&avc->lock);
    do {
	tc = afs_Conn(&avc->fid, areq);
	if (tc) {
#ifdef	NINTERFACE
	    code = AFS_RemoveFile(tc->id, (struct AFSFid *) &avc->fid.Fid, ain, &OutDirStatus);
	    AFSFetchStToViceSt(&OutDirStatus, &dstat);	    
#else
	    code = RViceRemove(tc->id, &avc->fid.Fid, ain, &dstat);
#endif
	}
	else code = -1;
    } while (afs_Analyze(tc, code, &avc->fid, areq));
    if (code) {
	if (tdc) afs_PutDCache(tdc);
	ReleaseWriteLock(&avc->lock);
	return afs_CheckCode(code, areq);
    }
    if (tdc) {
	/* we have the thing in the cache */
	if (afs_LocalHero(avc, tdc, &dstat, 1)) {
	    /* we can do it locally */
	    code = dir_Delete(&tdc->f.inode, ain);
	    if (code) tdc->f.versionNo = -1;	/* surprise error -- invalid value */
	    /* DEntryMod set by local hero */
	}
	afs_PutDCache(tdc);	/* drop ref count */
    }
    ReleaseWriteLock(&avc->lock);
    return 0;    
}

static PVenusLogging(avc, afun, areq, ain, aout, ainSize, aoutSize)
    struct vcache *avc;
    int afun;
    struct vrequest *areq;
    register char *ain;
    char *aout;
    long ainSize;
    long *aoutSize;	/* set this */ {
    long code, inputValue, outValue;
    long afsdeb, rdeb, rftpdeb, log;
    extern long afs_debug, r_debug, rftp_debug, LogFileInUse;

    bcopy(ain, &inputValue,sizeof(long));
    afsdeb = (inputValue >> 24) & 0xff;
    rdeb = (inputValue >> 16) & 0xff;
    rftpdeb = (inputValue >> 8) & 0xff;
    log = inputValue & 0xff;
    if (afsdeb != 99) afs_debug = afsdeb;
    if (rdeb != 99) r_debug = rdeb;
    if (rftpdeb != 99) rftp_debug = rftpdeb;    
    if (log == 1) StartLogFile();
    else if (log == 0) {
	EndLogFile();
	afs_debug = r_debug = rftp_debug = 0;	/* Automatic shuting down with "off" */
    }
    else if	(log ==	99) {	
	outValue = (afs_debug << 24) + (r_debug << 16) + (rftp_debug << 8) + (LogFileInUse & 0xff);
	bcopy(&outValue, aout, sizeof(long));
    }
    return 0;
}
