job control: Allow access to job control events through ptracees
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)
commit45cb24a1da53beb70f09efccc0373f6a47a9efe0
treef27b2a6ca76f9b4e17f2adddfcf27b3d988152ed
parent9b84cca2564b9a5b2d064fb44d2a55a5b44473a0
job control: Allow access to job control events through ptracees

Currently a real parent can't access job control stopped/continued
events through a ptraced child.  This utterly breaks job control when
the children are ptraced.

For example, if a program is run from an interactive shell and then
strace(1) attaches to it, pressing ^Z would send SIGTSTP and strace(1)
would notice it but the shell has no way to tell whether the child
entered job control stop and thus can't tell when to take over the
terminal - leading to awkward lone ^Z on the terminal.

Because the job control and ptrace stopped states are independent,
there is no reason to prevent real parents from accessing the stopped
state regardless of ptrace.  The continued state isn't separate but
ptracers don't have any use for them as ptracees can never resume
without explicit command from their ptracers, so as long as ptracers
don't consume it, it should be fine.

Although this is a behavior change, because the previous behavior is
utterly broken when viewed from real parents and the change is only
visible to real parents, I don't think it's necessary to make this
behavior optional.

One situation to be careful about is when a task from the real
parent's group is ptracing.  The parent group is the recipient of both
ptrace and job control stop events and one stop can be reported as
both job control and ptrace stops.  As this can break the current
ptrace users, suppress job control stopped events for these cases.

If a real parent ptracer wants to know about both job control and
ptrace stops, it can create a separate process to serve the role of
real parent.

Note that this only updates wait(2) side of things.  The real parent
can access the states via wait(2) but still is not properly notified
(woken up and delivered signal).  Test case polls wait(2) with WNOHANG
to work around.  Notification will be updated by future patches.

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 | WNOHANG);
  if (si.si_pid)
  printf("mommy : WAIT status=%02d code=%02d\n",
 si.si_status, si.si_code);
  nanosleep(&ts100ms, NULL);
  }
  return 0;
  }

Before the patch, while ptraced, the parent can't see any job control
events.

  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 the patch,

  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 wait(2) should be suppressed for the real
     parent's group instead of only the real parent task itself.
     Updated accordingly.

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