job control: Don't send duplicate job control stop notification while ptraced
Tejun Heo [Wed, 23 Mar 2011 09:37:01 +0000 (10:37 +0100)]
Just as group_exit_code shouldn't be generated when a PTRACE_CONT'd
task re-enters job control stop, notifiction for the event should be
suppressed too.  The logic is the same as the group_exit_code
generation suppression in do_signal_stop(), if SIGNAL_STOP_STOPPED is
already set, the task is re-entering job control stop without
intervening SIGCONT and the notifications should be suppressed.

Test case follows.

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

  static const struct timespec ts100ms = { .tv_nsec = 100000000 };
  static pid_t tracee, tracer;

  static const char *pid_who(pid_t pid)
  {
  return pid == tracee ? "tracee" : (pid == tracer ? "tracer" : "mommy ");
  }

  static void sigchld_sigaction(int signo, siginfo_t *si, void *ucxt)
  {
  printf("%s: SIG status=%02d code=%02d (%s)\n",
 pid_who(getpid()), si->si_status, si->si_code,
 pid_who(si->si_pid));
  }

  int main(void)
  {
  const struct sigaction chld_sa = { .sa_sigaction = sigchld_sigaction,
     .sa_flags = SA_SIGINFO|SA_RESTART };
  siginfo_t si;

  sigaction(SIGCHLD, &chld_sa, NULL);

  tracee = fork();
  if (!tracee) {
  tracee = getpid();
  while (1)
  pause();
  }

  kill(tracee, SIGSTOP);
  waitid(P_PID, tracee, &si, WSTOPPED);

  tracer = fork();
  if (!tracer) {
  tracer = getpid();
  ptrace(PTRACE_ATTACH, tracee, NULL, NULL);
  waitid(P_PID, tracee, &si, WSTOPPED);
  ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status);
  waitid(P_PID, tracee, &si, WSTOPPED);
  ptrace(PTRACE_CONT, tracee, NULL, (void *)(long)si.si_status);
  waitid(P_PID, tracee, &si, WSTOPPED);
  printf("tracer: detaching\n");
  ptrace(PTRACE_DETACH, tracee, NULL, NULL);
  return 0;
  }

  while (1)
  pause();
  return 0;
  }

Before the patch, the parent gets the second notification for the
tracee after the tracer detaches.  si_status is zero because
group_exit_code is not set by the group stop completion which
triggered this notification.

  mommy : SIG status=19 code=05 (tracee)
  tracer: SIG status=00 code=05 (tracee)
  tracer: SIG status=19 code=04 (tracee)
  tracer: SIG status=00 code=05 (tracee)
  tracer: detaching
  mommy : SIG status=00 code=05 (tracee)
  mommy : SIG status=00 code=01 (tracer)
  ^C

After the patch, the duplicate notification is gone.

  mommy : SIG status=19 code=05 (tracee)
  tracer: SIG status=00 code=05 (tracee)
  tracer: SIG status=19 code=04 (tracee)
  tracer: SIG status=00 code=05 (tracee)
  tracer: detaching
  mommy : SIG status=00 code=01 (tracer)
  ^C

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

kernel/signal.c

index f65403d..f799a05 100644 (file)
@@ -268,6 +268,10 @@ void task_clear_group_stop_pending(struct task_struct *task)
  *
  * CONTEXT:
  * Must be called with @task->sighand->siglock held.
+ *
+ * RETURNS:
+ * %true if group stop completion should be notified to the parent, %false
+ * otherwise.
  */
 static bool task_participate_group_stop(struct task_struct *task)
 {
@@ -284,7 +288,11 @@ static bool task_participate_group_stop(struct task_struct *task)
        if (!WARN_ON_ONCE(sig->group_stop_count == 0))
                sig->group_stop_count--;
 
-       if (!sig->group_stop_count) {
+       /*
+        * Tell the caller to notify completion iff we are entering into a
+        * fresh group stop.  Read comment in do_signal_stop() for details.
+        */
+       if (!sig->group_stop_count && !(sig->flags & SIGNAL_STOP_STOPPED)) {
                sig->flags = SIGNAL_STOP_STOPPED;
                return true;
        }