/*
 * $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: msg.c 1.11 90/01/25 $"

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

/*
**	Inter-Process Communication Message Facility.
*/

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/conf.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/debug.h"
#include "sys/errno.h"
#include "sys/ipc.h"
#include "sys/msg.h"
#include "sys/map.h"
#include "sys/vm.h"
#include "sys/pte.h"
#include "sys/engine.h"
#include "sys/plocal.h"

#include "sys/intctl.h"

struct map	*msgmap;	/* msg allocation map */
lock_t	msgmap_lock;
short	msgmap_want;
sema_t	msgmap_wait;

struct msqid_ds	*msgque;	/* msg queue headers */
lock_t	msgque_lock;

struct msg	*msgh;		/* message headers */
extern struct msginfo	msginfo;	/* message parameters */
struct msg		*msgfp;	/* ptr to head of free header list */
lock_t	msgfp_lock;
sema_t	msgfp_wait;

char *Bmsg;
caddr_t		msg;		/* base address of message buffer */

extern long	rmalloc();

struct ipc_perm		*ipcget();
struct msqid_ds		*msgconv();

int msgrlog[3];
#define MSGFP_WAIT	0
#define MSGMAP_WAIT	1
#define MSG_RW		2

int msgqlog[3];

/* Convert bytes to msg segments. */
#define	btoq(X)	((X + msginfo.msgssz - 1) / msginfo.msgssz)

/*
**	msgconv - Convert a user supplied message queue id into a ptr to a
**		msqid_ds structure.
**	
**		if found, return it locked.
**
*/

static struct msqid_ds *
msgconv(id)
register int	id;
{
	register struct msqid_ds	*qp;	/* ptr to associated q slot */
	spl_t	ipl;

	qp = &msgque[(unsigned)id % (unsigned)msginfo.msgmni];
	(void)p_sema(&qp->mutex, PMSGM);
	ipl = p_lock(&msgque_lock, SPLFS);
	if((qp->msg_perm.mode & IPC_ALLOC) == 0 ||
		id / msginfo.msgmni != qp->msg_perm.seq) {
		v_lock(&msgque_lock, ipl);
		v_sema(&qp->mutex);
		u.u_error = EINVAL;
		return(NULL);
	}
	v_lock(&msgque_lock, ipl);
	return(qp);
}


/*
**	msgctl - Msgctl system call.
*/

static
msgctl(uap)
	register struct {
		int		msgid,
				cmd;
		struct Dmsqid_ds *buf;
	} *uap;
{
	struct Dmsqid_ds		ds;	/* queue work area */
	register struct msqid_ds	*qp;	/* ptr to associated q */
	spl_t				ipl;

	u.u_r.r_val1 = 0;
	switch(uap->cmd) {
	case IPC_RMID:
		if((qp = msgconv(uap->msgid)) == NULL) {
			return;
		}
		if(u.u_uid != qp->msg_perm.uid && u.u_uid != qp->msg_perm.cuid
			&& !suser()) {
			break;
		}
		while(qp->msg_first)
			msgfree(qp, (struct msg *)NULL, qp->msg_first);
		qp->msg_cbytes = 0;
		while(qp->msg_nreaders > 0) {
			v_sema(&qp->msg_readers);
			qp->msg_nreaders--;
		}
		while(qp->msg_nwriters > 0) {
			v_sema(&qp->msg_writers);
			qp->msg_nwriters--;
		}
		ipl = p_lock(&msgque_lock, SPLFS);
		if(uap->msgid + msginfo.msgmni < 0)
			qp->msg_perm.seq = 0;
		else
			qp->msg_perm.seq++;
		qp->msg_perm.mode = 0;
		v_lock(&msgque_lock, ipl);
		break;
	case IPC_SET:
		if((qp = msgconv(uap->msgid)) == NULL) 
			return;
		if(u.u_uid != qp->msg_perm.uid && u.u_uid != qp->msg_perm.cuid
			 && !suser())
			break;
		if(copyin((caddr_t)uap->buf, (caddr_t)&ds, 
					(unsigned)sizeof(ds))) {
			u.u_error = EFAULT;
			break;
		}
		if(ds.Dmsg_qbytes > qp->msg_qbytes && !suser())
			break;
		qp->msg_perm.uid = ds.Dmsg_perm.uid;
		qp->msg_perm.gid = ds.Dmsg_perm.gid;
		qp->msg_perm.mode = (qp->msg_perm.mode & ~0777) |
			(ds.Dmsg_perm.mode & 0777);
		qp->msg_qbytes = ds.Dmsg_qbytes;
		qp->msg_ctime = (int)time.tv_sec;
		break;
	case IPC_STAT:
		if((qp = msgconv(uap->msgid)) == NULL) 
			return;
		if(ipcaccess(&qp->msg_perm, MSG_R))
			break;
		if(copyout((caddr_t)&qp->Dmsq, (caddr_t)uap->buf, 
					(unsigned)sizeof(qp->Dmsq))) {
			u.u_error = EFAULT;
			break;
		}
		break;
	default:
		u.u_error = EINVAL;
		return;
	}
	v_sema(&qp->mutex);
}

/*
**	msgfree - Free up space and message header, relink pointers on q,
**	and wakeup anyone waiting for resources.
**	lock the message free queue internally.  The message queue header
**	must already be locked on entry.  Note, processes won't block 
**	in this routine, so OK.
*/

static
msgfree(qp, pmp, mp)
register struct msqid_ds	*qp;	/* ptr to q of mesg being freed */
register struct msg		*mp,	/* ptr to msg being freed */
				*pmp;	/* ptr to mp's predecessor */
{
	spl_t ipl;

	/* Unlink message from the q. */
	if(pmp == NULL)
		qp->msg_first = mp->msg_next;
	else
		pmp->msg_next = mp->msg_next;
	if(mp->msg_next == NULL)
		qp->msg_last = pmp;
	qp->msg_qnum--;
	while(qp->msg_nwriters > 0) {
		v_sema(&qp->msg_writers);
		qp->msg_nwriters--;
	}

	/* Free up message text. */
	if(mp->msg_ts) {
		ipl = p_lock(&msgmap_lock, SPLFS);
		rmfree(msgmap, (long)btoq(mp->msg_ts), (long)(mp->msg_spot + 1));
		if (msgmap_want > 0) {
			vall_sema(&msgmap_wait);
			msgmap_want = 0;
		}
		v_lock(&msgmap_lock, ipl);
	}

	/* Free up header */
	ipl = p_lock(&msgfp_lock, SPLFS);
	mp->msg_next = msgfp;
	if(msgfp == NULL)
		vall_sema(&msgfp_wait);
	msgfp = mp;
	v_lock(&msgfp_lock, ipl);
}

/*
**	msgget - Msgget system call.
*/

static
msgget(uap)
	register struct {
		key_t	key;
		int	msgflg;
	} *uap;
{
	register struct msqid_ds	*qp;	/* ptr to associated q */
	int				s;	/* ipcget status return */
	spl_t				ipl;

	ipl = p_lock(&msgque_lock, SPLFS);
	if((qp = (struct msqid_ds *)ipcget(uap->key, uap->msgflg, 
			(struct ipc_perm *)msgque, 
			msginfo.msgmni, sizeof(*qp), &s)) == NULL) {
			v_lock(&msgque_lock, ipl);
	 		return;
	}

	if(s) {
		/* This is a new queue.  Finish initialization. */
		qp->msg_first = qp->msg_last = NULL;
		qp->msg_qnum = 0;
		qp->msg_qbytes = msginfo.msgmnb;
		qp->msg_lspid = qp->msg_lrpid = 0;
		qp->msg_stime = qp->msg_rtime = 0;
		qp->msg_ctime = (int)time.tv_sec;
		qp->msg_nreaders = qp->msg_nwriters = 0;
	}
	
	u.u_r.r_val1 = qp->msg_perm.seq * msginfo.msgmni + (qp - msgque);
	v_lock(&msgque_lock, ipl);
}

/*
**	msginit - Called by main(main.c) to initialize message queues.
*/

msginit()
{
	register int		i;	/* loop control */
	register struct msg	*mp;	/* ptr to msg begin linked */

	Bmsg = (caddr_t)calloc(msginfo.msgseg*msginfo.msgssz*sizeof(char));
	msgmap = (struct map *)calloc(msginfo.msgmap*sizeof(struct map));
	msgque = (struct msqid_ds *)calloc(msginfo.msgmni*sizeof(struct msqid_ds));
	msgh = (struct msg *)calloc(msginfo.msgtql*sizeof(struct msg));
	msg = Bmsg;
	init_lock(&msgque_lock);
	rminit(msgmap, (long)msginfo.msgseg, (long)1, "msgmap", msginfo.msgmap);
	init_lock(&msgmap_lock);
	msgmap_want = 0;
	init_sema(&msgmap_wait, 0);

	for(i = 0, mp = msgfp = msgh;++i < msginfo.msgtql;mp++) 
		mp->msg_next = mp + 1;
	init_lock(&msgfp_lock);
	init_sema(&msgfp_wait, 0);

	for(i = 0; i < msginfo.msgmni; i++) {
		init_sema(&msgque[i].mutex, 1);
		init_sema(&msgque[i].msg_writers, 0);
		init_sema(&msgque[i].msg_readers, 0);
	}
}

/*
**	msgrcv - Msgrcv system call.
*/

static
msgrcv(uap)
	register struct {
		int		msqid;
		struct msgbuf	*msgp;
		int		msgsz;
		long		msgtyp;
		int		msgflg;
	} *uap;
{
	register struct msg		*mp,	/* ptr to msg on q */
					*pmp,	/* ptr to mp's predecessor */
					*smp,	/* ptr to best msg on q */
					*spmp;	/* ptr to smp's predecessor */
	register struct msqid_ds	*qp;	/* ptr to associated q */
	int				sz;	/* transfer byte count */

	if(uap->msgsz < 0) {
		u.u_error = EINVAL;
		return;
	}
	l.cnt.v_msg++;
	if((qp = msgconv(uap->msqid)) == NULL)
		return;
	if(ipcaccess(&qp->msg_perm, MSG_R))
		goto leave;
	smp = spmp = NULL;
findmsg:
	pmp = NULL;
	mp = qp->msg_first;
	if(uap->msgtyp == 0)
		smp = mp;
	else
		for(;mp;pmp = mp, mp = mp->msg_next) {
			if(uap->msgtyp > 0) {
				if(uap->msgtyp != mp->msg_type)
					continue;
				smp = mp;
				spmp = pmp;
				break;
			}
			if(mp->msg_type <= -uap->msgtyp) {
				if(smp && smp->msg_type <= mp->msg_type)
					continue;
				smp = mp;
				spmp = pmp;
			}
		}
	if(smp) {
		if(uap->msgsz < smp->msg_ts)
			if(!(uap->msgflg & MSG_NOERROR)) {
				u.u_error = E2BIG;
				goto leave;
			} else
				sz = uap->msgsz;
		else
			sz = smp->msg_ts;
		u.u_error = copyout((caddr_t)&smp->msg_type, (caddr_t)uap->msgp,
					(unsigned)sizeof(smp->msg_type));
		if(u.u_error)
			goto leave;
		if(sz) {
			u.u_error = 
				copyout( msg + msginfo.msgssz * smp->msg_spot,
				(caddr_t)uap->msgp + sizeof(smp->msg_type), 
				(unsigned)sz);
			if(u.u_error)
				goto leave;
		}
		u.u_r.r_val1 = sz;
		qp->msg_cbytes -= smp->msg_ts;
		qp->msg_lrpid = u.u_procp->p_pid;
		qp->msg_rtime = (int)time.tv_sec;
		msgfree(qp, spmp, smp);
		goto leave;
	}
	if(uap->msgflg & IPC_NOWAIT) {
		u.u_error = ENOMSG;
		goto leave;
	}
	if(msgsleep(qp, &qp->msg_readers, &qp->msg_nreaders))
		return;
	if(msgconv(uap->msqid) == NULL) {
		u.u_error = EIDRM;
		return;
	}
	goto findmsg;
leave:
	v_sema(&qp->mutex);
}

/*
**	msgsnd - Msgsnd system call.
*/

/*
 * re-arrange a bunch of things to reduce locking problems.
 */

static
msgsnd(uap)
	register struct {
		int		msqid;
		struct msgbuf	*msgp;
		int		msgsz;
		int		msgflg;
	} *uap;
{
	register struct msqid_ds	*qp;	/* ptr to associated q */
	register struct msg		*mp, 
					*pmp;	/* ptr to allocated msg hdr */
	register int			cnt,	/* byte count */
					spot;	/* msg pool allocation spot */
	long				type;	/* msg type */
	int				lckd;
	spl_t				ipl;
	label_t				saved_qsave;

	if((cnt = uap->msgsz) < 0 || cnt > msginfo.msgmax) {
		u.u_error = EINVAL;
		return;
	}
	u.u_error = copyin((caddr_t)uap->msgp, (caddr_t)&type, 
					(unsigned)sizeof(type));
	if(u.u_error)
		return;
	if(type < 1) {
		u.u_error = EINVAL;
		return;
	}
	l.cnt.v_msg++;
	if((qp = msgconv(uap->msqid)) == NULL)
		return;
	if(ipcaccess(&qp->msg_perm, MSG_W)) {
		v_sema(&qp->mutex);
		return;
	}
	lckd = 1;

	/*
	 * Let's save away the u_qsave area that we had on
	 * entry to msgsnd.  Do it now (and before the
	 * getres loop to avoid saving away the wrong one).
	 */
	saved_qsave = u.u_qsave;
getres:
	/* Be sure that q has not been removed. */
	if(lckd == 0 && msgconv(uap->msqid) == NULL) {
		u.u_error = EIDRM;
		goto out;
	}
	lckd = 0;

	/* Allocate space on q, message header, & buffer space. */
	if(cnt + qp->msg_cbytes > qp->msg_qbytes) {
		if(uap->msgflg & IPC_NOWAIT) {
			u.u_error = EAGAIN;
			v_sema(&qp->mutex);
			goto out;
		}
		if (msgsleep(qp, &qp->msg_writers, &qp->msg_nwriters))
			goto out;
		goto getres;
	}
	ipl = p_lock(&msgfp_lock, SPLFS);
	if(msgfp == NULL) {
		v_sema(&qp->mutex);
		if(uap->msgflg & IPC_NOWAIT) {
			u.u_error = EAGAIN;
			v_lock(&msgfp_lock, ipl);
			goto out;
		}
		/*
		 * trap will return EINTR if interrupted from sleep
		 */
		if (setjmp(&u.u_qsave)) {
			msgqlog[MSGFP_WAIT]++;
			u.u_error = EINTR;
			goto out;
		}
		msgrlog[MSGFP_WAIT]++;
		(void)p_sema_v_lock(&msgfp_wait, PMSG, &msgfp_lock);
		goto getres;
	}
	(void)p_lock(&msgmap_lock, SPLFS);
	if(cnt && (spot = (int)rmalloc(msgmap, (long)btoq(cnt))) == NULL) {
		v_sema(&qp->mutex);
		v_lock(&msgfp_lock, SPLFS);
		if(uap->msgflg & IPC_NOWAIT) {
			u.u_error = EAGAIN;
			v_lock(&msgmap_lock, ipl);
			goto out;
		}
		msgmap_want = 1;
		/*
		 * trap will return EINTR if interrupted from sleep
		 */
		if (setjmp(&u.u_qsave)) {
			msgqlog[MSGMAP_WAIT]++;
			u.u_error = EINTR;
			goto out;
		}
		msgrlog[MSGMAP_WAIT]++;
		(void)p_sema_v_lock(&msgmap_wait, PMSG, &msgmap_lock);
		goto getres;
	}
	/* 
	 * put messge buffer on queue, delete again later if needed
	 */
	qp->msg_qnum++;
	qp->msg_cbytes += cnt;
	mp = msgfp;
	msgfp = mp->msg_next;
	mp->msg_next = NULL;
	mp->msg_type = type;
	mp->msg_ts = cnt;
	mp->msg_spot = cnt ? (spot - 1) : -1;
	if((pmp = qp->msg_last) == NULL)
		qp->msg_first = qp->msg_last = mp;
	else {
		qp->msg_last->msg_next = mp;
		qp->msg_last = mp;
	}
	v_lock(&msgfp_lock, SPLFS);
	v_lock(&msgmap_lock, ipl);
	/* Everything is available, copy in text */
	if(cnt) {
		u.u_error = copyin((caddr_t)uap->msgp + sizeof(type),
			msg + msginfo.msgssz * --spot, (unsigned)cnt);
		if(u.u_error) {
			msgfree(qp, pmp, mp);
			v_sema(&qp->mutex);
			goto out;
		}
	}
	qp->msg_lspid = u.u_procp->p_pid;
	qp->msg_stime = (int)time.tv_sec;
	while(qp->msg_nreaders > 0) {
		v_sema(&qp->msg_readers);
		qp->msg_nreaders--;
	}
	u.u_r.r_val1 = 0;
	v_sema(&qp->mutex);
out:
	/*
	 * Restore the saved u_qsave area.
	 */
	u.u_qsave = saved_qsave;
}

/*
**	msgsys - System entry point for msgctl, msgget, msgrcv, and msgsnd
**		system calls.
*/

msgsys()
{
	int		msgctl(),
			msgget(),
			msgrcv(),
			msgsnd();
	static int	(*calls[])() = { msgget, msgctl, msgrcv, msgsnd };
	register struct a {
		unsigned	id;	/* function code id */
		int		*ap;	/* arg pointer for recvmsg */
	}		*uap = (struct a *)u.u_ap;

	if (uap->id > 3) {
		u.u_error = EINVAL;
		return;
	}
	(*calls[uap->id])(&u.u_arg[1]);
}

/*
 * This function is entered with the mutex semaphore
 * held.  This semaphore is released during the sleep period
 * It remains released upon return.  This is because the
 * validity of the queue must be revalidiate anyway.
 */

static
msgsleep(qp, wait_sema, wait_count)
	struct msqid_ds *qp;
	sema_t	*wait_sema;
	short	*wait_count;
{
  	label_t saved_qsave;

	saved_qsave = u.u_qsave;
	if (setjmp(&u.u_qsave)) {
		u.u_qsave = saved_qsave;
		(void)p_sema(&qp->mutex, PMSGM);
		msgqlog[MSG_RW]++;
		(*wait_count)--;
		u.u_error = EINTR;
		v_sema(&qp->mutex);
		return(1);
	}
	(*wait_count)++;
	/*
	 * Note wait_count CAN be zero at this point, due to races with
	 * signals, etc.  BUT if this is true, then wait_sema must have
	 * a value which will cause this proces to NOT block.  SO, it'll
	 * return form here w/o sleeping.  The loop around the sleep code
	 * will take care of the rest.
	 */
	msgrlog[MSG_RW]++;
	v_sema(&qp->mutex);
	(void)p_sema(wait_sema, PMSG);
	u.u_qsave = saved_qsave;
	return(0);
}
