/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include "afs/param.h"


#include "afs/sysincludes.h"	/* Standard vendor system headers */
#include "afsincludes.h"	/* Afs-based standard headers */
#include "afs/afs_stats.h"
#include "rx/rx_globals.h"
#if !defined(UKERNEL) && !defined(AFS_LINUX_ENV)
#include "net/if.h"
#ifdef AFS_SGI_ENV
#include "h/hashing.h"
#endif
#if !defined(AFS_HPUX110_ENV) && !defined(AFS_DARWIN_ENV)
#include "netinet/in_var.h"
#endif
#endif /* !defined(UKERNEL) */


struct afs_icl_set *afs_iclSetp = (struct afs_icl_set *)0;
struct afs_icl_set *afs_iclLongTermSetp = (struct afs_icl_set *)0;

int afs_icl_sizeofLong = ICL_LONG;

int afs_icl_inited = 0;

/* init function, called once, under afs_icl_lock */
static int
afs_icl_Init(void)
{
    afs_icl_inited = 1;
    return 0;
}

/* Function called at shutdown - zap everything */
void
shutdown_icl(void)
{
    struct afs_icl_log *logp;
    struct afs_icl_set *setp;

    if (afs_iclSetp) {
	setp = afs_iclSetp;  /* "cm" */
	afs_iclSetp = NULL;
	afs_icl_SetFree(setp);
    }
    if (afs_iclLongTermSetp) {
	setp = afs_iclLongTermSetp; /* "cmlongterm" */
	afs_iclLongTermSetp = NULL;
	afs_icl_SetFree(setp);
    }
    logp = afs_icl_FindLog("cmfx");
    if (logp) {
	/* Release the reference from Find, and the initial one */
	afs_icl_LogFree(logp);
	afs_icl_LogFree(logp);
    }
}

int
afs_icl_InitLogs(void)
{
    struct afs_icl_log *logp;
    int code;

    /* initialize the ICL system */
    code = afs_icl_CreateLog("cmfx", 60 * 1024, &logp);
    if (code)
	return code;

    code = afs_icl_CreateSetWithFlags("cm", logp, NULL,
				      ICL_CRSET_FLAG_DEFAULT_OFF,
				      &afs_iclSetp);
    if (code)
	return code;

    code = afs_icl_CreateSet("cmlongterm", logp, NULL,
			     &afs_iclLongTermSetp);
    return code;
}


struct afs_icl_log *afs_icl_FindLog(char *);
struct afs_icl_set *afs_icl_FindSet(char *);


#ifdef AFS_DARWIN100_ENV
#define AFSKPTR(X) k ## X
int
Afscall_icl(long opcode, long p1, long p2, long p3, long p4, long *retval)
{
    return Afscall64_icl(opcode,
			 CAST_USER_ADDR_T((p1)),
			 CAST_USER_ADDR_T((p2)),
			 CAST_USER_ADDR_T((p3)),
			 CAST_USER_ADDR_T((p4)),
			 retval);
}
#else
#define AFSKPTR(X) ((caddr_t)X)
#endif

int
#ifdef AFS_DARWIN100_ENV
Afscall64_icl(int opcode, user_addr_t kp1, user_addr_t kp2, user_addr_t kp3, user_addr_t kp4, int *retval)
#else
Afscall_icl(long opcode, long p1, long p2, long p3, long p4, long *retval)
#endif
{
    afs_int32 *lp, elts, flags;
    afs_int32 code;
    struct afs_icl_log *logp;
    struct afs_icl_set *setp;
#if defined(AFS_SGI_ENV) || defined(AFS_SUN5_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
    size_t temp;
#else /* AFS_SGI_ENV */
#if defined(AFS_AIX51_ENV) && defined(AFS_64BIT_KERNEL)
    afs_uint64 temp;
#else
    afs_uint32 temp;
#endif
#endif /* AFS_SGI_ENV */
    char tname[65];
    afs_int32 startCookie;
    afs_int32 allocated;
    struct afs_icl_log *tlp;
#ifdef AFS_DARWIN100_ENV
    afs_uint32 p1 = (afs_uint32)kp1;
    afs_uint32 p2 = (afs_uint32)kp2;
    afs_uint32 p3 = (afs_uint32)kp3;
    afs_uint32 p4 = (afs_uint32)kp4;
#endif

#ifdef	AFS_SUN5_ENV
    if (!afs_suser(CRED())) {	/* only root can run this code */
	return (EACCES);
    }
#else
    if (!afs_suser(NULL)) {	/* only root can run this code */
#if defined(KERNEL_HAVE_UERROR)
	setuerror(EACCES);
	return EACCES;
#else
	return EPERM;
#endif
    }
#endif
    switch (opcode) {
    case ICL_OP_COPYOUTCLR:	/* copy out data then clear */
    case ICL_OP_COPYOUT:	/* copy ouy data */
	/* copyout: p1=logname, p2=&buffer, p3=size(words), p4=&cookie
	 * return flags<<24 + nwords.
	 * updates cookie to updated start (not end) if we had to
	 * skip some records.
	 */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	AFS_COPYIN(AFSKPTR(p4), (char *)&startCookie, sizeof(afs_int32), code);
	if (code)
	    return code;
	logp = afs_icl_FindLog(tname);
	if (!logp)
	    return ENOENT;
#define	BUFFERSIZE	AFS_LRALLOCSIZ
	lp = osi_AllocLargeSpace(AFS_LRALLOCSIZ);
	elts = BUFFERSIZE / sizeof(afs_int32);
	if (p3 < elts)
	    elts = p3;
	flags = (opcode == ICL_OP_COPYOUT) ? 0 : ICL_COPYOUTF_CLRAFTERREAD;
	code =
	    afs_icl_CopyOut(logp, lp, &elts, (afs_uint32 *) & startCookie,
			    &flags);
	if (code) {
	    osi_FreeLargeSpace((struct osi_buffer *)lp);
	    break;
	}
	AFS_COPYOUT((char *)lp, AFSKPTR(p2), elts * sizeof(afs_int32), code);
	if (code)
	    goto done;
	AFS_COPYOUT((char *)&startCookie, AFSKPTR(p4), sizeof(afs_int32),
		    code);
	if (code)
	    goto done;
#if defined(AFS_AIX51_ENV) && defined(AFS_64BIT_KERNEL)
	if (!(IS64U))
	    *retval = ((long)((flags << 24) | (elts & 0xffffff))) << 32;
	else
#endif
	    *retval = (flags << 24) | (elts & 0xffffff);
      done:
	afs_icl_LogRele(logp);
	osi_FreeLargeSpace((struct osi_buffer *)lp);
	break;

    case ICL_OP_ENUMLOGS:	/* enumerate logs */
	/* enumerate logs: p1=index, p2=&name, p3=sizeof(name), p4=&size.
	 * return 0 for success, otherwise error.
	 */
	for (tlp = afs_icl_allLogs; tlp; tlp = tlp->nextp) {
	    if (p1-- == 0)
		break;
	}
	if (!tlp)
	    return ENOENT;	/* past the end of file */
	temp = strlen(tlp->name) + 1;
	if (temp > p3)
	    return EINVAL;
	AFS_COPYOUT(tlp->name, AFSKPTR(p2), temp, code);
	if (!code)		/* copy out size of log */
	    AFS_COPYOUT((char *)&tlp->logSize, AFSKPTR(p4), sizeof(afs_int32),
			code);
	break;

    case ICL_OP_ENUMLOGSBYSET:	/* enumerate logs by set name */
	/* enumerate logs: p1=setname, p2=index, p3=&name, p4=sizeof(name).
	 * return 0 for success, otherwise error.
	 */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	setp = afs_icl_FindSet(tname);
	if (!setp)
	    return ENOENT;
	if (p2 >= ICL_LOGSPERSET)
	    return EINVAL;
	if (!(tlp = setp->logs[p2]))
	    return EBADF;
	temp = strlen(tlp->name) + 1;
	if (temp > p4)
	    return EINVAL;
	AFS_COPYOUT(tlp->name, AFSKPTR(p3), temp, code);
	break;

    case ICL_OP_CLRLOG:	/* clear specified log */
	/* zero out the specified log: p1=logname */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	logp = afs_icl_FindLog(tname);
	if (!logp)
	    return ENOENT;
	code = afs_icl_ZeroLog(logp);
	afs_icl_LogRele(logp);
	break;

    case ICL_OP_CLRSET:	/* clear specified set */
	/* zero out the specified set: p1=setname */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	setp = afs_icl_FindSet(tname);
	if (!setp)
	    return ENOENT;
	code = afs_icl_ZeroSet(setp);
	afs_icl_SetRele(setp);
	break;

    case ICL_OP_CLRALL:	/* clear all logs */
	/* zero out all logs -- no args */
	code = 0;
	ObtainWriteLock(&afs_icl_lock, 178);
	for (tlp = afs_icl_allLogs; tlp; tlp = tlp->nextp) {
	    tlp->refCount++;	/* hold this guy */
	    ReleaseWriteLock(&afs_icl_lock);
	    /* don't clear persistent logs */
	    if ((tlp->states & ICL_LOGF_PERSISTENT) == 0)
		code = afs_icl_ZeroLog(tlp);
	    ObtainWriteLock(&afs_icl_lock, 179);
	    if (--tlp->refCount == 0)
		afs_icl_ZapLog(tlp);
	    if (code)
		break;
	}
	ReleaseWriteLock(&afs_icl_lock);
	break;

    case ICL_OP_ENUMSETS:	/* enumerate all sets */
	/* enumerate sets: p1=index, p2=&name, p3=sizeof(name), p4=&states.
	 * return 0 for success, otherwise error.
	 */
	for (setp = afs_icl_allSets; setp; setp = setp->nextp) {
	    if (p1-- == 0)
		break;
	}
	if (!setp)
	    return ENOENT;	/* past the end of file */
	temp = strlen(setp->name) + 1;
	if (temp > p3)
	    return EINVAL;
	AFS_COPYOUT(setp->name, AFSKPTR(p2), temp, code);
	if (!code)		/* copy out size of log */
	    AFS_COPYOUT((char *)&setp->states, AFSKPTR(p4), sizeof(afs_int32),
			code);
	break;

    case ICL_OP_SETSTAT:	/* set status on a set */
	/* activate the specified set: p1=setname, p2=op */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	setp = afs_icl_FindSet(tname);
	if (!setp)
	    return ENOENT;
	code = afs_icl_SetSetStat(setp, p2);
	afs_icl_SetRele(setp);
	break;

    case ICL_OP_SETSTATALL:	/* set status on all sets */
	/* activate the specified set: p1=op */
	code = 0;
	ObtainWriteLock(&afs_icl_lock, 180);
	for (setp = afs_icl_allSets; setp; setp = setp->nextp) {
	    setp->refCount++;	/* hold this guy */
	    ReleaseWriteLock(&afs_icl_lock);
	    /* don't set states on persistent sets */
	    if ((setp->states & ICL_SETF_PERSISTENT) == 0)
		code = afs_icl_SetSetStat(setp, p1);
	    ObtainWriteLock(&afs_icl_lock, 181);
	    if (--setp->refCount == 0)
		afs_icl_ZapSet(setp);
	    if (code)
		break;
	}
	ReleaseWriteLock(&afs_icl_lock);
	break;

    case ICL_OP_SETLOGSIZE:	/* set size of log */
	/* set the size of the specified log: p1=logname, p2=size (in words) */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	logp = afs_icl_FindLog(tname);
	if (!logp)
	    return ENOENT;
	code = afs_icl_LogSetSize(logp, p2);
	afs_icl_LogRele(logp);
	break;

    case ICL_OP_GETLOGINFO:	/* get size of log */
	/* zero out the specified log: p1=logname, p2=&logSize, p3=&allocated */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	logp = afs_icl_FindLog(tname);
	if (!logp)
	    return ENOENT;
	allocated = !!logp->datap;
	AFS_COPYOUT((char *)&logp->logSize, AFSKPTR(p2), sizeof(afs_int32),
		    code);
	if (!code)
	    AFS_COPYOUT((char *)&allocated, AFSKPTR(p3), sizeof(afs_int32),
			code);
	afs_icl_LogRele(logp);
	break;

    case ICL_OP_GETSETINFO:	/* get state of set */
	/* zero out the specified set: p1=setname, p2=&state */
	AFS_COPYINSTR(AFSKPTR(p1), tname, sizeof(tname), &temp, code);
	if (code)
	    return code;
	setp = afs_icl_FindSet(tname);
	if (!setp)
	    return ENOENT;
	AFS_COPYOUT((char *)&setp->states, AFSKPTR(p2), sizeof(afs_int32),
		    code);
	afs_icl_SetRele(setp);
	break;

    default:
	code = EINVAL;
    }

    return code;
}


afs_lock_t afs_icl_lock;

/* exported routine: a 4 parameter event */
int
afs_icl_Event4(struct afs_icl_set *setp, afs_int32 eventID,
	       afs_int32 lAndT, long p1, long p2, long p3, long p4)
{
    afs_int32 mask;
    int i;
    afs_int32 tmask;
    int ix;

    /* If things aren't init'ed yet (or the set is inactive), don't panic */
    if (!ICL_SETACTIVE(setp))
	return 0;

    AFS_ASSERT_GLOCK();
    afs_icl_SetHold(setp);
    mask = lAndT >> 24 & 0xff;	/* mask of which logs to log to */
    ix = ICL_EVENTBYTE(eventID);
    ObtainReadLock(&setp->lock);
    if (setp->eventFlags[ix] & ICL_EVENTMASK(eventID)) {
	for (i = 0, tmask = 1; i < ICL_LOGSPERSET; i++, tmask <<= 1) {
	    if (mask & tmask) {
		afs_icl_AppendRecord(setp->logs[i], eventID, lAndT & 0xffffff,
				     p1, p2, p3, p4);
	    }
	    mask &= ~tmask;
	    if (mask == 0)
		break;		/* break early */
	}
    }
    ReleaseReadLock(&setp->lock);
    afs_icl_SetRele(setp);
    return 0;
}

/* Next 4 routines should be implemented via var-args or something.
 * Whole purpose is to avoid compiler warnings about parameter # mismatches.
 * Otherwise, could call afs_icl_Event4 directly.
 */
int
afs_icl_Event3(struct afs_icl_set *setp, afs_int32 eventID,
	       afs_int32 lAndT, long p1, long p2, long p3)
{
    return afs_icl_Event4(setp, eventID, lAndT, p1, p2, p3, (long)0);
}

int
afs_icl_Event2(struct afs_icl_set *setp, afs_int32 eventID,
	       afs_int32 lAndT, long p1, long p2)
{
    return afs_icl_Event4(setp, eventID, lAndT, p1, p2, (long)0, (long)0);
}

int
afs_icl_Event1(struct afs_icl_set *setp, afs_int32 eventID,
	       afs_int32 lAndT, long p1)
{
    return afs_icl_Event4(setp, eventID, lAndT, p1, (long)0, (long)0,
			  (long)0);
}

int
afs_icl_Event0(struct afs_icl_set *setp, afs_int32 eventID,
	       afs_int32 lAndT)
{
    return afs_icl_Event4(setp, eventID, lAndT, (long)0, (long)0, (long)0,
			  (long)0);
}

struct afs_icl_log *afs_icl_allLogs = 0;

/* function to purge records from the start of the log, until there
 * is at least minSpace long's worth of space available without
 * making the head and the tail point to the same word.
 *
 * Log must be write-locked.
 */
static void
afs_icl_GetLogSpace(struct afs_icl_log *logp, afs_int32 minSpace)
{
    unsigned int tsize;

    while (logp->logSize - logp->logElements <= minSpace) {
	/* eat a record */
	tsize = ((logp->datap[logp->firstUsed]) >> 24) & 0xff;
	logp->logElements -= tsize;
	logp->firstUsed += tsize;
	if (logp->firstUsed >= logp->logSize)
	    logp->firstUsed -= logp->logSize;
	logp->baseCookie += tsize;
    }
}

/* append string astr to buffer, including terminating null char.
 *
 * log must be write-locked.
 */
#define ICL_CHARSPERLONG	4
static void
afs_icl_AppendString(struct afs_icl_log *logp, char *astr)
{
    char *op;			/* ptr to char to write */
    int tc;
    int bib;		/* bytes in buffer */

    bib = 0;
    op = (char *)&(logp->datap[logp->firstFree]);
    while (1) {
	tc = *astr++;
	*op++ = tc;
	if (++bib >= ICL_CHARSPERLONG) {
	    /* new word */
	    bib = 0;
	    if (++(logp->firstFree) >= logp->logSize) {
		logp->firstFree = 0;
		op = (char *)&(logp->datap[0]);
	    }
	    logp->logElements++;
	}
	if (tc == 0)
	    break;
    }
    if (bib > 0) {
	/* if we've used this word at all, allocate it */
	if (++(logp->firstFree) >= logp->logSize) {
	    logp->firstFree = 0;
	}
	logp->logElements++;
    }
}

/* add a long to the log, ignoring overflow (checked already) */
#define ICL_APPENDINT32(lp, x) \
    MACRO_BEGIN \
        (lp)->datap[(lp)->firstFree] = (x); \
	if (++((lp)->firstFree) >= (lp)->logSize) { \
		(lp)->firstFree = 0; \
	} \
        (lp)->logElements++; \
    MACRO_END

#if ICL_LONG == 2
#define ICL_APPENDLONG(lp, x) \
    MACRO_BEGIN \
	ICL_APPENDINT32((lp), ((x) >> 32) & 0xffffffffL); \
	ICL_APPENDINT32((lp), (x) & 0xffffffffL); \
    MACRO_END

#else
#define ICL_APPENDLONG(lp, x) ICL_APPENDINT32((lp), (x))
#endif


static void
afs_icl_AppendOne(struct afs_icl_log *logp, int type, long parm)
{
    if (type) {
	/* marshall parameter 3 now */
	if (type == ICL_TYPE_STRING)
	    afs_icl_AppendString(logp, (char *)parm);
	else if (type == ICL_TYPE_HYPER) {
	    ICL_APPENDINT32(logp,
			    (afs_int32) ((struct afs_hyper_t *)parm)->high);
	    ICL_APPENDINT32(logp,
			    (afs_int32) ((struct afs_hyper_t *)parm)->low);
	} else if (type == ICL_TYPE_INT64) {
#ifndef WORDS_BIGENDIAN
#ifdef AFS_64BIT_CLIENT
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[1]);
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[0]);
#else /* AFS_64BIT_CLIENT */
	    ICL_APPENDINT32(logp, (afs_int32) parm);
	    ICL_APPENDINT32(logp, (afs_int32) 0);
#endif /* AFS_64BIT_CLIENT */
#else /* AFSLITTLE_ENDIAN */
#ifdef AFS_64BIT_CLIENT
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[0]);
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[1]);
#else /* AFS_64BIT_CLIENT */
	    ICL_APPENDINT32(logp, (afs_int32) 0);
	    ICL_APPENDINT32(logp, (afs_int32) parm);
#endif /* AFS_64BIT_CLIENT */
#endif /* AFSLITTLE_ENDIAN */
	} else if (type == ICL_TYPE_FID) {
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[0]);
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[1]);
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[2]);
	    ICL_APPENDINT32(logp, (afs_int32) ((afs_int32 *) parm)[3]);
	}
#if ICL_LONG == 2
	else if (type == ICL_TYPE_INT32)
	    ICL_APPENDINT32(logp, (afs_int32) parm);
#endif
	else
	    ICL_APPENDLONG(logp, parm);
    }
}

/* Function to append a record to the log.  Written for speed
 * since we know that we're going to have to make this work fast
 * pretty soon, anyway.  The log must be unlocked.
 */

void
afs_icl_AppendRecord(struct afs_icl_log *logp, afs_int32 op,
		     afs_int32 types, long p1, long p2, long p3, long p4)
{
    int rsize;			/* record size in longs */
    int tsize;		/* temp size */
    osi_timeval32_t tv;
    int t1, t2, t3, t4;

    t4 = types & 0x3f;		/* decode types */
    types >>= 6;
    t3 = types & 0x3f;
    types >>= 6;
    t2 = types & 0x3f;
    types >>= 6;
    t1 = types & 0x3f;

    osi_GetTime(&tv);		/* It panics for solaris if inside */
    ObtainWriteLock(&logp->lock, 182);
    if (!logp->datap) {
	ReleaseWriteLock(&logp->lock);
	return;
    }

    /* get timestamp as # of microseconds since some time that doesn't
     * change that often.  This algorithm ticks over every 20 minutes
     * or so (1000 seconds).  Write a timestamp record if it has.
     */
    if (tv.tv_sec - logp->lastTS > 1024) {
	/* the timer has wrapped -- write a timestamp record */
	if (logp->logSize - logp->logElements <= 5)
	    afs_icl_GetLogSpace(logp, 5);

	ICL_APPENDINT32(logp,
			(afs_int32) (5 << 24) + (ICL_TYPE_UNIXDATE << 18));
	ICL_APPENDINT32(logp, (afs_int32) ICL_INFO_TIMESTAMP);
	ICL_APPENDINT32(logp, (afs_int32) 0);	/* use thread ID zero for clocks */
	ICL_APPENDINT32(logp,
			(afs_int32) (tv.tv_sec & 0x3ff) * 1000000 +
			tv.tv_usec);
	ICL_APPENDINT32(logp, (afs_int32) tv.tv_sec);

	logp->lastTS = tv.tv_sec;
    }

    rsize = 4;			/* base case: see 4 items below */
    if (t1) {
	/* compute size of parameter p1.  Only tricky case is string.
	 * In that case, we have to call strlen to get the string length.
	 */
	ICL_SIZEHACK(t1, p1, tsize, rsize);
    }
    if (t2) {
	/* compute size of parameter p2.  Only tricky case is string.
	 * In that case, we have to call strlen to get the string length.
	 */
	ICL_SIZEHACK(t2, p2, tsize, rsize);
    }
    if (t3) {
	/* compute size of parameter p3.  Only tricky case is string.
	 * In that case, we have to call strlen to get the string length.
	 */
	ICL_SIZEHACK(t3, p3, tsize, rsize);
    }
    if (t4) {
	/* compute size of parameter p4.  Only tricky case is string.
	 * In that case, we have to call strlen to get the string length.
	 */
	ICL_SIZEHACK(t4, p4, tsize, rsize);
    }

    /* At this point, we've computed all of the parameter sizes, and
     * have in rsize the size of the entire record we want to append.
     * Next, we check that we actually have room in the log to do this
     * work, and then we do the append.
     */
    if (rsize > 255) {
	ReleaseWriteLock(&logp->lock);
	return;			/* log record too big to express */
    }

    if (logp->logSize - logp->logElements <= rsize)
	afs_icl_GetLogSpace(logp, rsize);

    ICL_APPENDINT32(logp,
		    (afs_int32) (rsize << 24) + (t1 << 18) + (t2 << 12) +
		    (t3 << 6) + t4);
    ICL_APPENDINT32(logp, (afs_int32) op);
    ICL_APPENDINT32(logp, (afs_int32) osi_ThreadUnique());
    ICL_APPENDINT32(logp,
		    (afs_int32) (tv.tv_sec & 0x3ff) * 1000000 + tv.tv_usec);

    afs_icl_AppendOne(logp, t1, p1);
    afs_icl_AppendOne(logp, t2, p2);
    afs_icl_AppendOne(logp, t3, p3);
    afs_icl_AppendOne(logp, t4, p4);
    ReleaseWriteLock(&logp->lock);
}

/* create a log with size logSize; return it in *outLogpp and tag
 * it with name "name."
 */
int
afs_icl_CreateLog(char *name, afs_int32 logSize,
		  struct afs_icl_log **outLogpp)
{
    return afs_icl_CreateLogWithFlags(name, logSize, /*flags */ 0, outLogpp);
}

/* create a log with size logSize; return it in *outLogpp and tag
 * it with name "name."  'flags' can be set to make the log unclearable.
 */
int
afs_icl_CreateLogWithFlags(char *name, afs_int32 logSize, afs_uint32 flags,
			   struct afs_icl_log **outLogpp)
{
    struct afs_icl_log *logp;
    size_t namelen;

    /* add into global list under lock */
    ObtainWriteLock(&afs_icl_lock, 183);
    if (!afs_icl_inited)
	afs_icl_Init();

    for (logp = afs_icl_allLogs; logp; logp = logp->nextp) {
	if (strcmp(logp->name, name) == 0) {
	    /* found it already created, just return it */
	    logp->refCount++;
	    *outLogpp = logp;
	    if (flags & ICL_CRLOG_FLAG_PERSISTENT) {
		ObtainWriteLock(&logp->lock, 184);
		logp->states |= ICL_LOGF_PERSISTENT;
		ReleaseWriteLock(&logp->lock);
	    }
	    ReleaseWriteLock(&afs_icl_lock);
	    return 0;
	}
    }

    logp = osi_AllocSmallSpace(sizeof(struct afs_icl_log));
    memset((caddr_t) logp, 0, sizeof(*logp));

    logp->refCount = 1;
    namelen = strlen(name) + 1;
    logp->name = osi_AllocSmallSpace(namelen);
    osi_Assert(strlcpy(logp->name, name, namelen) < namelen);
    LOCK_INIT(&logp->lock, "logp lock");
    logp->logSize = logSize;
    logp->datap = NULL;		/* don't allocate it until we need it */

    if (flags & ICL_CRLOG_FLAG_PERSISTENT)
	logp->states |= ICL_LOGF_PERSISTENT;

    logp->nextp = afs_icl_allLogs;
    afs_icl_allLogs = logp;
    ReleaseWriteLock(&afs_icl_lock);

    *outLogpp = logp;
    return 0;
}

/* called with a log, a pointer to a buffer, the size of the buffer
 * (in *bufSizep), the starting cookie (in *cookiep, use 0 at the start)
 * and returns data in the provided buffer, and returns output flags
 * in *flagsp.  The flag ICL_COPYOUTF_MISSEDSOME is set if we can't
 * find the record with cookie value cookie.
 */
int
afs_icl_CopyOut(struct afs_icl_log *logp, afs_int32 * bufferp,
		afs_int32 * bufSizep, afs_uint32 * cookiep,
		afs_int32 * flagsp)
{
    afs_int32 nwords;		/* number of words to copy out */
    afs_uint32 startCookie;	/* first cookie to use */
    afs_int32 outWords;		/* words we've copied out */
    afs_int32 inWords;		/* max words to copy out */
    afs_int32 code;		/* return code */
    afs_int32 ix;		/* index we're copying from */
    afs_int32 outFlags;		/* return flags */
    afs_int32 inFlags;		/* flags passed in */
    afs_int32 end;

    inWords = *bufSizep;	/* max to copy out */
    outWords = 0;		/* amount copied out */
    startCookie = *cookiep;
    outFlags = 0;
    inFlags = *flagsp;
    code = 0;

    ObtainWriteLock(&logp->lock, 185);
    if (!logp->datap) {
	ReleaseWriteLock(&logp->lock);
	goto done;
    }

    /* first, compute the index of the start cookie we've been passed */
    while (1) {
	/* (re-)compute where we should start */
	if (startCookie < logp->baseCookie) {
	    if (startCookie)	/* missed some output */
		outFlags |= ICL_COPYOUTF_MISSEDSOME;
	    /* skip to the first available record */
	    startCookie = logp->baseCookie;
	    *cookiep = startCookie;
	}

	/* compute where we find the first element to copy out */
	ix = logp->firstUsed + startCookie - logp->baseCookie;
	if (ix >= logp->logSize)
	    ix -= logp->logSize;

	/* if have some data now, break out and process it */
	if (startCookie - logp->baseCookie < logp->logElements)
	    break;

	/* At end of log, so clear it if we need to */
	if (inFlags & ICL_COPYOUTF_CLRAFTERREAD) {
	    logp->firstUsed = logp->firstFree = 0;
	    logp->logElements = 0;
	}
	/* otherwise, either wait for the data to arrive, or return */
	if (!(inFlags & ICL_COPYOUTF_WAITIO)) {
	    ReleaseWriteLock(&logp->lock);
	    code = 0;
	    goto done;
	}
	logp->states |= ICL_LOGF_WAITING;
	ReleaseWriteLock(&logp->lock);
	afs_osi_Sleep(&logp->lock);
	ObtainWriteLock(&logp->lock, 186);
    }
    /* copy out data from ix to logSize or firstFree, depending
     * upon whether firstUsed <= firstFree (no wrap) or otherwise.
     * be careful not to copy out more than nwords.
     */
    if (ix >= logp->firstUsed) {
	if (logp->firstUsed <= logp->firstFree)
	    /* no wrapping */
	    end = logp->firstFree;	/* first element not to copy */
	else
	    end = logp->logSize;
	nwords = inWords;	/* don't copy more than this */
	if (end - ix < nwords)
	    nwords = end - ix;
	if (nwords > 0) {
	    memcpy((char *)bufferp, (char *)&logp->datap[ix],
		   sizeof(afs_int32) * nwords);
	    outWords += nwords;
	    inWords -= nwords;
	    bufferp += nwords;
	}
	/* if we're going to copy more out below, we'll start here */
	ix = 0;
    }
    /* now, if active part of the log has wrapped, there's more stuff
     * starting at the head of the log.  Copy out more from there.
     */
    if (logp->firstUsed > logp->firstFree && ix < logp->firstFree
	&& inWords > 0) {
	/* (more to) copy out from the wrapped section at the
	 * start of the log.  May get here even if didn't copy any
	 * above, if the cookie points directly into the wrapped section.
	 */
	nwords = inWords;
	if (logp->firstFree - ix < nwords)
	    nwords = logp->firstFree - ix;
	memcpy((char *)bufferp, (char *)&logp->datap[ix],
	       sizeof(afs_int32) * nwords);
	outWords += nwords;
	inWords -= nwords;
	bufferp += nwords;
    }

    ReleaseWriteLock(&logp->lock);

  done:
    if (code == 0) {
	*bufSizep = outWords;
	*flagsp = outFlags;
    }
    return code;
}

/* hold and release logs */
int
afs_icl_LogHold(struct afs_icl_log *logp)
{
    ObtainWriteLock(&afs_icl_lock, 187);
    logp->refCount++;
    ReleaseWriteLock(&afs_icl_lock);
    return 0;
}

/* keep track of how many sets believe the log itself is allocated */
int
afs_icl_LogUse(struct afs_icl_log *logp)
{
    ObtainWriteLock(&logp->lock, 188);
    if (logp->setCount == 0) {
	/* this is the first set actually using the log -- allocate it */
	if (logp->logSize == 0) {
	    /* we weren't passed in a hint and it wasn't set */
	    logp->logSize = ICL_DEFAULT_LOGSIZE;
	}
	logp->datap = afs_osi_Alloc(sizeof(afs_int32) * logp->logSize);
	osi_Assert(logp->datap != NULL);
#ifdef	KERNEL_HAVE_PIN
	pin((char *)logp->datap, sizeof(afs_int32) * logp->logSize);
#endif
    }
    logp->setCount++;
    ReleaseWriteLock(&logp->lock);
    return 0;
}

/* decrement the number of real users of the log, free if possible */
int
afs_icl_LogFreeUse(struct afs_icl_log *logp)
{
    ObtainWriteLock(&logp->lock, 189);
    if (--logp->setCount == 0) {
	/* no more users -- free it (but keep log structure around) */
	afs_osi_Free(logp->datap, sizeof(afs_int32) * logp->logSize);
#ifdef	KERNEL_HAVE_PIN
	unpin((char *)logp->datap, sizeof(afs_int32) * logp->logSize);
#endif
	logp->firstUsed = logp->firstFree = 0;
	logp->logElements = 0;
	logp->datap = NULL;
    }
    ReleaseWriteLock(&logp->lock);
    return 0;
}

/* set the size of the log to 'logSize' */
int
afs_icl_LogSetSize(struct afs_icl_log *logp, afs_int32 logSize)
{
    ObtainWriteLock(&logp->lock, 190);
    if (!logp->datap) {
	/* nothing to worry about since it's not allocated */
	logp->logSize = logSize;
    } else {
	/* reset log */
	logp->firstUsed = logp->firstFree = 0;
	logp->logElements = 0;

	/* free and allocate a new one */
	afs_osi_Free(logp->datap, sizeof(afs_int32) * logp->logSize);
#ifdef	KERNEL_HAVE_PIN
	unpin((char *)logp->datap, sizeof(afs_int32) * logp->logSize);
#endif
	logp->datap = afs_osi_Alloc(sizeof(afs_int32) * logSize);
	osi_Assert(logp->datap != NULL);
#ifdef	KERNEL_HAVE_PIN
	pin((char *)logp->datap, sizeof(afs_int32) * logSize);
#endif
	logp->logSize = logSize;
    }
    ReleaseWriteLock(&logp->lock);

    return 0;
}

/* free a log.  Called with afs_icl_lock locked. */
int
afs_icl_ZapLog(struct afs_icl_log *logp)
{
    struct afs_icl_log **lpp, *tp;

    for (lpp = &afs_icl_allLogs, tp = *lpp; tp; lpp = &tp->nextp, tp = *lpp) {
	if (tp == logp) {
	    /* found the dude we want to remove */
	    *lpp = logp->nextp;
	    osi_FreeSmallSpace(logp->name);
	    afs_osi_Free(logp->datap, sizeof(afs_int32) * logp->logSize);
#ifdef KERNEL_HAVE_PIN
	    unpin((char *)logp->datap, sizeof(afs_int32) * logp->logSize);
#endif
	    osi_FreeSmallSpace(logp);
	    break;		/* won't find it twice */
	}
    }
    return 0;
}

/* do the release, watching for deleted entries */
int
afs_icl_LogRele(struct afs_icl_log *logp)
{
    ObtainWriteLock(&afs_icl_lock, 191);
    if (--logp->refCount == 0 && (logp->states & ICL_LOGF_DELETED)) {
	afs_icl_ZapLog(logp);	/* destroys logp's lock! */
    }
    ReleaseWriteLock(&afs_icl_lock);
    return 0;
}

/* do the release, watching for deleted entries, log already held */
int
afs_icl_LogReleNL(struct afs_icl_log *logp)
{
    if (--logp->refCount == 0 && (logp->states & ICL_LOGF_DELETED)) {
	afs_icl_ZapLog(logp);	/* destroys logp's lock! */
    }
    return 0;
}

/* zero out the log */
int
afs_icl_ZeroLog(struct afs_icl_log *logp)
{
    ObtainWriteLock(&logp->lock, 192);
    logp->firstUsed = logp->firstFree = 0;
    logp->logElements = 0;
    logp->baseCookie = 0;
    ReleaseWriteLock(&logp->lock);
    return 0;
}

/* free a log entry, and drop its reference count */
int
afs_icl_LogFree(struct afs_icl_log *logp)
{
    ObtainWriteLock(&logp->lock, 193);
    logp->states |= ICL_LOGF_DELETED;
    ReleaseWriteLock(&logp->lock);
    afs_icl_LogRele(logp);
    return 0;
}

/* find a log by name, returning it held */
struct afs_icl_log *
afs_icl_FindLog(char *name)
{
    struct afs_icl_log *tp;
    ObtainWriteLock(&afs_icl_lock, 194);
    for (tp = afs_icl_allLogs; tp; tp = tp->nextp) {
	if (strcmp(tp->name, name) == 0) {
	    /* this is the dude we want */
	    tp->refCount++;
	    break;
	}
    }
    ReleaseWriteLock(&afs_icl_lock);
    return tp;
}

struct afs_icl_set *afs_icl_allSets = 0;

int
afs_icl_CreateSet(char *name, struct afs_icl_log *baseLogp,
		  struct afs_icl_log *fatalLogp,
		  struct afs_icl_set **outSetpp)
{
    return afs_icl_CreateSetWithFlags(name, baseLogp, fatalLogp,
				      /*flags */ 0, outSetpp);
}

/* create a set, given pointers to base and fatal logs, if any.
 * Logs are unlocked, but referenced, and *outSetpp is returned
 * referenced.  Function bumps reference count on logs, since it
 * addds references from the new afs_icl_set.  When the set is destroyed,
 * those references will be released.
 */
int
afs_icl_CreateSetWithFlags(char *name, struct afs_icl_log *baseLogp,
			   struct afs_icl_log *fatalLogp, afs_uint32 flags,
			   struct afs_icl_set **outSetpp)
{
    struct afs_icl_set *setp;
    int i;
    afs_int32 states = ICL_DEFAULT_SET_STATES;
    size_t namelen;

    ObtainWriteLock(&afs_icl_lock, 197);
    if (!afs_icl_inited)
	afs_icl_Init();

    for (setp = afs_icl_allSets; setp; setp = setp->nextp) {
	if (strcmp(setp->name, name) == 0) {
	    setp->refCount++;
	    *outSetpp = setp;
	    if (flags & ICL_CRSET_FLAG_PERSISTENT) {
		ObtainWriteLock(&setp->lock, 198);
		setp->states |= ICL_SETF_PERSISTENT;
		ReleaseWriteLock(&setp->lock);
	    }
	    ReleaseWriteLock(&afs_icl_lock);
	    return 0;
	}
    }

    /* determine initial state */
    if (flags & ICL_CRSET_FLAG_DEFAULT_ON)
	states = ICL_SETF_ACTIVE;
    else if (flags & ICL_CRSET_FLAG_DEFAULT_OFF)
	states = ICL_SETF_FREED;
    if (flags & ICL_CRSET_FLAG_PERSISTENT)
	states |= ICL_SETF_PERSISTENT;

    setp = osi_AllocSmallSpace(sizeof(struct afs_icl_set));
    memset((caddr_t) setp, 0, sizeof(*setp));
    setp->refCount = 1;
    if (states & ICL_SETF_FREED)
	states &= ~ICL_SETF_ACTIVE;	/* if freed, can't be active */
    setp->states = states;

    LOCK_INIT(&setp->lock, "setp lock");
    /* next lock is obtained in wrong order, hierarchy-wise, but
     * it doesn't matter, since no one can find this lock yet, since
     * the afs_icl_lock is still held, and thus the obtain can't block.
     */
    ObtainWriteLock(&setp->lock, 199);
    namelen = strlen(name) + 1;
    setp->name = osi_AllocSmallSpace(namelen);
    osi_Assert(strlcpy(setp->name, name, namelen) < namelen);
    setp->nevents = ICL_DEFAULTEVENTS;
    setp->eventFlags = afs_osi_Alloc(ICL_DEFAULTEVENTS);
    osi_Assert(setp->eventFlags != NULL);
#ifdef	KERNEL_HAVE_PIN
    pin((char *)setp->eventFlags, ICL_DEFAULTEVENTS);
#endif
    for (i = 0; i < ICL_DEFAULTEVENTS; i++)
	setp->eventFlags[i] = 0xff;	/* default to enabled */

    /* update this global info under the afs_icl_lock */
    setp->nextp = afs_icl_allSets;
    afs_icl_allSets = setp;
    ReleaseWriteLock(&afs_icl_lock);

    /* set's basic lock is still held, so we can finish init */
    if (baseLogp) {
	setp->logs[0] = baseLogp;
	afs_icl_LogHold(baseLogp);
	if (!(setp->states & ICL_SETF_FREED))
	    afs_icl_LogUse(baseLogp);	/* log is actually being used */
    }
    if (fatalLogp) {
	setp->logs[1] = fatalLogp;
	afs_icl_LogHold(fatalLogp);
	if (!(setp->states & ICL_SETF_FREED))
	    afs_icl_LogUse(fatalLogp);	/* log is actually being used */
    }
    ReleaseWriteLock(&setp->lock);

    *outSetpp = setp;
    return 0;
}

/* hold and release event sets */
int
afs_icl_SetHold(struct afs_icl_set *setp)
{
    ObtainWriteLock(&afs_icl_lock, 201);
    setp->refCount++;
    ReleaseWriteLock(&afs_icl_lock);
    return 0;
}

/* free a set.  Called with afs_icl_lock locked */
int
afs_icl_ZapSet(struct afs_icl_set *setp)
{
    struct afs_icl_set **lpp, *tp;
    int i;
    struct afs_icl_log *tlp;

    for (lpp = &afs_icl_allSets, tp = *lpp; tp; lpp = &tp->nextp, tp = *lpp) {
	if (tp == setp) {
	    /* found the dude we want to remove */
	    *lpp = setp->nextp;
	    osi_FreeSmallSpace(setp->name);
	    afs_osi_Free(setp->eventFlags, ICL_DEFAULTEVENTS);
#ifdef	KERNEL_HAVE_PIN
	    unpin((char *)setp->eventFlags, ICL_DEFAULTEVENTS);
#endif
	    for (i = 0; i < ICL_LOGSPERSET; i++) {
		if ((tlp = setp->logs[i]))
		    afs_icl_LogReleNL(tlp);
	    }
	    osi_FreeSmallSpace(setp);
	    break;		/* won't find it twice */
	}
    }
    return 0;
}

/* do the release, watching for deleted entries */
int
afs_icl_SetRele(struct afs_icl_set *setp)
{
    ObtainWriteLock(&afs_icl_lock, 202);
    if (--setp->refCount == 0 && (setp->states & ICL_SETF_DELETED)) {
	afs_icl_ZapSet(setp);	/* destroys setp's lock! */
    }
    ReleaseWriteLock(&afs_icl_lock);
    return 0;
}

/* free a set entry, dropping its reference count */
int
afs_icl_SetFree(struct afs_icl_set *setp)
{
    ObtainWriteLock(&setp->lock, 203);
    setp->states |= ICL_SETF_DELETED;
    ReleaseWriteLock(&setp->lock);
    afs_icl_SetRele(setp);
    return 0;
}

/* find a set by name, returning it held */
struct afs_icl_set *
afs_icl_FindSet(char *name)
{
    struct afs_icl_set *tp;
    ObtainWriteLock(&afs_icl_lock, 204);
    for (tp = afs_icl_allSets; tp; tp = tp->nextp) {
	if (strcmp(tp->name, name) == 0) {
	    /* this is the dude we want */
	    tp->refCount++;
	    break;
	}
    }
    ReleaseWriteLock(&afs_icl_lock);
    return tp;
}

/* zero out all the logs in the set */
int
afs_icl_ZeroSet(struct afs_icl_set *setp)
{
    int i;
    int code = 0;
    int tcode;
    struct afs_icl_log *logp;

    ObtainReadLock(&setp->lock);
    for (i = 0; i < ICL_LOGSPERSET; i++) {
	logp = setp->logs[i];
	if (logp) {
	    afs_icl_LogHold(logp);
	    tcode = afs_icl_ZeroLog(logp);
	    if (tcode != 0)
		code = tcode;	/* save the last bad one */
	    afs_icl_LogRele(logp);
	}
    }
    ReleaseReadLock(&setp->lock);
    return code;
}

int
afs_icl_SetSetStat(struct afs_icl_set *setp, int op)
{
    int i;
    afs_int32 code;
    struct afs_icl_log *logp;

    ObtainWriteLock(&setp->lock, 208);
    switch (op) {
    case ICL_OP_SS_ACTIVATE:	/* activate a log */
	/*
	 * If we are not already active, see if we have released
	 * our demand that the log be allocated (FREED set).  If
	 * we have, reassert our desire.
	 */
	if (!(setp->states & ICL_SETF_ACTIVE)) {
	    if (setp->states & ICL_SETF_FREED) {
		/* have to reassert desire for logs */
		for (i = 0; i < ICL_LOGSPERSET; i++) {
		    logp = setp->logs[i];
		    if (logp) {
			afs_icl_LogHold(logp);
			afs_icl_LogUse(logp);
			afs_icl_LogRele(logp);
		    }
		}
		setp->states &= ~ICL_SETF_FREED;
	    }
	    setp->states |= ICL_SETF_ACTIVE;
	}
	code = 0;
	break;

    case ICL_OP_SS_DEACTIVATE:	/* deactivate a log */
	/* this doesn't require anything beyond clearing the ACTIVE flag */
	setp->states &= ~ICL_SETF_ACTIVE;
	code = 0;
	break;

    case ICL_OP_SS_FREE:	/* deassert design for log */
	/*
	 * if we are already in this state, do nothing; otherwise
	 * deassert desire for log
	 */
	if (setp->states & ICL_SETF_ACTIVE)
	    code = EINVAL;
	else {
	    if (!(setp->states & ICL_SETF_FREED)) {
		for (i = 0; i < ICL_LOGSPERSET; i++) {
		    logp = setp->logs[i];
		    if (logp) {
			afs_icl_LogHold(logp);
			afs_icl_LogFreeUse(logp);
			afs_icl_LogRele(logp);
		    }
		}
		setp->states |= ICL_SETF_FREED;
	    }
	    code = 0;
	}
	break;

    default:
	code = EINVAL;
    }
    ReleaseWriteLock(&setp->lock);
    return code;
}
