diff --git a/ctdb/packaging/RPM/ctdb.spec.in b/ctdb/packaging/RPM/ctdb.spec.in index 68eddc50beb..2a0fa0b4606 100644 --- a/ctdb/packaging/RPM/ctdb.spec.in +++ b/ctdb/packaging/RPM/ctdb.spec.in @@ -213,6 +213,7 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/ctdb_diagnostics %{_bindir}/onnode %dir %{_libexecdir}/ctdb +%{_libexecdir}/ctdb/ctdb_eventd %{_libexecdir}/ctdb/ctdb_lock_helper %{_libexecdir}/ctdb/ctdb_event_helper %{_libexecdir}/ctdb/ctdb_recovery_helper diff --git a/ctdb/server/ctdb_eventd.c b/ctdb/server/ctdb_eventd.c new file mode 100644 index 00000000000..232711ce4e2 --- /dev/null +++ b/ctdb/server/ctdb_eventd.c @@ -0,0 +1,1802 @@ +/* + Event script running daemon + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/dir.h" +#include "system/wait.h" +#include "system/locale.h" + +#include +#include +#include + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/blocking.h" +#include "lib/util/sys_rw.h" +#include "lib/util/dlinklist.h" +#include "lib/async_req/async_sock.h" + +#include "protocol/protocol_api.h" + +#include "common/comm.h" +#include "common/logging.h" +#include "common/run_proc.h" +#include "common/sock_daemon.h" + +struct pending_event { + struct pending_event *prev, *next; + + struct tevent_req *req; +}; + +struct eventd_client { + struct eventd_client *prev, *next; + + struct sock_client_context *client_ctx; + struct pending_event *pending_list; +}; + +struct eventd_context { + const char *script_dir; + const char *debug_script; + struct run_proc_context *run_ctx; + struct tevent_queue *queue; + + /* current state */ + bool running; + enum ctdb_event event; + struct tevent_req *req; + + /* result of last execution */ + int result_run; + int result_fail; + struct ctdb_script_list *status_run[CTDB_EVENT_MAX]; + struct ctdb_script_list *status_pass[CTDB_EVENT_MAX]; + struct ctdb_script_list *status_fail[CTDB_EVENT_MAX]; + + struct eventd_client *client_list; +}; + +/* + * Global state manipulation functions + */ + +static int eventd_context_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *script_dir, + const char *debug_script, + struct eventd_context **result) +{ + struct eventd_context *ectx; + int ret; + + ectx = talloc_zero(mem_ctx, struct eventd_context); + if (ectx == NULL) { + return ENOMEM; + } + + ectx->script_dir = talloc_strdup(ectx, script_dir); + if (ectx->script_dir == NULL) { + talloc_free(ectx); + return ENOMEM; + } + + if (debug_script != NULL) { + ectx->debug_script = talloc_strdup(ectx, debug_script); + if (ectx->debug_script == NULL) { + talloc_free(ectx); + return ENOMEM; + } + } + + ret = run_proc_init(ectx, ev, &ectx->run_ctx); + if (ret != 0) { + talloc_free(ectx); + return ret; + } + + ectx->queue = tevent_queue_create(ectx, "run event queue"); + if (ectx->queue == NULL) { + talloc_free(ectx); + return ENOMEM; + } + + ectx->running = false; + ectx->event = CTDB_EVENT_INIT; + + *result = ectx; + return 0; +} + +static const char *eventd_script_dir(struct eventd_context *ectx) +{ + return ectx->script_dir; +} + +static const char *eventd_debug_script(struct eventd_context *ectx) +{ + return ectx->debug_script; +} + +static struct tevent_queue *eventd_queue(struct eventd_context *ectx) +{ + return ectx->queue; +} + +static void eventd_start_running(struct eventd_context *ectx, + enum ctdb_event event, + struct tevent_req *req) +{ + ectx->running = true; + ectx->event = event; + ectx->req = req; +} + +static void eventd_stop_running(struct eventd_context *ectx) +{ + ectx->running = false; + ectx->req = NULL; +} + +static void eventd_cancel_running(struct eventd_context *ectx) +{ + if (ectx->req != NULL) { + tevent_req_error(ectx->req, ECANCELED); + } + + eventd_stop_running(ectx); +} + +static bool eventd_is_running(struct eventd_context *ectx, + enum ctdb_event *event) +{ + if (event != NULL && ectx->running) { + *event = ectx->event; + } + + return ectx->running; +} + +static struct ctdb_script_list *script_list_copy(TALLOC_CTX *mem_ctx, + struct ctdb_script_list *s) +{ + struct ctdb_script_list *s2; + + s2 = talloc_zero(mem_ctx, struct ctdb_script_list); + if (s2 == NULL) { + return NULL; + } + + s2->num_scripts = s->num_scripts; + s2->script = talloc_memdup(s2, s->script, + s->num_scripts * sizeof(struct ctdb_script)); + if (s2->script == NULL) { + talloc_free(s2); + return NULL; + } + + return s2; +} + +static void eventd_set_result(struct eventd_context *ectx, + enum ctdb_event event, + struct ctdb_script_list *script_list, + int result) +{ + struct ctdb_script_list *s; + + /* Avoid negative values, they represent -errno */ + result = (result < 0) ? -result : result; + + ectx->result_run = result; + if (script_list == NULL) { + return; + } + + TALLOC_FREE(ectx->status_run[event]); + ectx->status_run[event] = talloc_steal(ectx, script_list); + + s = script_list_copy(ectx, script_list); + if (s == NULL) { + return; + } + + if (result == 0) { + TALLOC_FREE(ectx->status_pass[event]); + ectx->status_pass[event] = s; + } else { + TALLOC_FREE(ectx->status_fail[event]); + ectx->status_fail[event] = s; + ectx->result_fail = result; + } +} + +static int eventd_get_result(struct eventd_context *ectx, + enum ctdb_event event, + enum ctdb_event_status_state state, + struct ctdb_script_list **out) +{ + struct ctdb_script_list *s = NULL; + int result = 0; + + switch (state) { + case CTDB_EVENT_LAST_RUN: + s = ectx->status_run[event]; + result = ectx->result_run; + break; + + case CTDB_EVENT_LAST_PASS: + s = ectx->status_pass[event]; + result = 0; + break; + + case CTDB_EVENT_LAST_FAIL: + s = ectx->status_fail[event]; + result = ectx->result_fail; + break; + } + + *out = s; + return result; +} + +/* + * Run debug script to dianose hung scripts + */ + +static int debug_args(TALLOC_CTX *mem_ctx, const char *path, + enum ctdb_event event, pid_t pid, const char ***out) +{ + const char **argv; + + argv = talloc_array(mem_ctx, const char *, 4); + if (argv == NULL) { + return ENOMEM; + } + + argv[0] = path; + argv[1] = talloc_asprintf(argv, "%d", pid); + argv[2] = ctdb_event_to_string(event); + if (argv[1] == NULL) { + talloc_free(argv); + return ENOMEM; + } + argv[3] = NULL; + + *out = argv; + return 0; +} + +static void debug_log(int loglevel, char *output, const char *log_prefix) +{ + char *line; + + line = strtok(output, "\n"); + while (line != NULL) { + DEBUG(loglevel, ("%s: %s\n", log_prefix, line)); + line = strtok(NULL, "\n"); + } +} + +struct run_debug_state { + pid_t pid; +}; + +static void run_debug_done(struct tevent_req *subreq); + +static struct tevent_req *run_debug_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + enum ctdb_event event, pid_t pid) +{ + struct tevent_req *req, *subreq; + struct run_debug_state *state; + const char **argv; + const char *debug_script; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct run_debug_state); + if (req == NULL) { + return NULL; + } + + state->pid = pid; + + debug_script = eventd_debug_script(ectx); + if (debug_script == NULL) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (pid == -1) { + D_DEBUG("Script terminated, nothing to debug\n"); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + ret = debug_args(state, debug_script, event, pid, &argv); + if (ret != 0) { + D_ERR("debug_args() failed\n"); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + D_DEBUG("Running debug %s with args \"%s %s\"\n", + debug_script, argv[1], argv[2]); + + subreq = run_proc_send(state, ev, ectx->run_ctx, debug_script, argv, + tevent_timeval_zero()); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, run_debug_done, req); + + talloc_free(argv); + return req; +} + +static void run_debug_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct run_debug_state *state = tevent_req_data( + req, struct run_debug_state); + char *output; + int ret; + bool status; + + status = run_proc_recv(subreq, &ret, NULL, NULL, state, &output); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("Running debug failed, ret=%d\n", ret); + } + + /* Log output */ + if (output != NULL) { + debug_log(DEBUG_ERR, output, "event_debug"); + talloc_free(output); + } + + kill(-state->pid, SIGTERM); + tevent_req_done(req); +} + +static bool run_debug_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +/* + * Utility functions for running a single event + */ + +static int script_filter(const struct dirent *de) +{ + size_t namelen = strlen(de->d_name); + char *ptr; + + /* Ignore . and .. */ + if (namelen < 3) { + return 0; + } + + /* Skip filenames with ~ */ + ptr = strchr(de->d_name, '~'); + if (ptr != NULL) { + return 0; + } + + /* Filename should start with [0-9][0-9]. */ + if ((! isdigit(de->d_name[0])) || + (! isdigit(de->d_name[1])) || + (de->d_name[2] != '.')) { + return 0; + } + + /* Ignore file names longer than MAX_SCRIPT_NAME */ + if (namelen > MAX_SCRIPT_NAME) { + return 0; + } + + return 1; +} + +static int get_script_list(TALLOC_CTX *mem_ctx, + const char *script_dir, + struct ctdb_script_list **out) +{ + struct dirent **namelist = NULL; + struct ctdb_script_list *script_list; + int count, ret; + int i; + + script_list = talloc_zero(mem_ctx, struct ctdb_script_list); + if (script_list == NULL) { + return ENOMEM; + } + + count = scandir(script_dir, &namelist, script_filter, alphasort); + if (count == -1) { + ret = errno; + if (ret == ENOENT) { + D_WARNING("event script dir %s removed\n", script_dir); + } else { + D_WARNING("scandir() failed on %s, ret=%d\n", + script_dir, ret); + } + *out = script_list; + ret = 0; + goto done; + } + + if (count == 0) { + *out = script_list; + ret = 0; + goto done; + } + + script_list->num_scripts = count; + script_list->script = talloc_zero_array(script_list, + struct ctdb_script, + count); + if (script_list->script == NULL) { + ret = ENOMEM; + talloc_free(script_list); + goto done; + } + + for (i=0; iscript[i]; + size_t len; + + len = strlcpy(s->name, namelist[i]->d_name, sizeof(s->name)); + if (len >= sizeof(s->name)) { + ret = EIO; + talloc_free(script_list); + goto done; + } + } + + *out = script_list; + ret = 0; + +done: + if (namelist != NULL && count != -1) { + for (i=0; id_name, script_name) == 0) { + + /* check for valid script names */ + ret = script_filter(de); + if (ret == 0) { + closedir(dirp); + return EINVAL; + } + + found = true; + break; + } + } + closedir(dirp); + + if (! found) { + return ENOENT; + } + + filename = talloc_asprintf(mem_ctx, "%s/%s", script_dir, script_name); + if (filename == NULL) { + return ENOMEM; + } + + ret = stat(filename, &st); + if (ret != 0) { + ret = errno; + goto done; + } + + if (enable) { + new_mode = st.st_mode | S_IXUSR; + } else { + new_mode = st.st_mode & ~(S_IXUSR | S_IXGRP | S_IXOTH); + } + + ret = chmod(filename, new_mode); + if (ret != 0) { + ret = errno; + goto done; + } + +done: + talloc_free(filename); + return ret; +} + +static int script_args(TALLOC_CTX *mem_ctx, enum ctdb_event event, + const char *arg_str, const char ***out) +{ + const char **argv; + int argc; + + argv = talloc_array(mem_ctx, const char *, 7); + if (argv == NULL) { + return ENOMEM; + } + + argv[0] = NULL; /* script name */ + argv[1] = ctdb_event_to_string(event); + argc = 2; + + if (arg_str != NULL) { + char *str, *t, *tok; + + str = talloc_strdup(argv, arg_str); + if (str == NULL) { + return ENOMEM; + } + + t = str; + while ((tok = strtok(t, " ")) != NULL) { + argv[argc] = talloc_strdup(argv, tok); + if (argv[argc] == NULL) { + talloc_free(argv); + return ENOMEM; + } + argc += 1; + if (argc >= 7) { + talloc_free(argv); + return EINVAL; + } + t = NULL; + } + + talloc_free(str); + } + + argv[argc] = NULL; + argc += 1; + + *out = argv; + return 0; +} + +/* + * Run a single event + */ + +struct run_event_state { + struct tevent_context *ev; + struct eventd_context *ectx; + struct timeval timeout; + enum ctdb_event event; + + struct ctdb_script_list *script_list; + const char **argv; + int index; + int status; +}; + +static struct tevent_req *run_event_run_script(struct tevent_req *req); +static void run_event_next_script(struct tevent_req *subreq); +static void run_event_debug(struct tevent_req *req, pid_t pid); +static void run_event_debug_done(struct tevent_req *subreq); + +static struct tevent_req *run_event_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + enum ctdb_event event, + const char *arg_str, + uint32_t timeout) +{ + struct tevent_req *req, *subreq; + struct run_event_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct run_event_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ectx = ectx; + state->event = event; + + ret = get_script_list(state, eventd_script_dir(ectx), + &state->script_list); + if (ret != 0) { + D_ERR("get_script_list() failed, ret=%d\n", ret); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + /* No scripts */ + if (state->script_list->num_scripts == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + ret = script_args(state, event, arg_str, &state->argv); + if (ret != 0) { + D_ERR("script_args() failed, ret=%d\n", ret); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + if (timeout > 0) { + state->timeout = tevent_timeval_current_ofs(timeout, 0); + } + state->index = 0; + eventd_start_running(ectx, event, req); + + subreq = run_event_run_script(req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, run_event_next_script, req); + + return req; +} + +static struct tevent_req *run_event_run_script(struct tevent_req *req) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct ctdb_script *script; + struct tevent_req *subreq; + char *path; + + script = &state->script_list->script[state->index]; + + path = talloc_asprintf(state, "%s/%s", + eventd_script_dir(state->ectx), script->name); + if (path == NULL) { + return NULL; + } + + state->argv[0] = script->name; + script->start = tevent_timeval_current(); + + D_DEBUG("Running %s with args \"%s %s\"\n", + path, state->argv[0], state->argv[1]); + + subreq = run_proc_send(state, state->ev, state->ectx->run_ctx, + path, state->argv, state->timeout); + + talloc_free(path); + + return subreq; +} + +static void run_event_next_script(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct ctdb_script *script; + char *output; + struct run_proc_result result; + pid_t pid; + int ret; + bool status; + + script = &state->script_list->script[state->index]; + script->finished = tevent_timeval_current(); + + status = run_proc_recv(subreq, &ret, &result, &pid, state, &output); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("run_proc failed for %s, ret=%d\n", script->name, ret); + tevent_req_error(req, ret); + return; + } + + D_DEBUG("Script %s finished sig=%d, err=%d, status=%d\n", + script->name, result.sig, result.err, result.status); + + if (output != NULL) { + debug_log(DEBUG_ERR, output, script->name); + } + + if (result.sig > 0) { + script->status = -EINTR; + } else if (result.err > 0) { + if (result.err == EACCES) { + /* Map EACCESS to ENOEXEC */ + script->status = -ENOEXEC; + } else { + script->status = -result.err; + } + } else { + script->status = result.status; + } + + if (script->status != 0 && output != NULL) { + size_t n; + + n = strlcpy(script->output, output, MAX_SCRIPT_OUTPUT); + if (n >= MAX_SCRIPT_OUTPUT) { + script->output[MAX_SCRIPT_OUTPUT] = '\0'; + } + } + + /* If a script fails, stop running */ + if (script->status != 0 && script->status != -ENOEXEC) { + state->status = script->status; + eventd_stop_running(state->ectx); + state->script_list->num_scripts = state->index + 1; + eventd_set_result(state->ectx, state->event, + state->script_list, state->status); + + if (state->status == -ETIME && pid != -1) { + run_event_debug(req, pid); + } + + D_ERR("%s event %s\n", ctdb_event_to_string(state->event), + (state->status == -ETIME) ? "timed out" : "failed"); + + tevent_req_done(req); + return; + } + + state->index += 1; + + /* All scripts executed */ + if (state->index >= state->script_list->num_scripts) { + eventd_stop_running(state->ectx); + eventd_set_result(state->ectx, state->event, + state->script_list, state->status); + tevent_req_done(req); + return; + } + + subreq = run_event_run_script(req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, run_event_next_script, req); +} + +static void run_event_debug(struct tevent_req *req, pid_t pid) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct tevent_req *subreq; + + /* Debug script is run with ectx as the memory context */ + subreq = run_debug_send(state->ectx, state->ev, state->ectx, + state->event, pid); + if (subreq == NULL) { + /* If run debug fails, it's not an error */ + D_NOTICE("Failed to run event debug\n"); + return; + } + tevent_req_set_callback(subreq, run_event_debug_done, NULL); +} + +static void run_event_debug_done(struct tevent_req *subreq) +{ + int ret = 0; + bool status; + + status = run_debug_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + D_NOTICE("run_debug() failed, ret=%d\n", ret); + } +} + +static bool run_event_recv(struct tevent_req *req, int *perr, int *status) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (ret == ECANCELED) { + if (status != NULL) { + *status = -ECANCELED; + } + return true; + } + + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (status != NULL) { + *status = state->status; + } + return true; +} + +/* + * Process RUN command + */ + +struct command_run_state { + struct tevent_context *ev; + struct eventd_context *ectx; + struct eventd_client *client; + + enum ctdb_event event; + uint32_t timeout; + const char *arg_str; + struct ctdb_event_reply *reply; +}; + +static void command_run_trigger(struct tevent_req *req, void *private_data); +static void command_run_done(struct tevent_req *subreq); + +static struct tevent_req *command_run_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + struct eventd_client *client, + struct ctdb_event_request *request) +{ + struct tevent_req *req; + struct command_run_state *state; + struct pending_event *pending; + enum ctdb_event running_event; + bool running, status; + + req = tevent_req_create(mem_ctx, &state, struct command_run_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ectx = ectx; + state->client = client; + + state->event = request->rdata.data.run->event; + state->timeout = request->rdata.data.run->timeout; + state->arg_str = talloc_steal(state, request->rdata.data.run->arg_str); + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.command = request->rdata.command; + + /* + * If monitor event is running, + * Cancel the running monitor event and run new event + * + * If any other event is running, + * If new event is monitor, cancel that event + * Else add new event to the queue + */ + + running = eventd_is_running(ectx, &running_event); + if (running) { + if (running_event == CTDB_EVENT_MONITOR) { + eventd_cancel_running(ectx); + } else if (state->event == CTDB_EVENT_MONITOR) { + state->reply->rdata.result = -ECANCELED; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + } + + pending = talloc_zero(state, struct pending_event); + if (tevent_req_nomem(pending, req)) { + return tevent_req_post(req, ev); + } + + pending->req = req; + DLIST_ADD(client->pending_list, pending); + + status = tevent_queue_add(eventd_queue(ectx), ev, req, + command_run_trigger, pending); + if (! status) { + tevent_req_error(req, ENOMEM); + return tevent_req_post(req, ev); + } + + return req; +} + +static void command_run_trigger(struct tevent_req *req, void *private_data) +{ + struct pending_event *pending = talloc_get_type_abort( + private_data, struct pending_event); + struct command_run_state *state = tevent_req_data( + req, struct command_run_state); + struct tevent_req *subreq; + + DLIST_REMOVE(state->client->pending_list, pending); + + if (pending->req != req) { + tevent_req_error(req, EIO); + return; + } + + talloc_free(pending); + + D_DEBUG("Running event %s with args \"%s\"\n", + ctdb_event_to_string(state->event), state->arg_str); + + subreq = run_event_send(state, state->ev, state->ectx, + state->event, state->arg_str, state->timeout); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, command_run_done, req); +} + +static void command_run_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct command_run_state *state = tevent_req_data( + req, struct command_run_state); + int ret, result; + bool status; + + status = run_event_recv(subreq, &ret, &result); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->reply->rdata.result = result; + tevent_req_done(req); +} + +static bool command_run_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct command_run_state *state = tevent_req_data( + req, struct command_run_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + return true; +} + +/* + * Process STATUS command + */ + +struct command_status_state { + struct ctdb_event_reply *reply; +}; + +static struct tevent_req *command_status_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + struct eventd_client *client, + struct ctdb_event_request *request) +{ + struct tevent_req *req; + struct command_status_state *state; + enum ctdb_event event; + enum ctdb_event_status_state estate; + + req = tevent_req_create(mem_ctx, &state, struct command_status_state); + if (req == NULL) { + return NULL; + } + + event = request->rdata.data.status->event; + estate = request->rdata.data.status->state; + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.data.status = + talloc(state->reply, struct ctdb_event_reply_status); + if (tevent_req_nomem(state->reply->rdata.data.status, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.command = request->rdata.command; + state->reply->rdata.result = 0; + state->reply->rdata.data.status->status = + eventd_get_result(ectx, event, estate, + &state->reply->rdata.data.status->script_list); + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool command_status_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct command_status_state *state = tevent_req_data( + req, struct command_status_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + return true; +} + +/* + * Process SCRIPT_LIST command + */ + +struct command_script_list_state { + struct ctdb_event_reply *reply; +}; + +static struct tevent_req *command_script_list_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + struct eventd_client *client, + struct ctdb_event_request *request) +{ + struct tevent_req *req; + struct command_script_list_state *state; + struct ctdb_script_list *script_list; + int ret, i; + + req = tevent_req_create(mem_ctx, &state, + struct command_script_list_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->rdata.data.script_list = + talloc(state->reply, struct ctdb_event_reply_script_list); + if (tevent_req_nomem(state->reply->rdata.data.script_list, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.command = request->rdata.command; + + ret = get_script_list(state, eventd_script_dir(ectx), &script_list); + if (ret != 0) { + state->reply->rdata.result = -ret; + state->reply->rdata.data.script_list->script_list = NULL; + + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + for (i=0; inum_scripts; i++) { + struct ctdb_script *script = &script_list->script[i]; + struct stat st; + char *path = NULL; + + path = talloc_asprintf(state, "%s/%s", + eventd_script_dir(ectx), script->name); + if (tevent_req_nomem(path, req)) { + continue; + } + + ret = stat(path, &st); + if (ret != 0) { + TALLOC_FREE(path); + continue; + } + + if (! (st.st_mode & S_IXUSR)) { + script->status = -ENOEXEC; + } + + TALLOC_FREE(path); + } + + state->reply->rdata.data.script_list->script_list = + talloc_steal(state->reply, script_list); + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool command_script_list_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct command_script_list_state *state = tevent_req_data( + req, struct command_script_list_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + return true; +} + +/* + * Process SCRIPT_ENABLE command + */ + +struct command_script_enable_state { + struct ctdb_event_reply *reply; +}; + +static struct tevent_req *command_script_enable_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + struct eventd_client *client, + struct ctdb_event_request *request) +{ + struct tevent_req *req; + struct command_script_enable_state *state; + const char *script_name; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct command_script_enable_state); + if (req == NULL) { + return NULL; + } + + script_name = request->rdata.data.script_enable->script_name; + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.command = request->rdata.command; + + ret = script_chmod(state, eventd_script_dir(ectx), script_name, true); + state->reply->rdata.result = -ret; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool command_script_enable_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct command_script_enable_state *state = tevent_req_data( + req, struct command_script_enable_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + return true; +} + +/* + * Process SCRIPT_DISABLE command + */ + +struct command_script_disable_state { + struct ctdb_event_reply *reply; +}; + +static struct tevent_req *command_script_disable_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct eventd_context *ectx, + struct eventd_client *client, + struct ctdb_event_request *request) +{ + struct tevent_req *req; + struct command_script_disable_state *state; + const char *script_name; + int ret; + + req = tevent_req_create(mem_ctx, &state, + struct command_script_disable_state); + if (req == NULL) { + return NULL; + } + + script_name = request->rdata.data.script_disable->script_name; + + state->reply = talloc_zero(state, struct ctdb_event_reply); + if (tevent_req_nomem(state->reply, req)) { + return tevent_req_post(req, ev); + } + + state->reply->rdata.command = request->rdata.command; + + ret = script_chmod(state, eventd_script_dir(ectx), script_name, false); + state->reply->rdata.result = -ret; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool command_script_disable_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct ctdb_event_reply **reply) +{ + struct command_script_disable_state *state = tevent_req_data( + req, struct command_script_disable_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + return true; +} + +/* + * Process clients + */ + +static struct eventd_client *client_find(struct eventd_context *ectx, + struct sock_client_context *client_ctx) +{ + struct eventd_client *client; + + for (client = ectx->client_list; + client != NULL; + client = client->next) { + if (client->client_ctx == client_ctx) { + return client; + } + } + + return NULL; +} + +static bool client_connect(struct sock_client_context *client_ctx, + void *private_data) +{ + struct eventd_context *ectx = talloc_get_type_abort( + private_data, struct eventd_context); + struct eventd_client *client; + + client = talloc_zero(ectx, struct eventd_client); + if (client == NULL) { + return false; + } + + client->client_ctx = client_ctx; + + DLIST_ADD(ectx->client_list, client); + return true; +} + +static void client_disconnect(struct sock_client_context *client_ctx, + void *private_data) +{ + struct eventd_context *ectx = talloc_get_type_abort( + private_data, struct eventd_context); + struct eventd_client *client; + struct pending_event *pe; + + client = client_find(ectx, client_ctx); + if (client == NULL) { + return; + } + + /* Get rid of pending events */ + while ((pe = client->pending_list) != NULL) { + DLIST_REMOVE(client->pending_list, pe); + talloc_free(pe->req); + } +} + +struct client_process_state { + struct tevent_context *ev; + + struct eventd_client *client; + struct ctdb_event_request request; +}; + +static void client_run_done(struct tevent_req *subreq); +static void client_status_done(struct tevent_req *subreq); +static void client_script_list_done(struct tevent_req *subreq); +static void client_script_enable_done(struct tevent_req *subreq); +static void client_script_disable_done(struct tevent_req *subreq); +static void client_process_reply(struct tevent_req *req, + struct ctdb_event_reply *reply); +static void client_process_reply_done(struct tevent_req *subreq); + +static struct tevent_req *client_process_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client_ctx, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct eventd_context *ectx = talloc_get_type_abort( + private_data, struct eventd_context); + struct tevent_req *req, *subreq; + struct client_process_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct client_process_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + + state->client = client_find(ectx, client_ctx); + if (state->client == NULL) { + tevent_req_error(req, EIO); + return tevent_req_post(req, ev); + } + + ret = ctdb_event_request_pull(buf, buflen, state, &state->request); + if (ret != 0) { + tevent_req_error(req, EPROTO); + return tevent_req_post(req, ev); + } + + switch (state->request.rdata.command) { + case CTDB_EVENT_COMMAND_RUN: + subreq = command_run_send(state, ev, ectx, state->client, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, client_run_done, req); + break; + + case CTDB_EVENT_COMMAND_STATUS: + subreq = command_status_send(state, ev, ectx, state->client, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, client_status_done, req); + break; + + case CTDB_EVENT_COMMAND_SCRIPT_LIST: + subreq = command_script_list_send(state, ev, ectx, + state->client, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, client_script_list_done, req); + break; + + case CTDB_EVENT_COMMAND_SCRIPT_ENABLE: + subreq = command_script_enable_send(state, ev, ectx, + state->client, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, client_script_enable_done, + req); + break; + + case CTDB_EVENT_COMMAND_SCRIPT_DISABLE: + subreq = command_script_disable_send(state, ev, ectx, + state->client, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, client_script_disable_done, + req); + break; + } + + return req; +} + +static void client_run_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct ctdb_event_reply *reply = NULL; + int ret = 0; + bool status; + + status = command_run_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("COMMAND_RUN failed\n"); + tevent_req_error(req, ret); + return; + } + + client_process_reply(req, reply); + talloc_free(reply); +} + +static void client_status_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct ctdb_event_reply *reply = NULL; + int ret = 0; + bool status; + + status = command_status_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("COMMAND_STATUS failed\n"); + tevent_req_error(req, ret); + return; + } + + client_process_reply(req, reply); + talloc_free(reply); +} + +static void client_script_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct ctdb_event_reply *reply = NULL; + int ret = 0; + bool status; + + status = command_script_list_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("COMMAND_SCRIPT_LIST failed\n"); + tevent_req_error(req, ret); + return; + } + + client_process_reply(req, reply); + talloc_free(reply); +} + +static void client_script_enable_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct ctdb_event_reply *reply = NULL; + int ret = 0; + bool status; + + status = command_script_enable_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("COMMAND_SCRIPT_ENABLE failed\n"); + tevent_req_error(req, ret); + return; + } + + client_process_reply(req, reply); + talloc_free(reply); +} + +static void client_script_disable_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct ctdb_event_reply *reply = NULL; + int ret = 0; + bool status; + + status = command_script_disable_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("COMMAND_SCRIPT_DISABLE failed\n"); + tevent_req_error(req, ret); + return; + } + + client_process_reply(req, reply); + talloc_free(reply); +} + +static void client_process_reply(struct tevent_req *req, + struct ctdb_event_reply *reply) +{ + struct client_process_state *state = tevent_req_data( + req, struct client_process_state); + struct tevent_req *subreq; + uint8_t *buf; + size_t buflen; + int ret; + + ctdb_event_header_fill(&reply->header, state->request.header.reqid); + + buflen = ctdb_event_reply_len(reply); + buf = talloc_zero_size(state, buflen); + if (tevent_req_nomem(buf, req)) { + return; + } + + ret = ctdb_event_reply_push(reply, buf, &buflen); + if (ret != 0) { + talloc_free(buf); + tevent_req_error(req, ret); + return; + } + + subreq = sock_socket_write_send(state, state->ev, + state->client->client_ctx, + buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_process_reply_done, req); +} + +static void client_process_reply_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("Sending reply failed\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool client_process_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +/* + * Event daemon + */ + +static void eventd_shutdown(void *private_data) +{ + struct eventd_context *ectx = talloc_get_type_abort( + private_data, struct eventd_context); + struct eventd_client *client; + + while ((client = ectx->client_list) != NULL) { + DLIST_REMOVE(ectx->client_list, client); + talloc_free(client); + } +} + +static struct { + const char *debug_script; + const char *script_dir; + const char *logging; + const char *debug_level; + const char *pidfile; + const char *socket; + int pid; +} options = { + .debug_level = "ERR", +}; + +struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "debug_script", 'D', POPT_ARG_STRING, &options.debug_script, 0, + "debug script", "FILE" }, + { "pid", 'P', POPT_ARG_INT, &options.pid, 0, + "pid to wait for", "PID" }, + { "event_script_dir", 'e', POPT_ARG_STRING, &options.script_dir, 0, + "event script dir", "DIRECTORY" }, + { "logging", 'l', POPT_ARG_STRING, &options.logging, 0, + "logging specification" }, + { "debug", 'd', POPT_ARG_STRING, &options.debug_level, 0, + "debug level" }, + { "pidfile", 'p', POPT_ARG_STRING, &options.pidfile, 0, + "eventd pid file", "FILE" }, + { "socket", 's', POPT_ARG_STRING, &options.socket, 0, + "eventd socket path", "FILE" }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + poptContext pc; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct eventd_context *ectx; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs daemon_funcs; + struct sock_socket_funcs socket_funcs; + struct stat statbuf; + int opt, ret; + + /* Set default options */ + options.pid = -1; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid options %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + exit(1); + } + + if (options.socket == NULL) { + fprintf(stderr, "Please specify eventd socket (--socket)\n"); + exit(1); + } + + if (options.script_dir == NULL) { + fprintf(stderr, + "Please specify script dir (--event_script_dir)\n"); + exit(1); + } + + if (options.logging == NULL) { + fprintf(stderr, + "Please specify logging (--logging)\n"); + exit(1); + } + + ret = stat(options.script_dir, &statbuf); + if (ret != 0) { + ret = errno; + fprintf(stderr, "Error reading script_dir %s, ret=%d\n", + options.script_dir, ret); + exit(1); + } + if (! S_ISDIR(statbuf.st_mode)) { + fprintf(stderr, "script_dir %s is not a directory\n", + options.script_dir); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + ret = 1; + goto fail; + } + + ret = eventd_context_init(mem_ctx, ev, options.script_dir, + options.debug_script, &ectx); + if (ret != 0) { + goto fail; + } + + daemon_funcs = (struct sock_daemon_funcs) { + .shutdown = eventd_shutdown, + }; + + ret = sock_daemon_setup(mem_ctx, "ctdb-eventd", options.logging, + options.debug_level, options.pidfile, + &daemon_funcs, ectx, &sockd); + if (ret != 0) { + goto fail; + } + + socket_funcs = (struct sock_socket_funcs) { + .connect = client_connect, + .disconnect = client_disconnect, + .read_send = client_process_send, + .read_recv = client_process_recv, + }; + + ret = sock_daemon_add_unix(sockd, options.socket, &socket_funcs, ectx); + if (ret != 0) { + goto fail; + } + + ret = sock_daemon_run(ev, sockd, options.pid); + if (ret == EINTR) { + ret = 0; + } + +fail: + talloc_free(mem_ctx); + (void)poptFreeContext(pc); + exit(ret); +} diff --git a/ctdb/wscript b/ctdb/wscript index 979c653a654..8ce43a955b2 100644 --- a/ctdb/wscript +++ b/ctdb/wscript @@ -487,6 +487,12 @@ def build(bld): install_path='${BINDIR}', manpages='ltdbtool.1') + bld.SAMBA_BINARY('ctdb_eventd', + source='server/ctdb_eventd.c', + deps='''ctdb-server-util ctdb-protocol ctdb-util + samba-util LIBASYNC_REQ replace popt''', + install_path='${CTDB_HELPER_BINDIR}') + bld.SAMBA_BINARY('ctdb_lock_helper', source='server/ctdb_lock_helper.c', deps='samba-util sys_rw ctdb-system talloc tdb',