/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: log.c 1.19 89/08/07 $"

/* $Log:	log.c,v $
 */


/*
 * Streams log/trace driver 
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/errno.h"
#include "sys/stropts.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/strstat.h"
#include "sys/strid.h"
#include "sys/log.h"
#include "sys/strlog.h"
#include "sys/sysmacros.h"
#include "sys/systm.h"
#include "sys/debug.h"
#include "sys/cmn_err.h"

static struct module_info logm_info = 
	{STRID_LOG, LOG_NAME, LOG_MINPS, LOG_MAXPS, LOG_HIWAT, LOG_LOWAT};

int logopen(), logclose(), logput(), logrsrv();
void log_init();

static struct qinit logrinit = 
		{ NULL, logrsrv, logopen, logclose, NULL, &logm_info, NULL };
static struct qinit logwinit = 
		{ logput, NULL, NULL, NULL, NULL, &logm_info, NULL };

struct streamtab loginfo = { &logrinit, &logwinit, NULL, NULL };

struct log *log_errp;			/* pointer to error logger, used by strlog() */
struct log *log_trcp;			/* pointer to trace logger, used by strlog() */
static mblk_t *log_trcmsg;		/* message containing trace_ids structures */
static struct trace_ids *log_trcids;	/* pointer to base of trace_ids array */
static int log_ntrcid = 0;		/* the number of trace_ids */
static int log_errseq = 0;		/* log sequence number */
static int log_trcseq = 0;		/* trace sequence number */
static lock_t log_errlock, log_trclock, log_lock;

#ifdef DEBUG
int logdebug = 0;
#define LOGPRINTF if (logdebug) CPRINTF
#endif /* DEBUG */

extern int log_c_major;
extern struct devops logdevops;

int log_cnt;			/* number of configured log devices */
struct log *log_log;		/* table of log device state info */

/*
 * Initialization function - called during system initialization to 
 * prepare the id table
 */

void
log_init(num)
	int num;
{
	struct dev *dp;
	int	d;

	for (d=0; d < num ; d++) {
		dp = (struct dev *)calloc(sizeof(struct dev));
		dp->d_dev = makedev(log_c_major, d);
		dp->d_ops = &logdevops;
		if (devsw_add(dp) != 0)
			CPRINTF("log: unable to add line dev %d to devsw\n", d);
	}
	if (clonedev_add(makedev(log_c_major, 0))) 
		CPRINTF("log: unable to add driver's clone entry in devsw\n");
	log_cnt = num;
	log_log = (struct log *)calloc(log_cnt * sizeof(struct log));
	log_errp = NULL;
	log_trcp = NULL;
	log_errseq = log_trcseq = 0;
	init_lock(&log_errlock);
	init_lock(&log_trclock);
	init_lock(&log_lock);
}


/*
 * Device open call 
 */
/*ARGSUSED*/
static int
logopen(q, dev, flag, sflag)
	queue_t *q;
{
	int i;
	struct log *lp;
	spl_t s;

	if (sflag != CLONEOPEN) 
		return(OPENFAIL);

	lp = log_log;
	s = p_lock(&log_lock, SPL_LOG);
	for (i = 0; i < log_cnt; i++, lp++) {
		if (!(lp->log_state & LOGOPEN)) 
			break;
	}
	if (i >= log_cnt) {
		v_lock(&log_lock, s);
		return(OPENFAIL);
	}
#ifdef DEBUG
	LOGPRINTF("logopen: opening dev %d\n", i);
#endif /* DEBUG */

	lp->log_state = LOGOPEN;
	v_lock(&log_lock, s);
	lp->log_rdq = q;
	lp->log_bcnt = 0;
	q->q_ptr = (caddr_t)lp;
	WR(q)->q_ptr = (caddr_t)lp;
	return(lp - log_log);
}


/*
 * Device close call
 */
/*ARGSUSED*/
static
logclose(q)
	queue_t *q;
{
	struct log *lp;
	spl_t s[3];
	int i = 0;

	lp = (struct log *)(q->q_ptr);

	/*
	 * Grab all necessary locks -- must be grabbed in correct
	 * order - errlock, trclock, then local lock!
	 */
	if (lp == log_errp) {
#ifdef DEBUG
		LOGPRINTF("logclose: grabbing errlock\n");
#endif /* DEBUG */
		s[i++] = p_lock(&log_errlock, SPL_LOG);
	}
	if (lp == log_trcp) {
#ifdef DEBUG
		LOGPRINTF("logclose: grabbing trclock\n");
#endif /* DEBUG */
		s[i++] = p_lock(&log_trclock, SPL_LOG);
	}
#ifdef DEBUG
	LOGPRINTF("logclose: grabbing dev lock\n");
#endif /* DEBUG */
	s[i++] = p_lock(&lp->log_lock, SPL_LOG);

	lp->log_state = 0;
	lp->log_rdq = NULL;
		
	if (lp == log_trcp) {
		log_trcp = NULL;
		freemsg(log_trcmsg);
		v_lock(&log_trclock, s[--i]);
	}
	if (lp == log_errp) {
		log_errp = NULL;
		v_lock(&log_errlock, s[--i]);
	}
	/* XXX */
	disable_procs(q);
	flushq(q,1);
	flushq(WR(q), 1);
	q->q_ptr = NULL;
	WR(q)->q_ptr = NULL;
	v_lock(&lp->log_lock, s[--i]);
}

/*
 * Write queue put procedure.  
 */
static
logput(q, bp)
	queue_t *q;
	mblk_t *bp;
{
	register struct iocblk *ioc;
	register struct log *lp;
	struct log_ctl *lcp;
	mblk_t *bp2, *dp2;
	int both;
	spl_t s[3];
	int i = 0;

	switch (bp->b_datap->db_type) {

	case M_FLUSH:
		if (*bp->b_rptr & FLUSHW) 
			flushq(q, 0);
		if (*bp->b_rptr & FLUSHR) {
			flushq(RD(q), 0);
			*bp->b_rptr &= ~FLUSHW;
			qreply(q, bp);
			return;
		}
		freemsg(bp);
		return;

	case M_IOCTL:
		lp = (struct log *)q->q_ptr;
		ioc = (struct iocblk *)bp->b_rptr;
		switch (ioc->ioc_cmd) {

		case I_TRCLOG:
#ifdef DEBUG
			LOGPRINTF("logput: I_TRCLOG\n");
#endif /* DEBUG */	
			s[0] = p_lock(&log_trclock, SPL_LOG);
			if (!log_trcp && bp->b_cont) {
#ifdef DEBUG
				LOGPRINTF("logput: not in use\n");
#endif /* DEBUG */
				log_trcp = lp;
				log_trcmsg = bp->b_cont;
				bp->b_cont = NULL;
				log_trcids = (struct trace_ids *)log_trcmsg->b_rptr;
				log_ntrcid = (long)(log_trcmsg->b_wptr - 
						    log_trcmsg->b_rptr) / 
							sizeof(struct trace_ids);
				ioc->ioc_count = 0;
				bp->b_datap->db_type = M_IOCACK;
				qreply(q,bp);
				v_lock(&log_trclock, s[0]);
				return;
			}
			v_lock(&log_trclock, s[0]);
			bp->b_datap->db_type = M_IOCNAK;
			qreply(q, bp);
			return;

		case I_ERRLOG:
#ifdef DEBUG
			LOGPRINTF("logput: I_ERRLOG\n");
#endif /* DEBUG */
			s[0] = p_lock(&log_errlock, SPL_LOG);
			if (!log_errp) {
#ifdef DEBUG
				LOGPRINTF("logput: not in use\n");
#endif /* DEBUG */
				log_errp = lp;
				bp->b_datap->db_type = M_IOCACK;
			} else {
				bp->b_datap->db_type = M_IOCNAK;
			}
			v_lock(&log_errlock, s[0]);
			qreply(q, bp);
			return;

		default:
			bp->b_datap->db_type = M_IOCNAK;
			qreply(q, bp);
			return;
		}

	case M_PROTO:
		if (((bp->b_wptr - bp->b_rptr) != sizeof(struct log_ctl)) 
		    || !bp->b_cont ) {
			freemsg(bp);
			return;
		}
		lcp = (struct log_ctl *)(bp->b_rptr);
		s[i++] = p_lock(&log_errlock, SPL_LOG);
		s[i++] = p_lock(&log_trclock, SPL_LOG);
		if (lcp->flags & SL_TRACE) {
#ifdef DEBUG
			LOGPRINTF("logput: SL_TRACE flag set\n");
#endif /* DEBUG */
			log_trcseq++;
			if (!log_trcp 
			    || !logtrchk(STRID_LOG, 
					 (struct log *)(q->q_ptr) - log_log,
					  lcp->level)) {
				lcp->flags &= ~SL_TRACE;
			} else
				s[i++] = p_lock(&log_trcp->log_lock, SPL_LOG);
		}
		v_lock(&log_trclock, s[--i]);
		if (lcp->flags & SL_ERROR) {
#ifdef DEBUG
			LOGPRINTF("logput: SL_ERROR flag set\n");
#endif /* DEBUG */
			log_errseq++;
			if (!log_errp) {
				lcp->flags &= ~SL_ERROR;
			} else
				s[i++] = p_lock(&log_errp->log_lock, SPL_LOG);
		}
		v_lock(&log_errlock, s[--i]);

		if (!(lcp->flags & (SL_ERROR|SL_TRACE))) {
			freemsg(bp);
#ifdef DEBUG
			LOGPRINTF("logput: no flags so returning\n");
#endif /* DEBUG */
			return;
		}

		both = ((lcp->flags & (SL_ERROR|SL_TRACE)) == 
			(SL_ERROR|SL_TRACE)) ? 1 : 0;

		lcp->ttime = time;
		lcp->mid = STRID_LOG;
		lcp->sid = (struct log *)(q->q_ptr) - log_log;
		if (lcp->flags & SL_TRACE) {
			lcp->seq_no = log_trcseq;
			if (both) {
#ifdef DEBUG
				LOGPRINTF("logput: both - doing trace\n");
#endif /* DEBUG */
				if (bp2 = copyb(bp)) {
					if (dp2 = dupb(bp->b_cont)) {
						bp2->b_cont = dp2;
						putq(log_trcp->log_rdq, bp2);
						if (log_trcp->log_bcnt >= log_bsz) 
							freemsg(getq(log_trcp->log_rdq));
						else
							log_trcp->log_bcnt++;
					}
					else 
						freeb(bp2);
				}
				v_lock(&log_trcp->log_lock, s[--i]);
			} else {
				putq(log_trcp->log_rdq, bp);
				if (log_trcp->log_bcnt >= log_bsz) 
					freemsg(getq(log_trcp->log_rdq));
				else
					log_trcp->log_bcnt++;
				v_lock(&log_trcp->log_lock, s[--i]);
				return;
			}
		}
		if (lcp->flags & SL_ERROR) {
			lcp->seq_no = log_errseq;
			putq(log_errp->log_rdq, bp);
			if (log_errp->log_bcnt >= log_bsz) 
				freemsg(getq(log_errp->log_rdq));
			else
				log_errp->log_bcnt++;
			v_lock(&log_errp->log_lock, s[--i]);	
			return;
		}
		return;		

	default:
		bp->b_datap->db_type = M_ERROR;
		bp->b_rptr = bp->b_datap->db_base;
		bp->b_wptr = bp->b_rptr + 1;
		*bp->b_rptr = EIO;
		qreply(q, bp);
		return;
	}
}



/*
 * Kernel logger interface function.  Attempts to construct a log
 * message and send it up the logger stream.  Delivery will not be
 * done if message blocks cannot be allocated or if the logger
 * is not registered.
 */

/*VARARGS 5*/

strlog(mid,sid,level,flags,fmt,arg)
	short mid,sid;
	char level;
	unsigned short flags;
	char *fmt;
	unsigned arg;
{
	register char *dst, *src;
	register narg;
	unsigned char both;
	register unsigned *argp;
	mblk_t *cbp, *dbp; 
	mblk_t *cbp2, *dbp2;
	struct log_ctl *lcp;
	spl_t s[3];
	int i = 0;


#ifdef DEBUG
	LOGPRINTF("strlog: begin\n");
	ASSERT(flags & (SL_ERROR | SL_TRACE), "strlog: illegal flag value");
#endif /* DEBUG */
	s[i++] = p_lock(&log_errlock, SPL_LOG);
	s[i++] = p_lock(&log_trclock, SPL_LOG);
	if (flags & SL_TRACE) {
#ifdef DEBUG
		LOGPRINTF("strlog: SL_TRACE set\n");
#endif /* DEBUG */
		log_trcseq++;
		if (!log_trcp || !logtrchk(mid,sid,level) ) {
			flags &= ~SL_TRACE;
		} else
			s[i++] = p_lock(&log_trcp->log_lock, SPL_LOG);
	}
	v_lock(&log_trclock, s[--i]);
	if (flags & SL_ERROR) {
#ifdef DEBUG
		LOGPRINTF("strlog: SL_ERROR set\n");
#endif /* DEBUG */
		log_errseq++;
		if (!log_errp)  {
			flags &= ~SL_ERROR;
		} else
			s[i++] = p_lock(&log_errp->log_lock, SPL_LOG);
	}
	v_lock(&log_errlock, s[--i]);

	if (!(flags & (SL_ERROR|SL_TRACE)))  {
#ifdef DEBUG
		LOGPRINTF("strlog: no flags -- returning\n");
#endif /* DEBUG */
		return;
	}

	both = ((flags & (SL_ERROR|SL_TRACE)) == (SL_ERROR|SL_TRACE)) ? 1 : 0;
	
	/*
	 * allocate message blocks for log text, log header, and 
	 * proto control field.
	 */
	if (!(dbp = allocb(LOGMSGSZ, BPRI_HI))
	    || !(cbp = allocb(sizeof(struct log_ctl), BPRI_HI))) {
		if (dbp)
			freeb(dbp);
		if (flags & SL_ERROR)
			v_lock(&log_errp->log_lock, s[--i]);
		if (flags & SL_TRACE)
			v_lock(&log_trcp->log_lock, s[--i]);
		return;
	}

	/*
	 * copy log text into text message block.  This consists of a 
	 * format string and NLOGARGS integer arguments.
	*/
	dbp->b_datap->db_type = M_DATA;
	dst = (char *)dbp->b_wptr;
	src = fmt;
	logstrcpy(dst, src);
	/*
	 * dst now points to the null byte at the end of the format string.  
	 * Move the wptr to the first int boundary after dst.
	 */
	dbp->b_wptr = (unsigned char *)logadjust(dst);

	ASSERT((int)(dbp->b_datap->db_lim - dbp->b_wptr) >= NLOGARGS*sizeof(int),
		"strlog: not enough room for log message");
	/*
	 *+ The log driver allocated a streams buffer, but the
	 *+ buffer returned was less than the size requested.
	 */

	argp = &arg;
	for (narg = 0; narg < NLOGARGS; narg++) {
		*((int *)dbp->b_wptr) = *argp++;
		dbp->b_wptr += sizeof(int);
	}

	/*
	 * set up proto header
	 */
	cbp->b_datap->db_type = M_PROTO;
	cbp->b_cont = dbp;
	cbp->b_wptr += sizeof(struct log_ctl);
	lcp = (struct log_ctl *)cbp->b_rptr;
	lcp->mid = mid;
	lcp->sid = sid;
	lcp->ttime = time;
	lcp->level = level;
	lcp->flags = flags;

	if (flags & SL_TRACE) {
		lcp->seq_no = log_trcseq;
		if (both) {
#ifdef DEBUG
			LOGPRINTF("strlog: both - doing trace\n");
#endif /* DEBUG */
			if (cbp2 = copyb(cbp)) {
				if (dbp2 = dupb(dbp)) {
					cbp2->b_cont = dbp2;
					putq(log_trcp->log_rdq, cbp2);
					if (log_trcp->log_bcnt >= log_bsz) 
						freemsg(getq(log_trcp->log_rdq));
					else
						log_trcp->log_bcnt++;
				}
				else 
					freeb(cbp2);
			}
			v_lock(&log_trcp->log_lock, s[--i]);
		} else {
#ifdef DEBUG
			LOGPRINTF("strlog: doing trace\n");
#endif /* DEBUG */
			putq(log_trcp->log_rdq, cbp);
			if (log_trcp->log_bcnt >= log_bsz) 
				freemsg(getq(log_trcp->log_rdq));
			else
				log_trcp->log_bcnt++;
			v_lock(&log_trcp->log_lock, s[--i]);
			return;
		}
	}
	if (flags & SL_ERROR) {
#ifdef DEBUG
		LOGPRINTF("strlog: doing err\n");
#endif /* DEBUG */
		lcp->seq_no = log_errseq;
		putq(log_errp->log_rdq, cbp);
		if (log_errp->log_bcnt >= log_bsz) 
			freemsg(getq(log_errp->log_rdq));
		else
			log_errp->log_bcnt++;
		v_lock(&log_errp->log_lock, s[--i]);	
		return;
	}
}


/*
 * Send a log message up a given log stream.
 */
logrsrv(q)
	queue_t *q;
{
	mblk_t *mp;
	struct log *lp;
	spl_t s;

#ifdef DEBUG
	LOGPRINTF("logrsrv: begin\n");
#endif /* DEBUG */
	lp = (struct log *)q->q_ptr;
	s = p_lock(&lp->log_lock, SPL_LOG);
	while (mp = getq(q)) {
		if (!canput(q->q_next)) {
#ifdef DEBUG
			LOGPRINTF("logrsrv: putbq and return\n");
#endif /* DEBUG */
			putbq(q, mp);
			v_lock(&lp->log_lock, s);
			return;
		}
		lp->log_bcnt--;
		putnext(q, mp);
	}
	v_lock(&lp->log_lock, s);
}



/*
 * Check mid,sid against list of values requested by the tracer.
 */
logtrchk(mid, sid, level)
	register short mid, sid;
	register char level;
{
	register struct trace_ids *tid;
	register i;

	ASSERT(log_trcp, "logtrchk: log_trcp null");
	/*
	 *+ The log driver is in an inconsistent internal state.
	 */

	for (tid = log_trcids, i = 0; i < log_ntrcid; tid++, i++) {
		if (((int)tid->ti_level < (int)level) 
		     && ((int)tid->ti_level >= 0)) 
			continue;
		if ((tid->ti_mid != mid) && (tid->ti_mid >= 0)) 
			continue;
		if ((tid->ti_sid != sid) && (tid->ti_sid >= 0)) 
			continue;
#ifdef DEBUG
		LOGPRINTF("logtrchk: returning 1\n");
#endif /* DEBUG */
		return(1);
	}
#ifdef DEBUG
	LOGPRINTF("logtrchk: returning 0\n");
#endif /* DEBUG */
	return(0);
}
