summaryrefslogtreecommitdiffstats
path: root/arch/arm/kernel/signal.c
diff options
context:
space:
mode:
authorMikael Pettersson <mikpe@it.uu.se>2009-08-15 12:58:11 +0100
committerRussell King <rmk+kernel@arm.linux.org.uk>2009-08-15 15:10:31 +0100
commit369842658a36bcea28ecb643ba4bdb53919330dd (patch)
tree1a590eabceea85b974360eca2cfba54d86ae4933 /arch/arm/kernel/signal.c
parent4bf1fa5a34aa2dd0d2cc58f0fc213a2e22d007a4 (diff)
ARM: 5677/1: ARM support for TIF_RESTORE_SIGMASK/pselect6/ppoll/epoll_pwait
This patch adds support for TIF_RESTORE_SIGMASK to ARM's signal handling, which allows to hook up the pselect6, ppoll, and epoll_pwait syscalls on ARM. Tested here with eabi userspace and a test program with a deliberate race between a child's exit and the parent's sigprocmask/select sequence. Using sys_pselect6() instead of sigprocmask/select reliably prevents the race. The other arch's support for TIF_RESTORE_SIGMASK has evolved over time: In 2.6.16: - add TIF_RESTORE_SIGMASK which parallels TIF_SIGPENDING - test both when checking for pending signal [changed later] - reimplement sys_sigsuspend() to use current->saved_sigmask, TIF_RESTORE_SIGMASK [changed later], and -ERESTARTNOHAND; ditto for sys_rt_sigsuspend(), but drop private code and use common code via __ARCH_WANT_SYS_RT_SIGSUSPEND; - there are now no "extra" calls to do_signal() so its oldset parameter is always &current->blocked so need not be passed, also its return value is changed to void - change handle_signal() to return 0/-errno - change do_signal() to honor TIF_RESTORE_SIGMASK: + get oldset from current->saved_sigmask if TIF_RESTORE_SIGMASK is set + if handle_signal() was successful then clear TIF_RESTORE_SIGMASK + if no signal was delivered and TIF_RESTORE_SIGMASK is set then clear it and restore the sigmask - hook up sys_pselect6() and sys_ppoll() In 2.6.19: - hook up sys_epoll_pwait() In 2.6.26: - allow archs to override how TIF_RESTORE_SIGMASK is implemented; default set_restore_sigmask() sets both TIF_RESTORE_SIGMASK and TIF_SIGPENDING; archs need now just test TIF_SIGPENDING again when checking for pending signal work; some archs now implement TIF_RESTORE_SIGMASK as a secondary/non-atomic thread flag bit - call set_restore_sigmask() in sys_sigsuspend() instead of setting TIF_RESTORE_SIGMASK In 2.6.29-rc: - kill sys_pselect7() which no arch wanted So for 2.6.31-rc6/ARM this patch does the following: - Add TIF_RESTORE_SIGMASK. Use the generic set_restore_sigmask() which sets both TIF_SIGPENDING and TIF_RESTORE_SIGMASK, so TIF_RESTORE_SIGMASK need not claim one of the scarce low thread flags, and existing TIF_SIGPENDING and _TIF_WORK_MASK tests need not be extended for TIF_RESTORE_SIGMASK. - sys_sigsuspend() is reimplemented to use current->saved_sigmask and set_restore_sigmask(), making it identical to most other archs - The private code for sys_rt_sigsuspend() is removed, instead generic code supplies it via __ARCH_WANT_SYS_RT_SIGSUSPEND. - sys_sigsuspend() and sys_rt_sigsuspend() no longer need a pt_regs parameter, so their assembly code wrappers are removed. - handle_signal() is changed to return 0 on success or -errno. - The oldset parameter to do_signal() is now redundant and removed, and the return value is now also redundant and changed to void. - do_signal() is changed to honor TIF_RESTORE_SIGMASK: + get oldset from current->saved_sigmask if TIF_RESTORE_SIGMASK is set + if handle_signal() was successful then clear TIF_RESTORE_SIGMASK + if no signal was delivered and TIF_RESTORE_SIGMASK is set then clear it and restore the sigmask - Hook up sys_pselect6, sys_ppoll, and sys_epoll_pwait. Signed-off-by: Mikael Pettersson <mikpe@it.uu.se> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/kernel/signal.c')
-rw-r--r--arch/arm/kernel/signal.c86
1 files changed, 37 insertions, 49 deletions
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c
index 93bb4247b7e..e27ee1f701d 100644
--- a/arch/arm/kernel/signal.c
+++ b/arch/arm/kernel/signal.c
@@ -47,57 +47,22 @@ const unsigned long sigreturn_codes[7] = {
MOV_R7_NR_RT_SIGRETURN, SWI_SYS_RT_SIGRETURN, SWI_THUMB_RT_SIGRETURN,
};
-static int do_signal(sigset_t *oldset, struct pt_regs * regs, int syscall);
-
/*
* atomically swap in the new signal mask, and wait for a signal.
*/
-asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, old_sigset_t mask, struct pt_regs *regs)
+asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, old_sigset_t mask)
{
- sigset_t saveset;
-
mask &= _BLOCKABLE;
spin_lock_irq(&current->sighand->siglock);
- saveset = current->blocked;
+ current->saved_sigmask = current->blocked;
siginitset(&current->blocked, mask);
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
- regs->ARM_r0 = -EINTR;
-
- while (1) {
- current->state = TASK_INTERRUPTIBLE;
- schedule();
- if (do_signal(&saveset, regs, 0))
- return regs->ARM_r0;
- }
-}
-
-asmlinkage int
-sys_rt_sigsuspend(sigset_t __user *unewset, size_t sigsetsize, struct pt_regs *regs)
-{
- sigset_t saveset, newset;
-
- /* XXX: Don't preclude handling different sized sigset_t's. */
- if (sigsetsize != sizeof(sigset_t))
- return -EINVAL;
-
- if (copy_from_user(&newset, unewset, sizeof(newset)))
- return -EFAULT;
- sigdelsetmask(&newset, ~_BLOCKABLE);
-
- spin_lock_irq(&current->sighand->siglock);
- saveset = current->blocked;
- current->blocked = newset;
- recalc_sigpending();
- spin_unlock_irq(&current->sighand->siglock);
- regs->ARM_r0 = -EINTR;
- while (1) {
- current->state = TASK_INTERRUPTIBLE;
- schedule();
- if (do_signal(&saveset, regs, 0))
- return regs->ARM_r0;
- }
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+ set_restore_sigmask();
+ return -ERESTARTNOHAND;
}
asmlinkage int
@@ -545,7 +510,7 @@ static inline void setup_syscall_restart(struct pt_regs *regs)
/*
* OK, we're invoking a handler
*/
-static void
+static int
handle_signal(unsigned long sig, struct k_sigaction *ka,
siginfo_t *info, sigset_t *oldset,
struct pt_regs * regs, int syscall)
@@ -596,7 +561,7 @@ handle_signal(unsigned long sig, struct k_sigaction *ka,
if (ret != 0) {
force_sigsegv(sig, tsk);
- return;
+ return ret;
}
/*
@@ -610,6 +575,7 @@ handle_signal(unsigned long sig, struct k_sigaction *ka,
recalc_sigpending();
spin_unlock_irq(&tsk->sighand->siglock);
+ return 0;
}
/*
@@ -621,7 +587,7 @@ handle_signal(unsigned long sig, struct k_sigaction *ka,
* the kernel can handle, and then we build all the user-level signal handling
* stack-frames in one go after that.
*/
-static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)
+static void do_signal(struct pt_regs *regs, int syscall)
{
struct k_sigaction ka;
siginfo_t info;
@@ -634,7 +600,7 @@ static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)
* if so.
*/
if (!user_mode(regs))
- return 0;
+ return;
if (try_to_freeze())
goto no_signal;
@@ -643,9 +609,24 @@ static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)
signr = get_signal_to_deliver(&info, &ka, regs, NULL);
if (signr > 0) {
- handle_signal(signr, &ka, &info, oldset, regs, syscall);
+ sigset_t *oldset;
+
+ if (test_thread_flag(TIF_RESTORE_SIGMASK))
+ oldset = &current->saved_sigmask;
+ else
+ oldset = &current->blocked;
+ if (handle_signal(signr, &ka, &info, oldset, regs, syscall) == 0) {
+ /*
+ * A signal was successfully delivered; the saved
+ * sigmask will have been stored in the signal frame,
+ * and will be restored by sigreturn, so we can simply
+ * clear the TIF_RESTORE_SIGMASK flag.
+ */
+ if (test_thread_flag(TIF_RESTORE_SIGMASK))
+ clear_thread_flag(TIF_RESTORE_SIGMASK);
+ }
single_step_set(current);
- return 1;
+ return;
}
no_signal:
@@ -697,14 +678,21 @@ static int do_signal(sigset_t *oldset, struct pt_regs *regs, int syscall)
regs->ARM_r0 == -ERESTARTNOINTR) {
setup_syscall_restart(regs);
}
+
+ /* If there's no signal to deliver, we just put the saved sigmask
+ * back.
+ */
+ if (test_thread_flag(TIF_RESTORE_SIGMASK)) {
+ clear_thread_flag(TIF_RESTORE_SIGMASK);
+ sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
+ }
}
single_step_set(current);
- return 0;
}
asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
if (thread_flags & _TIF_SIGPENDING)
- do_signal(&current->blocked, regs, syscall);
+ do_signal(regs, syscall);
}