/*
 * linux/ipc/msg.c
 * Copyright (C) 1992 Krishna Balasubramanian 
 *
 * Kerneld extensions by Bjorn Ekwall <bj0rn@blox.se> in May 1995, and May 1996
 *
 * See <linux/kerneld.h> for the (optional) new kerneld protocol
 */

#include <linux/config.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/msg.h>
#include <linux/stat.h>
#include <linux/malloc.h>
#include <linux/kerneld.h>
#include <linux/interrupt.h>

#include <asm/segment.h>

extern int ipcperms (struct ipc_perm *ipcp, short msgflg);

static void freeque (int id);
static int newque (key_t key, int msgflg);
static int findkey (key_t key);

static struct msqid_ds *msgque[MSGMNI];
static int msgbytes = 0;
static int msghdrs = 0;
static unsigned short msg_seq = 0;
static int used_queues = 0;
static int max_msqid = 0;
static struct wait_queue *msg_lock = NULL;
static int kerneld_msqid = -1;

#define MAX_KERNELDS 20
static int kerneld_arr[MAX_KERNELDS];
static int n_kernelds = 0;

void msg_init (void)
{
	int id;
	
	for (id = 0; id < MSGMNI; id++) 
		msgque[id] = (struct msqid_ds *) IPC_UNUSED;
	msgbytes = msghdrs = msg_seq = max_msqid = used_queues = 0;
	msg_lock = NULL;
	return;
}

/*
 * If the send queue is full, try to free any old messages.
 * These are most probably unwanted, since no one has picked them up...
 */
#define MSG_FLUSH_TIME 10 /* seconds */
static void flush_msg(struct msqid_ds *msq)
{
	struct msg *nmsg;
	unsigned long flags;
	int flushed = 0;

	save_flags(flags);
	cli();

	/* messages were put on the queue in time order */
	while ( (nmsg = msq->msg_first) &&
		((CURRENT_TIME - nmsg->msg_stime) > MSG_FLUSH_TIME)) {
		msgbytes -= nmsg->msg_ts; 
		msghdrs--; 
		msq->msg_cbytes -= nmsg->msg_ts;
		msq->msg_qnum--;
		msq->msg_first = nmsg->msg_next;
		++flushed;
		kfree(nmsg);
	}

	if (msq->msg_qnum == 0)
		msq->msg_first = msq->msg_last = NULL;
	restore_flags(flags);
	if (flushed)
		printk(KERN_WARNING "flushed %d old SYSVIPC messages", flushed);
}

static int real_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
{
	int id, err;
	struct msqid_ds *msq;
	struct ipc_perm *ipcp;
	struct msg *msgh;
	long mtype;
	unsigned long flags;
	
	if (msgsz > MSGMAX || (long) msgsz < 0 || msqid < 0)
		return -EINVAL;
	if (!msgp) 
		return -EFAULT;
	/*
	 * Calls from kernel level (IPC_KERNELD set)
	 * have the message somewhere in kernel space already!
	 */
	if ((msgflg & IPC_KERNELD))
		mtype = msgp->mtype;
	else {
		err = verify_area (VERIFY_READ, msgp->mtext, msgsz);
		if (err) 
			return err;
		if ((mtype = get_user (&msgp->mtype)) < 1)
			return -EINVAL;
	}
	id = (unsigned int) msqid % MSGMNI;
	msq = msgque [id];
	if (msq == IPC_UNUSED || msq == IPC_NOID)
		return -EINVAL;
	ipcp = &msq->msg_perm; 

 slept:
	if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) 
		return -EIDRM;
	/*
	 * Non-root kernel level processes may send to kerneld! 
	 * i.e. no permission check if called from the kernel
	 * otoh we don't want user level non-root snoopers...
	 */
	if ((msgflg & IPC_KERNELD) == 0)
		if (ipcperms(ipcp, S_IWUGO)) 
			return -EACCES;
	
	if (msgsz + msq->msg_cbytes > msq->msg_qbytes) { 
		if ((kerneld_msqid != -1) && (kerneld_msqid == msqid))
			flush_msg(msq); /* flush the kerneld channel only */
		if (msgsz + msq->msg_cbytes > msq->msg_qbytes) { 
			/* still no space in queue */
			if (msgflg & IPC_NOWAIT)
				return -EAGAIN;
			if (current->signal & ~current->blocked)
				return -EINTR;
			if (intr_count) {
				/* Very unlikely, but better safe than sorry */
				printk(KERN_WARNING "Ouch, kerneld:msgsnd buffers full!\n");
				return -EINTR;
			}
			interruptible_sleep_on (&msq->wwait);
			goto slept;
		}
	}
	
	/* allocate message header and text space*/ 
	msgh = (struct msg *) kmalloc (sizeof(*msgh) + msgsz, GFP_ATOMIC);
	if (!msgh)
		return -ENOMEM;
	msgh->msg_spot = (char *) (msgh + 1);

	/*
	 * Calls from kernel level (IPC_KERNELD set)
	 * have the message somewhere in kernel space already!
	 */
	if (msgflg & IPC_KERNELD) {
		struct kerneld_msg *kdmp = (struct kerneld_msg *)msgp;

		/*
		 * Note that the kernel supplies a pointer
		 * but the user-level kerneld uses a char array...
		 */
		memcpy(msgh->msg_spot, (char *)(&(kdmp->id)), KDHDR); 
		memcpy(msgh->msg_spot + KDHDR, kdmp->text, msgsz - KDHDR); 
	}
	else
		memcpy_fromfs (msgh->msg_spot, msgp->mtext, msgsz); 
	
	if (msgque[id] == IPC_UNUSED || msgque[id] == IPC_NOID
		|| msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) {
		kfree(msgh);
		return -EIDRM;
	}

	msgh->msg_next = NULL;
	msgh->msg_ts = msgsz;
	msgh->msg_type = mtype;
	msgh->msg_stime = CURRENT_TIME;

	save_flags(flags);
	cli();
	if (!msq->msg_first)
		msq->msg_first = msq->msg_last = msgh;
	else {
		msq->msg_last->msg_next = msgh;
		msq->msg_last = msgh;
	}
	msq->msg_cbytes += msgsz;
	msgbytes  += msgsz;
	msghdrs++;
	msq->msg_qnum++;
	msq->msg_lspid = current->pid;
	msq->msg_stime = CURRENT_TIME;
	restore_flags(flags);
	wake_up (&msq->rwait);
	return 0;
}

/*
 * Take care of missing kerneld, especially in case of multiple daemons
 */
#define KERNELD_TIMEOUT 1 * (HZ)
#define DROP_TIMER del_timer(&kd_timer)
/*#define DROP_TIMER if ((msgflg & IPC_KERNELD) && kd_timer.next && kd_timer.prev) del_timer(&kd_timer)*/

static void kd_timeout(unsigned long msgid)
{
	struct msqid_ds *msq;
	struct msg *tmsg;
	unsigned long flags;

	msq = msgque [ (unsigned int) kerneld_msqid % MSGMNI ];
	if (msq == IPC_NOID || msq == IPC_UNUSED)
		return;

	save_flags(flags);
	cli();
	for (tmsg = msq->msg_first; tmsg; tmsg = tmsg->msg_next)
		if (*(long *)(tmsg->msg_spot) == msgid)
			break;
	restore_flags(flags);
	if (tmsg) { /* still there! */
		struct kerneld_msg kmsp = { msgid, NULL_KDHDR, "" };

		printk(KERN_ALERT "Ouch, no kerneld for message %ld\n", msgid);
		kmsp.id = -ENODEV;
		real_msgsnd(kerneld_msqid, (struct msgbuf *)&kmsp, KDHDR,
			S_IRUSR | S_IWUSR | IPC_KERNELD | MSG_NOERROR);
	}
}

static int real_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg)
{
	struct timer_list kd_timer = { NULL, NULL, 0, 0, 0};
	struct msqid_ds *msq;
	struct ipc_perm *ipcp;
	struct msg *tmsg, *leastp = NULL;
	struct msg *nmsg = NULL;
	int id, err;
	unsigned long flags;

	if (msqid < 0 || (long) msgsz < 0)
		return -EINVAL;
	if (!msgp || !msgp->mtext)
	    return -EFAULT;
	/*
	 * Calls from kernel level (IPC_KERNELD set)
	 * wants the message put in kernel space!
	 */
	if ((msgflg & IPC_KERNELD) == 0) {
		err = verify_area (VERIFY_WRITE, msgp->mtext, msgsz);
		if (err)
			return err;
	}

	id = (unsigned int) msqid % MSGMNI;
	msq = msgque [id];
	if (msq == IPC_NOID || msq == IPC_UNUSED)
		return -EINVAL;
	ipcp = &msq->msg_perm; 

	/*
	 * Start timer for missing kerneld
	 */
	if (msgflg & IPC_KERNELD) {
		kd_timer.data = (unsigned long)msgtyp;
		kd_timer.expires = jiffies + KERNELD_TIMEOUT;
		kd_timer.function = kd_timeout;
		add_timer(&kd_timer);
	}

	/* 
	 *  find message of correct type.
	 *  msgtyp = 0 => get first.
	 *  msgtyp > 0 => get first message of matching type.
	 *  msgtyp < 0 => get message with least type must be < abs(msgtype).  
	 */
	while (!nmsg) {
		if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI) {
			DROP_TIMER;
			return -EIDRM;
		}
		if ((msgflg & IPC_KERNELD) == 0) {
			/*
			 * All kernel level processes may receive from kerneld! 
			 * i.e. no permission check if called from the kernel
			 * otoh we don't want user level non-root snoopers...
			 */
			if (ipcperms (ipcp, S_IRUGO)) {
				DROP_TIMER; /* Not needed, but doesn't hurt */
				return -EACCES;
			}
		}

		save_flags(flags);
		cli();
		if (msgtyp == 0) 
			nmsg = msq->msg_first;
		else if (msgtyp > 0) {
			if (msgflg & MSG_EXCEPT) { 
				for (tmsg = msq->msg_first; tmsg; 
				     tmsg = tmsg->msg_next)
					if (tmsg->msg_type != msgtyp)
						break;
				nmsg = tmsg;
			} else {
				for (tmsg = msq->msg_first; tmsg; 
				     tmsg = tmsg->msg_next)
					if (tmsg->msg_type == msgtyp)
						break;
				nmsg = tmsg;
			}
		} else {
			for (leastp = tmsg = msq->msg_first; tmsg; 
			     tmsg = tmsg->msg_next) 
				if (tmsg->msg_type < leastp->msg_type) 
					leastp = tmsg;
			if (leastp && leastp->msg_type <= - msgtyp)
				nmsg = leastp;
		}
		restore_flags(flags);
		
		if (nmsg) { /* done finding a message */
			DROP_TIMER;
			if ((msgsz < nmsg->msg_ts) && !(msgflg & MSG_NOERROR)) {
				return -E2BIG;
			}
			msgsz = (msgsz > nmsg->msg_ts)? nmsg->msg_ts : msgsz;
			save_flags(flags);
			cli();
			if (nmsg ==  msq->msg_first)
				msq->msg_first = nmsg->msg_next;
			else {
				for (tmsg = msq->msg_first; tmsg; 
				     tmsg = tmsg->msg_next)
					if (tmsg->msg_next == nmsg) 
						break;
				tmsg->msg_next = nmsg->msg_next;
				if (nmsg == msq->msg_last)
					msq->msg_last = tmsg;
			}
			if (!(--msq->msg_qnum))
				msq->msg_last = msq->msg_first = NULL;
			
			msq->msg_rtime = CURRENT_TIME;
			msq->msg_lrpid = current->pid;
			msgbytes -= nmsg->msg_ts; 
			msghdrs--; 
			msq->msg_cbytes -= nmsg->msg_ts;
			restore_flags(flags);
			wake_up (&msq->wwait);
			/*
			 * Calls from kernel level (IPC_KERNELD set)
			 * wants the message copied to kernel space!
			 */
			if (msgflg & IPC_KERNELD) {
				struct kerneld_msg *kdmp = (struct kerneld_msg *) msgp;

				memcpy((char *)(&(kdmp->id)),
					nmsg->msg_spot, KDHDR); 
				/*
				 * Note that kdmp->text is a pointer
				 * when called from kernel space!
				 */
				if ((msgsz > KDHDR) && kdmp->text)
					memcpy(kdmp->text,
						nmsg->msg_spot + KDHDR,
						msgsz - KDHDR); 
			}
			else {
				put_user (nmsg->msg_type, &msgp->mtype);
				memcpy_tofs (msgp->mtext, nmsg->msg_spot, msgsz);
			}
			kfree(nmsg);
			return msgsz;
		} else {  /* did not find a message */
			if (msgflg & IPC_NOWAIT) {
				DROP_TIMER;
				return -ENOMSG;
			}
			if (current->signal & ~current->blocked) {
				DROP_TIMER;
				return -EINTR; 
			}
			interruptible_sleep_on (&msq->rwait);
		}
	} /* end while */
	DROP_TIMER;
	return -1;
}

asmlinkage int sys_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
{
	/* IPC_KERNELD is used as a marker for kernel level calls */
	return real_msgsnd(msqid, msgp, msgsz, msgflg & ~IPC_KERNELD);
}

asmlinkage int sys_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,
	long msgtyp, int msgflg)
{
	/* IPC_KERNELD is used as a marker for kernel level calls */
	return real_msgrcv (msqid, msgp, msgsz, msgtyp, msgflg & ~IPC_KERNELD);
}

static int findkey (key_t key)
{
	int id;
	struct msqid_ds *msq;
	
	for (id = 0; id <= max_msqid; id++) {
		while ((msq = msgque[id]) == IPC_NOID) 
			interruptible_sleep_on (&msg_lock);
		if (msq == IPC_UNUSED)
			continue;
		if (key == msq->msg_perm.key)
			return id;
	}
	return -1;
}

static int newque (key_t key, int msgflg)
{
	int id;
	struct msqid_ds *msq;
	struct ipc_perm *ipcp;

	for (id = 0; id < MSGMNI; id++) 
		if (msgque[id] == IPC_UNUSED) {
			msgque[id] = (struct msqid_ds *) IPC_NOID;
			goto found;
		}
	return -ENOSPC;

found:
	msq = (struct msqid_ds *) kmalloc (sizeof (*msq), GFP_KERNEL);
	if (!msq) {
		msgque[id] = (struct msqid_ds *) IPC_UNUSED;
		wake_up (&msg_lock);
		return -ENOMEM;
	}
	ipcp = &msq->msg_perm;
	ipcp->mode = (msgflg & S_IRWXUGO);
	ipcp->key = key;
	ipcp->cuid = ipcp->uid = current->euid;
	ipcp->gid = ipcp->cgid = current->egid;
	msq->msg_perm.seq = msg_seq;
	msq->msg_first = msq->msg_last = NULL;
	msq->rwait = msq->wwait = NULL;
	msq->msg_cbytes = msq->msg_qnum = 0;
	msq->msg_lspid = msq->msg_lrpid = 0;
	msq->msg_stime = msq->msg_rtime = 0;
	msq->msg_qbytes = MSGMNB;
	msq->msg_ctime = CURRENT_TIME;
	if (id > max_msqid)
		max_msqid = id;
	msgque[id] = msq;
	used_queues++;
	wake_up (&msg_lock);
	return (unsigned int) msq->msg_perm.seq * MSGMNI + id;
}

asmlinkage int sys_msgget (key_t key, int msgflg)
{
	int id;
	struct msqid_ds *msq;
	
	/*
	 * If the IPC_KERNELD flag is set, the key is forced to IPC_PRIVATE,
	 * and a designated kerneld message queue is created/referred to
	 */
	if ((msgflg & IPC_KERNELD)) {
		int i;
		if (!suser())
			return -EPERM;
#ifdef NEW_KERNELD_PROTOCOL
		if ((msgflg & IPC_KERNELD) == OLDIPC_KERNELD) {
			printk(KERN_ALERT "Please recompile your kerneld daemons!\n");
			return -EPERM;
		}
#endif
		if ((kerneld_msqid == -1) && (kerneld_msqid =
				newque(IPC_PRIVATE, msgflg & S_IRWXU)) < 0)
			return -ENOSPC;
		for (i = 0; i < MAX_KERNELDS; ++i) {
			if (kerneld_arr[i] == 0) {
				kerneld_arr[i] = current->pid;
				++n_kernelds;
				return kerneld_msqid;
			}
		}
		return -ENOSPC;
	}
	/* else it is a "normal" request */
	if (key == IPC_PRIVATE) 
		return newque(key, msgflg);
	if ((id = findkey (key)) == -1) { /* key not used */
		if (!(msgflg & IPC_CREAT))
			return -ENOENT;
		return newque(key, msgflg);
	}
	if (msgflg & IPC_CREAT && msgflg & IPC_EXCL)
		return -EEXIST;
	msq = msgque[id];
	if (msq == IPC_UNUSED || msq == IPC_NOID)
		return -EIDRM;
	if (ipcperms(&msq->msg_perm, msgflg))
		return -EACCES;
	return (unsigned int) msq->msg_perm.seq * MSGMNI + id;
} 

static void freeque (int id)
{
	struct msqid_ds *msq = msgque[id];
	struct msg *msgp, *msgh;

	msq->msg_perm.seq++;
	msg_seq = (msg_seq+1) % ((unsigned)(1<<31)/MSGMNI); /* increment, but avoid overflow */
	msgbytes -= msq->msg_cbytes;
	if (id == max_msqid)
		while (max_msqid && (msgque[--max_msqid] == IPC_UNUSED));
	msgque[id] = (struct msqid_ds *) IPC_UNUSED;
	used_queues--;
	while (waitqueue_active(&msq->rwait) || waitqueue_active(&msq->wwait)) {
		wake_up (&msq->rwait); 
		wake_up (&msq->wwait);
		schedule(); 
	}
	for (msgp = msq->msg_first; msgp; msgp = msgh ) {
		msgh = msgp->msg_next;
		msghdrs--;
		kfree(msgp);
	}
	kfree(msq);
}

asmlinkage int sys_msgctl (int msqid, int cmd, struct msqid_ds *buf)
{
	int id, err;
	struct msqid_ds *msq;
	struct msqid_ds tbuf;
	struct ipc_perm *ipcp;
	
	if (msqid < 0 || cmd < 0)
		return -EINVAL;
	switch (cmd) {
	case IPC_INFO: 
	case MSG_INFO: 
		if (!buf)
			return -EFAULT;
	{ 
		struct msginfo msginfo;
		msginfo.msgmni = MSGMNI;
		msginfo.msgmax = MSGMAX;
		msginfo.msgmnb = MSGMNB;
		msginfo.msgmap = MSGMAP;
		msginfo.msgpool = MSGPOOL;
		msginfo.msgtql = MSGTQL;
		msginfo.msgssz = MSGSSZ;
		msginfo.msgseg = MSGSEG;
		if (cmd == MSG_INFO) {
			msginfo.msgpool = used_queues;
			msginfo.msgmap = msghdrs;
			msginfo.msgtql = msgbytes;
		}
		err = verify_area (VERIFY_WRITE, buf, sizeof (struct msginfo));
		if (err)
			return err;
		memcpy_tofs (buf, &msginfo, sizeof(struct msginfo));
		return max_msqid;
	}
	case MSG_STAT:
		if (!buf)
			return -EFAULT;
		err = verify_area (VERIFY_WRITE, buf, sizeof (*buf));
		if (err)
			return err;
		if (msqid > max_msqid)
			return -EINVAL;
		msq = msgque[msqid];
		if (msq == IPC_UNUSED || msq == IPC_NOID)
			return -EINVAL;
		if (ipcperms (&msq->msg_perm, S_IRUGO))
			return -EACCES;
		id = (unsigned int) msq->msg_perm.seq * MSGMNI + msqid;
		tbuf.msg_perm   = msq->msg_perm;
		tbuf.msg_stime  = msq->msg_stime;
		tbuf.msg_rtime  = msq->msg_rtime;
		tbuf.msg_ctime  = msq->msg_ctime;
		tbuf.msg_cbytes = msq->msg_cbytes;
		tbuf.msg_qnum   = msq->msg_qnum;
		tbuf.msg_qbytes = msq->msg_qbytes;
		tbuf.msg_lspid  = msq->msg_lspid;
		tbuf.msg_lrpid  = msq->msg_lrpid;
		memcpy_tofs (buf, &tbuf, sizeof(*buf));
		return id;
	case IPC_SET:
		if (!buf)
			return -EFAULT;
		err = verify_area (VERIFY_READ, buf, sizeof (*buf));
		if (err)
			return err;
		memcpy_fromfs (&tbuf, buf, sizeof (*buf));
		break;
	case IPC_STAT:
		if (!buf)
			return -EFAULT;
		err = verify_area (VERIFY_WRITE, buf, sizeof(*buf));
		if (err)
			return err;
		break;
	}

	id = (unsigned int) msqid % MSGMNI;
	msq = msgque [id];
	if (msq == IPC_UNUSED || msq == IPC_NOID)
		return -EINVAL;
	if (msq->msg_perm.seq != (unsigned int) msqid / MSGMNI)
		return -EIDRM;
	ipcp = &msq->msg_perm;

	switch (cmd) {
	case IPC_STAT:
		if (ipcperms (ipcp, S_IRUGO))
			return -EACCES;
		tbuf.msg_perm   = msq->msg_perm;
		tbuf.msg_stime  = msq->msg_stime;
		tbuf.msg_rtime  = msq->msg_rtime;
		tbuf.msg_ctime  = msq->msg_ctime;
		tbuf.msg_cbytes = msq->msg_cbytes;
		tbuf.msg_qnum   = msq->msg_qnum;
		tbuf.msg_qbytes = msq->msg_qbytes;
		tbuf.msg_lspid  = msq->msg_lspid;
		tbuf.msg_lrpid  = msq->msg_lrpid;
		memcpy_tofs (buf, &tbuf, sizeof (*buf));
		return 0;
	case IPC_SET:
		if (!suser() && current->euid != ipcp->cuid && 
		    current->euid != ipcp->uid)
			return -EPERM;
		if (tbuf.msg_qbytes > MSGMNB && !suser())
			return -EPERM;
		msq->msg_qbytes = tbuf.msg_qbytes;
		ipcp->uid = tbuf.msg_perm.uid;
		ipcp->gid =  tbuf.msg_perm.gid;
		ipcp->mode = (ipcp->mode & ~S_IRWXUGO) | 
			(S_IRWXUGO & tbuf.msg_perm.mode);
		msq->msg_ctime = CURRENT_TIME;
		return 0;
	case IPC_RMID:
		if (!suser() && current->euid != ipcp->cuid && 
		    current->euid != ipcp->uid)
			return -EPERM;
		/*
		 * There is only one kerneld message queue,
		 * mark it as non-existent
		 */
		if ((kerneld_msqid >= 0) && (msqid == kerneld_msqid))
			kerneld_msqid = -1;
		freeque (id); 
		return 0;
	default:
		return -EINVAL;
	}
}

/*
 * We do perhaps need a "flush" for waiting processes,
 * so that if they are terminated, a call from do_exit
 * will minimize the possibility of orphaned received
 * messages in the queue.  For now we just make sure
 * that the queue is shut down whenever all kernelds have died.
 */
void kerneld_exit(void)
{
	int i;

        if (kerneld_msqid == -1)
		return;
	for (i = 0; i < MAX_KERNELDS; ++i) {
		if (kerneld_arr[i] == current->pid) {
			kerneld_arr[i] = 0;
			--n_kernelds;
			if (n_kernelds == 0)
				sys_msgctl(kerneld_msqid, IPC_RMID, NULL);
			break;
		}
	}
}

/*
 * Kerneld internal message format/syntax:
 *
 * The message type from the kernel to kerneld is used to specify _what_
 * function we want kerneld to perform. 
 *
 * The "normal" message area is divided into a header, followed by a char array.
 * The header is used to hold the sequence number of the request, which will
 * be used as the return message type from kerneld back to the kernel.
 * In the return message, the header will be used to store the exit status
 * of the kerneld "job", or task.
 * The character array is used to pass parameters to kerneld and (optional)
 * return information from kerneld back to the kernel.
 * It is the responsibility of kerneld and the kernel level caller
 * to set usable sizes on the parameter/return value array, since
 * that information is _not_ included in the message format
 */

/*
 * The basic kernel level entry point to kerneld.
 *	msgtype should correspond to a task type for (a) kerneld
 *	ret_size is the size of the (optional) return _value,
 *		OR-ed with KERNELD_WAIT if we want an answer
 *	msgsize is the size (in bytes) of the message, not including
 *		the header that is always sent first in a kerneld message
 *	text is the parameter for the kerneld specific task
 *	ret_val is NULL or the kernel address where an expected answer
 *		from kerneld should be placed.
 *
 * See <linux/kerneld.h> for usage (inline convenience functions)
 *
 */
int kerneld_send(int msgtype, int ret_size, int msgsz,
		const char *text, const char *ret_val)
{
	int status = -ENOSYS;
#ifdef CONFIG_KERNELD
	static int id = KERNELD_MINSEQ;
	struct kerneld_msg kmsp = { msgtype, NULL_KDHDR, (char *)text };
	int msgflg = S_IRUSR | S_IWUSR | IPC_KERNELD | MSG_NOERROR;
	unsigned long flags;

	if (kerneld_msqid == -1)
		return -ENODEV;

	/* Do not wait for an answer at interrupt-time! */
	if (intr_count)
		ret_size &= ~KERNELD_WAIT;
#ifdef NEW_KERNELD_PROTOCOL
	else
		kmsp.pid = current->pid;
#endif

	msgsz += KDHDR;
	if (ret_size & KERNELD_WAIT) {
		save_flags(flags);
		cli();
		if (++id <= 0) /* overflow */
			id = KERNELD_MINSEQ;
		kmsp.id = id;
		restore_flags(flags);
	}

	status = real_msgsnd(kerneld_msqid, (struct msgbuf *)&kmsp, msgsz, msgflg);
	if ((status >= 0) && (ret_size & KERNELD_WAIT)) {
		ret_size &= ~KERNELD_WAIT;
		kmsp.text = (char *)ret_val;
		status = real_msgrcv(kerneld_msqid, (struct msgbuf *)&kmsp,
				KDHDR + ((ret_val)?ret_size:0),
				kmsp.id, msgflg);
		if (status > 0) /* a valid answer contains at least a long */
			status = kmsp.id;
	}

#endif /* CONFIG_KERNELD */
	return status;
}
