ipc: drop ipcctl_pre_down
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / ipc / msg.c
index fede1d06ef305cc59386bb4490c7e2e020b9f25d..b65fdf1a09dd1f81b32731bfa2fcb455389ea63c 100644 (file)
--- a/ipc/msg.c
+++ b/ipc/msg.c
@@ -66,6 +66,7 @@ struct msg_sender {
 #define SEARCH_EQUAL           2
 #define SEARCH_NOTEQUAL                3
 #define SEARCH_LESSEQUAL       4
+#define SEARCH_NUMBER          5
 
 #define msg_ids(ns)    ((ns)->ids[IPC_MSG_IDS])
 
@@ -140,27 +141,23 @@ void __init msg_init(void)
                                IPC_MSG_IDS, sysvipc_msg_proc_show);
 }
 
-/*
- * msg_lock_(check_) routines are called in the paths where the rw_mutex
- * is not held.
- */
-static inline struct msg_queue *msg_lock(struct ipc_namespace *ns, int id)
+static inline struct msg_queue *msq_obtain_object(struct ipc_namespace *ns, int id)
 {
-       struct kern_ipc_perm *ipcp = ipc_lock(&msg_ids(ns), id);
+       struct kern_ipc_perm *ipcp = ipc_obtain_object(&msg_ids(ns), id);
 
        if (IS_ERR(ipcp))
-               return (struct msg_queue *)ipcp;
+               return ERR_CAST(ipcp);
 
        return container_of(ipcp, struct msg_queue, q_perm);
 }
 
-static inline struct msg_queue *msg_lock_check(struct ipc_namespace *ns,
-                                               int id)
+static inline struct msg_queue *msq_obtain_object_check(struct ipc_namespace *ns,
+                                                       int id)
 {
-       struct kern_ipc_perm *ipcp = ipc_lock_check(&msg_ids(ns), id);
+       struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&msg_ids(ns), id);
 
        if (IS_ERR(ipcp))
-               return (struct msg_queue *)ipcp;
+               return ERR_CAST(ipcp);
 
        return container_of(ipcp, struct msg_queue, q_perm);
 }
@@ -198,9 +195,7 @@ static int newque(struct ipc_namespace *ns, struct ipc_params *params)
                return retval;
        }
 
-       /*
-        * ipc_addid() locks msq
-        */
+       /* ipc_addid() locks msq upon success. */
        id = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni);
        if (id < 0) {
                security_msg_queue_free(msq);
@@ -217,7 +212,8 @@ static int newque(struct ipc_namespace *ns, struct ipc_params *params)
        INIT_LIST_HEAD(&msq->q_receivers);
        INIT_LIST_HEAD(&msq->q_senders);
 
-       msg_unlock(msq);
+       ipc_unlock_object(&msq->q_perm);
+       rcu_read_unlock();
 
        return msq->q_perm.id;
 }
@@ -237,14 +233,9 @@ static inline void ss_del(struct msg_sender *mss)
 
 static void ss_wakeup(struct list_head *h, int kill)
 {
-       struct list_head *tmp;
-
-       tmp = h->next;
-       while (tmp != h) {
-               struct msg_sender *mss;
+       struct msg_sender *mss, *t;
 
-               mss = list_entry(tmp, struct msg_sender, list);
-               tmp = tmp->next;
+       list_for_each_entry_safe(mss, t, h, list) {
                if (kill)
                        mss->list.next = NULL;
                wake_up_process(mss->tsk);
@@ -253,14 +244,9 @@ static void ss_wakeup(struct list_head *h, int kill)
 
 static void expunge_all(struct msg_queue *msq, int res)
 {
-       struct list_head *tmp;
+       struct msg_receiver *msr, *t;
 
-       tmp = msq->q_receivers.next;
-       while (tmp != &msq->q_receivers) {
-               struct msg_receiver *msr;
-
-               msr = list_entry(tmp, struct msg_receiver, r_list);
-               tmp = tmp->next;
+       list_for_each_entry_safe(msr, t, &msq->q_receivers, r_list) {
                msr->r_msg = NULL;
                wake_up_process(msr->r_tsk);
                smp_mb();
@@ -278,7 +264,7 @@ static void expunge_all(struct msg_queue *msq, int res)
  */
 static void freeque(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)
 {
-       struct list_head *tmp;
+       struct msg_msg *msg, *t;
        struct msg_queue *msq = container_of(ipcp, struct msg_queue, q_perm);
 
        expunge_all(msq, -EIDRM);
@@ -286,11 +272,7 @@ static void freeque(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)
        msg_rmid(ns, msq);
        msg_unlock(msq);
 
-       tmp = msq->q_messages.next;
-       while (tmp != &msq->q_messages) {
-               struct msg_msg *msg = list_entry(tmp, struct msg_msg, m_list);
-
-               tmp = tmp->next;
+       list_for_each_entry_safe(msg, t, &msq->q_messages, m_list) {
                atomic_dec(&ns->msg_hdrs);
                free_msg(msg);
        }
@@ -421,31 +403,39 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
                        return -EFAULT;
        }
 
-       ipcp = ipcctl_pre_down(ns, &msg_ids(ns), msqid, cmd,
-                              &msqid64.msg_perm, msqid64.msg_qbytes);
-       if (IS_ERR(ipcp))
-               return PTR_ERR(ipcp);
+       down_write(&msg_ids(ns).rw_mutex);
+       rcu_read_lock();
+
+       ipcp = ipcctl_pre_down_nolock(ns, &msg_ids(ns), msqid, cmd,
+                                     &msqid64.msg_perm, msqid64.msg_qbytes);
+       if (IS_ERR(ipcp)) {
+               err = PTR_ERR(ipcp);
+               goto out_unlock1;
+       }
 
        msq = container_of(ipcp, struct msg_queue, q_perm);
 
        err = security_msg_queue_msgctl(msq, cmd);
        if (err)
-               goto out_unlock;
+               goto out_unlock1;
 
        switch (cmd) {
        case IPC_RMID:
+               ipc_lock_object(&msq->q_perm);
+               /* freeque unlocks the ipc object and rcu */
                freeque(ns, ipcp);
                goto out_up;
        case IPC_SET:
                if (msqid64.msg_qbytes > ns->msg_ctlmnb &&
                    !capable(CAP_SYS_RESOURCE)) {
                        err = -EPERM;
-                       goto out_unlock;
+                       goto out_unlock1;
                }
 
+               ipc_lock_object(&msq->q_perm);
                err = ipc_update_perm(&msqid64.msg_perm, ipcp);
                if (err)
-                       goto out_unlock;
+                       goto out_unlock0;
 
                msq->q_qbytes = msqid64.msg_qbytes;
 
@@ -461,25 +451,23 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
                break;
        default:
                err = -EINVAL;
+               goto out_unlock1;
        }
-out_unlock:
-       msg_unlock(msq);
+
+out_unlock0:
+       ipc_unlock_object(&msq->q_perm);
+out_unlock1:
+       rcu_read_unlock();
 out_up:
        up_write(&msg_ids(ns).rw_mutex);
        return err;
 }
 
-SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
+static int msgctl_nolock(struct ipc_namespace *ns, int msqid,
+                        int cmd, int version, void __user *buf)
 {
+       int err;
        struct msg_queue *msq;
-       int err, version;
-       struct ipc_namespace *ns;
-
-       if (msqid < 0 || cmd < 0)
-               return -EINVAL;
-
-       version = ipc_parse_version(&cmd);
-       ns = current->nsproxy->ipc_ns;
 
        switch (cmd) {
        case IPC_INFO:
@@ -490,6 +478,7 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
 
                if (!buf)
                        return -EFAULT;
+
                /*
                 * We must not return kernel stack data.
                 * due to padding, it's not enough
@@ -521,7 +510,8 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
                        return -EFAULT;
                return (max_id < 0) ? 0 : max_id;
        }
-       case MSG_STAT:  /* msqid is an index rather than a msg queue id */
+
+       case MSG_STAT:
        case IPC_STAT:
        {
                struct msqid64_ds tbuf;
@@ -530,17 +520,25 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
                if (!buf)
                        return -EFAULT;
 
+               memset(&tbuf, 0, sizeof(tbuf));
+
+               rcu_read_lock();
                if (cmd == MSG_STAT) {
-                       msq = msg_lock(ns, msqid);
-                       if (IS_ERR(msq))
-                               return PTR_ERR(msq);
+                       msq = msq_obtain_object(ns, msqid);
+                       if (IS_ERR(msq)) {
+                               err = PTR_ERR(msq);
+                               goto out_unlock;
+                       }
                        success_return = msq->q_perm.id;
                } else {
-                       msq = msg_lock_check(ns, msqid);
-                       if (IS_ERR(msq))
-                               return PTR_ERR(msq);
+                       msq = msq_obtain_object_check(ns, msqid);
+                       if (IS_ERR(msq)) {
+                               err = PTR_ERR(msq);
+                               goto out_unlock;
+                       }
                        success_return = 0;
                }
+
                err = -EACCES;
                if (ipcperms(ns, &msq->q_perm, S_IRUGO))
                        goto out_unlock;
@@ -549,8 +547,6 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
                if (err)
                        goto out_unlock;
 
-               memset(&tbuf, 0, sizeof(tbuf));
-
                kernel_to_ipc64_perm(&msq->q_perm, &tbuf.msg_perm);
                tbuf.msg_stime  = msq->q_stime;
                tbuf.msg_rtime  = msq->q_rtime;
@@ -560,29 +556,54 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
                tbuf.msg_qbytes = msq->q_qbytes;
                tbuf.msg_lspid  = msq->q_lspid;
                tbuf.msg_lrpid  = msq->q_lrpid;
-               msg_unlock(msq);
+               rcu_read_unlock();
+
                if (copy_msqid_to_user(buf, &tbuf, version))
                        return -EFAULT;
                return success_return;
        }
-       case IPC_SET:
-       case IPC_RMID:
-               err = msgctl_down(ns, msqid, cmd, buf, version);
-               return err;
+
        default:
-               return  -EINVAL;
+               return -EINVAL;
        }
 
+       return err;
 out_unlock:
-       msg_unlock(msq);
+       rcu_read_unlock();
        return err;
 }
 
+SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
+{
+       int version;
+       struct ipc_namespace *ns;
+
+       if (msqid < 0 || cmd < 0)
+               return -EINVAL;
+
+       version = ipc_parse_version(&cmd);
+       ns = current->nsproxy->ipc_ns;
+
+       switch (cmd) {
+       case IPC_INFO:
+       case MSG_INFO:
+       case MSG_STAT:  /* msqid is an index rather than a msg queue id */
+       case IPC_STAT:
+               return msgctl_nolock(ns, msqid, cmd, version, buf);
+       case IPC_SET:
+       case IPC_RMID:
+               return msgctl_down(ns, msqid, cmd, buf, version);
+       default:
+               return  -EINVAL;
+       }
+}
+
 static int testmsg(struct msg_msg *msg, long type, int mode)
 {
        switch(mode)
        {
                case SEARCH_ANY:
+               case SEARCH_NUMBER:
                        return 1;
                case SEARCH_LESSEQUAL:
                        if (msg->m_type <=type)
@@ -602,14 +623,9 @@ static int testmsg(struct msg_msg *msg, long type, int mode)
 
 static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg)
 {
-       struct list_head *tmp;
-
-       tmp = msq->q_receivers.next;
-       while (tmp != &msq->q_receivers) {
-               struct msg_receiver *msr;
+       struct msg_receiver *msr, *t;
 
-               msr = list_entry(tmp, struct msg_receiver, r_list);
-               tmp = tmp->next;
+       list_for_each_entry_safe(msr, t, &msq->q_receivers, r_list) {
                if (testmsg(msg, msr->r_msgtype, msr->r_mode) &&
                    !security_msg_queue_msgrcv(msq, msg, msr->r_tsk,
                                               msr->r_msgtype, msr->r_mode)) {
@@ -657,22 +673,25 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext,
        msg->m_type = mtype;
        msg->m_ts = msgsz;
 
-       msq = msg_lock_check(ns, msqid);
+       rcu_read_lock();
+       msq = msq_obtain_object_check(ns, msqid);
        if (IS_ERR(msq)) {
                err = PTR_ERR(msq);
-               goto out_free;
+               goto out_unlock1;
        }
 
+       ipc_lock_object(&msq->q_perm);
+
        for (;;) {
                struct msg_sender s;
 
                err = -EACCES;
                if (ipcperms(ns, &msq->q_perm, S_IWUGO))
-                       goto out_unlock_free;
+                       goto out_unlock0;
 
                err = security_msg_queue_msgsnd(msq, msg, msgflg);
                if (err)
-                       goto out_unlock_free;
+                       goto out_unlock0;
 
                if (msgsz + msq->q_cbytes <= msq->q_qbytes &&
                                1 + msq->q_qnum <= msq->q_qbytes) {
@@ -682,27 +701,37 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext,
                /* queue full, wait: */
                if (msgflg & IPC_NOWAIT) {
                        err = -EAGAIN;
-                       goto out_unlock_free;
+                       goto out_unlock0;
                }
+
                ss_add(msq, &s);
-               ipc_rcu_getref(msq);
-               msg_unlock(msq);
+
+               if (!ipc_rcu_getref(msq)) {
+                       err = -EIDRM;
+                       goto out_unlock0;
+               }
+
+               ipc_unlock_object(&msq->q_perm);
+               rcu_read_unlock();
                schedule();
 
-               ipc_lock_by_ptr(&msq->q_perm);
+               rcu_read_lock();
+               ipc_lock_object(&msq->q_perm);
+
                ipc_rcu_putref(msq);
                if (msq->q_perm.deleted) {
                        err = -EIDRM;
-                       goto out_unlock_free;
+                       goto out_unlock0;
                }
+
                ss_del(&s);
 
                if (signal_pending(current)) {
                        err = -ERESTARTNOHAND;
-                       goto out_unlock_free;
+                       goto out_unlock0;
                }
-       }
 
+       }
        msq->q_lspid = task_tgid_vnr(current);
        msq->q_stime = get_seconds();
 
@@ -718,9 +747,10 @@ long do_msgsnd(int msqid, long mtype, void __user *mtext,
        err = 0;
        msg = NULL;
 
-out_unlock_free:
-       msg_unlock(msq);
-out_free:
+out_unlock0:
+       ipc_unlock_object(&msq->q_perm);
+out_unlock1:
+       rcu_read_unlock();
        if (msg != NULL)
                free_msg(msg);
        return err;
@@ -738,6 +768,8 @@ SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
 
 static inline int convert_mode(long *msgtyp, int msgflg)
 {
+       if (msgflg & MSG_COPY)
+               return SEARCH_NUMBER;
        /*
         *  find message of correct type.
         *  msgtyp = 0 => get first.
@@ -774,14 +806,10 @@ static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)
  * This function creates new kernel message structure, large enough to store
  * bufsz message bytes.
  */
-static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz,
-                                          int msgflg, long *msgtyp,
-                                          unsigned long *copy_number)
+static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
 {
        struct msg_msg *copy;
 
-       *copy_number = *msgtyp;
-       *msgtyp = 0;
        /*
         * Create dummy message to copy real message to.
         */
@@ -797,9 +825,7 @@ static inline void free_copy(struct msg_msg *copy)
                free_msg(copy);
 }
 #else
-static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz,
-                                          int msgflg, long *msgtyp,
-                                          unsigned long *copy_number)
+static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
 {
        return ERR_PTR(-ENOSYS);
 }
@@ -809,76 +835,67 @@ static inline void free_copy(struct msg_msg *copy)
 }
 #endif
 
-long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
-              int msgflg,
+static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)
+{
+       struct msg_msg *msg, *found = NULL;
+       long count = 0;
+
+       list_for_each_entry(msg, &msq->q_messages, m_list) {
+               if (testmsg(msg, *msgtyp, mode) &&
+                   !security_msg_queue_msgrcv(msq, msg, current,
+                                              *msgtyp, mode)) {
+                       if (mode == SEARCH_LESSEQUAL && msg->m_type != 1) {
+                               *msgtyp = msg->m_type - 1;
+                               found = msg;
+                       } else if (mode == SEARCH_NUMBER) {
+                               if (*msgtyp == count)
+                                       return msg;
+                       } else
+                               return msg;
+                       count++;
+               }
+       }
+
+       return found ?: ERR_PTR(-EAGAIN);
+}
+
+long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
               long (*msg_handler)(void __user *, struct msg_msg *, size_t))
 {
-       struct msg_queue *msq;
-       struct msg_msg *msg;
        int mode;
+       struct msg_queue *msq;
        struct ipc_namespace *ns;
-       struct msg_msg *copy = NULL;
-       unsigned long copy_number = 0;
+       struct msg_msg *msg, *copy = NULL;
 
        ns = current->nsproxy->ipc_ns;
 
        if (msqid < 0 || (long) bufsz < 0)
                return -EINVAL;
+
        if (msgflg & MSG_COPY) {
-               copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax),
-                                   msgflg, &msgtyp, &copy_number);
+               copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax));
                if (IS_ERR(copy))
                        return PTR_ERR(copy);
        }
        mode = convert_mode(&msgtyp, msgflg);
 
-       msq = msg_lock_check(ns, msqid);
+       rcu_read_lock();
+       msq = msq_obtain_object_check(ns, msqid);
        if (IS_ERR(msq)) {
+               rcu_read_unlock();
                free_copy(copy);
                return PTR_ERR(msq);
        }
 
        for (;;) {
                struct msg_receiver msr_d;
-               struct list_head *tmp;
-               long msg_counter = 0;
 
                msg = ERR_PTR(-EACCES);
                if (ipcperms(ns, &msq->q_perm, S_IRUGO))
-                       goto out_unlock;
+                       goto out_unlock1;
 
-               msg = ERR_PTR(-EAGAIN);
-               tmp = msq->q_messages.next;
-               while (tmp != &msq->q_messages) {
-                       struct msg_msg *walk_msg;
-
-                       walk_msg = list_entry(tmp, struct msg_msg, m_list);
-                       if (testmsg(walk_msg, msgtyp, mode) &&
-                           !security_msg_queue_msgrcv(msq, walk_msg, current,
-                                                      msgtyp, mode)) {
-
-                               msg = walk_msg;
-                               if (mode == SEARCH_LESSEQUAL &&
-                                               walk_msg->m_type != 1) {
-                                       msgtyp = walk_msg->m_type - 1;
-                               } else if (msgflg & MSG_COPY) {
-                                       if (copy_number == msg_counter) {
-                                               /*
-                                                * Found requested message.
-                                                * Copy it.
-                                                */
-                                               msg = copy_msg(msg, copy);
-                                               if (IS_ERR(msg))
-                                                       goto out_unlock;
-                                               break;
-                                       }
-                                       msg = ERR_PTR(-EAGAIN);
-                               } else
-                                       break;
-                               msg_counter++;
-                       }
-                       tmp = tmp->next;
-               }
+               ipc_lock_object(&msq->q_perm);
+               msg = find_msg(msq, &msgtyp, mode);
                if (!IS_ERR(msg)) {
                        /*
                         * Found a suitable message.
@@ -886,14 +903,17 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
                         */
                        if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
                                msg = ERR_PTR(-E2BIG);
-                               goto out_unlock;
+                               goto out_unlock0;
                        }
                        /*
                         * If we are copying, then do not unlink message and do
                         * not update queue parameters.
                         */
-                       if (msgflg & MSG_COPY)
-                               goto out_unlock;
+                       if (msgflg & MSG_COPY) {
+                               msg = copy_msg(msg, copy);
+                               goto out_unlock0;
+                       }
+
                        list_del(&msg->m_list);
                        msq->q_qnum--;
                        msq->q_rtime = get_seconds();
@@ -902,14 +922,16 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
                        atomic_sub(msg->m_ts, &ns->msg_bytes);
                        atomic_dec(&ns->msg_hdrs);
                        ss_wakeup(&msq->q_senders, 0);
-                       msg_unlock(msq);
-                       break;
+
+                       goto out_unlock0;
                }
+
                /* No message waiting. Wait for a message */
                if (msgflg & IPC_NOWAIT) {
                        msg = ERR_PTR(-ENOMSG);
-                       goto out_unlock;
+                       goto out_unlock0;
                }
+
                list_add_tail(&msr_d.r_list, &msq->q_receivers);
                msr_d.r_tsk = current;
                msr_d.r_msgtype = msgtyp;
@@ -920,8 +942,9 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
                        msr_d.r_maxsize = bufsz;
                msr_d.r_msg = ERR_PTR(-EAGAIN);
                current->state = TASK_INTERRUPTIBLE;
-               msg_unlock(msq);
 
+               ipc_unlock_object(&msq->q_perm);
+               rcu_read_unlock();
                schedule();
 
                /* Lockless receive, part 1:
@@ -932,7 +955,7 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
                 * Prior to destruction, expunge_all(-EIRDM) changes r_msg.
                 * Thus if r_msg is -EAGAIN, then the queue not yet destroyed.
                 * rcu_read_lock() prevents preemption between reading r_msg
-                * and the spin_lock() inside ipc_lock_by_ptr().
+                * and acquiring the q_perm.lock in ipc_lock_object().
                 */
                rcu_read_lock();
 
@@ -951,32 +974,34 @@ long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
                 * If there is a message or an error then accept it without
                 * locking.
                 */
-               if (msg != ERR_PTR(-EAGAIN)) {
-                       rcu_read_unlock();
-                       break;
-               }
+               if (msg != ERR_PTR(-EAGAIN))
+                       goto out_unlock1;
 
                /* Lockless receive, part 3:
                 * Acquire the queue spinlock.
                 */
-               ipc_lock_by_ptr(&msq->q_perm);
-               rcu_read_unlock();
+               ipc_lock_object(&msq->q_perm);
 
                /* Lockless receive, part 4:
                 * Repeat test after acquiring the spinlock.
                 */
                msg = (struct msg_msg*)msr_d.r_msg;
                if (msg != ERR_PTR(-EAGAIN))
-                       goto out_unlock;
+                       goto out_unlock0;
 
                list_del(&msr_d.r_list);
                if (signal_pending(current)) {
                        msg = ERR_PTR(-ERESTARTNOHAND);
-out_unlock:
-                       msg_unlock(msq);
-                       break;
+                       goto out_unlock0;
                }
+
+               ipc_unlock_object(&msq->q_perm);
        }
+
+out_unlock0:
+       ipc_unlock_object(&msq->q_perm);
+out_unlock1:
+       rcu_read_unlock();
        if (IS_ERR(msg)) {
                free_copy(copy);
                return PTR_ERR(msg);