[S390] use set_current_state in sigsuspend
[safe/jmp/linux-2.6] / arch / s390 / kernel / signal.c
index 13592d0..6289945 100644 (file)
@@ -1,8 +1,7 @@
 /*
  *  arch/s390/kernel/signal.c
  *
- *  S390 version
- *    Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ *    Copyright (C) IBM Corp. 1999,2006
  *    Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
  *
  *    Based on Intel version
  *  1997-11-28  Modified for POSIX.1b signals by Richard Henderson
  */
 
-#include <linux/config.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
 #include <linux/smp.h>
-#include <linux/smp_lock.h>
 #include <linux/kernel.h>
 #include <linux/signal.h>
 #include <linux/errno.h>
 #include <linux/tty.h>
 #include <linux/personality.h>
 #include <linux/binfmts.h>
+#include <linux/tracehook.h>
+#include <linux/syscalls.h>
+#include <linux/compat.h>
 #include <asm/ucontext.h>
 #include <asm/uaccess.h>
 #include <asm/lowcore.h>
+#include "entry.h"
 
 #define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP)))
 
@@ -51,65 +52,27 @@ typedef struct
        struct ucontext uc;
 } rt_sigframe;
 
-int do_signal(struct pt_regs *regs, sigset_t *oldset);
-
 /*
  * Atomically swap in the new signal mask, and wait for a signal.
  */
-asmlinkage int
-sys_sigsuspend(struct pt_regs * regs, int history0, int history1,
-              old_sigset_t mask)
+SYSCALL_DEFINE3(sigsuspend, int, history0, int, history1, 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->gprs[2] = -EINTR;
-
-       while (1) {
-               set_current_state(TASK_INTERRUPTIBLE);
-               schedule();
-               if (do_signal(regs, &saveset))
-                       return -EINTR;
-       }
-}
-
-asmlinkage long
-sys_rt_sigsuspend(struct pt_regs *regs, sigset_t __user *unewset,
-                                               size_t sigsetsize)
-{
-       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->gprs[2] = -EINTR;
+       set_current_state(TASK_INTERRUPTIBLE);
+       schedule();
+       set_thread_flag(TIF_RESTORE_SIGMASK);
 
-       while (1) {
-               set_current_state(TASK_INTERRUPTIBLE);
-               schedule();
-               if (do_signal(regs, &saveset))
-                       return -EINTR;
-       }
+       return -ERESTARTNOHAND;
 }
 
-asmlinkage long
-sys_sigaction(int sig, const struct old_sigaction __user *act,
-             struct old_sigaction __user *oact)
+SYSCALL_DEFINE3(sigaction, int, sig, const struct old_sigaction __user *, act,
+               struct old_sigaction __user *, oact)
 {
        struct k_sigaction new_ka, old_ka;
        int ret;
@@ -118,10 +81,10 @@ sys_sigaction(int sig, const struct old_sigaction __user *act,
                old_sigset_t mask;
                if (!access_ok(VERIFY_READ, act, sizeof(*act)) ||
                    __get_user(new_ka.sa.sa_handler, &act->sa_handler) ||
-                   __get_user(new_ka.sa.sa_restorer, &act->sa_restorer))
+                   __get_user(new_ka.sa.sa_restorer, &act->sa_restorer) ||
+                   __get_user(new_ka.sa.sa_flags, &act->sa_flags) ||
+                   __get_user(mask, &act->sa_mask))
                        return -EFAULT;
-               __get_user(new_ka.sa.sa_flags, &act->sa_flags);
-               __get_user(mask, &act->sa_mask);
                siginitset(&new_ka.sa.sa_mask, mask);
        }
 
@@ -130,87 +93,78 @@ sys_sigaction(int sig, const struct old_sigaction __user *act,
        if (!ret && oact) {
                if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) ||
                    __put_user(old_ka.sa.sa_handler, &oact->sa_handler) ||
-                   __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer))
+                   __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer) ||
+                   __put_user(old_ka.sa.sa_flags, &oact->sa_flags) ||
+                   __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask))
                        return -EFAULT;
-               __put_user(old_ka.sa.sa_flags, &oact->sa_flags);
-               __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask);
        }
 
        return ret;
 }
 
-asmlinkage long
-sys_sigaltstack(const stack_t __user *uss, stack_t __user *uoss,
-                                       struct pt_regs *regs)
+SYSCALL_DEFINE2(sigaltstack, const stack_t __user *, uss,
+               stack_t __user *, uoss)
 {
+       struct pt_regs *regs = task_pt_regs(current);
        return do_sigaltstack(uss, uoss, regs->gprs[15]);
 }
 
-
-
 /* Returns non-zero on fault. */
 static int save_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
 {
-       unsigned long old_mask = regs->psw.mask;
-       int err;
-  
+       _sigregs user_sregs;
+
        save_access_regs(current->thread.acrs);
 
        /* Copy a 'clean' PSW mask to the user to avoid leaking
           information about whether PER is currently on.  */
-       regs->psw.mask = PSW_MASK_MERGE(PSW_USER_BITS, regs->psw.mask);
-       err = __copy_to_user(&sregs->regs.psw, &regs->psw,
-                            sizeof(sregs->regs.psw)+sizeof(sregs->regs.gprs));
-       regs->psw.mask = old_mask;
-       if (err != 0)
-               return err;
-       err = __copy_to_user(&sregs->regs.acrs, current->thread.acrs,
-                            sizeof(sregs->regs.acrs));
-       if (err != 0)
-               return err;
+       user_sregs.regs.psw.mask = PSW_MASK_MERGE(psw_user_bits, regs->psw.mask);
+       user_sregs.regs.psw.addr = regs->psw.addr;
+       memcpy(&user_sregs.regs.gprs, &regs->gprs, sizeof(sregs->regs.gprs));
+       memcpy(&user_sregs.regs.acrs, current->thread.acrs,
+              sizeof(sregs->regs.acrs));
        /* 
         * We have to store the fp registers to current->thread.fp_regs
         * to merge them with the emulated registers.
         */
        save_fp_regs(&current->thread.fp_regs);
-       return __copy_to_user(&sregs->fpregs, &current->thread.fp_regs,
-                             sizeof(s390_fp_regs));
+       memcpy(&user_sregs.fpregs, &current->thread.fp_regs,
+              sizeof(s390_fp_regs));
+       return __copy_to_user(sregs, &user_sregs, sizeof(_sigregs));
 }
 
 /* Returns positive number on error */
 static int restore_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
 {
-       unsigned long old_mask = regs->psw.mask;
        int err;
+       _sigregs user_sregs;
 
        /* Alwys make any pending restarted system call return -EINTR */
        current_thread_info()->restart_block.fn = do_no_restart_syscall;
 
-       err = __copy_from_user(&regs->psw, &sregs->regs.psw,
-                              sizeof(sregs->regs.psw)+sizeof(sregs->regs.gprs));
-       regs->psw.mask = PSW_MASK_MERGE(old_mask, regs->psw.mask);
-       regs->psw.addr |= PSW_ADDR_AMODE;
-       if (err)
-               return err;
-       err = __copy_from_user(&current->thread.acrs, &sregs->regs.acrs,
-                              sizeof(sregs->regs.acrs));
+       err = __copy_from_user(&user_sregs, sregs, sizeof(_sigregs));
        if (err)
                return err;
+       regs->psw.mask = PSW_MASK_MERGE(regs->psw.mask,
+                                       user_sregs.regs.psw.mask);
+       regs->psw.addr = PSW_ADDR_AMODE | user_sregs.regs.psw.addr;
+       memcpy(&regs->gprs, &user_sregs.regs.gprs, sizeof(sregs->regs.gprs));
+       memcpy(&current->thread.acrs, &user_sregs.regs.acrs,
+              sizeof(sregs->regs.acrs));
        restore_access_regs(current->thread.acrs);
 
-       err = __copy_from_user(&current->thread.fp_regs, &sregs->fpregs,
-                              sizeof(s390_fp_regs));
+       memcpy(&current->thread.fp_regs, &user_sregs.fpregs,
+              sizeof(s390_fp_regs));
        current->thread.fp_regs.fpc &= FPC_VALID_MASK;
-       if (err)
-               return err;
 
        restore_fp_regs(&current->thread.fp_regs);
-       regs->trap = -1;        /* disable syscall checks */
+       regs->svcnr = 0;        /* disable syscall checks */
        return 0;
 }
 
-asmlinkage long sys_sigreturn(struct pt_regs *regs)
+SYSCALL_DEFINE0(sigreturn)
 {
+       struct pt_regs *regs = task_pt_regs(current);
        sigframe __user *frame = (sigframe __user *)regs->gprs[15];
        sigset_t set;
 
@@ -235,8 +189,9 @@ badframe:
        return 0;
 }
 
-asmlinkage long sys_rt_sigreturn(struct pt_regs *regs)
+SYSCALL_DEFINE0(rt_sigreturn)
 {
+       struct pt_regs *regs = task_pt_regs(current);
        rt_sigframe __user *frame = (rt_sigframe __user *)regs->gprs[15];
        sigset_t set;
 
@@ -280,6 +235,10 @@ get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size)
        /* Default to using normal stack */
        sp = regs->gprs[15];
 
+       /* Overflow on alternate signal stack gives SIGSEGV. */
+       if (on_sig_stack(sp) && !on_sig_stack((sp - frame_size) & -8UL))
+               return (void __user *) -1UL;
+
        /* This is the X/Open sanctioned signal stack switching.  */
        if (ka->sa.sa_flags & SA_ONSTACK) {
                if (! sas_ss_flags(sp))
@@ -306,8 +265,8 @@ static inline int map_signal(int sig)
                return sig;
 }
 
-static void setup_frame(int sig, struct k_sigaction *ka,
-                       sigset_t *set, struct pt_regs * regs)
+static int setup_frame(int sig, struct k_sigaction *ka,
+                      sigset_t *set, struct pt_regs * regs)
 {
        sigframe __user *frame;
 
@@ -315,6 +274,9 @@ static void setup_frame(int sig, struct k_sigaction *ka,
        if (!access_ok(VERIFY_WRITE, frame, sizeof(sigframe)))
                goto give_sigsegv;
 
+       if (frame == (void __user *) -1UL)
+               goto give_sigsegv;
+
        if (__copy_to_user(&frame->sc.oldmask, &set->sig, _SIGMASK_COPY_SIZE))
                goto give_sigsegv;
 
@@ -355,13 +317,14 @@ static void setup_frame(int sig, struct k_sigaction *ka,
        /* Place signal number on stack to allow backtrace from handler.  */
        if (__put_user(regs->gprs[2], (int __user *) &frame->signo))
                goto give_sigsegv;
-       return;
+       return 0;
 
 give_sigsegv:
        force_sigsegv(sig, current);
+       return -EFAULT;
 }
 
-static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
+static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
                           sigset_t *set, struct pt_regs * regs)
 {
        int err = 0;
@@ -371,6 +334,9 @@ static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
        if (!access_ok(VERIFY_WRITE, frame, sizeof(rt_sigframe)))
                goto give_sigsegv;
 
+       if (frame == (void __user *) -1UL)
+               goto give_sigsegv;
+
        if (copy_siginfo_to_user(&frame->info, info))
                goto give_sigsegv;
 
@@ -394,8 +360,9 @@ static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
        } else {
                 regs->gprs[14] = (unsigned long)
                        frame->retcode | PSW_ADDR_AMODE;
-               err |= __put_user(S390_SYSCALL_OPCODE | __NR_rt_sigreturn,
-                                 (u16 __user *)(frame->retcode));
+               if (__put_user(S390_SYSCALL_OPCODE | __NR_rt_sigreturn,
+                              (u16 __user *)(frame->retcode)))
+                       goto give_sigsegv;
        }
 
        /* Set up backchain. */
@@ -409,32 +376,39 @@ static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
        regs->gprs[2] = map_signal(sig);
        regs->gprs[3] = (unsigned long) &frame->info;
        regs->gprs[4] = (unsigned long) &frame->uc;
-       return;
+       return 0;
 
 give_sigsegv:
        force_sigsegv(sig, current);
+       return -EFAULT;
 }
 
 /*
  * 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 ret;
+
        /* Set up the stack frame */
        if (ka->sa.sa_flags & SA_SIGINFO)
-               setup_rt_frame(sig, ka, info, oldset, regs);
+               ret = setup_rt_frame(sig, ka, info, oldset, regs);
        else
-               setup_frame(sig, ka, oldset, regs);
+               ret = setup_frame(sig, ka, oldset, regs);
+
+       if (ret == 0) {
+               spin_lock_irq(&current->sighand->siglock);
+               sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
+               if (!(ka->sa.sa_flags & SA_NODEFER))
+                       sigaddset(&current->blocked,sig);
+               recalc_sigpending();
+               spin_unlock_irq(&current->sighand->siglock);
+       }
 
-       spin_lock_irq(&current->sighand->siglock);
-       sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
-       if (!(ka->sa.sa_flags & SA_NODEFER))
-               sigaddset(&current->blocked,sig);
-       recalc_sigpending();
-       spin_unlock_irq(&current->sighand->siglock);
+       return ret;
 }
 
 /*
@@ -446,12 +420,13 @@ 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.
  */
-int do_signal(struct pt_regs *regs, sigset_t *oldset)
+void do_signal(struct pt_regs *regs)
 {
        unsigned long retval = 0, continue_addr = 0, restart_addr = 0;
        siginfo_t info;
        int signr;
        struct k_sigaction ka;
+       sigset_t *oldset;
 
        /*
         * We want the common case to go fast, which
@@ -460,27 +435,32 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
         * if so.
         */
        if (!user_mode(regs))
-               return 1;
+               return;
 
-       if (!oldset)
+       if (test_thread_flag(TIF_RESTORE_SIGMASK))
+               oldset = &current->saved_sigmask;
+       else
                oldset = &current->blocked;
 
        /* Are we from a system call? */
-       if (regs->trap == __LC_SVC_OLD_PSW) {
+       if (regs->svcnr) {
                continue_addr = regs->psw.addr;
                restart_addr = continue_addr - regs->ilc;
                retval = regs->gprs[2];
 
                /* Prepare for system call restart.  We do this here so that a
                   debugger will see the already changed PSW. */
-               if (retval == -ERESTARTNOHAND ||
-                   retval == -ERESTARTSYS ||
-                   retval == -ERESTARTNOINTR) {
+               switch (retval) {
+               case -ERESTARTNOHAND:
+               case -ERESTARTSYS:
+               case -ERESTARTNOINTR:
                        regs->gprs[2] = regs->orig_gpr2;
                        regs->psw.addr = restart_addr;
-               } else if (retval == -ERESTART_RESTARTBLOCK) {
+                       break;
+               case -ERESTART_RESTARTBLOCK:
                        regs->gprs[2] = -EINTR;
                }
+               regs->svcnr = 0;        /* Don't deal with this again. */
        }
 
        /* Get signal to deliver.  When running under ptrace, at this point
@@ -501,19 +481,39 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
 
        if (signr > 0) {
                /* Whee!  Actually deliver the signal.  */
-#ifdef CONFIG_S390_SUPPORT
-               if (test_thread_flag(TIF_31BIT)) {
-                       extern void handle_signal32(unsigned long sig,
-                                                   struct k_sigaction *ka,
-                                                   siginfo_t *info,
-                                                   sigset_t *oldset,
-                                                   struct pt_regs *regs);
-                       handle_signal32(signr, &ka, &info, oldset, regs);
-                       return 1;
+               int ret;
+#ifdef CONFIG_COMPAT
+               if (is_compat_task()) {
+                       ret = handle_signal32(signr, &ka, &info, oldset, regs);
                }
+               else
 #endif
-               handle_signal(signr, &ka, &info, oldset, regs);
-               return 1;
+                       ret = handle_signal(signr, &ka, &info, oldset, regs);
+               if (!ret) {
+                       /*
+                        * 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);
+
+                       /*
+                        * Let tracing know that we've done the handler setup.
+                        */
+                       tracehook_signal_handler(signr, &info, &ka, regs,
+                                       current->thread.per_info.single_step);
+               }
+               return;
+       }
+
+       /*
+        * 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);
        }
 
        /* Restart a different system call. */
@@ -522,5 +522,12 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
                regs->gprs[2] = __NR_restart_syscall;
                set_thread_flag(TIF_RESTART_SVC);
        }
-       return 0;
+}
+
+void do_notify_resume(struct pt_regs *regs)
+{
+       clear_thread_flag(TIF_NOTIFY_RESUME);
+       tracehook_notify_resume(regs);
+       if (current->replacement_session_keyring)
+               key_replace_session_keyring();
 }