Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/percpu
[safe/jmp/linux-2.6] / kernel / ptrace.c
index 9a4184e..42ad8ae 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/pid_namespace.h>
 #include <linux/syscalls.h>
 #include <linux/uaccess.h>
+#include <linux/regset.h>
 
 
 /*
@@ -181,8 +182,8 @@ int ptrace_attach(struct task_struct *task)
         * interference; SUID, SGID and LSM creds get determined differently
         * under ptrace.
         */
-       retval = mutex_lock_interruptible(&task->cred_guard_mutex);
-       if (retval < 0)
+       retval = -ERESTARTNOINTR;
+       if (mutex_lock_interruptible(&task->cred_guard_mutex))
                goto out;
 
        task_lock(task);
@@ -266,9 +267,10 @@ static int ignoring_children(struct sighand_struct *sigh)
  * or self-reaping.  Do notification now if it would have happened earlier.
  * If it should reap itself, return true.
  *
- * If it's our own child, there is no notification to do.
- * But if our normal children self-reap, then this child
- * was prevented by ptrace and we must reap it now.
+ * If it's our own child, there is no notification to do. But if our normal
+ * children self-reap, then this child was prevented by ptrace and we must
+ * reap it now, in that case we must also wake up sub-threads sleeping in
+ * do_wait().
  */
 static bool __ptrace_detach(struct task_struct *tracer, struct task_struct *p)
 {
@@ -278,8 +280,10 @@ static bool __ptrace_detach(struct task_struct *tracer, struct task_struct *p)
                if (!task_detached(p) && thread_group_empty(p)) {
                        if (!same_thread_group(p->real_parent, tracer))
                                do_notify_parent(p, p->exit_signal);
-                       else if (ignoring_children(tracer->sighand))
+                       else if (ignoring_children(tracer->sighand)) {
+                               __wake_up_parent(p, tracer);
                                p->exit_signal = -1;
+                       }
                }
                if (task_detached(p)) {
                        /* Mark it as in the process of being reaped. */
@@ -508,6 +512,47 @@ static int ptrace_resume(struct task_struct *child, long request, long data)
        return 0;
 }
 
+#ifdef CONFIG_HAVE_ARCH_TRACEHOOK
+
+static const struct user_regset *
+find_regset(const struct user_regset_view *view, unsigned int type)
+{
+       const struct user_regset *regset;
+       int n;
+
+       for (n = 0; n < view->n; ++n) {
+               regset = view->regsets + n;
+               if (regset->core_note_type == type)
+                       return regset;
+       }
+
+       return NULL;
+}
+
+static int ptrace_regset(struct task_struct *task, int req, unsigned int type,
+                        struct iovec *kiov)
+{
+       const struct user_regset_view *view = task_user_regset_view(task);
+       const struct user_regset *regset = find_regset(view, type);
+       int regset_no;
+
+       if (!regset || (kiov->iov_len % regset->size) != 0)
+               return -EINVAL;
+
+       regset_no = regset - view->regsets;
+       kiov->iov_len = min(kiov->iov_len,
+                           (__kernel_size_t) (regset->n * regset->size));
+
+       if (req == PTRACE_GETREGSET)
+               return copy_regset_to_user(task, view, regset_no, 0,
+                                          kiov->iov_len, kiov->iov_base);
+       else
+               return copy_regset_from_user(task, view, regset_no, 0,
+                                            kiov->iov_len, kiov->iov_base);
+}
+
+#endif
+
 int ptrace_request(struct task_struct *child, long request,
                   long addr, long data)
 {
@@ -570,6 +615,26 @@ int ptrace_request(struct task_struct *child, long request,
                        return 0;
                return ptrace_resume(child, request, SIGKILL);
 
+#ifdef CONFIG_HAVE_ARCH_TRACEHOOK
+       case PTRACE_GETREGSET:
+       case PTRACE_SETREGSET:
+       {
+               struct iovec kiov;
+               struct iovec __user *uiov = (struct iovec __user *) data;
+
+               if (!access_ok(VERIFY_WRITE, uiov, sizeof(*uiov)))
+                       return -EFAULT;
+
+               if (__get_user(kiov.iov_base, &uiov->iov_base) ||
+                   __get_user(kiov.iov_len, &uiov->iov_len))
+                       return -EFAULT;
+
+               ret = ptrace_regset(child, request, addr, &kiov);
+               if (!ret)
+                       ret = __put_user(kiov.iov_len, &uiov->iov_len);
+               break;
+       }
+#endif
        default:
                break;
        }
@@ -708,6 +773,32 @@ int compat_ptrace_request(struct task_struct *child, compat_long_t request,
                else
                        ret = ptrace_setsiginfo(child, &siginfo);
                break;
+#ifdef CONFIG_HAVE_ARCH_TRACEHOOK
+       case PTRACE_GETREGSET:
+       case PTRACE_SETREGSET:
+       {
+               struct iovec kiov;
+               struct compat_iovec __user *uiov =
+                       (struct compat_iovec __user *) datap;
+               compat_uptr_t ptr;
+               compat_size_t len;
+
+               if (!access_ok(VERIFY_WRITE, uiov, sizeof(*uiov)))
+                       return -EFAULT;
+
+               if (__get_user(ptr, &uiov->iov_base) ||
+                   __get_user(len, &uiov->iov_len))
+                       return -EFAULT;
+
+               kiov.iov_base = compat_ptr(ptr);
+               kiov.iov_len = len;
+
+               ret = ptrace_regset(child, request, addr, &kiov);
+               if (!ret)
+                       ret = __put_user(kiov.iov_len, &uiov->iov_len);
+               break;
+       }
+#endif
 
        default:
                ret = ptrace_request(child, request, addr, data);