| /* libnih |
| * |
| * test_child.c - test suite for nih/child.c |
| * |
| * Copyright © 2009 Scott James Remnant <scott@netsplit.com>. |
| * Copyright © 2009 Canonical Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2, as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include <nih/test.h> |
| |
| #if HAVE_VALGRIND_VALGRIND_H |
| #include <valgrind/valgrind.h> |
| #endif /* HAVE_VALGRIND_VALGRIND_H */ |
| |
| #include <sys/ptrace.h> |
| |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <unistd.h> |
| |
| #include <nih/macros.h> |
| #include <nih/alloc.h> |
| #include <nih/list.h> |
| #include <nih/string.h> |
| #include <nih/child.h> |
| |
| |
| static int handler_called = 0; |
| static void *last_data = NULL; |
| static pid_t last_pid; |
| static NihChildEvents last_event = -1; |
| static int last_status; |
| |
| static void |
| my_handler (void *data, |
| pid_t pid, |
| NihChildEvents event, |
| int status) |
| { |
| handler_called++; |
| last_data = data; |
| last_pid = pid; |
| last_event = event; |
| last_status = status; |
| } |
| |
| void |
| test_add_watch (void) |
| { |
| NihChildWatch *watch; |
| |
| TEST_FUNCTION ("nih_child_add_watch"); |
| nih_child_poll (); |
| |
| |
| /* Check that we can add a watch on a specific pid, and that the |
| * structure is filled in correctly and part of a list. |
| */ |
| TEST_FEATURE ("with pid"); |
| TEST_ALLOC_FAIL { |
| watch = nih_child_add_watch (NULL, getpid (), NIH_CHILD_EXITED, |
| my_handler, &watch); |
| |
| if (test_alloc_failed) { |
| TEST_EQ_P (watch, NULL); |
| continue; |
| } |
| |
| TEST_ALLOC_SIZE (watch, sizeof (NihChildWatch)); |
| TEST_EQ (watch->pid, getpid ()); |
| TEST_EQ (watch->events, NIH_CHILD_EXITED); |
| TEST_EQ_P (watch->handler, my_handler); |
| TEST_EQ_P (watch->data, &watch); |
| TEST_LIST_NOT_EMPTY (&watch->entry); |
| |
| nih_free (watch); |
| } |
| |
| |
| /* Check that we can add a watch on a pid of -1, which represents |
| * any child. |
| */ |
| TEST_FEATURE ("with -1 for pid"); |
| TEST_ALLOC_FAIL { |
| watch = nih_child_add_watch (NULL, -1, NIH_CHILD_ALL, |
| my_handler, &watch); |
| |
| if (test_alloc_failed) { |
| TEST_EQ_P (watch, NULL); |
| continue; |
| } |
| |
| TEST_ALLOC_SIZE (watch, sizeof (NihChildWatch)); |
| TEST_EQ (watch->pid, -1); |
| TEST_EQ (watch->events, NIH_CHILD_ALL); |
| TEST_EQ_P (watch->handler, my_handler); |
| TEST_EQ_P (watch->data, &watch); |
| TEST_LIST_NOT_EMPTY (&watch->entry); |
| |
| nih_free (watch); |
| } |
| } |
| |
| |
| void |
| test_poll (void) |
| { |
| NihChildWatch *watch; |
| siginfo_t siginfo; |
| pid_t pid, child; |
| unsigned long data; |
| |
| TEST_FUNCTION ("nih_child_poll"); |
| |
| /* Check that when a child exits normally, the handler receives |
| * an exited event and the zero status code and is then removed from |
| * the list and freed. |
| */ |
| TEST_FEATURE ("with normal termination"); |
| |
| TEST_CHILD (pid) { |
| exit (0); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_EXITED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_EXITED); |
| TEST_EQ (last_status, 0); |
| TEST_FREE (watch); |
| |
| |
| /* Check that when a child exits with a non-zero status code, the |
| * reaper receives an exited event and the status code and is then |
| * removed from the list and freed. |
| */ |
| TEST_FEATURE ("with normal non-zero termination"); |
| |
| TEST_CHILD (pid) { |
| exit (123); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_EXITED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_EXITED); |
| TEST_EQ (last_status, 123); |
| TEST_FREE (watch); |
| |
| |
| /* Check that when a child is killed by a signal, the reaper receives |
| * a killed event with the signal in the status field before being |
| * removed from the list and freed. |
| */ |
| TEST_FEATURE ("with termination by signal"); |
| |
| TEST_CHILD (pid) { |
| pause (); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, |
| NIH_CHILD_KILLED | NIH_CHILD_DUMPED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_KILLED); |
| TEST_EQ (last_status, SIGTERM); |
| TEST_FREE (watch); |
| |
| |
| /* Check that when a child is killed by aborting, the reaper receives |
| * a dumped event with the signal in the status field before being |
| * removed from the list and freed. |
| */ |
| TEST_FEATURE ("with termination by abort"); |
| |
| TEST_CHILD (pid) { |
| abort (); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, |
| NIH_CHILD_KILLED | NIH_CHILD_DUMPED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| /* We might get killed if we never dumped core... fiddling with |
| * the limit doesn't help, since we might be under gdb and that |
| * never lets us dump core. |
| */ |
| if (last_event != NIH_CHILD_KILLED) { |
| TEST_EQ (last_event, NIH_CHILD_DUMPED); |
| unlink ("core"); |
| } |
| TEST_EQ (last_status, SIGABRT); |
| TEST_FREE (watch); |
| |
| |
| /* Check that when a child emits the stopped signal, the reaper |
| * receives a stopped event with nothing relevant in the status field. |
| * It should not be removed from the list, since the child hasn't |
| * gone anyway. |
| */ |
| TEST_FEATURE ("with stopped child"); |
| |
| TEST_CHILD (pid) { |
| raise (SIGSTOP); |
| pause (); |
| exit (0); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, |
| NIH_CHILD_STOPPED | NIH_CHILD_CONTINUED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_STOPPED); |
| TEST_EQ (last_status, SIGSTOP); |
| TEST_NOT_FREE (watch); |
| |
| |
| /* Check that when the child is continued again, the reaper |
| * receives a continued event with nothing relevant in the status |
| * field. It should still not be removed from the list since the |
| * child still hasn't gone away. |
| */ |
| TEST_FEATURE ("with continued child"); |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| kill (pid, SIGCONT); |
| |
| waitid (P_PID, pid, &siginfo, WCONTINUED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_CONTINUED); |
| TEST_EQ (last_status, SIGCONT); |
| TEST_NOT_FREE (watch); |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED); |
| nih_free (watch); |
| |
| #if 0 |
| /* The following tests fail on xen VM instances, hanging the |
| * machine. */ |
| /* TODO(kmixter): understand why these fail of xen VMs */ |
| |
| /* Check that a signal raised from a traced child causes the reaper |
| * to be called with a traced event and the event in the status |
| * field. It should not be removed from the list since the child |
| * hasn't gone away. |
| */ |
| TEST_FEATURE ("with signal from traced child"); |
| |
| TEST_CHILD (pid) { |
| assert0 (ptrace (PTRACE_TRACEME, 0, NULL, NULL)); |
| |
| raise (SIGSTOP); |
| raise (SIGCHLD); |
| pause (); |
| exit (0); |
| } |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED); |
| |
| assert0 (ptrace (PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD)); |
| assert0 (ptrace (PTRACE_CONT, pid, NULL, SIGCONT)); |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED | WNOWAIT); |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_TRAPPED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_TRAPPED); |
| TEST_EQ (last_status, SIGCHLD); |
| TEST_NOT_FREE (watch); |
| |
| assert0 (ptrace (PTRACE_DETACH, pid, NULL, 0)); |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED); |
| nih_free (watch); |
| |
| |
| #if HAVE_VALGRIND_VALGRIND_H |
| /* These tests fail when running under valgrind. |
| */ |
| if (! RUNNING_ON_VALGRIND) { |
| #endif |
| /* Check that when a traced child forks it causes the reaper |
| * to be called with a ptrace event and the fork event in the |
| * status field. It should not be removed from the list since the |
| * child hasn't gone away. |
| */ |
| TEST_FEATURE ("with fork by traced child"); |
| |
| TEST_CHILD (pid) { |
| assert0 (ptrace (PTRACE_TRACEME, 0, NULL, NULL)); |
| |
| raise (SIGSTOP); |
| |
| child = fork (); |
| assert (child >= 0); |
| |
| pause (); |
| |
| exit (0); |
| } |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED); |
| |
| assert0 (ptrace (PTRACE_SETOPTIONS, pid, NULL, |
| PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK)); |
| assert0 (ptrace (PTRACE_CONT, pid, NULL, SIGCONT)); |
| |
| /* Wait for ptrace to stop the parent (signalling the fork) */ |
| waitid (P_PID, pid, &siginfo, WSTOPPED | WNOWAIT); |
| |
| /* Will be able to get the child pid now, we have to do it here |
| * because we want to wait on it to ensure the test is synchronous; |
| * otherwise nih_child_poll() could actually eat the child event |
| * before it returns -- normally we'd do this inside the handler, |
| * so things would probably work (we iter the handlers for each |
| * event, so you can add one). |
| */ |
| data = 0; |
| assert0 (ptrace (PTRACE_GETEVENTMSG, pid, NULL, &data)); |
| assert (data != 0); |
| child = (pid_t)data; |
| |
| /* Wait for ptrace to stop the child, otherwise it might not be |
| * ready for us to actually detach from. |
| */ |
| waitid (P_PID, child, &siginfo, WSTOPPED | WNOWAIT); |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_PTRACE, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_PTRACE); |
| TEST_EQ (last_status, PTRACE_EVENT_FORK); |
| TEST_NOT_FREE (watch); |
| |
| assert0 (ptrace (PTRACE_DETACH, child, NULL, SIGCONT)); |
| kill (child, SIGTERM); |
| |
| assert0 (ptrace (PTRACE_DETACH, pid, NULL, SIGCONT)); |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED); |
| nih_free (watch); |
| |
| |
| /* Check that when a traced child execs it causes the reaper |
| * to be called with a ptrace event and the exec event in the |
| * status field. It should not be removed from the list since the |
| * child hasn't gone away. |
| */ |
| TEST_FEATURE ("with exec by traced child"); |
| |
| TEST_CHILD (pid) { |
| assert0 (ptrace (PTRACE_TRACEME, 0, NULL, NULL)); |
| |
| raise (SIGSTOP); |
| |
| execl ("/bin/true", "true", NULL); |
| exit (255); |
| } |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED); |
| |
| assert0 (ptrace (PTRACE_SETOPTIONS, pid, NULL, |
| PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC)); |
| assert0 (ptrace (PTRACE_CONT, pid, NULL, SIGCONT)); |
| |
| waitid (P_PID, pid, &siginfo, WSTOPPED | WNOWAIT); |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_PTRACE, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_PTRACE); |
| TEST_EQ (last_status, PTRACE_EVENT_EXEC); |
| TEST_NOT_FREE (watch); |
| |
| assert0 (ptrace (PTRACE_DETACH, pid, NULL, SIGCONT)); |
| |
| waitid (P_PID, pid, &siginfo, WEXITED); |
| nih_free (watch); |
| #if HAVE_VALGRIND_VALGRIND_H |
| } |
| #endif |
| |
| |
| /* Check that we can watch for events from any process, which |
| * shouldn't be freed when the child dies. |
| */ |
| TEST_FEATURE ("with generic watcher"); |
| |
| TEST_CHILD (pid) { |
| pause (); |
| } |
| |
| watch = nih_child_add_watch (NULL, -1, NIH_CHILD_ALL, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_TRUE (handler_called); |
| TEST_EQ (last_pid, pid); |
| TEST_EQ (last_event, NIH_CHILD_KILLED); |
| TEST_EQ (last_status, SIGTERM); |
| TEST_NOT_FREE (watch); |
| |
| nih_free (watch); |
| |
| |
| /* Check that if we poll with an unknown pid, and no catch-all, |
| * nothing is triggered and the watch is not removed. |
| */ |
| TEST_FEATURE ("with pid-specific watcher and wrong pid"); |
| |
| TEST_CHILD (pid) { |
| pause (); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid - 1, NIH_CHILD_ALL, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_FALSE (handler_called); |
| TEST_NOT_FREE (watch); |
| |
| nih_free (watch); |
| |
| |
| /* Check that if we poll with a known pid but for a different event |
| * set, nothing is triggered and the watch is not removed. |
| */ |
| TEST_FEATURE ("with event-specific watcher and wrong event"); |
| |
| TEST_CHILD (pid) { |
| pause (); |
| } |
| |
| watch = nih_child_add_watch (NULL, pid, NIH_CHILD_STOPPED, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| last_data = NULL; |
| last_pid = 0; |
| last_event = -1; |
| last_status = 0; |
| |
| kill (pid, SIGTERM); |
| waitid (P_PID, pid, &siginfo, WEXITED | WNOWAIT); |
| |
| nih_child_poll (); |
| |
| TEST_FALSE (handler_called); |
| TEST_NOT_FREE (watch); |
| |
| nih_free (watch); |
| |
| |
| /* Check that a poll when nothing has died does nothing. */ |
| TEST_FEATURE ("with nothing dead"); |
| |
| TEST_CHILD (pid) { |
| pause (); |
| } |
| |
| watch = nih_child_add_watch (NULL, -1, NIH_CHILD_ALL, |
| my_handler, &watch); |
| |
| TEST_FREE_TAG (watch); |
| |
| handler_called = 0; |
| nih_child_poll (); |
| |
| TEST_FALSE (handler_called); |
| TEST_NOT_FREE (watch); |
| |
| kill (pid, SIGTERM); |
| waitpid (pid, NULL, 0); |
| #endif |
| |
| |
| /* Check that a poll when there are no child processes does nothing */ |
| TEST_FEATURE ("with no children"); |
| handler_called = 0; |
| nih_child_poll (); |
| |
| TEST_FALSE (handler_called); |
| TEST_NOT_FREE (watch); |
| |
| nih_free (watch); |
| } |
| |
| |
| int |
| main (int argc, |
| char *argv[]) |
| { |
| test_add_watch (); |
| test_poll (); |
| |
| return 0; |
| } |