diff options
Diffstat (limited to '')
-rw-r--r-- | ctdb/event/event.h | 55 | ||||
-rw-r--r-- | ctdb/event/event_client.c | 351 | ||||
-rw-r--r-- | ctdb/event/event_cmd.c | 358 | ||||
-rw-r--r-- | ctdb/event/event_conf.c | 85 | ||||
-rw-r--r-- | ctdb/event/event_conf.h | 31 | ||||
-rw-r--r-- | ctdb/event/event_config.c | 122 | ||||
-rw-r--r-- | ctdb/event/event_context.c | 472 | ||||
-rw-r--r-- | ctdb/event/event_daemon.c | 382 | ||||
-rw-r--r-- | ctdb/event/event_private.h | 103 | ||||
-rw-r--r-- | ctdb/event/event_protocol.c | 1123 | ||||
-rw-r--r-- | ctdb/event/event_protocol.h | 100 | ||||
-rw-r--r-- | ctdb/event/event_protocol_api.h | 61 | ||||
-rw-r--r-- | ctdb/event/event_protocol_test.c | 412 | ||||
-rw-r--r-- | ctdb/event/event_protocol_util.c | 46 | ||||
-rw-r--r-- | ctdb/event/event_request.c | 217 | ||||
-rw-r--r-- | ctdb/event/event_tool.c | 792 | ||||
-rw-r--r-- | ctdb/event/event_tool.h | 38 |
17 files changed, 4748 insertions, 0 deletions
diff --git a/ctdb/event/event.h b/ctdb/event/event.h new file mode 100644 index 0000000..13617c0 --- /dev/null +++ b/ctdb/event/event.h @@ -0,0 +1,55 @@ +/* + CTDB event daemon client + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_H__ +#define __CTDB_EVENT_H__ + +#include "event/event_protocol.h" + +struct ctdb_event_context; + +int ctdb_event_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context **result); + +struct tevent_req *ctdb_event_run_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_run *run); +bool ctdb_event_run_recv(struct tevent_req *req, int *perr, int *result); + +struct tevent_req *ctdb_event_status_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_status *status); +bool ctdb_event_status_recv(struct tevent_req *req, + int *perr, + int *result, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply_status **status); + +struct tevent_req *ctdb_event_script_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_script *script); +bool ctdb_event_script_recv(struct tevent_req *req, int *perr, int *result); + +#endif /* __CTDB_EVENT_H__ */ diff --git a/ctdb/event/event_client.c b/ctdb/event/event_client.c new file mode 100644 index 0000000..d0bee0d --- /dev/null +++ b/ctdb/event/event_client.c @@ -0,0 +1,351 @@ +/* + CTDB event daemon client + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "common/logging.h" +#include "common/path.h" +#include "common/sock_client.h" + +#include "event/event_protocol_api.h" +#include "event/event.h" + +struct ctdb_event_context { + char *socket; + struct sock_client_context *sockc; +}; + +struct event_request { + struct ctdb_event_header header; + struct ctdb_event_request *request; +}; + +struct event_reply { + struct ctdb_event_header header; + struct ctdb_event_reply *reply; +}; + +static int event_request_push(void *request_data, + uint32_t reqid, + TALLOC_CTX *mem_ctx, + uint8_t **buf, + size_t *buflen, + void *private_data) +{ + struct event_request *r = (struct event_request *)request_data; + int ret; + + r->header.reqid = reqid; + + *buflen = ctdb_event_request_len(&r->header, r->request); + *buf = talloc_size(mem_ctx, *buflen); + if (*buf == NULL) { + return ENOMEM; + } + + ret = ctdb_event_request_push(&r->header, r->request, *buf, buflen); + if (ret != 0) { + talloc_free(*buf); + return ret; + } + + return 0; +} + +static int event_reply_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + void **reply_data, + void *private_data) +{ + struct event_reply *r; + int ret; + + r = talloc_zero(mem_ctx, struct event_reply); + if (r == NULL) { + return ENOMEM; + } + + ret = ctdb_event_reply_pull(buf, buflen, &r->header, r, &r->reply); + if (ret != 0) { + talloc_free(r); + return ret; + } + + *reply_data = r; + + return 0; +} + +static int event_reply_reqid(uint8_t *buf, + size_t buflen, + uint32_t *reqid, + void *private_data) +{ + struct ctdb_event_header header; + int ret; + + ret = ctdb_event_header_extract(buf, buflen, &header); + if (ret != 0) { + return ret; + } + + *reqid = header.reqid; + return 0; +} + +struct sock_client_proto_funcs event_proto_funcs = { + .request_push = event_request_push, + .reply_pull = event_reply_pull, + .reply_reqid = event_reply_reqid, +}; + +int ctdb_event_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context **result) +{ + struct ctdb_event_context *eclient; + int ret; + + eclient = talloc_zero(mem_ctx, struct ctdb_event_context); + if (eclient == NULL) { + return ENOMEM; + } + + eclient->socket = path_socket(eclient, "eventd"); + if (eclient->socket == NULL) { + talloc_free(eclient); + return ENOMEM; + } + + ret = sock_client_setup(eclient, + ev, + eclient->socket, + &event_proto_funcs, + eclient, + &eclient->sockc); + if (ret != 0) { + talloc_free(eclient); + return ret; + } + + *result = eclient; + return 0; +} + +/* + * Handle request and reply + */ + +struct ctdb_event_msg_state { + struct event_request e_request; + struct event_reply *e_reply; +}; + +static void ctdb_event_msg_done(struct tevent_req *subreq); + +static struct tevent_req *ctdb_event_msg_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request *request) +{ + struct tevent_req *req, *subreq; + struct ctdb_event_msg_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ctdb_event_msg_state); + if (req == NULL) { + return NULL; + } + + state->e_request.request = request; + + subreq = sock_client_msg_send(mem_ctx, + ev, + eclient->sockc, + tevent_timeval_zero(), + &state->e_request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, ctdb_event_msg_done, req); + + return req; +} + +static void ctdb_event_msg_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct ctdb_event_msg_state *state = tevent_req_data( + req, struct ctdb_event_msg_state); + int ret = 0; + bool ok; + + ok = sock_client_msg_recv(subreq, &ret, state, &state->e_reply); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool ctdb_event_msg_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct ctdb_event_msg_state *state = tevent_req_data( + req, struct ctdb_event_msg_state); + int ret = 0; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + *reply = talloc_steal(mem_ctx, state->e_reply->reply); + + return true; +} + +/* + * API functions + */ + +struct tevent_req *ctdb_event_run_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_run *run) +{ + struct ctdb_event_request request; + + request.cmd = CTDB_EVENT_CMD_RUN; + request.data.run = run; + + return ctdb_event_msg_send(mem_ctx, ev, eclient, &request); +} + +bool ctdb_event_run_recv(struct tevent_req *req, int *perr, int *result) +{ + struct ctdb_event_reply *reply = NULL; + bool ok; + + ok = ctdb_event_msg_recv(req, perr, req, &reply); + if (!ok) { + return false; + } + + if (reply->cmd != CTDB_EVENT_CMD_RUN) { + *result = EPROTO; + } else { + *result = reply->result; + } + + talloc_free(reply); + return true; +} + +struct tevent_req *ctdb_event_status_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_status *status) +{ + struct ctdb_event_request request; + + request.cmd = CTDB_EVENT_CMD_STATUS; + request.data.status = status; + + return ctdb_event_msg_send(mem_ctx, ev, eclient, &request); +} + +bool ctdb_event_status_recv(struct tevent_req *req, + int *perr, + int *result, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply_status **status) +{ + struct ctdb_event_reply *reply = NULL; + bool ok; + + ok = ctdb_event_msg_recv(req, perr, req, &reply); + if (!ok) { + return false; + } + + if (reply->cmd != CTDB_EVENT_CMD_STATUS) { + *result = EPROTO; + } else { + *result = reply->result; + } + + if (reply->result == 0) { + *status = talloc_steal(mem_ctx, reply->data.status); + } else { + *status = NULL; + } + + talloc_free(reply); + return true; +} + +struct tevent_req *ctdb_event_script_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_event_context *eclient, + struct ctdb_event_request_script *script) +{ + struct ctdb_event_request request; + + request.cmd = CTDB_EVENT_CMD_SCRIPT; + request.data.script = script; + + return ctdb_event_msg_send(mem_ctx, ev, eclient, &request); +} + +bool ctdb_event_script_recv(struct tevent_req *req, int *perr, int *result) +{ + struct ctdb_event_reply *reply = NULL; + bool ok; + + ok = ctdb_event_msg_recv(req, perr, req, &reply); + if (!ok) { + return false; + } + + if (reply->cmd != CTDB_EVENT_CMD_SCRIPT) { + *result = EPROTO; + } else { + *result = reply->result; + } + + talloc_free(reply); + return true; +} diff --git a/ctdb/event/event_cmd.c b/ctdb/event/event_cmd.c new file mode 100644 index 0000000..db39e4d --- /dev/null +++ b/ctdb/event/event_cmd.c @@ -0,0 +1,358 @@ +/* + CTDB event daemon - command handling + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "common/logging.h" + +#include "event/event_private.h" + +struct event_cmd_state { + struct event_context *eventd; + struct ctdb_event_request *request; + struct ctdb_event_reply *reply; +}; + +/* + * CTDB_EVENT_CMD_RUN + */ + +static void event_cmd_run_done(struct tevent_req *subreq); + +static struct tevent_req *event_cmd_run_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_request *request, + struct ctdb_event_reply *reply) +{ + struct tevent_req *req, *subreq; + struct event_cmd_state *state; + struct run_event_context *run_ctx; + struct ctdb_event_request_run *rdata; + int ret; + bool continue_on_failure = false; + + req = tevent_req_create(mem_ctx, &state, struct event_cmd_state); + if (req == NULL) { + return NULL; + } + + state->eventd = eventd; + state->request = request; + state->reply = reply; + + rdata = request->data.run; + + ret = eventd_run_ctx(eventd, rdata->component, &run_ctx); + if (ret != 0) { + state->reply->result = ret; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (rdata->flags & CTDB_EVENT_RUN_ALL) { + continue_on_failure = true; + } + + subreq = run_event_send(state, + ev, + run_ctx, + rdata->event, + rdata->args, + tevent_timeval_current_ofs(rdata->timeout,0), + continue_on_failure); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, event_cmd_run_done, req); + + return req; +} + +static void event_cmd_run_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct event_cmd_state *state = tevent_req_data( + req, struct event_cmd_state); + struct run_event_script_list *script_list = NULL; + struct ctdb_event_request_run *rdata; + int ret; + bool ok; + + ok = run_event_recv(subreq, &ret, state, &script_list); + TALLOC_FREE(subreq); + if (!ok) { + state->reply->result = ret; + goto done; + } + + if (script_list == NULL) { + state->reply->result = EIO; + goto done; + } + + if (script_list->summary == -ECANCELED) { + state->reply->result = ECANCELED; + goto done; + } + + rdata = state->request->data.run; + ret = eventd_set_event_result(state->eventd, + rdata->component, + rdata->event, + script_list); + if (ret != 0) { + state->reply->result = ret; + goto done; + } + + if (script_list->summary == -ETIMEDOUT) { + state->reply->result = ETIMEDOUT; + } else if (script_list->summary != 0) { + state->reply->result = ENOEXEC; + } + +done: + tevent_req_done(req); +} + +/* + * CTDB_EVENT_CMD_STATUS + */ + +static struct tevent_req *event_cmd_status_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_request *request, + struct ctdb_event_reply *reply) +{ + struct tevent_req *req; + struct event_cmd_state *state; + struct ctdb_event_request_run *rdata; + struct run_event_script_list *script_list; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct event_cmd_state); + if (req == NULL) { + return NULL; + } + + reply->data.status = talloc_zero(reply, + struct ctdb_event_reply_status); + if (tevent_req_nomem(reply->data.status, req)) { + reply->result = ENOMEM; + goto done; + } + + rdata = request->data.run; + + ret = eventd_get_event_result(eventd, + rdata->component, + rdata->event, + &script_list); + if (ret != 0) { + reply->result = ret; + goto done; + } + + reply->data.status->script_list = eventd_script_list(reply, + script_list); + if (reply->data.status->script_list == NULL) { + reply->result = ENOMEM; + goto done; + } + reply->data.status->summary = script_list->summary; + + reply->result = 0; + +done: + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +/* + * CTDB_EVENT_CMD_SCRIPT + */ + +static struct tevent_req *event_cmd_script_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_request *request, + struct ctdb_event_reply *reply) +{ + struct tevent_req *req; + struct event_cmd_state *state; + struct run_event_context *run_ctx; + struct ctdb_event_request_script *rdata; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct event_cmd_state); + if (req == NULL) { + return NULL; + } + + rdata = request->data.script; + + ret = eventd_run_ctx(eventd, rdata->component, &run_ctx); + if (ret != 0) { + reply->result = ret; + goto done; + } + + if (rdata->action == CTDB_EVENT_SCRIPT_DISABLE) { + ret = run_event_script_disable(run_ctx, rdata->script); + } else if (rdata->action == CTDB_EVENT_SCRIPT_ENABLE) { + ret = run_event_script_enable(run_ctx, rdata->script); + } else { + D_ERR("Invalid action specified\n"); + reply->result = EPROTO; + goto done; + } + + if (ret != 0) { + reply->result = ret; + goto done; + } + + reply->result = 0; + +done: + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool event_cmd_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + + +struct event_cmd_dispatch_state { + struct ctdb_event_reply *reply; +}; + +static void event_cmd_dispatch_done(struct tevent_req *subreq); + +struct tevent_req *event_cmd_dispatch_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_request *request) +{ + struct tevent_req *req, *subreq; + struct event_cmd_dispatch_state *state; + + req = tevent_req_create(mem_ctx, + &state, + struct event_cmd_dispatch_state); + if (req == NULL) { + return NULL; + } + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return tevent_req_post(req, ev); + } + + state->reply->cmd = request->cmd; + + switch (request->cmd) { + case CTDB_EVENT_CMD_RUN: + subreq = event_cmd_run_send(state, + ev, + eventd, + request, + state->reply); + break; + + case CTDB_EVENT_CMD_STATUS: + subreq = event_cmd_status_send(state, + ev, + eventd, + request, + state->reply); + break; + + case CTDB_EVENT_CMD_SCRIPT: + subreq = event_cmd_script_send(state, + ev, + eventd, + request, + state->reply); + break; + + default: + state->reply->result = EPROTO; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, event_cmd_dispatch_done, req); + + return req; +} + +static void event_cmd_dispatch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool ok; + + ok = event_cmd_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +bool event_cmd_dispatch_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct event_cmd_dispatch_state *state = tevent_req_data( + req, struct event_cmd_dispatch_state); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + *reply = talloc_steal(mem_ctx, state->reply); + return true; +} diff --git a/ctdb/event/event_conf.c b/ctdb/event/event_conf.c new file mode 100644 index 0000000..61ecf8e --- /dev/null +++ b/ctdb/event/event_conf.c @@ -0,0 +1,85 @@ +/* + CTDB event daemon + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/dir.h" + +#include "lib/util/debug.h" + +#include "common/conf.h" +#include "common/path.h" + +#include "event/event_conf.h" + +static bool event_conf_validate_debug_script(const char *key, + const char *old_script, + const char *new_script, + enum conf_update_mode mode) +{ + char script[PATH_MAX]; + char script_path[PATH_MAX]; + struct stat st; + size_t len; + int ret; + + len = strlcpy(script, new_script, sizeof(script)); + if (len >= sizeof(script)) { + D_ERR("debug script name too long\n"); + return false; + } + + ret = snprintf(script_path, + sizeof(script_path), + "%s/%s", + path_etcdir(), + basename(script)); + if (ret < 0 || (size_t)ret >= sizeof(script_path)) { + D_ERR("debug script path too long\n"); + return false; + } + + ret = stat(script_path, &st); + if (ret == -1) { + D_ERR("debug script %s does not exist\n", script_path); + return false; + } + + if (! S_ISREG(st.st_mode)) { + D_ERR("debug script %s is not a file\n", script_path); + return false; + } + if (! (st.st_mode & S_IXUSR)) { + D_ERR("debug script %s is not executable\n", script_path); + return false; + } + + return true; +} + +void event_conf_init(struct conf_context *conf) +{ + conf_define_section(conf, EVENT_CONF_SECTION, NULL); + + conf_define_string(conf, + EVENT_CONF_SECTION, + EVENT_CONF_DEBUG_SCRIPT, + NULL, + event_conf_validate_debug_script); +} diff --git a/ctdb/event/event_conf.h b/ctdb/event/event_conf.h new file mode 100644 index 0000000..964a18a --- /dev/null +++ b/ctdb/event/event_conf.h @@ -0,0 +1,31 @@ +/* + CTDB event daemon + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_CONF_H__ +#define __CTDB_EVENT_CONF_H__ + +#include "common/conf.h" + +#define EVENT_CONF_SECTION "event" + +#define EVENT_CONF_DEBUG_SCRIPT "debug script" + +void event_conf_init(struct conf_context *conf); + +#endif /* __CTDB_EVENT_CONF_H__ */ diff --git a/ctdb/event/event_config.c b/ctdb/event/event_config.c new file mode 100644 index 0000000..d282622 --- /dev/null +++ b/ctdb/event/event_config.c @@ -0,0 +1,122 @@ +/* + CTDB event daemon - config handling + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> + +#include "common/conf.h" +#include "common/logging_conf.h" +#include "common/path.h" + +#include "event/event_private.h" +#include "event/event_conf.h" + +struct event_config { + char *config_file; + struct conf_context *conf; + + const char *logging_location; + const char *logging_loglevel; + const char *debug_script; +}; + +int event_config_init(TALLOC_CTX *mem_ctx, struct event_config **result) +{ + struct event_config *config; + int ret; + bool ok; + + config = talloc_zero(mem_ctx, struct event_config); + if (config == NULL) { + return ENOMEM; + } + + config->config_file = path_config(config); + if (config->config_file == NULL) { + talloc_free(config); + return ENOMEM; + } + + ret = conf_init(config, &config->conf); + if (ret != 0) { + talloc_free(config); + return ret; + } + + logging_conf_init(config->conf, NULL); + + conf_assign_string_pointer(config->conf, + LOGGING_CONF_SECTION, + LOGGING_CONF_LOCATION, + &config->logging_location); + conf_assign_string_pointer(config->conf, + LOGGING_CONF_SECTION, + LOGGING_CONF_LOG_LEVEL, + &config->logging_loglevel); + + event_conf_init(config->conf); + + conf_assign_string_pointer(config->conf, + EVENT_CONF_SECTION, + EVENT_CONF_DEBUG_SCRIPT, + &config->debug_script); + + ok = conf_valid(config->conf); + if (!ok) { + talloc_free(config); + return EINVAL; + } + + ret = conf_load(config->conf, config->config_file, true); + if (ret != 0 && ret != ENOENT) { + talloc_free(config); + return ret; + } + + *result = config; + return 0; +} + +const char *event_config_log_location(struct event_config *config) +{ + return config->logging_location; +} + +const char *event_config_log_level(struct event_config *config) +{ + return config->logging_loglevel; +} + +const char *event_config_debug_script(struct event_config *config) +{ + return config->debug_script; +} + +int event_config_reload(struct event_config *config) +{ + int ret; + + ret = conf_reload(config->conf); + if (ret != 0 && ret != ENOENT) { + return ret; + } + + return 0; +} diff --git a/ctdb/event/event_context.c b/ctdb/event/event_context.c new file mode 100644 index 0000000..79bcd83 --- /dev/null +++ b/ctdb/event/event_context.c @@ -0,0 +1,472 @@ +/* + CTDB event daemon - daemon state + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/dir.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/dlinklist.h" + +#include "common/logging.h" +#include "common/run_event.h" +#include "common/path.h" + +#include "event/event_private.h" + +struct event_event { + struct event_event *prev, *next; + + const char *name; + struct run_event_script_list *script_list; +}; + +struct event_component { + struct event_component *prev, *next; + + /* component state */ + const char *name; + const char *path; + struct run_event_context *run_ctx; + + /* events list */ + struct event_event *event; +}; + +struct event_client { + struct event_client *prev, *next; + + struct sock_client_context *client; +}; + +struct event_context { + struct tevent_context *ev; + struct event_config *config; + struct run_proc_context *run_proc_ctx; + + const char *script_dir; + const char *debug_script; + + /* component list */ + struct event_component *component; + + /* client list */ + struct event_client *client; +}; + +/* + * event_event functions + */ + +static struct event_event *eventd_event_find(struct event_component *comp, + const char *event_name) +{ + struct event_event *event; + + if (event_name == NULL) { + return NULL; + } + + for (event = comp->event; event != NULL; event = event->next) { + if (strcmp(event->name, event_name) == 0) { + return event; + } + } + + return NULL; +} + +static int eventd_event_add(struct event_component *comp, + const char *event_name, + struct event_event **result) +{ + struct event_event *event; + + if (event_name == NULL) { + return EINVAL; + } + + event = eventd_event_find(comp, event_name); + if (event != NULL) { + goto done; + } + + event = talloc_zero(comp, struct event_event); + if (event == NULL) { + return ENOMEM; + } + + event->name = talloc_strdup(event, event_name); + if (event->name == NULL) { + talloc_free(event); + return ENOMEM; + } + + DLIST_ADD_END(comp->event, event); + +done: + if (result != NULL) { + *result = event; + } + return 0; +} + +static int eventd_event_set(struct event_component *comp, + const char *event_name, + struct run_event_script_list *script_list) +{ + struct event_event *event = NULL; + int ret; + + ret = eventd_event_add(comp, event_name, &event); + if (ret != 0) { + return ret; + } + + TALLOC_FREE(event->script_list); + if (script_list != NULL) { + event->script_list = talloc_steal(event, script_list); + } + + return 0; +} + +static int eventd_event_get(struct event_component *comp, + const char *event_name, + struct run_event_script_list **result) +{ + struct event_event *event; + + event = eventd_event_find(comp, event_name); + if (event == NULL) { + return EINVAL; + } + + *result = event->script_list; + return 0; +} + +/* + * event_component functions + */ + +static struct event_component *eventd_component_find( + struct event_context *eventd, + const char *comp_name) +{ + struct event_component *comp; + + if (comp_name == NULL) { + return NULL; + } + + for (comp = eventd->component; comp != NULL; comp = comp->next) { + if (strcmp(comp->name, comp_name) == 0) { + return comp; + } + } + + return NULL; +} + +static int eventd_component_add(struct event_context *eventd, + const char *comp_name, + struct event_component **result) +{ + struct event_component *comp; + int ret; + + if (comp_name == NULL) { + return EINVAL; + } + + comp = eventd_component_find(eventd, comp_name); + if (comp != NULL) { + goto done; + } + + comp = talloc_zero(eventd, struct event_component); + if (comp == NULL) { + return ENOMEM; + } + + comp->name = talloc_strdup(comp, comp_name); + if (comp->name == NULL) { + talloc_free(comp); + return ENOMEM; + } + + comp->path = talloc_asprintf(comp, + "%s/%s", + eventd->script_dir, + comp_name); + if (comp->path == NULL) { + talloc_free(comp); + return ENOMEM; + } + + ret = run_event_init(eventd, + eventd->run_proc_ctx, + comp->path, + eventd->debug_script, + &comp->run_ctx); + if (ret != 0) { + talloc_free(comp); + return ret; + } + + DLIST_ADD_END(eventd->component, comp); + +done: + if (result != NULL) { + *result = comp; + } + return 0; +} + +/* + * event_client functions + */ + +static struct event_client *eventd_client_find( + struct event_context *eventd, + struct sock_client_context *client) +{ + struct event_client *e; + + for (e = eventd->client; e != NULL; e = e->next) { + if (e->client == client) { + return e; + } + } + + return NULL; +} + +int eventd_client_add(struct event_context *eventd, + struct sock_client_context *client) +{ + struct event_client *e; + + e = talloc_zero(eventd, struct event_client); + if (e == NULL) { + return ENOMEM; + } + + e->client = client; + + DLIST_ADD_END(eventd->client, e); + + return 0; +} + +void eventd_client_del(struct event_context *eventd, + struct sock_client_context *client) +{ + struct event_client *e; + + e = eventd_client_find(eventd, client); + if (e == NULL) { + return; + } + + DLIST_REMOVE(eventd->client, e); + + talloc_free(e); +} + +bool eventd_client_exists(struct event_context *eventd, + struct sock_client_context *client) +{ + struct event_client *e; + + e = eventd_client_find(eventd, client); + if (e == NULL) { + return false; + } + + return true; +} + +/* public functions */ + +int event_context_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_config *config, + struct event_context **result) +{ + struct event_context *eventd; + const char *debug_script; + int ret; + + eventd = talloc_zero(mem_ctx, struct event_context); + if (eventd == NULL) { + return ENOMEM; + } + + eventd->ev = ev; + eventd->config = config; + + ret = run_proc_init(eventd, ev, &eventd->run_proc_ctx); + if (ret != 0) { + talloc_free(eventd); + return ret; + } + + eventd->script_dir = path_etcdir_append(eventd, "events"); + if (eventd->script_dir == NULL) { + talloc_free(eventd); + return ENOMEM; + } + + /* FIXME + status = directory_exist(eventd->script_dir); + if (! status) { + talloc_free(eventd); + return EINVAL; + } + */ + + debug_script = event_config_debug_script(config); + if (debug_script != NULL) { + eventd->debug_script = path_etcdir_append(eventd, + debug_script); + if (eventd->debug_script == NULL) { + D_WARNING("Failed to set debug script to %s\n", + debug_script); + } + } + + *result = eventd; + return 0; +} + +struct event_config *eventd_config(struct event_context *eventd) +{ + return eventd->config; +} + +int eventd_run_ctx(struct event_context *eventd, + const char *comp_name, + struct run_event_context **result) +{ + struct event_component *comp; + int ret; + + ret = eventd_component_add(eventd, comp_name, &comp); + if (ret != 0) { + return ret; + } + + *result = comp->run_ctx; + return 0; +} + +int eventd_set_event_result(struct event_context *eventd, + const char *comp_name, + const char *event_name, + struct run_event_script_list *script_list) +{ + struct event_component *comp; + + comp = eventd_component_find(eventd, comp_name); + if (comp == NULL) { + return ENOENT; + } + + return eventd_event_set(comp, event_name, script_list); +} + +int eventd_get_event_result(struct event_context *eventd, + const char *comp_name, + const char *event_name, + struct run_event_script_list **result) +{ + struct event_component *comp; + int ret; + + ret = eventd_component_add(eventd, comp_name, &comp); + if (ret != 0) { + return ret; + } + + return eventd_event_get(comp, event_name, result); +} + +struct ctdb_event_script_list *eventd_script_list( + TALLOC_CTX *mem_ctx, + struct run_event_script_list *script_list) +{ + struct ctdb_event_script_list *value; + int num_scripts = 0; + int i; + + value = talloc_zero(mem_ctx, struct ctdb_event_script_list); + if (value == NULL) { + return NULL; + } + + if (script_list != NULL) { + num_scripts = script_list->num_scripts; + } + + if (num_scripts <= 0) { + return value; + } + + value->script = talloc_array(value, + struct ctdb_event_script, + num_scripts); + if (value->script == NULL) { + goto fail; + } + + for (i=0; i<num_scripts; i++) { + struct run_event_script *rscript = &script_list->script[i]; + struct ctdb_event_script *escript = &value->script[i]; + + escript->name = talloc_strdup(value, rscript->name); + if (escript->name == NULL) { + goto fail; + } + + escript->begin = rscript->begin; + escript->end = rscript->end; + escript->result = rscript->summary; + + if (rscript->output == NULL) { + escript->output = NULL; + continue; + } + + escript->output = talloc_strdup(value, rscript->output); + if (escript->output == NULL) { + goto fail; + } + } + value->num_scripts = num_scripts; + + return value; + +fail: + talloc_free(value); + return NULL; +} diff --git a/ctdb/event/event_daemon.c b/ctdb/event/event_daemon.c new file mode 100644 index 0000000..d96ff6f --- /dev/null +++ b/ctdb/event/event_daemon.c @@ -0,0 +1,382 @@ +/* + CTDB event daemon + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/tevent_unix.h" + +#include "common/logging.h" +#include "common/path.h" +#include "common/sock_daemon.h" + +#include "event/event_private.h" + +struct event_daemon_state { + TALLOC_CTX *mem_ctx; + char *socket; + char *pidfile; + struct tevent_context *ev; + struct event_config *config; + struct sock_daemon_context *sockd; + struct event_context *eventd; +}; + +static int event_daemon_startup(void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + int ret; + + ret = event_context_init(e_state, + e_state->ev, + e_state->config, + &e_state->eventd); + if (ret != 0) { + D_ERR("Failed to initialize event context\n"); + return ret; + } + + return 0; +} + +static int event_daemon_reconfigure(void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + int ret; + + ret = event_config_reload(e_state->config); + if (ret != 0) { + D_WARNING("Configuration reload failed\n"); + } + + return 0; +} + +static int event_daemon_reopen_logs(void *private_data) +{ + bool status; + + status = logging_reopen_logs(); + + return status ? 0 : 1; +} + +static void event_daemon_shutdown(void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + + TALLOC_FREE(e_state->eventd); +} + +static bool event_client_connect(struct sock_client_context *client, + pid_t pid, + void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + int ret; + + ret = eventd_client_add(e_state->eventd, client); + if (ret != 0) { + D_ERR("Failed to register client, ret=%d\n", ret); + return false; + } + + return true; +} + +static void event_client_disconnect(struct sock_client_context *client, + void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + + eventd_client_del(e_state->eventd, client); +} + +struct event_client_state { + struct tevent_context *ev; + struct event_context *eventd; + struct sock_client_context *client; + uint8_t *buf; + size_t buflen; +}; + +static void event_client_request_done(struct tevent_req *subreq); +static void event_client_reply_done(struct tevent_req *subreq); + +static struct tevent_req *event_client_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, + size_t buflen, + void *private_data) +{ + struct event_daemon_state *e_state = talloc_get_type_abort( + private_data, struct event_daemon_state); + struct tevent_req *req, *subreq; + struct event_client_state *state; + + req = tevent_req_create(mem_ctx, &state, struct event_client_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->eventd = e_state->eventd; + state->client = client; + + subreq = event_pkt_send(state, ev, e_state->eventd, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, event_client_request_done, req); + + return req; +} + +static void event_client_request_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct event_client_state *state = tevent_req_data( + req, struct event_client_state); + int ret = 0; + bool ok; + + ok = event_pkt_recv(subreq, &ret, state, &state->buf, &state->buflen); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, ret); + return; + } + + ok = eventd_client_exists(state->eventd, state->client); + if (!ok) { + /* Client has already disconnected */ + talloc_free(state->buf); + tevent_req_done(req); + return; + } + + subreq = sock_socket_write_send(state, + state->ev, + state->client, + state->buf, + state->buflen); + if (tevent_req_nomem(subreq, req)) { + talloc_free(state->buf); + return; + } + tevent_req_set_callback(subreq, event_client_reply_done, req); +} + +static void event_client_reply_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct event_client_state *state = tevent_req_data( + req, struct event_client_state); + int ret = 0; + bool ok; + + talloc_free(state->buf); + + ok = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + D_ERR("Sending reply failed\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool event_client_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static struct { + int pid; + int startup_fd; +} options = { + .pid = -1, + .startup_fd = -1, +}; + +struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "pid", 'P', POPT_ARG_INT, &options.pid, 0, + "pid to wait for", "PID" }, + { "startup-fd", 'S', POPT_ARG_INT, &options.startup_fd, 0, + "file descriptor to notify of successful start", "FD" }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + poptContext pc; + struct event_daemon_state *e_state; + struct sock_daemon_funcs daemon_funcs; + struct sock_socket_funcs socket_funcs; + const char *log_location = "file:"; + const char *log_level = "NOTICE"; + const char *t; + int interactive = 0; + int opt, ret; + bool ok; + + pc = poptGetContext(argv[0], + argc, + argv, + cmdline_options, + 0); + while ((opt = poptGetNextOpt(pc)) != -1) { + D_ERR("Invalid options %s: %s\n", + poptBadOption(pc, 0), + poptStrerror(opt)); + exit(1); + } + + t = getenv("CTDB_INTERACTIVE"); + if (t != NULL) { + interactive = 1; + } + + e_state = talloc_zero(NULL, struct event_daemon_state); + if (e_state == NULL) { + D_ERR("Memory allocation error\n"); + ret = 1; + goto fail; + } + + e_state->mem_ctx = talloc_new(e_state); + if (e_state->mem_ctx == NULL) { + D_ERR("Memory allocation error\n"); + ret = 1; + goto fail; + } + + e_state->socket = path_socket(e_state, "eventd"); + if (e_state->socket == NULL) { + D_ERR("Memory allocation error\n"); + ret = 1; + goto fail; + } + + e_state->pidfile = path_pidfile(e_state, "eventd"); + if (e_state->pidfile == NULL) { + D_ERR("Memory allocation error\n"); + ret = 1; + goto fail; + } + + ret = event_config_init(e_state, &e_state->config); + if (ret != 0) { + D_ERR("Failed to initialize event config\n"); + goto fail; + } + + e_state->ev = tevent_context_init(e_state->mem_ctx); + if (e_state->ev == NULL) { + D_ERR("Failed to initialize tevent\n"); + ret = 1; + goto fail; + } + + daemon_funcs = (struct sock_daemon_funcs) { + .startup = event_daemon_startup, + .reconfigure = event_daemon_reconfigure, + .reopen_logs = event_daemon_reopen_logs, + .shutdown = event_daemon_shutdown, + }; + + if (interactive == 0) { + log_location = event_config_log_location(e_state->config); + log_level = event_config_log_level(e_state->config); + } + + ret = sock_daemon_setup(e_state->mem_ctx, + "ctdb-eventd", + log_location, + log_level, + &daemon_funcs, + e_state, + &e_state->sockd); + if (ret != 0) { + D_ERR("Failed to setup sock daemon\n"); + goto fail; + } + + socket_funcs = (struct sock_socket_funcs) { + .connect = event_client_connect, + .disconnect = event_client_disconnect, + .read_send = event_client_send, + .read_recv = event_client_recv, + }; + + ret = sock_daemon_add_unix(e_state->sockd, + e_state->socket, + &socket_funcs, + e_state); + if (ret != 0) { + D_ERR("Failed to setup socket %s\n", e_state->socket); + goto fail; + } + + if (options.startup_fd != -1) { + ok = sock_daemon_set_startup_fd(e_state->sockd, + options.startup_fd); + if (!ok) { + goto fail; + } + } + + ret = sock_daemon_run(e_state->ev, + e_state->sockd, + e_state->pidfile, + false, + false, + options.pid); + if (ret == EINTR) { + ret = 0; + } + + if (t != NULL) { + talloc_report_full(e_state->mem_ctx, stderr); + } + +fail: + talloc_free(e_state); + (void)poptFreeContext(pc); + exit(ret); +} diff --git a/ctdb/event/event_private.h b/ctdb/event/event_private.h new file mode 100644 index 0000000..0cc8d80 --- /dev/null +++ b/ctdb/event/event_private.h @@ -0,0 +1,103 @@ +/* + CTDB event daemon + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_PRIVATE_H__ +#define __CTDB_EVENT_PRIVATE_H__ + +#include <talloc.h> +#include <tevent.h> + +#include "common/run_event.h" +#include "common/sock_daemon.h" + +#include "event/event_protocol.h" + +struct event_config; +struct event_context; + +/* From event/event_cmd.c */ + +struct tevent_req *event_cmd_dispatch_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_request *request); +bool event_cmd_dispatch_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply); + +/* From event/event_config.c */ + +int event_config_init(TALLOC_CTX *mem_ctx, struct event_config **result); + +const char *event_config_log_location(struct event_config *config); +const char *event_config_log_level(struct event_config *config); +const char *event_config_debug_script(struct event_config *config); + +int event_config_reload(struct event_config *config); + +/* From event/event_context.c */ + +int eventd_client_add(struct event_context *eventd, + struct sock_client_context *client); +void eventd_client_del(struct event_context *eventd, + struct sock_client_context *client); +bool eventd_client_exists(struct event_context *eventd, + struct sock_client_context *client); + +int event_context_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_config *config, + struct event_context **result); + +struct event_config *eventd_config(struct event_context *eventd); +int eventd_run_ctx(struct event_context *eventd, + const char *comp_name, + struct run_event_context **result); + +int eventd_set_event_result(struct event_context *eventd, + const char *comp_name, + const char *event_name, + struct run_event_script_list *script_list); +int eventd_get_event_result(struct event_context *eventd, + const char *comp_name, + const char *event_name, + struct run_event_script_list **result); + +struct ctdb_event_script_list *eventd_script_list( + TALLOC_CTX *mem_ctx, + struct run_event_script_list *script_list); + + +/* From event/event_request.c */ + +struct tevent_req *event_pkt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + uint8_t *buf, + size_t buflen); + +bool event_pkt_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + uint8_t **buf, + size_t *buflen); + +#endif /* __CTDB_EVENT_PRIVATE_H__ */ diff --git a/ctdb/event/event_protocol.c b/ctdb/event/event_protocol.c new file mode 100644 index 0000000..baa9e1e --- /dev/null +++ b/ctdb/event/event_protocol.c @@ -0,0 +1,1123 @@ +/* + CTDB event daemon protocol + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> + +#include "protocol/protocol_basic.h" + +#include "event_protocol.h" +#include "event_protocol_api.h" + +static size_t ctdb_event_script_action_len(enum ctdb_event_script_action in) +{ + uint32_t u32 = in; + + return ctdb_uint32_len(&u32); +} + +static void ctdb_event_script_action_push(enum ctdb_event_script_action in, + uint8_t *buf, + size_t *npush) +{ + uint32_t u32 = in; + + ctdb_uint32_push(&u32, buf, npush); +} + +static int ctdb_event_script_action_pull(uint8_t *buf, + size_t buflen, + enum ctdb_event_script_action *out, + size_t *npull) +{ + enum ctdb_event_script_action value; + uint32_t u32; + size_t np; + int ret; + + ret = ctdb_uint32_pull(buf, buflen, &u32, &np); + if (ret != 0) { + return ret; + } + + switch (u32) { + case 0: + value = CTDB_EVENT_SCRIPT_DISABLE; + break; + + case 1: + value = CTDB_EVENT_SCRIPT_ENABLE; + break; + + default: + return EINVAL; + } + + *out = value; + *npull = np; + + return 0; +} + +static size_t ctdb_event_command_len(enum ctdb_event_command in) +{ + uint32_t u32 = in; + + return ctdb_uint32_len(&u32); +} + +static void ctdb_event_command_push(enum ctdb_event_command in, + uint8_t *buf, + size_t *npush) +{ + uint32_t u32 = in; + + ctdb_uint32_push(&u32, buf, npush); +} + +static int ctdb_event_command_pull(uint8_t *buf, + size_t buflen, + enum ctdb_event_command *out, + size_t *npull) +{ + enum ctdb_event_command value; + uint32_t u32; + size_t np; + int ret; + + ret = ctdb_uint32_pull(buf, buflen, &u32, &np); + if (ret != 0) { + return ret; + } + + switch (u32) { + case 1: + value = CTDB_EVENT_CMD_RUN; + break; + + case 2: + value = CTDB_EVENT_CMD_STATUS; + break; + + case 3: + value = CTDB_EVENT_CMD_SCRIPT; + break; + + default: + return EINVAL; + } + + *out = value; + *npull = np; + + return 0; +} + +static size_t ctdb_event_script_len(struct ctdb_event_script *in) +{ + return ctdb_stringn_len(&in->name) + + ctdb_timeval_len(&in->begin) + + ctdb_timeval_len(&in->end) + + ctdb_int32_len(&in->result) + + ctdb_stringn_len(&in->output); +} + +static void ctdb_event_script_push(struct ctdb_event_script *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_stringn_push(&in->name, buf+offset, &np); + offset += np; + + ctdb_timeval_push(&in->begin, buf+offset, &np); + offset += np; + + ctdb_timeval_push(&in->end, buf+offset, &np); + offset += np; + + ctdb_int32_push(&in->result, buf+offset, &np); + offset += np; + + ctdb_stringn_push(&in->output, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_script_pull_elems(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_script *value, + size_t *npull) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + mem_ctx, + &value->name, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_timeval_pull(buf+offset, + buflen-offset, + &value->begin, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_timeval_pull(buf+offset, + buflen-offset, + &value->end, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_int32_pull(buf+offset, + buflen-offset, + &value->result, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + mem_ctx, + &value->output, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + *npull = offset; + + return 0; +} + +#ifdef EVENT_PROTOCOL_TEST +static int ctdb_event_script_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_script **out, + size_t *npull) +{ + struct ctdb_event_script *value; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_script); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_event_script_pull_elems(buf, buflen, value, value, npull); + if (ret != 0) { + talloc_free(value); + return ret; + } + + *out = value; + + return 0; +} +#endif + +static size_t ctdb_event_script_list_len(struct ctdb_event_script_list *in) +{ + size_t len; + int i; + + len = ctdb_int32_len(&in->num_scripts); + + for (i=0; i<in->num_scripts; i++) { + len += ctdb_event_script_len(&in->script[i]); + } + + return len; +} + +static void ctdb_event_script_list_push(struct ctdb_event_script_list *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + int i; + + ctdb_int32_push(&in->num_scripts, buf+offset, &np); + offset += np; + + for (i=0; i<in->num_scripts; i++) { + ctdb_event_script_push(&in->script[i], buf+offset, &np); + offset += np; + } + + *npush = offset; +} + +static int ctdb_event_script_list_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_script_list **out, + size_t *npull) +{ + struct ctdb_event_script_list *value = NULL; + size_t offset = 0, np; + int num_scripts; + int ret, i; + + ret = ctdb_int32_pull(buf+offset, buflen-offset, &num_scripts, &np); + if (ret != 0) { + return ret; + } + offset += np; + + if (num_scripts < 0) { + return EINVAL; + } + + value = talloc_zero(mem_ctx, struct ctdb_event_script_list); + if (value == NULL) { + return ENOMEM; + } + + value->num_scripts = num_scripts; + if (num_scripts == 0) { + goto done; + } + + value->script = talloc_array(value, struct ctdb_event_script, + num_scripts); + if (value->script == NULL) { + ret = ENOMEM; + goto fail; + } + + for (i=0; i<num_scripts; i++) { + ret = ctdb_event_script_pull_elems(buf+offset, + buflen-offset, + value, + &value->script[i], + &np); + if (ret != 0) { + goto fail; + } + offset += np; + } + +done: + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_request_run_len(struct ctdb_event_request_run *in) +{ + return ctdb_stringn_len(&in->component) + + ctdb_stringn_len(&in->event) + + ctdb_stringn_len(&in->args) + + ctdb_uint32_len(&in->timeout) + + ctdb_uint32_len(&in->flags); +} + +static void ctdb_event_request_run_push(struct ctdb_event_request_run *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_stringn_push(&in->component, buf+offset, &np); + offset += np; + + ctdb_stringn_push(&in->event, buf+offset, &np); + offset += np; + + ctdb_stringn_push(&in->args, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->timeout, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->flags, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_request_run_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request_run **out, + size_t *npull) +{ + struct ctdb_event_request_run *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_request_run); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->component, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->event, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->args, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, + buflen-offset, + &value->timeout, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, + buflen-offset, + &value->flags, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_request_status_len( + struct ctdb_event_request_status *in) +{ + return ctdb_stringn_len(&in->component) + + ctdb_stringn_len(&in->event); +} + +static void ctdb_event_request_status_push( + struct ctdb_event_request_status *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_stringn_push(&in->component, buf+offset, &np); + offset += np; + + ctdb_stringn_push(&in->event, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_request_status_pull( + uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request_status **out, + size_t *npull) +{ + struct ctdb_event_request_status *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_request_status); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->component, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->event, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_request_script_len( + struct ctdb_event_request_script *in) +{ + return ctdb_stringn_len(&in->component) + + ctdb_stringn_len(&in->script) + + ctdb_event_script_action_len(in->action); +} + +static void ctdb_event_request_script_push( + struct ctdb_event_request_script *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_stringn_push(&in->component, buf+offset, &np); + offset += np; + + ctdb_stringn_push(&in->script, buf+offset, &np); + offset += np; + + ctdb_event_script_action_push(in->action, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_request_script_pull( + uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request_script **out, + size_t *npull) +{ + struct ctdb_event_request_script *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_request_script); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->component, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_stringn_pull(buf+offset, + buflen-offset, + value, + &value->script, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_event_script_action_pull(buf+offset, + buflen-offset, + &value->action, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_reply_status_len( + struct ctdb_event_reply_status *in) +{ + return ctdb_int32_len(&in->summary) + + ctdb_event_script_list_len(in->script_list); +} + +static void ctdb_event_reply_status_push( + struct ctdb_event_reply_status *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_int32_push(&in->summary, buf+offset, &np); + offset += np; + + ctdb_event_script_list_push(in->script_list, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_reply_status_pull( + uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply_status **out, + size_t *npull) +{ + struct ctdb_event_reply_status *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_reply_status); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_int32_pull(buf+offset, buflen-offset, &value->summary, &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_event_script_list_pull(buf+offset, + buflen-offset, + value, + &value->script_list, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_header_len(struct ctdb_event_header *in) +{ + return ctdb_uint32_len(&in->length) + + ctdb_uint32_len(&in->version) + + ctdb_uint32_len(&in->reqid); +} + +static void ctdb_event_header_push(struct ctdb_event_header *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_uint32_push(&in->length, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->version, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->reqid, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int ctdb_event_header_pull(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *value, + size_t *npull) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_uint32_pull(buf+offset, + buflen-offset, + &value->length, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, + buflen-offset, + &value->version, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, + buflen-offset, + &value->reqid, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + *npull = offset; + + return 0; +} + +int ctdb_event_header_extract(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *value) +{ + size_t np; + + return ctdb_event_header_pull(buf, buflen, value, &np); +} + +static size_t ctdb_event_request_data_len(struct ctdb_event_request *in) +{ + size_t len; + + len = ctdb_event_command_len(in->cmd); + + switch (in->cmd) { + case CTDB_EVENT_CMD_RUN: + len += ctdb_event_request_run_len(in->data.run); + break; + + case CTDB_EVENT_CMD_STATUS: + len += ctdb_event_request_status_len(in->data.status); + break; + + case CTDB_EVENT_CMD_SCRIPT: + len += ctdb_event_request_script_len(in->data.script); + break; + + default: + break; + } + + return len; +} + +static void ctdb_event_request_data_push(struct ctdb_event_request *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_event_command_push(in->cmd, buf+offset, &np); + offset += np; + + switch (in->cmd) { + case CTDB_EVENT_CMD_RUN: + ctdb_event_request_run_push(in->data.run, buf+offset, &np); + break; + + case CTDB_EVENT_CMD_STATUS: + ctdb_event_request_status_push(in->data.status, + buf+offset, + &np); + break; + + case CTDB_EVENT_CMD_SCRIPT: + ctdb_event_request_script_push(in->data.script, + buf+offset, + &np); + break; + default: + np = 0; + break; + } + offset += np; + + *npush = offset; +} + +static int ctdb_event_request_data_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request **out, + size_t *npull) +{ + struct ctdb_event_request *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_request); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_event_command_pull(buf+offset, + buflen-offset, + &value->cmd, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + switch (value->cmd) { + case CTDB_EVENT_CMD_RUN: + ret = ctdb_event_request_run_pull(buf+offset, + buflen-offset, + value, + &value->data.run, + &np); + break; + + case CTDB_EVENT_CMD_STATUS: + ret = ctdb_event_request_status_pull(buf+offset, + buflen-offset, + value, + &value->data.status, + &np); + break; + + case CTDB_EVENT_CMD_SCRIPT: + ret = ctdb_event_request_script_pull(buf+offset, + buflen-offset, + value, + &value->data.script, + &np); + break; + + default: + np = 0; + break; + } + + if (ret != 0) { + goto fail; + } + offset += np; + + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +static size_t ctdb_event_reply_data_len(struct ctdb_event_reply *in) +{ + size_t len; + + len = ctdb_event_command_len(in->cmd) + + ctdb_int32_len(&in->result); + + if (in->result != 0) { + goto done; + } + + switch (in->cmd) { + case CTDB_EVENT_CMD_STATUS: + len += ctdb_event_reply_status_len(in->data.status); + break; + + default: + break; + } + +done: + return len; +} + +static void ctdb_event_reply_data_push(struct ctdb_event_reply *in, + uint8_t *buf, + size_t *npush) +{ + size_t offset = 0, np; + + ctdb_event_command_push(in->cmd, buf+offset, &np); + offset += np; + + ctdb_int32_push(&in->result, buf+offset, &np); + offset += np; + + if (in->result != 0) { + goto done; + } + + switch (in->cmd) { + case CTDB_EVENT_CMD_STATUS: + ctdb_event_reply_status_push(in->data.status, buf+offset, &np); + break; + + default: + np = 0; + break; + } + offset += np; + +done: + *npush = offset; +} + +static int ctdb_event_reply_data_pull(uint8_t *buf, + size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **out, + size_t *npull) +{ + struct ctdb_event_reply *value; + size_t offset = 0, np; + int ret; + + value = talloc(mem_ctx, struct ctdb_event_reply); + if (value == NULL) { + return ENOMEM; + } + + ret = ctdb_event_command_pull(buf+offset, + buflen-offset, + &value->cmd, + &np); + if (ret != 0) { + goto fail; + } + offset += np; + + ret = ctdb_int32_pull(buf+offset, buflen-offset, &value->result, &np); + if (ret != 0) { + goto fail; + } + offset += np; + + if (value->result != 0) { + goto done; + } + + switch (value->cmd) { + case CTDB_EVENT_CMD_STATUS: + ret = ctdb_event_reply_status_pull(buf+offset, + buflen-offset, + value, + &value->data.status, + &np); + break; + + default: + np = 0; + break; + } + + if (ret != 0) { + goto fail; + } + offset += np; + +done: + *out = value; + *npull = offset; + + return 0; + +fail: + talloc_free(value); + return ret; +} + +size_t ctdb_event_request_len(struct ctdb_event_header *h, + struct ctdb_event_request *in) +{ + return ctdb_event_header_len(h) + + ctdb_event_request_data_len(in); +} + +int ctdb_event_request_push(struct ctdb_event_header *h, + struct ctdb_event_request *in, + uint8_t *buf, + size_t *buflen) +{ + size_t len, offset = 0, np; + + len = ctdb_event_request_len(h, in); + if (*buflen < len) { + *buflen = len; + return EMSGSIZE; + } + + h->length = *buflen; + + ctdb_event_header_push(h, buf+offset, &np); + offset += np; + + ctdb_event_request_data_push(in, buf+offset, &np); + offset += np; + + if (offset > *buflen) { + return EMSGSIZE; + } + + return 0; +} + +int ctdb_event_request_pull(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request **out) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_event_header_pull(buf+offset, buflen-offset, h, &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_event_request_data_pull(buf+offset, + buflen-offset, + mem_ctx, + out, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + if (offset > buflen) { + return EMSGSIZE; + } + + return 0; +} + +size_t ctdb_event_reply_len(struct ctdb_event_header *h, + struct ctdb_event_reply *in) +{ + return ctdb_event_header_len(h) + + ctdb_event_reply_data_len(in); +} + +int ctdb_event_reply_push(struct ctdb_event_header *h, + struct ctdb_event_reply *in, + uint8_t *buf, + size_t *buflen) +{ + size_t len, offset = 0, np; + + len = ctdb_event_reply_len(h, in); + if (*buflen < len) { + *buflen = len; + return EMSGSIZE; + } + + h->length = *buflen; + + ctdb_event_header_push(h, buf+offset, &np); + offset += np; + + ctdb_event_reply_data_push(in, buf+offset, &np); + offset += np; + + if (offset > *buflen) { + return EMSGSIZE; + } + + return 0; +} + +int ctdb_event_reply_pull(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **out) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_event_header_pull(buf+offset, buflen-offset, h, &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_event_reply_data_pull(buf+offset, + buflen-offset, + mem_ctx, + out, + &np); + if (ret != 0) { + return ret; + } + offset += np; + + if (offset > buflen) { + return EMSGSIZE; + } + + return 0; +} diff --git a/ctdb/event/event_protocol.h b/ctdb/event/event_protocol.h new file mode 100644 index 0000000..e7680fc --- /dev/null +++ b/ctdb/event/event_protocol.h @@ -0,0 +1,100 @@ +/* + CTDB event daemon protocol + Based on eventd code + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_PROTOCOL_H__ +#define __CTDB_EVENT_PROTOCOL_H__ + +#define CTDB_EVENT_PROTOCOL_VERSION 1 + +enum ctdb_event_script_action { + CTDB_EVENT_SCRIPT_DISABLE = 0, + CTDB_EVENT_SCRIPT_ENABLE = 1, +}; + +enum ctdb_event_command { + CTDB_EVENT_CMD_RUN = 1, + CTDB_EVENT_CMD_STATUS = 2, + CTDB_EVENT_CMD_SCRIPT = 3, + CTDB_EVENT_CMD_MAX = 4, +}; + +struct ctdb_event_script { + const char *name; + struct timeval begin; + struct timeval end; + int result; + const char *output; +}; + +struct ctdb_event_script_list { + int num_scripts; + struct ctdb_event_script *script; +}; + +#define CTDB_EVENT_RUN_ALL 1 + +struct ctdb_event_request_run { + const char *component; + const char *event; + const char *args; + uint32_t timeout; + uint32_t flags; +}; + +struct ctdb_event_request_status { + const char *component; + const char *event; +}; + +struct ctdb_event_request_script { + const char *component; + const char *script; + enum ctdb_event_script_action action; +}; + +struct ctdb_event_reply_status { + int32_t summary; + struct ctdb_event_script_list *script_list; +}; + +struct ctdb_event_header { + uint32_t length; + uint32_t version; + uint32_t reqid; +}; + +struct ctdb_event_request { + enum ctdb_event_command cmd; + union { + struct ctdb_event_request_run *run; + struct ctdb_event_request_status *status; + struct ctdb_event_request_script *script; + } data; +}; + +struct ctdb_event_reply { + enum ctdb_event_command cmd; + int32_t result; + union { + struct ctdb_event_reply_status *status; + } data; +}; + +#endif /* __CTDB_EVENT_PROTOCOL_H__ */ diff --git a/ctdb/event/event_protocol_api.h b/ctdb/event/event_protocol_api.h new file mode 100644 index 0000000..e2ab439 --- /dev/null +++ b/ctdb/event/event_protocol_api.h @@ -0,0 +1,61 @@ +/* + CTDB event daemon protocol + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_PROTOCOL_API_H__ +#define __CTDB_EVENT_PROTOCOL_API_H__ + +#include <talloc.h> + +#include "event/event_protocol.h" + +/* From event/event_protocol.c */ + +int ctdb_event_header_extract(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *h); + +size_t ctdb_event_request_len(struct ctdb_event_header *h, + struct ctdb_event_request *in); +int ctdb_event_request_push(struct ctdb_event_header *h, + struct ctdb_event_request *in, + uint8_t *buf, + size_t *buflen); +int ctdb_event_request_pull(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_event_request **out); + +size_t ctdb_event_reply_len(struct ctdb_event_header *h, + struct ctdb_event_reply *in); +int ctdb_event_reply_push(struct ctdb_event_header *h, + struct ctdb_event_reply *in, + uint8_t *buf, + size_t *buflen); +int ctdb_event_reply_pull(uint8_t *buf, + size_t buflen, + struct ctdb_event_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **out); + +/* From event/event_protocol_util.c */ + +const char *ctdb_event_command_to_string(enum ctdb_event_command cmd); + +#endif /* __CTDB_EVENT_PROTOCOL_API_H__ */ diff --git a/ctdb/event/event_protocol_test.c b/ctdb/event/event_protocol_test.c new file mode 100644 index 0000000..8f34fa5 --- /dev/null +++ b/ctdb/event/event_protocol_test.c @@ -0,0 +1,412 @@ +/* + CTDB event daemon - protocol test + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <assert.h> + +#define EVENT_PROTOCOL_TEST +#include "event/event_protocol.c" + +#include "tests/src/protocol_common_basic.h" + +/* + * Functions to fill and verify event protocol structures + */ + +static void fill_ctdb_event_script(TALLOC_CTX *mem_ctx, + struct ctdb_event_script *p) +{ + fill_ctdb_stringn(mem_ctx, &p->name); + fill_ctdb_timeval(&p->begin); + fill_ctdb_timeval(&p->end); + p->result = rand32i(); + fill_ctdb_stringn(mem_ctx, &p->output); +} + +static void verify_ctdb_event_script(struct ctdb_event_script *p1, + struct ctdb_event_script *p2) +{ + verify_ctdb_stringn(&p1->name, &p2->name); + verify_ctdb_timeval(&p1->begin, &p2->begin); + verify_ctdb_timeval(&p1->end, &p2->end); + assert(p1->result == p2->result); + verify_ctdb_stringn(&p1->output, &p2->output); +} + +static void fill_ctdb_event_script_list(TALLOC_CTX *mem_ctx, + struct ctdb_event_script_list *p) +{ + int i; + + p->num_scripts = rand_int(32); + if (p->num_scripts > 0) { + p->script = talloc_array(mem_ctx, + struct ctdb_event_script, + p->num_scripts); + assert(p->script != NULL); + + for (i=0; i<p->num_scripts; i++) { + fill_ctdb_event_script(mem_ctx, &p->script[i]); + } + } else { + p->script = NULL; + } +} + +static void verify_ctdb_event_script_list(struct ctdb_event_script_list *p1, + struct ctdb_event_script_list *p2) +{ + int i; + + assert(p1->num_scripts == p2->num_scripts); + for (i=0; i<p1->num_scripts; i++) { + verify_ctdb_event_script(&p1->script[i], &p2->script[i]); + } +} + +static void fill_ctdb_event_request_run(TALLOC_CTX *mem_ctx, + struct ctdb_event_request_run *p) +{ + fill_ctdb_stringn(mem_ctx, &p->component); + fill_ctdb_stringn(mem_ctx, &p->event); + fill_ctdb_stringn(mem_ctx, &p->args); + p->timeout = rand32(); + p->flags = rand32(); +} + +static void verify_ctdb_event_request_run(struct ctdb_event_request_run *p1, + struct ctdb_event_request_run *p2) +{ + verify_ctdb_stringn(&p1->component, &p2->component); + verify_ctdb_stringn(&p1->event, &p2->event); + verify_ctdb_stringn(&p1->args, &p2->args); + assert(p1->timeout == p2->timeout); + assert(p1->flags == p2->flags); +} + +static void fill_ctdb_event_request_status(TALLOC_CTX *mem_ctx, + struct ctdb_event_request_status *p) +{ + fill_ctdb_stringn(mem_ctx, &p->component); + fill_ctdb_stringn(mem_ctx, &p->event); +} + +static void verify_ctdb_event_request_status( + struct ctdb_event_request_status *p1, + struct ctdb_event_request_status *p2) +{ + verify_ctdb_stringn(&p1->component, &p2->component); + verify_ctdb_stringn(&p1->event, &p2->event); +} + +static void fill_ctdb_event_request_script(TALLOC_CTX *mem_ctx, + struct ctdb_event_request_script *p) +{ + fill_ctdb_stringn(mem_ctx, &p->component); + fill_ctdb_stringn(mem_ctx, &p->script); + if (rand_int(1) == 0) { + p->action = CTDB_EVENT_SCRIPT_DISABLE; + } else { + p->action = CTDB_EVENT_SCRIPT_ENABLE; + } +} + +static void fill_ctdb_event_reply_status(TALLOC_CTX *mem_ctx, + struct ctdb_event_reply_status *p) +{ + p->summary = rand32i(); + p->script_list = talloc(mem_ctx, struct ctdb_event_script_list); + assert(p->script_list != NULL); + + fill_ctdb_event_script_list(mem_ctx, p->script_list); +} + +static void verify_ctdb_event_reply_status(struct ctdb_event_reply_status *p1, + struct ctdb_event_reply_status *p2) +{ + assert(p1->summary == p2->summary); + verify_ctdb_event_script_list(p1->script_list, p2->script_list); +} + +static void verify_ctdb_event_request_script( + struct ctdb_event_request_script *p1, + struct ctdb_event_request_script *p2) +{ + verify_ctdb_stringn(&p1->component, &p2->component); + verify_ctdb_stringn(&p1->script, &p2->script); + assert(p1->action == p2->action); +} + +static void fill_ctdb_event_request_data(TALLOC_CTX *mem_ctx, + struct ctdb_event_request *p, + uint32_t cmd) +{ + p->cmd = cmd; + + switch (cmd) { + case CTDB_EVENT_CMD_RUN: + p->data.run = talloc(mem_ctx, struct ctdb_event_request_run); + assert(p->data.run != NULL); + + fill_ctdb_event_request_run(mem_ctx, p->data.run); + break; + + case CTDB_EVENT_CMD_STATUS: + p->data.status = talloc(mem_ctx, + struct ctdb_event_request_status); + assert(p->data.status != NULL); + + fill_ctdb_event_request_status(mem_ctx, p->data.status); + break; + + case CTDB_EVENT_CMD_SCRIPT: + p->data.script = talloc(mem_ctx, + struct ctdb_event_request_script); + assert(p->data.script != NULL); + + fill_ctdb_event_request_script(mem_ctx, p->data.script); + break; + + default: + assert(cmd > 0 && cmd < CTDB_EVENT_CMD_MAX); + } +} + +static void verify_ctdb_event_request_data(struct ctdb_event_request *p1, + struct ctdb_event_request *p2) +{ + assert(p1->cmd == p2->cmd); + + switch (p1->cmd) { + case CTDB_EVENT_CMD_RUN: + verify_ctdb_event_request_run(p1->data.run, p2->data.run); + break; + + case CTDB_EVENT_CMD_STATUS: + verify_ctdb_event_request_status(p1->data.status, + p2->data.status); + break; + + case CTDB_EVENT_CMD_SCRIPT: + verify_ctdb_event_request_script(p1->data.script, + p2->data.script); + break; + + default: + assert(p1->cmd > 0 && p1->cmd < CTDB_EVENT_CMD_MAX); + } +} + +static void fill_ctdb_event_reply_data(TALLOC_CTX *mem_ctx, + struct ctdb_event_reply *p, + uint32_t cmd) +{ + p->cmd = cmd; + p->result = rand32i(); + + if (p->result != 0) { + return; + } + + switch (cmd) { + case CTDB_EVENT_CMD_STATUS: + p->data.status = talloc(mem_ctx, + struct ctdb_event_reply_status); + assert(p->data.status != NULL); + + fill_ctdb_event_reply_status(mem_ctx, p->data.status); + break; + + default: + assert(cmd > 0 && cmd < CTDB_EVENT_CMD_MAX); + } +} + +static void verify_ctdb_event_reply_data(struct ctdb_event_reply *p1, + struct ctdb_event_reply *p2) +{ + assert(p1->cmd == p2->cmd); + assert(p1->result == p2->result); + + if (p1->result != 0) { + return; + } + + switch (p1->cmd) { + case CTDB_EVENT_CMD_STATUS: + verify_ctdb_event_reply_status(p1->data.status, + p2->data.status); + break; + + default: + assert(p1->cmd > 0 && p1->cmd < CTDB_EVENT_CMD_MAX); + } +} + +static void fill_ctdb_event_header(struct ctdb_event_header *p) +{ + p->length = 0; /* updated by push functions */ + p->version = 0; /* updated by push functions */ + p->reqid = rand32(); +} + +static void verify_ctdb_event_header(struct ctdb_event_header *p1, + struct ctdb_event_header *p2) +{ + assert(p1->length == p2->length); + assert(p1->version == p2->version); + assert(p1->reqid == p2->reqid); +} + +static void fill_ctdb_event_request(TALLOC_CTX *mem_ctx, + struct ctdb_event_request *p, + uint32_t cmd) +{ + fill_ctdb_event_request_data(mem_ctx, p, cmd); +} + +static void verify_ctdb_event_request(struct ctdb_event_request *p1, + struct ctdb_event_request *p2) +{ + verify_ctdb_event_request_data(p1, p2); +} + +static void fill_ctdb_event_reply(TALLOC_CTX *mem_ctx, + struct ctdb_event_reply *p, + uint32_t cmd) +{ + fill_ctdb_event_reply_data(mem_ctx, p, cmd); +} + +static void verify_ctdb_event_reply(struct ctdb_event_reply *p1, + struct ctdb_event_reply *p2) +{ + verify_ctdb_event_reply_data(p1, p2); +} + +#define EVENT_PROTOCOL1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint32_t cmd) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, *c2; \ + uint8_t *buf; \ + size_t buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, cmd); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, cmd); \ + buflen = LEN_FUNC(NAME)(&c1); \ + buf = talloc_size(mem_ctx, buflen); \ + assert(buf != NULL); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, buf, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(buf, buflen, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, c2); \ + talloc_free(mem_ctx); \ +} + +#define EVENT_PROTOCOL2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint32_t cmd) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_event_header h1, h2; \ + TYPE c1, *c2; \ + uint8_t *buf; \ + size_t buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, cmd); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_event_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, cmd); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + buf = talloc_size(mem_ctx, buflen); \ + assert(buf != NULL); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, buf, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, buf, &buflen); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(buf, buflen, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_event_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&c1, c2); \ + talloc_free(mem_ctx); \ +} + +PROTOCOL_TYPE3_TEST(struct ctdb_event_script, ctdb_event_script); +PROTOCOL_TYPE3_TEST(struct ctdb_event_script_list, ctdb_event_script_list); + +PROTOCOL_TYPE3_TEST(struct ctdb_event_request_run, ctdb_event_request_run); +PROTOCOL_TYPE3_TEST(struct ctdb_event_request_status, + ctdb_event_request_status); +PROTOCOL_TYPE3_TEST(struct ctdb_event_request_script, + ctdb_event_request_script); + +PROTOCOL_TYPE3_TEST(struct ctdb_event_reply_status, ctdb_event_reply_status); + +EVENT_PROTOCOL1_TEST(struct ctdb_event_request, ctdb_event_request_data); +EVENT_PROTOCOL1_TEST(struct ctdb_event_reply, ctdb_event_reply_data); + +EVENT_PROTOCOL2_TEST(struct ctdb_event_request, ctdb_event_request); +EVENT_PROTOCOL2_TEST(struct ctdb_event_reply, ctdb_event_reply); + +static void event_protocol_test(void) +{ + uint32_t cmd; + + TEST_FUNC(ctdb_event_script)(); + TEST_FUNC(ctdb_event_script_list)(); + + TEST_FUNC(ctdb_event_request_run)(); + TEST_FUNC(ctdb_event_request_status)(); + TEST_FUNC(ctdb_event_request_script)(); + + TEST_FUNC(ctdb_event_reply_status)(); + + for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) { + TEST_FUNC(ctdb_event_request_data)(cmd); + } + for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) { + TEST_FUNC(ctdb_event_reply_data)(cmd); + } + + for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) { + TEST_FUNC(ctdb_event_request)(cmd); + } + for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) { + TEST_FUNC(ctdb_event_reply)(cmd); + } +} + +int main(int argc, const char **argv) +{ + protocol_test_iterate(argc, argv, event_protocol_test); + return 0; +} diff --git a/ctdb/event/event_protocol_util.c b/ctdb/event/event_protocol_util.c new file mode 100644 index 0000000..cc59615 --- /dev/null +++ b/ctdb/event/event_protocol_util.c @@ -0,0 +1,46 @@ +/* + CTDB event daemon - protocol utilities + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include "event/event_protocol.h" +#include "event/event_protocol_api.h" + +static struct { + enum ctdb_event_command command; + const char *label; +} event_command_map[] = { + { CTDB_EVENT_CMD_RUN, "RUN" }, + { CTDB_EVENT_CMD_STATUS, "STATUS" }, + { CTDB_EVENT_CMD_SCRIPT, "SCRIPT" }, + { CTDB_EVENT_CMD_MAX, NULL }, +}; + +const char *ctdb_event_command_to_string(enum ctdb_event_command command) +{ + int i; + + for (i=0; event_command_map[i].label != NULL; i++) { + if (event_command_map[i].command == command) { + return event_command_map[i].label; + } + } + + return "UNKNOWN"; +} diff --git a/ctdb/event/event_request.c b/ctdb/event/event_request.c new file mode 100644 index 0000000..303e735 --- /dev/null +++ b/ctdb/event/event_request.c @@ -0,0 +1,217 @@ +/* + CTDB event daemon - handle requests + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "common/logging.h" + +#include "event/event_private.h" +#include "event/event_protocol_api.h" + +struct event_request_state { + struct ctdb_event_request *request; + struct ctdb_event_reply *reply; +}; + +static void event_request_done(struct tevent_req *subreq); + +static struct tevent_req *event_request_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + struct ctdb_event_header *header, + struct ctdb_event_request *request) +{ + struct tevent_req *req, *subreq; + struct event_request_state *state; + + req = tevent_req_create(mem_ctx, &state, struct event_request_state); + if (req == NULL) { + return NULL; + } + + state->request = request; + + subreq = event_cmd_dispatch_send(state, ev, eventd, request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, event_request_done, req); + + return req; +} + +static void event_request_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct event_request_state *state = tevent_req_data( + req, struct event_request_state); + int ret; + bool ok; + + ok = event_cmd_dispatch_recv(subreq, &ret, state, &state->reply); + TALLOC_FREE(subreq); + if (!ok) { + D_ERR("Command %s failed, ret=%d\n", + ctdb_event_command_to_string(state->request->cmd), ret); + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return; + } + + state->reply->cmd = state->request->cmd; + state->reply->result = EIO; + } + + tevent_req_done(req); +} + +static bool event_request_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct event_request_state *state = tevent_req_data( + req, struct event_request_state); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + *reply = talloc_steal(mem_ctx, state->reply); + + return true; +} + +struct event_pkt_state { + struct ctdb_event_header header; + struct ctdb_event_request *request; + uint8_t *buf; + size_t buflen; +}; + +static void event_pkt_done(struct tevent_req *subreq); + +struct tevent_req *event_pkt_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct event_context *eventd, + uint8_t *buf, + size_t buflen) +{ + struct tevent_req *req, *subreq; + struct event_pkt_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct event_pkt_state); + if (req == NULL) { + return NULL; + } + + ret = ctdb_event_request_pull(buf, + buflen, + &state->header, + state, + &state->request); + if (ret != 0) { + /* Ignore invalid packets */ + D_ERR("Invalid packet received, buflen=%zu\n", buflen); + tevent_req_error(req, EPROTO); + return tevent_req_post(req, ev); + } + + subreq = event_request_send(state, + ev, + eventd, + &state->header, + state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, event_pkt_done, req); + + return req; +} + +static void event_pkt_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct event_pkt_state *state = tevent_req_data( + req, struct event_pkt_state); + struct ctdb_event_header header; + struct ctdb_event_reply *reply; + int ret; + bool ok; + + ok = event_request_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + TALLOC_FREE(state->request); + if (!ok) { + tevent_req_error(req, ret); + return; + } + + header = (struct ctdb_event_header) { + .reqid = state->header.reqid, + }; + + state->buflen = ctdb_event_reply_len(&header, reply); + state->buf = talloc_zero_size(state, state->buflen); + if (tevent_req_nomem(state->buf, req)) { + talloc_free(reply); + return; + } + + ret = ctdb_event_reply_push(&header, + reply, + state->buf, + &state->buflen); + talloc_free(reply); + if (ret != 0) { + talloc_free(state->buf); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +bool event_pkt_recv(struct tevent_req *req, + int *perr, + TALLOC_CTX *mem_ctx, + uint8_t **buf, + size_t *buflen) +{ + struct event_pkt_state *state = tevent_req_data( + req, struct event_pkt_state); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + *buf = talloc_steal(mem_ctx, state->buf); + *buflen = state->buflen; + + return true; +} diff --git a/ctdb/event/event_tool.c b/ctdb/event/event_tool.c new file mode 100644 index 0000000..46dc25e --- /dev/null +++ b/ctdb/event/event_tool.c @@ -0,0 +1,792 @@ +/* + CTDB event daemon utility code + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/time.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" + +#include "common/cmdline.h" +#include "common/logging.h" +#include "common/path.h" +#include "common/event_script.h" + +#include "event/event_protocol_api.h" +#include "event/event.h" +#include "event/event_tool.h" + +struct event_tool_context { + struct cmdline_context *cmdline; + struct tevent_context *ev; + struct ctdb_event_context *eclient; +}; + +static int compact_args(TALLOC_CTX *mem_ctx, + const char **argv, + int argc, + int from, + const char **result) +{ + char *arg_str; + int i; + + if (argc <= from) { + *result = NULL; + return 0; + } + + arg_str = talloc_strdup(mem_ctx, argv[from]); + if (arg_str == NULL) { + return ENOMEM; + } + + for (i = from+1; i < argc; i++) { + arg_str = talloc_asprintf_append(arg_str, " %s", argv[i]); + if (arg_str == NULL) { + return ENOMEM; + } + } + + *result = arg_str; + return 0; +} + +static int event_command_run(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct event_tool_context *ctx = talloc_get_type_abort( + private_data, struct event_tool_context); + struct tevent_req *req; + struct ctdb_event_request_run request_run; + const char *arg_str = NULL; + const char *t; + int timeout, ret = 0, result = 0; + bool ok; + + if (argc < 3) { + cmdline_usage(ctx->cmdline, "run"); + return 1; + } + + ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient); + if (ret != 0) { + D_ERR("Failed to initialize event client, ret=%d\n", ret); + return ret; + } + + timeout = atoi(argv[0]); + if (timeout < 0) { + timeout = 0; + } + + ret = compact_args(mem_ctx, argv, argc, 3, &arg_str); + if (ret != 0) { + D_ERR("Memory allocation error\n"); + return 1; + } + + request_run.component = argv[1]; + request_run.event = argv[2]; + request_run.args = arg_str; + request_run.timeout = timeout; + request_run.flags = 0; + + t = getenv("CTDB_TEST_MODE"); + if (t != NULL) { + t = getenv("CTDB_EVENT_RUN_ALL"); + if (t != NULL) { + request_run.flags = CTDB_EVENT_RUN_ALL; + } + } + + req = ctdb_event_run_send(mem_ctx, + ctx->ev, + ctx->eclient, + &request_run); + if (req == NULL) { + D_ERR("Memory allocation error\n"); + return 1; + } + + tevent_req_poll(req, ctx->ev); + + ok = ctdb_event_run_recv(req, &ret, &result); + if (!ok) { + D_ERR("Failed to run event %s in %s, ret=%d\n", + argv[2], + argv[1], + ret); + return 1; + } + + D_NOTICE("Command run finished with result=%d\n", result); + + if (result == ENOENT) { + printf("Event dir for %s does not exist\n", argv[1]); + } else if (result == ETIMEDOUT) { + printf("Event %s in %s timed out\n", argv[2], argv[1]); + } else if (result == ECANCELED) { + printf("Event %s in %s got cancelled\n", argv[2], argv[1]); + } else if (result == ENOEXEC) { + printf("Event %s in %s failed\n", argv[2], argv[1]); + } else if (result != 0) { + printf("Failed to run event %s in %s, result=%d\n", + argv[2], + argv[1], + result); + } + + ret = (result < 0) ? -result : result; + return ret; +} + +static double timeval_delta(struct timeval *tv2, struct timeval *tv) +{ + return (tv2->tv_sec - tv->tv_sec) + + (tv2->tv_usec - tv->tv_usec) * 1.0e-6; +} + +static void print_status_one(struct ctdb_event_script *script) +{ + if (script->result == -ETIMEDOUT) { + printf("%-20s %-10s %s", + script->name, + "TIMEDOUT", + ctime(&script->begin.tv_sec)); + } else if (script->result == -ENOEXEC) { + printf("%-20s %-10s\n", script->name, "DISABLED"); + } else if (script->result < 0) { + printf("%-20s %-10s (%s)\n", + script->name, + "CANNOT RUN", + strerror(-script->result)); + } else if (script->result == 0) { + printf("%-20s %-10s %.3lf %s", + script->name, + "OK", + timeval_delta(&script->end, &script->begin), + ctime(&script->begin.tv_sec)); + } else { + printf("%-20s %-10s %.3lf %s", + script->name, + "ERROR", + timeval_delta(&script->end, &script->begin), + ctime(&script->begin.tv_sec)); + } + + if (script->result != 0 && script->result != -ENOEXEC) { + printf(" OUTPUT: %s\n", + script->output == NULL ? "" : script->output); + } +} + +static void print_status(const char *component, + const char *event, + int result, + struct ctdb_event_reply_status *status) +{ + int i; + + if (result != 0) { + if (result == ENOENT) { + printf("Event dir for %s does not exist\n", component); + } else if (result == EINVAL) { + printf("Event %s has never run in %s\n", + event, + component); + } else { + printf("Unknown error (%d) for event %s in %s\n", + result, + event, + component); + } + return; + } + + for (i=0; i<status->script_list->num_scripts; i++) { + print_status_one(&status->script_list->script[i]); + } +} + +static int event_command_status(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct event_tool_context *ctx = talloc_get_type_abort( + private_data, struct event_tool_context); + struct tevent_req *req; + struct ctdb_event_request_status request_status; + struct ctdb_event_reply_status *reply_status; + int ret = 0, result = 0; + bool ok; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "status"); + return 1; + } + + ret = ctdb_event_init(ctx, ctx->ev, &ctx->eclient); + if (ret != 0) { + D_ERR("Failed to initialize event client, ret=%d\n", ret); + return ret; + } + + request_status.component = argv[0]; + request_status.event = argv[1]; + + req = ctdb_event_status_send(mem_ctx, + ctx->ev, + ctx->eclient, + &request_status); + if (req == NULL) { + D_ERR("Memory allocation error\n"); + return 1; + } + + tevent_req_poll(req, ctx->ev); + + ok = ctdb_event_status_recv(req, + &ret, + &result, + mem_ctx, + &reply_status); + if (!ok) { + D_ERR("Failed to get status for event %s in %s, ret=%d\n", + argv[1], + argv[0], + ret); + return 1; + } + + D_NOTICE("Command status finished with result=%d\n", result); + + print_status(argv[0], argv[1], result, reply_status); + + if (reply_status == NULL) { + ret = result; + } else { + ret = reply_status->summary; + ret = (ret < 0) ? -ret : ret; + } + return ret; +} + +#define EVENT_SCRIPT_DISABLED ' ' +#define EVENT_SCRIPT_ENABLED '*' + +static int event_command_script_list(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct event_tool_context *ctx = talloc_get_type_abort( + private_data, struct event_tool_context); + char *subdir = NULL; + char *data_dir = NULL; + char *etc_dir = NULL; + char *t = NULL; + struct event_script_list *data_list = NULL; + struct event_script_list *etc_list = NULL; + unsigned int i, j, matched; + int ret = 0; + + if (argc != 1) { + cmdline_usage(ctx->cmdline, "script list"); + return 1; + } + + subdir = talloc_asprintf(mem_ctx, "events/%s", argv[0]); + if (subdir == NULL) { + return ENOMEM; + } + + data_dir = path_datadir_append(mem_ctx, subdir); + if (data_dir == NULL) { + return ENOMEM; + } + + t = talloc_size(mem_ctx, PATH_MAX); + if (t == NULL) { + return ENOMEM; + } + + data_dir = realpath(data_dir, t); + if (data_dir == NULL) { + if (errno != ENOENT) { + return errno; + } + D_ERR("Command script list finished with result=%d\n", ENOENT); + return ENOENT; + } + + etc_dir = path_etcdir_append(mem_ctx, subdir); + if (etc_dir == NULL) { + return ENOMEM; + } + + /* + * Ignore error on ENOENT for cut down (e.g. fixed/embedded) + * installs that don't use symlinks but just populate etc_dir + * directly + */ + ret = event_script_get_list(mem_ctx, data_dir, &data_list); + if (ret != 0 && ret != ENOENT) { + D_ERR("Command script list finished with result=%d\n", ret); + goto done; + } + + ret = event_script_get_list(mem_ctx, etc_dir, &etc_list); + if (ret != 0) { + D_ERR("Command script list finished with result=%d\n", ret); + goto done; + } + + D_NOTICE("Command script list finished with result=%d\n", ret); + + if (data_list == NULL) { + goto list_enabled_only; + } + + /* + * First list scripts provided by CTDB. Flag those that are + * enabled via a symlink and arrange for them to be excluded + * from the subsequent list of local scripts. + * + * Both lists are sorted, so walk the list of enabled scripts + * only once in this pass. + */ + j = 0; + matched = 0; + for (i = 0; i < data_list->num_scripts; i++) { + struct event_script *d = data_list->script[i]; + char flag = EVENT_SCRIPT_DISABLED; + char buf[PATH_MAX]; + ssize_t len; + + /* Check to see if this script is enabled */ + while (j < etc_list->num_scripts) { + struct event_script *e = etc_list->script[j]; + + ret = strcmp(e->name, d->name); + + if (ret > 0) { + /* + * Enabled name is greater, so needs + * to be considered later: done + */ + break; + } + + if (ret < 0) { + /* Enabled name is less: next */ + j++; + continue; + } + + len = readlink(e->path, buf, sizeof(buf)); + if (len == -1 || (size_t)len >= sizeof(buf)) { + /* + * Not a link? Disappeared? Invalid + * link target? Something else? + * + * Doesn't match provided script: next, done + */ + j++; + break; + } + + /* readlink() does not NUL-terminate */ + buf[len] = '\0'; + + ret = strcmp(buf, d->path); + if (ret != 0) { + /* Enabled link doesn't match: next, done */ + j++; + break; + } + + /* + * Enabled script's symlink matches our + * script: flag our script as enabled + * + * Also clear the enabled script so it can be + * trivially skipped in the next pass + */ + flag = EVENT_SCRIPT_ENABLED; + TALLOC_FREE(etc_list->script[j]); + j++; + matched++; + break; + } + + printf("%c %s\n", flag, d->name); + } + + /* Print blank line if both provided and local lists are being printed */ + if (data_list->num_scripts > 0 && matched != etc_list->num_scripts) { + printf("\n"); + } + +list_enabled_only: + + /* Now print details of local scripts, after a blank line */ + for (j = 0; j < etc_list->num_scripts; j++) { + struct event_script *e = etc_list->script[j]; + char flag = EVENT_SCRIPT_DISABLED; + + if (e == NULL) { + /* Matched in previous pass: next */ + continue; + } + + /* Script is local: if executable then flag as enabled */ + if (e->enabled) { + flag = EVENT_SCRIPT_ENABLED; + } + + printf("%c %s\n", flag, e->name); + } + + ret = 0; + +done: + talloc_free(subdir); + talloc_free(data_dir); + talloc_free(etc_dir); + talloc_free(data_list); + talloc_free(etc_list); + + return ret; +} + +static int event_command_script(TALLOC_CTX *mem_ctx, + struct event_tool_context *ctx, + const char *component, + const char *script, + bool enable) +{ + char *subdir, *etc_dir; + int result = 0; + + subdir = talloc_asprintf(mem_ctx, "events/%s", component); + if (subdir == NULL) { + return ENOMEM; + } + + etc_dir = path_etcdir_append(mem_ctx, subdir); + if (etc_dir == NULL) { + return ENOMEM; + } + + if (enable) { + result = event_script_chmod(etc_dir, script, true); + } else { + result = event_script_chmod(etc_dir, script, false); + } + + talloc_free(subdir); + talloc_free(etc_dir); + + D_NOTICE("Command script finished with result=%d\n", result); + + if (result == EINVAL) { + printf("Script %s is invalid in %s\n", script, component); + } else if (result == ENOENT) { + printf("Script %s does not exist in %s\n", script, component); + } + + return result; +} + +static int event_command_script_enable(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct event_tool_context *ctx = talloc_get_type_abort( + private_data, struct event_tool_context); + struct stat statbuf; + char *script, *etc_script; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "script enable"); + return 1; + } + + script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]); + if (script == NULL) { + return ENOMEM; + } + + etc_script = path_etcdir_append(mem_ctx, script); + if (etc_script == NULL) { + return ENOMEM; + } + + ret = lstat(etc_script, &statbuf); + if (ret == 0) { + if (S_ISLNK(statbuf.st_mode)) { + /* Link already exists */ + return 0; + } else if (S_ISREG(statbuf.st_mode)) { + return event_command_script(mem_ctx, + ctx, + argv[0], + argv[1], + true); + } + + printf("Script %s is not a file or a link\n", etc_script); + return EINVAL; + } else { + if (errno == ENOENT) { + char *t; + char *data_script; + + data_script = path_datadir_append(mem_ctx, script); + if (data_script == NULL) { + return ENOMEM; + } + + t = talloc_size(mem_ctx, PATH_MAX); + if (t == NULL) { + return ENOMEM; + } + + data_script = realpath(data_script, t); + if (data_script == NULL) { + if (errno != ENOENT) { + return errno; + } + printf("Script %s does not exist in %s\n", + argv[1], + argv[0]); + return ENOENT; + } + + ret = stat(data_script, &statbuf); + if (ret != 0) { + printf("Script %s does not exist in %s\n", + argv[1], argv[0]); + return ENOENT; + } + + ret = symlink(data_script, etc_script); + if (ret != 0) { + printf("Failed to create symlink %s\n", + etc_script); + return EIO; + } + + return 0; + } + + printf("Script %s does not exist\n", etc_script); + return EINVAL; + } +} + +static int event_command_script_disable(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct event_tool_context *ctx = talloc_get_type_abort( + private_data, struct event_tool_context); + struct stat statbuf; + char *script, *etc_script; + int ret; + + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "script disable"); + return 1; + } + + script = talloc_asprintf(mem_ctx, "events/%s/%s.script", argv[0], argv[1]); + if (script == NULL) { + return ENOMEM; + } + + etc_script = path_etcdir_append(mem_ctx, script); + if (etc_script == NULL) { + return ENOMEM; + } + + ret = lstat(etc_script, &statbuf); + if (ret == 0) { + if (S_ISLNK(statbuf.st_mode)) { + /* Link exists */ + ret = unlink(etc_script); + if (ret != 0) { + printf("Failed to remove symlink %s\n", + etc_script); + return EIO; + } + + return 0; + } else if (S_ISREG(statbuf.st_mode)) { + return event_command_script(mem_ctx, + ctx, + argv[0], + argv[1], + false); + } + + printf("Script %s is not a file or a link\n", etc_script); + return EINVAL; + } + + return 0; +} + +struct cmdline_command event_commands[] = { + { "run", event_command_run, + "Run an event", "<timeout> <component> <event> <args>" }, + { "status", event_command_status, + "Get status of an event", "<component> <event>" }, + { "script list", event_command_script_list, + "List event scripts", "<component>" }, + { "script enable", event_command_script_enable, + "Enable an event script", "<component> <script>" }, + { "script disable", event_command_script_disable, + "Disable an event script", "<component> <script>" }, + CMDLINE_TABLEEND +}; + +int event_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct event_tool_context **result) +{ + struct event_tool_context *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct event_tool_context); + if (ctx == NULL) { + D_ERR("Memory allocation error\n"); + return ENOMEM; + } + + ret = cmdline_init(mem_ctx, + prog, + options, + NULL, + event_commands, + &ctx->cmdline); + if (ret != 0) { + D_ERR("Failed to initialize cmdline, ret=%d\n", ret); + talloc_free(ctx); + return ret; + } + + ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options); + if (ret != 0) { + cmdline_usage(ctx->cmdline, NULL); + talloc_free(ctx); + return ret; + } + + *result = ctx; + return 0; +} + +int event_tool_run(struct event_tool_context *ctx, int *result) +{ + int ret; + + ctx->ev = tevent_context_init(ctx); + if (ctx->ev == NULL) { + D_ERR("Failed to initialize tevent\n"); + return ENOMEM; + } + + ret = cmdline_run(ctx->cmdline, ctx, result); + return ret; +} + +#ifdef CTDB_EVENT_TOOL + +static struct { + const char *debug; +} event_data = { + .debug = "ERROR", +}; + +struct poptOption event_options[] = { + { "debug", 'd', POPT_ARG_STRING, &event_data.debug, 0, + "debug level", "ERROR|WARNING|NOTICE|INFO|DEBUG" }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct event_tool_context *ctx; + int ret, result = 0; + int level; + bool ok; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = event_tool_init(mem_ctx, + "ctdb-event", + event_options, + argc, + argv, + true, + &ctx); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + setup_logging("ctdb-event", DEBUG_STDERR); + ok = debug_level_parse(event_data.debug, &level); + if (!ok) { + level = DEBUG_ERR; + } + debuglevel_set(level); + + ret = event_tool_run(ctx, &result); + if (ret != 0) { + exit(1); + } + + talloc_free(mem_ctx); + exit(result); +} + +#endif /* CTDB_EVENT_TOOL */ diff --git a/ctdb/event/event_tool.h b/ctdb/event/event_tool.h new file mode 100644 index 0000000..3abe716 --- /dev/null +++ b/ctdb/event/event_tool.h @@ -0,0 +1,38 @@ +/* + CTDB event daemon utility code + + Copyright (C) Amitay Isaacs 2018 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_EVENT_TOOL_H__ +#define __CTDB_EVENT_TOOL_H__ + +#include <popt.h> +#include <talloc.h> + +struct event_tool_context; + +int event_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct event_tool_context **result); + +int event_tool_run(struct event_tool_context *ctx, int *result); + +#endif /* __CTDB_EVENT_TOOL_H__ */ |