diff options
Diffstat (limited to 'kernel/signal.c')
-rw-r--r-- | kernel/signal.c | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/kernel/signal.c b/kernel/signal.c new file mode 100644 index 000000000..f21d7a2c9 --- /dev/null +++ b/kernel/signal.c @@ -0,0 +1,174 @@ +/* + * linux/kernel/signal.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/ptrace.h> +#include <linux/unistd.h> +#include <linux/mm.h> + +#include <asm/segment.h> + +#define _S(nr) (1<<((nr)-1)) + +#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) + +asmlinkage int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset) +{ + sigset_t new_set, old_set = current->blocked; + int error; + + if (set) { + error = verify_area(VERIFY_READ, set, sizeof(sigset_t)); + if (error) + return error; + new_set = get_fs_long((unsigned long *) set) & _BLOCKABLE; + switch (how) { + case SIG_BLOCK: + current->blocked |= new_set; + break; + case SIG_UNBLOCK: + current->blocked &= ~new_set; + break; + case SIG_SETMASK: + current->blocked = new_set; + break; + default: + return -EINVAL; + } + } + if (oset) { + error = verify_area(VERIFY_WRITE, oset, sizeof(sigset_t)); + if (error) + return error; + put_fs_long(old_set, (unsigned long *) oset); + } + return 0; +} + +asmlinkage int sys_sgetmask(void) +{ + return current->blocked; +} + +asmlinkage int sys_ssetmask(int newmask) +{ + int old=current->blocked; + + current->blocked = newmask & _BLOCKABLE; + return old; +} + +asmlinkage int sys_sigpending(sigset_t *set) +{ + int error; + /* fill in "set" with signals pending but blocked. */ + error = verify_area(VERIFY_WRITE, set, 4); + if (!error) + put_fs_long(current->blocked & current->signal, (unsigned long *)set); + return error; +} + +/* + * POSIX 3.3.1.3: + * "Setting a signal action to SIG_IGN for a signal that is pending + * shall cause the pending signal to be discarded, whether or not + * it is blocked" (but SIGCHLD is unspecified: linux leaves it alone). + * + * "Setting a signal action to SIG_DFL for a signal that is pending + * and whose default action is to ignore the signal (for example, + * SIGCHLD), shall cause the pending signal to be discarded, whether + * or not it is blocked" + * + * 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.. + */ +static void check_pending(int signum) +{ + struct sigaction *p; + + p = signum - 1 + current->sigaction; + if (p->sa_handler == SIG_IGN) { + if (signum == SIGCHLD) + return; + current->signal &= ~_S(signum); + return; + } + if (p->sa_handler == SIG_DFL) { + if (signum != SIGCONT && signum != SIGCHLD && signum != SIGWINCH) + return; + current->signal &= ~_S(signum); + return; + } +} + +asmlinkage unsigned long sys_signal(int signum, void (*handler)(int)) +{ + int err; + struct sigaction tmp; + + 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; + } + tmp.sa_handler = handler; + tmp.sa_mask = 0; + tmp.sa_flags = SA_ONESHOT | SA_NOMASK; + tmp.sa_restorer = NULL; + handler = current->sigaction[signum-1].sa_handler; + current->sigaction[signum-1] = tmp; + check_pending(signum); + return (unsigned long) handler; +} + +asmlinkage int sys_sigaction(int signum, const struct sigaction * action, + struct sigaction * oldaction) +{ + struct sigaction new_sa, *p; + + if (signum<1 || signum>32) + return -EINVAL; + if (signum==SIGKILL || signum==SIGSTOP) + return -EINVAL; + p = signum - 1 + current->sigaction; + if (action) { + int err = verify_area(VERIFY_READ, action, sizeof(*action)); + if (err) + return err; + memcpy_fromfs(&new_sa, action, sizeof(struct sigaction)); + if (new_sa.sa_flags & SA_NOMASK) + new_sa.sa_mask = 0; + else { + new_sa.sa_mask |= _S(signum); + new_sa.sa_mask &= _BLOCKABLE; + } + 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) { + int err = verify_area(VERIFY_WRITE, oldaction, sizeof(*oldaction)); + if (err) + return err; + memcpy_tofs(oldaction, p, sizeof(struct sigaction)); + } + if (action) { + *p = new_sa; + check_pending(signum); + } + return 0; +} |