Pull bugzilla-5452 into release branch
[pandora-kernel.git] / arch / s390 / kernel / signal.c
index 6e0110d..d48cfc7 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
@@ -51,60 +50,24 @@ 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)
+sys_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;
+       current->state = TASK_INTERRUPTIBLE;
+       schedule();
+       set_thread_flag(TIF_RESTORE_SIGMASK);
 
-       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;
-
-       while (1) {
-               set_current_state(TASK_INTERRUPTIBLE);
-               schedule();
-               if (do_signal(regs, &saveset))
-                       return -EINTR;
-       }
+       return -ERESTARTNOHAND;
 }
 
 asmlinkage long
@@ -254,9 +217,9 @@ asmlinkage long sys_rt_sigreturn(struct pt_regs *regs)
        if (restore_sigregs(regs, &frame->uc.uc_mcontext))
                goto badframe;
 
-       /* It is more difficult to avoid calling this function than to
-          call it and ignore errors.  */
-       do_sigaltstack(&frame->uc.uc_stack, NULL, regs->gprs[15]);
+       if (do_sigaltstack(&frame->uc.uc_stack, NULL,
+                          regs->gprs[15]) == -EFAULT)
+               goto badframe;
        return regs->gprs[2];
 
 badframe:
@@ -306,8 +269,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;
 
@@ -355,13 +318,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;
@@ -394,8 +358,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 +374,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 +418,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,9 +433,11 @@ 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? */
@@ -473,12 +448,14 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
 
                /* 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;
                }
        }
@@ -501,19 +478,40 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
 
        if (signr > 0) {
                /* Whee!  Actually deliver the signal.  */
-#ifdef CONFIG_S390_SUPPORT
+#ifdef CONFIG_COMPAT
                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;
+                       extern int handle_signal32(unsigned long sig,
+                                                  struct k_sigaction *ka,
+                                                  siginfo_t *info,
+                                                  sigset_t *oldset,
+                                                  struct pt_regs *regs);
+                       if (handle_signal32(
+                                   signr, &ka, &info, oldset, regs) == 0) {
+                               if (test_thread_flag(TIF_RESTORE_SIGMASK))
+                                       clear_thread_flag(TIF_RESTORE_SIGMASK);
+                       }
+                       return;
                }
 #endif
-               handle_signal(signr, &ka, &info, oldset, regs);
-               return 1;
+               if (handle_signal(signr, &ka, &info, oldset, regs) == 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);
+               }
+               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 +520,4 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset)
                regs->gprs[2] = __NR_restart_syscall;
                set_thread_flag(TIF_RESTART_SVC);
        }
-       return 0;
 }