| /* |
| * linux/ipc/msg.c |
| * Copyright (C) 1992 Krishna Balasubramanian |
| */ |
| |
| #include <linux/errno.h> |
| #include <asm/segment.h> |
| #include <linux/sched.h> |
| #include <linux/msg.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 int 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 = msqid % MSGMNI; |
| msq = msgque [id]; |
| if (msq == IPC_UNUSED || msq == IPC_NOID) |
| return -EINVAL; |
| ipcp = &msq->msg_perm; |
| |
| slept: |
| if (ipcp->seq != (msqid / MSGMNI)) |
| return -EIDRM; |
| if (ipcperms(ipcp, 0222)) |
| 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 |
| || ipcp->seq != msqid / MSGMNI) { |
| kfree_s (msgh, sizeof(*msgh) + msgsz); |
| 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 = 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(ipcp->seq != msqid / MSGMNI) |
| return -EIDRM; |
| if (ipcperms (ipcp, 0444)) |
| 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_s (nmsg, sizeof(*nmsg) + msgsz); |
| 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 & 0x01FF); |
| ipcp->key = key; |
| ipcp->cuid = ipcp->uid = current->euid; |
| ipcp->gid = ipcp->cgid = current->egid; |
| ipcp->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 msg_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 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++; |
| if ((int)((++msg_seq + 1) * MSGMNI) < 0) |
| msg_seq = 0; |
| 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_s (msgp, sizeof(*msgp) + msgp->msg_ts); |
| } |
| kfree_s (msq, sizeof (*msq)); |
| } |
| |
| int sys_msgctl (int msqid, int cmd, struct msqid_ds *buf) |
| { |
| int id, err; |
| struct msqid_ds *msq, 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 (*msq)); |
| 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, 0444)) |
| return -EACCES; |
| id = msqid + msq->msg_perm.seq * MSGMNI; |
| memcpy_tofs (buf, msq, sizeof(*msq)); |
| return id; |
| case IPC_SET: |
| if (!buf) |
| return -EFAULT; |
| memcpy_fromfs (&tbuf, buf, sizeof (*buf)); |
| break; |
| case IPC_STAT: |
| if (!buf) |
| return -EFAULT; |
| err = verify_area (VERIFY_WRITE, buf, sizeof(*msq)); |
| if (err) |
| return err; |
| break; |
| } |
| |
| id = msqid % MSGMNI; |
| msq = msgque [id]; |
| if (msq == IPC_UNUSED || msq == IPC_NOID) |
| return -EINVAL; |
| ipcp = &msq->msg_perm; |
| if (ipcp->seq != msqid / MSGMNI) |
| return -EIDRM; |
| |
| switch (cmd) { |
| case IPC_STAT: |
| if (ipcperms (ipcp, 0444)) |
| return -EACCES; |
| memcpy_tofs (buf, msq, sizeof (*msq)); |
| return 0; |
| break; |
| case IPC_RMID: case IPC_SET: |
| if (!suser() && current->euid != ipcp->cuid && |
| current->euid != ipcp->uid) |
| return -EPERM; |
| if (cmd == IPC_RMID) { |
| freeque (id); |
| return 0; |
| } |
| 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 & ~0x1FF) | |
| (0x1FF & tbuf.msg_perm.mode); |
| msq->msg_ctime = CURRENT_TIME; |
| break; |
| default: |
| return -EINVAL; |
| break; |
| } |
| return 0; |
| } |