/* * linux/ipc/msg.c * Copyright (C) 1992 Krishna Balasubramanian */ #include #include #include #include #include #include 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; } }