Coherent4.2.10/conf/msg/src/msg386.c

Compare this file to the similar file:
Show the results in this format:

/* (-lgl
 *	Coherent 386 release 4.2
 *	Copyright (c) 1982, 1994 by Mark Williams Company.
 *	All rights reserved. May not be copied without permission.
 *	For copying permission and licensing info, write licensing@mwc.com
 -lgl) */
/* $Header: /ker/io.386/RCS/msg386.c,v 2.4 93/08/19 04:03:01 nigel Exp Locker: nigel $ */
/*
 * System V Compatible Messaging
 *
 * $Log:	msg386.c,v $
 * Revision 2.4  93/08/19  04:03:01  nigel
 * Nigel's R83
 */

#include <kernel/_timers.h>
#include <sys/coherent.h>
#include <kernel/trace.h>
#include <sys/sched.h>
#include <sys/types.h>
#include <sys/uproc.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/con.h>
#include <sys/seg.h>

#include <sys/ipc.h>
#include <sys/msg.h>

#define	__MSG_READ	__IRUGO
#define	__MSG_WRITE	__IWUGO


/*
 * Global Message Parameters. We want them to be patchable.
 */

extern	unsigned NMSQID;	/* maximum number of message queues */
extern	unsigned NMSQB;		/* default maximum queue size in bytes */
extern	unsigned NMSG;		/* maximum number of messages per queue */
extern	unsigned NMSC;		/* message text size */

#ifdef	TRACER
int	dballoc = 0;	/* For debug only */
int	dbfree = 0;
#endif

/* Message Information */
struct msqid_ds *msqs = NULL;	/* Array of message queues */
__DUMB_GATE	*msg_gate;	/* Message gates */
char		**msg_map;	/* Memory map */

#define	__LOCK_MESSAGE_QUEUE(n,where) \
		(__GATE_LOCK (msg_gate [n], "lock : msgq " where))
#define	__UNLOCK_MESSAGE_QUEUE(n) \
		(__GATE_UNLOCK (msg_gate [n]))


/* 
 * Msgctl - Message Control Operations.
 */

umsgctl(qid, cmd, buf)
int qid;
int cmd;
struct msqid_ds *buf;
{
	struct msqid_ds * qp;	/* message queues */
	struct msg    *	mp;	/* single message queue */
	unsigned short	n;	/* temporary variable */
	struct msqid_ds	temp;	/* copy of user buf */

	/* Validate qid */
	if (qid < 0) {
		set_user_error (EINVAL);
		return -1;
	}
	qp = msqs + qid % NMSQID;

	/* Validate queue existence.*/
	if ((qp->msg_perm.seq != qid ||
	    (qp->msg_perm.mode & __IPC_ALLOC) == 0) &&
	    (cmd != IPC_STAT || ! super ())) {
		set_user_error (EINVAL);
		return -1;
	}

	switch (cmd) {
	case IPC_STAT:
		/* Validate access authority. */
		if (ipc_lack_perm (& qp->msg_perm, __MSG_READ))
			break;

		 /* Copy queue info to user buffer */
		kucopy (qp, buf, sizeof (struct msqid_ds));
		break;

	case IPC_SET:
		/* Validate modify authority. */
		if (ipc_cred_match (& qp->msg_perm) != _CRED_OWNER) {
			set_user_error (EPERM);
			break;
		}

		/*
		 * Get desired queue size.
		 */

		if (ukcopy (buf, & temp, sizeof (temp)) != sizeof (temp))
			break;

		/*
		 * Only super-user can increase queue size.
		 */

		if (temp.msg_qbytes > qp->msg_qbytes && ! super ()) {
			set_user_error (EPERM);
			break;
		}

		/*
		 * Set queue parameters.
		 */

		qp->msg_perm.uid = temp.msg_perm.uid;
		qp->msg_perm.gid = temp.msg_perm.gid;
		qp->msg_perm.mode = (qp->msg_perm.mode & ~ __IRWXUGO) |
					(temp.msg_perm.mode & __IRWXUGO);

		/* We may want to change the max size of a single message too.
		 * It is not obvious how to do it. There is no
		 * description in SVID. So it is possible that at some point
		 * the size of the single message happens to be greater than
		 * the size of message queue ;-(
		 */

		qp->msg_qbytes = NMSQB = temp.msg_qbytes;
		break;

	case IPC_RMID:
		 /* Validate removal authority. */
		/* Validate modify authority. */
		if (ipc_cred_match (& qp->msg_perm) != _CRED_OWNER) {
			set_user_error (EPERM);
			break;
		}

		/* Free all messages on the queue being removed. */
		while (mp = qp->msg_first) {
			qp->msg_first = mp->msg_next;
			T_MSGQ(0x01, dballoc -= sizeof (struct msg));
			msgfree (mp);
		}
		T_MSGQ(0x01, printf("F%d", dballoc));

		/* Reset queue parameters. */
		qp->msg_last = NULL;
		qp->msg_qnum = 0;
		qp->msg_cbytes = 0;
		qp->msg_perm.mode = 0;

		/* Set last change time */
		qp->msg_ctime = posix_current_time ();

		/* We have to pick up a new unique sequence number.
		 * There is a "wrap around bug". But, it is BCS.
		 */
		qp->msg_perm.seq += (unsigned short) 50;
		break;

	default:
		set_user_error (EINVAL);
	}

	if (get_user_error ())
		return -1;

	return 0;
}


/*
 * Msgget - Get set of messages
 */

umsgget (mykey, msgflg)
key_t mykey;
int msgflg;
{
	struct msqid_ds * qp;		
	struct msqid_ds * freeidp = NULL;	
	
	/* Init message queues on the first msgget */
	if (msqs == NULL)
		if (msginit ()) {
			set_user_error (ENOSPC);
			return -1;
		}

	/* Search for desired message queue [also for first free queue]. */
	for (qp = msqs; qp < msqs + NMSQID; qp++) {
		/* Look for an older free queue */
		if ((qp->msg_perm.mode & __IPC_ALLOC) == 0) {
			if (freeidp == NULL ||
			    freeidp->msg_ctime > qp->msg_ctime)
				freeidp = qp;
			continue;
		}
		if (mykey == IPC_PRIVATE) {	/* creat a new queue */
#if	0
			/*
			 * NIGEL: This looks bogus to me... why should a
			 * request for a private queue fail? My feeling is
			 * that the IPC_EXCL should be ignored.
			 */

			if ((msgflg & IPC_EXCL) != 0 &&	/* unique new queue */
			    mykey == qp->msg_perm.key) {
				set_user_error (EEXIST);/* We cannot creat */
				return -1;	   /* exclusive queue */
			}
#endif
			continue;
		}

		if (qp->msg_perm.key != mykey) 	
			continue;

		if ((msgflg & (IPC_CREAT | IPC_EXCL)) ==
		    (IPC_CREAT | IPC_EXCL)) {
			set_user_error (EEXIST);/* We cannot creat */
			return -1;		/* exclusive queue */
		}

		if (ipc_lack_perm (& qp->msg_perm, msgflg))
			return -1;
		return qp->msg_perm.seq;
	}

	/* Creat a new queue */
	if ((msgflg & IPC_CREAT) == 0) {
		set_user_error (ENOENT);
		return -1;
	}
	if ((qp = freeidp) == NULL) {
		set_user_error (ENOSPC);
		return -1;
	}

	/* Set new queue */
	ipc_perm_init (& qp->msg_perm, msgflg);
	qp->msg_ctime = posix_current_time ();
	qp->msg_qnum = qp->msg_lspid = qp->msg_lrpid = qp->msg_stime 
		= qp->msg_rtime = 0;
	qp->msg_perm.key = mykey;

	return qp->msg_perm.seq;
}


/*
 * Allocate space for the message queues and gates.
 * Initialize message queue headers
 * Return -1 on error.
 */

msginit ()
{
	int		i;
	struct msqid_ds	*qp;
	
	T_MSGQ(0x01, printf("A%d",dballoc += sizeof(struct msqid_ds) * NMSQID));

	/* Allocate space for message headers */
	if ((msqs = (struct msqid_ds *) kalloc (sizeof (struct msqid_ds)
						* NMSQID)) == NULL)
		return -1;

	T_MSGQ(0x01, printf("A%d", dballoc += sizeof(__DUMB_GATE) * NMSQID));

	/* Allocate space for message gates */
	if ((msg_gate = (__DUMB_GATE *) kalloc (sizeof (__DUMB_GATE) *
						NMSQID)) == NULL) {
		kfree (msqs);
		msqs = NULL;
		return -1;
	}

	T_MSGQ(0x01, printf("A%d", dballoc += sizeof(char *) * NMSQID * NMSG));

	/* Allocate space for the message map */
	if ((msg_map = kalloc (sizeof(char *) * NMSQID * NMSG)) == NULL) {
		kfree (msqs);
		msqs = NULL;
		kfree (msg_gate);
		return -1;
	}

	/* Clear gate and map areas */
	memset (msg_map, sizeof (char *) * NMSQID * NMSG, 0);

	for (i = 0 ; i < NMSQID ; i ++)
		__GATE_INIT (msg_gate [i], "message queue", 0);

	/* Set initial queue values */

	for (qp = msqs; qp < msqs + NMSQID; qp++) {
		qp->msg_first  = NULL;	/* First and last pointers to 	*/
		qp->msg_last   = NULL;	/* message queue	      	*/
		qp->msg_cbytes = 0;	/* Number of bytes in queue	*/
		qp->msg_qnum   = 0;	/* Number of messages in queue	*/
		qp->msg_qbytes = NMSQB;	/* Max size of a queue		*/
		qp->msg_lspid  = 0;	/* Pid of last msgsnd		*/
		qp->msg_lrpid  = 0;	/* Pid of last msgrcv		*/
		qp->msg_stime  = 0;	/* Last msgsnd time		*/
		qp->msg_rtime  = 0;	/* Last msgrcv time		*/
		qp->msg_ctime  = posix_current_time (); /* Last change time */
		qp->msg_perm.seq = qp - msqs;
		qp->msg_perm.mode = 0;
	}
	return 0;
}


/* 
 * Remove the message.	Clear message text and header. Reset values in 
 * message map.
 */

msgfree(mp)
struct msg	*mp;	/* Message header to be remove */
{
	kfree (msg_map [mp->msg_spot]);
	msg_map [mp->msg_spot] = NULL;
	kfree (mp);
	T_MSGQ(0x01, printf("F%d", dballoc-=(mp->msg_ts + sizeof(struct msg))));
}


/*
 * Send a Message
 */

umsgsnd(qid, bufp, msgsz, msgflg)
int 		qid;	/* queue id */
long	     *	bufp;	/* message buffer */
int 		msgsz;	/* message size */
int		msgflg;	/* flags */
{
	struct msqid_ds	*qp;	/* message queue */
	struct msg    *	mp;	/* message struct */ 
	struct msg    *	tmp;
	int		q_num;	/* number of a queue */
	int		i_spot;	/* # of empty entry in map */

	/* Validate queue identifier. */
	for (qp = msqs; qp < msqs + NMSQID; qp++) 
		if (qp->msg_perm.seq == qid) 	/* found */
			break;

	q_num = qp - msqs;		
	/* qid is not a valid qid identifier */
	if (q_num >= NMSQID) {
		set_user_error (EINVAL);
		return -1;
	}

	if (ipc_lack_perm (& qp->msg_perm, __MSG_WRITE))
		return -1;

	/* Check if message has a valid message type and size.
	 * The comparison with NMSQB was done because user could
	 * reduce this value.
	 */

	if (* bufp < 1 || msgsz < 0 || msgsz > NMSC || msgsz > NMSQB) {
		set_user_error (EINVAL);
		return -1;
	}
	
	/* Now we have a valid message. Check if we can send it. */

	__LOCK_MESSAGE_QUEUE (q_num, "umsgsend () #1");

	while (qp->msg_qnum >= NMSG || 
	       qp->msg_qbytes < (qp->msg_cbytes + msgsz)) {
		if (msgflg & IPC_NOWAIT) {
			set_user_error (EAGAIN);
			__UNLOCK_MESSAGE_QUEUE (q_num);
			return -1;
		}
		/* We have to wait here */
		qp->msg_perm.mode |= MSG_WWAIT;

		__UNLOCK_MESSAGE_QUEUE (q_num);

		if (x_sleep (qp, pritty, slpriSigCatch, "umsgsnd")
		    == PROCESS_SIGNALLED) {
			/* Abort if a signal was received */
			set_user_error (EINTR);
			return -1;
		}

		/* Abort if the message queue was removed. */
		if (qid != qp->msg_perm.seq) {
			set_user_error (EINVAL);
			return -1;
		}

		__LOCK_MESSAGE_QUEUE (q_num, "umsgsend () #2");
	}

	/* Find empty entry in message map */
	for (i_spot = 0; i_spot < NMSQID * NMSG ; i_spot ++)
		if (msg_map [i_spot] == NULL)
			break;
	/* It cannot happen when we do not have empty entry in map,
	 * but let check it.
	 */

	if (i_spot >= NMSQID * NMSG) {
		set_user_error (ENOSPC);
		return -1;
	}

	T_MSGQ(0x01, printf("A%d", dballoc += sizeof(struct msg)));

	/* Get space for the message header */
	if ((mp = kalloc (sizeof (struct msg))) == NULL) {

		__UNLOCK_MESSAGE_QUEUE (q_num);
		set_user_error (ENOSPC);
		return -1;
	}

	T_MSGQ(0x01, printf("A%d", dballoc += msgsz));

	/* Alloc space for the message text */
	if ((msg_map [i_spot] = kalloc (msgsz)) == NULL) {
		kfree (mp);
		__UNLOCK_MESSAGE_QUEUE (q_num);
		set_user_error (ENOSPC);
		return -1;
	}

	mp->msg_next = NULL;
	mp->msg_ts = msgsz;
	/* The map address is a number of msg_map array element */
	mp->msg_spot = i_spot;

	/* Transfer the message type and text.*/
	if (ukcopy (bufp, & mp->msg_type, sizeof (mp->msg_type)) !=
	    sizeof (mp->msg_type))
		set_user_error (EFAULT);
	else if (ukcopy (bufp + 1, msg_map [i_spot], msgsz) != msgsz)
		set_user_error (EFAULT);

	if (get_user_error ()) {
		msgfree (mp);
		__UNLOCK_MESSAGE_QUEUE (q_num);
		return -1;
	}

	/* Move the message to the desired queue. */
	if (qp->msg_first == NULL) /* This is the first message per queue */
		qp->msg_first = qp->msg_last = mp;
	else {	/* There are messages */
		/* Find last message in gueue */
		for (tmp = qp->msg_first; ; tmp = tmp->msg_next)
			if (tmp->msg_next == NULL)
				break;
		qp->msg_last = tmp->msg_next = mp;
	}
	mp->msg_next = NULL;

	/* Update queue statistics. */
	qp->msg_cbytes += msgsz;
	qp->msg_qnum ++;
	qp->msg_lspid = SELF->p_pid;
	qp->msg_stime = posix_current_time ();

	/* Unlock queue and wake processes waiting to receive. */

	__UNLOCK_MESSAGE_QUEUE (q_num);

	if (qp->msg_perm.mode & MSG_RWAIT) {
		qp->msg_perm.mode &= ~MSG_RWAIT;
		wakeup(qp);
	}
	return 0;
}


/*
 * Receive a Message
 */

umsgrcv (qid, bufp, msgsz, msgtyp, msgflg)
int		qid;	/* Message queue id */
long	      *	bufp;	/* Message buffer */
int		msgsz;	/* Message text size */
long		msgtyp;	/* Message type	*/
int 		msgflg;	/* Message flag	*/
{
	struct msqid_ds	*qp;	/* queue headers */
	struct msg    *	mp;	/* message headers */
	struct msg    *	prev;
	int		q_num;	/* queue number */

	/* Validate queue identifier. */
	if (qid < 0 || msqs == NULL) {
		set_user_error (EINVAL);
		return -1;
	}
	q_num = qid % NMSQID;
	qp = msqs + q_num;

	/* Validate queue existence.*/
	if (qp->msg_perm.seq != qid || (qp->msg_perm.mode & __IPC_ALLOC) == 0) {
		set_user_error (EINVAL);
		return -1;
	}

	/* Check permissions */

	if (ipc_lack_perm (& qp->msg_perm, __MSG_READ))
		return -1;

	/* Wait for message */
	__LOCK_MESSAGE_QUEUE (q_num, "umsgrcv () #1");

	for (;;) {
		prev = NULL;
		mp = qp->msg_first;
		/* Find mesg of type <= abs(msgtyp) */
		if (msgtyp < 0) {
			struct msg *qmin;	/* Message with lowest mtype */
			struct msg *xprev;	/* Previous message */
			
			qmin = NULL;
			xprev = prev;
			msgtyp = -msgtyp;

			for (; mp != NULL; prev = mp, mp = mp->msg_next) {
				if (mp->msg_type > msgtyp)
					continue;
				if (qmin == NULL 
					   || mp->msg_type < qmin->msg_type) {
					xprev = prev;
					qmin = mp;
				}
			}
			prev = xprev;
			mp  = qmin;
			msgtyp = -msgtyp;
		} else if (msgtyp > 0) { /* Find message of type == msgtyp */
			while (mp != NULL && mp->msg_type != msgtyp) {
				prev = mp;
				mp = mp->msg_next;
			}
		} else 	/* Else take first message */
			mp = qp->msg_first;

		if (mp != NULL)	/* Found */
			break;
	
		/* Can't wait to receive mesg */
		if ((msgflg & IPC_NOWAIT) != 0) {
			set_user_error (ENOMSG);
			__UNLOCK_MESSAGE_QUEUE (q_num);
			return -1;
		}

		/* We can go sleep now */
		qp->msg_perm.mode |= MSG_RWAIT;

		__UNLOCK_MESSAGE_QUEUE (q_num);

		if (x_sleep (qp, pritty, slpriSigCatch, "umsgrcv")
		    == PROCESS_SIGNALLED) {
			set_user_error (EINTR);
			return -1;
		}

		/* Not same q anymore */
		if (qid != qp->msg_perm.seq) {
			set_user_error (EINVAL);
			return -1;
		}

		__LOCK_MESSAGE_QUEUE (q_num, "umsgrcv () #2");
	}

	/* Ensure entire message can be transferred, or MSG_NOERROR asserted.*/
	if (msgsz < mp->msg_ts && (msgflg & MSG_NOERROR) == 0) {
		__UNLOCK_MESSAGE_QUEUE (q_num);
		set_user_error (E2BIG);
		return -1;
	}

	/* Transfer message data */
	if (msgsz > mp->msg_ts)
		msgsz = mp->msg_ts;

	if (kucopy (& mp->msg_type, bufp, sizeof (mp->msg_type)) !=
	    sizeof (mp->msg_type))
		set_user_error (EFAULT);
	else if (kucopy (msg_map [mp->msg_spot], bufp + 1, msgsz) != msgsz)
		set_user_error (EFAULT);

	/* Abort if address fault occurred during transfer. */
	if (get_user_error ()) {
		__UNLOCK_MESSAGE_QUEUE (q_num);
		return -1;
	}

	/* Remove message from queue */
	if (prev != NULL)
		prev->msg_next = mp->msg_next;
	else
		qp->msg_first = mp->msg_next;

	if (qp->msg_last == mp)
		qp->msg_last = prev;


	/* Update queue statistics */
	qp->msg_cbytes -= mp->msg_ts;
	qp->msg_qnum --;
	qp->msg_lrpid = SELF->p_pid;
	qp->msg_rtime = posix_current_time ();

	/* free message */
	msgfree(mp);

	__UNLOCK_MESSAGE_QUEUE (q_num);

	/* Wakeup processes waiting to send. */

	if (qp->msg_perm.mode & MSG_WWAIT) {
		qp->msg_perm.mode &= ~ MSG_WWAIT;
		wakeup (qp);
	}
	return msgsz;
}


/*
 * Dispatch the top-level system call; under iBCS2, all the msg... () calls
 * are funneled through a single kernel entry point.
 */

umsgsys(func, arg1, arg2, arg3, arg4, arg5)
{
	switch (func) {
	case 0: return umsgget (arg1, arg2);
	case 1: return umsgctl (arg1, arg2, arg3);
	case 2: return umsgrcv (arg1, arg2, arg3, arg4, arg5);
	case 3:	return umsgsnd (arg1, arg2, arg3, arg4);
	default:set_user_error (EINVAL);
	}
}