diff options
Diffstat (limited to 'kernel/signal.c')
-rw-r--r-- | kernel/signal.c | 128 |
1 files changed, 71 insertions, 57 deletions
diff --git a/kernel/signal.c b/kernel/signal.c index 325663bed..3203ad39c 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -12,6 +12,8 @@ #include <linux/ptrace.h> #include <linux/unistd.h> #include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> #include <asm/uaccess.h> @@ -19,61 +21,70 @@ #define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) -#if !defined(__alpha__) && !defined(__mips__) +#ifndef __alpha__ /* * This call isn't used by all ports, in particular, the Alpha * uses osf_sigprocmask instead. Maybe it should be moved into * arch-dependent dir? + * + * 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) + * + * No SMP locking would prevent the inherent races present in this + * routine, thus we do not perform any locking at all. */ asmlinkage int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset) { - sigset_t new_set, old_set = current->blocked; - int error; + sigset_t old_set = current->blocked; if (set) { - error = get_user(new_set, set); - if (error) - return error; + sigset_t new_set; + + if(get_user(new_set, set)) + return -EFAULT; + new_set &= _BLOCKABLE; switch (how) { + default: + return -EINVAL; case SIG_BLOCK: - current->blocked |= new_set; + new_set |= old_set; break; case SIG_UNBLOCK: - current->blocked &= ~new_set; + new_set = old_set & ~new_set; break; case SIG_SETMASK: - current->blocked = new_set; break; - default: - return -EINVAL; } + current->blocked = new_set; } if (oset) { - error = put_user(old_set, oset); - if (error) - return error; + if(put_user(old_set, oset)) + return -EFAULT; } return 0; } -#endif - -#ifndef __alpha__ /* * For backwards compatibility? Functionality superseded by sigprocmask. */ asmlinkage int sys_sgetmask(void) { + /* SMP safe */ return current->blocked; } asmlinkage int sys_ssetmask(int newmask) { - int old=current->blocked; + int old; + spin_lock_irq(¤t->sigmask_lock); + old = current->blocked; current->blocked = newmask & _BLOCKABLE; + spin_unlock_irq(¤t->sigmask_lock); + return old; } @@ -81,8 +92,13 @@ asmlinkage int sys_ssetmask(int newmask) asmlinkage int sys_sigpending(sigset_t *set) { - return put_user(current->blocked & current->signal, - /* Hack */(unsigned long *)set); + int ret; + + /* fill in "set" with signals pending but blocked. */ + spin_lock_irq(¤t->sigmask_lock); + ret = put_user(current->blocked & current->signal, set); + spin_unlock_irq(¤t->sigmask_lock); + return ret; } /* @@ -99,22 +115,24 @@ asmlinkage int sys_sigpending(sigset_t *set) * Note the silly behaviour of SIGCHLD: SIG_IGN means that the signal * isn't actually ignored, but does automatic child reaping, while * SIG_DFL is explicitly said by POSIX to force the signal to be ignored.. + * + * All callers of check_pending must be holding current->sig->siglock. */ -static inline void check_pending(int signum) +inline void check_pending(int signum) { struct sigaction *p; p = signum - 1 + current->sig->action; + spin_lock(¤t->sigmask_lock); if (p->sa_handler == SIG_IGN) { - k_sigdelset(¤t->signal, signum); - return; - } - if (p->sa_handler == SIG_DFL) { - if (signum != SIGCONT && signum != SIGCHLD && signum != SIGWINCH) - return; - k_sigdelset(¤t->signal, signum); - return; + current->signal &= ~_S(signum); + } else if (p->sa_handler == SIG_DFL) { + if (signum == SIGCONT || + signum == SIGCHLD || + signum != SIGWINCH) + current->signal &= ~_S(signum); } + spin_unlock(¤t->sigmask_lock); } #if !defined(__alpha__) && !defined(__mips__) @@ -123,69 +141,65 @@ static inline void check_pending(int signum) */ asmlinkage unsigned long sys_signal(int signum, __sighandler_t handler) { - int err; struct sigaction tmp; - /* - * HACK: We still cannot handle signals > 32 due to the limited - * size of ksigset_t (which will go away). - */ - if (signum > 32) - return -EINVAL; - if (signum<1 || signum>_NSIG) + if (signum<1 || signum>32) return -EINVAL; if (signum==SIGKILL || signum==SIGSTOP) return -EINVAL; if (handler != SIG_DFL && handler != SIG_IGN) { - err = verify_area(VERIFY_READ, handler, 1); - if (err) - return err; + if(verify_area(VERIFY_READ, handler, 1)) + return -EFAULT; } + memset(&tmp, 0, sizeof(tmp)); tmp.sa_handler = handler; tmp.sa_flags = SA_ONESHOT | SA_NOMASK; + + spin_lock_irq(¤t->sig->siglock); handler = current->sig->action[signum-1].sa_handler; current->sig->action[signum-1] = tmp; check_pending(signum); + spin_unlock_irq(¤t->sig->siglock); + return (unsigned long) handler; } #endif /* !defined(__alpha__) && !defined(__mips__) */ +#ifndef __sparc__ asmlinkage int sys_sigaction(int signum, const struct sigaction * action, struct sigaction * oldaction) { struct sigaction new_sa, *p; - /* - * HACK: We still cannot handle signals > 32 due to the limited - * size of ksigset_t (which will go away). - */ - if (signum > 32) - return -EINVAL; - if (signum<1 || signum>_NSIG) + if (signum < 1 || signum > 32) return -EINVAL; + p = signum - 1 + current->sig->action; + if (action) { - int err = verify_area(VERIFY_READ, action, sizeof(*action)); - if (err) - return err; + if (copy_from_user(&new_sa, action, sizeof(struct sigaction))) + return -EFAULT; if (signum==SIGKILL || signum==SIGSTOP) return -EINVAL; - if (copy_from_user(&new_sa, action, sizeof(struct sigaction))) - return -EFAULT; - if (new_sa.sa_handler != SIG_DFL && new_sa.sa_handler != SIG_IGN) { - err = verify_area(VERIFY_READ, new_sa.sa_handler, 1); - if (err) - return err; - } } + if (oldaction) { + /* In the clone() case we could copy half consistant + * state to the user, however this could sleep and + * deadlock us if we held the signal lock on SMP. So for + * now I take the easy way out and do no locking. + */ if (copy_to_user(oldaction, p, sizeof(struct sigaction))) return -EFAULT; } + if (action) { + spin_lock_irq(¤t->sig->siglock); *p = new_sa; check_pending(signum); + spin_unlock_irq(¤t->sig->siglock); } return 0; } +#endif |