diff options
Diffstat (limited to 'ctdb/server/eventscript.c')
-rw-r--r-- | ctdb/server/eventscript.c | 484 |
1 files changed, 269 insertions, 215 deletions
diff --git a/ctdb/server/eventscript.c b/ctdb/server/eventscript.c index 3514c321825..b61c26dbbb1 100644 --- a/ctdb/server/eventscript.c +++ b/ctdb/server/eventscript.c @@ -32,6 +32,19 @@ static struct { const char *script_running; } child_state; +static const char *call_names[] = { + "startup", + "startrecovery", + "recovered", + "takeip", + "releaseip", + "stopped", + "monitor", + "status", + "shutdown", + "reload" +}; + static void ctdb_event_script_timeout(struct event_context *ev, struct timed_event *te, struct timeval t, void *p); /* @@ -61,11 +74,13 @@ static void sigterm(int sig) struct ctdb_event_script_state { struct ctdb_context *ctdb; pid_t child; + /* Warning: this can free us! */ void (*callback)(struct ctdb_context *, int, void *); + int cb_status; int fd[2]; void *private_data; + enum ctdb_eventscript_call call; const char *options; - struct timed_event *te; struct timeval timeout; }; @@ -81,28 +96,22 @@ struct ctdb_monitor_script_status { char *output; }; -struct ctdb_monitor_status { - struct timeval start; - struct timeval finished; - int32_t status; +struct ctdb_monitor_script_status_ctx { struct ctdb_monitor_script_status *scripts; - struct ctdb_event_script_state *state; }; - /* called from ctdb_logging when we have received output on STDERR from * one of the eventscripts */ int ctdb_log_event_script_output(struct ctdb_context *ctdb, char *str, uint16_t len) { - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; struct ctdb_monitor_script_status *script; - if (monitoring_status == NULL) { + if (ctdb->current_monitor_status_ctx == NULL) { return -1; } - script = monitoring_status->scripts; + script = ctdb->current_monitor_status_ctx->scripts; if (script == NULL) { return -1; } @@ -121,17 +130,13 @@ int ctdb_log_event_script_output(struct ctdb_context *ctdb, char *str, uint16_t */ int32_t ctdb_control_event_script_init(struct ctdb_context *ctdb) { - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; - DEBUG(DEBUG_INFO, ("event script init called\n")); - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR,(__location__ " Init called when context is NULL\n")); - return 0; + if (ctdb->current_monitor_status_ctx == NULL) { + DEBUG(DEBUG_ERR,(__location__ " current_monitor_status_ctx is NULL when initing script\n")); + return -1; } - monitoring_status->start = timeval_current(); - return 0; } @@ -142,41 +147,26 @@ int32_t ctdb_control_event_script_init(struct ctdb_context *ctdb) int32_t ctdb_control_event_script_start(struct ctdb_context *ctdb, TDB_DATA indata) { const char *name = (const char *)indata.dptr; - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; - struct ctdb_event_script_state *state; struct ctdb_monitor_script_status *script; DEBUG(DEBUG_INFO, ("event script start called : %s\n", name)); - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR,(__location__ " script_status is NULL when starting to run script %s\n", name)); + if (ctdb->current_monitor_status_ctx == NULL) { + DEBUG(DEBUG_ERR,(__location__ " current_monitor_status_ctx is NULL when starting script\n")); return -1; } - script = talloc_zero(monitoring_status, struct ctdb_monitor_script_status); + script = talloc_zero(ctdb->current_monitor_status_ctx, struct ctdb_monitor_script_status); if (script == NULL) { DEBUG(DEBUG_ERR,(__location__ " Failed to talloc ctdb_monitor_script_status for script %s\n", name)); return -1; } - script->next = monitoring_status->scripts; + script->next = ctdb->current_monitor_status_ctx->scripts; script->name = talloc_strdup(script, name); CTDB_NO_MEMORY(ctdb, script->name); script->start = timeval_current(); - monitoring_status->scripts = script; - - state = monitoring_status->state; - if (state != NULL) { - /* reset the timeout for the next eventscript */ - if (!timeval_is_zero(&state->timeout)) { - if (state->te != NULL) { - talloc_free(state->te); - state->te = NULL; - } - state->te = event_add_timed(ctdb->ev, state, timeval_current_ofs(state->timeout.tv_sec, state->timeout.tv_usec), ctdb_event_script_timeout, state); - } - - } + ctdb->current_monitor_status_ctx->scripts = script; return 0; } @@ -187,15 +177,14 @@ int32_t ctdb_control_event_script_start(struct ctdb_context *ctdb, TDB_DATA inda int32_t ctdb_control_event_script_stop(struct ctdb_context *ctdb, TDB_DATA indata) { int32_t res = *((int32_t *)indata.dptr); - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; struct ctdb_monitor_script_status *script; - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR,(__location__ " script_status is NULL when script finished.\n")); + if (ctdb->current_monitor_status_ctx == NULL) { + DEBUG(DEBUG_ERR,(__location__ " current_monitor_status_ctx is NULL when script finished\n")); return -1; } - script = monitoring_status->scripts; + script = ctdb->current_monitor_status_ctx->scripts; if (script == NULL) { DEBUG(DEBUG_ERR,(__location__ " script is NULL when the script had finished\n")); return -1; @@ -214,17 +203,16 @@ int32_t ctdb_control_event_script_stop(struct ctdb_context *ctdb, TDB_DATA indat int32_t ctdb_control_event_script_disabled(struct ctdb_context *ctdb, TDB_DATA indata) { const char *name = (const char *)indata.dptr; - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; struct ctdb_monitor_script_status *script; DEBUG(DEBUG_INFO, ("event script disabed called for script %s\n", name)); - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR,(__location__ " script_status is NULL when script finished.\n")); + if (ctdb->current_monitor_status_ctx == NULL) { + DEBUG(DEBUG_ERR,(__location__ " current_monitor_status_ctx is NULL when script finished\n")); return -1; } - script = monitoring_status->scripts; + script = ctdb->current_monitor_status_ctx->scripts; if (script == NULL) { DEBUG(DEBUG_ERR,(__location__ " script is NULL when the script had finished\n")); return -1; @@ -242,24 +230,19 @@ int32_t ctdb_control_event_script_disabled(struct ctdb_context *ctdb, TDB_DATA i */ int32_t ctdb_control_event_script_finished(struct ctdb_context *ctdb) { - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; - DEBUG(DEBUG_INFO, ("event script finished called\n")); - if (monitoring_status == NULL) { + if (ctdb->current_monitor_status_ctx == NULL) { DEBUG(DEBUG_ERR,(__location__ " script_status is NULL when monitoring event finished\n")); return -1; } - monitoring_status->finished = timeval_current(); - monitoring_status->status = MONITOR_SCRIPT_OK; - - if (ctdb->last_monitor_ctx) { - talloc_free(ctdb->last_monitor_ctx); - ctdb->last_monitor_ctx = NULL; + if (ctdb->last_monitor_status_ctx) { + talloc_free(ctdb->last_monitor_status_ctx); + ctdb->last_monitor_status_ctx = NULL; } - ctdb->last_monitor_ctx = talloc_steal(ctdb, ctdb->script_monitor_ctx); - ctdb->script_monitor_ctx = NULL; + ctdb->last_monitor_status_ctx = ctdb->current_monitor_status_ctx; + ctdb->current_monitor_status_ctx = NULL; return 0; } @@ -303,11 +286,11 @@ static struct ctdb_monitoring_wire *marshall_monitoring_scripts(TALLOC_CTX *mem_ int32_t ctdb_control_get_event_script_status(struct ctdb_context *ctdb, TDB_DATA *outdata) { - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->last_monitor_ctx; + struct ctdb_monitor_script_status_ctx *script_status = talloc_get_type(ctdb->last_monitor_status_ctx, struct ctdb_monitor_script_status_ctx); struct ctdb_monitoring_wire *monitoring_scripts; - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR,(__location__ " last_monitor_ctx is NULL when reading status\n")); + if (script_status == NULL) { + DEBUG(DEBUG_ERR,(__location__ " last_monitor_status_ctx is NULL when reading status\n")); return -1; } @@ -318,7 +301,7 @@ int32_t ctdb_control_get_event_script_status(struct ctdb_context *ctdb, TDB_DATA } monitoring_scripts->num_scripts = 0; - monitoring_scripts = marshall_monitoring_scripts(outdata, monitoring_scripts, monitoring_status->scripts); + monitoring_scripts = marshall_monitoring_scripts(outdata, monitoring_scripts, script_status->scripts); if (monitoring_scripts == NULL) { DEBUG(DEBUG_ERR,(__location__ " Monitoring scritps is NULL. can not return data to client\n")); return -1; @@ -474,23 +457,21 @@ static struct ctdb_script_list *ctdb_get_script_list(struct ctdb_context *ctdb, /* - run the event script - varargs version + Actually run the event script this function is called and run in the context of a forked child which allows it to do blocking calls such as system() */ -static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) +static int ctdb_run_event_script(struct ctdb_context *ctdb, + bool from_user, + enum ctdb_eventscript_call call, + const char *options) { char *cmdstr; int ret; TALLOC_CTX *tmp_ctx = talloc_new(ctdb); struct ctdb_script_list *scripts, *current; - int is_monitor = 0; - if (!strcmp(options, "monitor")) { - is_monitor = 1; - } - - if (is_monitor == 1) { + if (!from_user && call == CTDB_EVENT_MONITOR) { /* This is running in the forked child process. At this stage * we want to switch from being a ctdb daemon into being a * client and connect to the real local daemon. @@ -510,14 +491,15 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) { /* we guarantee that only some specifically allowed event scripts are run while in recovery */ - const char *allowed_scripts[] = {"startrecovery", "shutdown", "releaseip", "stopped" }; + const enum ctdb_eventscript_call allowed_calls[] = { + CTDB_EVENT_START_RECOVERY, CTDB_EVENT_SHUTDOWN, CTDB_EVENT_RELEASE_IP, CTDB_EVENT_STOPPED }; int i; - for (i=0;i<ARRAY_SIZE(allowed_scripts);i++) { - if (strncmp(options, allowed_scripts[i], strlen(allowed_scripts[i])) == 0) break; + for (i=0;i<ARRAY_SIZE(allowed_calls);i++) { + if (call == allowed_calls[i]) break; } - if (i == ARRAY_SIZE(allowed_scripts)) { - DEBUG(DEBUG_ERR,("Refusing to run event scripts with option '%s' while in recovery\n", - options)); + if (i == ARRAY_SIZE(allowed_calls)) { + DEBUG(DEBUG_ERR,("Refusing to run event scripts call '%s' while in recovery\n", + call_names[call])); talloc_free(tmp_ctx); return -1; } @@ -541,10 +523,26 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) them */ for (current=scripts; current; current=current->next) { - /* we dont run disabled scripts, we just report they are disabled */ - cmdstr = talloc_asprintf(tmp_ctx, "%s/%s %s", - ctdb->event_script_dir, - current->name, options); + const char *str = from_user ? "CTDB_CALLED_BY_USER=1 " : ""; + + /* Allow a setting where we run the actual monitor event + from an external source and replace it with + a "status" event that just picks up the actual + status of the event asynchronously. + */ + if ((ctdb->tunable.use_status_events_for_monitoring != 0) + && (call == CTDB_EVENT_MONITOR) + && !from_user) { + cmdstr = talloc_asprintf(tmp_ctx, "%s%s/%s %s", + str, + ctdb->event_script_dir, + current->name, "status"); + } else { + cmdstr = talloc_asprintf(tmp_ctx, "%s%s/%s %s %s", + str, + ctdb->event_script_dir, + current->name, call_names[call], options); + } CTDB_NO_MEMORY(ctdb, cmdstr); DEBUG(DEBUG_INFO,("Executing event script %s\n",cmdstr)); @@ -552,7 +550,7 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) child_state.start = timeval_current(); child_state.script_running = cmdstr; - if (is_monitor == 1) { + if (!from_user && call == CTDB_EVENT_MONITOR) { if (ctdb_ctrl_event_script_start(ctdb, current->name) != 0) { DEBUG(DEBUG_ERR,(__location__ " Failed to start event script monitoring\n")); talloc_free(tmp_ctx); @@ -585,7 +583,7 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) DEBUG(DEBUG_ERR,("Script %s returned status 127. Someone just deleted it?\n", cmdstr)); } - if (is_monitor == 1) { + if (!from_user && call == CTDB_EVENT_MONITOR) { if (ctdb_ctrl_event_script_stop(ctdb, ret) != 0) { DEBUG(DEBUG_ERR,(__location__ " Failed to stop event script monitoring\n")); talloc_free(tmp_ctx); @@ -596,7 +594,7 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) /* return an error if the script failed */ if (ret != 0) { DEBUG(DEBUG_ERR,("Event script %s failed with error %d\n", cmdstr, ret)); - if (is_monitor == 1) { + if (!from_user && call == CTDB_EVENT_MONITOR) { if (ctdb_ctrl_event_script_finished(ctdb) != 0) { DEBUG(DEBUG_ERR,(__location__ " Failed to finish event script monitoring\n")); talloc_free(tmp_ctx); @@ -612,7 +610,7 @@ static int ctdb_event_script_v(struct ctdb_context *ctdb, const char *options) child_state.start = timeval_current(); child_state.script_running = "finished"; - if (is_monitor == 1) { + if (!from_user && call == CTDB_EVENT_MONITOR) { if (ctdb_ctrl_event_script_finished(ctdb) != 0) { DEBUG(DEBUG_ERR,(__location__ " Failed to finish event script monitoring\n")); talloc_free(tmp_ctx); @@ -631,20 +629,18 @@ static void ctdb_event_script_handler(struct event_context *ev, struct fd_event struct ctdb_event_script_state *state = talloc_get_type(p, struct ctdb_event_script_state); struct ctdb_context *ctdb = state->ctdb; - signed char rt = -1; - read(state->fd[0], &rt, sizeof(rt)); - - DEBUG(DEBUG_INFO,(__location__ " Eventscript %s finished with state %d\n", state->options, rt)); - - if (state->callback) { - state->callback(ctdb, rt, state->private_data); - state->callback = NULL; + if (read(state->fd[0], &state->cb_status, sizeof(state->cb_status)) != + sizeof(state->cb_status)) { + state->cb_status = -2; } - talloc_set_destructor(state, NULL); - talloc_free(state); + DEBUG(DEBUG_INFO,(__location__ " Eventscript %s %s finished with state %d\n", + call_names[state->call], state->options, state->cb_status)); + + state->child = 0; ctdb->event_script_timeouts = 0; + talloc_free(state); } static void ctdb_ban_self(struct ctdb_context *ctdb, uint32_t ban_period) @@ -667,29 +663,19 @@ static void ctdb_event_script_timeout(struct event_context *ev, struct timed_eve struct timeval t, void *p) { struct ctdb_event_script_state *state = talloc_get_type(p, struct ctdb_event_script_state); - void *private_data = state->private_data; struct ctdb_context *ctdb = state->ctdb; - char *options; - struct ctdb_monitor_status *monitoring_status = (struct ctdb_monitor_status *)ctdb->script_monitor_ctx; - state->te = NULL; + DEBUG(DEBUG_ERR,("Event script timed out : %s %s count : %u pid : %d\n", + call_names[state->call], state->options, ctdb->event_script_timeouts, state->child)); - DEBUG(DEBUG_ERR,("Event script timed out : %s count : %u pid : %d\n", state->options, ctdb->event_script_timeouts, state->child)); if (kill(state->child, 0) != 0) { DEBUG(DEBUG_ERR,("Event script child process already dead, errno %s(%d)\n", strerror(errno), errno)); - if (state->callback) { - state->callback(ctdb, 0, private_data); - state->callback = NULL; - } - talloc_set_destructor(state, NULL); + state->child = 0; talloc_free(state); return; } - options = talloc_strdup(ctdb, state->options); - CTDB_NO_MEMORY_VOID(ctdb, options); - - if (!strcmp(options, "monitor")) { + if (state->call == CTDB_EVENT_MONITOR) { /* if it is a monitor event, we allow it to "hang" a few times before we declare it a failure and ban ourself (and make ourself unhealthy) @@ -697,135 +683,180 @@ static void ctdb_event_script_timeout(struct event_context *ev, struct timed_eve DEBUG(DEBUG_ERR, (__location__ " eventscript for monitor event timedout.\n")); ctdb->event_script_timeouts++; + if (ctdb->event_script_timeouts > ctdb->tunable.script_ban_count) { - if (ctdb->tunable.script_unhealthy_on_timeout != 0) { - DEBUG(DEBUG_ERR, ("Maximum timeout count %u reached for eventscript. Making node unhealthy\n", ctdb->tunable.script_ban_count)); - if (state->callback) { - state->callback(ctdb, -ETIME, private_data); - state->callback = NULL; - } - } else { - ctdb->event_script_timeouts = 0; - DEBUG(DEBUG_ERR, ("Maximum timeout count %u reached for eventscript. Banning self for %d seconds\n", ctdb->tunable.script_ban_count, ctdb->tunable.recovery_ban_period)); - ctdb_ban_self(ctdb, ctdb->tunable.recovery_ban_period); - if (state->callback) { - state->callback(ctdb, -1, private_data); - state->callback = NULL; - } - } + DEBUG(DEBUG_ERR, ("Maximum timeout count %u reached for eventscript. Making node unhealthy\n", ctdb->tunable.script_ban_count)); + state->cb_status = -ETIME; } else { - if (state->callback) { - state->callback(ctdb, 0, private_data); - state->callback = NULL; - } + state->cb_status = 0; } - } else if (!strcmp(options, "startup")) { + } else if (state->call == CTDB_EVENT_STARTUP) { DEBUG(DEBUG_ERR, (__location__ " eventscript for startup event timedout.\n")); - if (state->callback) { - state->callback(ctdb, -1, private_data); - state->callback = NULL; - } + state->cb_status = -1; } else { - /* if it is not a monitor event we ban ourself immediately */ + /* if it is not a monitor or a startup event we ban ourself + immediately + */ DEBUG(DEBUG_ERR, (__location__ " eventscript for NON-monitor/NON-startup event timedout. Immediately banning ourself for %d seconds\n", ctdb->tunable.recovery_ban_period)); + ctdb_ban_self(ctdb, ctdb->tunable.recovery_ban_period); - if (state->callback) { - state->callback(ctdb, -1, private_data); - state->callback = NULL; - } + + state->cb_status = -1; } - if ((!strcmp(options, "monitor")) && (monitoring_status != NULL)) { + if (state->call == CTDB_EVENT_MONITOR || state->call == CTDB_EVENT_STATUS) { struct ctdb_monitor_script_status *script; - script = monitoring_status->scripts; + if (ctdb->current_monitor_status_ctx == NULL) { + talloc_free(state); + return; + } + + script = ctdb->current_monitor_status_ctx->scripts; if (script != NULL) { script->timedout = 1; } - monitoring_status->status = MONITOR_SCRIPT_TIMEOUT; - if (ctdb->last_monitor_ctx) { - talloc_free(ctdb->last_monitor_ctx); - ctdb->last_monitor_ctx = NULL; + + if (ctdb->last_monitor_status_ctx) { + talloc_free(ctdb->last_monitor_status_ctx); + ctdb->last_monitor_status_ctx = NULL; } - ctdb->last_monitor_ctx = talloc_steal(ctdb, ctdb->script_monitor_ctx); - ctdb->script_monitor_ctx = NULL; + ctdb->last_monitor_status_ctx = talloc_steal(ctdb, ctdb->current_monitor_status_ctx); + ctdb->current_monitor_status_ctx = NULL; } talloc_free(state); - talloc_free(options); } /* - destroy a running event script + destroy an event script: kill it if ->child != 0. */ static int event_script_destructor(struct ctdb_event_script_state *state) { - DEBUG(DEBUG_ERR,(__location__ " Sending SIGTERM to child pid:%d\n", state->child)); + if (state->child) { + DEBUG(DEBUG_ERR,(__location__ " Sending SIGTERM to child pid:%d\n", state->child)); - if (state->callback) { - state->callback(state->ctdb, 0, state->private_data); - state->callback = NULL; + if (kill(state->child, SIGTERM) != 0) { + DEBUG(DEBUG_ERR,("Failed to kill child process for eventscript, errno %s(%d)\n", strerror(errno), errno)); + } } - if (kill(state->child, SIGTERM) != 0) { - DEBUG(DEBUG_ERR,("Failed to kill child process for eventscript, errno %s(%d)\n", strerror(errno), errno)); + /* This is allowed to free us; talloc will prevent double free anyway, + * but beware if you call this outside the destructor! */ + if (state->callback) { + state->callback(state->ctdb, state->cb_status, state->private_data); } return 0; } +static unsigned int count_words(const char *options) +{ + unsigned int words = 0; + + options += strspn(options, " \t"); + while (*options) { + words++; + options += strcspn(options, " \t"); + options += strspn(options, " \t"); + } + return words; +} + +static bool check_options(enum ctdb_eventscript_call call, const char *options) +{ + switch (call) { + /* These all take no arguments. */ + case CTDB_EVENT_STARTUP: + case CTDB_EVENT_START_RECOVERY: + case CTDB_EVENT_RECOVERED: + case CTDB_EVENT_STOPPED: + case CTDB_EVENT_MONITOR: + case CTDB_EVENT_STATUS: + case CTDB_EVENT_SHUTDOWN: + case CTDB_EVENT_RELOAD: + return count_words(options) == 0; + + case CTDB_EVENT_TAKE_IP: /* interface, IP address, netmask bits. */ + case CTDB_EVENT_RELEASE_IP: + return count_words(options) == 3; + + default: + DEBUG(DEBUG_ERR,(__location__ "Unknown ctdb_eventscript_call %u\n", call)); + return false; + } +} + /* run the event script in the background, calling the callback when finished */ static int ctdb_event_script_callback_v(struct ctdb_context *ctdb, - struct timeval timeout, void (*callback)(struct ctdb_context *, int, void *), void *private_data, + bool from_user, + enum ctdb_eventscript_call call, const char *fmt, va_list ap) { - struct ctdb_monitor_status *monitoring_status; + TALLOC_CTX *mem_ctx; struct ctdb_event_script_state *state; int ret; - if (!strcmp(fmt, "monitor")) { - if (ctdb->script_monitor_ctx != NULL) { - talloc_free(ctdb->script_monitor_ctx); - ctdb->script_monitor_ctx = NULL; + if (!from_user && (call == CTDB_EVENT_MONITOR || call == CTDB_EVENT_STATUS)) { + /* if this was a "monitor" or a status event, we recycle the + context to start a new monitor event + */ + if (ctdb->monitor_event_script_ctx != NULL) { + talloc_free(ctdb->monitor_event_script_ctx); + ctdb->monitor_event_script_ctx = NULL; } - monitoring_status = talloc_zero(ctdb, struct ctdb_monitor_status); - } else { - if (ctdb->event_script_ctx == NULL) { - ctdb->event_script_ctx = talloc_zero(ctdb, struct ctdb_monitor_status); + ctdb->monitor_event_script_ctx = talloc_new(ctdb); + mem_ctx = ctdb->monitor_event_script_ctx; + + if (ctdb->current_monitor_status_ctx != NULL) { + talloc_free(ctdb->current_monitor_status_ctx); + ctdb->current_monitor_status_ctx = NULL; } - monitoring_status = ctdb->event_script_ctx; - } - if (monitoring_status == NULL) { - DEBUG(DEBUG_ERR, (__location__ " ERROR: Failed to talloc script_monitoring context\n")); - return -1; + ctdb->current_monitor_status_ctx = talloc(ctdb, struct ctdb_monitor_script_status_ctx); + CTDB_NO_MEMORY(ctdb, ctdb->current_monitor_status_ctx); + ctdb->current_monitor_status_ctx->scripts = NULL; + } else { + /* any other script will first terminate any monitor event */ + if (ctdb->monitor_event_script_ctx != NULL) { + talloc_free(ctdb->monitor_event_script_ctx); + ctdb->monitor_event_script_ctx = NULL; + } + /* and then use a context common for all non-monitor events */ + if (ctdb->other_event_script_ctx == NULL) { + ctdb->other_event_script_ctx = talloc_new(ctdb); + } + mem_ctx = ctdb->other_event_script_ctx; } - state = talloc(monitoring_status, struct ctdb_event_script_state); - if (state == NULL) { - DEBUG(DEBUG_ERR,(__location__ " could not allocate state\n")); - return -1; - } - monitoring_status->state = state; + state = talloc(mem_ctx, struct ctdb_event_script_state); + CTDB_NO_MEMORY(ctdb, state); state->ctdb = ctdb; state->callback = callback; state->private_data = private_data; + state->call = call; state->options = talloc_vasprintf(state, fmt, ap); - state->timeout = timeout; - state->te = NULL; + state->timeout = timeval_set(ctdb->tunable.script_timeout, 0); if (state->options == NULL) { DEBUG(DEBUG_ERR, (__location__ " could not allocate state->options\n")); talloc_free(state); return -1; } + if (!check_options(state->call, state->options)) { + DEBUG(DEBUG_ERR, ("Bad eventscript options '%s' for %s\n", + call_names[state->call], state->options)); + talloc_free(state); + return -1; + } - DEBUG(DEBUG_INFO,(__location__ " Starting eventscript %s\n", state->options)); + DEBUG(DEBUG_INFO,(__location__ " Starting eventscript %s %s\n", + call_names[state->call], state->options)); ret = pipe(state->fd); if (ret != 0) { @@ -843,28 +874,22 @@ static int ctdb_event_script_callback_v(struct ctdb_context *ctdb, } if (state->child == 0) { - signed char rt; + int rt; close(state->fd[0]); set_close_on_exec(state->fd[1]); - rt = ctdb_event_script_v(ctdb, state->options); - while ((ret = write(state->fd[1], &rt, sizeof(rt))) != sizeof(rt)) { - write(state->fd[1], &rt, sizeof(rt)); - usleep(100000); - } + rt = ctdb_run_event_script(ctdb, from_user, state->call, state->options); + /* We must be able to write PIPEBUF bytes at least; if this + somehow fails, the read above will be short. */ + write(state->fd[1], &rt, sizeof(rt)); + close(state->fd[1]); _exit(rt); } - talloc_set_destructor(state, event_script_destructor); - if (!strcmp(fmt, "monitor")) { - ctdb->script_monitor_ctx = monitoring_status; - } else { - ctdb->event_script_ctx = monitoring_status; - } - close(state->fd[1]); set_close_on_exec(state->fd[0]); + talloc_set_destructor(state, event_script_destructor); DEBUG(DEBUG_DEBUG, (__location__ " Created PIPE FD:%d to child eventscript process\n", state->fd[0])); @@ -872,9 +897,10 @@ static int ctdb_event_script_callback_v(struct ctdb_context *ctdb, ctdb_event_script_handler, state); if (!timeval_is_zero(&state->timeout)) { - state->te = event_add_timed(ctdb->ev, state, timeval_current_ofs(state->timeout.tv_sec, state->timeout.tv_usec), ctdb_event_script_timeout, state); + event_add_timed(ctdb->ev, state, timeval_current_ofs(state->timeout.tv_sec, state->timeout.tv_usec), ctdb_event_script_timeout, state); } else { - DEBUG(DEBUG_ERR, (__location__ " eventscript %s called with no timeout\n", state->options)); + DEBUG(DEBUG_ERR, (__location__ " eventscript %s %s called with no timeout\n", + call_names[state->call], state->options)); } return 0; @@ -886,17 +912,18 @@ static int ctdb_event_script_callback_v(struct ctdb_context *ctdb, finished */ int ctdb_event_script_callback(struct ctdb_context *ctdb, - struct timeval timeout, TALLOC_CTX *mem_ctx, void (*callback)(struct ctdb_context *, int, void *), void *private_data, + bool from_user, + enum ctdb_eventscript_call call, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); - ret = ctdb_event_script_callback_v(ctdb, timeout, callback, private_data, fmt, ap); + ret = ctdb_event_script_callback_v(ctdb, callback, private_data, from_user, call, fmt, ap); va_end(ap); return ret; @@ -919,24 +946,23 @@ static void event_script_callback(struct ctdb_context *ctdb, int status, void *p } /* - run the event script, waiting for it to complete. Used when the caller doesn't want to - continue till the event script has finished. + run the event script, waiting for it to complete. Used when the caller + doesn't want to continue till the event script has finished. */ -int ctdb_event_script(struct ctdb_context *ctdb, const char *fmt, ...) +int ctdb_event_script_args(struct ctdb_context *ctdb, enum ctdb_eventscript_call call, + const char *fmt, ...) { va_list ap; int ret; struct callback_status status; va_start(ap, fmt); - ret = ctdb_event_script_callback_v(ctdb, - timeval_set(ctdb->tunable.script_timeout, 0), - event_script_callback, &status, fmt, ap); - va_end(ap); - + ret = ctdb_event_script_callback_v(ctdb, + event_script_callback, &status, false, call, fmt, ap); if (ret != 0) { return ret; } + va_end(ap); status.status = -1; status.done = false; @@ -946,6 +972,11 @@ int ctdb_event_script(struct ctdb_context *ctdb, const char *fmt, ...) return status.status; } +int ctdb_event_script(struct ctdb_context *ctdb, enum ctdb_eventscript_call call) +{ + /* GCC complains about empty format string, so use %s and "". */ + return ctdb_event_script_args(ctdb, call, "%s", ""); +} struct eventscript_callback_state { struct ctdb_req_control *c; @@ -964,17 +995,36 @@ static void run_eventscripts_callback(struct ctdb_context *ctdb, int status, if (status != 0) { DEBUG(DEBUG_ERR,(__location__ " Failed to forcibly run eventscripts\n")); - ctdb_request_control_reply(ctdb, state->c, NULL, status, NULL); - talloc_free(state); - return; } - /* the control succeeded */ - ctdb_request_control_reply(ctdb, state->c, NULL, 0, NULL); + ctdb_request_control_reply(ctdb, state->c, NULL, status, NULL); + /* This will free the struct ctdb_event_script_state we are in! */ talloc_free(state); return; } + +/* Returns rest of string, or NULL if no match. */ +static const char *get_call(const char *p, enum ctdb_eventscript_call *call) +{ + unsigned int len; + + /* Skip any initial whitespace. */ + p += strspn(p, " \t"); + + /* See if we match any. */ + for (*call = 0; *call < ARRAY_SIZE(call_names); (*call)++) { + len = strlen(call_names[*call]); + if (strncmp(p, call_names[*call], len) == 0) { + /* If end of string or whitespace, we're done. */ + if (strcspn(p + len, " \t") == 0) { + return p + len; + } + } + } + return NULL; +} + /* A control to force running of the eventscripts from the ctdb client tool */ @@ -984,29 +1034,33 @@ int32_t ctdb_run_eventscripts(struct ctdb_context *ctdb, { int ret; struct eventscript_callback_state *state; + const char *options; + enum ctdb_eventscript_call call; + + /* Figure out what call they want. */ + options = get_call((const char *)indata.dptr, &call); + if (!options) { + DEBUG(DEBUG_ERR, (__location__ " Invalid forced \"%s\"\n", (const char *)indata.dptr)); + return -1; + } - if (ctdb->event_script_ctx == NULL) { - ctdb->event_script_ctx = talloc_zero(ctdb, struct ctdb_monitor_status); + if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) { + DEBUG(DEBUG_ERR, (__location__ " Aborted running eventscript \"%s\" while in RECOVERY mode\n", indata.dptr)); + return -1; } - state = talloc(ctdb->event_script_ctx, struct eventscript_callback_state); + state = talloc(ctdb->other_event_script_ctx, struct eventscript_callback_state); CTDB_NO_MEMORY(ctdb, state); state->c = talloc_steal(state, c); DEBUG(DEBUG_NOTICE,("Forced running of eventscripts with arguments %s\n", indata.dptr)); - if (ctdb->recovery_mode != CTDB_RECOVERY_NORMAL) { - DEBUG(DEBUG_ERR, (__location__ " Aborted running eventscript \"%s\" while in RECOVERY mode\n", indata.dptr)); - return -1; - } - ctdb_disable_monitoring(ctdb); ret = ctdb_event_script_callback(ctdb, - timeval_set(ctdb->tunable.script_timeout, 0), state, run_eventscripts_callback, state, - "%s", (const char *)indata.dptr); + true, call, "%s", options); if (ret != 0) { ctdb_enable_monitoring(ctdb); |