/* Test trivial FD monitoring Copyright (C) Martin Schwenke, DataDirect Networks 2022 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 "replace.h" #include "system/filesys.h" #include "system/network.h" #include "system/wait.h" #include #include #include #include "lib/util/tevent_unix.h" #include "common/tmon.h" #include "tests/src/test_backtrace.h" struct test_state { const char *label; unsigned long async_wait_time; unsigned long blocking_sleep_time; }; static void test_tmon_ping_done(struct tevent_req *subreq); static void test_async_wait_done(struct tevent_req *subreq); static struct tevent_req *test_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *label, int fd, int direction, unsigned long timeout, unsigned long interval, unsigned long async_wait_time, unsigned long blocking_sleep_time) { struct tevent_req *req, *subreq; struct test_state *state; req = tevent_req_create(mem_ctx, &state, struct test_state); if (req == NULL) { return NULL; } state->label = label; state->async_wait_time = async_wait_time; state->blocking_sleep_time = blocking_sleep_time; subreq = tmon_ping_send(state, ev, fd, direction, timeout, interval); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, test_tmon_ping_done, req); if (state->async_wait_time != 0) { fprintf(stderr, "%s: async wait start %lu\n", state->label, state->async_wait_time); } subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs( (uint32_t)async_wait_time, 0)); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, test_async_wait_done, req); return req; } static void test_tmon_ping_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct test_state *state = tevent_req_data(req, struct test_state); bool status; int err; status = tmon_ping_recv(subreq, &err); TALLOC_FREE(subreq); if (!status) { switch(err) { case EPIPE: fprintf(stderr, "%s: pipe closed\n", state->label); break; case ETIMEDOUT: fprintf(stderr, "%s: ping timeout\n", state->label); break; default: fprintf(stderr, "%s: error (%d)\n", state->label, err); } tevent_req_error(req, err); return; } fprintf(stderr, "%s: done\n", state->label); tevent_req_done(req); } static void test_async_wait_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct test_state *state = tevent_req_data(req, struct test_state); unsigned int left; bool status; status = tevent_wakeup_recv(subreq); TALLOC_FREE(subreq); if (!status) { fprintf(stderr, "%s: tevent_wakeup_recv() failed\n", state->label); /* Ignore error */ } if (state->async_wait_time != 0) { fprintf(stderr, "%s: async wait end\n", state->label); } if (state->blocking_sleep_time == 0) { goto done; } fprintf(stderr, "%s: blocking sleep start %lu\n", state->label, state->blocking_sleep_time); left = sleep((unsigned int)state->blocking_sleep_time); fprintf(stderr, "%s: blocking sleep end\n", state->label); if (left != 0) { tevent_req_error(req, EINTR); return; } done: tevent_req_done(req); } static bool test_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } static int test_one(bool is_parent, int sync_fd, int fd, int direction, unsigned long timeout, unsigned long interval, unsigned long async_wait_time, unsigned long blocking_sleep_time) { TALLOC_CTX *mem_ctx; struct tevent_context *ev; struct tevent_req *req; bool status; char buf[1] = ""; ssize_t count; int err; int ret; if (!is_parent) { count = read(sync_fd, buf, sizeof(buf)); assert(count == 1); assert(buf[0] == '\0'); close(sync_fd); } mem_ctx = talloc_new(NULL); if (mem_ctx == NULL) { ret = ENOMEM; goto done; } ev = tevent_context_init(mem_ctx); if (ev == NULL) { ret = ENOMEM; goto done; } req = test_send(mem_ctx, ev, is_parent ? "parent" : "child", fd, direction, timeout, interval, async_wait_time, blocking_sleep_time); if (req == NULL) { ret = ENOMEM; goto done; } if (is_parent) { count = write(sync_fd, buf, sizeof(buf)); assert(count == 1); } status = tevent_req_poll(req, ev); if (!status) { ret = EIO; goto done; } status = test_recv(req, &err); ret = status ? 0 : err; done: return ret; } static void test(unsigned long parent_timeout, unsigned long parent_interval, unsigned long parent_async_wait_time, unsigned long parent_blocking_sleep_time, int parent_result, unsigned long child_timeout, unsigned long child_interval, unsigned long child_async_wait_time, unsigned long child_blocking_sleep_time, int child_result) { int sync[2]; int fd[2]; pid_t pid; int wstatus; int ret; /* Pipe for synchronisation */ ret = pipe(sync); assert(ret == 0); ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); assert(ret == 0); pid = fork(); assert(pid != -1); if (pid == 0) { /* child */ close(sync[1]); close(fd[0]); ret = test_one(false, sync[0], fd[1], TMON_FD_BOTH, child_timeout, child_interval, child_async_wait_time, child_blocking_sleep_time); _exit(ret); } /* Parent */ close(sync[0]); close(fd[1]); ret = test_one(true, sync[1], fd[0], TMON_FD_BOTH, parent_timeout, parent_interval, parent_async_wait_time, parent_blocking_sleep_time); assert(ret == parent_result); /* Close to mimic exit, so child status can be checked below */ close(fd[0]); /* Abort if child failed */ waitpid(pid, &wstatus, 0); if (WIFEXITED(wstatus)) { assert(WEXITSTATUS(wstatus) == child_result); } } struct test_inputs { unsigned int timeout; unsigned int interval; unsigned int async_wait_time; unsigned int blocking_sleep_time; int expected_result; }; static void get_test_inputs(const char **args, struct test_inputs *inputs) { if (strcmp(args[0], "false") == 0) { inputs->interval = 0; } else if (strcmp(args[0], "true") == 0) { inputs->interval = 1; } else { inputs->interval = strtoul(args[0], NULL, 0); } inputs->timeout = strtoul(args[1], NULL, 0); inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0); inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0); inputs->expected_result = (int)strtoul(args[4], NULL, 0); } static void usage(const char *prog) { fprintf(stderr, "usage: %s " "\\\n\t" " " " " " " " " " " "\\\n\t" " " " " " " " " " " "\n", prog); exit(1); } int main(int argc, const char **argv) { struct test_inputs parent; struct test_inputs child; if (argc != 11) { usage(argv[0]); } test_backtrace_setup(); get_test_inputs(&argv[1], &parent); get_test_inputs(&argv[6], &child); test(parent.timeout, parent.interval, parent.async_wait_time, parent.blocking_sleep_time, parent.expected_result, child.timeout, child.interval, child.async_wait_time, child.blocking_sleep_time, child.expected_result); return 0; }