diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
commit | e5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch) | |
tree | a6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/fencing | |
parent | Initial commit. (diff) | |
download | pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip |
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/fencing')
-rw-r--r-- | lib/fencing/Makefile.am | 28 | ||||
-rw-r--r-- | lib/fencing/fencing_private.h | 51 | ||||
-rw-r--r-- | lib/fencing/st_actions.c | 710 | ||||
-rw-r--r-- | lib/fencing/st_client.c | 2702 | ||||
-rw-r--r-- | lib/fencing/st_lha.c | 279 | ||||
-rw-r--r-- | lib/fencing/st_output.c | 600 | ||||
-rw-r--r-- | lib/fencing/st_rhcs.c | 316 |
7 files changed, 4686 insertions, 0 deletions
diff --git a/lib/fencing/Makefile.am b/lib/fencing/Makefile.am new file mode 100644 index 0000000..a72b7d6 --- /dev/null +++ b/lib/fencing/Makefile.am @@ -0,0 +1,28 @@ +# +# Original Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright 2004 International Business Machines +# +# with later changes copyright 2004-2022 the Pacemaker project contributors. +# The version control history for this file may have further details. +# +# This source code is licensed under the GNU General Public License version 2 +# or later (GPLv2+) WITHOUT ANY WARRANTY. +# +include $(top_srcdir)/mk/common.mk + +noinst_HEADERS = fencing_private.h + +lib_LTLIBRARIES = libstonithd.la + +libstonithd_la_LDFLAGS = -version-info 34:3:8 + +libstonithd_la_CFLAGS = $(CFLAGS_HARDENED_LIB) +libstonithd_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) + +libstonithd_la_LIBADD = $(top_builddir)/lib/common/libcrmcommon.la +libstonithd_la_LIBADD += $(top_builddir)/lib/services/libcrmservice.la + +libstonithd_la_SOURCES = st_actions.c st_client.c st_output.c st_rhcs.c +if BUILD_LHA_SUPPORT +libstonithd_la_SOURCES += st_lha.c +endif diff --git a/lib/fencing/fencing_private.h b/lib/fencing/fencing_private.h new file mode 100644 index 0000000..506e7f7 --- /dev/null +++ b/lib/fencing/fencing_private.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018-2021 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef PCMK__FENCING_PRIVATE__H +# define PCMK__FENCING_PRIVATE__H + +G_GNUC_INTERNAL +int stonith__execute(stonith_action_t *action); + +// Utilities from st_rhcs.c + +G_GNUC_INTERNAL +int stonith__list_rhcs_agents(stonith_key_value_t **devices); + +G_GNUC_INTERNAL +int stonith__rhcs_metadata(const char *agent, int timeout, char **output); + +G_GNUC_INTERNAL +bool stonith__agent_is_rhcs(const char *agent); + +G_GNUC_INTERNAL +int stonith__rhcs_validate(stonith_t *st, int call_options, const char *target, + const char *agent, GHashTable *params, + const char *host_arg, int timeout, + char **output, char **error_output); + +#ifdef HAVE_STONITH_STONITH_H +// Utilities from st_lha.c + +G_GNUC_INTERNAL +int stonith__list_lha_agents(stonith_key_value_t **devices); + +G_GNUC_INTERNAL +int stonith__lha_metadata(const char *agent, int timeout, char **output); + +G_GNUC_INTERNAL +bool stonith__agent_is_lha(const char *agent); + +G_GNUC_INTERNAL +int stonith__lha_validate(stonith_t *st, int call_options, const char *target, + const char *agent, GHashTable *params, + int timeout, char **output, char **error_output); +#endif + +#endif // PCMK__FENCING_PRIVATE__H diff --git a/lib/fencing/st_actions.c b/lib/fencing/st_actions.c new file mode 100644 index 0000000..b81015e --- /dev/null +++ b/lib/fencing/st_actions.c @@ -0,0 +1,710 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <libgen.h> +#include <inttypes.h> +#include <sys/types.h> +#include <glib.h> + +#include <crm/crm.h> +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> +#include <crm/msg_xml.h> +#include <crm/services_internal.h> + +#include "fencing_private.h" + +struct stonith_action_s { + /*! user defined data */ + char *agent; + char *action; + GHashTable *args; + int timeout; + bool async; + void *userdata; + void (*done_cb) (int pid, const pcmk__action_result_t *result, + void *user_data); + void (*fork_cb) (int pid, void *user_data); + + svc_action_t *svc_action; + + /*! internal timing information */ + time_t initial_start_time; + int tries; + int remaining_timeout; + int max_retries; + + int pid; + pcmk__action_result_t result; +}; + +static int internal_stonith_action_execute(stonith_action_t *action); +static void log_action(stonith_action_t *action, pid_t pid); + +/*! + * \internal + * \brief Set an action's result based on services library result + * + * \param[in,out] action Fence action to set result for + * \param[in,out] svc_action Service action to get result from + */ +static void +set_result_from_svc_action(stonith_action_t *action, svc_action_t *svc_action) +{ + pcmk__set_result(&(action->result), svc_action->rc, svc_action->status, + services__exit_reason(svc_action)); + pcmk__set_result_output(&(action->result), + services__grab_stdout(svc_action), + services__grab_stderr(svc_action)); +} + +static void +log_action(stonith_action_t *action, pid_t pid) +{ + /* The services library has already logged the output at info or debug + * level, so just raise to warning for stderr. + */ + if (action->result.action_stderr != NULL) { + /* Logging the whole string confuses syslog when the string is xml */ + char *prefix = crm_strdup_printf("%s[%d] stderr:", action->agent, pid); + + crm_log_output(LOG_WARNING, prefix, action->result.action_stderr); + free(prefix); + } +} + +static void +append_config_arg(gpointer key, gpointer value, gpointer user_data) +{ + /* The fencer will filter "action" out when it registers the device, + * but ignore it here in case any external API users don't. + * + * Also filter out parameters handled directly by Pacemaker. + */ + if (!pcmk__str_eq(key, STONITH_ATTR_ACTION_OP, pcmk__str_casei) + && !pcmk_stonith_param(key) + && (strstr(key, CRM_META) == NULL) + && !pcmk__str_eq(key, "crm_feature_set", pcmk__str_casei)) { + + crm_trace("Passing %s=%s with fence action", + (const char *) key, (const char *) (value? value : "")); + g_hash_table_insert((GHashTable *) user_data, + strdup(key), strdup(value? value : "")); + } +} + +/*! + * \internal + * \brief Create a table of arguments for a fencing action + * + * \param[in] agent Fencing agent name + * \param[in] action Name of fencing action + * \param[in] target Name of target node for fencing action + * \param[in] target_nodeid Node ID of target node for fencing action + * \param[in] device_args Fence device parameters + * \param[in] port_map Target node-to-port mapping for fence device + * \param[in] host_arg Argument name for passing target + * + * \return Newly created hash table of arguments for fencing action + */ +static GHashTable * +make_args(const char *agent, const char *action, const char *target, + uint32_t target_nodeid, GHashTable *device_args, + GHashTable *port_map, const char *host_arg) +{ + GHashTable *arg_list = NULL; + const char *value = NULL; + + CRM_CHECK(action != NULL, return NULL); + + arg_list = pcmk__strkey_table(free, free); + + // Add action to arguments (using an alias if requested) + if (device_args) { + char buffer[512]; + + snprintf(buffer, sizeof(buffer), "pcmk_%s_action", action); + value = g_hash_table_lookup(device_args, buffer); + if (value) { + crm_debug("Substituting '%s' for fence action %s targeting %s", + value, action, pcmk__s(target, "no node")); + action = value; + } + } + g_hash_table_insert(arg_list, strdup(STONITH_ATTR_ACTION_OP), + strdup(action)); + + /* If this is a fencing operation against another node, add more standard + * arguments. + */ + if ((target != NULL) && (device_args != NULL)) { + const char *param = NULL; + + /* Always pass the target's name, per + * https://github.com/ClusterLabs/fence-agents/blob/main/doc/FenceAgentAPI.md + */ + g_hash_table_insert(arg_list, strdup("nodename"), strdup(target)); + + // If the target's node ID was specified, pass it, too + if (target_nodeid != 0) { + char *nodeid = crm_strdup_printf("%" PRIu32, target_nodeid); + + // cts-fencing looks for this log message + crm_info("Passing '%s' as nodeid with fence action '%s' targeting %s", + nodeid, action, pcmk__s(target, "no node")); + g_hash_table_insert(arg_list, strdup("nodeid"), nodeid); + } + + // Check whether target must be specified in some other way + param = g_hash_table_lookup(device_args, PCMK_STONITH_HOST_ARGUMENT); + if (!pcmk__str_eq(agent, "fence_legacy", pcmk__str_none) + && !pcmk__str_eq(param, PCMK__VALUE_NONE, pcmk__str_casei)) { + + if (param == NULL) { + /* Use the caller's default for pcmk_host_argument, or "port" if + * none was given + */ + param = (host_arg == NULL)? "port" : host_arg; + } + value = g_hash_table_lookup(device_args, param); + + if (pcmk__str_eq(value, "dynamic", + pcmk__str_casei|pcmk__str_null_matches)) { + /* If the host argument was "dynamic" or not explicitly specified, + * add it with the target + */ + const char *alias = NULL; + + if (port_map) { + alias = g_hash_table_lookup(port_map, target); + } + if (alias == NULL) { + alias = target; + } + crm_debug("Passing %s='%s' with fence action %s targeting %s", + param, alias, action, pcmk__s(target, "no node")); + g_hash_table_insert(arg_list, strdup(param), strdup(alias)); + } + } + } + + if (device_args) { + g_hash_table_foreach(device_args, append_config_arg, arg_list); + } + + return arg_list; +} + +/*! + * \internal + * \brief Free all memory used by a stonith action + * + * \param[in,out] action Action to free + */ +void +stonith__destroy_action(stonith_action_t *action) +{ + if (action) { + free(action->agent); + if (action->args) { + g_hash_table_destroy(action->args); + } + free(action->action); + if (action->svc_action) { + services_action_free(action->svc_action); + } + pcmk__reset_result(&(action->result)); + free(action); + } +} + +/*! + * \internal + * \brief Get the result of an executed stonith action + * + * \param[in] action Executed action + * + * \return Pointer to action's result (or NULL if \p action is NULL) + */ +pcmk__action_result_t * +stonith__action_result(stonith_action_t *action) +{ + return (action == NULL)? NULL : &(action->result); +} + +#define FAILURE_MAX_RETRIES 2 + +/*! + * \internal + * \brief Create a new fencing action to be executed + * + * \param[in] agent Fence agent to use + * \param[in] action_name Fencing action to be executed + * \param[in] target Name of target of fencing action (if known) + * \param[in] target_nodeid Node ID of target of fencing action (if known) + * \param[in] timeout_sec Timeout to be used when executing action + * \param[in] device_args Parameters to pass to fence agent + * \param[in] port_map Mapping of target names to device ports + * \param[in] host_arg Agent parameter used to pass target name + * + * \return Newly created fencing action (asserts on error, never NULL) + */ +stonith_action_t * +stonith__action_create(const char *agent, const char *action_name, + const char *target, uint32_t target_nodeid, + int timeout_sec, GHashTable *device_args, + GHashTable *port_map, const char *host_arg) +{ + stonith_action_t *action = calloc(1, sizeof(stonith_action_t)); + + CRM_ASSERT(action != NULL); + + action->args = make_args(agent, action_name, target, target_nodeid, + device_args, port_map, host_arg); + crm_debug("Preparing '%s' action targeting %s using agent %s", + action_name, pcmk__s(target, "no node"), agent); + action->agent = strdup(agent); + action->action = strdup(action_name); + action->timeout = action->remaining_timeout = timeout_sec; + action->max_retries = FAILURE_MAX_RETRIES; + + pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN, + "Initialization bug in fencing library"); + + if (device_args) { + char buffer[512]; + const char *value = NULL; + + snprintf(buffer, sizeof(buffer), "pcmk_%s_retries", action_name); + value = g_hash_table_lookup(device_args, buffer); + + if (value) { + action->max_retries = atoi(value); + } + } + + return action; +} + +static gboolean +update_remaining_timeout(stonith_action_t * action) +{ + int diff = time(NULL) - action->initial_start_time; + + if (action->tries >= action->max_retries) { + crm_info("Attempted to execute agent %s (%s) the maximum number of times (%d) allowed", + action->agent, action->action, action->max_retries); + action->remaining_timeout = 0; + } else if ((action->result.execution_status != PCMK_EXEC_TIMEOUT) + && (diff < (action->timeout * 0.7))) { + /* only set remaining timeout period if there is 30% + * or greater of the original timeout period left */ + action->remaining_timeout = action->timeout - diff; + } else { + action->remaining_timeout = 0; + } + return action->remaining_timeout ? TRUE : FALSE; +} + +/*! + * \internal + * \brief Map a fencing action result to a standard return code + * + * \param[in] result Fencing action result to map + * + * \return Standard Pacemaker return code that best corresponds to \p result + */ +int +stonith__result2rc(const pcmk__action_result_t *result) +{ + if (pcmk__result_ok(result)) { + return pcmk_rc_ok; + } + + switch (result->execution_status) { + case PCMK_EXEC_PENDING: return EINPROGRESS; + case PCMK_EXEC_CANCELLED: return ECANCELED; + case PCMK_EXEC_TIMEOUT: return ETIME; + case PCMK_EXEC_NOT_INSTALLED: return ENOENT; + case PCMK_EXEC_NOT_SUPPORTED: return EOPNOTSUPP; + case PCMK_EXEC_NOT_CONNECTED: return ENOTCONN; + case PCMK_EXEC_NO_FENCE_DEVICE: return ENODEV; + case PCMK_EXEC_NO_SECRETS: return EACCES; + + /* For the fencing API, PCMK_EXEC_INVALID is used with fencer API + * operations that don't involve executing an agent (for example, + * registering devices). This allows us to use the CRM_EX_* codes in the + * exit status for finer-grained responses. + */ + case PCMK_EXEC_INVALID: + switch (result->exit_status) { + case CRM_EX_INVALID_PARAM: return EINVAL; + case CRM_EX_INSUFFICIENT_PRIV: return EACCES; + case CRM_EX_PROTOCOL: return EPROTO; + + /* CRM_EX_EXPIRED is used for orphaned fencing operations left + * over from a previous instance of the fencer. For API backward + * compatibility, this is mapped to the previously used code for + * this case, EHOSTUNREACH. + */ + case CRM_EX_EXPIRED: return EHOSTUNREACH; + default: break; + } + break; + + default: + break; + } + + // Try to provide useful error code based on result's error output + + if (result->action_stderr == NULL) { + return ENODATA; + + } else if (strcasestr(result->action_stderr, "timed out") + || strcasestr(result->action_stderr, "timeout")) { + return ETIME; + + } else if (strcasestr(result->action_stderr, "unrecognised action") + || strcasestr(result->action_stderr, "unrecognized action") + || strcasestr(result->action_stderr, "unsupported action")) { + return EOPNOTSUPP; + } + + // Oh well, we tried + return pcmk_rc_error; +} + +/*! + * \internal + * \brief Determine execution status equivalent of legacy fencer return code + * + * Fence action notifications, and fence action callbacks from older fencers + * (<=2.1.2) in a rolling upgrade, will have only a legacy return code. Map this + * to an execution status as best as possible (essentially, the inverse of + * stonith__result2rc()). + * + * \param[in] rc Legacy return code from fencer + * + * \return Execution status best corresponding to \p rc + */ +int +stonith__legacy2status(int rc) +{ + if (rc >= 0) { + return PCMK_EXEC_DONE; + } + switch (-rc) { + case EACCES: return PCMK_EXEC_NO_SECRETS; + case ECANCELED: return PCMK_EXEC_CANCELLED; + case EHOSTUNREACH: return PCMK_EXEC_INVALID; + case EINPROGRESS: return PCMK_EXEC_PENDING; + case ENODEV: return PCMK_EXEC_NO_FENCE_DEVICE; + case ENOENT: return PCMK_EXEC_NOT_INSTALLED; + case ENOTCONN: return PCMK_EXEC_NOT_CONNECTED; + case EOPNOTSUPP: return PCMK_EXEC_NOT_SUPPORTED; + case EPROTO: return PCMK_EXEC_INVALID; + case EPROTONOSUPPORT: return PCMK_EXEC_NOT_SUPPORTED; + case ETIME: return PCMK_EXEC_TIMEOUT; + case ETIMEDOUT: return PCMK_EXEC_TIMEOUT; + default: return PCMK_EXEC_ERROR; + } +} + +/*! + * \internal + * \brief Add a fencing result to an XML element as attributes + * + * \param[in,out] xml XML element to add result to + * \param[in] result Fencing result to add (assume success if NULL) + */ +void +stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result) +{ + int exit_status = CRM_EX_OK; + enum pcmk_exec_status execution_status = PCMK_EXEC_DONE; + const char *exit_reason = NULL; + const char *action_stdout = NULL; + int rc = pcmk_ok; + + CRM_CHECK(xml != NULL, return); + + if (result != NULL) { + exit_status = result->exit_status; + execution_status = result->execution_status; + exit_reason = result->exit_reason; + action_stdout = result->action_stdout; + rc = pcmk_rc2legacy(stonith__result2rc(result)); + } + + crm_xml_add_int(xml, XML_LRM_ATTR_OPSTATUS, (int) execution_status); + crm_xml_add_int(xml, XML_LRM_ATTR_RC, exit_status); + crm_xml_add(xml, XML_LRM_ATTR_EXIT_REASON, exit_reason); + crm_xml_add(xml, F_STONITH_OUTPUT, action_stdout); + + /* @COMPAT Peers in rolling upgrades, Pacemaker Remote nodes, and external + * code that use libstonithd <=2.1.2 don't check for the full result, and + * need a legacy return code instead. + */ + crm_xml_add_int(xml, F_STONITH_RC, rc); +} + +/*! + * \internal + * \brief Find a fencing result beneath an XML element + * + * \param[in] xml XML element to search + * + * \return \p xml or descendant of it that contains a fencing result, else NULL + */ +xmlNode * +stonith__find_xe_with_result(xmlNode *xml) +{ + xmlNode *match = get_xpath_object("//@" XML_LRM_ATTR_RC, xml, LOG_NEVER); + + if (match == NULL) { + /* @COMPAT Peers <=2.1.2 in a rolling upgrade provide only a legacy + * return code, not a full result, so check for that. + */ + match = get_xpath_object("//@" F_STONITH_RC, xml, LOG_ERR); + } + return match; +} + +/*! + * \internal + * \brief Get a fencing result from an XML element's attributes + * + * \param[in] xml XML element with fencing result + * \param[out] result Where to store fencing result + */ +void +stonith__xe_get_result(const xmlNode *xml, pcmk__action_result_t *result) +{ + int exit_status = CRM_EX_OK; + int execution_status = PCMK_EXEC_DONE; + const char *exit_reason = NULL; + char *action_stdout = NULL; + + CRM_CHECK((xml != NULL) && (result != NULL), return); + + exit_reason = crm_element_value(xml, XML_LRM_ATTR_EXIT_REASON); + action_stdout = crm_element_value_copy(xml, F_STONITH_OUTPUT); + + // A result must include an exit status and execution status + if ((crm_element_value_int(xml, XML_LRM_ATTR_RC, &exit_status) < 0) + || (crm_element_value_int(xml, XML_LRM_ATTR_OPSTATUS, + &execution_status) < 0)) { + int rc = pcmk_ok; + exit_status = CRM_EX_ERROR; + + /* @COMPAT Peers <=2.1.2 in rolling upgrades provide only a legacy + * return code, not a full result, so check for that. + */ + if (crm_element_value_int(xml, F_STONITH_RC, &rc) == 0) { + if ((rc == pcmk_ok) || (rc == -EINPROGRESS)) { + exit_status = CRM_EX_OK; + } + execution_status = stonith__legacy2status(rc); + exit_reason = pcmk_strerror(rc); + + } else { + execution_status = PCMK_EXEC_ERROR; + exit_reason = "Fencer reply contained neither a full result " + "nor a legacy return code (bug?)"; + } + } + pcmk__set_result(result, exit_status, execution_status, exit_reason); + pcmk__set_result_output(result, action_stdout, NULL); +} + +static void +stonith_action_async_done(svc_action_t *svc_action) +{ + stonith_action_t *action = (stonith_action_t *) svc_action->cb_data; + + set_result_from_svc_action(action, svc_action); + svc_action->params = NULL; + log_action(action, action->pid); + + if (!pcmk__result_ok(&(action->result)) + && update_remaining_timeout(action)) { + + int rc = internal_stonith_action_execute(action); + if (rc == pcmk_ok) { + return; + } + } + + if (action->done_cb) { + action->done_cb(action->pid, &(action->result), action->userdata); + } + + action->svc_action = NULL; // don't remove our caller + stonith__destroy_action(action); +} + +static void +stonith_action_async_forked(svc_action_t *svc_action) +{ + stonith_action_t *action = (stonith_action_t *) svc_action->cb_data; + + action->pid = svc_action->pid; + action->svc_action = svc_action; + + if (action->fork_cb) { + (action->fork_cb) (svc_action->pid, action->userdata); + } + + pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, + NULL); + + crm_trace("Child process %d performing action '%s' successfully forked", + action->pid, action->action); +} + +static int +internal_stonith_action_execute(stonith_action_t * action) +{ + int rc = -EPROTO; + int is_retry = 0; + svc_action_t *svc_action = NULL; + static int stonith_sequence = 0; + char *buffer = NULL; + + CRM_CHECK(action != NULL, return -EINVAL); + + if ((action->action == NULL) || (action->args == NULL) + || (action->agent == NULL)) { + pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN_ERROR, + PCMK_EXEC_ERROR_FATAL, "Bug in fencing library"); + return -EINVAL; + } + + if (!action->tries) { + action->initial_start_time = time(NULL); + } + action->tries++; + + if (action->tries > 1) { + crm_info("Attempt %d to execute %s (%s). remaining timeout is %d", + action->tries, action->agent, action->action, action->remaining_timeout); + is_retry = 1; + } + + buffer = crm_strdup_printf(PCMK__FENCE_BINDIR "/%s", + basename(action->agent)); + svc_action = services_action_create_generic(buffer, NULL); + free(buffer); + + if (svc_action->rc != PCMK_OCF_UNKNOWN) { + set_result_from_svc_action(action, svc_action); + services_action_free(svc_action); + return -E2BIG; + } + + svc_action->timeout = 1000 * action->remaining_timeout; + svc_action->standard = strdup(PCMK_RESOURCE_CLASS_STONITH); + svc_action->id = crm_strdup_printf("%s_%s_%dof%d", basename(action->agent), + action->action, action->tries, + action->max_retries); + svc_action->agent = strdup(action->agent); + svc_action->sequence = stonith_sequence++; + svc_action->params = action->args; + svc_action->cb_data = (void *) action; + svc_action->flags = pcmk__set_flags_as(__func__, __LINE__, + LOG_TRACE, "Action", + svc_action->id, svc_action->flags, + SVC_ACTION_NON_BLOCKED, + "SVC_ACTION_NON_BLOCKED"); + + /* keep retries from executing out of control and free previous results */ + if (is_retry) { + pcmk__reset_result(&(action->result)); + sleep(1); + } + + if (action->async) { + // We never create a recurring action, so this should always return TRUE + CRM_LOG_ASSERT(services_action_async_fork_notify(svc_action, + &stonith_action_async_done, + &stonith_action_async_forked)); + return pcmk_ok; + + } else if (services_action_sync(svc_action)) { // sync success + rc = pcmk_ok; + + } else { // sync failure + rc = -ECONNABORTED; + } + + set_result_from_svc_action(action, svc_action); + svc_action->params = NULL; + services_action_free(svc_action); + return rc; +} + +/*! + * \internal + * \brief Kick off execution of an async stonith action + * + * \param[in,out] action Action to be executed + * \param[in,out] userdata Datapointer to be passed to callbacks + * \param[in] done Callback to notify action has failed/succeeded + * \param[in] fork_callback Callback to notify successful fork of child + * + * \return pcmk_ok if ownership of action has been taken, -errno otherwise + */ +int +stonith__execute_async(stonith_action_t * action, void *userdata, + void (*done) (int pid, + const pcmk__action_result_t *result, + void *user_data), + void (*fork_cb) (int pid, void *user_data)) +{ + if (!action) { + return -EINVAL; + } + + action->userdata = userdata; + action->done_cb = done; + action->fork_cb = fork_cb; + action->async = true; + + return internal_stonith_action_execute(action); +} + +/*! + * \internal + * \brief Execute a stonith action + * + * \param[in,out] action Action to execute + * + * \return pcmk_ok on success, -errno otherwise + */ +int +stonith__execute(stonith_action_t *action) +{ + int rc = pcmk_ok; + + CRM_CHECK(action != NULL, return -EINVAL); + + // Keep trying until success, max retries, or timeout + do { + rc = internal_stonith_action_execute(action); + } while ((rc != pcmk_ok) && update_remaining_timeout(action)); + + return rc; +} diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c new file mode 100644 index 0000000..e2783d5 --- /dev/null +++ b/lib/fencing/st_client.c @@ -0,0 +1,2702 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <inttypes.h> +#include <sys/types.h> +#include <glib.h> + +#include <crm/crm.h> +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> +#include <crm/msg_xml.h> + +#include <crm/common/mainloop.h> + +#include "fencing_private.h" + +CRM_TRACE_INIT_DATA(stonith); + +// Used as stonith_t:st_private +typedef struct stonith_private_s { + char *token; + crm_ipc_t *ipc; + mainloop_io_t *source; + GHashTable *stonith_op_callback_table; + GList *notify_list; + int notify_refcnt; + bool notify_deletes; + + void (*op_callback) (stonith_t * st, stonith_callback_data_t * data); + +} stonith_private_t; + +// Used as stonith_event_t:opaque +struct event_private { + pcmk__action_result_t result; +}; + +typedef struct stonith_notify_client_s { + const char *event; + const char *obj_id; /* implement one day */ + const char *obj_type; /* implement one day */ + void (*notify) (stonith_t * st, stonith_event_t * e); + bool delete; + +} stonith_notify_client_t; + +typedef struct stonith_callback_client_s { + void (*callback) (stonith_t * st, stonith_callback_data_t * data); + const char *id; + void *user_data; + gboolean only_success; + gboolean allow_timeout_updates; + struct timer_rec_s *timer; + +} stonith_callback_client_t; + +struct notify_blob_s { + stonith_t *stonith; + xmlNode *xml; +}; + +struct timer_rec_s { + int call_id; + int timeout; + guint ref; + stonith_t *stonith; +}; + +typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *, + xmlNode *, xmlNode *, xmlNode **, xmlNode **); + +bool stonith_dispatch(stonith_t * st); +xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, + int call_options); +static int stonith_send_command(stonith_t *stonith, const char *op, + xmlNode *data, xmlNode **output_data, + int call_options, int timeout); + +static void stonith_connection_destroy(gpointer user_data); +static void stonith_send_notification(gpointer data, gpointer user_data); +static int stonith_api_del_notification(stonith_t *stonith, + const char *event); +/*! + * \brief Get agent namespace by name + * + * \param[in] namespace_s Name of namespace as string + * + * \return Namespace as enum value + */ +enum stonith_namespace +stonith_text2namespace(const char *namespace_s) +{ + if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) { + return st_namespace_any; + + } else if (!strcmp(namespace_s, "redhat") + || !strcmp(namespace_s, "stonith-ng")) { + return st_namespace_rhcs; + + } else if (!strcmp(namespace_s, "internal")) { + return st_namespace_internal; + + } else if (!strcmp(namespace_s, "heartbeat")) { + return st_namespace_lha; + } + return st_namespace_invalid; +} + +/*! + * \brief Get agent namespace name + * + * \param[in] namespace Namespace as enum value + * + * \return Namespace name as string + */ +const char * +stonith_namespace2text(enum stonith_namespace st_namespace) +{ + switch (st_namespace) { + case st_namespace_any: return "any"; + case st_namespace_rhcs: return "stonith-ng"; + case st_namespace_internal: return "internal"; + case st_namespace_lha: return "heartbeat"; + default: break; + } + return "unsupported"; +} + +/*! + * \brief Determine namespace of a fence agent + * + * \param[in] agent Fence agent type + * \param[in] namespace_s Name of agent namespace as string, if known + * + * \return Namespace of specified agent, as enum value + */ +enum stonith_namespace +stonith_get_namespace(const char *agent, const char *namespace_s) +{ + if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) { + return st_namespace_internal; + } + + if (stonith__agent_is_rhcs(agent)) { + return st_namespace_rhcs; + } + +#if HAVE_STONITH_STONITH_H + if (stonith__agent_is_lha(agent)) { + return st_namespace_lha; + } +#endif + + crm_err("Unknown fence agent: %s", agent); + return st_namespace_invalid; +} + +gboolean +stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node) +{ + gboolean rv = FALSE; + stonith_t *stonith_api = st?st:stonith_api_new(); + char *list = NULL; + + if(stonith_api) { + if (stonith_api->state == stonith_disconnected) { + int rc = stonith_api->cmds->connect(stonith_api, "stonith-api", NULL); + + if (rc != pcmk_ok) { + crm_err("Failed connecting to Stonith-API for watchdog-fencing-query."); + } + } + + if (stonith_api->state != stonith_disconnected) { + /* caveat!!! + * this might fail when when stonithd is just updating the device-list + * probably something we should fix as well for other api-calls */ + int rc = stonith_api->cmds->list(stonith_api, st_opt_sync_call, STONITH_WATCHDOG_ID, &list, 0); + if ((rc != pcmk_ok) || (list == NULL)) { + /* due to the race described above it can happen that + * we drop in here - so as not to make remote nodes + * panic on that answer + */ + if (rc == -ENODEV) { + crm_notice("Cluster does not have watchdog fencing device"); + } else { + crm_warn("Could not check for watchdog fencing device: %s", + pcmk_strerror(rc)); + } + } else if (list[0] == '\0') { + rv = TRUE; + } else { + GList *targets = stonith__parse_targets(list); + rv = pcmk__str_in_list(node, targets, pcmk__str_casei); + g_list_free_full(targets, free); + } + free(list); + if (!st) { + /* if we're provided the api we still might have done the + * connection - but let's assume the caller won't bother + */ + stonith_api->cmds->disconnect(stonith_api); + } + } + + if (!st) { + stonith_api_delete(stonith_api); + } + } else { + crm_err("Stonith-API for watchdog-fencing-query couldn't be created."); + } + crm_trace("Pacemaker assumes node %s %sto do watchdog-fencing.", + node, rv?"":"not "); + return rv; +} + +gboolean +stonith__watchdog_fencing_enabled_for_node(const char *node) +{ + return stonith__watchdog_fencing_enabled_for_node_api(NULL, node); +} + +/* when cycling through the list we don't want to delete items + so just mark them and when we know nobody is using the list + loop over it to remove the marked items + */ +static void +foreach_notify_entry (stonith_private_t *private, + GFunc func, + gpointer user_data) +{ + private->notify_refcnt++; + g_list_foreach(private->notify_list, func, user_data); + private->notify_refcnt--; + if ((private->notify_refcnt == 0) && + private->notify_deletes) { + GList *list_item = private->notify_list; + + private->notify_deletes = FALSE; + while (list_item != NULL) + { + stonith_notify_client_t *list_client = list_item->data; + GList *next = g_list_next(list_item); + + if (list_client->delete) { + free(list_client); + private->notify_list = + g_list_delete_link(private->notify_list, list_item); + } + list_item = next; + } + } +} + +static void +stonith_connection_destroy(gpointer user_data) +{ + stonith_t *stonith = user_data; + stonith_private_t *native = NULL; + struct notify_blob_s blob; + + crm_trace("Sending destroyed notification"); + blob.stonith = stonith; + blob.xml = create_xml_node(NULL, "notify"); + + native = stonith->st_private; + native->ipc = NULL; + native->source = NULL; + + free(native->token); native->token = NULL; + stonith->state = stonith_disconnected; + crm_xml_add(blob.xml, F_TYPE, T_STONITH_NOTIFY); + crm_xml_add(blob.xml, F_SUBTYPE, T_STONITH_NOTIFY_DISCONNECT); + + foreach_notify_entry(native, stonith_send_notification, &blob); + free_xml(blob.xml); +} + +xmlNode * +create_device_registration_xml(const char *id, enum stonith_namespace namespace, + const char *agent, + const stonith_key_value_t *params, + const char *rsc_provides) +{ + xmlNode *data = create_xml_node(NULL, F_STONITH_DEVICE); + xmlNode *args = create_xml_node(data, XML_TAG_ATTRS); + +#if HAVE_STONITH_STONITH_H + if (namespace == st_namespace_any) { + namespace = stonith_get_namespace(agent, NULL); + } + if (namespace == st_namespace_lha) { + hash2field((gpointer) "plugin", (gpointer) agent, args); + agent = "fence_legacy"; + } +#endif + + crm_xml_add(data, XML_ATTR_ID, id); + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + crm_xml_add(data, "agent", agent); + if ((namespace != st_namespace_any) && (namespace != st_namespace_invalid)) { + crm_xml_add(data, "namespace", stonith_namespace2text(namespace)); + } + if (rsc_provides) { + crm_xml_add(data, "rsc_provides", rsc_provides); + } + + for (; params; params = params->next) { + hash2field((gpointer) params->key, (gpointer) params->value, args); + } + + return data; +} + +static int +stonith_api_register_device(stonith_t *st, int call_options, + const char *id, const char *namespace, + const char *agent, + const stonith_key_value_t *params) +{ + int rc = 0; + xmlNode *data = NULL; + + data = create_device_registration_xml(id, stonith_text2namespace(namespace), + agent, params, NULL); + + rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0); + free_xml(data); + + return rc; +} + +static int +stonith_api_remove_device(stonith_t * st, int call_options, const char *name) +{ + int rc = 0; + xmlNode *data = NULL; + + data = create_xml_node(NULL, F_STONITH_DEVICE); + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + crm_xml_add(data, XML_ATTR_ID, name); + rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0); + free_xml(data); + + return rc; +} + +static int +stonith_api_remove_level_full(stonith_t *st, int options, + const char *node, const char *pattern, + const char *attr, const char *value, int level) +{ + int rc = 0; + xmlNode *data = NULL; + + CRM_CHECK(node || pattern || (attr && value), return -EINVAL); + + data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + + if (node) { + crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); + + } else if (pattern) { + crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern); + + } else { + crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr); + crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value); + } + + crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); + rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0); + free_xml(data); + + return rc; +} + +static int +stonith_api_remove_level(stonith_t * st, int options, const char *node, int level) +{ + return stonith_api_remove_level_full(st, options, node, + NULL, NULL, NULL, level); +} + +/*! + * \internal + * \brief Create XML for fence topology level registration request + * + * \param[in] node If not NULL, target level by this node name + * \param[in] pattern If not NULL, target by node name using this regex + * \param[in] attr If not NULL, target by this node attribute + * \param[in] value If not NULL, target by this node attribute value + * \param[in] level Index number of level to register + * \param[in] device_list List of devices in level + * + * \return Newly allocated XML tree on success, NULL otherwise + * + * \note The caller should set only one of node, pattern or attr/value. + */ +xmlNode * +create_level_registration_xml(const char *node, const char *pattern, + const char *attr, const char *value, + int level, const stonith_key_value_t *device_list) +{ + GString *list = NULL; + xmlNode *data; + + CRM_CHECK(node || pattern || (attr && value), return NULL); + + data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); + CRM_CHECK(data, return NULL); + + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + crm_xml_add_int(data, XML_ATTR_ID, level); + crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); + + if (node) { + crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); + + } else if (pattern) { + crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern); + + } else { + crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr); + crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value); + } + + for (; device_list; device_list = device_list->next) { + pcmk__add_separated_word(&list, 1024, device_list->value, ","); + } + + if (list != NULL) { + crm_xml_add(data, XML_ATTR_STONITH_DEVICES, (const char *) list->str); + g_string_free(list, TRUE); + } + return data; +} + +static int +stonith_api_register_level_full(stonith_t *st, int options, const char *node, + const char *pattern, const char *attr, + const char *value, int level, + const stonith_key_value_t *device_list) +{ + int rc = 0; + xmlNode *data = create_level_registration_xml(node, pattern, attr, value, + level, device_list); + CRM_CHECK(data != NULL, return -EINVAL); + + rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0); + free_xml(data); + + return rc; +} + +static int +stonith_api_register_level(stonith_t * st, int options, const char *node, int level, + const stonith_key_value_t * device_list) +{ + return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL, + level, device_list); +} + +static int +stonith_api_device_list(stonith_t * stonith, int call_options, const char *namespace, + stonith_key_value_t ** devices, int timeout) +{ + int count = 0; + enum stonith_namespace ns = stonith_text2namespace(namespace); + + if (devices == NULL) { + crm_err("Parameter error: stonith_api_device_list"); + return -EFAULT; + } + +#if HAVE_STONITH_STONITH_H + // Include Linux-HA agents if requested + if ((ns == st_namespace_any) || (ns == st_namespace_lha)) { + count += stonith__list_lha_agents(devices); + } +#endif + + // Include Red Hat agents if requested + if ((ns == st_namespace_any) || (ns == st_namespace_rhcs)) { + count += stonith__list_rhcs_agents(devices); + } + + return count; +} + +// See stonith_api_operations_t:metadata() documentation +static int +stonith_api_device_metadata(stonith_t *stonith, int call_options, + const char *agent, const char *namespace, + char **output, int timeout_sec) +{ + /* By executing meta-data directly, we can get it from stonith_admin when + * the cluster is not running, which is important for higher-level tools. + */ + + enum stonith_namespace ns = stonith_get_namespace(agent, namespace); + + if (timeout_sec <= 0) { + timeout_sec = CRMD_METADATA_CALL_TIMEOUT; + } + + crm_trace("Looking up metadata for %s agent %s", + stonith_namespace2text(ns), agent); + + switch (ns) { + case st_namespace_rhcs: + return stonith__rhcs_metadata(agent, timeout_sec, output); + +#if HAVE_STONITH_STONITH_H + case st_namespace_lha: + return stonith__lha_metadata(agent, timeout_sec, output); +#endif + + default: + crm_err("Can't get fence agent '%s' meta-data: No such agent", + agent); + break; + } + return -ENODEV; +} + +static int +stonith_api_query(stonith_t * stonith, int call_options, const char *target, + stonith_key_value_t ** devices, int timeout) +{ + int rc = 0, lpc = 0, max = 0; + + xmlNode *data = NULL; + xmlNode *output = NULL; + xmlXPathObjectPtr xpathObj = NULL; + + CRM_CHECK(devices != NULL, return -EINVAL); + + data = create_xml_node(NULL, F_STONITH_DEVICE); + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + crm_xml_add(data, F_STONITH_TARGET, target); + crm_xml_add(data, F_STONITH_ACTION, "off"); + rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout); + + if (rc < 0) { + return rc; + } + + xpathObj = xpath_search(output, "//@agent"); + if (xpathObj) { + max = numXpathResults(xpathObj); + + for (lpc = 0; lpc < max; lpc++) { + xmlNode *match = getXpathResult(xpathObj, lpc); + + CRM_LOG_ASSERT(match != NULL); + if(match != NULL) { + xmlChar *match_path = xmlGetNodePath(match); + + crm_info("%s[%d] = %s", "//@agent", lpc, match_path); + free(match_path); + *devices = stonith_key_value_add(*devices, NULL, crm_element_value(match, XML_ATTR_ID)); + } + } + + freeXpathObject(xpathObj); + } + + free_xml(output); + free_xml(data); + return max; +} + +/*! + * \internal + * \brief Make a STONITH_OP_EXEC request + * + * \param[in,out] stonith Fencer connection + * \param[in] call_options Bitmask of \c stonith_call_options + * \param[in] id Fence device ID that request is for + * \param[in] action Agent action to request (list, status, monitor) + * \param[in] target Name of target node for requested action + * \param[in] timeout_sec Error if not completed within this many seconds + * \param[out] output Where to set agent output + */ +static int +stonith_api_call(stonith_t *stonith, int call_options, const char *id, + const char *action, const char *target, int timeout_sec, + xmlNode **output) +{ + int rc = 0; + xmlNode *data = NULL; + + data = create_xml_node(NULL, F_STONITH_DEVICE); + crm_xml_add(data, F_STONITH_ORIGIN, __func__); + crm_xml_add(data, F_STONITH_DEVICE, id); + crm_xml_add(data, F_STONITH_ACTION, action); + crm_xml_add(data, F_STONITH_TARGET, target); + + rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output, + call_options, timeout_sec); + free_xml(data); + + return rc; +} + +static int +stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info, + int timeout) +{ + int rc; + xmlNode *output = NULL; + + rc = stonith_api_call(stonith, call_options, id, "list", NULL, timeout, &output); + + if (output && list_info) { + const char *list_str; + + list_str = crm_element_value(output, F_STONITH_OUTPUT); + + if (list_str) { + *list_info = strdup(list_str); + } + } + + if (output) { + free_xml(output); + } + + return rc; +} + +static int +stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout) +{ + return stonith_api_call(stonith, call_options, id, "monitor", NULL, timeout, NULL); +} + +static int +stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port, + int timeout) +{ + return stonith_api_call(stonith, call_options, id, "status", port, timeout, NULL); +} + +static int +stonith_api_fence_with_delay(stonith_t * stonith, int call_options, const char *node, + const char *action, int timeout, int tolerance, int delay) +{ + int rc = 0; + xmlNode *data = NULL; + + data = create_xml_node(NULL, __func__); + crm_xml_add(data, F_STONITH_TARGET, node); + crm_xml_add(data, F_STONITH_ACTION, action); + crm_xml_add_int(data, F_STONITH_TIMEOUT, timeout); + crm_xml_add_int(data, F_STONITH_TOLERANCE, tolerance); + crm_xml_add_int(data, F_STONITH_DELAY, delay); + + rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout); + free_xml(data); + + return rc; +} + +static int +stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action, + int timeout, int tolerance) +{ + return stonith_api_fence_with_delay(stonith, call_options, node, action, + timeout, tolerance, 0); +} + +static int +stonith_api_confirm(stonith_t * stonith, int call_options, const char *target) +{ + stonith__set_call_options(call_options, target, st_opt_manual_ack); + return stonith_api_fence(stonith, call_options, target, "off", 0, 0); +} + +static int +stonith_api_history(stonith_t * stonith, int call_options, const char *node, + stonith_history_t ** history, int timeout) +{ + int rc = 0; + xmlNode *data = NULL; + xmlNode *output = NULL; + stonith_history_t *last = NULL; + + *history = NULL; + + if (node) { + data = create_xml_node(NULL, __func__); + crm_xml_add(data, F_STONITH_TARGET, node); + } + + stonith__set_call_options(call_options, node, st_opt_sync_call); + rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output, + call_options, timeout); + free_xml(data); + + if (rc == 0) { + xmlNode *op = NULL; + xmlNode *reply = get_xpath_object("//" F_STONITH_HISTORY_LIST, output, + LOG_NEVER); + + for (op = pcmk__xml_first_child(reply); op != NULL; + op = pcmk__xml_next(op)) { + stonith_history_t *kvp; + long long completed; + long long completed_nsec = 0L; + + kvp = calloc(1, sizeof(stonith_history_t)); + kvp->target = crm_element_value_copy(op, F_STONITH_TARGET); + kvp->action = crm_element_value_copy(op, F_STONITH_ACTION); + kvp->origin = crm_element_value_copy(op, F_STONITH_ORIGIN); + kvp->delegate = crm_element_value_copy(op, F_STONITH_DELEGATE); + kvp->client = crm_element_value_copy(op, F_STONITH_CLIENTNAME); + crm_element_value_ll(op, F_STONITH_DATE, &completed); + kvp->completed = (time_t) completed; + crm_element_value_ll(op, F_STONITH_DATE_NSEC, &completed_nsec); + kvp->completed_nsec = completed_nsec; + crm_element_value_int(op, F_STONITH_STATE, &kvp->state); + kvp->exit_reason = crm_element_value_copy(op, + XML_LRM_ATTR_EXIT_REASON); + + if (last) { + last->next = kvp; + } else { + *history = kvp; + } + last = kvp; + } + } + + free_xml(output); + + return rc; +} + +void stonith_history_free(stonith_history_t *history) +{ + stonith_history_t *hp, *hp_old; + + for (hp = history; hp; hp_old = hp, hp = hp->next, free(hp_old)) { + free(hp->target); + free(hp->action); + free(hp->origin); + free(hp->delegate); + free(hp->client); + free(hp->exit_reason); + } +} + +static gint +stonithlib_GCompareFunc(gconstpointer a, gconstpointer b) +{ + int rc = 0; + const stonith_notify_client_t *a_client = a; + const stonith_notify_client_t *b_client = b; + + if (a_client->delete || b_client->delete) { + /* make entries marked for deletion not findable */ + return -1; + } + CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0); + rc = strcmp(a_client->event, b_client->event); + if (rc == 0) { + if (a_client->notify == NULL || b_client->notify == NULL) { + return 0; + + } else if (a_client->notify == b_client->notify) { + return 0; + + } else if (((long)a_client->notify) < ((long)b_client->notify)) { + crm_err("callbacks for %s are not equal: %p vs. %p", + a_client->event, a_client->notify, b_client->notify); + return -1; + } + crm_err("callbacks for %s are not equal: %p vs. %p", + a_client->event, a_client->notify, b_client->notify); + return 1; + } + return rc; +} + +xmlNode * +stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options) +{ + xmlNode *op_msg = create_xml_node(NULL, "stonith_command"); + + CRM_CHECK(op_msg != NULL, return NULL); + CRM_CHECK(token != NULL, return NULL); + + crm_xml_add(op_msg, F_XML_TAGNAME, "stonith_command"); + + crm_xml_add(op_msg, F_TYPE, T_STONITH_NG); + crm_xml_add(op_msg, F_STONITH_CALLBACK_TOKEN, token); + crm_xml_add(op_msg, F_STONITH_OPERATION, op); + crm_xml_add_int(op_msg, F_STONITH_CALLID, call_id); + crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options); + crm_xml_add_int(op_msg, F_STONITH_CALLOPTS, call_options); + + if (data != NULL) { + add_message_xml(op_msg, F_STONITH_CALLDATA, data); + } + + return op_msg; +} + +static void +stonith_destroy_op_callback(gpointer data) +{ + stonith_callback_client_t *blob = data; + + if (blob->timer && blob->timer->ref > 0) { + g_source_remove(blob->timer->ref); + } + free(blob->timer); + free(blob); +} + +static int +stonith_api_signoff(stonith_t * stonith) +{ + stonith_private_t *native = stonith->st_private; + + crm_debug("Disconnecting from the fencer"); + + if (native->source != NULL) { + /* Attached to mainloop */ + mainloop_del_ipc_client(native->source); + native->source = NULL; + native->ipc = NULL; + + } else if (native->ipc) { + /* Not attached to mainloop */ + crm_ipc_t *ipc = native->ipc; + + native->ipc = NULL; + crm_ipc_close(ipc); + crm_ipc_destroy(ipc); + } + + free(native->token); native->token = NULL; + stonith->state = stonith_disconnected; + return pcmk_ok; +} + +static int +stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks) +{ + stonith_private_t *private = stonith->st_private; + + if (all_callbacks) { + private->op_callback = NULL; + g_hash_table_destroy(private->stonith_op_callback_table); + private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback); + + } else if (call_id == 0) { + private->op_callback = NULL; + + } else { + pcmk__intkey_table_remove(private->stonith_op_callback_table, call_id); + } + return pcmk_ok; +} + +/*! + * \internal + * \brief Invoke a (single) specified fence action callback + * + * \param[in,out] st Fencer API connection + * \param[in] call_id If positive, call ID of completed fence action, + * otherwise legacy return code for early failure + * \param[in,out] result Full result for action + * \param[in,out] userdata User data to pass to callback + * \param[in] callback Fence action callback to invoke + */ +static void +invoke_fence_action_callback(stonith_t *st, int call_id, + pcmk__action_result_t *result, + void *userdata, + void (*callback) (stonith_t *st, + stonith_callback_data_t *data)) +{ + stonith_callback_data_t data = { 0, }; + + data.call_id = call_id; + data.rc = pcmk_rc2legacy(stonith__result2rc(result)); + data.userdata = userdata; + data.opaque = (void *) result; + + callback(st, &data); +} + +/*! + * \internal + * \brief Invoke any callbacks registered for a specified fence action result + * + * Given a fence action result from the fencer, invoke any callback registered + * for that action, as well as any global callback registered. + * + * \param[in,out] stonith Fencer API connection + * \param[in] msg If non-NULL, fencer reply + * \param[in] call_id If \p msg is NULL, call ID of action that timed out + */ +static void +invoke_registered_callbacks(stonith_t *stonith, const xmlNode *msg, int call_id) +{ + stonith_private_t *private = NULL; + stonith_callback_client_t *cb_info = NULL; + pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; + + CRM_CHECK(stonith != NULL, return); + CRM_CHECK(stonith->st_private != NULL, return); + + private = stonith->st_private; + + if (msg == NULL) { + // Fencer didn't reply in time + pcmk__set_result(&result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT, + "Fencer accepted request but did not reply in time"); + CRM_LOG_ASSERT(call_id > 0); + + } else { + // We have the fencer reply + if ((crm_element_value_int(msg, F_STONITH_CALLID, &call_id) != 0) + || (call_id <= 0)) { + crm_log_xml_warn(msg, "Bad fencer reply"); + } + stonith__xe_get_result(msg, &result); + } + + if (call_id > 0) { + cb_info = pcmk__intkey_table_lookup(private->stonith_op_callback_table, + call_id); + } + + if ((cb_info != NULL) && (cb_info->callback != NULL) + && (pcmk__result_ok(&result) || !(cb_info->only_success))) { + crm_trace("Invoking callback %s for call %d", + pcmk__s(cb_info->id, "without ID"), call_id); + invoke_fence_action_callback(stonith, call_id, &result, + cb_info->user_data, cb_info->callback); + + } else if ((private->op_callback == NULL) && !pcmk__result_ok(&result)) { + crm_warn("Fencing action without registered callback failed: %d (%s%s%s)", + result.exit_status, + pcmk_exec_status_str(result.execution_status), + ((result.exit_reason == NULL)? "" : ": "), + ((result.exit_reason == NULL)? "" : result.exit_reason)); + crm_log_xml_debug(msg, "Failed fence update"); + } + + if (private->op_callback != NULL) { + crm_trace("Invoking global callback for call %d", call_id); + invoke_fence_action_callback(stonith, call_id, &result, NULL, + private->op_callback); + } + + if (cb_info != NULL) { + stonith_api_del_callback(stonith, call_id, FALSE); + } + pcmk__reset_result(&result); +} + +static gboolean +stonith_async_timeout_handler(gpointer data) +{ + struct timer_rec_s *timer = data; + + crm_err("Async call %d timed out after %dms", timer->call_id, timer->timeout); + invoke_registered_callbacks(timer->stonith, NULL, timer->call_id); + + /* Always return TRUE, never remove the handler + * We do that in stonith_del_callback() + */ + return TRUE; +} + +static void +set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id, + int timeout) +{ + struct timer_rec_s *async_timer = callback->timer; + + if (timeout <= 0) { + return; + } + + if (!async_timer) { + async_timer = calloc(1, sizeof(struct timer_rec_s)); + callback->timer = async_timer; + } + + async_timer->stonith = stonith; + async_timer->call_id = call_id; + /* Allow a fair bit of grace to allow the server to tell us of a timeout + * This is only a fallback + */ + async_timer->timeout = (timeout + 60) * 1000; + if (async_timer->ref) { + g_source_remove(async_timer->ref); + } + async_timer->ref = + g_timeout_add(async_timer->timeout, stonith_async_timeout_handler, async_timer); +} + +static void +update_callback_timeout(int call_id, int timeout, stonith_t * st) +{ + stonith_callback_client_t *callback = NULL; + stonith_private_t *private = st->st_private; + + callback = pcmk__intkey_table_lookup(private->stonith_op_callback_table, + call_id); + if (!callback || !callback->allow_timeout_updates) { + return; + } + + set_callback_timeout(callback, st, call_id, timeout); +} + +static int +stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata) +{ + const char *type = NULL; + struct notify_blob_s blob; + + stonith_t *st = userdata; + stonith_private_t *private = NULL; + + CRM_ASSERT(st != NULL); + private = st->st_private; + + blob.stonith = st; + blob.xml = string2xml(buffer); + if (blob.xml == NULL) { + crm_warn("Received malformed message from fencer: %s", buffer); + return 0; + } + + /* do callbacks */ + type = crm_element_value(blob.xml, F_TYPE); + crm_trace("Activating %s callbacks...", type); + + if (pcmk__str_eq(type, T_STONITH_NG, pcmk__str_none)) { + invoke_registered_callbacks(st, blob.xml, 0); + + } else if (pcmk__str_eq(type, T_STONITH_NOTIFY, pcmk__str_none)) { + foreach_notify_entry(private, stonith_send_notification, &blob); + } else if (pcmk__str_eq(type, T_STONITH_TIMEOUT_VALUE, pcmk__str_none)) { + int call_id = 0; + int timeout = 0; + + crm_element_value_int(blob.xml, F_STONITH_TIMEOUT, &timeout); + crm_element_value_int(blob.xml, F_STONITH_CALLID, &call_id); + + update_callback_timeout(call_id, timeout, st); + } else { + crm_err("Unknown message type: %s", type); + crm_log_xml_warn(blob.xml, "BadReply"); + } + + free_xml(blob.xml); + return 1; +} + +static int +stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd) +{ + int rc = pcmk_ok; + stonith_private_t *native = NULL; + const char *display_name = name? name : "client"; + + struct ipc_client_callbacks st_callbacks = { + .dispatch = stonith_dispatch_internal, + .destroy = stonith_connection_destroy + }; + + CRM_CHECK(stonith != NULL, return -EINVAL); + + native = stonith->st_private; + CRM_ASSERT(native != NULL); + + crm_debug("Attempting fencer connection by %s with%s mainloop", + display_name, (stonith_fd? "out" : "")); + + stonith->state = stonith_connected_command; + if (stonith_fd) { + /* No mainloop */ + native->ipc = crm_ipc_new("stonith-ng", 0); + + if (native->ipc && crm_ipc_connect(native->ipc)) { + *stonith_fd = crm_ipc_get_fd(native->ipc); + } else if (native->ipc) { + crm_ipc_close(native->ipc); + crm_ipc_destroy(native->ipc); + native->ipc = NULL; + } + + } else { + /* With mainloop */ + native->source = + mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks); + native->ipc = mainloop_get_ipc_client(native->source); + } + + if (native->ipc == NULL) { + rc = -ENOTCONN; + } else { + xmlNode *reply = NULL; + xmlNode *hello = create_xml_node(NULL, "stonith_command"); + + crm_xml_add(hello, F_TYPE, T_STONITH_NG); + crm_xml_add(hello, F_STONITH_OPERATION, CRM_OP_REGISTER); + crm_xml_add(hello, F_STONITH_CLIENTNAME, name); + rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply); + + if (rc < 0) { + crm_debug("Couldn't register with the fencer: %s " + CRM_XS " rc=%d", pcmk_strerror(rc), rc); + rc = -ECOMM; + + } else if (reply == NULL) { + crm_debug("Couldn't register with the fencer: no reply"); + rc = -EPROTO; + + } else { + const char *msg_type = crm_element_value(reply, F_STONITH_OPERATION); + + native->token = crm_element_value_copy(reply, F_STONITH_CLIENTID); + if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_none)) { + crm_debug("Couldn't register with the fencer: invalid reply type '%s'", + (msg_type? msg_type : "(missing)")); + crm_log_xml_debug(reply, "Invalid fencer reply"); + rc = -EPROTO; + + } else if (native->token == NULL) { + crm_debug("Couldn't register with the fencer: no token in reply"); + crm_log_xml_debug(reply, "Invalid fencer reply"); + rc = -EPROTO; + + } else { + crm_debug("Connection to fencer by %s succeeded (registration token: %s)", + display_name, native->token); + rc = pcmk_ok; + } + } + + free_xml(reply); + free_xml(hello); + } + + if (rc != pcmk_ok) { + crm_debug("Connection attempt to fencer by %s failed: %s " + CRM_XS " rc=%d", display_name, pcmk_strerror(rc), rc); + stonith->cmds->disconnect(stonith); + } + return rc; +} + +static int +stonith_set_notification(stonith_t * stonith, const char *callback, int enabled) +{ + int rc = pcmk_ok; + xmlNode *notify_msg = create_xml_node(NULL, __func__); + stonith_private_t *native = stonith->st_private; + + if (stonith->state != stonith_disconnected) { + + crm_xml_add(notify_msg, F_STONITH_OPERATION, T_STONITH_NOTIFY); + if (enabled) { + crm_xml_add(notify_msg, F_STONITH_NOTIFY_ACTIVATE, callback); + } else { + crm_xml_add(notify_msg, F_STONITH_NOTIFY_DEACTIVATE, callback); + } + + rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL); + if (rc < 0) { + crm_perror(LOG_DEBUG, "Couldn't register for fencing notifications: %d", rc); + rc = -ECOMM; + } else { + rc = pcmk_ok; + } + } + + free_xml(notify_msg); + return rc; +} + +static int +stonith_api_add_notification(stonith_t * stonith, const char *event, + void (*callback) (stonith_t * stonith, stonith_event_t * e)) +{ + GList *list_item = NULL; + stonith_notify_client_t *new_client = NULL; + stonith_private_t *private = NULL; + + private = stonith->st_private; + crm_trace("Adding callback for %s events (%d)", event, g_list_length(private->notify_list)); + + new_client = calloc(1, sizeof(stonith_notify_client_t)); + new_client->event = event; + new_client->notify = callback; + + list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc); + + if (list_item != NULL) { + crm_warn("Callback already present"); + free(new_client); + return -ENOTUNIQ; + + } else { + private->notify_list = g_list_append(private->notify_list, new_client); + + stonith_set_notification(stonith, event, 1); + + crm_trace("Callback added (%d)", g_list_length(private->notify_list)); + } + return pcmk_ok; +} + +static void +del_notify_entry(gpointer data, gpointer user_data) +{ + stonith_notify_client_t *entry = data; + stonith_t * stonith = user_data; + + if (!entry->delete) { + crm_debug("Removing callback for %s events", entry->event); + stonith_api_del_notification(stonith, entry->event); + } +} + +static int +stonith_api_del_notification(stonith_t * stonith, const char *event) +{ + GList *list_item = NULL; + stonith_notify_client_t *new_client = NULL; + stonith_private_t *private = stonith->st_private; + + if (event == NULL) { + foreach_notify_entry(private, del_notify_entry, stonith); + crm_trace("Removed callback"); + + return pcmk_ok; + } + + crm_debug("Removing callback for %s events", event); + + new_client = calloc(1, sizeof(stonith_notify_client_t)); + new_client->event = event; + new_client->notify = NULL; + + list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc); + + stonith_set_notification(stonith, event, 0); + + if (list_item != NULL) { + stonith_notify_client_t *list_client = list_item->data; + + if (private->notify_refcnt) { + list_client->delete = TRUE; + private->notify_deletes = TRUE; + } else { + private->notify_list = g_list_remove(private->notify_list, list_client); + free(list_client); + } + + crm_trace("Removed callback"); + + } else { + crm_trace("Callback not present"); + } + free(new_client); + return pcmk_ok; +} + +static int +stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options, + void *user_data, const char *callback_name, + void (*callback) (stonith_t * st, stonith_callback_data_t * data)) +{ + stonith_callback_client_t *blob = NULL; + stonith_private_t *private = NULL; + + CRM_CHECK(stonith != NULL, return -EINVAL); + CRM_CHECK(stonith->st_private != NULL, return -EINVAL); + private = stonith->st_private; + + if (call_id == 0) { // Add global callback + private->op_callback = callback; + + } else if (call_id < 0) { // Call failed immediately, so call callback now + if (!(options & st_opt_report_only_success)) { + pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; + + crm_trace("Call failed, calling %s: %s", callback_name, pcmk_strerror(call_id)); + pcmk__set_result(&result, CRM_EX_ERROR, + stonith__legacy2status(call_id), NULL); + invoke_fence_action_callback(stonith, call_id, &result, + user_data, callback); + } else { + crm_warn("Fencer call failed: %s", pcmk_strerror(call_id)); + } + return FALSE; + } + + blob = calloc(1, sizeof(stonith_callback_client_t)); + blob->id = callback_name; + blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE; + blob->user_data = user_data; + blob->callback = callback; + blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE; + + if (timeout > 0) { + set_callback_timeout(blob, stonith, call_id, timeout); + } + + pcmk__intkey_table_insert(private->stonith_op_callback_table, call_id, + blob); + crm_trace("Added callback to %s for call %d", callback_name, call_id); + + return TRUE; +} + +static void +stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data) +{ + int call = GPOINTER_TO_INT(key); + stonith_callback_client_t *blob = value; + + crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "no ID")); +} + +void +stonith_dump_pending_callbacks(stonith_t * stonith) +{ + stonith_private_t *private = stonith->st_private; + + if (private->stonith_op_callback_table == NULL) { + return; + } + return g_hash_table_foreach(private->stonith_op_callback_table, stonith_dump_pending_op, NULL); +} + +/*! + * \internal + * \brief Get the data section of a fencer notification + * + * \param[in] msg Notification XML + * \param[in] ntype Notification type + */ +static xmlNode * +get_event_data_xml(xmlNode *msg, const char *ntype) +{ + char *data_addr = crm_strdup_printf("//%s", ntype); + xmlNode *data = get_xpath_object(data_addr, msg, LOG_DEBUG); + + free(data_addr); + return data; +} + +/* + <notify t="st_notify" subt="st_device_register" st_op="st_device_register" st_rc="0" > + <st_calldata > + <stonith_command t="stonith-ng" st_async_id="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_op="st_device_register" st_callid="2" st_callopt="4096" st_timeout="0" st_clientid="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_clientname="cts-fence-helper" > + <st_calldata > + <st_device_id id="test-id" origin="create_device_registration_xml" agent="fence_virsh" namespace="stonith-ng" > + <attributes ipaddr="localhost" pcmk-portmal="some-host=pcmk-1 pcmk-3=3,4" login="root" identity_file="/root/.ssh/id_dsa" /> + </st_device_id> + </st_calldata> + </stonith_command> + </st_calldata> + </notify> + + <notify t="st_notify" subt="st_notify_fence" st_op="st_notify_fence" st_rc="0" > + <st_calldata > + <st_notify_fence st_rc="0" st_target="some-host" st_op="st_fence" st_delegate="test-id" st_origin="61dd7759-e229-4be7-b1f8-ef49dd14d9f0" /> + </st_calldata> + </notify> +*/ +static stonith_event_t * +xml_to_event(xmlNode *msg) +{ + stonith_event_t *event = calloc(1, sizeof(stonith_event_t)); + struct event_private *event_private = NULL; + + CRM_ASSERT(event != NULL); + + event->opaque = calloc(1, sizeof(struct event_private)); + CRM_ASSERT(event->opaque != NULL); + event_private = (struct event_private *) event->opaque; + + crm_log_xml_trace(msg, "stonith_notify"); + + // All notification types have the operation result and notification subtype + stonith__xe_get_result(msg, &event_private->result); + event->operation = crm_element_value_copy(msg, F_STONITH_OPERATION); + + // @COMPAT The API originally provided the result as a legacy return code + event->result = pcmk_rc2legacy(stonith__result2rc(&event_private->result)); + + // Some notification subtypes have additional information + + if (pcmk__str_eq(event->operation, T_STONITH_NOTIFY_FENCE, + pcmk__str_none)) { + xmlNode *data = get_event_data_xml(msg, event->operation); + + if (data == NULL) { + crm_err("No data for %s event", event->operation); + crm_log_xml_notice(msg, "BadEvent"); + } else { + event->origin = crm_element_value_copy(data, F_STONITH_ORIGIN); + event->action = crm_element_value_copy(data, F_STONITH_ACTION); + event->target = crm_element_value_copy(data, F_STONITH_TARGET); + event->executioner = crm_element_value_copy(data, F_STONITH_DELEGATE); + event->id = crm_element_value_copy(data, F_STONITH_REMOTE_OP_ID); + event->client_origin = crm_element_value_copy(data, F_STONITH_CLIENTNAME); + event->device = crm_element_value_copy(data, F_STONITH_DEVICE); + } + + } else if (pcmk__str_any_of(event->operation, + STONITH_OP_DEVICE_ADD, STONITH_OP_DEVICE_DEL, + STONITH_OP_LEVEL_ADD, STONITH_OP_LEVEL_DEL, + NULL)) { + xmlNode *data = get_event_data_xml(msg, event->operation); + + if (data == NULL) { + crm_err("No data for %s event", event->operation); + crm_log_xml_notice(msg, "BadEvent"); + } else { + event->device = crm_element_value_copy(data, F_STONITH_DEVICE); + } + } + + return event; +} + +static void +event_free(stonith_event_t * event) +{ + struct event_private *event_private = event->opaque; + + free(event->id); + free(event->type); + free(event->message); + free(event->operation); + free(event->origin); + free(event->action); + free(event->target); + free(event->executioner); + free(event->device); + free(event->client_origin); + pcmk__reset_result(&event_private->result); + free(event->opaque); + free(event); +} + +static void +stonith_send_notification(gpointer data, gpointer user_data) +{ + struct notify_blob_s *blob = user_data; + stonith_notify_client_t *entry = data; + stonith_event_t *st_event = NULL; + const char *event = NULL; + + if (blob->xml == NULL) { + crm_warn("Skipping callback - NULL message"); + return; + } + + event = crm_element_value(blob->xml, F_SUBTYPE); + + if (entry == NULL) { + crm_warn("Skipping callback - NULL callback client"); + return; + + } else if (entry->delete) { + crm_trace("Skipping callback - marked for deletion"); + return; + + } else if (entry->notify == NULL) { + crm_warn("Skipping callback - NULL callback"); + return; + + } else if (!pcmk__str_eq(entry->event, event, pcmk__str_none)) { + crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); + return; + } + + st_event = xml_to_event(blob->xml); + + crm_trace("Invoking callback for %p/%s event...", entry, event); + entry->notify(blob->stonith, st_event); + crm_trace("Callback invoked..."); + + event_free(st_event); +} + +/*! + * \internal + * \brief Create and send an API request + * + * \param[in,out] stonith Stonith connection + * \param[in] op API operation to request + * \param[in] data Data to attach to request + * \param[out] output_data If not NULL, will be set to reply if synchronous + * \param[in] call_options Bitmask of stonith_call_options to use + * \param[in] timeout Error if not completed within this many seconds + * + * \return pcmk_ok (for synchronous requests) or positive call ID + * (for asynchronous requests) on success, -errno otherwise + */ +static int +stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data, + int call_options, int timeout) +{ + int rc = 0; + int reply_id = -1; + + xmlNode *op_msg = NULL; + xmlNode *op_reply = NULL; + stonith_private_t *native = NULL; + + CRM_ASSERT(stonith && stonith->st_private && op); + native = stonith->st_private; + + if (output_data != NULL) { + *output_data = NULL; + } + + if ((stonith->state == stonith_disconnected) || (native->token == NULL)) { + return -ENOTCONN; + } + + /* Increment the call ID, which must be positive to avoid conflicting with + * error codes. This shouldn't be a problem unless the client mucked with + * it or the counter wrapped around. + */ + stonith->call_id++; + if (stonith->call_id < 1) { + stonith->call_id = 1; + } + + op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options); + if (op_msg == NULL) { + return -EINVAL; + } + + crm_xml_add_int(op_msg, F_STONITH_TIMEOUT, timeout); + crm_trace("Sending %s message to fencer with timeout %ds", op, timeout); + + if (data) { + const char *delay_s = crm_element_value(data, F_STONITH_DELAY); + + if (delay_s) { + crm_xml_add(op_msg, F_STONITH_DELAY, delay_s); + } + } + + { + enum crm_ipc_flags ipc_flags = crm_ipc_flags_none; + + if (call_options & st_opt_sync_call) { + pcmk__set_ipc_flags(ipc_flags, "stonith command", + crm_ipc_client_response); + } + rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, + 1000 * (timeout + 60), &op_reply); + } + free_xml(op_msg); + + if (rc < 0) { + crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%ds): %d", op, timeout, rc); + rc = -ECOMM; + goto done; + } + + crm_log_xml_trace(op_reply, "Reply"); + + if (!(call_options & st_opt_sync_call)) { + crm_trace("Async call %d, returning", stonith->call_id); + free_xml(op_reply); + return stonith->call_id; + } + + crm_element_value_int(op_reply, F_STONITH_CALLID, &reply_id); + + if (reply_id == stonith->call_id) { + pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; + + crm_trace("Synchronous reply %d received", reply_id); + + stonith__xe_get_result(op_reply, &result); + rc = pcmk_rc2legacy(stonith__result2rc(&result)); + pcmk__reset_result(&result); + + if ((call_options & st_opt_discard_reply) || output_data == NULL) { + crm_trace("Discarding reply"); + + } else { + *output_data = op_reply; + op_reply = NULL; /* Prevent subsequent free */ + } + + } else if (reply_id <= 0) { + crm_err("Received bad reply: No id set"); + crm_log_xml_err(op_reply, "Bad reply"); + free_xml(op_reply); + rc = -ENOMSG; + + } else { + crm_err("Received bad reply: %d (wanted %d)", reply_id, stonith->call_id); + crm_log_xml_err(op_reply, "Old reply"); + free_xml(op_reply); + rc = -ENOMSG; + } + + done: + if (!crm_ipc_connected(native->ipc)) { + crm_err("Fencer disconnected"); + free(native->token); native->token = NULL; + stonith->state = stonith_disconnected; + } + + free_xml(op_reply); + return rc; +} + +/* Not used with mainloop */ +bool +stonith_dispatch(stonith_t * st) +{ + gboolean stay_connected = TRUE; + stonith_private_t *private = NULL; + + CRM_ASSERT(st != NULL); + private = st->st_private; + + while (crm_ipc_ready(private->ipc)) { + + if (crm_ipc_read(private->ipc) > 0) { + const char *msg = crm_ipc_buffer(private->ipc); + + stonith_dispatch_internal(msg, strlen(msg), st); + } + + if (!crm_ipc_connected(private->ipc)) { + crm_err("Connection closed"); + stay_connected = FALSE; + } + } + + return stay_connected; +} + +static int +stonith_api_free(stonith_t * stonith) +{ + int rc = pcmk_ok; + + crm_trace("Destroying %p", stonith); + + if (stonith->state != stonith_disconnected) { + crm_trace("Unregistering notifications and disconnecting %p first", + stonith); + stonith->cmds->remove_notification(stonith, NULL); + rc = stonith->cmds->disconnect(stonith); + } + + if (stonith->state == stonith_disconnected) { + stonith_private_t *private = stonith->st_private; + + crm_trace("Removing %d callbacks", g_hash_table_size(private->stonith_op_callback_table)); + g_hash_table_destroy(private->stonith_op_callback_table); + + crm_trace("Destroying %d notification clients", g_list_length(private->notify_list)); + g_list_free_full(private->notify_list, free); + + free(stonith->st_private); + free(stonith->cmds); + free(stonith); + + } else { + crm_err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc), rc); + } + + return rc; +} + +void +stonith_api_delete(stonith_t * stonith) +{ + crm_trace("Destroying %p", stonith); + if(stonith) { + stonith->cmds->free(stonith); + } +} + +static int +stonith_api_validate(stonith_t *st, int call_options, const char *rsc_id, + const char *namespace_s, const char *agent, + const stonith_key_value_t *params, int timeout_sec, + char **output, char **error_output) +{ + /* Validation should be done directly via the agent, so we can get it from + * stonith_admin when the cluster is not running, which is important for + * higher-level tools. + */ + + int rc = pcmk_ok; + + /* Use a dummy node name in case the agent requires a target. We assume the + * actual target doesn't matter for validation purposes (if in practice, + * that is incorrect, we will need to allow the caller to pass the target). + */ + const char *target = "node1"; + const char *host_arg = NULL; + + GHashTable *params_table = pcmk__strkey_table(free, free); + + // Convert parameter list to a hash table + for (; params; params = params->next) { + if (pcmk__str_eq(params->key, PCMK_STONITH_HOST_ARGUMENT, + pcmk__str_none)) { + host_arg = params->value; + } + if (!pcmk_stonith_param(params->key)) { + g_hash_table_insert(params_table, strdup(params->key), + strdup(params->value)); + } + } + +#if SUPPORT_CIBSECRETS + rc = pcmk__substitute_secrets(rsc_id, params_table); + if (rc != pcmk_rc_ok) { + crm_warn("Could not replace secret parameters for validation of %s: %s", + agent, pcmk_rc_str(rc)); + // rc is standard return value, don't return it in this function + } +#endif + + if (output) { + *output = NULL; + } + if (error_output) { + *error_output = NULL; + } + + if (timeout_sec <= 0) { + timeout_sec = CRMD_METADATA_CALL_TIMEOUT; // Questionable + } + + switch (stonith_get_namespace(agent, namespace_s)) { + case st_namespace_rhcs: + rc = stonith__rhcs_validate(st, call_options, target, agent, + params_table, host_arg, timeout_sec, + output, error_output); + break; + +#if HAVE_STONITH_STONITH_H + case st_namespace_lha: + rc = stonith__lha_validate(st, call_options, target, agent, + params_table, timeout_sec, output, + error_output); + break; +#endif + + case st_namespace_invalid: + errno = ENOENT; + rc = -errno; + + if (error_output) { + *error_output = crm_strdup_printf("Agent %s not found", agent); + } else { + crm_err("Agent %s not found", agent); + } + + break; + + default: + errno = EOPNOTSUPP; + rc = -errno; + + if (error_output) { + *error_output = crm_strdup_printf("Agent %s does not support validation", + agent); + } else { + crm_err("Agent %s does not support validation", agent); + } + + break; + } + + g_hash_table_destroy(params_table); + return rc; +} + +stonith_t * +stonith_api_new(void) +{ + stonith_t *new_stonith = NULL; + stonith_private_t *private = NULL; + + new_stonith = calloc(1, sizeof(stonith_t)); + if (new_stonith == NULL) { + return NULL; + } + + private = calloc(1, sizeof(stonith_private_t)); + if (private == NULL) { + free(new_stonith); + return NULL; + } + new_stonith->st_private = private; + + private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback); + private->notify_list = NULL; + private->notify_refcnt = 0; + private->notify_deletes = FALSE; + + new_stonith->call_id = 1; + new_stonith->state = stonith_disconnected; + + new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t)); + if (new_stonith->cmds == NULL) { + free(new_stonith->st_private); + free(new_stonith); + return NULL; + } + +/* *INDENT-OFF* */ + new_stonith->cmds->free = stonith_api_free; + new_stonith->cmds->connect = stonith_api_signon; + new_stonith->cmds->disconnect = stonith_api_signoff; + + new_stonith->cmds->list = stonith_api_list; + new_stonith->cmds->monitor = stonith_api_monitor; + new_stonith->cmds->status = stonith_api_status; + new_stonith->cmds->fence = stonith_api_fence; + new_stonith->cmds->fence_with_delay = stonith_api_fence_with_delay; + new_stonith->cmds->confirm = stonith_api_confirm; + new_stonith->cmds->history = stonith_api_history; + + new_stonith->cmds->list_agents = stonith_api_device_list; + new_stonith->cmds->metadata = stonith_api_device_metadata; + + new_stonith->cmds->query = stonith_api_query; + new_stonith->cmds->remove_device = stonith_api_remove_device; + new_stonith->cmds->register_device = stonith_api_register_device; + + new_stonith->cmds->remove_level = stonith_api_remove_level; + new_stonith->cmds->remove_level_full = stonith_api_remove_level_full; + new_stonith->cmds->register_level = stonith_api_register_level; + new_stonith->cmds->register_level_full = stonith_api_register_level_full; + + new_stonith->cmds->remove_callback = stonith_api_del_callback; + new_stonith->cmds->register_callback = stonith_api_add_callback; + new_stonith->cmds->remove_notification = stonith_api_del_notification; + new_stonith->cmds->register_notification = stonith_api_add_notification; + + new_stonith->cmds->validate = stonith_api_validate; +/* *INDENT-ON* */ + + return new_stonith; +} + +/*! + * \brief Make a blocking connection attempt to the fencer + * + * \param[in,out] st Fencer API object + * \param[in] name Client name to use with fencer + * \param[in] max_attempts Return error if this many attempts fail + * + * \return pcmk_ok on success, result of last attempt otherwise + */ +int +stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts) +{ + int rc = -EINVAL; // if max_attempts is not positive + + for (int attempt = 1; attempt <= max_attempts; attempt++) { + rc = st->cmds->connect(st, name, NULL); + if (rc == pcmk_ok) { + return pcmk_ok; + } else if (attempt < max_attempts) { + crm_notice("Fencer connection attempt %d of %d failed (retrying in 2s): %s " + CRM_XS " rc=%d", + attempt, max_attempts, pcmk_strerror(rc), rc); + sleep(2); + } + } + crm_notice("Could not connect to fencer: %s " CRM_XS " rc=%d", + pcmk_strerror(rc), rc); + return rc; +} + +stonith_key_value_t * +stonith_key_value_add(stonith_key_value_t * head, const char *key, const char *value) +{ + stonith_key_value_t *p, *end; + + p = calloc(1, sizeof(stonith_key_value_t)); + pcmk__str_update(&p->key, key); + pcmk__str_update(&p->value, value); + + end = head; + while (end && end->next) { + end = end->next; + } + + if (end) { + end->next = p; + } else { + head = p; + } + + return head; +} + +void +stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values) +{ + stonith_key_value_t *p; + + while (head) { + p = head->next; + if (keys) { + free(head->key); + } + if (values) { + free(head->value); + } + free(head); + head = p; + } +} + +#define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON) +#define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __func__, args) + +int +stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off) +{ + int rc = pcmk_ok; + stonith_t *st = stonith_api_new(); + const char *action = off? "off" : "reboot"; + + api_log_open(); + if (st == NULL) { + api_log(LOG_ERR, "API initialization failed, could not kick (%s) node %u/%s", + action, nodeid, uname); + return -EPROTO; + } + + rc = st->cmds->connect(st, "stonith-api", NULL); + if (rc != pcmk_ok) { + api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)", + action, nodeid, uname, pcmk_strerror(rc), rc); + } else { + char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname); + int opts = 0; + + stonith__set_call_options(opts, name, + st_opt_sync_call|st_opt_allow_suicide); + if ((uname == NULL) && (nodeid > 0)) { + stonith__set_call_options(opts, name, st_opt_cs_nodeid); + } + rc = st->cmds->fence(st, opts, name, action, timeout, 0); + free(name); + + if (rc != pcmk_ok) { + api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)", + action, nodeid, uname, pcmk_strerror(rc), rc); + } else { + api_log(LOG_NOTICE, "Node %u/%s kicked: %s", nodeid, uname, action); + } + } + + stonith_api_delete(st); + return rc; +} + +time_t +stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress) +{ + int rc = pcmk_ok; + time_t when = 0; + stonith_t *st = stonith_api_new(); + stonith_history_t *history = NULL, *hp = NULL; + + if (st == NULL) { + api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: " + "API initialization failed", nodeid, uname); + return when; + } + + rc = st->cmds->connect(st, "stonith-api", NULL); + if (rc != pcmk_ok) { + api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc); + } else { + int entries = 0; + int progress = 0; + int completed = 0; + int opts = 0; + char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname); + + stonith__set_call_options(opts, name, st_opt_sync_call); + if ((uname == NULL) && (nodeid > 0)) { + stonith__set_call_options(opts, name, st_opt_cs_nodeid); + } + rc = st->cmds->history(st, opts, name, &history, 120); + free(name); + + for (hp = history; hp; hp = hp->next) { + entries++; + if (in_progress) { + progress++; + if (hp->state != st_done && hp->state != st_failed) { + when = time(NULL); + } + + } else if (hp->state == st_done) { + completed++; + if (hp->completed > when) { + when = hp->completed; + } + } + } + + stonith_history_free(history); + + if(rc == pcmk_ok) { + api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed); + } else { + api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc); + } + } + + stonith_api_delete(st); + + if(when) { + api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when); + } + return when; +} + +bool +stonith_agent_exists(const char *agent, int timeout) +{ + stonith_t *st = NULL; + stonith_key_value_t *devices = NULL; + stonith_key_value_t *dIter = NULL; + bool rc = FALSE; + + if (agent == NULL) { + return rc; + } + + st = stonith_api_new(); + if (st == NULL) { + crm_err("Could not list fence agents: API memory allocation failed"); + return FALSE; + } + st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout == 0 ? 120 : timeout); + + for (dIter = devices; dIter != NULL; dIter = dIter->next) { + if (pcmk__str_eq(dIter->value, agent, pcmk__str_none)) { + rc = TRUE; + break; + } + } + + stonith_key_value_freeall(devices, 1, 1); + stonith_api_delete(st); + return rc; +} + +const char * +stonith_action_str(const char *action) +{ + if (action == NULL) { + return "fencing"; + } else if (!strcmp(action, "on")) { + return "unfencing"; + } else if (!strcmp(action, "off")) { + return "turning off"; + } else { + return action; + } +} + +/*! + * \internal + * \brief Parse a target name from one line of a target list string + * + * \param[in] line One line of a target list string + * \param[in] len String length of line + * \param[in,out] output List to add newly allocated target name to + */ +static void +parse_list_line(const char *line, int len, GList **output) +{ + size_t i = 0; + size_t entry_start = 0; + + /* Skip complaints about additional parameters device doesn't understand + * + * @TODO Document or eliminate the implied restriction of target names + */ + if (strstr(line, "invalid") || strstr(line, "variable")) { + crm_debug("Skipping list output line: %s", line); + return; + } + + // Process line content, character by character + for (i = 0; i <= len; i++) { + + if (isspace(line[i]) || (line[i] == ',') || (line[i] == ';') + || (line[i] == '\0')) { + // We've found a separator (i.e. the end of an entry) + + int rc = 0; + char *entry = NULL; + + if (i == entry_start) { + // Skip leading and sequential separators + entry_start = i + 1; + continue; + } + + entry = calloc(i - entry_start + 1, sizeof(char)); + CRM_ASSERT(entry != NULL); + + /* Read entry, stopping at first separator + * + * @TODO Document or eliminate these character restrictions + */ + rc = sscanf(line + entry_start, "%[a-zA-Z0-9_-.]", entry); + if (rc != 1) { + crm_warn("Could not parse list output entry: %s " + CRM_XS " entry_start=%d position=%d", + line + entry_start, entry_start, i); + free(entry); + + } else if (pcmk__strcase_any_of(entry, "on", "off", NULL)) { + /* Some agents print the target status in the list output, + * though none are known now (the separate list-status command + * is used for this, but it can also print "UNKNOWN"). To handle + * this possibility, skip such entries. + * + * @TODO Document or eliminate the implied restriction of target + * names. + */ + free(entry); + + } else { + // We have a valid entry + *output = g_list_append(*output, entry); + } + entry_start = i + 1; + } + } +} + +/*! + * \internal + * \brief Parse a list of targets from a string + * + * \param[in] list_output Target list as a string + * + * \return List of target names + * \note The target list string format is flexible, to allow for user-specified + * lists such pcmk_host_list and the output of an agent's list action + * (whether direct or via the API, which escapes newlines). There may be + * multiple lines, separated by either a newline or an escaped newline + * (backslash n). Each line may have one or more target names, separated + * by any combination of whitespace, commas, and semi-colons. Lines + * containing "invalid" or "variable" will be ignored entirely. Target + * names "on" or "off" (case-insensitive) will be ignored. Target names + * may contain only alphanumeric characters, underbars (_), dashes (-), + * and dots (.) (if any other character occurs in the name, it and all + * subsequent characters in the name will be ignored). + * \note The caller is responsible for freeing the result with + * g_list_free_full(result, free). + */ +GList * +stonith__parse_targets(const char *target_spec) +{ + GList *targets = NULL; + + if (target_spec != NULL) { + size_t out_len = strlen(target_spec); + size_t line_start = 0; // Starting index of line being processed + + for (size_t i = 0; i <= out_len; ++i) { + if ((target_spec[i] == '\n') || (target_spec[i] == '\0') + || ((target_spec[i] == '\\') && (target_spec[i + 1] == 'n'))) { + // We've reached the end of one line of output + + int len = i - line_start; + + if (len > 0) { + char *line = strndup(target_spec + line_start, len); + + line[len] = '\0'; // Because it might be a newline + parse_list_line(line, len, &targets); + free(line); + } + if (target_spec[i] == '\\') { + ++i; // backslash-n takes up two positions + } + line_start = i + 1; + } + } + } + return targets; +} + +/*! + * \internal + * \brief Check whether a fencing failure was followed by an equivalent success + * + * \param[in] event Fencing failure + * \param[in] top_history Complete fencing history (must be sorted by + * stonith__sort_history() beforehand) + * + * \return The name of the node that executed the fencing if a later successful + * event exists, or NULL if no such event exists + */ +const char * +stonith__later_succeeded(const stonith_history_t *event, + const stonith_history_t *top_history) +{ + const char *other = NULL; + + for (const stonith_history_t *prev_hp = top_history; + prev_hp != NULL; prev_hp = prev_hp->next) { + if (prev_hp == event) { + break; + } + if ((prev_hp->state == st_done) && + pcmk__str_eq(event->target, prev_hp->target, pcmk__str_casei) && + pcmk__str_eq(event->action, prev_hp->action, pcmk__str_none) && + ((event->completed < prev_hp->completed) || + ((event->completed == prev_hp->completed) && (event->completed_nsec < prev_hp->completed_nsec)))) { + + if ((event->delegate == NULL) + || pcmk__str_eq(event->delegate, prev_hp->delegate, + pcmk__str_casei)) { + // Prefer equivalent fencing by same executioner + return prev_hp->delegate; + + } else if (other == NULL) { + // Otherwise remember first successful executioner + other = (prev_hp->delegate == NULL)? "some node" : prev_hp->delegate; + } + } + } + return other; +} + +/*! + * \internal + * \brief Sort fencing history, pending first then by most recently completed + * + * \param[in,out] history List of stonith actions + * + * \return New head of sorted \p history + */ +stonith_history_t * +stonith__sort_history(stonith_history_t *history) +{ + stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp; + + for (hp = history; hp; ) { + tmp = hp->next; + if ((hp->state == st_done) || (hp->state == st_failed)) { + /* sort into new */ + if ((!new) || (hp->completed > new->completed) || + ((hp->completed == new->completed) && (hp->completed_nsec > new->completed_nsec))) { + hp->next = new; + new = hp; + } else { + np = new; + do { + if ((!np->next) || (hp->completed > np->next->completed) || + ((hp->completed == np->next->completed) && (hp->completed_nsec > np->next->completed_nsec))) { + hp->next = np->next; + np->next = hp; + break; + } + np = np->next; + } while (1); + } + } else { + /* put into pending */ + hp->next = pending; + pending = hp; + } + hp = tmp; + } + + /* pending actions don't have a completed-stamp so make them go front */ + if (pending) { + stonith_history_t *last_pending = pending; + + while (last_pending->next) { + last_pending = last_pending->next; + } + + last_pending->next = new; + new = pending; + } + return new; +} + +/*! + * \brief Return string equivalent of an operation state value + * + * \param[in] state Fencing operation state value + * + * \return Human-friendly string equivalent of state + */ +const char * +stonith_op_state_str(enum op_state state) +{ + switch (state) { + case st_query: return "querying"; + case st_exec: return "executing"; + case st_done: return "completed"; + case st_duplicate: return "duplicate"; + case st_failed: return "failed"; + } + return "unknown"; +} + +stonith_history_t * +stonith__first_matching_event(stonith_history_t *history, + bool (*matching_fn)(stonith_history_t *, void *), + void *user_data) +{ + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (matching_fn(hp, user_data)) { + return hp; + } + } + + return NULL; +} + +bool +stonith__event_state_pending(stonith_history_t *history, void *user_data) +{ + return history->state != st_failed && history->state != st_done; +} + +bool +stonith__event_state_eq(stonith_history_t *history, void *user_data) +{ + return history->state == GPOINTER_TO_INT(user_data); +} + +bool +stonith__event_state_neq(stonith_history_t *history, void *user_data) +{ + return history->state != GPOINTER_TO_INT(user_data); +} + +void +stonith__device_parameter_flags(uint32_t *device_flags, const char *device_name, + xmlNode *metadata) +{ + xmlXPathObjectPtr xpath = NULL; + int max = 0; + int lpc = 0; + + CRM_CHECK((device_flags != NULL) && (metadata != NULL), return); + + xpath = xpath_search(metadata, "//parameter"); + max = numXpathResults(xpath); + + if (max <= 0) { + freeXpathObject(xpath); + return; + } + + for (lpc = 0; lpc < max; lpc++) { + const char *parameter = NULL; + xmlNode *match = getXpathResult(xpath, lpc); + + CRM_LOG_ASSERT(match != NULL); + if (match == NULL) { + continue; + } + + parameter = crm_element_value(match, "name"); + + if (pcmk__str_eq(parameter, "plug", pcmk__str_casei)) { + stonith__set_device_flags(*device_flags, device_name, + st_device_supports_parameter_plug); + + } else if (pcmk__str_eq(parameter, "port", pcmk__str_casei)) { + stonith__set_device_flags(*device_flags, device_name, + st_device_supports_parameter_port); + } + } + + freeXpathObject(xpath); +} + +/*! + * \internal + * \brief Retrieve fence agent meta-data asynchronously + * + * \param[in] agent Agent to execute + * \param[in] timeout_sec Error if not complete within this time + * \param[in] callback Function to call with result (this will always be + * called, whether by this function directly or + * later via the main loop, and on success the + * metadata will be in its result argument's + * action_stdout) + * \param[in,out] user_data User data to pass to callback + * + * \return Standard Pacemaker return code + * \note The caller must use a main loop. This function is not a + * stonith_api_operations_t method because it does not need a stonith_t + * object and does not go through the fencer, but executes the agent + * directly. + */ +int +stonith__metadata_async(const char *agent, int timeout_sec, + void (*callback)(int pid, + const pcmk__action_result_t *result, + void *user_data), + void *user_data) +{ + switch (stonith_get_namespace(agent, NULL)) { + case st_namespace_rhcs: + { + stonith_action_t *action = NULL; + int rc = pcmk_ok; + + action = stonith__action_create(agent, "metadata", NULL, 0, + timeout_sec, NULL, NULL, NULL); + + rc = stonith__execute_async(action, user_data, callback, NULL); + if (rc != pcmk_ok) { + callback(0, stonith__action_result(action), user_data); + stonith__destroy_action(action); + } + return pcmk_legacy2rc(rc); + } + +#if HAVE_STONITH_STONITH_H + case st_namespace_lha: + // LHA metadata is simply synthesized, so simulate async + { + pcmk__action_result_t result = { + .exit_status = CRM_EX_OK, + .execution_status = PCMK_EXEC_DONE, + .exit_reason = NULL, + .action_stdout = NULL, + .action_stderr = NULL, + }; + + stonith__lha_metadata(agent, timeout_sec, + &result.action_stdout); + callback(0, &result, user_data); + pcmk__reset_result(&result); + return pcmk_rc_ok; + } +#endif + + default: + { + pcmk__action_result_t result = { + .exit_status = CRM_EX_NOSUCH, + .execution_status = PCMK_EXEC_ERROR_HARD, + .exit_reason = crm_strdup_printf("No such agent '%s'", + agent), + .action_stdout = NULL, + .action_stderr = NULL, + }; + + callback(0, &result, user_data); + pcmk__reset_result(&result); + return ENOENT; + } + } +} + +/*! + * \internal + * \brief Return the exit status from an async action callback + * + * \param[in] data Callback data + * + * \return Exit status from callback data + */ +int +stonith__exit_status(const stonith_callback_data_t *data) +{ + if ((data == NULL) || (data->opaque == NULL)) { + return CRM_EX_ERROR; + } + return ((pcmk__action_result_t *) data->opaque)->exit_status; +} + +/*! + * \internal + * \brief Return the execution status from an async action callback + * + * \param[in] data Callback data + * + * \return Execution status from callback data + */ +int +stonith__execution_status(const stonith_callback_data_t *data) +{ + if ((data == NULL) || (data->opaque == NULL)) { + return PCMK_EXEC_UNKNOWN; + } + return ((pcmk__action_result_t *) data->opaque)->execution_status; +} + +/*! + * \internal + * \brief Return the exit reason from an async action callback + * + * \param[in] data Callback data + * + * \return Exit reason from callback data + */ +const char * +stonith__exit_reason(const stonith_callback_data_t *data) +{ + if ((data == NULL) || (data->opaque == NULL)) { + return NULL; + } + return ((pcmk__action_result_t *) data->opaque)->exit_reason; +} + +/*! + * \internal + * \brief Return the exit status from an event notification + * + * \param[in] event Event + * + * \return Exit status from event + */ +int +stonith__event_exit_status(const stonith_event_t *event) +{ + if ((event == NULL) || (event->opaque == NULL)) { + return CRM_EX_ERROR; + } else { + struct event_private *event_private = event->opaque; + + return event_private->result.exit_status; + } +} + +/*! + * \internal + * \brief Return the execution status from an event notification + * + * \param[in] event Event + * + * \return Execution status from event + */ +int +stonith__event_execution_status(const stonith_event_t *event) +{ + if ((event == NULL) || (event->opaque == NULL)) { + return PCMK_EXEC_UNKNOWN; + } else { + struct event_private *event_private = event->opaque; + + return event_private->result.execution_status; + } +} + +/*! + * \internal + * \brief Return the exit reason from an event notification + * + * \param[in] event Event + * + * \return Exit reason from event + */ +const char * +stonith__event_exit_reason(const stonith_event_t *event) +{ + if ((event == NULL) || (event->opaque == NULL)) { + return NULL; + } else { + struct event_private *event_private = event->opaque; + + return event_private->result.exit_reason; + } +} + +/*! + * \internal + * \brief Return a human-friendly description of a fencing event + * + * \param[in] event Event to describe + * + * \return Newly allocated string with description of \p event + * \note The caller is responsible for freeing the return value. + * This function asserts on memory errors and never returns NULL. + */ +char * +stonith__event_description(const stonith_event_t *event) +{ + // Use somewhat readable defaults + const char *origin = pcmk__s(event->client_origin, "a client"); + const char *origin_node = pcmk__s(event->origin, "a node"); + const char *executioner = pcmk__s(event->executioner, "the cluster"); + const char *device = pcmk__s(event->device, "unknown"); + const char *action = pcmk__s(event->action, event->operation); + const char *target = pcmk__s(event->target, "no node"); + const char *reason = stonith__event_exit_reason(event); + const char *status; + + if (action == NULL) { + action = "(unknown)"; + } + + if (stonith__event_execution_status(event) != PCMK_EXEC_DONE) { + status = pcmk_exec_status_str(stonith__event_execution_status(event)); + } else if (stonith__event_exit_status(event) != CRM_EX_OK) { + status = pcmk_exec_status_str(PCMK_EXEC_ERROR); + } else { + status = crm_exit_str(CRM_EX_OK); + } + + if (pcmk__str_eq(event->operation, T_STONITH_NOTIFY_HISTORY, + pcmk__str_none)) { + return crm_strdup_printf("Fencing history may have changed"); + + } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_ADD, + pcmk__str_none)) { + return crm_strdup_printf("A fencing device (%s) was added", device); + + } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_DEL, + pcmk__str_none)) { + return crm_strdup_printf("A fencing device (%s) was removed", device); + + } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_ADD, + pcmk__str_none)) { + return crm_strdup_printf("A fencing topology level (%s) was added", + device); + + } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_DEL, + pcmk__str_none)) { + return crm_strdup_printf("A fencing topology level (%s) was removed", + device); + } + + // event->operation should be T_STONITH_NOTIFY_FENCE at this point + + return crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s%s%s%s (ref=%s)", + action, target, executioner, origin, origin_node, + status, + ((reason == NULL)? "" : " ("), pcmk__s(reason, ""), + ((reason == NULL)? "" : ")"), + pcmk__s(event->id, "(none)")); +} + + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +const char *get_stonith_provider(const char *agent, const char *provider); + +const char * +get_stonith_provider(const char *agent, const char *provider) +{ + return stonith_namespace2text(stonith_get_namespace(agent, provider)); +} + +// LCOV_EXCL_STOP +// End deprecated API diff --git a/lib/fencing/st_lha.c b/lib/fencing/st_lha.c new file mode 100644 index 0000000..d477ded --- /dev/null +++ b/lib/fencing/st_lha.c @@ -0,0 +1,279 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <glib.h> +#include <dlfcn.h> + +#include <crm/crm.h> +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> +#include <crm/msg_xml.h> +#include <crm/common/xml.h> + +#include <stonith/stonith.h> + +#include "fencing_private.h" + +#define LHA_STONITH_LIBRARY "libstonith.so.1" + +static void *lha_agents_lib = NULL; + +static const char META_TEMPLATE[] = + "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" + "<resource-agent name=\"%s\">\n" + " <version>1.0</version>\n" + " <longdesc lang=\"en\">\n" + "%s\n" + " </longdesc>\n" + " <shortdesc lang=\"en\">%s</shortdesc>\n" + "%s\n" + " <actions>\n" + " <action name=\"start\" timeout=\"20\" />\n" + " <action name=\"stop\" timeout=\"15\" />\n" + " <action name=\"status\" timeout=\"20\" />\n" + " <action name=\"monitor\" timeout=\"20\" interval=\"3600\"/>\n" + " <action name=\"meta-data\" timeout=\"15\" />\n" + " </actions>\n" + " <special tag=\"heartbeat\">\n" + " <version>2.0</version>\n" " </special>\n" "</resource-agent>\n"; + +static void * +find_library_function(void **handle, const char *lib, const char *fn) +{ + void *a_function; + + if (*handle == NULL) { + *handle = dlopen(lib, RTLD_LAZY); + if ((*handle) == NULL) { + crm_err("Could not open %s: %s", lib, dlerror()); + return NULL; + } + } + + a_function = dlsym(*handle, fn); + if (a_function == NULL) { + crm_err("Could not find %s in %s: %s", fn, lib, dlerror()); + } + + return a_function; +} + +/*! + * \internal + * \brief Check whether a given fence agent is an LHA agent + * + * \param[in] agent Fence agent type + * + * \return true if \p agent is an LHA agent, otherwise false + */ +bool +stonith__agent_is_lha(const char *agent) +{ + Stonith *stonith_obj = NULL; + + static bool need_init = true; + static Stonith *(*st_new_fn) (const char *) = NULL; + static void (*st_del_fn) (Stonith *) = NULL; + + if (need_init) { + need_init = false; + st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_new"); + st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_delete"); + } + + if (lha_agents_lib && st_new_fn && st_del_fn) { + stonith_obj = (*st_new_fn) (agent); + if (stonith_obj) { + (*st_del_fn) (stonith_obj); + return true; + } + } + return false; +} + +int +stonith__list_lha_agents(stonith_key_value_t **devices) +{ + static gboolean need_init = TRUE; + + int count = 0; + char **entry = NULL; + char **type_list = NULL; + static char **(*type_list_fn) (void) = NULL; + static void (*type_free_fn) (char **) = NULL; + + if (need_init) { + need_init = FALSE; + type_list_fn = find_library_function(&lha_agents_lib, + LHA_STONITH_LIBRARY, + "stonith_types"); + type_free_fn = find_library_function(&lha_agents_lib, + LHA_STONITH_LIBRARY, + "stonith_free_hostlist"); + } + + if (type_list_fn) { + type_list = (*type_list_fn) (); + } + + for (entry = type_list; entry != NULL && *entry; ++entry) { + crm_trace("Added: %s", *entry); + *devices = stonith_key_value_add(*devices, NULL, *entry); + count++; + } + if (type_list && type_free_fn) { + (*type_free_fn) (type_list); + } + return count; +} + +static void +stonith_plugin(int priority, const char *fmt, ...) G_GNUC_PRINTF(2, 3); + +static void +stonith_plugin(int priority, const char *format, ...) +{ + int err = errno; + + va_list ap; + int len = 0; + char *string = NULL; + + va_start(ap, format); + + len = vasprintf (&string, format, ap); + va_end(ap); + CRM_ASSERT(len > 0); + + do_crm_log_alias(priority, __FILE__, __func__, __LINE__, "%s", string); + + free(string); + errno = err; +} + +int +stonith__lha_metadata(const char *agent, int timeout, char **output) +{ + int rc = 0; + char *buffer = NULL; + static const char *no_parameter_info = "<!-- no value -->"; + + Stonith *stonith_obj = NULL; + + static gboolean need_init = TRUE; + static Stonith *(*st_new_fn) (const char *) = NULL; + static const char *(*st_info_fn) (Stonith *, int) = NULL; + static void (*st_del_fn) (Stonith *) = NULL; + static void (*st_log_fn) (Stonith *, PILLogFun) = NULL; + + if (need_init) { + need_init = FALSE; + st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_new"); + st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_delete"); + st_log_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_set_log"); + st_info_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, + "stonith_get_info"); + } + + if (lha_agents_lib && st_new_fn && st_del_fn && st_info_fn && st_log_fn) { + char *xml_meta_longdesc = NULL; + char *xml_meta_shortdesc = NULL; + + char *meta_param = NULL; + char *meta_longdesc = NULL; + char *meta_shortdesc = NULL; + + stonith_obj = (*st_new_fn) (agent); + if (stonith_obj) { + (*st_log_fn) (stonith_obj, (PILLogFun) & stonith_plugin); + pcmk__str_update(&meta_longdesc, + (*st_info_fn) (stonith_obj, ST_DEVICEDESCR)); + if (meta_longdesc == NULL) { + crm_warn("no long description in %s's metadata.", agent); + meta_longdesc = strdup(no_parameter_info); + } + + pcmk__str_update(&meta_shortdesc, + (*st_info_fn) (stonith_obj, ST_DEVICEID)); + if (meta_shortdesc == NULL) { + crm_warn("no short description in %s's metadata.", agent); + meta_shortdesc = strdup(no_parameter_info); + } + + pcmk__str_update(&meta_param, + (*st_info_fn) (stonith_obj, ST_CONF_XML)); + if (meta_param == NULL) { + crm_warn("no list of parameters in %s's metadata.", agent); + meta_param = strdup(no_parameter_info); + } + (*st_del_fn) (stonith_obj); + } else { + errno = EINVAL; + crm_perror(LOG_ERR, "Agent %s not found", agent); + return -EINVAL; + } + + xml_meta_longdesc = + (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc); + xml_meta_shortdesc = + (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc); + + buffer = crm_strdup_printf(META_TEMPLATE, agent, xml_meta_longdesc, + xml_meta_shortdesc, meta_param); + + xmlFree(xml_meta_longdesc); + xmlFree(xml_meta_shortdesc); + + free(meta_shortdesc); + free(meta_longdesc); + free(meta_param); + } + if (output) { + *output = buffer; + } else { + free(buffer); + } + return rc; +} + +/* Implement a dummy function that uses -lpils so that linkers don't drop the + * reference. + */ + +#include <pils/plugin.h> + +const char *i_hate_pils(int rc); + +const char * +i_hate_pils(int rc) +{ + return PIL_strerror(rc); +} + +int +stonith__lha_validate(stonith_t *st, int call_options, const char *target, + const char *agent, GHashTable *params, int timeout, + char **output, char **error_output) +{ + errno = EOPNOTSUPP; + crm_perror(LOG_ERR, "Cannot validate Linux-HA fence agents"); + return -EOPNOTSUPP; +} diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c new file mode 100644 index 0000000..a0ce8e7 --- /dev/null +++ b/lib/fencing/st_output.c @@ -0,0 +1,600 @@ +/* + * Copyright 2019-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> +#include <stdarg.h> +#include <stdint.h> + +#include <crm/stonith-ng.h> +#include <crm/msg_xml.h> +#include <crm/common/iso8601.h> +#include <crm/common/util.h> +#include <crm/common/xml.h> +#include <crm/common/output.h> +#include <crm/common/output_internal.h> +#include <crm/common/xml_internal.h> +#include <crm/fencing/internal.h> +#include <crm/pengine/internal.h> + +/*! + * \internal + * \brief Convert seconds and nanoseconds to a date/time/time-zone string + * + * \param[in] sec Seconds + * \param[in] nsec Nanoseconds + * \param[in] show_usec Whether to show time in microseconds resolution (if + * false, use seconds resolution) + * + * \return A string representation of \p sec and \nsec + * + * \note The caller is responsible for freeing the return value using \p free(). + */ +static char * +timespec_string(time_t sec, long nsec, bool show_usec) { + const struct timespec ts = { + .tv_sec = sec, + .tv_nsec = nsec, + }; + + return pcmk__timespec2str(&ts, + crm_time_log_date + |crm_time_log_timeofday + |crm_time_log_with_timezone + |(show_usec? crm_time_usecs : 0)); +} + +/*! + * \internal + * \brief Return a status-friendly description of fence history entry state + * + * \param[in] history Fence history entry to describe + * + * \return One-word description of history entry state + * \note This is similar to stonith_op_state_str() except user-oriented (i.e. + * for cluster status) instead of developer-oriented (for debug logs). + */ +static const char * +state_str(const stonith_history_t *history) +{ + switch (history->state) { + case st_failed: return "failed"; + case st_done: return "successful"; + default: return "pending"; + } +} + +/*! + * \internal + * \brief Create a description of a fencing history entry for status displays + * + * \param[in] history Fencing history entry to describe + * \param[in] full_history Whether this is for full or condensed history + * \param[in] later_succeeded Node that a later equivalent attempt succeeded + * from, or NULL if none + * \param[in] show_opts Flag group of pcmk_show_opt_e + * + * \return Newly created string with fencing history entry description + * + * \note The caller is responsible for freeing the return value with g_free(). + * \note This is similar to stonith__event_description(), except this is used + * for history entries (stonith_history_t) in status displays rather than + * event notifications (stonith_event_t) in log messages. + */ +gchar * +stonith__history_description(const stonith_history_t *history, + bool full_history, const char *later_succeeded, + uint32_t show_opts) +{ + GString *str = g_string_sized_new(256); // Generous starting size + char *completed_time_s = NULL; + + if ((history->state == st_failed) || (history->state == st_done)) { + completed_time_s = timespec_string(history->completed, + history->completed_nsec, true); + } + + pcmk__g_strcat(str, + stonith_action_str(history->action), " of ", history->target, + NULL); + + if (!pcmk_is_set(show_opts, pcmk_show_failed_detail)) { + // More human-friendly + if (((history->state == st_failed) || (history->state == st_done)) + && (history->delegate != NULL)) { + + pcmk__g_strcat(str, " by ", history->delegate, NULL); + } + pcmk__g_strcat(str, " for ", history->client, "@", history->origin, + NULL); + if (!full_history) { + g_string_append(str, " last"); // For example, "last failed at ..." + } + } + + pcmk__add_word(&str, 0, state_str(history)); + + // For failed actions, add exit reason if available + if ((history->state == st_failed) && (history->exit_reason != NULL)) { + pcmk__g_strcat(str, " (", history->exit_reason, ")", NULL); + } + + if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) { + // More technical + g_string_append(str, ": "); + + // For completed actions, add delegate if available + if (((history->state == st_failed) || (history->state == st_done)) + && (history->delegate != NULL)) { + + pcmk__g_strcat(str, "delegate=", history->delegate, ", ", NULL); + } + + // Add information about originator + pcmk__g_strcat(str, + "client=", history->client, ", origin=", history->origin, + NULL); + + // For completed actions, add completion time + if (completed_time_s != NULL) { + if (full_history) { + g_string_append(str, ", completed"); + } else if (history->state == st_failed) { + g_string_append(str, ", last-failed"); + } else { + g_string_append(str, ", last-successful"); + } + pcmk__g_strcat(str, "='", completed_time_s, "'", NULL); + } + } else if (completed_time_s != NULL) { + // More human-friendly + pcmk__g_strcat(str, " at ", completed_time_s, NULL); + } + + if ((history->state == st_failed) && (later_succeeded != NULL)) { + pcmk__g_strcat(str, + " (a later attempt from ", later_succeeded, + " succeeded)", NULL); + } + + free(completed_time_s); + return g_string_free(str, FALSE); +} + +PCMK__OUTPUT_ARGS("failed-fencing-list", "stonith_history_t *", "GList *", + "uint32_t", "uint32_t", "bool") +static int +failed_history(pcmk__output_t *out, va_list args) +{ + stonith_history_t *history = va_arg(args, stonith_history_t *); + GList *only_node = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (hp->state != st_failed) { + continue; + } + + if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Fencing Actions"); + out->message(out, "stonith-event", hp, + pcmk_all_flags_set(section_opts, pcmk_section_fencing_all), + false, stonith__later_succeeded(hp, history), show_opts); + out->increment_list(out); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("fencing-list", "stonith_history_t *", "GList *", "uint32_t", + "uint32_t", "bool") +static int +stonith_history(pcmk__output_t *out, va_list args) +{ + stonith_history_t *history = va_arg(args, stonith_history_t *); + GList *only_node = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + if (hp->state != st_failed) { + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History"); + out->message(out, "stonith-event", hp, + pcmk_all_flags_set(section_opts, + pcmk_section_fencing_all), + false, stonith__later_succeeded(hp, history), show_opts); + out->increment_list(out); + } + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *", + "GList *", "uint32_t", "uint32_t", "bool") +static int +full_history(pcmk__output_t *out, va_list args) +{ + crm_exit_t history_rc G_GNUC_UNUSED = va_arg(args, crm_exit_t); + stonith_history_t *history = va_arg(args, stonith_history_t *); + GList *only_node = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Fencing History"); + out->message(out, "stonith-event", hp, + pcmk_all_flags_set(section_opts, pcmk_section_fencing_all), + false, stonith__later_succeeded(hp, history), show_opts); + out->increment_list(out); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("full-fencing-list", "crm_exit_t", "stonith_history_t *", + "GList *", "uint32_t", "uint32_t", "bool") +static int +full_history_xml(pcmk__output_t *out, va_list args) +{ + crm_exit_t history_rc = va_arg(args, crm_exit_t); + stonith_history_t *history = va_arg(args, stonith_history_t *); + GList *only_node = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer G_GNUC_UNUSED = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + if (history_rc == 0) { + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Fencing History"); + out->message(out, "stonith-event", hp, + pcmk_all_flags_set(section_opts, + pcmk_section_fencing_all), + false, stonith__later_succeeded(hp, history), show_opts); + out->increment_list(out); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + } else { + char *rc_s = pcmk__itoa(history_rc); + + pcmk__output_create_xml_node(out, "fence_history", + "status", rc_s, + NULL); + free(rc_s); + + rc = pcmk_rc_ok; + } + + return rc; +} + +PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t") +static int +last_fenced_html(pcmk__output_t *out, va_list args) { + const char *target = va_arg(args, const char *); + time_t when = va_arg(args, time_t); + + if (when) { + char *buf = crm_strdup_printf("Node %s last fenced at: %s", target, ctime(&when)); + pcmk__output_create_html_node(out, "div", NULL, NULL, buf); + free(buf); + return pcmk_rc_ok; + } else { + return pcmk_rc_no_output; + } +} + +PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t") +static int +last_fenced_text(pcmk__output_t *out, va_list args) { + const char *target = va_arg(args, const char *); + time_t when = va_arg(args, time_t); + + if (when) { + pcmk__indented_printf(out, "Node %s last fenced at: %s", target, ctime(&when)); + } else { + pcmk__indented_printf(out, "Node %s has never been fenced\n", target); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("last-fenced", "const char *", "time_t") +static int +last_fenced_xml(pcmk__output_t *out, va_list args) { + const char *target = va_arg(args, const char *); + time_t when = va_arg(args, time_t); + + if (when) { + char *buf = timespec_string(when, 0, false); + + pcmk__output_create_xml_node(out, "last-fenced", + "target", target, + "when", buf, + NULL); + + free(buf); + return pcmk_rc_ok; + } else { + return pcmk_rc_no_output; + } +} + +PCMK__OUTPUT_ARGS("pending-fencing-list", "stonith_history_t *", "GList *", + "uint32_t", "uint32_t", "bool") +static int +pending_actions(pcmk__output_t *out, va_list args) +{ + stonith_history_t *history = va_arg(args, stonith_history_t *); + GList *only_node = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + for (stonith_history_t *hp = history; hp; hp = hp->next) { + if (!pcmk__str_in_list(hp->target, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + /* Skip the rest of the history after we see a failed/done action */ + if ((hp->state == st_failed) || (hp->state == st_done)) { + break; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Pending Fencing Actions"); + out->message(out, "stonith-event", hp, + pcmk_all_flags_set(section_opts, pcmk_section_fencing_all), + false, stonith__later_succeeded(hp, history), show_opts); + out->increment_list(out); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool", + "const char *", "uint32_t") +static int +stonith_event_html(pcmk__output_t *out, va_list args) +{ + stonith_history_t *event = va_arg(args, stonith_history_t *); + bool full_history = va_arg(args, int); + bool completed_only G_GNUC_UNUSED = va_arg(args, int); + const char *succeeded = va_arg(args, const char *); + uint32_t show_opts = va_arg(args, uint32_t); + + gchar *desc = stonith__history_description(event, full_history, succeeded, + show_opts); + + switch(event->state) { + case st_done: + out->list_item(out, "successful-stonith-event", "%s", desc); + break; + + case st_failed: + out->list_item(out, "failed-stonith-event", "%s", desc); + break; + + default: + out->list_item(out, "pending-stonith-event", "%s", desc); + break; + } + g_free(desc); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool", + "const char *", "uint32_t") +static int +stonith_event_text(pcmk__output_t *out, va_list args) +{ + stonith_history_t *event = va_arg(args, stonith_history_t *); + bool full_history = va_arg(args, int); + bool completed_only = va_arg(args, int); + const char *succeeded = va_arg(args, const char *); + uint32_t show_opts = va_arg(args, uint32_t); + + if (completed_only) { + pcmk__formatted_printf(out, "%lld\n", (long long) event->completed); + } else { + gchar *desc = stonith__history_description(event, full_history, succeeded, + show_opts); + + pcmk__indented_printf(out, "%s\n", desc); + g_free(desc); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("stonith-event", "stonith_history_t *", "bool", "bool", + "const char *", "uint32_t") +static int +stonith_event_xml(pcmk__output_t *out, va_list args) +{ + stonith_history_t *event = va_arg(args, stonith_history_t *); + bool full_history G_GNUC_UNUSED = va_arg(args, int); + bool completed_only G_GNUC_UNUSED = va_arg(args, int); + const char *succeeded G_GNUC_UNUSED = va_arg(args, const char *); + uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "fence_event", + "action", event->action, + "target", event->target, + "client", event->client, + "origin", event->origin, + NULL); + + switch (event->state) { + case st_failed: + pcmk__xe_set_props(node, "status", "failed", + XML_LRM_ATTR_EXIT_REASON, event->exit_reason, + NULL); + break; + + case st_done: + crm_xml_add(node, "status", "success"); + break; + + default: { + char *state = pcmk__itoa(event->state); + pcmk__xe_set_props(node, "status", "pending", + "extended-status", state, + NULL); + free(state); + break; + } + } + + if (event->delegate != NULL) { + crm_xml_add(node, "delegate", event->delegate); + } + + if ((event->state == st_failed) || (event->state == st_done)) { + char *time_s = timespec_string(event->completed, event->completed_nsec, + true); + + crm_xml_add(node, "completed", time_s); + free(time_s); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *", + "const char *", "int") +static int +validate_agent_html(pcmk__output_t *out, va_list args) { + const char *agent = va_arg(args, const char *); + const char *device = va_arg(args, const char *); + const char *output = va_arg(args, const char *); + const char *error_output = va_arg(args, const char *); + int rc = va_arg(args, int); + + if (device) { + char *buf = crm_strdup_printf("Validation of %s on %s %s", agent, device, + rc ? "failed" : "succeeded"); + pcmk__output_create_html_node(out, "div", NULL, NULL, buf); + free(buf); + } else { + char *buf = crm_strdup_printf("Validation of %s %s", agent, + rc ? "failed" : "succeeded"); + pcmk__output_create_html_node(out, "div", NULL, NULL, buf); + free(buf); + } + + out->subprocess_output(out, rc, output, error_output); + return rc; +} + +PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *", + "const char *", "int") +static int +validate_agent_text(pcmk__output_t *out, va_list args) { + const char *agent = va_arg(args, const char *); + const char *device = va_arg(args, const char *); + const char *output = va_arg(args, const char *); + const char *error_output = va_arg(args, const char *); + int rc = va_arg(args, int); + + if (device) { + pcmk__indented_printf(out, "Validation of %s on %s %s\n", agent, device, + rc ? "failed" : "succeeded"); + } else { + pcmk__indented_printf(out, "Validation of %s %s\n", agent, + rc ? "failed" : "succeeded"); + } + + out->subprocess_output(out, rc, output, error_output); + return rc; +} + +PCMK__OUTPUT_ARGS("validate", "const char *", "const char *", "const char *", + "const char *", "int") +static int +validate_agent_xml(pcmk__output_t *out, va_list args) { + const char *agent = va_arg(args, const char *); + const char *device = va_arg(args, const char *); + const char *output = va_arg(args, const char *); + const char *error_output = va_arg(args, const char *); + int rc = va_arg(args, int); + + xmlNodePtr node = pcmk__output_create_xml_node( + out, "validate", "agent", agent, "valid", pcmk__btoa(rc == pcmk_ok), + NULL); + + if (device != NULL) { + crm_xml_add(node, "device", device); + } + + pcmk__output_xml_push_parent(out, node); + out->subprocess_output(out, rc, output, error_output); + pcmk__output_xml_pop_parent(out); + + return rc; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "failed-fencing-list", "default", failed_history }, + { "fencing-list", "default", stonith_history }, + { "full-fencing-list", "default", full_history }, + { "full-fencing-list", "xml", full_history_xml }, + { "last-fenced", "html", last_fenced_html }, + { "last-fenced", "log", last_fenced_text }, + { "last-fenced", "text", last_fenced_text }, + { "last-fenced", "xml", last_fenced_xml }, + { "pending-fencing-list", "default", pending_actions }, + { "stonith-event", "html", stonith_event_html }, + { "stonith-event", "log", stonith_event_text }, + { "stonith-event", "text", stonith_event_text }, + { "stonith-event", "xml", stonith_event_xml }, + { "validate", "html", validate_agent_html }, + { "validate", "log", validate_agent_text }, + { "validate", "text", validate_agent_text }, + { "validate", "xml", validate_agent_xml }, + + { NULL, NULL, NULL } +}; + +void +stonith__register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} diff --git a/lib/fencing/st_rhcs.c b/lib/fencing/st_rhcs.c new file mode 100644 index 0000000..ec80793 --- /dev/null +++ b/lib/fencing/st_rhcs.c @@ -0,0 +1,316 @@ +/* + * Copyright 2004-2022 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <glib.h> +#include <dirent.h> + +#include <crm/crm.h> +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> + +#include "fencing_private.h" + +#define RH_STONITH_PREFIX "fence_" + +/*! + * \internal + * \brief Add available RHCS-compatible agents to a list + * + * \param[in,out] List to add to + * + * \return Number of agents added + */ +int +stonith__list_rhcs_agents(stonith_key_value_t **devices) +{ + // Essentially: ls -1 @sbin_dir@/fence_* + + int count = 0, i; + struct dirent **namelist; + const int file_num = scandir(PCMK__FENCE_BINDIR, &namelist, 0, alphasort); + +#if _POSIX_C_SOURCE < 200809L && !(defined(O_SEARCH) || defined(O_PATH)) + char buffer[FILENAME_MAX + 1]; +#elif defined(O_SEARCH) + const int dirfd = open(PCMK__FENCE_BINDIR, O_SEARCH); +#else + const int dirfd = open(PCMK__FENCE_BINDIR, O_PATH); +#endif + + for (i = 0; i < file_num; i++) { + struct stat prop; + + if (pcmk__starts_with(namelist[i]->d_name, RH_STONITH_PREFIX)) { +#if _POSIX_C_SOURCE < 200809L && !(defined(O_SEARCH) || defined(O_PATH)) + snprintf(buffer, sizeof(buffer), "%s/%s", PCMK__FENCE_BINDIR, + namelist[i]->d_name); + if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) { +#else + if (dirfd == -1) { + if (i == 0) { + crm_notice("Problem with listing %s directory" + CRM_XS "errno=%d", RH_STONITH_PREFIX, errno); + } + free(namelist[i]); + continue; + } + /* note: we can possibly prevent following symlinks here, + which may be a good idea, but fall on the nose when + these agents are moved elsewhere & linked back */ + if (fstatat(dirfd, namelist[i]->d_name, &prop, 0) == 0 + && S_ISREG(prop.st_mode)) { +#endif + *devices = stonith_key_value_add(*devices, NULL, + namelist[i]->d_name); + count++; + } + } + free(namelist[i]); + } + if (file_num > 0) { + free(namelist); + } +#if _POSIX_C_SOURCE >= 200809L || defined(O_SEARCH) || defined(O_PATH) + if (dirfd >= 0) { + close(dirfd); + } +#endif + return count; +} + +static void +stonith_rhcs_parameter_not_required(xmlNode *metadata, const char *parameter) +{ + char *xpath = NULL; + xmlXPathObject *xpathObj = NULL; + + CRM_CHECK(metadata != NULL, return); + CRM_CHECK(parameter != NULL, return); + + xpath = crm_strdup_printf("//parameter[@name='%s']", parameter); + /* Fudge metadata so that the parameter isn't required in config + * Pacemaker handles and adds it */ + xpathObj = xpath_search(metadata, xpath); + if (numXpathResults(xpathObj) > 0) { + xmlNode *tmp = getXpathResult(xpathObj, 0); + + crm_xml_add(tmp, "required", "0"); + } + freeXpathObject(xpathObj); + free(xpath); +} + +/*! + * \brief Execute RHCS-compatible agent's metadata action + * + * \param[in] agent Agent to execute + * \param[in] timeout_sec Action timeout + * \param[out] metadata Where to store output xmlNode (or NULL to ignore) + */ +static int +stonith__rhcs_get_metadata(const char *agent, int timeout_sec, + xmlNode **metadata) +{ + xmlNode *xml = NULL; + xmlNode *actions = NULL; + xmlXPathObject *xpathObj = NULL; + stonith_action_t *action = stonith__action_create(agent, "metadata", NULL, + 0, timeout_sec, NULL, + NULL, NULL); + int rc = stonith__execute(action); + pcmk__action_result_t *result = stonith__action_result(action); + + if (result == NULL) { + if (rc < 0) { + crm_warn("Could not execute metadata action for %s: %s " + CRM_XS " rc=%d", agent, pcmk_strerror(rc), rc); + } + stonith__destroy_action(action); + return rc; + } + + if (result->execution_status != PCMK_EXEC_DONE) { + crm_warn("Could not execute metadata action for %s: %s", + agent, pcmk_exec_status_str(result->execution_status)); + rc = pcmk_rc2legacy(stonith__result2rc(result)); + stonith__destroy_action(action); + return rc; + } + + if (!pcmk__result_ok(result)) { + crm_warn("Metadata action for %s returned error code %d", + agent, result->exit_status); + rc = pcmk_rc2legacy(stonith__result2rc(result)); + stonith__destroy_action(action); + return rc; + } + + if (result->action_stdout == NULL) { + crm_warn("Metadata action for %s returned no data", agent); + stonith__destroy_action(action); + return -ENODATA; + } + + xml = string2xml(result->action_stdout); + stonith__destroy_action(action); + + if (xml == NULL) { + crm_warn("Metadata for %s is invalid", agent); + return -pcmk_err_schema_validation; + } + + xpathObj = xpath_search(xml, "//actions"); + if (numXpathResults(xpathObj) > 0) { + actions = getXpathResult(xpathObj, 0); + } + freeXpathObject(xpathObj); + + // Add start and stop (implemented by pacemaker, not agent) to meta-data + xpathObj = xpath_search(xml, "//action[@name='stop']"); + if (numXpathResults(xpathObj) <= 0) { + xmlNode *tmp = NULL; + + tmp = create_xml_node(actions, "action"); + crm_xml_add(tmp, "name", "stop"); + crm_xml_add(tmp, "timeout", CRM_DEFAULT_OP_TIMEOUT_S); + + tmp = create_xml_node(actions, "action"); + crm_xml_add(tmp, "name", "start"); + crm_xml_add(tmp, "timeout", CRM_DEFAULT_OP_TIMEOUT_S); + } + freeXpathObject(xpathObj); + + // Fudge metadata so parameters are not required in config (pacemaker adds them) + stonith_rhcs_parameter_not_required(xml, "action"); + stonith_rhcs_parameter_not_required(xml, "plug"); + stonith_rhcs_parameter_not_required(xml, "port"); + + if (metadata) { + *metadata = xml; + + } else { + free_xml(xml); + } + + return pcmk_ok; +} + +/*! + * \brief Retrieve metadata for RHCS-compatible fence agent + * + * \param[in] agent Agent to execute + * \param[in] timeout_sec Action timeout + * \param[out] output Where to store action output (or NULL to ignore) + */ +int +stonith__rhcs_metadata(const char *agent, int timeout_sec, char **output) +{ + char *buffer = NULL; + xmlNode *xml = NULL; + + int rc = stonith__rhcs_get_metadata(agent, timeout_sec, &xml); + + if (rc != pcmk_ok) { + free_xml(xml); + return rc; + } + + buffer = dump_xml_formatted_with_text(xml); + free_xml(xml); + if (buffer == NULL) { + return -pcmk_err_schema_validation; + } + if (output) { + *output = buffer; + } else { + free(buffer); + } + return pcmk_ok; +} + +bool +stonith__agent_is_rhcs(const char *agent) +{ + struct stat prop; + char *buffer = crm_strdup_printf(PCMK__FENCE_BINDIR "/%s", agent); + int rc = stat(buffer, &prop); + + free(buffer); + return (rc >= 0) && S_ISREG(prop.st_mode); +} + +int +stonith__rhcs_validate(stonith_t *st, int call_options, const char *target, + const char *agent, GHashTable *params, + const char * host_arg, int timeout, + char **output, char **error_output) +{ + int rc = pcmk_ok; + int remaining_timeout = timeout; + xmlNode *metadata = NULL; + stonith_action_t *action = NULL; + pcmk__action_result_t *result = NULL; + + if (host_arg == NULL) { + time_t start_time = time(NULL); + + rc = stonith__rhcs_get_metadata(agent, remaining_timeout, &metadata); + + if (rc == pcmk_ok) { + uint32_t device_flags = 0; + + stonith__device_parameter_flags(&device_flags, agent, metadata); + if (pcmk_is_set(device_flags, st_device_supports_parameter_port)) { + host_arg = "port"; + + } else if (pcmk_is_set(device_flags, + st_device_supports_parameter_plug)) { + host_arg = "plug"; + } + } + + free_xml(metadata); + + remaining_timeout -= time(NULL) - start_time; + + if (rc == -ETIME || remaining_timeout <= 0 ) { + return -ETIME; + } + + } else if (pcmk__str_eq(host_arg, PCMK__VALUE_NONE, pcmk__str_casei)) { + host_arg = NULL; + } + + action = stonith__action_create(agent, "validate-all", target, 0, + remaining_timeout, params, NULL, host_arg); + + rc = stonith__execute(action); + result = stonith__action_result(action); + + if (result != NULL) { + rc = pcmk_rc2legacy(stonith__result2rc(result)); + + // Take ownership of output so stonith__destroy_action() doesn't free it + if (output != NULL) { + *output = result->action_stdout; + result->action_stdout = NULL; + } + if (error_output != NULL) { + *error_output = result->action_stderr; + result->action_stderr = NULL; + } + } + stonith__destroy_action(action); + return rc; +} |