aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/exit.c
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
committerTejun Heo <tj@kernel.org>2011-03-23 05:37:01 -0400
commit45cb24a1da53beb70f09efccc0373f6a47a9efe0 (patch)
treef27b2a6ca76f9b4e17f2adddfcf27b3d988152ed /kernel/exit.c
parent9b84cca2564b9a5b2d064fb44d2a55a5b44473a0 (diff)
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>
Diffstat (limited to 'kernel/exit.c')
-rw-r--r--kernel/exit.c41
1 files changed, 33 insertions, 8 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index 84d13d6bb30b..1a0f10f0a4db 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1541,17 +1541,19 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,
1541 if (p->exit_state == EXIT_DEAD) 1541 if (p->exit_state == EXIT_DEAD)
1542 return 0; 1542 return 0;
1543 1543
1544 if (likely(!ptrace) && unlikely(task_ptrace(p))) { 1544 /* slay zombie? */
1545 if (p->exit_state == EXIT_ZOMBIE) {
1545 /* 1546 /*
1546 * This child is hidden by ptrace. 1547 * A zombie ptracee is only visible to its ptracer.
1547 * We aren't allowed to see it now, but eventually we will. 1548 * Notification and reaping will be cascaded to the real
1549 * parent when the ptracer detaches.
1548 */ 1550 */
1549 wo->notask_error = 0; 1551 if (likely(!ptrace) && unlikely(task_ptrace(p))) {
1550 return 0; 1552 /* it will become visible, clear notask_error */
1551 } 1553 wo->notask_error = 0;
1554 return 0;
1555 }
1552 1556
1553 /* slay zombie? */
1554 if (p->exit_state == EXIT_ZOMBIE) {
1555 /* we don't reap group leaders with subthreads */ 1557 /* we don't reap group leaders with subthreads */
1556 if (!delay_group_leader(p)) 1558 if (!delay_group_leader(p))
1557 return wait_task_zombie(wo, p); 1559 return wait_task_zombie(wo, p);
@@ -1580,15 +1582,38 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,
1580 wo->notask_error = 0; 1582 wo->notask_error = 0;
1581 } else { 1583 } else {
1582 /* 1584 /*
1585 * If @p is ptraced by a task in its real parent's group,
1586 * hide group stop/continued state when looking at @p as
1587 * the real parent; otherwise, a single stop can be
1588 * reported twice as group and ptrace stops.
1589 *
1590 * If a ptracer wants to distinguish the two events for its
1591 * own children, it should create a separate process which
1592 * takes the role of real parent.
1593 */
1594 if (likely(!ptrace) && task_ptrace(p) &&
1595 same_thread_group(p->parent, p->real_parent))
1596 return 0;
1597
1598 /*
1583 * @p is alive and it's gonna stop, continue or exit, so 1599 * @p is alive and it's gonna stop, continue or exit, so
1584 * there always is something to wait for. 1600 * there always is something to wait for.
1585 */ 1601 */
1586 wo->notask_error = 0; 1602 wo->notask_error = 0;
1587 } 1603 }
1588 1604
1605 /*
1606 * Wait for stopped. Depending on @ptrace, different stopped state
1607 * is used and the two don't interact with each other.
1608 */
1589 if (task_stopped_code(p, ptrace)) 1609 if (task_stopped_code(p, ptrace))
1590 return wait_task_stopped(wo, ptrace, p); 1610 return wait_task_stopped(wo, ptrace, p);
1591 1611
1612 /*
1613 * Wait for continued. There's only one continued state and the
1614 * ptracer can consume it which can confuse the real parent. Don't
1615 * use WCONTINUED from ptracer. You don't need or want it.
1616 */
1592 return wait_task_continued(wo, p); 1617 return wait_task_continued(wo, p);
1593} 1618}
1594 1619