]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - kernel/signal.c
Merge branch 'ptrace' of git://git.kernel.org/pub/scm/linux/kernel/git/oleg/misc
[karo-tx-linux.git] / kernel / signal.c
index 707736e64e0eaf339b6a8b096aea8c9d8469357a..ad5e818baacc43fb5e4046926bf52dde55acacfa 100644 (file)
@@ -238,8 +238,8 @@ static void task_clear_group_stop_trapping(struct task_struct *task)
 {
        if (unlikely(task->group_stop & GROUP_STOP_TRAPPING)) {
                task->group_stop &= ~GROUP_STOP_TRAPPING;
-               __wake_up_sync(&task->parent->signal->wait_chldexit,
-                              TASK_UNINTERRUPTIBLE, 1);
+               __wake_up_sync_key(&task->parent->signal->wait_chldexit,
+                                  TASK_UNINTERRUPTIBLE, 1, task);
        }
 }
 
@@ -669,7 +669,7 @@ static int rm_from_queue_full(sigset_t *mask, struct sigpending *s)
        if (sigisemptyset(&m))
                return 0;
 
-       signandsets(&s->signal, &s->signal, mask);
+       sigandnsets(&s->signal, &s->signal, mask);
        list_for_each_entry_safe(q, n, &s->list, list) {
                if (sigismember(mask, q->info.si_signo)) {
                        list_del_init(&q->list);
@@ -2068,7 +2068,7 @@ relock:
        for (;;) {
                struct k_sigaction *ka;
                /*
-                * Tracing can induce an artifical signal and choose sigaction.
+                * Tracing can induce an artificial signal and choose sigaction.
                 * The return value in @signr determines the default action,
                 * but @info->si_signo is the signal number we will report.
                 */
@@ -2299,11 +2299,33 @@ long do_no_restart_syscall(struct restart_block *param)
        return -EINTR;
 }
 
-/*
- * We don't need to get the kernel lock - this is all local to this
- * particular thread.. (and that's good, because this is _heavily_
- * used by various programs)
+static void __set_task_blocked(struct task_struct *tsk, const sigset_t *newset)
+{
+       if (signal_pending(tsk) && !thread_group_empty(tsk)) {
+               sigset_t newblocked;
+               /* A set of now blocked but previously unblocked signals. */
+               sigandnsets(&newblocked, newset, &current->blocked);
+               retarget_shared_pending(tsk, &newblocked);
+       }
+       tsk->blocked = *newset;
+       recalc_sigpending();
+}
+
+/**
+ * set_current_blocked - change current->blocked mask
+ * @newset: new mask
+ *
+ * It is wrong to change ->blocked directly, this helper should be used
+ * to ensure the process can't miss a shared signal we are going to block.
  */
+void set_current_blocked(const sigset_t *newset)
+{
+       struct task_struct *tsk = current;
+
+       spin_lock_irq(&tsk->sighand->siglock);
+       __set_task_blocked(tsk, newset);
+       spin_unlock_irq(&tsk->sighand->siglock);
+}
 
 /*
  * This is also useful for kernel threads that want to temporarily
@@ -2315,30 +2337,29 @@ long do_no_restart_syscall(struct restart_block *param)
  */
 int sigprocmask(int how, sigset_t *set, sigset_t *oldset)
 {
-       int error;
+       struct task_struct *tsk = current;
+       sigset_t newset;
 
-       spin_lock_irq(&current->sighand->siglock);
+       /* Lockless, only current can change ->blocked, never from irq */
        if (oldset)
-               *oldset = current->blocked;
+               *oldset = tsk->blocked;
 
-       error = 0;
        switch (how) {
        case SIG_BLOCK:
-               sigorsets(&current->blocked, &current->blocked, set);
+               sigorsets(&newset, &tsk->blocked, set);
                break;
        case SIG_UNBLOCK:
-               signandsets(&current->blocked, &current->blocked, set);
+               sigandnsets(&newset, &tsk->blocked, set);
                break;
        case SIG_SETMASK:
-               current->blocked = *set;
+               newset = *set;
                break;
        default:
-               error = -EINVAL;
+               return -EINVAL;
        }
-       recalc_sigpending();
-       spin_unlock_irq(&current->sighand->siglock);
 
-       return error;
+       set_current_blocked(&newset);
+       return 0;
 }
 
 /**
@@ -2348,40 +2369,34 @@ int sigprocmask(int how, sigset_t *set, sigset_t *oldset)
  *  @oset: previous value of signal mask if non-null
  *  @sigsetsize: size of sigset_t type
  */
-SYSCALL_DEFINE4(rt_sigprocmask, int, how, sigset_t __user *, set,
+SYSCALL_DEFINE4(rt_sigprocmask, int, how, sigset_t __user *, nset,
                sigset_t __user *, oset, size_t, sigsetsize)
 {
-       int error = -EINVAL;
        sigset_t old_set, new_set;
+       int error;
 
        /* XXX: Don't preclude handling different sized sigset_t's.  */
        if (sigsetsize != sizeof(sigset_t))
-               goto out;
+               return -EINVAL;
 
-       if (set) {
-               error = -EFAULT;
-               if (copy_from_user(&new_set, set, sizeof(*set)))
-                       goto out;
+       old_set = current->blocked;
+
+       if (nset) {
+               if (copy_from_user(&new_set, nset, sizeof(sigset_t)))
+                       return -EFAULT;
                sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));
 
-               error = sigprocmask(how, &new_set, &old_set);
+               error = sigprocmask(how, &new_set, NULL);
                if (error)
-                       goto out;
-               if (oset)
-                       goto set_old;
-       } else if (oset) {
-               spin_lock_irq(&current->sighand->siglock);
-               old_set = current->blocked;
-               spin_unlock_irq(&current->sighand->siglock);
+                       return error;
+       }
 
-       set_old:
-               error = -EFAULT;
-               if (copy_to_user(oset, &old_set, sizeof(*oset)))
-                       goto out;
+       if (oset) {
+               if (copy_to_user(oset, &old_set, sizeof(sigset_t)))
+                       return -EFAULT;
        }
-       error = 0;
-out:
-       return error;
+
+       return 0;
 }
 
 long do_sigpending(void __user *set, unsigned long sigsetsize)
@@ -2493,6 +2508,66 @@ int copy_siginfo_to_user(siginfo_t __user *to, siginfo_t *from)
 
 #endif
 
+/**
+ *  do_sigtimedwait - wait for queued signals specified in @which
+ *  @which: queued signals to wait for
+ *  @info: if non-null, the signal's siginfo is returned here
+ *  @ts: upper bound on process time suspension
+ */
+int do_sigtimedwait(const sigset_t *which, siginfo_t *info,
+                       const struct timespec *ts)
+{
+       struct task_struct *tsk = current;
+       long timeout = MAX_SCHEDULE_TIMEOUT;
+       sigset_t mask = *which;
+       int sig;
+
+       if (ts) {
+               if (!timespec_valid(ts))
+                       return -EINVAL;
+               timeout = timespec_to_jiffies(ts);
+               /*
+                * We can be close to the next tick, add another one
+                * to ensure we will wait at least the time asked for.
+                */
+               if (ts->tv_sec || ts->tv_nsec)
+                       timeout++;
+       }
+
+       /*
+        * Invert the set of allowed signals to get those we want to block.
+        */
+       sigdelsetmask(&mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
+       signotset(&mask);
+
+       spin_lock_irq(&tsk->sighand->siglock);
+       sig = dequeue_signal(tsk, &mask, info);
+       if (!sig && timeout) {
+               /*
+                * None ready, temporarily unblock those we're interested
+                * while we are sleeping in so that we'll be awakened when
+                * they arrive. Unblocking is always fine, we can avoid
+                * set_current_blocked().
+                */
+               tsk->real_blocked = tsk->blocked;
+               sigandsets(&tsk->blocked, &tsk->blocked, &mask);
+               recalc_sigpending();
+               spin_unlock_irq(&tsk->sighand->siglock);
+
+               timeout = schedule_timeout_interruptible(timeout);
+
+               spin_lock_irq(&tsk->sighand->siglock);
+               __set_task_blocked(tsk, &tsk->real_blocked);
+               siginitset(&tsk->real_blocked, 0);
+               sig = dequeue_signal(tsk, &mask, info);
+       }
+       spin_unlock_irq(&tsk->sighand->siglock);
+
+       if (sig)
+               return sig;
+       return timeout ? -EINTR : -EAGAIN;
+}
+
 /**
  *  sys_rt_sigtimedwait - synchronously wait for queued signals specified
  *                     in @uthese
@@ -2505,11 +2580,10 @@ SYSCALL_DEFINE4(rt_sigtimedwait, const sigset_t __user *, uthese,
                siginfo_t __user *, uinfo, const struct timespec __user *, uts,
                size_t, sigsetsize)
 {
-       int ret, sig;
        sigset_t these;
        struct timespec ts;
        siginfo_t info;
-       long timeout = 0;
+       int ret;
 
        /* XXX: Don't preclude handling different sized sigset_t's.  */
        if (sigsetsize != sizeof(sigset_t))
@@ -2518,61 +2592,16 @@ SYSCALL_DEFINE4(rt_sigtimedwait, const sigset_t __user *, uthese,
        if (copy_from_user(&these, uthese, sizeof(these)))
                return -EFAULT;
 
-       /*
-        * Invert the set of allowed signals to get those we
-        * want to block.
-        */
-       sigdelsetmask(&these, sigmask(SIGKILL)|sigmask(SIGSTOP));
-       signotset(&these);
-
        if (uts) {
                if (copy_from_user(&ts, uts, sizeof(ts)))
                        return -EFAULT;
-               if (ts.tv_nsec >= 1000000000L || ts.tv_nsec < 0
-                   || ts.tv_sec < 0)
-                       return -EINVAL;
        }
 
-       spin_lock_irq(&current->sighand->siglock);
-       sig = dequeue_signal(current, &these, &info);
-       if (!sig) {
-               timeout = MAX_SCHEDULE_TIMEOUT;
-               if (uts)
-                       timeout = (timespec_to_jiffies(&ts)
-                                  + (ts.tv_sec || ts.tv_nsec));
-
-               if (timeout) {
-                       /*
-                        * None ready -- temporarily unblock those we're
-                        * interested while we are sleeping in so that we'll
-                        * be awakened when they arrive.
-                        */
-                       current->real_blocked = current->blocked;
-                       sigandsets(&current->blocked, &current->blocked, &these);
-                       recalc_sigpending();
-                       spin_unlock_irq(&current->sighand->siglock);
-
-                       timeout = schedule_timeout_interruptible(timeout);
-
-                       spin_lock_irq(&current->sighand->siglock);
-                       sig = dequeue_signal(current, &these, &info);
-                       current->blocked = current->real_blocked;
-                       siginitset(&current->real_blocked, 0);
-                       recalc_sigpending();
-               }
-       }
-       spin_unlock_irq(&current->sighand->siglock);
+       ret = do_sigtimedwait(&these, &info, uts ? &ts : NULL);
 
-       if (sig) {
-               ret = sig;
-               if (uinfo) {
-                       if (copy_siginfo_to_user(uinfo, &info))
-                               ret = -EFAULT;
-               }
-       } else {
-               ret = -EAGAIN;
-               if (timeout)
-                       ret = -EINTR;
+       if (ret > 0 && uinfo) {
+               if (copy_siginfo_to_user(uinfo, &info))
+                       ret = -EFAULT;
        }
 
        return ret;
@@ -2860,60 +2889,51 @@ SYSCALL_DEFINE1(sigpending, old_sigset_t __user *, set)
 /**
  *  sys_sigprocmask - examine and change blocked signals
  *  @how: whether to add, remove, or set signals
- *  @set: signals to add or remove (if non-null)
+ *  @nset: signals to add or remove (if non-null)
  *  @oset: previous value of signal mask if non-null
  *
  * Some platforms have their own version with special arguments;
  * others support only sys_rt_sigprocmask.
  */
 
-SYSCALL_DEFINE3(sigprocmask, int, how, old_sigset_t __user *, set,
+SYSCALL_DEFINE3(sigprocmask, int, how, old_sigset_t __user *, nset,
                old_sigset_t __user *, oset)
 {
-       int error;
        old_sigset_t old_set, new_set;
+       sigset_t new_blocked;
 
-       if (set) {
-               error = -EFAULT;
-               if (copy_from_user(&new_set, set, sizeof(*set)))
-                       goto out;
+       old_set = current->blocked.sig[0];
+
+       if (nset) {
+               if (copy_from_user(&new_set, nset, sizeof(*nset)))
+                       return -EFAULT;
                new_set &= ~(sigmask(SIGKILL) | sigmask(SIGSTOP));
 
-               spin_lock_irq(&current->sighand->siglock);
-               old_set = current->blocked.sig[0];
+               new_blocked = current->blocked;
 
-               error = 0;
                switch (how) {
-               default:
-                       error = -EINVAL;
-                       break;
                case SIG_BLOCK:
-                       sigaddsetmask(&current->blocked, new_set);
+                       sigaddsetmask(&new_blocked, new_set);
                        break;
                case SIG_UNBLOCK:
-                       sigdelsetmask(&current->blocked, new_set);
+                       sigdelsetmask(&new_blocked, new_set);
                        break;
                case SIG_SETMASK:
-                       current->blocked.sig[0] = new_set;
+                       new_blocked.sig[0] = new_set;
                        break;
+               default:
+                       return -EINVAL;
                }
 
-               recalc_sigpending();
-               spin_unlock_irq(&current->sighand->siglock);
-               if (error)
-                       goto out;
-               if (oset)
-                       goto set_old;
-       } else if (oset) {
-               old_set = current->blocked.sig[0];
-       set_old:
-               error = -EFAULT;
+               set_current_blocked(&new_blocked);
+       }
+
+       if (oset) {
                if (copy_to_user(oset, &old_set, sizeof(*oset)))
-                       goto out;
+                       return -EFAULT;
        }
-       error = 0;
-out:
-       return error;
+
+       return 0;
 }
 #endif /* __ARCH_WANT_SYS_SIGPROCMASK */
 
@@ -2921,8 +2941,8 @@ out:
 /**
  *  sys_rt_sigaction - alter an action taken by a process
  *  @sig: signal to be sent
- *  @act: the thread group ID of the thread
- *  @oact: the PID of the thread
+ *  @act: new sigaction
+ *  @oact: used to save the previous sigaction
  *  @sigsetsize: size of sigset_t type
  */
 SYSCALL_DEFINE4(rt_sigaction, int, sig,