job control: Notify the real parent of job control events regardless of ptrace
authorTejun Heo <tj@kernel.org>
Wed, 23 Mar 2011 09:37:01 +0000 (10:37 +0100)
committerTejun Heo <tj@kernel.org>
Wed, 23 Mar 2011 09:37:01 +0000 (10:37 +0100)
commitceb6bd67f9b9db765e1c29405f26e8460391badd
treed4db6cfe58bf2d84bf6af8f1079619050f6c8adc
parent62bcf9d992ecc19ea4f37ff57ee0b3333e3e843e
job control: Notify the real parent of job control events regardless of ptrace

With recent changes, job control and ptrace stopped states are
properly separated and accessible to the real parent and the ptracer
respectively; however, notifications of job control stopped/continued
events to the real parent while ptraced are still missing.

A ptracee participates in group stop in ptrace_stop() but the
completion isn't notified.  If participation results in completion of
group stop, notify the real parent of the event.  The ptrace and group
stops are separate and can be handled as such.

However, when the real parent and the ptracer are in the same thread
group, only the ptrace stop event is visible through wait(2) and the
duplicate notifications are different from the current behavior and
are confusing.  Suppress group stop notification in such cases.

The continued state is shared between the real parent and the ptracer
but is only meaningful to the real parent.  Always notify the real
parent and notify the ptracer too for backward compatibility.  Similar
to stop notification, if the real parent is the ptracer, suppress a
duplicate notification.

Test case follows.

  #include <stdio.h>
  #include <unistd.h>
  #include <time.h>
  #include <errno.h>
  #include <sys/types.h>
  #include <sys/ptrace.h>
  #include <sys/wait.h>

  int main(void)
  {
  const struct timespec ts100ms = { .tv_nsec = 100000000 };
  pid_t tracee, tracer;
  siginfo_t si;
  int i;

  tracee = fork();
  if (tracee == 0) {
  while (1) {
  printf("tracee: SIGSTOP\n");
  raise(SIGSTOP);
  nanosleep(&ts100ms, NULL);
  printf("tracee: SIGCONT\n");
  raise(SIGCONT);
  nanosleep(&ts100ms, NULL);
  }
  }

  waitid(P_PID, tracee, &si, WSTOPPED | WNOHANG | WNOWAIT);

  tracer = fork();
  if (tracer == 0) {
  nanosleep(&ts100ms, NULL);
  ptrace(PTRACE_ATTACH, tracee, NULL, NULL);

  for (i = 0; i < 11; i++) {
  si.si_pid = 0;
  waitid(P_PID, tracee, &si, WSTOPPED);
  if (si.si_pid && si.si_code == CLD_TRAPPED)
  ptrace(PTRACE_CONT, tracee, NULL,
 (void *)(long)si.si_status);
  }
  printf("tracer: EXITING\n");
  return 0;
  }

  while (1) {
  si.si_pid = 0;
  waitid(P_PID, tracee, &si, WSTOPPED | WCONTINUED | WEXITED);
  if (si.si_pid)
  printf("mommy : WAIT status=%02d code=%02d\n",
 si.si_status, si.si_code);
  }
  return 0;
  }

Before this patch, while ptraced, the real parent doesn't get
notifications for job control events, so although it can access those
events, the later waitid(2) call never wakes up.

  tracee: SIGSTOP
  mommy : WAIT status=19 code=05
  tracee: SIGCONT
  tracee: SIGSTOP
  tracee: SIGCONT
  tracee: SIGSTOP
  tracee: SIGCONT
  tracee: SIGSTOP
  tracer: EXITING
  mommy : WAIT status=19 code=05
  ^C

After this patch, it works as expected.

  tracee: SIGSTOP
  mommy : WAIT status=19 code=05
  tracee: SIGCONT
  mommy : WAIT status=18 code=06
  tracee: SIGSTOP
  mommy : WAIT status=19 code=05
  tracee: SIGCONT
  mommy : WAIT status=18 code=06
  tracee: SIGSTOP
  mommy : WAIT status=19 code=05
  tracee: SIGCONT
  mommy : WAIT status=18 code=06
  tracee: SIGSTOP
  tracer: EXITING
  mommy : WAIT status=19 code=05
  ^C

-v2: Oleg pointed out that

     * Group stop notification to the real parent should also happen
       when ptracer detach races with ptrace_stop().

     * real_parent_is_ptracer() should be testing thread group
       equality not the task itself as wait(2) and stop/cont
       notifications are normally thread-group wide.

     Both issues are fixed accordingly.

-v3: real_parent_is_ptracer() updated to test child->real_parent
     instead of child->group_leader->real_parent per Oleg's
     suggestion.

Signed-off-by: Tejun Heo <tj@kernel.org>
Acked-by: Oleg Nesterov <oleg@redhat.com>
kernel/signal.c