/* 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/wait.h" #include #include #include #include #include "lib/util/tevent_unix.h" #include "common/tmon.h" #include "tests/src/test_backtrace.h" struct test_write_state { const char *write_data; size_t write_data_len; unsigned int offset; struct tevent_req *req; }; static int test_write_callback(void *private_data, struct tmon_pkt *pkt) { struct test_write_state *state = talloc_get_type_abort( private_data, struct test_write_state); bool status; size_t len; char *end; int err; char c; const char *t; assert(state->write_data != NULL); len = strlen(state->write_data); if (state->offset >= len) { return TMON_STATUS_EXIT; } c = state->write_data[state->offset]; state->offset++; if (isdigit(c)) { err = c - '0'; if (err == 0) { status = tmon_set_exit(pkt); } else { status = tmon_set_errno(pkt, err); } } else if (ispunct(c)) { switch (c) { case '.': return TMON_STATUS_SKIP; break; case '!': status = tmon_set_ping(pkt); break; case '#': /* Additional errno syntax: #nnn[;] */ t = &state->write_data[state->offset]; err = (int)strtol(t, &end, 10); state->offset += (end - t); if (state->write_data[state->offset] == ';') { state->offset++; } status = tmon_set_errno(pkt, err); break; default: status = false; } } else if (isascii(c) && !isspace(c)) { status = tmon_set_ascii(pkt, c); } else { status = tmon_set_custom(pkt, (uint16_t)c); } if (!status) { return EDOM; } t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE"); if (t == NULL) { return 0; } /* * This is write-skip mode: tmon_write() is called directly * here in the callback and TMON_WRITE_SKIP is returned. This * allows tmon_write() to be exercised by reusing test cases * rather than writing extra test code and test cases. */ status = tmon_write(state->req, pkt); if (!status) { return EIO; } return TMON_STATUS_SKIP; } static void test_tmon_done(struct tevent_req *subreq); static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, int fd, const char *write_data) { struct tevent_req *req, *subreq; struct test_write_state *state; struct tmon_actions actions = { .write_callback = test_write_callback, }; req = tevent_req_create(mem_ctx, &state, struct test_write_state); if (req == NULL) { return NULL; } state->write_data = write_data; state->offset = 0; subreq = tmon_send(state, ev, fd, TMON_FD_WRITE, 0, 1, &actions, state); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, test_tmon_done, req); /* Nasty hack, but OK to cheapen testing - see test_write_callback() */ state->req = subreq; return req; } static void test_tmon_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); bool status; int err; status = tmon_recv(subreq, &err); TALLOC_FREE(subreq); if (!status) { tevent_req_error(req, err); return; } tevent_req_done(req); } static bool test_write_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } static int test_timeout_ok_callback(void *private_data) { return 0; } static int test_read_callback(void *private_data, struct tmon_pkt *pkt) { bool status; char c; uint16_t val; status = tmon_parse_ping(pkt); if (status) { printf("PING\n"); fflush(stdout); return 0; } status = tmon_parse_ascii(pkt, &c); if (status) { printf("ASCII %c\n", c); fflush(stdout); return 0; } status = tmon_parse_custom(pkt, &val); if (status) { printf("CUSTOM 0x%"PRIx16"\n", val); fflush(stdout); return 0; } return 0; } static int test_close_ok_callback(void *private_data) { return 0; } struct test_read_state { }; static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, int fd, bool close_ok, unsigned long timeout, bool timeout_ok) { struct tevent_req *req, *subreq; struct test_read_state *state; struct tmon_actions actions = { .read_callback = test_read_callback, }; req = tevent_req_create(mem_ctx, &state, struct test_read_state); if (req == NULL) { return NULL; } if (timeout_ok) { actions.timeout_callback = test_timeout_ok_callback; } if (close_ok) { actions.close_callback = test_close_ok_callback; } subreq = tmon_send(state, ev, fd, TMON_FD_READ, timeout, 0, &actions, state); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, test_tmon_done, req); return req; } static bool test_read_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } static void test(const char *write_data, bool close_ok, unsigned long timeout, bool timeout_ok) { TALLOC_CTX *mem_ctx; struct tevent_context *ev; struct tevent_req *req; int fd[2]; pid_t pid; int wstatus; bool status; int err; int ret; mem_ctx = talloc_new(NULL); assert(mem_ctx != NULL); ev = tevent_context_init(mem_ctx); assert(ev != NULL); ret = pipe(fd); assert(ret == 0); pid = fork(); assert(pid != -1); if (pid == 0) { /* child */ close(fd[1]); req = test_read_send(mem_ctx, ev, fd[0], close_ok, timeout, timeout_ok); assert(req != NULL); status = tevent_req_poll(req, ev); assert(status); status = test_read_recv(req, &err); if (status) { err = 0; printf("READER OK\n"); } else { printf("READER ERR=%d\n", err); } fflush(stdout); _exit(ret); } /* Parent */ close(fd[0]); req = test_write_send(mem_ctx, ev, fd[1], write_data); assert(req != NULL); status = tevent_req_poll(req, ev); assert(status); status = test_write_recv(req, &err); if (status) { err = 0; printf("WRITER OK\n"); } else { printf("WRITER ERR=%d\n", err); } fflush(stdout); /* Close to mimick exit, so child status can be checked below */ close(fd[1]); waitpid(pid, &wstatus, 0); } static void usage(const char *prog) { fprintf(stderr, "usage: %s \n\n" " is processed by test_write_callback(), " "1 character per second:\n" " 0: write EXIT\n" " 1-9: write ERRNO 1-9\n" " .: skip write\n" " : write CUSTOM containing \n" " other : write ASCII containing \n" " other: write CUSTOM\n" " See test_write_callback() for more details\n" , prog); exit(1); } int main(int argc, const char **argv) { bool close_ok, timeout_ok; unsigned long timeout; if (argc != 5) { usage(argv[0]); } test_backtrace_setup(); close_ok = (strcmp(argv[2], "true") == 0); timeout = strtoul(argv[3], NULL, 0); if (timeout == 0) { /* * Default timeout that should not come into play but * will cause tests to fail after a reasonable amount * of time, if something unexpected happens. */ timeout = 20; } timeout_ok = (strcmp(argv[4], "true") == 0); test(argv[1], close_ok, timeout, timeout_ok); return 0; }