/* Authors: Jakub Hrozek Copyright (C) 2014 Red Hat SSSD tests: Child handlers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 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, see . */ #include #include #include #include #include "util/child_common.h" #include "tests/cmocka/common_mock.h" #define TEST_BIN "dummy-child" #define ECHO_STR "Hello child" static int destructor_called; struct child_test_ctx { int pipefd_to_child[2]; int pipefd_from_child[2]; struct sss_test_ctx *test_ctx; int save_debug_timestamps; }; static int child_test_setup(void **state) { struct child_test_ctx *child_tctx; errno_t ret; assert_true(leak_check_setup()); child_tctx = talloc(global_talloc_context, struct child_test_ctx); assert_non_null(child_tctx); child_tctx->test_ctx = create_ev_test_ctx(child_tctx); assert_non_null(child_tctx->test_ctx); ret = pipe(child_tctx->pipefd_from_child); assert_int_not_equal(ret, -1); DEBUG(SSSDBG_TRACE_LIBS, "from_child: %d:%d\n", child_tctx->pipefd_from_child[0], child_tctx->pipefd_from_child[1]); ret = pipe(child_tctx->pipefd_to_child); assert_int_not_equal(ret, -1); DEBUG(SSSDBG_TRACE_LIBS, "to_child: %d:%d\n", child_tctx->pipefd_to_child[0], child_tctx->pipefd_to_child[1]); *state = child_tctx; return 0; } static int child_test_teardown(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); talloc_free(child_tctx); assert_true(leak_check_teardown()); return 0; } /* Just make sure the exec works. The child does nothing but exits */ void test_exec_child(void **state) { errno_t ret; pid_t child_pid; int status; struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); child_pid = fork(); assert_int_not_equal(child_pid, -1); if (child_pid == 0) { exec_child(child_tctx, child_tctx->pipefd_to_child, child_tctx->pipefd_from_child, CHILD_DIR"/"TEST_BIN, 2); } else { do { errno = 0; ret = waitpid(child_pid, &status, 0); } while (ret == -1 && errno == EINTR); if (ret > 0) { ret = EIO; if (WIFEXITED(status)) { ret = WEXITSTATUS(status); assert_int_equal(ret, 0); } } else { DEBUG(SSSDBG_FUNC_DATA, "Failed to wait for children %d\n", child_pid); ret = EIO; } } } static int only_extra_args_setup(void **state) { struct child_test_ctx *child_tctx; errno_t ret; ret = child_test_setup((void **) &child_tctx); if (ret != 0) { return ret; } child_tctx->save_debug_timestamps = debug_timestamps; *state = child_tctx; return 0; } static int only_extra_args_teardown(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); errno_t ret; debug_timestamps = child_tctx->save_debug_timestamps; ret = child_test_teardown((void **) &child_tctx); if (ret != 0) { return ret; } return 0; } static void extra_args_test(struct child_test_ctx *child_tctx, bool extra_args_only) { pid_t child_pid; errno_t ret; int status; const char *extra_args[] = { "--guitar=george", "--drums=ringo", NULL }; child_pid = fork(); assert_int_not_equal(child_pid, -1); if (child_pid == 0) { debug_timestamps = 1; exec_child_ex(child_tctx, child_tctx->pipefd_to_child, child_tctx->pipefd_from_child, CHILD_DIR"/"TEST_BIN, 2, extra_args, extra_args_only, STDIN_FILENO, STDOUT_FILENO); } else { do { errno = 0; ret = waitpid(child_pid, &status, 0); } while (ret == -1 && errno == EINTR); if (ret > 0) { ret = EIO; if (WIFEXITED(status)) { ret = WEXITSTATUS(status); assert_int_equal(ret, 0); } } else { DEBUG(SSSDBG_FUNC_DATA, "Failed to wait for children %d\n", child_pid); ret = EIO; } } } /* Make sure extra arguments are passed correctly */ void test_exec_child_extra_args(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); setenv("TEST_CHILD_ACTION", "check_extra_args", 1); extra_args_test(child_tctx, false); } /* Make sure extra arguments are passed correctly */ void test_exec_child_only_extra_args(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); setenv("TEST_CHILD_ACTION", "check_only_extra_args", 1); extra_args_test(child_tctx, true); } void test_exec_child_only_extra_args_neg(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); setenv("TEST_CHILD_ACTION", "check_only_extra_args_neg", 1); extra_args_test(child_tctx, false); } struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx, struct child_test_ctx *child_tctx, struct child_io_fds *io_fds, const char *input); static void echo_child_write_done(struct tevent_req *subreq); static void echo_child_read_done(struct tevent_req *subreq); int __real_child_io_destructor(void *ptr); int __wrap_child_io_destructor(void *ptr) { destructor_called = 1; return __real_child_io_destructor(ptr); } /* Test that writing to the pipes works as expected */ void test_exec_child_io_destruct(void **state) { struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); struct child_io_fds *io_fds; io_fds = talloc(child_tctx, struct child_io_fds); io_fds->read_from_child_fd = -1; io_fds->write_to_child_fd = -1; assert_non_null(io_fds); talloc_set_destructor((void *) io_fds, child_io_destructor); io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0]; io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1]; destructor_called = 0; talloc_free(io_fds); assert_int_equal(destructor_called, 1); errno = 0; close(child_tctx->pipefd_from_child[0]); assert_int_equal(errno, EBADF); errno = 0; close(child_tctx->pipefd_from_child[1]); assert_int_equal(errno, 0); errno = 0; close(child_tctx->pipefd_to_child[0]); assert_int_equal(errno, 0); errno = 0; close(child_tctx->pipefd_to_child[1]); assert_int_equal(errno, EBADF); } void test_child_cb(int child_status, struct tevent_signal *sige, void *pvt); /* Test that writing to the pipes works as expected */ void test_exec_child_handler(void **state) { errno_t ret; pid_t child_pid; struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); struct sss_child_ctx_old *child_old_ctx; ret = unsetenv("TEST_CHILD_ACTION"); assert_int_equal(ret, 0); child_pid = fork(); assert_int_not_equal(child_pid, -1); if (child_pid == 0) { exec_child(child_tctx, child_tctx->pipefd_to_child, child_tctx->pipefd_from_child, CHILD_DIR"/"TEST_BIN, 2); } ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid, test_child_cb, child_tctx, &child_old_ctx); assert_int_equal(ret, EOK); ret = test_ev_loop(child_tctx->test_ctx); assert_int_equal(ret, EOK); assert_int_equal(child_tctx->test_ctx->error, 0); } void test_child_cb(int child_status, struct tevent_signal *sige, void *pvt) { struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx); child_ctx->test_ctx->error = EIO; if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) { child_ctx->test_ctx->error = 0; } child_ctx->test_ctx->done = true; } /* Test that writing to the pipes works as expected */ void test_exec_child_echo(void **state) { errno_t ret; pid_t child_pid; struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); struct tevent_req *req; struct child_io_fds *io_fds; setenv("TEST_CHILD_ACTION", "echo", 1); io_fds = talloc(child_tctx, struct child_io_fds); assert_non_null(io_fds); io_fds->read_from_child_fd = -1; io_fds->write_to_child_fd = -1; talloc_set_destructor((void *) io_fds, child_io_destructor); child_pid = fork(); assert_int_not_equal(child_pid, -1); if (child_pid == 0) { exec_child_ex(child_tctx, child_tctx->pipefd_to_child, child_tctx->pipefd_from_child, CHILD_DIR"/"TEST_BIN, 2, NULL, false, STDIN_FILENO, 3); } DEBUG(SSSDBG_FUNC_DATA, "Forked into %d\n", child_pid); io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0]; close(child_tctx->pipefd_from_child[1]); io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1]; close(child_tctx->pipefd_to_child[0]); sss_fd_nonblocking(io_fds->write_to_child_fd); sss_fd_nonblocking(io_fds->read_from_child_fd); ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid, NULL, NULL, NULL); assert_int_equal(ret, EOK); req = echo_child_write_send(child_tctx, child_tctx, io_fds, ECHO_STR); assert_non_null(req); ret = test_ev_loop(child_tctx->test_ctx); talloc_free(io_fds); assert_int_equal(ret, EOK); } struct test_exec_echo_state { struct child_io_fds *io_fds; struct io_buffer buf; struct child_test_ctx *child_test_ctx; }; struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx, struct child_test_ctx *child_tctx, struct child_io_fds *io_fds, const char *input) { struct tevent_req *req; struct tevent_req *subreq; struct test_exec_echo_state *echo_state; req = tevent_req_create(mem_ctx, &echo_state, struct test_exec_echo_state); assert_non_null(req); echo_state->child_test_ctx = child_tctx; echo_state->buf.data = (unsigned char *) talloc_strdup(echo_state, input); assert_non_null(echo_state->buf.data); echo_state->buf.size = strlen(input) + 1; echo_state->io_fds = io_fds; DEBUG(SSSDBG_TRACE_INTERNAL, "Writing..\n"); subreq = write_pipe_send(child_tctx, child_tctx->test_ctx->ev, echo_state->buf.data, echo_state->buf.size, echo_state->io_fds->write_to_child_fd); assert_non_null(subreq); tevent_req_set_callback(subreq, echo_child_write_done, req); return req; } static void echo_child_write_done(struct tevent_req *subreq) { struct tevent_req *req; struct test_exec_echo_state *echo_state; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); echo_state = tevent_req_data(req, struct test_exec_echo_state); ret = write_pipe_recv(subreq); DEBUG(SSSDBG_TRACE_INTERNAL, "Writing OK\n"); talloc_zfree(subreq); assert_int_equal(ret, EOK); close(echo_state->io_fds->write_to_child_fd); echo_state->io_fds->write_to_child_fd = -1; DEBUG(SSSDBG_TRACE_INTERNAL, "Reading..\n"); subreq = read_pipe_send(echo_state, echo_state->child_test_ctx->test_ctx->ev, echo_state->io_fds->read_from_child_fd); assert_non_null(subreq); tevent_req_set_callback(subreq, echo_child_read_done, req); } static void echo_child_read_done(struct tevent_req *subreq) { struct tevent_req *req; struct test_exec_echo_state *echo_state; errno_t ret; ssize_t len; uint8_t *buf; req = tevent_req_callback_data(subreq, struct tevent_req); echo_state = tevent_req_data(req, struct test_exec_echo_state); ret = read_pipe_recv(subreq, echo_state, &buf, &len); talloc_zfree(subreq); DEBUG(SSSDBG_TRACE_INTERNAL, "Reading OK\n"); assert_int_equal(ret, EOK); close(echo_state->io_fds->read_from_child_fd); echo_state->io_fds->read_from_child_fd = -1; assert_string_equal(buf, echo_state->buf.data); echo_state->child_test_ctx->test_ctx->done = true; } void sss_child_cb(int pid, int wait_status, void *pvt); /* Just make sure the exec works. The child does nothing but exits */ void test_sss_child(void **state) { errno_t ret; pid_t child_pid; struct child_test_ctx *child_tctx = talloc_get_type(*state, struct child_test_ctx); struct sss_sigchild_ctx *sc_ctx; struct sss_child_ctx *sss_child; ret = unsetenv("TEST_CHILD_ACTION"); assert_int_equal(ret, 0); ret = sss_sigchld_init(child_tctx, child_tctx->test_ctx->ev, &sc_ctx); assert_int_equal(ret, EOK); child_pid = fork(); assert_int_not_equal(child_pid, -1); if (child_pid == 0) { exec_child(child_tctx, child_tctx->pipefd_to_child, child_tctx->pipefd_from_child, CHILD_DIR"/"TEST_BIN, 2); } ret = sss_child_register(child_tctx, sc_ctx, child_pid, sss_child_cb, child_tctx, &sss_child); assert_int_equal(ret, EOK); ret = test_ev_loop(child_tctx->test_ctx); assert_int_equal(ret, EOK); assert_int_equal(child_tctx->test_ctx->error, 0); } void sss_child_cb(int pid, int wait_status, void *pvt) { struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx); child_ctx->test_ctx->error = EIO; if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status) == 0) { child_ctx->test_ctx->error = 0; } child_ctx->test_ctx->done = true; } int main(int argc, const char *argv[]) { int rv; poptContext pc; int opt; struct poptOption long_options[] = { POPT_AUTOHELP SSSD_DEBUG_OPTS POPT_TABLEEND }; const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_exec_child, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_exec_child_extra_args, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_exec_child_io_destruct, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_exec_child_handler, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_exec_child_echo, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_sss_child, child_test_setup, child_test_teardown), cmocka_unit_test_setup_teardown(test_exec_child_only_extra_args, only_extra_args_setup, only_extra_args_teardown), cmocka_unit_test_setup_teardown(test_exec_child_only_extra_args_neg, only_extra_args_setup, only_extra_args_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ debug_level = SSSDBG_INVALID; pc = poptGetContext(argv[0], argc, argv, long_options, 0); while((opt = poptGetNextOpt(pc)) != -1) { switch(opt) { default: fprintf(stderr, "\nInvalid option %s: %s\n\n", poptBadOption(pc, 0), poptStrerror(opt)); poptPrintUsage(pc, stderr, 0); return 1; } } poptFreeContext(pc); DEBUG_CLI_INIT(debug_level); rv = cmocka_run_group_tests(tests, NULL, NULL); return rv; }