summaryrefslogtreecommitdiffstats
path: root/ipc/msg.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/msg.c')
-rw-r--r--ipc/msg.c441
1 files changed, 441 insertions, 0 deletions
diff --git a/ipc/msg.c b/ipc/msg.c
new file mode 100644
index 000000000..04aa12328
--- /dev/null
+++ b/ipc/msg.c
@@ -0,0 +1,441 @@
+/*
+ * linux/ipc/msg.c
+ * Copyright (C) 1992 Krishna Balasubramanian
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/msg.h>
+#include <linux/stat.h>
+#include <linux/malloc.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;
+
+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;
+}
+
+int sys_msgsnd (int msqid, struct msgbuf *msgp, int msgsz, int msgflg)
+{
+ int id, err;
+ struct msqid_ds *msq;
+ struct ipc_perm *ipcp;
+ struct msg *msgh;
+ long mtype;
+
+ if (msgsz > MSGMAX || msgsz < 0 || msqid < 0)
+ return -EINVAL;
+ if (!msgp)
+ return -EFAULT;
+ err = verify_area (VERIFY_READ, msgp->mtext, msgsz);
+ if (err)
+ return err;
+ if ((mtype = get_fs_long (&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;
+ if (ipcperms(ipcp, S_IWUGO))
+ return -EACCES;
+
+ if (msgsz + msq->msg_cbytes > msq->msg_qbytes) {
+ /* no space in queue */
+ if (msgflg & IPC_NOWAIT)
+ return -EAGAIN;
+ if (current->signal & ~current->blocked)
+ return -EINTR;
+ interruptible_sleep_on (&msq->wwait);
+ goto slept;
+ }
+
+ /* allocate message header and text space*/
+ msgh = (struct msg *) kmalloc (sizeof(*msgh) + msgsz, GFP_USER);
+ if (!msgh)
+ return -ENOMEM;
+ msgh->msg_spot = (char *) (msgh + 1);
+ 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;
+ if (!msq->msg_first)
+ msq->msg_first = msq->msg_last = msgh;
+ else {
+ msq->msg_last->msg_next = msgh;
+ msq->msg_last = msgh;
+ }
+ msgh->msg_ts = msgsz;
+ msgh->msg_type = mtype;
+ msq->msg_cbytes += msgsz;
+ msgbytes += msgsz;
+ msghdrs++;
+ msq->msg_qnum++;
+ msq->msg_lspid = current->pid;
+ msq->msg_stime = CURRENT_TIME;
+ if (msq->rwait)
+ wake_up (&msq->rwait);
+ return msgsz;
+}
+
+int sys_msgrcv (int msqid, struct msgbuf *msgp, int msgsz, long msgtyp,
+ int msgflg)
+{
+ struct msqid_ds *msq;
+ struct ipc_perm *ipcp;
+ struct msg *tmsg, *leastp = NULL;
+ struct msg *nmsg = NULL;
+ int id, err;
+
+ if (msqid < 0 || msgsz < 0)
+ return -EINVAL;
+ if (!msgp || !msgp->mtext)
+ return -EFAULT;
+ 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;
+
+ /*
+ * 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)
+ return -EIDRM;
+ if (ipcperms (ipcp, S_IRUGO))
+ return -EACCES;
+ 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;
+ }
+
+ if (nmsg) { /* done finding a message */
+ if ((msgsz < nmsg->msg_ts) && !(msgflg & MSG_NOERROR))
+ return -E2BIG;
+ msgsz = (msgsz > nmsg->msg_ts)? nmsg->msg_ts : msgsz;
+ 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;
+ if (msq->wwait)
+ wake_up (&msq->wwait);
+ put_fs_long (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)
+ return -ENOMSG;
+ if (current->signal & ~current->blocked)
+ return -EINTR;
+ interruptible_sleep_on (&msq->rwait);
+ }
+ } /* end while */
+ return -1;
+}
+
+
+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;
+ if (msg_lock)
+ 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++;
+ if (msg_lock)
+ wake_up (&msg_lock);
+ return (unsigned int) msq->msg_perm.seq * MSGMNI + id;
+}
+
+int sys_msgget (key_t key, int msgflg)
+{
+ int id;
+ struct msqid_ds *msq;
+
+ 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 (msq->rwait || msq->wwait) {
+ if (msq->rwait)
+ wake_up (&msq->rwait);
+ if (msq->wwait)
+ wake_up (&msq->wwait);
+ schedule();
+ }
+ for (msgp = msq->msg_first; msgp; msgp = msgh ) {
+ msgh = msgp->msg_next;
+ msghdrs--;
+ kfree(msgp);
+ }
+ kfree(msq);
+}
+
+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;
+ freeque (id);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}