/*
 * Copyright (c) 1982, 1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 *
 *	@(#)subr_log.c	2.1 (2.11BSD) 1999/4/29
 */

/*
 * logioctl() had the wrong number of arguments.  Argh!  Apparently this
 * driver was overlooked when 'dev' was added to ioctl entry points.
 *
 * logclose() returned garbage.  this went unnoticed because most programs
 * don't check status when doing a close.

 * Remove vax ifdefs - this driver is never going back to vaxland.
 *
 * Add support for multiple log devices.  Minor device 0 is the traditional
 * kernel logger (/dev/klog), minor device 1 is reserved for the future device 
 * error logging daemon.  Minor device 2 is used by the 'accounting' daemon
 * 'acctd'.
 */

#define	NLOG	3
	int	nlog = NLOG;

#include "param.h"
#include "user.h"
#include "proc.h"
#include "ioctl.h"
#include "msgbuf.h"
#include "file.h"
#include "inode.h"
#include "errno.h"
#include "uio.h"
#include "machine/seg.h"
#include "map.h"

#define LOG_RDPRI	(PZERO + 1)

#define	LOG_OPEN	0x01
#define LOG_ASYNC	0x04
#define LOG_RDWAIT	0x08

/*
 * This is an efficiency hack.  This global is provided for exit() to
 * test and avoid the overhead of function calls when accounting is
 * turned off.
*/
	int	Acctopen;

	struct	msgbuf	msgbuf[NLOG];
	static	struct logsoftc
		{
		int	sc_state;	/* see above for possibilities */
		struct	proc *sc_selp;	/* process waiting on select call */
		int	sc_pgid;	/* process/group for async I/O */
		int	sc_overrun;	/* full buffer count */
		} logsoftc[NLOG];

/*ARGSUSED*/
logopen(dev, mode)
	dev_t dev;
	int mode;
	{
	register int	unit = minor(dev);

	if	(unit >= NLOG)
		return(ENODEV);
	if	(logisopen(unit))
		return(EBUSY);
	if	(msgbuf[unit].msg_click == 0)	/* no buffer allocated */
		return(ENOMEM);
	logsoftc[unit].sc_state |= LOG_OPEN;
	if	(unit == logACCT)
		Acctopen = 1;
	logsoftc[unit].sc_pgid = u.u_procp->p_pid;  /* signal process only */
	logsoftc[unit].sc_overrun = 0;
	return(0);
	}

/*ARGSUSED*/
logclose(dev, flag)
	dev_t	dev;
	int	flag;
	{
	register int unit = minor(dev);

	logsoftc[unit].sc_state = 0;
	if	(unit == logACCT)
		Acctopen = 0;
	return(0);
	}

/*
 * This is a helper function to keep knowledge of this driver's data
 * structures away from the rest of the kernel.
*/
logisopen(unit)
	int	unit;
	{

	if	(logsoftc[unit].sc_state & LOG_OPEN)
		return(1);
	return(0);
	}

/*ARGSUSED*/
logread(dev, uio, flag)
	dev_t dev;
	struct uio *uio;
	int flag;
	{
	register int l;
	register struct logsoftc *lp;
	register struct msgbuf *mp;
	int	s, error = 0;
	char	buf[ctob(2)];

	l = minor(dev);
	lp = &logsoftc[l];
	mp = &msgbuf[l];
	s = splhigh();
	while	(mp->msg_bufr == mp->msg_bufx)
		{
		if	(flag & IO_NDELAY)
			{
			splx(s);
			return(EWOULDBLOCK);
			}
		lp->sc_state |= LOG_RDWAIT;
		sleep((caddr_t)mp, LOG_RDPRI);
		}
	lp->sc_state &= ~LOG_RDWAIT;

	while	(uio->uio_resid)
		{
		l = mp->msg_bufx - mp->msg_bufr;
/*
 * If the reader and writer are equal then we have caught up and there
 * is nothing more to transfer.
*/
		if	(l == 0)
			break;
/*
 * If the write pointer is behind the reader then only consider as
 * available for now the bytes from the read pointer thru the end of 
 * the buffer.
*/
		if	(l < 0)
			{
			l = MSG_BSIZE - mp->msg_bufr;
/*
 * If the reader is exactly at the end of the buffer it is
 * time to wrap it around to the beginning and recalculate the
 * amount of data to transfer.
*/
			if	(l == 0)
				{
				mp->msg_bufr = 0;
				continue;
				}
			}
		l = MIN(l, uio->uio_resid);
		l = MIN(l, sizeof buf);
		mapseg5(mp->msg_click, (btoc(MSG_BSIZE) << 8) | RW);
		bcopy(&mp->msg_bufc[mp->msg_bufr], buf, l);
		normalseg5();
		error = uiomove(buf, l, uio);
		if	(error)
			break;
		mp->msg_bufr += l;
		}
	splx(s);
	return(error);
	}

/*ARGSUSED*/
logselect(dev, rw)
	dev_t dev;
	int rw;
	{
	register int s = splhigh();
	int	unit = minor(dev);

	switch	(rw)
		{
		case	FREAD:
			if	(msgbuf[unit].msg_bufr != msgbuf[unit].msg_bufx)
				{
				splx(s);
				return(1);
				}
			logsoftc[unit].sc_selp = u.u_procp;
			break;
		}
	splx(s);
	return(0);
	}

logwakeup(unit)
	int	unit;
	{
	register struct proc *p;
	register struct logsoftc *lp;
	register struct msgbuf *mp;

	if	(!logisopen(unit))
		return;
	lp = &logsoftc[unit];
	mp = &msgbuf[unit];
	if	(lp->sc_selp)
		{
		selwakeup(lp->sc_selp, (long) 0);
		lp->sc_selp = 0;
		}
	if	(lp->sc_state & LOG_ASYNC && (mp->msg_bufx != mp->msg_bufr))
		{
		if	(lp->sc_pgid < 0)
			gsignal(-lp->sc_pgid, SIGIO); 
		else if (p = pfind(lp->sc_pgid))
			psignal(p, SIGIO);
		}
	if	(lp->sc_state & LOG_RDWAIT)
		{
		wakeup((caddr_t)mp);
		lp->sc_state &= ~LOG_RDWAIT;
		}
}

/*ARGSUSED*/
logioctl(dev, com, data, flag)
	dev_t	dev;
	u_int	com;
	caddr_t data;
	int	flag;
	{
	long l;
	register int s;
	int	unit;
	register struct logsoftc *lp;
	register struct msgbuf *mp;

	unit = minor(dev);
	lp = &logsoftc[unit];
	mp = &msgbuf[unit];

	switch	(com)
		{
		case	FIONREAD:
			s = splhigh();
			l = mp->msg_bufx - mp->msg_bufr;
			splx(s);
			if	(l < 0)
				l += MSG_BSIZE;
			*(off_t *)data = l;
			break;
		case	FIONBIO:
			break;
		case	FIOASYNC:
			if (*(int *)data)
				lp->sc_state |= LOG_ASYNC;
			else
				lp->sc_state &= ~LOG_ASYNC;
			break;
		case	TIOCSPGRP:
			lp->sc_pgid = *(int *)data;
			break;
		case	TIOCGPGRP:
			*(int *)data = lp->sc_pgid;
			break;
		default:
			return(-1);
		}
	return(0);
	}

/*
 * This is inefficient for single character writes.  Alas, changing this
 * to be buffered would affect the networking code's use of printf.  
*/

logwrt(buf,len,log)
	char	*buf;
	int	len;
	int	log;
	{
	segm	s5;
	register struct msgbuf *mp = &msgbuf[log];
	struct	logsoftc *lp = &logsoftc[log];
	register int	infront;
	int  s, n, writer, err = 0;

	if	(mp->msg_magic != MSG_MAGIC || (len > MSG_BSIZE))
		return(-1);
	saveseg5(s5);
/*
 * Hate to do this but since this can be called from anywhere in the kernel
 * we have to hold off any interrupt service routines so they don't change
 * things.  This looks like a lot of code but it isn't really.
*/
	s = splhigh();
	while	(len)
		{
again:		infront = MSG_BSIZE - mp->msg_bufx;
		if	(infront <= 0)
			{
			mp->msg_bufx = 0;
			infront = MSG_BSIZE - mp->msg_bufr;
			}
		n = mp->msg_bufr - mp->msg_bufx;
		if	(n < 0)		/* bufr < bufx */
			writer = (MSG_BSIZE - mp->msg_bufx) + mp->msg_bufr;
		else if	(n == 0)
			writer = MSG_BSIZE;
		else
			{
			writer = n;
			infront = n;
			}
		if	(len > writer)
			{
/*
 * won't fit.  the total number of bytes to be written is
 * greater than the number available.  the buffer is full.
 * throw away the old data and keep the current data by resetting
 * the 'writer' pointer to the current 'reader' position.  Bump the
 * overrun counter in case anyone wants to look at it for debugging.
*/
			lp->sc_overrun++;
			mp->msg_bufx = mp->msg_bufr;
			goto	again;
			}
		if	(infront > len)
			infront = len;
		mapseg5(mp->msg_click, (btoc(MSG_BSIZE) << 8) | RW);  /* XXX */
		bcopy(buf, &mp->msg_bufc[mp->msg_bufx], infront);
		restorseg5(s5);
		mp->msg_bufx += infront;
		len -= infront;
		buf += infront;
		}
out:	splx(s);
	return(err);
	}

/*
 * Initialize the log driver.  Called from the system startup code (machdep2.c).
 * All buffers are the same (MSG_BSIZE) size.
*/

loginit()
	{
register struct msgbuf *mp;

	for	(mp = &msgbuf[0]; mp < &msgbuf[NLOG]; mp++)
		{
		mp->msg_click = malloc(coremap,btoc(MSG_BSIZE));
		if	(!mp->msg_click)
			return(-1);
		mp->msg_magic = MSG_MAGIC;
		mp->msg_bufc = SEG5;
		mp->msg_bufx = mp->msg_bufr = 0;
		}
	return(0);
	}
