Line data Source code
1 : /*
2 : Authors:
3 : Jakub Hrozek <jhrozek@redhat.com>
4 :
5 : Copyright (C) 2014 Red Hat
6 :
7 : SSSD tests: Child handlers
8 :
9 : This program is free software; you can redistribute it and/or modify
10 : it under the terms of the GNU General Public License as published by
11 : the Free Software Foundation; either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program. If not, see <http://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include <talloc.h>
24 : #include <tevent.h>
25 : #include <errno.h>
26 : #include <popt.h>
27 :
28 : #include "util/child_common.h"
29 : #include "tests/cmocka/common_mock.h"
30 :
31 : #define TEST_BIN "dummy-child"
32 : #define ECHO_STR "Hello child"
33 :
34 : static int destructor_called;
35 :
36 : struct child_test_ctx {
37 : int pipefd_to_child[2];
38 : int pipefd_from_child[2];
39 :
40 : struct sss_test_ctx *test_ctx;
41 : };
42 :
43 6 : static int child_test_setup(void **state)
44 : {
45 : struct child_test_ctx *child_tctx;
46 : errno_t ret;
47 :
48 6 : check_leaks_push(global_talloc_context);
49 6 : child_tctx = talloc(global_talloc_context, struct child_test_ctx);
50 6 : assert_non_null(child_tctx);
51 :
52 6 : child_tctx->test_ctx = create_ev_test_ctx(child_tctx);
53 6 : assert_non_null(child_tctx->test_ctx);
54 :
55 6 : ret = pipe(child_tctx->pipefd_from_child);
56 6 : assert_int_not_equal(ret, -1);
57 6 : DEBUG(SSSDBG_TRACE_LIBS, "from_child: %d:%d\n",
58 : child_tctx->pipefd_from_child[0],
59 : child_tctx->pipefd_from_child[1]);
60 :
61 6 : ret = pipe(child_tctx->pipefd_to_child);
62 6 : assert_int_not_equal(ret, -1);
63 6 : DEBUG(SSSDBG_TRACE_LIBS, "to_child: %d:%d\n",
64 : child_tctx->pipefd_to_child[0],
65 : child_tctx->pipefd_to_child[1]);
66 :
67 6 : *state = child_tctx;
68 6 : return 0;
69 : }
70 :
71 6 : static int child_test_teardown(void **state)
72 : {
73 6 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
74 : struct child_test_ctx);
75 :
76 6 : talloc_free(child_tctx);
77 6 : check_leaks_pop(global_talloc_context);
78 6 : return 0;
79 : }
80 :
81 : /* Just make sure the exec works. The child does nothing but exits */
82 1 : void test_exec_child(void **state)
83 : {
84 : errno_t ret;
85 : pid_t child_pid;
86 : int status;
87 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
88 : struct child_test_ctx);
89 :
90 1 : child_pid = fork();
91 2 : assert_int_not_equal(child_pid, -1);
92 2 : if (child_pid == 0) {
93 1 : ret = exec_child(child_tctx,
94 1 : child_tctx->pipefd_to_child,
95 1 : child_tctx->pipefd_from_child,
96 : CHILD_DIR"/"TEST_BIN, 2);
97 0 : assert_int_equal(ret, EOK);
98 : } else {
99 : do {
100 1 : errno = 0;
101 1 : ret = waitpid(child_pid, &status, 0);
102 1 : } while (ret == -1 && errno == EINTR);
103 :
104 1 : if (ret > 0) {
105 1 : ret = EIO;
106 1 : if (WIFEXITED(status)) {
107 1 : ret = WEXITSTATUS(status);
108 1 : assert_int_equal(ret, 0);
109 : }
110 : } else {
111 0 : DEBUG(SSSDBG_FUNC_DATA,
112 : "Failed to wait for children %d\n", child_pid);
113 0 : ret = EIO;
114 : }
115 : }
116 1 : }
117 :
118 : /* Make sure extra arguments are passed correctly */
119 1 : void test_exec_child_extra_args(void **state)
120 : {
121 : errno_t ret;
122 : pid_t child_pid;
123 : int status;
124 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
125 : struct child_test_ctx);
126 1 : const char *extra_args[] = { "--guitar=george",
127 : "--drums=ringo",
128 : NULL };
129 :
130 1 : setenv("TEST_CHILD_ACTION", "check_extra_args", 1);
131 :
132 1 : child_pid = fork();
133 2 : assert_int_not_equal(child_pid, -1);
134 2 : if (child_pid == 0) {
135 1 : ret = exec_child_ex(child_tctx,
136 1 : child_tctx->pipefd_to_child,
137 1 : child_tctx->pipefd_from_child,
138 : CHILD_DIR"/"TEST_BIN, 2, extra_args,
139 : STDIN_FILENO, STDOUT_FILENO);
140 0 : assert_int_equal(ret, EOK);
141 : } else {
142 : do {
143 1 : errno = 0;
144 1 : ret = waitpid(child_pid, &status, 0);
145 1 : } while (ret == -1 && errno == EINTR);
146 :
147 1 : if (ret > 0) {
148 1 : ret = EIO;
149 1 : if (WIFEXITED(status)) {
150 1 : ret = WEXITSTATUS(status);
151 1 : assert_int_equal(ret, 0);
152 : }
153 : } else {
154 0 : DEBUG(SSSDBG_FUNC_DATA,
155 : "Failed to wait for children %d\n", child_pid);
156 0 : ret = EIO;
157 : }
158 : }
159 1 : }
160 :
161 : struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx,
162 : struct child_test_ctx *child_tctx,
163 : struct child_io_fds *io_fds,
164 : const char *input);
165 : static void echo_child_write_done(struct tevent_req *subreq);
166 : static void echo_child_read_done(struct tevent_req *subreq);
167 :
168 : int __real_child_io_destructor(void *ptr);
169 :
170 2 : int __wrap_child_io_destructor(void *ptr)
171 : {
172 2 : destructor_called = 1;
173 2 : return __real_child_io_destructor(ptr);
174 : }
175 :
176 : /* Test that writing to the pipes works as expected */
177 1 : void test_exec_child_io_destruct(void **state)
178 : {
179 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
180 : struct child_test_ctx);
181 : struct child_io_fds *io_fds;
182 :
183 1 : io_fds = talloc(child_tctx, struct child_io_fds);
184 1 : io_fds->read_from_child_fd = -1;
185 1 : io_fds->write_to_child_fd = -1;
186 1 : assert_non_null(io_fds);
187 1 : talloc_set_destructor((void *) io_fds, child_io_destructor);
188 :
189 1 : io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0];
190 1 : io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1];
191 :
192 1 : destructor_called = 0;
193 1 : talloc_free(io_fds);
194 1 : assert_int_equal(destructor_called, 1);
195 :
196 1 : errno = 0;
197 1 : close(child_tctx->pipefd_from_child[0]);
198 1 : assert_int_equal(errno, EBADF);
199 :
200 1 : errno = 0;
201 1 : close(child_tctx->pipefd_from_child[1]);
202 1 : assert_int_equal(errno, 0);
203 :
204 1 : errno = 0;
205 1 : close(child_tctx->pipefd_to_child[0]);
206 1 : assert_int_equal(errno, 0);
207 :
208 1 : errno = 0;
209 1 : close(child_tctx->pipefd_to_child[1]);
210 1 : assert_int_equal(errno, EBADF);
211 1 : }
212 :
213 : void test_child_cb(int child_status,
214 : struct tevent_signal *sige,
215 : void *pvt);
216 :
217 : /* Test that writing to the pipes works as expected */
218 1 : void test_exec_child_handler(void **state)
219 : {
220 : errno_t ret;
221 : pid_t child_pid;
222 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
223 : struct child_test_ctx);
224 : struct sss_child_ctx_old *child_old_ctx;
225 :
226 1 : ret = unsetenv("TEST_CHILD_ACTION");
227 1 : assert_int_equal(ret, 0);
228 :
229 1 : child_pid = fork();
230 2 : assert_int_not_equal(child_pid, -1);
231 2 : if (child_pid == 0) {
232 1 : ret = exec_child(child_tctx,
233 1 : child_tctx->pipefd_to_child,
234 1 : child_tctx->pipefd_from_child,
235 : CHILD_DIR"/"TEST_BIN, 2);
236 0 : assert_int_equal(ret, EOK);
237 : }
238 :
239 1 : ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid,
240 : test_child_cb, child_tctx, &child_old_ctx);
241 1 : assert_int_equal(ret, EOK);
242 :
243 1 : ret = test_ev_loop(child_tctx->test_ctx);
244 1 : assert_int_equal(ret, EOK);
245 1 : assert_int_equal(child_tctx->test_ctx->error, 0);
246 1 : }
247 :
248 1 : void test_child_cb(int child_status,
249 : struct tevent_signal *sige,
250 : void *pvt)
251 : {
252 1 : struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx);
253 :
254 1 : child_ctx->test_ctx->error = EIO;
255 1 : if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) {
256 1 : child_ctx->test_ctx->error = 0;
257 : }
258 :
259 1 : child_ctx->test_ctx->done = true;
260 1 : }
261 :
262 : /* Test that writing to the pipes works as expected */
263 1 : void test_exec_child_echo(void **state)
264 : {
265 : errno_t ret;
266 : pid_t child_pid;
267 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
268 : struct child_test_ctx);
269 : struct tevent_req *req;
270 : struct child_io_fds *io_fds;
271 :
272 1 : setenv("TEST_CHILD_ACTION", "echo", 1);
273 :
274 1 : io_fds = talloc(child_tctx, struct child_io_fds);
275 1 : assert_non_null(io_fds);
276 1 : io_fds->read_from_child_fd = -1;
277 1 : io_fds->write_to_child_fd = -1;
278 1 : talloc_set_destructor((void *) io_fds, child_io_destructor);
279 :
280 1 : child_pid = fork();
281 2 : assert_int_not_equal(child_pid, -1);
282 2 : if (child_pid == 0) {
283 1 : ret = exec_child_ex(child_tctx,
284 1 : child_tctx->pipefd_to_child,
285 1 : child_tctx->pipefd_from_child,
286 : CHILD_DIR"/"TEST_BIN, 2, NULL,
287 : STDIN_FILENO, 3);
288 0 : assert_int_equal(ret, EOK);
289 : }
290 :
291 1 : DEBUG(SSSDBG_FUNC_DATA, "Forked into %d\n", child_pid);
292 :
293 1 : io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0];
294 1 : close(child_tctx->pipefd_from_child[1]);
295 1 : io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1];
296 1 : close(child_tctx->pipefd_to_child[0]);
297 :
298 1 : sss_fd_nonblocking(io_fds->write_to_child_fd);
299 1 : sss_fd_nonblocking(io_fds->read_from_child_fd);
300 :
301 1 : ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid,
302 : NULL, NULL, NULL);
303 1 : assert_int_equal(ret, EOK);
304 :
305 1 : req = echo_child_write_send(child_tctx, child_tctx, io_fds, ECHO_STR);
306 1 : assert_non_null(req);
307 :
308 1 : ret = test_ev_loop(child_tctx->test_ctx);
309 1 : talloc_free(io_fds);
310 1 : assert_int_equal(ret, EOK);
311 1 : }
312 :
313 : struct test_exec_echo_state {
314 : struct child_io_fds *io_fds;
315 : struct io_buffer buf;
316 : struct child_test_ctx *child_test_ctx;
317 : };
318 :
319 1 : struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx,
320 : struct child_test_ctx *child_tctx,
321 : struct child_io_fds *io_fds,
322 : const char *input)
323 : {
324 : struct tevent_req *req;
325 : struct tevent_req *subreq;
326 : struct test_exec_echo_state *echo_state;
327 :
328 1 : req = tevent_req_create(mem_ctx, &echo_state, struct test_exec_echo_state);
329 1 : assert_non_null(req);
330 :
331 1 : echo_state->child_test_ctx = child_tctx;
332 :
333 1 : echo_state->buf.data = (unsigned char *) talloc_strdup(echo_state, input);
334 1 : assert_non_null(echo_state->buf.data);
335 1 : echo_state->buf.size = strlen(input) + 1;
336 1 : echo_state->io_fds = io_fds;
337 :
338 1 : DEBUG(SSSDBG_TRACE_INTERNAL, "Writing..\n");
339 3 : subreq = write_pipe_send(child_tctx, child_tctx->test_ctx->ev,
340 2 : echo_state->buf.data, echo_state->buf.size,
341 1 : echo_state->io_fds->write_to_child_fd);
342 1 : assert_non_null(subreq);
343 1 : tevent_req_set_callback(subreq, echo_child_write_done, req);
344 :
345 1 : return req;
346 : }
347 :
348 1 : static void echo_child_write_done(struct tevent_req *subreq)
349 : {
350 : struct tevent_req *req;
351 : struct test_exec_echo_state *echo_state;
352 : errno_t ret;
353 :
354 1 : req = tevent_req_callback_data(subreq, struct tevent_req);
355 1 : echo_state = tevent_req_data(req, struct test_exec_echo_state);
356 :
357 1 : ret = write_pipe_recv(subreq);
358 1 : DEBUG(SSSDBG_TRACE_INTERNAL, "Writing OK\n");
359 1 : talloc_zfree(subreq);
360 1 : assert_int_equal(ret, EOK);
361 :
362 1 : close(echo_state->io_fds->write_to_child_fd);
363 1 : echo_state->io_fds->write_to_child_fd = -1;
364 :
365 1 : DEBUG(SSSDBG_TRACE_INTERNAL, "Reading..\n");
366 2 : subreq = read_pipe_send(echo_state,
367 1 : echo_state->child_test_ctx->test_ctx->ev,
368 1 : echo_state->io_fds->read_from_child_fd);
369 1 : assert_non_null(subreq);
370 1 : tevent_req_set_callback(subreq, echo_child_read_done, req);
371 1 : }
372 :
373 1 : static void echo_child_read_done(struct tevent_req *subreq)
374 : {
375 : struct tevent_req *req;
376 : struct test_exec_echo_state *echo_state;
377 : errno_t ret;
378 : ssize_t len;
379 : uint8_t *buf;
380 :
381 1 : req = tevent_req_callback_data(subreq, struct tevent_req);
382 1 : echo_state = tevent_req_data(req, struct test_exec_echo_state);
383 :
384 1 : ret = read_pipe_recv(subreq, echo_state, &buf, &len);
385 1 : talloc_zfree(subreq);
386 1 : DEBUG(SSSDBG_TRACE_INTERNAL, "Reading OK\n");
387 1 : assert_int_equal(ret, EOK);
388 :
389 1 : close(echo_state->io_fds->read_from_child_fd);
390 1 : echo_state->io_fds->read_from_child_fd = -1;
391 :
392 1 : assert_string_equal(buf, echo_state->buf.data);
393 1 : echo_state->child_test_ctx->test_ctx->done = true;
394 1 : }
395 :
396 : void sss_child_cb(int pid, int wait_status, void *pvt);
397 :
398 : /* Just make sure the exec works. The child does nothing but exits */
399 1 : void test_sss_child(void **state)
400 : {
401 : errno_t ret;
402 : pid_t child_pid;
403 1 : struct child_test_ctx *child_tctx = talloc_get_type(*state,
404 : struct child_test_ctx);
405 : struct sss_sigchild_ctx *sc_ctx;
406 : struct sss_child_ctx *sss_child;
407 :
408 1 : ret = unsetenv("TEST_CHILD_ACTION");
409 1 : assert_int_equal(ret, 0);
410 :
411 1 : ret = sss_sigchld_init(child_tctx, child_tctx->test_ctx->ev, &sc_ctx);
412 1 : assert_int_equal(ret, EOK);
413 :
414 1 : child_pid = fork();
415 2 : assert_int_not_equal(child_pid, -1);
416 2 : if (child_pid == 0) {
417 1 : ret = exec_child(child_tctx,
418 1 : child_tctx->pipefd_to_child,
419 1 : child_tctx->pipefd_from_child,
420 : CHILD_DIR"/"TEST_BIN, 2);
421 0 : assert_int_equal(ret, EOK);
422 : }
423 :
424 1 : ret = sss_child_register(child_tctx, sc_ctx,
425 : child_pid,
426 : sss_child_cb,
427 : child_tctx, &sss_child);
428 1 : assert_int_equal(ret, EOK);
429 :
430 1 : ret = test_ev_loop(child_tctx->test_ctx);
431 1 : assert_int_equal(ret, EOK);
432 1 : assert_int_equal(child_tctx->test_ctx->error, 0);
433 1 : }
434 :
435 1 : void sss_child_cb(int pid, int wait_status, void *pvt)
436 : {
437 1 : struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx);
438 :
439 1 : child_ctx->test_ctx->error = EIO;
440 1 : if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status) == 0) {
441 1 : child_ctx->test_ctx->error = 0;
442 : }
443 :
444 1 : child_ctx->test_ctx->done = true;
445 1 : }
446 :
447 1 : int main(int argc, const char *argv[])
448 : {
449 : int rv;
450 : poptContext pc;
451 : int opt;
452 6 : struct poptOption long_options[] = {
453 : POPT_AUTOHELP
454 5 : SSSD_DEBUG_OPTS
455 : POPT_TABLEEND
456 : };
457 :
458 1 : const struct CMUnitTest tests[] = {
459 : cmocka_unit_test_setup_teardown(test_exec_child,
460 : child_test_setup,
461 : child_test_teardown),
462 : cmocka_unit_test_setup_teardown(test_exec_child_extra_args,
463 : child_test_setup,
464 : child_test_teardown),
465 : cmocka_unit_test_setup_teardown(test_exec_child_io_destruct,
466 : child_test_setup,
467 : child_test_teardown),
468 : cmocka_unit_test_setup_teardown(test_exec_child_handler,
469 : child_test_setup,
470 : child_test_teardown),
471 : cmocka_unit_test_setup_teardown(test_exec_child_echo,
472 : child_test_setup,
473 : child_test_teardown),
474 : cmocka_unit_test_setup_teardown(test_sss_child,
475 : child_test_setup,
476 : child_test_teardown),
477 : };
478 :
479 : /* Set debug level to invalid value so we can deside if -d 0 was used. */
480 1 : debug_level = SSSDBG_INVALID;
481 :
482 1 : pc = poptGetContext(argv[0], argc, argv, long_options, 0);
483 1 : while((opt = poptGetNextOpt(pc)) != -1) {
484 : switch(opt) {
485 : default:
486 0 : fprintf(stderr, "\nInvalid option %s: %s\n\n",
487 : poptBadOption(pc, 0), poptStrerror(opt));
488 0 : poptPrintUsage(pc, stderr, 0);
489 0 : return 1;
490 : }
491 : }
492 1 : poptFreeContext(pc);
493 :
494 1 : DEBUG_CLI_INIT(debug_level);
495 :
496 1 : rv = cmocka_run_group_tests(tests, NULL, NULL);
497 1 : return rv;
498 : }
|