-\f
-/* A helper function for linux_test_for_tracefork, called after fork (). */
-
-static void
-linux_tracefork_child (void)
-{
- ptrace (PTRACE_TRACEME, 0, 0, 0);
- kill (getpid (), SIGSTOP);
- fork ();
- _exit (0);
-}
-
-/* Wrapper function for waitpid which handles EINTR. */
-
-static int
-my_waitpid (int pid, int *statusp, int flags)
-{
- int ret;
-
- do
- {
- ret = waitpid (pid, statusp, flags);
- }
- while (ret == -1 && errno == EINTR);
-
- return ret;
-}
-
-/* Determine if PTRACE_O_TRACEFORK can be used to follow fork events.
-
- First, we try to enable fork tracing on ORIGINAL_PID. If this fails,
- we know that the feature is not available. This may change the tracing
- options for ORIGINAL_PID, but we'll be setting them shortly anyway.
-
- However, if it succeeds, we don't know for sure that the feature is
- available; old versions of PTRACE_SETOPTIONS ignored unknown options. We
- create a child process, attach to it, use PTRACE_SETOPTIONS to enable
- fork tracing, and let it fork. If the process exits, we assume that we
- can't use TRACEFORK; if we get the fork notification, and we can extract
- the new child's PID, then we assume that we can. */
-
-static void
-linux_test_for_tracefork (int original_pid)
-{
- int child_pid, ret, status;
- long second_pid;
- sigset_t prev_mask;
-
- /* We don't want those ptrace calls to be interrupted. */
- block_child_signals (&prev_mask);
-
- linux_supports_tracefork_flag = 0;
- linux_supports_tracevforkdone_flag = 0;
-
- ret = ptrace (PTRACE_SETOPTIONS, original_pid, 0, PTRACE_O_TRACEFORK);
- if (ret != 0)
- {
- restore_child_signals_mask (&prev_mask);
- return;
- }
-
- child_pid = fork ();
- if (child_pid == -1)
- perror_with_name (("fork"));
-
- if (child_pid == 0)
- linux_tracefork_child ();
-
- ret = my_waitpid (child_pid, &status, 0);
- if (ret == -1)
- perror_with_name (("waitpid"));
- else if (ret != child_pid)
- error (_("linux_test_for_tracefork: waitpid: unexpected result %d."), ret);
- if (! WIFSTOPPED (status))
- error (_("linux_test_for_tracefork: waitpid: unexpected status %d."),
- status);
-
- ret = ptrace (PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACEFORK);
- if (ret != 0)
- {
- ret = ptrace (PTRACE_KILL, child_pid, 0, 0);
- if (ret != 0)
- {
- warning (_("linux_test_for_tracefork: failed to kill child"));
- restore_child_signals_mask (&prev_mask);
- return;
- }
-
- ret = my_waitpid (child_pid, &status, 0);
- if (ret != child_pid)
- warning (_("linux_test_for_tracefork: failed "
- "to wait for killed child"));
- else if (!WIFSIGNALED (status))
- warning (_("linux_test_for_tracefork: unexpected "
- "wait status 0x%x from killed child"), status);
-
- restore_child_signals_mask (&prev_mask);
- return;
- }
-
- /* Check whether PTRACE_O_TRACEVFORKDONE is available. */
- ret = ptrace (PTRACE_SETOPTIONS, child_pid, 0,
- PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORKDONE);
- linux_supports_tracevforkdone_flag = (ret == 0);
-
- ret = ptrace (PTRACE_CONT, child_pid, 0, 0);
- if (ret != 0)
- warning (_("linux_test_for_tracefork: failed to resume child"));
-
- ret = my_waitpid (child_pid, &status, 0);
-
- if (ret == child_pid && WIFSTOPPED (status)
- && status >> 16 == PTRACE_EVENT_FORK)
- {
- second_pid = 0;
- ret = ptrace (PTRACE_GETEVENTMSG, child_pid, 0, &second_pid);
- if (ret == 0 && second_pid != 0)
- {
- int second_status;
-
- linux_supports_tracefork_flag = 1;
- my_waitpid (second_pid, &second_status, 0);
- ret = ptrace (PTRACE_KILL, second_pid, 0, 0);
- if (ret != 0)
- warning (_("linux_test_for_tracefork: "
- "failed to kill second child"));
- my_waitpid (second_pid, &status, 0);
- }
- }
- else
- warning (_("linux_test_for_tracefork: unexpected result from waitpid "
- "(%d, status 0x%x)"), ret, status);
-
- ret = ptrace (PTRACE_KILL, child_pid, 0, 0);
- if (ret != 0)
- warning (_("linux_test_for_tracefork: failed to kill child"));
- my_waitpid (child_pid, &status, 0);
-
- restore_child_signals_mask (&prev_mask);
-}
-
-/* Determine if PTRACE_O_TRACESYSGOOD can be used to follow syscalls.
-
- We try to enable syscall tracing on ORIGINAL_PID. If this fails,
- we know that the feature is not available. This may change the tracing
- options for ORIGINAL_PID, but we'll be setting them shortly anyway. */
-
-static void
-linux_test_for_tracesysgood (int original_pid)
-{
- int ret;
- sigset_t prev_mask;
-
- /* We don't want those ptrace calls to be interrupted. */
- block_child_signals (&prev_mask);
-
- linux_supports_tracesysgood_flag = 0;
-
- ret = ptrace (PTRACE_SETOPTIONS, original_pid, 0, PTRACE_O_TRACESYSGOOD);
- if (ret != 0)
- goto out;
-
- linux_supports_tracesysgood_flag = 1;
-out:
- restore_child_signals_mask (&prev_mask);
-}
-
-/* Determine wether we support PTRACE_O_TRACESYSGOOD option available.
- This function also sets linux_supports_tracesysgood_flag. */
-
-static int
-linux_supports_tracesysgood (int pid)
-{
- if (linux_supports_tracesysgood_flag == -1)
- linux_test_for_tracesysgood (pid);
- return linux_supports_tracesysgood_flag;
-}
-
-/* Return non-zero iff we have tracefork functionality available.
- This function also sets linux_supports_tracefork_flag. */
-
-static int
-linux_supports_tracefork (int pid)
-{
- if (linux_supports_tracefork_flag == -1)
- linux_test_for_tracefork (pid);
- return linux_supports_tracefork_flag;
-}
-
-static int
-linux_supports_tracevforkdone (int pid)
-{
- if (linux_supports_tracefork_flag == -1)
- linux_test_for_tracefork (pid);
- return linux_supports_tracevforkdone_flag;
-}