summaryrefslogtreecommitdiffstats
path: root/daemons/fenced
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/fenced')
-rw-r--r--daemons/fenced/Makefile.am52
-rw-r--r--daemons/fenced/cts-fence-helper.c681
-rw-r--r--daemons/fenced/fenced_commands.c3674
-rw-r--r--daemons/fenced/fenced_history.c548
-rw-r--r--daemons/fenced/fenced_remote.c2509
-rw-r--r--daemons/fenced/pacemaker-fenced.c1751
-rw-r--r--daemons/fenced/pacemaker-fenced.h315
7 files changed, 9530 insertions, 0 deletions
diff --git a/daemons/fenced/Makefile.am b/daemons/fenced/Makefile.am
new file mode 100644
index 0000000..2ca0088
--- /dev/null
+++ b/daemons/fenced/Makefile.am
@@ -0,0 +1,52 @@
+#
+# Original Author: Sun Jiang Dong <sunjd@cn.ibm.com>
+# Copyright 2004 International Business Machines
+#
+# with later changes copyright 2004-2023 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
+include $(top_srcdir)/mk/man.mk
+
+halibdir = $(CRM_DAEMON_DIR)
+
+halib_PROGRAMS = pacemaker-fenced cts-fence-helper
+
+noinst_HEADERS = pacemaker-fenced.h
+
+if BUILD_XML_HELP
+man7_MANS = pacemaker-fenced.7
+endif
+
+cts_fence_helper_SOURCES = cts-fence-helper.c
+cts_fence_helper_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \
+ $(top_builddir)/lib/fencing/libstonithd.la
+
+pacemaker_fenced_YFLAGS = -d
+pacemaker_fenced_CFLAGS = $(CFLAGS_HARDENED_EXE)
+pacemaker_fenced_LDFLAGS = $(LDFLAGS_HARDENED_EXE)
+pacemaker_fenced_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \
+ $(top_builddir)/lib/cib/libcib.la \
+ $(top_builddir)/lib/cluster/libcrmcluster.la \
+ $(top_builddir)/lib/fencing/libstonithd.la \
+ $(top_builddir)/lib/pengine/libpe_status.la \
+ $(top_builddir)/lib/pacemaker/libpacemaker.la \
+ $(CLUSTERLIBS)
+pacemaker_fenced_SOURCES = pacemaker-fenced.c \
+ fenced_commands.c \
+ fenced_remote.c \
+ fenced_history.c
+
+CLEANFILES = $(man7_MANS) $(man8_MANS)
+
+if BUILD_LEGACY_LINKS
+install-exec-hook:
+ cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f stonithd && $(LN_S) pacemaker-fenced stonithd
+
+uninstall-hook:
+ cd $(DESTDIR)$(CRM_DAEMON_DIR) && rm -f stonithd
+endif
diff --git a/daemons/fenced/cts-fence-helper.c b/daemons/fenced/cts-fence-helper.c
new file mode 100644
index 0000000..e18a1f4
--- /dev/null
+++ b/daemons/fenced/cts-fence-helper.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright 2009-2023 the Pacemaker project contributors
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/cluster/internal.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/fencing/internal.h>
+#include <crm/common/agents.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/xml.h>
+
+#include <crm/common/mainloop.h>
+
+#define SUMMARY "cts-fence-helper - inject commands into the Pacemaker fencer and watch for events"
+
+static GMainLoop *mainloop = NULL;
+static crm_trigger_t *trig = NULL;
+static int mainloop_iter = 0;
+static pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+
+typedef void (*mainloop_test_iteration_cb) (int check_event);
+
+#define MAINLOOP_DEFAULT_TIMEOUT 2
+
+enum test_modes {
+ test_standard = 0, // test using a specific developer environment
+ test_passive, // watch notifications only
+ test_api_sanity, // sanity-test stonith client API using fence_dummy
+ test_api_mainloop, // sanity-test mainloop code with async responses
+};
+
+struct {
+ enum test_modes mode;
+} options = {
+ .mode = test_standard
+};
+
+static gboolean
+mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+ if (pcmk__str_any_of(option_name, "--mainloop_api_test", "-m", NULL)) {
+ options.mode = test_api_mainloop;
+ } else if (pcmk__str_any_of(option_name, "--api_test", "-t", NULL)) {
+ options.mode = test_api_sanity;
+ } else if (pcmk__str_any_of(option_name, "--passive", "-p", NULL)) {
+ options.mode = test_passive;
+ }
+
+ return TRUE;
+}
+
+static GOptionEntry entries[] = {
+ { "mainloop_api_test", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
+ NULL, NULL,
+ },
+
+ { "api_test", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
+ NULL, NULL,
+ },
+
+ { "passive", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
+ NULL, NULL,
+ },
+
+ { NULL }
+};
+
+static stonith_t *st = NULL;
+static struct pollfd pollfd;
+static const int st_opts = st_opt_sync_call;
+static int expected_notifications = 0;
+static int verbose = 0;
+
+static void
+mainloop_test_done(const char *origin, bool pass)
+{
+ if (pass) {
+ crm_info("SUCCESS - %s", origin);
+ mainloop_iter++;
+ mainloop_set_trigger(trig);
+ result.execution_status = PCMK_EXEC_DONE;
+ result.exit_status = CRM_EX_OK;
+ } else {
+ crm_err("FAILURE - %s (%d: %s)", origin, result.exit_status,
+ pcmk_exec_status_str(result.execution_status));
+ crm_exit(CRM_EX_ERROR);
+ }
+}
+
+
+static void
+dispatch_helper(int timeout)
+{
+ int rc;
+
+ crm_debug("Looking for notification");
+ pollfd.events = POLLIN;
+ while (true) {
+ rc = poll(&pollfd, 1, timeout); /* wait 10 minutes, -1 forever */
+ if (rc > 0) {
+ if (!stonith_dispatch(st)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+static void
+st_callback(stonith_t * st, stonith_event_t * e)
+{
+ char *desc = NULL;
+
+ if (st->state == stonith_disconnected) {
+ crm_exit(CRM_EX_DISCONNECT);
+ }
+
+ desc = stonith__event_description(e);
+ crm_notice("%s", desc);
+ free(desc);
+
+ if (expected_notifications) {
+ expected_notifications--;
+ }
+}
+
+static void
+st_global_callback(stonith_t * stonith, stonith_callback_data_t * data)
+{
+ crm_notice("Call %d exited %d: %s (%s)",
+ data->call_id, stonith__exit_status(data),
+ stonith__execution_status(data),
+ pcmk__s(stonith__exit_reason(data), "unspecified reason"));
+}
+
+static void
+passive_test(void)
+{
+ int rc = 0;
+
+ rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
+ if (rc != pcmk_ok) {
+ stonith_api_delete(st);
+ crm_exit(CRM_EX_DISCONNECT);
+ }
+ st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, st_callback);
+ st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, st_callback);
+ st->cmds->register_notification(st, STONITH_OP_DEVICE_ADD, st_callback);
+ st->cmds->register_notification(st, STONITH_OP_DEVICE_DEL, st_callback);
+ st->cmds->register_callback(st, 0, 120, st_opt_timeout_updates, NULL, "st_global_callback",
+ st_global_callback);
+
+ dispatch_helper(600 * 1000);
+}
+
+#define single_test(cmd, str, num_notifications, expected_rc) \
+{ \
+ int rc = 0; \
+ rc = cmd; \
+ expected_notifications = 0; \
+ if (num_notifications) { \
+ expected_notifications = num_notifications; \
+ dispatch_helper(500); \
+ } \
+ if (rc != expected_rc) { \
+ crm_err("FAILURE - expected rc %d != %d(%s) for cmd - %s", expected_rc, rc, pcmk_strerror(rc), str); \
+ crm_exit(CRM_EX_ERROR); \
+ } else if (expected_notifications) { \
+ crm_err("FAILURE - expected %d notifications, got only %d for cmd - %s", \
+ num_notifications, num_notifications - expected_notifications, str); \
+ crm_exit(CRM_EX_ERROR); \
+ } else { \
+ if (verbose) { \
+ crm_info("SUCCESS - %s: %d", str, rc); \
+ } else { \
+ crm_debug("SUCCESS - %s: %d", str, rc); \
+ } \
+ } \
+}\
+
+static void
+run_fence_failure_test(void)
+{
+ stonith_key_value_t *params = NULL;
+
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "false_1_node1=1,2 false_1_node2=3,4");
+ params = stonith_key_value_add(params, "mode", "fail");
+
+ single_test(st->
+ cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params),
+ "Register device1 for failure test", 1, 0);
+
+ single_test(st->cmds->fence(st, st_opts, "false_1_node2", "off", 3, 0),
+ "Fence failure results off", 1, -ENODATA);
+
+ single_test(st->cmds->fence(st, st_opts, "false_1_node2", "reboot", 3, 0),
+ "Fence failure results reboot", 1, -ENODATA);
+
+ single_test(st->cmds->remove_device(st, st_opts, "test-id1"),
+ "Remove device1 for failure test", 1, 0);
+
+ stonith_key_value_freeall(params, 1, 1);
+}
+
+static void
+run_fence_failure_rollover_test(void)
+{
+ stonith_key_value_t *params = NULL;
+
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "false_1_node1=1,2 false_1_node2=3,4");
+ params = stonith_key_value_add(params, "mode", "fail");
+
+ single_test(st->
+ cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params),
+ "Register device1 for rollover test", 1, 0);
+ stonith_key_value_freeall(params, 1, 1);
+ params = NULL;
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "false_1_node1=1,2 false_1_node2=3,4");
+ params = stonith_key_value_add(params, "mode", "pass");
+
+ single_test(st->
+ cmds->register_device(st, st_opts, "test-id2", "stonith-ng", "fence_dummy", params),
+ "Register device2 for rollover test", 1, 0);
+
+ single_test(st->cmds->fence(st, st_opts, "false_1_node2", "off", 3, 0),
+ "Fence rollover results off", 1, 0);
+
+ /* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */
+ single_test(st->cmds->fence(st, st_opts, "false_1_node2", "on", 3, 0),
+ "Fence rollover results on", 1, -ENODEV);
+
+ single_test(st->cmds->remove_device(st, st_opts, "test-id1"),
+ "Remove device1 for rollover tests", 1, 0);
+
+ single_test(st->cmds->remove_device(st, st_opts, "test-id2"),
+ "Remove device2 for rollover tests", 1, 0);
+
+ stonith_key_value_freeall(params, 1, 1);
+}
+
+static void
+run_standard_test(void)
+{
+ stonith_key_value_t *params = NULL;
+
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "false_1_node1=1,2 false_1_node2=3,4");
+ params = stonith_key_value_add(params, "mode", "pass");
+ params = stonith_key_value_add(params, "mock_dynamic_hosts", "false_1_node1 false_1_node2");
+
+ single_test(st->
+ cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_dummy", params),
+ "Register", 1, 0);
+ stonith_key_value_freeall(params, 1, 1);
+ params = NULL;
+
+ single_test(st->cmds->list(st, st_opts, "test-id", NULL, 1), "list", 1, 0);
+
+ single_test(st->cmds->monitor(st, st_opts, "test-id", 1), "Monitor", 1, 0);
+
+ single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node2", 1),
+ "Status false_1_node2", 1, 0);
+
+ single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node1", 1),
+ "Status false_1_node1", 1, 0);
+
+ single_test(st->cmds->fence(st, st_opts, "unknown-host", "off", 1, 0),
+ "Fence unknown-host (expected failure)", 0, -ENODEV);
+
+ single_test(st->cmds->fence(st, st_opts, "false_1_node1", "off", 1, 0),
+ "Fence false_1_node1", 1, 0);
+
+ /* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */
+ single_test(st->cmds->fence(st, st_opts, "false_1_node1", "on", 1, 0),
+ "Unfence false_1_node1", 1, -ENODEV);
+
+ /* Confirm that an invalid level index is rejected */
+ single_test(st->cmds->register_level(st, st_opts, "node1", 999, params),
+ "Attempt to register an invalid level index", 0, -EINVAL);
+
+ single_test(st->cmds->remove_device(st, st_opts, "test-id"), "Remove test-id", 1, 0);
+
+ stonith_key_value_freeall(params, 1, 1);
+}
+
+static void
+sanity_tests(void)
+{
+ int rc = 0;
+
+ rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
+ if (rc != pcmk_ok) {
+ stonith_api_delete(st);
+ crm_exit(CRM_EX_DISCONNECT);
+ }
+ st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, st_callback);
+ st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, st_callback);
+ st->cmds->register_notification(st, STONITH_OP_DEVICE_ADD, st_callback);
+ st->cmds->register_notification(st, STONITH_OP_DEVICE_DEL, st_callback);
+ st->cmds->register_callback(st, 0, 120, st_opt_timeout_updates, NULL, "st_global_callback",
+ st_global_callback);
+
+ crm_info("Starting API Sanity Tests");
+ run_standard_test();
+ run_fence_failure_test();
+ run_fence_failure_rollover_test();
+ crm_info("Sanity Tests Passed");
+}
+
+static void
+standard_dev_test(void)
+{
+ int rc = 0;
+ char *tmp = NULL;
+ stonith_key_value_t *params = NULL;
+
+ rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
+ if (rc != pcmk_ok) {
+ stonith_api_delete(st);
+ crm_exit(CRM_EX_DISCONNECT);
+ }
+
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "some-host=pcmk-7 true_1_node1=3,4");
+
+ rc = st->cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_xvm", params);
+ crm_debug("Register: %d", rc);
+
+ rc = st->cmds->list(st, st_opts, "test-id", &tmp, 10);
+ crm_debug("List: %d output: %s", rc, tmp ? tmp : "<none>");
+
+ rc = st->cmds->monitor(st, st_opts, "test-id", 10);
+ crm_debug("Monitor: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "false_1_node2", 10);
+ crm_debug("Status false_1_node2: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
+ crm_debug("Status false_1_node1: %d", rc);
+
+ rc = st->cmds->fence(st, st_opts, "unknown-host", "off", 60, 0);
+ crm_debug("Fence unknown-host: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
+ crm_debug("Status false_1_node1: %d", rc);
+
+ rc = st->cmds->fence(st, st_opts, "false_1_node1", "off", 60, 0);
+ crm_debug("Fence false_1_node1: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
+ crm_debug("Status false_1_node1: %d", rc);
+
+ rc = st->cmds->fence(st, st_opts, "false_1_node1", "on", 10, 0);
+ crm_debug("Unfence false_1_node1: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
+ crm_debug("Status false_1_node1: %d", rc);
+
+ rc = st->cmds->fence(st, st_opts, "some-host", "off", 10, 0);
+ crm_debug("Fence alias: %d", rc);
+
+ rc = st->cmds->status(st, st_opts, "test-id", "some-host", 10);
+ crm_debug("Status alias: %d", rc);
+
+ rc = st->cmds->fence(st, st_opts, "false_1_node1", "on", 10, 0);
+ crm_debug("Unfence false_1_node1: %d", rc);
+
+ rc = st->cmds->remove_device(st, st_opts, "test-id");
+ crm_debug("Remove test-id: %d", rc);
+
+ stonith_key_value_freeall(params, 1, 1);
+}
+
+static void
+ iterate_mainloop_tests(gboolean event_ready);
+
+static void
+mainloop_callback(stonith_t * stonith, stonith_callback_data_t * data)
+{
+ pcmk__set_result(&result, stonith__exit_status(data),
+ stonith__execution_status(data),
+ stonith__exit_reason(data));
+ iterate_mainloop_tests(TRUE);
+}
+
+static int
+register_callback_helper(int callid)
+{
+ return st->cmds->register_callback(st,
+ callid,
+ MAINLOOP_DEFAULT_TIMEOUT,
+ st_opt_timeout_updates, NULL, "callback", mainloop_callback);
+}
+
+static void
+test_async_fence_pass(int check_event)
+{
+ int rc = 0;
+
+ if (check_event) {
+ mainloop_test_done(__func__, (result.exit_status == CRM_EX_OK));
+ return;
+ }
+
+ rc = st->cmds->fence(st, 0, "true_1_node1", "off", MAINLOOP_DEFAULT_TIMEOUT, 0);
+ if (rc < 0) {
+ crm_err("fence failed with rc %d", rc);
+ mainloop_test_done(__func__, false);
+ }
+ register_callback_helper(rc);
+ /* wait for event */
+}
+
+#define CUSTOM_TIMEOUT_ADDITION 10
+static void
+test_async_fence_custom_timeout(int check_event)
+{
+ int rc = 0;
+ static time_t begin = 0;
+
+ if (check_event) {
+ uint32_t diff = (time(NULL) - begin);
+
+ if (result.execution_status != PCMK_EXEC_TIMEOUT) {
+ mainloop_test_done(__func__, false);
+ } else if (diff < CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT) {
+ crm_err
+ ("Custom timeout test failed, callback expiration should be updated to %d, actual timeout was %d",
+ CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT, diff);
+ mainloop_test_done(__func__, false);
+ } else {
+ mainloop_test_done(__func__, true);
+ }
+ return;
+ }
+ begin = time(NULL);
+
+ rc = st->cmds->fence(st, 0, "custom_timeout_node1", "off", MAINLOOP_DEFAULT_TIMEOUT, 0);
+ if (rc < 0) {
+ crm_err("fence failed with rc %d", rc);
+ mainloop_test_done(__func__, false);
+ }
+ register_callback_helper(rc);
+ /* wait for event */
+}
+
+static void
+test_async_fence_timeout(int check_event)
+{
+ int rc = 0;
+
+ if (check_event) {
+ mainloop_test_done(__func__,
+ (result.execution_status == PCMK_EXEC_NO_FENCE_DEVICE));
+ return;
+ }
+
+ rc = st->cmds->fence(st, 0, "false_1_node2", "off", MAINLOOP_DEFAULT_TIMEOUT, 0);
+ if (rc < 0) {
+ crm_err("fence failed with rc %d", rc);
+ mainloop_test_done(__func__, false);
+ }
+ register_callback_helper(rc);
+ /* wait for event */
+}
+
+static void
+test_async_monitor(int check_event)
+{
+ int rc = 0;
+
+ if (check_event) {
+ mainloop_test_done(__func__, (result.exit_status == CRM_EX_OK));
+ return;
+ }
+
+ rc = st->cmds->monitor(st, 0, "false_1", MAINLOOP_DEFAULT_TIMEOUT);
+ if (rc < 0) {
+ crm_err("monitor failed with rc %d", rc);
+ mainloop_test_done(__func__, false);
+ }
+
+ register_callback_helper(rc);
+ /* wait for event */
+}
+
+static void
+test_register_async_devices(int check_event)
+{
+ char buf[16] = { 0, };
+ stonith_key_value_t *params = NULL;
+
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "false_1_node1=1,2");
+ params = stonith_key_value_add(params, "mode", "fail");
+ st->cmds->register_device(st, st_opts, "false_1", "stonith-ng", "fence_dummy", params);
+ stonith_key_value_freeall(params, 1, 1);
+
+ params = NULL;
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "true_1_node1=1,2");
+ params = stonith_key_value_add(params, "mode", "pass");
+ st->cmds->register_device(st, st_opts, "true_1", "stonith-ng", "fence_dummy", params);
+ stonith_key_value_freeall(params, 1, 1);
+
+ params = NULL;
+ params = stonith_key_value_add(params, PCMK_STONITH_HOST_MAP,
+ "custom_timeout_node1=1,2");
+ params = stonith_key_value_add(params, "mode", "fail");
+ params = stonith_key_value_add(params, "delay", "1000");
+ snprintf(buf, sizeof(buf) - 1, "%d", MAINLOOP_DEFAULT_TIMEOUT + CUSTOM_TIMEOUT_ADDITION);
+ params = stonith_key_value_add(params, "pcmk_off_timeout", buf);
+ st->cmds->register_device(st, st_opts, "false_custom_timeout", "stonith-ng", "fence_dummy",
+ params);
+ stonith_key_value_freeall(params, 1, 1);
+
+ mainloop_test_done(__func__, true);
+}
+
+static void
+try_mainloop_connect(int check_event)
+{
+ int rc = stonith_api_connect_retry(st, crm_system_name, 10);
+
+ if (rc == pcmk_ok) {
+ mainloop_test_done(__func__, true);
+ return;
+ }
+ crm_err("API CONNECTION FAILURE");
+ mainloop_test_done(__func__, false);
+}
+
+static void
+iterate_mainloop_tests(gboolean event_ready)
+{
+ static mainloop_test_iteration_cb callbacks[] = {
+ try_mainloop_connect,
+ test_register_async_devices,
+ test_async_monitor,
+ test_async_fence_pass,
+ test_async_fence_timeout,
+ test_async_fence_custom_timeout,
+ };
+
+ if (mainloop_iter == (sizeof(callbacks) / sizeof(mainloop_test_iteration_cb))) {
+ /* all tests ran, everything passed */
+ crm_info("ALL MAINLOOP TESTS PASSED!");
+ crm_exit(CRM_EX_OK);
+ }
+
+ callbacks[mainloop_iter] (event_ready);
+}
+
+static gboolean
+trigger_iterate_mainloop_tests(gpointer user_data)
+{
+ iterate_mainloop_tests(FALSE);
+ return TRUE;
+}
+
+static void
+test_shutdown(int nsig)
+{
+ int rc = 0;
+
+ if (st) {
+ rc = st->cmds->disconnect(st);
+ crm_info("Disconnect: %d", rc);
+
+ crm_debug("Destroy");
+ stonith_api_delete(st);
+ }
+
+ if (rc) {
+ crm_exit(CRM_EX_ERROR);
+ }
+}
+
+static void
+mainloop_tests(void)
+{
+ trig = mainloop_add_trigger(G_PRIORITY_HIGH, trigger_iterate_mainloop_tests, NULL);
+ mainloop_set_trigger(trig);
+ mainloop_add_signal(SIGTERM, test_shutdown);
+
+ crm_info("Starting");
+ mainloop = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(mainloop);
+}
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
+ GOptionContext *context = NULL;
+
+ context = pcmk__build_arg_context(args, NULL, group, NULL);
+ pcmk__add_main_args(context, entries);
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ GError *error = NULL;
+ crm_exit_t exit_code = CRM_EX_OK;
+
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
+ GOptionContext *context = build_arg_context(args, NULL);
+
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ /* We have to use crm_log_init here to set up the logging because there's
+ * different handling for daemons vs. command line programs, and
+ * pcmk__cli_init_logging is set up to only handle the latter.
+ */
+ crm_log_init(NULL, LOG_INFO, TRUE, (verbose? TRUE : FALSE), argc, argv,
+ FALSE);
+
+ for (int i = 0; i < args->verbosity; i++) {
+ crm_bump_log_level(argc, argv);
+ }
+
+ st = stonith_api_new();
+ if (st == NULL) {
+ exit_code = CRM_EX_DISCONNECT;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Could not connect to fencer: API memory allocation failed");
+ goto done;
+ }
+
+ switch (options.mode) {
+ case test_standard:
+ standard_dev_test();
+ break;
+ case test_passive:
+ passive_test();
+ break;
+ case test_api_sanity:
+ sanity_tests();
+ break;
+ case test_api_mainloop:
+ mainloop_tests();
+ break;
+ }
+
+ test_shutdown(0);
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ pcmk__output_and_clear_error(&error, NULL);
+ crm_exit(exit_code);
+}
diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c
new file mode 100644
index 0000000..ba63cf8
--- /dev/null
+++ b/daemons/fenced/fenced_commands.c
@@ -0,0 +1,3674 @@
+/*
+ * Copyright 2009-2023 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 <crm_internal.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/cluster/internal.h>
+#include <crm/common/mainloop.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/fencing/internal.h>
+#include <crm/common/xml.h>
+
+#include <pacemaker-fenced.h>
+
+GHashTable *device_list = NULL;
+GHashTable *topology = NULL;
+static GList *cmd_list = NULL;
+
+static GHashTable *fenced_handlers = NULL;
+
+struct device_search_s {
+ /* target of fence action */
+ char *host;
+ /* requested fence action */
+ char *action;
+ /* timeout to use if a device is queried dynamically for possible targets */
+ int per_device_timeout;
+ /* number of registered fencing devices at time of request */
+ int replies_needed;
+ /* number of device replies received so far */
+ int replies_received;
+ /* whether the target is eligible to perform requested action (or off) */
+ bool allow_suicide;
+
+ /* private data to pass to search callback function */
+ void *user_data;
+ /* function to call when all replies have been received */
+ void (*callback) (GList * devices, void *user_data);
+ /* devices capable of performing requested action (or off if remapping) */
+ GList *capable;
+ /* Whether to perform searches that support the action */
+ uint32_t support_action_only;
+};
+
+static gboolean stonith_device_dispatch(gpointer user_data);
+static void st_child_done(int pid, const pcmk__action_result_t *result,
+ void *user_data);
+static void stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer,
+ pcmk__client_t *client);
+
+static void search_devices_record_result(struct device_search_s *search, const char *device,
+ gboolean can_fence);
+
+static int get_agent_metadata(const char *agent, xmlNode **metadata);
+static void read_action_metadata(stonith_device_t *device);
+static enum fenced_target_by unpack_level_kind(const xmlNode *level);
+
+typedef struct async_command_s {
+
+ int id;
+ int pid;
+ int fd_stdout;
+ int options;
+ int default_timeout; /* seconds */
+ int timeout; /* seconds */
+
+ int start_delay; // seconds (-1 means disable static/random fencing delays)
+ int delay_id;
+
+ char *op;
+ char *origin;
+ char *client;
+ char *client_name;
+ char *remote_op_id;
+
+ char *target;
+ uint32_t target_nodeid;
+ char *action;
+ char *device;
+
+ GList *device_list;
+ GList *next_device_iter; // device_list entry for next device to execute
+
+ void *internal_user_data;
+ void (*done_cb) (int pid, const pcmk__action_result_t *result,
+ void *user_data);
+ guint timer_sigterm;
+ guint timer_sigkill;
+ /*! If the operation timed out, this is the last signal
+ * we sent to the process to get it to terminate */
+ int last_timeout_signo;
+
+ stonith_device_t *active_on;
+ stonith_device_t *activating_on;
+} async_command_t;
+
+static xmlNode *construct_async_reply(const async_command_t *cmd,
+ const pcmk__action_result_t *result);
+
+static gboolean
+is_action_required(const char *action, const stonith_device_t *device)
+{
+ return (device != NULL) && device->automatic_unfencing
+ && pcmk__str_eq(action, "on", pcmk__str_none);
+}
+
+static int
+get_action_delay_max(const stonith_device_t *device, const char *action)
+{
+ const char *value = NULL;
+ int delay_max = 0;
+
+ if (!pcmk__is_fencing_action(action)) {
+ return 0;
+ }
+
+ value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_MAX);
+ if (value) {
+ delay_max = crm_parse_interval_spec(value) / 1000;
+ }
+
+ return delay_max;
+}
+
+static int
+get_action_delay_base(const stonith_device_t *device, const char *action,
+ const char *target)
+{
+ char *hash_value = NULL;
+ int delay_base = 0;
+
+ if (!pcmk__is_fencing_action(action)) {
+ return 0;
+ }
+
+ hash_value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_BASE);
+
+ if (hash_value) {
+ char *value = strdup(hash_value);
+ char *valptr = value;
+
+ CRM_ASSERT(value != NULL);
+
+ if (target != NULL) {
+ for (char *val = strtok(value, "; \t"); val != NULL; val = strtok(NULL, "; \t")) {
+ char *mapval = strchr(val, ':');
+
+ if (mapval == NULL || mapval[1] == 0) {
+ crm_err("pcmk_delay_base: empty value in mapping", val);
+ continue;
+ }
+
+ if (mapval != val && strncasecmp(target, val, (size_t)(mapval - val)) == 0) {
+ value = mapval + 1;
+ crm_debug("pcmk_delay_base mapped to %s for %s",
+ value, target);
+ break;
+ }
+ }
+ }
+
+ if (strchr(value, ':') == 0) {
+ delay_base = crm_parse_interval_spec(value) / 1000;
+ }
+
+ free(valptr);
+ }
+
+ return delay_base;
+}
+
+/*!
+ * \internal
+ * \brief Override STONITH timeout with pcmk_*_timeout if available
+ *
+ * \param[in] device STONITH device to use
+ * \param[in] action STONITH action name
+ * \param[in] default_timeout Timeout to use if device does not have
+ * a pcmk_*_timeout parameter for action
+ *
+ * \return Value of pcmk_(action)_timeout if available, otherwise default_timeout
+ * \note For consistency, it would be nice if reboot/off/on timeouts could be
+ * set the same way as start/stop/monitor timeouts, i.e. with an
+ * <operation> entry in the fencing resource configuration. However that
+ * is insufficient because fencing devices may be registered directly via
+ * the fencer's register_device() API instead of going through the CIB
+ * (e.g. stonith_admin uses it for its -R option, and the executor uses it
+ * to ensure a device is registered when a command is issued). As device
+ * properties, pcmk_*_timeout parameters can be grabbed by the fencer when
+ * the device is registered, whether by CIB change or API call.
+ */
+static int
+get_action_timeout(const stonith_device_t *device, const char *action,
+ int default_timeout)
+{
+ if (action && device && device->params) {
+ char buffer[64] = { 0, };
+ const char *value = NULL;
+
+ /* If "reboot" was requested but the device does not support it,
+ * we will remap to "off", so check timeout for "off" instead
+ */
+ if (pcmk__str_eq(action, "reboot", pcmk__str_none)
+ && !pcmk_is_set(device->flags, st_device_supports_reboot)) {
+ crm_trace("%s doesn't support reboot, using timeout for off instead",
+ device->id);
+ action = "off";
+ }
+
+ /* If the device config specified an action-specific timeout, use it */
+ snprintf(buffer, sizeof(buffer), "pcmk_%s_timeout", action);
+ value = g_hash_table_lookup(device->params, buffer);
+ if (value) {
+ return atoi(value);
+ }
+ }
+ return default_timeout;
+}
+
+/*!
+ * \internal
+ * \brief Get the currently executing device for a fencing operation
+ *
+ * \param[in] cmd Fencing operation to check
+ *
+ * \return Currently executing device for \p cmd if any, otherwise NULL
+ */
+static stonith_device_t *
+cmd_device(const async_command_t *cmd)
+{
+ if ((cmd == NULL) || (cmd->device == NULL) || (device_list == NULL)) {
+ return NULL;
+ }
+ return g_hash_table_lookup(device_list, cmd->device);
+}
+
+/*!
+ * \internal
+ * \brief Return the configured reboot action for a given device
+ *
+ * \param[in] device_id Device ID
+ *
+ * \return Configured reboot action for \p device_id
+ */
+const char *
+fenced_device_reboot_action(const char *device_id)
+{
+ const char *action = NULL;
+
+ if ((device_list != NULL) && (device_id != NULL)) {
+ stonith_device_t *device = g_hash_table_lookup(device_list, device_id);
+
+ if ((device != NULL) && (device->params != NULL)) {
+ action = g_hash_table_lookup(device->params, "pcmk_reboot_action");
+ }
+ }
+ return pcmk__s(action, "reboot");
+}
+
+/*!
+ * \internal
+ * \brief Check whether a given device supports the "on" action
+ *
+ * \param[in] device_id Device ID
+ *
+ * \return true if \p device_id supports "on", otherwise false
+ */
+bool
+fenced_device_supports_on(const char *device_id)
+{
+ if ((device_list != NULL) && (device_id != NULL)) {
+ stonith_device_t *device = g_hash_table_lookup(device_list, device_id);
+
+ if (device != NULL) {
+ return pcmk_is_set(device->flags, st_device_supports_on);
+ }
+ }
+ return false;
+}
+
+static void
+free_async_command(async_command_t * cmd)
+{
+ if (!cmd) {
+ return;
+ }
+
+ if (cmd->delay_id) {
+ g_source_remove(cmd->delay_id);
+ }
+
+ cmd_list = g_list_remove(cmd_list, cmd);
+
+ g_list_free_full(cmd->device_list, free);
+ free(cmd->device);
+ free(cmd->action);
+ free(cmd->target);
+ free(cmd->remote_op_id);
+ free(cmd->client);
+ free(cmd->client_name);
+ free(cmd->origin);
+ free(cmd->op);
+ free(cmd);
+}
+
+/*!
+ * \internal
+ * \brief Create a new asynchronous fencing operation from request XML
+ *
+ * \param[in] msg Fencing request XML (from IPC or CPG)
+ *
+ * \return Newly allocated fencing operation on success, otherwise NULL
+ *
+ * \note This asserts on memory errors, so a NULL return indicates an
+ * unparseable message.
+ */
+static async_command_t *
+create_async_command(xmlNode *msg)
+{
+ xmlNode *op = NULL;
+ async_command_t *cmd = NULL;
+
+ if (msg == NULL) {
+ return NULL;
+ }
+
+ op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR);
+ if (op == NULL) {
+ return NULL;
+ }
+
+ cmd = calloc(1, sizeof(async_command_t));
+ CRM_ASSERT(cmd != NULL);
+
+ // All messages must include these
+ cmd->action = crm_element_value_copy(op, F_STONITH_ACTION);
+ cmd->op = crm_element_value_copy(msg, F_STONITH_OPERATION);
+ cmd->client = crm_element_value_copy(msg, F_STONITH_CLIENTID);
+ if ((cmd->action == NULL) || (cmd->op == NULL) || (cmd->client == NULL)) {
+ free_async_command(cmd);
+ return NULL;
+ }
+
+ crm_element_value_int(msg, F_STONITH_CALLID, &(cmd->id));
+ crm_element_value_int(msg, F_STONITH_CALLOPTS, &(cmd->options));
+ crm_element_value_int(msg, F_STONITH_DELAY, &(cmd->start_delay));
+ crm_element_value_int(msg, F_STONITH_TIMEOUT, &(cmd->default_timeout));
+ cmd->timeout = cmd->default_timeout;
+
+ cmd->origin = crm_element_value_copy(msg, F_ORIG);
+ cmd->remote_op_id = crm_element_value_copy(msg, F_STONITH_REMOTE_OP_ID);
+ cmd->client_name = crm_element_value_copy(msg, F_STONITH_CLIENTNAME);
+ cmd->target = crm_element_value_copy(op, F_STONITH_TARGET);
+ cmd->device = crm_element_value_copy(op, F_STONITH_DEVICE);
+
+ cmd->done_cb = st_child_done;
+
+ // Track in global command list
+ cmd_list = g_list_append(cmd_list, cmd);
+
+ return cmd;
+}
+
+static int
+get_action_limit(stonith_device_t * device)
+{
+ const char *value = NULL;
+ int action_limit = 1;
+
+ value = g_hash_table_lookup(device->params, PCMK_STONITH_ACTION_LIMIT);
+ if ((value == NULL)
+ || (pcmk__scan_min_int(value, &action_limit, INT_MIN) != pcmk_rc_ok)
+ || (action_limit == 0)) {
+ action_limit = 1;
+ }
+ return action_limit;
+}
+
+static int
+get_active_cmds(stonith_device_t * device)
+{
+ int counter = 0;
+ GList *gIter = NULL;
+ GList *gIterNext = NULL;
+
+ CRM_CHECK(device != NULL, return 0);
+
+ for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) {
+ async_command_t *cmd = gIter->data;
+
+ gIterNext = gIter->next;
+
+ if (cmd->active_on == device) {
+ counter++;
+ }
+ }
+
+ return counter;
+}
+
+static void
+fork_cb(int pid, void *user_data)
+{
+ async_command_t *cmd = (async_command_t *) user_data;
+ stonith_device_t * device =
+ /* in case of a retry we've done the move from
+ activating_on to active_on already
+ */
+ cmd->activating_on?cmd->activating_on:cmd->active_on;
+
+ CRM_ASSERT(device);
+ crm_debug("Operation '%s' [%d]%s%s using %s now running with %ds timeout",
+ cmd->action, pid,
+ ((cmd->target == NULL)? "" : " targeting "),
+ pcmk__s(cmd->target, ""), device->id, cmd->timeout);
+ cmd->active_on = device;
+ cmd->activating_on = NULL;
+}
+
+static int
+get_agent_metadata_cb(gpointer data) {
+ stonith_device_t *device = data;
+ guint period_ms;
+
+ switch (get_agent_metadata(device->agent, &device->agent_metadata)) {
+ case pcmk_rc_ok:
+ if (device->agent_metadata) {
+ read_action_metadata(device);
+ stonith__device_parameter_flags(&(device->flags), device->id,
+ device->agent_metadata);
+ }
+ return G_SOURCE_REMOVE;
+
+ case EAGAIN:
+ period_ms = pcmk__mainloop_timer_get_period(device->timer);
+ if (period_ms < 160 * 1000) {
+ mainloop_timer_set_period(device->timer, 2 * period_ms);
+ }
+ return G_SOURCE_CONTINUE;
+
+ default:
+ return G_SOURCE_REMOVE;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Call a command's action callback for an internal (not library) result
+ *
+ * \param[in,out] cmd Command to report result for
+ * \param[in] execution_status Execution status to use for result
+ * \param[in] exit_status Exit status to use for result
+ * \param[in] exit_reason Exit reason to use for result
+ */
+static void
+report_internal_result(async_command_t *cmd, int exit_status,
+ int execution_status, const char *exit_reason)
+{
+ pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+
+ pcmk__set_result(&result, exit_status, execution_status, exit_reason);
+ cmd->done_cb(0, &result, cmd);
+ pcmk__reset_result(&result);
+}
+
+static gboolean
+stonith_device_execute(stonith_device_t * device)
+{
+ int exec_rc = 0;
+ const char *action_str = NULL;
+ const char *host_arg = NULL;
+ async_command_t *cmd = NULL;
+ stonith_action_t *action = NULL;
+ int active_cmds = 0;
+ int action_limit = 0;
+ GList *gIter = NULL;
+ GList *gIterNext = NULL;
+
+ CRM_CHECK(device != NULL, return FALSE);
+
+ active_cmds = get_active_cmds(device);
+ action_limit = get_action_limit(device);
+ if (action_limit > -1 && active_cmds >= action_limit) {
+ crm_trace("%s is over its action limit of %d (%u active action%s)",
+ device->id, action_limit, active_cmds,
+ pcmk__plural_s(active_cmds));
+ return TRUE;
+ }
+
+ for (gIter = device->pending_ops; gIter != NULL; gIter = gIterNext) {
+ async_command_t *pending_op = gIter->data;
+
+ gIterNext = gIter->next;
+
+ if (pending_op && pending_op->delay_id) {
+ crm_trace("Operation '%s'%s%s using %s was asked to run too early, "
+ "waiting for start delay of %ds",
+ pending_op->action,
+ ((pending_op->target == NULL)? "" : " targeting "),
+ pcmk__s(pending_op->target, ""),
+ device->id, pending_op->start_delay);
+ continue;
+ }
+
+ device->pending_ops = g_list_remove_link(device->pending_ops, gIter);
+ g_list_free_1(gIter);
+
+ cmd = pending_op;
+ break;
+ }
+
+ if (cmd == NULL) {
+ crm_trace("No actions using %s are needed", device->id);
+ return TRUE;
+ }
+
+ if (pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
+ if (pcmk__is_fencing_action(cmd->action)) {
+ if (node_does_watchdog_fencing(stonith_our_uname)) {
+ pcmk__panic(__func__);
+ goto done;
+ }
+ } else {
+ crm_info("Faking success for %s watchdog operation", cmd->action);
+ report_internal_result(cmd, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ goto done;
+ }
+ }
+
+#if SUPPORT_CIBSECRETS
+ exec_rc = pcmk__substitute_secrets(device->id, device->params);
+ if (exec_rc != pcmk_rc_ok) {
+ if (pcmk__str_eq(cmd->action, "stop", pcmk__str_none)) {
+ crm_info("Proceeding with stop operation for %s "
+ "despite being unable to load CIB secrets (%s)",
+ device->id, pcmk_rc_str(exec_rc));
+ } else {
+ crm_err("Considering %s unconfigured "
+ "because unable to load CIB secrets: %s",
+ device->id, pcmk_rc_str(exec_rc));
+ report_internal_result(cmd, CRM_EX_ERROR, PCMK_EXEC_NO_SECRETS,
+ "Failed to get CIB secrets");
+ goto done;
+ }
+ }
+#endif
+
+ action_str = cmd->action;
+ if (pcmk__str_eq(cmd->action, "reboot", pcmk__str_none)
+ && !pcmk_is_set(device->flags, st_device_supports_reboot)) {
+
+ crm_notice("Remapping 'reboot' action%s%s using %s to 'off' "
+ "because agent '%s' does not support reboot",
+ ((cmd->target == NULL)? "" : " targeting "),
+ pcmk__s(cmd->target, ""), device->id, device->agent);
+ action_str = "off";
+ }
+
+ 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";
+ }
+
+ action = stonith__action_create(device->agent, action_str, cmd->target,
+ cmd->target_nodeid, cmd->timeout,
+ device->params, device->aliases, host_arg);
+
+ /* for async exec, exec_rc is negative for early error exit
+ otherwise handling of success/errors is done via callbacks */
+ cmd->activating_on = device;
+ exec_rc = stonith__execute_async(action, (void *)cmd, cmd->done_cb,
+ fork_cb);
+ if (exec_rc < 0) {
+ cmd->activating_on = NULL;
+ cmd->done_cb(0, stonith__action_result(action), cmd);
+ stonith__destroy_action(action);
+ }
+
+done:
+ /* Device might get triggered to work by multiple fencing commands
+ * simultaneously. Trigger the device again to make sure any
+ * remaining concurrent commands get executed. */
+ if (device->pending_ops) {
+ mainloop_set_trigger(device->work);
+ }
+ return TRUE;
+}
+
+static gboolean
+stonith_device_dispatch(gpointer user_data)
+{
+ return stonith_device_execute(user_data);
+}
+
+static gboolean
+start_delay_helper(gpointer data)
+{
+ async_command_t *cmd = data;
+ stonith_device_t *device = cmd_device(cmd);
+
+ cmd->delay_id = 0;
+ if (device) {
+ mainloop_set_trigger(device->work);
+ }
+
+ return FALSE;
+}
+
+static void
+schedule_stonith_command(async_command_t * cmd, stonith_device_t * device)
+{
+ int delay_max = 0;
+ int delay_base = 0;
+ int requested_delay = cmd->start_delay;
+
+ CRM_CHECK(cmd != NULL, return);
+ CRM_CHECK(device != NULL, return);
+
+ if (cmd->device) {
+ free(cmd->device);
+ }
+
+ if (device->include_nodeid && (cmd->target != NULL)) {
+ crm_node_t *node = crm_get_peer(0, cmd->target);
+
+ cmd->target_nodeid = node->id;
+ }
+
+ cmd->device = strdup(device->id);
+ cmd->timeout = get_action_timeout(device, cmd->action, cmd->default_timeout);
+
+ if (cmd->remote_op_id) {
+ crm_debug("Scheduling '%s' action%s%s using %s for remote peer %s "
+ "with op id %.8s and timeout %ds",
+ cmd->action,
+ (cmd->target == NULL)? "" : " targeting ",
+ pcmk__s(cmd->target, ""),
+ device->id, cmd->origin, cmd->remote_op_id, cmd->timeout);
+ } else {
+ crm_debug("Scheduling '%s' action%s%s using %s for %s with timeout %ds",
+ cmd->action,
+ (cmd->target == NULL)? "" : " targeting ",
+ pcmk__s(cmd->target, ""),
+ device->id, cmd->client, cmd->timeout);
+ }
+
+ device->pending_ops = g_list_append(device->pending_ops, cmd);
+ mainloop_set_trigger(device->work);
+
+ // Value -1 means disable any static/random fencing delays
+ if (requested_delay < 0) {
+ return;
+ }
+
+ delay_max = get_action_delay_max(device, cmd->action);
+ delay_base = get_action_delay_base(device, cmd->action, cmd->target);
+ if (delay_max == 0) {
+ delay_max = delay_base;
+ }
+ if (delay_max < delay_base) {
+ crm_warn(PCMK_STONITH_DELAY_BASE " (%ds) is larger than "
+ PCMK_STONITH_DELAY_MAX " (%ds) for %s using %s "
+ "(limiting to maximum delay)",
+ delay_base, delay_max, cmd->action, device->id);
+ delay_base = delay_max;
+ }
+ if (delay_max > 0) {
+ // coverity[dont_call] We're not using rand() for security
+ cmd->start_delay +=
+ ((delay_max != delay_base)?(rand() % (delay_max - delay_base)):0)
+ + delay_base;
+ }
+
+ if (cmd->start_delay > 0) {
+ crm_notice("Delaying '%s' action%s%s using %s for %ds " CRM_XS
+ " timeout=%ds requested_delay=%ds base=%ds max=%ds",
+ cmd->action,
+ (cmd->target == NULL)? "" : " targeting ",
+ pcmk__s(cmd->target, ""),
+ device->id, cmd->start_delay, cmd->timeout,
+ requested_delay, delay_base, delay_max);
+ cmd->delay_id =
+ g_timeout_add_seconds(cmd->start_delay, start_delay_helper, cmd);
+ }
+}
+
+static void
+free_device(gpointer data)
+{
+ GList *gIter = NULL;
+ stonith_device_t *device = data;
+
+ g_hash_table_destroy(device->params);
+ g_hash_table_destroy(device->aliases);
+
+ for (gIter = device->pending_ops; gIter != NULL; gIter = gIter->next) {
+ async_command_t *cmd = gIter->data;
+
+ crm_warn("Removal of device '%s' purged operation '%s'", device->id, cmd->action);
+ report_internal_result(cmd, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "Device was removed before action could be executed");
+ }
+ g_list_free(device->pending_ops);
+
+ g_list_free_full(device->targets, free);
+
+ if (device->timer) {
+ mainloop_timer_stop(device->timer);
+ mainloop_timer_del(device->timer);
+ }
+
+ mainloop_destroy_trigger(device->work);
+
+ free_xml(device->agent_metadata);
+ free(device->namespace);
+ if (device->on_target_actions != NULL) {
+ g_string_free(device->on_target_actions, TRUE);
+ }
+ free(device->agent);
+ free(device->id);
+ free(device);
+}
+
+void free_device_list(void)
+{
+ if (device_list != NULL) {
+ g_hash_table_destroy(device_list);
+ device_list = NULL;
+ }
+}
+
+void
+init_device_list(void)
+{
+ if (device_list == NULL) {
+ device_list = pcmk__strkey_table(NULL, free_device);
+ }
+}
+
+static GHashTable *
+build_port_aliases(const char *hostmap, GList ** targets)
+{
+ char *name = NULL;
+ int last = 0, lpc = 0, max = 0, added = 0;
+ GHashTable *aliases = pcmk__strikey_table(free, free);
+
+ if (hostmap == NULL) {
+ return aliases;
+ }
+
+ max = strlen(hostmap);
+ for (; lpc <= max; lpc++) {
+ switch (hostmap[lpc]) {
+ /* Skip escaped chars */
+ case '\\':
+ lpc++;
+ break;
+
+ /* Assignment chars */
+ case '=':
+ case ':':
+ if (lpc > last) {
+ free(name);
+ name = calloc(1, 1 + lpc - last);
+ memcpy(name, hostmap + last, lpc - last);
+ }
+ last = lpc + 1;
+ break;
+
+ /* Delimeter chars */
+ /* case ',': Potentially used to specify multiple ports */
+ case 0:
+ case ';':
+ case ' ':
+ case '\t':
+ if (name) {
+ char *value = NULL;
+ int k = 0;
+
+ value = calloc(1, 1 + lpc - last);
+ memcpy(value, hostmap + last, lpc - last);
+
+ for (int i = 0; value[i] != '\0'; i++) {
+ if (value[i] != '\\') {
+ value[k++] = value[i];
+ }
+ }
+ value[k] = '\0';
+
+ crm_debug("Adding alias '%s'='%s'", name, value);
+ g_hash_table_replace(aliases, name, value);
+ if (targets) {
+ *targets = g_list_append(*targets, strdup(value));
+ }
+ value = NULL;
+ name = NULL;
+ added++;
+
+ } else if (lpc > last) {
+ crm_debug("Parse error at offset %d near '%s'", lpc - last, hostmap + last);
+ }
+
+ last = lpc + 1;
+ break;
+ }
+
+ if (hostmap[lpc] == 0) {
+ break;
+ }
+ }
+
+ if (added == 0) {
+ crm_info("No host mappings detected in '%s'", hostmap);
+ }
+
+ free(name);
+ return aliases;
+}
+
+GHashTable *metadata_cache = NULL;
+
+void
+free_metadata_cache(void) {
+ if (metadata_cache != NULL) {
+ g_hash_table_destroy(metadata_cache);
+ metadata_cache = NULL;
+ }
+}
+
+static void
+init_metadata_cache(void) {
+ if (metadata_cache == NULL) {
+ metadata_cache = pcmk__strkey_table(free, free);
+ }
+}
+
+int
+get_agent_metadata(const char *agent, xmlNode ** metadata)
+{
+ char *buffer = NULL;
+
+ if (metadata == NULL) {
+ return EINVAL;
+ }
+ *metadata = NULL;
+ if (pcmk__str_eq(agent, STONITH_WATCHDOG_AGENT_INTERNAL, pcmk__str_none)) {
+ return pcmk_rc_ok;
+ }
+ init_metadata_cache();
+ buffer = g_hash_table_lookup(metadata_cache, agent);
+ if (buffer == NULL) {
+ stonith_t *st = stonith_api_new();
+ int rc;
+
+ if (st == NULL) {
+ crm_warn("Could not get agent meta-data: "
+ "API memory allocation failed");
+ return EAGAIN;
+ }
+ rc = st->cmds->metadata(st, st_opt_sync_call, agent,
+ NULL, &buffer, 10);
+ stonith_api_delete(st);
+ if (rc || !buffer) {
+ crm_err("Could not retrieve metadata for fencing agent %s", agent);
+ return EAGAIN;
+ }
+ g_hash_table_replace(metadata_cache, strdup(agent), buffer);
+ }
+
+ *metadata = string2xml(buffer);
+ return pcmk_rc_ok;
+}
+
+static gboolean
+is_nodeid_required(xmlNode * xml)
+{
+ xmlXPathObjectPtr xpath = NULL;
+
+ if (stand_alone) {
+ return FALSE;
+ }
+
+ if (!xml) {
+ return FALSE;
+ }
+
+ xpath = xpath_search(xml, "//parameter[@name='nodeid']");
+ if (numXpathResults(xpath) <= 0) {
+ freeXpathObject(xpath);
+ return FALSE;
+ }
+
+ freeXpathObject(xpath);
+ return TRUE;
+}
+
+static void
+read_action_metadata(stonith_device_t *device)
+{
+ xmlXPathObjectPtr xpath = NULL;
+ int max = 0;
+ int lpc = 0;
+
+ if (device->agent_metadata == NULL) {
+ return;
+ }
+
+ xpath = xpath_search(device->agent_metadata, "//action");
+ max = numXpathResults(xpath);
+
+ if (max <= 0) {
+ freeXpathObject(xpath);
+ return;
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ const char *action = NULL;
+ xmlNode *match = getXpathResult(xpath, lpc);
+
+ CRM_LOG_ASSERT(match != NULL);
+ if(match == NULL) { continue; };
+
+ action = crm_element_value(match, "name");
+
+ if (pcmk__str_eq(action, "list", pcmk__str_none)) {
+ stonith__set_device_flags(device->flags, device->id,
+ st_device_supports_list);
+ } else if (pcmk__str_eq(action, "status", pcmk__str_none)) {
+ stonith__set_device_flags(device->flags, device->id,
+ st_device_supports_status);
+ } else if (pcmk__str_eq(action, "reboot", pcmk__str_none)) {
+ stonith__set_device_flags(device->flags, device->id,
+ st_device_supports_reboot);
+ } else if (pcmk__str_eq(action, "on", pcmk__str_none)) {
+ /* "automatic" means the cluster will unfence node when it joins */
+ /* "required" is a deprecated synonym for "automatic" */
+ if (pcmk__xe_attr_is_true(match, "automatic") || pcmk__xe_attr_is_true(match, "required")) {
+ device->automatic_unfencing = TRUE;
+ }
+ stonith__set_device_flags(device->flags, device->id,
+ st_device_supports_on);
+ }
+
+ if ((action != NULL) && pcmk__xe_attr_is_true(match, "on_target")) {
+ pcmk__add_word(&(device->on_target_actions), 64, action);
+ }
+ }
+
+ freeXpathObject(xpath);
+}
+
+/*!
+ * \internal
+ * \brief Set a pcmk_*_action parameter if not already set
+ *
+ * \param[in,out] params Device parameters
+ * \param[in] action Name of action
+ * \param[in] value Value to use if action is not already set
+ */
+static void
+map_action(GHashTable *params, const char *action, const char *value)
+{
+ char *key = crm_strdup_printf("pcmk_%s_action", action);
+
+ if (g_hash_table_lookup(params, key)) {
+ crm_warn("Ignoring %s='%s', see %s instead",
+ STONITH_ATTR_ACTION_OP, value, key);
+ free(key);
+ } else {
+ crm_warn("Mapping %s='%s' to %s='%s'",
+ STONITH_ATTR_ACTION_OP, value, key, value);
+ g_hash_table_insert(params, key, strdup(value));
+ }
+}
+
+/*!
+ * \internal
+ * \brief Create device parameter table from XML
+ *
+ * \param[in] name Device name (used for logging only)
+ * \param[in] dev XML containing device parameters
+ */
+static GHashTable *
+xml2device_params(const char *name, const xmlNode *dev)
+{
+ GHashTable *params = xml2list(dev);
+ const char *value;
+
+ /* Action should never be specified in the device configuration,
+ * but we support it for users who are familiar with other software
+ * that worked that way.
+ */
+ value = g_hash_table_lookup(params, STONITH_ATTR_ACTION_OP);
+ if (value != NULL) {
+ crm_warn("%s has '%s' parameter, which should never be specified in configuration",
+ name, STONITH_ATTR_ACTION_OP);
+
+ if (*value == '\0') {
+ crm_warn("Ignoring empty '%s' parameter", STONITH_ATTR_ACTION_OP);
+
+ } else if (strcmp(value, "reboot") == 0) {
+ crm_warn("Ignoring %s='reboot' (see stonith-action cluster property instead)",
+ STONITH_ATTR_ACTION_OP);
+
+ } else if (strcmp(value, "off") == 0) {
+ map_action(params, "reboot", value);
+
+ } else {
+ map_action(params, "off", value);
+ map_action(params, "reboot", value);
+ }
+
+ g_hash_table_remove(params, STONITH_ATTR_ACTION_OP);
+ }
+
+ return params;
+}
+
+static const char *
+target_list_type(stonith_device_t * dev)
+{
+ const char *check_type = NULL;
+
+ check_type = g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK);
+
+ if (check_type == NULL) {
+
+ if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_LIST)) {
+ check_type = "static-list";
+ } else if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_MAP)) {
+ check_type = "static-list";
+ } else if (pcmk_is_set(dev->flags, st_device_supports_list)) {
+ check_type = "dynamic-list";
+ } else if (pcmk_is_set(dev->flags, st_device_supports_status)) {
+ check_type = "status";
+ } else {
+ check_type = PCMK__VALUE_NONE;
+ }
+ }
+
+ return check_type;
+}
+
+static stonith_device_t *
+build_device_from_xml(xmlNode *dev)
+{
+ const char *value;
+ stonith_device_t *device = NULL;
+ char *agent = crm_element_value_copy(dev, "agent");
+
+ CRM_CHECK(agent != NULL, return device);
+
+ device = calloc(1, sizeof(stonith_device_t));
+
+ CRM_CHECK(device != NULL, {free(agent); return device;});
+
+ device->id = crm_element_value_copy(dev, XML_ATTR_ID);
+ device->agent = agent;
+ device->namespace = crm_element_value_copy(dev, "namespace");
+ device->params = xml2device_params(device->id, dev);
+
+ value = g_hash_table_lookup(device->params, PCMK_STONITH_HOST_LIST);
+ if (value) {
+ device->targets = stonith__parse_targets(value);
+ }
+
+ value = g_hash_table_lookup(device->params, PCMK_STONITH_HOST_MAP);
+ device->aliases = build_port_aliases(value, &(device->targets));
+
+ value = target_list_type(device);
+ if (!pcmk__str_eq(value, "static-list", pcmk__str_casei) && device->targets) {
+ /* Other than "static-list", dev-> targets is unnecessary. */
+ g_list_free_full(device->targets, free);
+ device->targets = NULL;
+ }
+ switch (get_agent_metadata(device->agent, &device->agent_metadata)) {
+ case pcmk_rc_ok:
+ if (device->agent_metadata) {
+ read_action_metadata(device);
+ stonith__device_parameter_flags(&(device->flags), device->id,
+ device->agent_metadata);
+ }
+ break;
+
+ case EAGAIN:
+ if (device->timer == NULL) {
+ device->timer = mainloop_timer_add("get_agent_metadata", 10 * 1000,
+ TRUE, get_agent_metadata_cb, device);
+ }
+ if (!mainloop_timer_running(device->timer)) {
+ mainloop_timer_start(device->timer);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ value = g_hash_table_lookup(device->params, "nodeid");
+ if (!value) {
+ device->include_nodeid = is_nodeid_required(device->agent_metadata);
+ }
+
+ value = crm_element_value(dev, "rsc_provides");
+ if (pcmk__str_eq(value, PCMK__VALUE_UNFENCING, pcmk__str_casei)) {
+ device->automatic_unfencing = TRUE;
+ }
+
+ if (is_action_required("on", device)) {
+ crm_info("Fencing device '%s' requires unfencing", device->id);
+ }
+
+ if (device->on_target_actions != NULL) {
+ crm_info("Fencing device '%s' requires actions (%s) to be executed "
+ "on target", device->id,
+ (const char *) device->on_target_actions->str);
+ }
+
+ device->work = mainloop_add_trigger(G_PRIORITY_HIGH, stonith_device_dispatch, device);
+ /* TODO: Hook up priority */
+
+ return device;
+}
+
+static void
+schedule_internal_command(const char *origin,
+ stonith_device_t * device,
+ const char *action,
+ const char *target,
+ int timeout,
+ void *internal_user_data,
+ void (*done_cb) (int pid,
+ const pcmk__action_result_t *result,
+ void *user_data))
+{
+ async_command_t *cmd = NULL;
+
+ cmd = calloc(1, sizeof(async_command_t));
+
+ cmd->id = -1;
+ cmd->default_timeout = timeout ? timeout : 60;
+ cmd->timeout = cmd->default_timeout;
+ cmd->action = strdup(action);
+ pcmk__str_update(&cmd->target, target);
+ cmd->device = strdup(device->id);
+ cmd->origin = strdup(origin);
+ cmd->client = strdup(crm_system_name);
+ cmd->client_name = strdup(crm_system_name);
+
+ cmd->internal_user_data = internal_user_data;
+ cmd->done_cb = done_cb; /* cmd, not internal_user_data, is passed to 'done_cb' as the userdata */
+
+ schedule_stonith_command(cmd, device);
+}
+
+// Fence agent status commands use custom exit status codes
+enum fence_status_code {
+ fence_status_invalid = -1,
+ fence_status_active = 0,
+ fence_status_unknown = 1,
+ fence_status_inactive = 2,
+};
+
+static void
+status_search_cb(int pid, const pcmk__action_result_t *result, void *user_data)
+{
+ async_command_t *cmd = user_data;
+ struct device_search_s *search = cmd->internal_user_data;
+ stonith_device_t *dev = cmd_device(cmd);
+ gboolean can = FALSE;
+
+ free_async_command(cmd);
+
+ if (!dev) {
+ search_devices_record_result(search, NULL, FALSE);
+ return;
+ }
+
+ mainloop_set_trigger(dev->work);
+
+ if (result->execution_status != PCMK_EXEC_DONE) {
+ crm_warn("Assuming %s cannot fence %s "
+ "because status could not be executed: %s%s%s%s",
+ dev->id, search->host,
+ pcmk_exec_status_str(result->execution_status),
+ ((result->exit_reason == NULL)? "" : " ("),
+ ((result->exit_reason == NULL)? "" : result->exit_reason),
+ ((result->exit_reason == NULL)? "" : ")"));
+ search_devices_record_result(search, dev->id, FALSE);
+ return;
+ }
+
+ switch (result->exit_status) {
+ case fence_status_unknown:
+ crm_trace("%s reported it cannot fence %s", dev->id, search->host);
+ break;
+
+ case fence_status_active:
+ case fence_status_inactive:
+ crm_trace("%s reported it can fence %s", dev->id, search->host);
+ can = TRUE;
+ break;
+
+ default:
+ crm_warn("Assuming %s cannot fence %s "
+ "(status returned unknown code %d)",
+ dev->id, search->host, result->exit_status);
+ break;
+ }
+ search_devices_record_result(search, dev->id, can);
+}
+
+static void
+dynamic_list_search_cb(int pid, const pcmk__action_result_t *result,
+ void *user_data)
+{
+ async_command_t *cmd = user_data;
+ struct device_search_s *search = cmd->internal_user_data;
+ stonith_device_t *dev = cmd_device(cmd);
+ gboolean can_fence = FALSE;
+
+ free_async_command(cmd);
+
+ /* Host/alias must be in the list output to be eligible to be fenced
+ *
+ * Will cause problems if down'd nodes aren't listed or (for virtual nodes)
+ * if the guest is still listed despite being moved to another machine
+ */
+ if (!dev) {
+ search_devices_record_result(search, NULL, FALSE);
+ return;
+ }
+
+ mainloop_set_trigger(dev->work);
+
+ if (pcmk__result_ok(result)) {
+ crm_info("Refreshing target list for %s", dev->id);
+ g_list_free_full(dev->targets, free);
+ dev->targets = stonith__parse_targets(result->action_stdout);
+ dev->targets_age = time(NULL);
+
+ } else if (dev->targets != NULL) {
+ if (result->execution_status == PCMK_EXEC_DONE) {
+ crm_info("Reusing most recent target list for %s "
+ "because list returned error code %d",
+ dev->id, result->exit_status);
+ } else {
+ crm_info("Reusing most recent target list for %s "
+ "because list could not be executed: %s%s%s%s",
+ dev->id, pcmk_exec_status_str(result->execution_status),
+ ((result->exit_reason == NULL)? "" : " ("),
+ ((result->exit_reason == NULL)? "" : result->exit_reason),
+ ((result->exit_reason == NULL)? "" : ")"));
+ }
+
+ } else { // We have never successfully executed list
+ if (result->execution_status == PCMK_EXEC_DONE) {
+ crm_warn("Assuming %s cannot fence %s "
+ "because list returned error code %d",
+ dev->id, search->host, result->exit_status);
+ } else {
+ crm_warn("Assuming %s cannot fence %s "
+ "because list could not be executed: %s%s%s%s",
+ dev->id, search->host,
+ pcmk_exec_status_str(result->execution_status),
+ ((result->exit_reason == NULL)? "" : " ("),
+ ((result->exit_reason == NULL)? "" : result->exit_reason),
+ ((result->exit_reason == NULL)? "" : ")"));
+ }
+
+ /* Fall back to pcmk_host_check="status" if the user didn't explicitly
+ * specify "dynamic-list".
+ */
+ if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK) == NULL) {
+ crm_notice("Switching to pcmk_host_check='status' for %s", dev->id);
+ g_hash_table_replace(dev->params, strdup(PCMK_STONITH_HOST_CHECK),
+ strdup("status"));
+ }
+ }
+
+ if (dev->targets) {
+ const char *alias = g_hash_table_lookup(dev->aliases, search->host);
+
+ if (!alias) {
+ alias = search->host;
+ }
+ if (pcmk__str_in_list(alias, dev->targets, pcmk__str_casei)) {
+ can_fence = TRUE;
+ }
+ }
+ search_devices_record_result(search, dev->id, can_fence);
+}
+
+/*!
+ * \internal
+ * \brief Returns true if any key in first is not in second or second has a different value for key
+ */
+static int
+device_params_diff(GHashTable *first, GHashTable *second) {
+ char *key = NULL;
+ char *value = NULL;
+ GHashTableIter gIter;
+
+ g_hash_table_iter_init(&gIter, first);
+ while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&value)) {
+
+ if(strstr(key, "CRM_meta") == key) {
+ continue;
+ } else if(strcmp(key, "crm_feature_set") == 0) {
+ continue;
+ } else {
+ char *other_value = g_hash_table_lookup(second, key);
+
+ if (!other_value || !pcmk__str_eq(other_value, value, pcmk__str_casei)) {
+ crm_trace("Different value for %s: %s != %s", key, other_value, value);
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Checks to see if an identical device already exists in the device_list
+ */
+static stonith_device_t *
+device_has_duplicate(const stonith_device_t *device)
+{
+ stonith_device_t *dup = g_hash_table_lookup(device_list, device->id);
+
+ if (!dup) {
+ crm_trace("No match for %s", device->id);
+ return NULL;
+
+ } else if (!pcmk__str_eq(dup->agent, device->agent, pcmk__str_casei)) {
+ crm_trace("Different agent: %s != %s", dup->agent, device->agent);
+ return NULL;
+ }
+
+ /* Use calculate_operation_digest() here? */
+ if (device_params_diff(device->params, dup->params) ||
+ device_params_diff(dup->params, device->params)) {
+ return NULL;
+ }
+
+ crm_trace("Match");
+ return dup;
+}
+
+int
+stonith_device_register(xmlNode *dev, gboolean from_cib)
+{
+ stonith_device_t *dup = NULL;
+ stonith_device_t *device = build_device_from_xml(dev);
+ guint ndevices = 0;
+ int rv = pcmk_ok;
+
+ CRM_CHECK(device != NULL, return -ENOMEM);
+
+ /* do we have a watchdog-device? */
+ if (pcmk__str_eq(device->id, STONITH_WATCHDOG_ID, pcmk__str_none) ||
+ pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) do {
+ if (stonith_watchdog_timeout_ms <= 0) {
+ crm_err("Ignoring watchdog fence device without "
+ "stonith-watchdog-timeout set.");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else if (!pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
+ STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
+ crm_err("Ignoring watchdog fence device with unknown "
+ "agent '%s' unequal '" STONITH_WATCHDOG_AGENT "'.",
+ device->agent?device->agent:"");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else if (!pcmk__str_eq(device->id, STONITH_WATCHDOG_ID,
+ pcmk__str_none)) {
+ crm_err("Ignoring watchdog fence device "
+ "named %s !='"STONITH_WATCHDOG_ID"'.",
+ device->id?device->id:"");
+ rv = -ENODEV;
+ /* fall through to cleanup & return */
+ } else {
+ if (pcmk__str_eq(device->agent, STONITH_WATCHDOG_AGENT,
+ pcmk__str_none)) {
+ /* this either has an empty list or the targets
+ configured for watchdog-fencing
+ */
+ g_list_free_full(stonith_watchdog_targets, free);
+ stonith_watchdog_targets = device->targets;
+ device->targets = NULL;
+ }
+ if (node_does_watchdog_fencing(stonith_our_uname)) {
+ g_list_free_full(device->targets, free);
+ device->targets = stonith__parse_targets(stonith_our_uname);
+ g_hash_table_replace(device->params,
+ strdup(PCMK_STONITH_HOST_LIST),
+ strdup(stonith_our_uname));
+ /* proceed as with any other stonith-device */
+ break;
+ }
+
+ crm_debug("Skip registration of watchdog fence device on node not in host-list.");
+ /* cleanup and fall through to more cleanup and return */
+ device->targets = NULL;
+ stonith_device_remove(device->id, from_cib);
+ }
+ free_device(device);
+ return rv;
+ } while (0);
+
+ dup = device_has_duplicate(device);
+ if (dup) {
+ ndevices = g_hash_table_size(device_list);
+ crm_debug("Device '%s' already in device list (%d active device%s)",
+ device->id, ndevices, pcmk__plural_s(ndevices));
+ free_device(device);
+ device = dup;
+ dup = g_hash_table_lookup(device_list, device->id);
+ dup->dirty = FALSE;
+
+ } else {
+ stonith_device_t *old = g_hash_table_lookup(device_list, device->id);
+
+ if (from_cib && old && old->api_registered) {
+ /* If the cib is writing over an entry that is shared with a stonith client,
+ * copy any pending ops that currently exist on the old entry to the new one.
+ * Otherwise the pending ops will be reported as failures
+ */
+ crm_info("Overwriting existing entry for %s from CIB", device->id);
+ device->pending_ops = old->pending_ops;
+ device->api_registered = TRUE;
+ old->pending_ops = NULL;
+ if (device->pending_ops) {
+ mainloop_set_trigger(device->work);
+ }
+ }
+ g_hash_table_replace(device_list, device->id, device);
+
+ ndevices = g_hash_table_size(device_list);
+ crm_notice("Added '%s' to device list (%d active device%s)",
+ device->id, ndevices, pcmk__plural_s(ndevices));
+ }
+
+ if (from_cib) {
+ device->cib_registered = TRUE;
+ } else {
+ device->api_registered = TRUE;
+ }
+
+ return pcmk_ok;
+}
+
+void
+stonith_device_remove(const char *id, bool from_cib)
+{
+ stonith_device_t *device = g_hash_table_lookup(device_list, id);
+ guint ndevices = 0;
+
+ if (!device) {
+ ndevices = g_hash_table_size(device_list);
+ crm_info("Device '%s' not found (%d active device%s)",
+ id, ndevices, pcmk__plural_s(ndevices));
+ return;
+ }
+
+ if (from_cib) {
+ device->cib_registered = FALSE;
+ } else {
+ device->verified = FALSE;
+ device->api_registered = FALSE;
+ }
+
+ if (!device->cib_registered && !device->api_registered) {
+ g_hash_table_remove(device_list, id);
+ ndevices = g_hash_table_size(device_list);
+ crm_info("Removed '%s' from device list (%d active device%s)",
+ id, ndevices, pcmk__plural_s(ndevices));
+ } else {
+ crm_trace("Not removing '%s' from device list (%d active) because "
+ "still registered via:%s%s",
+ id, g_hash_table_size(device_list),
+ (device->cib_registered? " cib" : ""),
+ (device->api_registered? " api" : ""));
+ }
+}
+
+/*!
+ * \internal
+ * \brief Return the number of stonith levels registered for a node
+ *
+ * \param[in] tp Node's topology table entry
+ *
+ * \return Number of non-NULL levels in topology entry
+ * \note This function is used only for log messages.
+ */
+static int
+count_active_levels(const stonith_topology_t *tp)
+{
+ int lpc = 0;
+ int count = 0;
+
+ for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
+ if (tp->levels[lpc] != NULL) {
+ count++;
+ }
+ }
+ return count;
+}
+
+static void
+free_topology_entry(gpointer data)
+{
+ stonith_topology_t *tp = data;
+
+ int lpc = 0;
+
+ for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
+ if (tp->levels[lpc] != NULL) {
+ g_list_free_full(tp->levels[lpc], free);
+ }
+ }
+ free(tp->target);
+ free(tp->target_value);
+ free(tp->target_pattern);
+ free(tp->target_attribute);
+ free(tp);
+}
+
+void
+free_topology_list(void)
+{
+ if (topology != NULL) {
+ g_hash_table_destroy(topology);
+ topology = NULL;
+ }
+}
+
+void
+init_topology_list(void)
+{
+ if (topology == NULL) {
+ topology = pcmk__strkey_table(NULL, free_topology_entry);
+ }
+}
+
+char *
+stonith_level_key(const xmlNode *level, enum fenced_target_by mode)
+{
+ if (mode == fenced_target_by_unknown) {
+ mode = unpack_level_kind(level);
+ }
+ switch (mode) {
+ case fenced_target_by_name:
+ return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET);
+
+ case fenced_target_by_pattern:
+ return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
+
+ case fenced_target_by_attribute:
+ return crm_strdup_printf("%s=%s",
+ crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE),
+ crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE));
+
+ default:
+ return crm_strdup_printf("unknown-%s", ID(level));
+ }
+}
+
+/*!
+ * \internal
+ * \brief Parse target identification from topology level XML
+ *
+ * \param[in] level Topology level XML to parse
+ *
+ * \return How to identify target of \p level
+ */
+static enum fenced_target_by
+unpack_level_kind(const xmlNode *level)
+{
+ if (crm_element_value(level, XML_ATTR_STONITH_TARGET) != NULL) {
+ return fenced_target_by_name;
+ }
+ if (crm_element_value(level, XML_ATTR_STONITH_TARGET_PATTERN) != NULL) {
+ return fenced_target_by_pattern;
+ }
+ if (!stand_alone /* if standalone, there's no attribute manager */
+ && (crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE) != NULL)
+ && (crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE) != NULL)) {
+ return fenced_target_by_attribute;
+ }
+ return fenced_target_by_unknown;
+}
+
+static stonith_key_value_t *
+parse_device_list(const char *devices)
+{
+ int lpc = 0;
+ int max = 0;
+ int last = 0;
+ stonith_key_value_t *output = NULL;
+
+ if (devices == NULL) {
+ return output;
+ }
+
+ max = strlen(devices);
+ for (lpc = 0; lpc <= max; lpc++) {
+ if (devices[lpc] == ',' || devices[lpc] == 0) {
+ char *line = strndup(devices + last, lpc - last);
+
+ output = stonith_key_value_add(output, NULL, line);
+ free(line);
+
+ last = lpc + 1;
+ }
+ }
+
+ return output;
+}
+
+/*!
+ * \internal
+ * \brief Unpack essential information from topology request XML
+ *
+ * \param[in] xml Request XML to search
+ * \param[out] mode If not NULL, where to store level kind
+ * \param[out] target If not NULL, where to store representation of target
+ * \param[out] id If not NULL, where to store level number
+ * \param[out] desc If not NULL, where to store log-friendly level description
+ *
+ * \return Topology level XML from within \p xml, or NULL if not found
+ * \note The caller is responsible for freeing \p *target and \p *desc if set.
+ */
+static xmlNode *
+unpack_level_request(xmlNode *xml, enum fenced_target_by *mode, char **target,
+ int *id, char **desc)
+{
+ enum fenced_target_by local_mode = fenced_target_by_unknown;
+ char *local_target = NULL;
+ int local_id = 0;
+
+ /* The level element can be the top element or lower. If top level, don't
+ * search by xpath, because it might give multiple hits if the XML is the
+ * CIB.
+ */
+ if ((xml != NULL)
+ && !pcmk__str_eq(TYPE(xml), XML_TAG_FENCING_LEVEL, pcmk__str_none)) {
+ xml = get_xpath_object("//" XML_TAG_FENCING_LEVEL, xml, LOG_WARNING);
+ }
+
+ if (xml == NULL) {
+ if (desc != NULL) {
+ *desc = crm_strdup_printf("missing");
+ }
+ } else {
+ local_mode = unpack_level_kind(xml);
+ local_target = stonith_level_key(xml, local_mode);
+ crm_element_value_int(xml, XML_ATTR_STONITH_INDEX, &local_id);
+ if (desc != NULL) {
+ *desc = crm_strdup_printf("%s[%d]", local_target, local_id);
+ }
+ }
+
+ if (mode != NULL) {
+ *mode = local_mode;
+ }
+ if (id != NULL) {
+ *id = local_id;
+ }
+
+ if (target != NULL) {
+ *target = local_target;
+ } else {
+ free(local_target);
+ }
+
+ return xml;
+}
+
+/*!
+ * \internal
+ * \brief Register a fencing topology level for a target
+ *
+ * Given an XML request specifying the target name, level index, and device IDs
+ * for the level, this will create an entry for the target in the global topology
+ * table if one does not already exist, then append the specified device IDs to
+ * the entry's device list for the specified level.
+ *
+ * \param[in] msg XML request for STONITH level registration
+ * \param[out] desc If not NULL, set to string representation "TARGET[LEVEL]"
+ * \param[out] result Where to set result of registration
+ */
+void
+fenced_register_level(xmlNode *msg, char **desc, pcmk__action_result_t *result)
+{
+ int id = 0;
+ xmlNode *level;
+ enum fenced_target_by mode;
+ char *target;
+
+ stonith_topology_t *tp;
+ stonith_key_value_t *dIter = NULL;
+ stonith_key_value_t *devices = NULL;
+
+ CRM_CHECK((msg != NULL) && (result != NULL), return);
+
+ level = unpack_level_request(msg, &mode, &target, &id, desc);
+ if (level == NULL) {
+ fenced_set_protocol_error(result);
+ return;
+ }
+
+ // Ensure an ID was given (even the client API adds an ID)
+ if (pcmk__str_empty(ID(level))) {
+ crm_warn("Ignoring registration for topology level without ID");
+ free(target);
+ crm_log_xml_trace(level, "Bad level");
+ pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
+ "Topology level is invalid without ID");
+ return;
+ }
+
+ // Ensure a valid target was specified
+ if (mode == fenced_target_by_unknown) {
+ crm_warn("Ignoring registration for topology level '%s' "
+ "without valid target", ID(level));
+ free(target);
+ crm_log_xml_trace(level, "Bad level");
+ pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
+ "Invalid target for topology level '%s'",
+ ID(level));
+ return;
+ }
+
+ // Ensure level ID is in allowed range
+ if ((id <= 0) || (id >= ST_LEVEL_MAX)) {
+ crm_warn("Ignoring topology registration for %s with invalid level %d",
+ target, id);
+ free(target);
+ crm_log_xml_trace(level, "Bad level");
+ pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
+ "Invalid level number '%s' for topology level '%s'",
+ pcmk__s(crm_element_value(level,
+ XML_ATTR_STONITH_INDEX),
+ ""),
+ ID(level));
+ return;
+ }
+
+ /* Find or create topology table entry */
+ tp = g_hash_table_lookup(topology, target);
+ if (tp == NULL) {
+ tp = calloc(1, sizeof(stonith_topology_t));
+ if (tp == NULL) {
+ pcmk__set_result(result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ strerror(ENOMEM));
+ free(target);
+ return;
+ }
+ tp->kind = mode;
+ tp->target = target;
+ tp->target_value = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_VALUE);
+ tp->target_pattern = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
+ tp->target_attribute = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE);
+
+ g_hash_table_replace(topology, tp->target, tp);
+ crm_trace("Added %s (%d) to the topology (%d active entries)",
+ target, (int) mode, g_hash_table_size(topology));
+ } else {
+ free(target);
+ }
+
+ if (tp->levels[id] != NULL) {
+ crm_info("Adding to the existing %s[%d] topology entry",
+ tp->target, id);
+ }
+
+ devices = parse_device_list(crm_element_value(level, XML_ATTR_STONITH_DEVICES));
+ for (dIter = devices; dIter; dIter = dIter->next) {
+ const char *device = dIter->value;
+
+ crm_trace("Adding device '%s' for %s[%d]", device, tp->target, id);
+ tp->levels[id] = g_list_append(tp->levels[id], strdup(device));
+ }
+ stonith_key_value_freeall(devices, 1, 1);
+
+ {
+ int nlevels = count_active_levels(tp);
+
+ crm_info("Target %s has %d active fencing level%s",
+ tp->target, nlevels, pcmk__plural_s(nlevels));
+ }
+
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Unregister a fencing topology level for a target
+ *
+ * Given an XML request specifying the target name and level index (or 0 for all
+ * levels), this will remove any corresponding entry for the target from the
+ * global topology table.
+ *
+ * \param[in] msg XML request for STONITH level registration
+ * \param[out] desc If not NULL, set to string representation "TARGET[LEVEL]"
+ * \param[out] result Where to set result of unregistration
+ */
+void
+fenced_unregister_level(xmlNode *msg, char **desc,
+ pcmk__action_result_t *result)
+{
+ int id = -1;
+ stonith_topology_t *tp;
+ char *target;
+ xmlNode *level = NULL;
+
+ CRM_CHECK(result != NULL, return);
+
+ level = unpack_level_request(msg, NULL, &target, &id, desc);
+ if (level == NULL) {
+ fenced_set_protocol_error(result);
+ return;
+ }
+
+ // Ensure level ID is in allowed range
+ if ((id < 0) || (id >= ST_LEVEL_MAX)) {
+ crm_warn("Ignoring topology unregistration for %s with invalid level %d",
+ target, id);
+ free(target);
+ crm_log_xml_trace(level, "Bad level");
+ pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
+ "Invalid level number '%s' for topology level %s",
+ pcmk__s(crm_element_value(level,
+ XML_ATTR_STONITH_INDEX),
+ "<null>"),
+
+ // Client API doesn't add ID to unregistration XML
+ pcmk__s(ID(level), ""));
+ return;
+ }
+
+ tp = g_hash_table_lookup(topology, target);
+ if (tp == NULL) {
+ guint nentries = g_hash_table_size(topology);
+
+ crm_info("No fencing topology found for %s (%d active %s)",
+ target, nentries,
+ pcmk__plural_alt(nentries, "entry", "entries"));
+
+ } else if (id == 0 && g_hash_table_remove(topology, target)) {
+ guint nentries = g_hash_table_size(topology);
+
+ crm_info("Removed all fencing topology entries related to %s "
+ "(%d active %s remaining)", target, nentries,
+ pcmk__plural_alt(nentries, "entry", "entries"));
+
+ } else if (tp->levels[id] != NULL) {
+ guint nlevels;
+
+ g_list_free_full(tp->levels[id], free);
+ tp->levels[id] = NULL;
+
+ nlevels = count_active_levels(tp);
+ crm_info("Removed level %d from fencing topology for %s "
+ "(%d active level%s remaining)",
+ id, target, nlevels, pcmk__plural_s(nlevels));
+ }
+
+ free(target);
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+}
+
+static char *
+list_to_string(GList *list, const char *delim, gboolean terminate_with_delim)
+{
+ int max = g_list_length(list);
+ size_t delim_len = delim?strlen(delim):0;
+ size_t alloc_size = 1 + (max?((max-1+(terminate_with_delim?1:0))*delim_len):0);
+ char *rv;
+ GList *gIter;
+
+ for (gIter = list; gIter != NULL; gIter = gIter->next) {
+ const char *value = (const char *) gIter->data;
+
+ alloc_size += strlen(value);
+ }
+ rv = calloc(alloc_size, sizeof(char));
+ if (rv) {
+ char *pos = rv;
+ const char *lead_delim = "";
+
+ for (gIter = list; gIter != NULL; gIter = gIter->next) {
+ const char *value = (const char *) gIter->data;
+
+ pos = &pos[sprintf(pos, "%s%s", lead_delim, value)];
+ lead_delim = delim;
+ }
+ if (max && terminate_with_delim) {
+ sprintf(pos, "%s", delim);
+ }
+ }
+ return rv;
+}
+
+/*!
+ * \internal
+ * \brief Execute a fence agent action directly (and asynchronously)
+ *
+ * Handle a STONITH_OP_EXEC API message by scheduling a requested agent action
+ * directly on a specified device. Only list, monitor, and status actions are
+ * expected to use this call, though it should work with any agent command.
+ *
+ * \param[in] msg Request XML specifying action
+ * \param[out] result Where to store result of action
+ *
+ * \note If the action is monitor, the device must be registered via the API
+ * (CIB registration is not sufficient), because monitor should not be
+ * possible unless the device is "started" (API registered).
+ */
+static void
+execute_agent_action(xmlNode *msg, pcmk__action_result_t *result)
+{
+ xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR);
+ xmlNode *op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR);
+ const char *id = crm_element_value(dev, F_STONITH_DEVICE);
+ const char *action = crm_element_value(op, F_STONITH_ACTION);
+ async_command_t *cmd = NULL;
+ stonith_device_t *device = NULL;
+
+ if ((id == NULL) || (action == NULL)) {
+ crm_info("Malformed API action request: device %s, action %s",
+ (id? id : "not specified"),
+ (action? action : "not specified"));
+ fenced_set_protocol_error(result);
+ return;
+ }
+
+ if (pcmk__str_eq(id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
+ // Watchdog agent actions are implemented internally
+ if (stonith_watchdog_timeout_ms <= 0) {
+ pcmk__set_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "Watchdog fence device not configured");
+ return;
+
+ } else if (pcmk__str_eq(action, "list", pcmk__str_none)) {
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ pcmk__set_result_output(result,
+ list_to_string(stonith_watchdog_targets,
+ "\n", TRUE),
+ NULL);
+ return;
+
+ } else if (pcmk__str_eq(action, "monitor", pcmk__str_none)) {
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return;
+ }
+ }
+
+ device = g_hash_table_lookup(device_list, id);
+ if (device == NULL) {
+ crm_info("Ignoring API '%s' action request because device %s not found",
+ action, id);
+ pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "'%s' not found", id);
+ return;
+
+ } else if (!device->api_registered && !strcmp(action, "monitor")) {
+ // Monitors may run only on "started" (API-registered) devices
+ crm_info("Ignoring API '%s' action request because device %s not active",
+ action, id);
+ pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "'%s' not active", id);
+ return;
+ }
+
+ cmd = create_async_command(msg);
+ if (cmd == NULL) {
+ crm_log_xml_warn(msg, "invalid");
+ fenced_set_protocol_error(result);
+ return;
+ }
+
+ schedule_stonith_command(cmd, device);
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
+}
+
+static void
+search_devices_record_result(struct device_search_s *search, const char *device, gboolean can_fence)
+{
+ search->replies_received++;
+ if (can_fence && device) {
+ if (search->support_action_only != st_device_supports_none) {
+ stonith_device_t *dev = g_hash_table_lookup(device_list, device);
+ if (dev && !pcmk_is_set(dev->flags, search->support_action_only)) {
+ return;
+ }
+ }
+ search->capable = g_list_append(search->capable, strdup(device));
+ }
+
+ if (search->replies_needed == search->replies_received) {
+
+ guint ndevices = g_list_length(search->capable);
+
+ crm_debug("Search found %d device%s that can perform '%s' targeting %s",
+ ndevices, pcmk__plural_s(ndevices),
+ (search->action? search->action : "unknown action"),
+ (search->host? search->host : "any node"));
+
+ search->callback(search->capable, search->user_data);
+ free(search->host);
+ free(search->action);
+ free(search);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Check whether the local host is allowed to execute a fencing action
+ *
+ * \param[in] device Fence device to check
+ * \param[in] action Fence action to check
+ * \param[in] target Hostname of fence target
+ * \param[in] allow_suicide Whether self-fencing is allowed for this operation
+ *
+ * \return TRUE if local host is allowed to execute action, FALSE otherwise
+ */
+static gboolean
+localhost_is_eligible(const stonith_device_t *device, const char *action,
+ const char *target, gboolean allow_suicide)
+{
+ gboolean localhost_is_target = pcmk__str_eq(target, stonith_our_uname,
+ pcmk__str_casei);
+
+ if ((device != NULL) && (action != NULL)
+ && (device->on_target_actions != NULL)
+ && (strstr((const char*) device->on_target_actions->str,
+ action) != NULL)) {
+
+ if (!localhost_is_target) {
+ crm_trace("Operation '%s' using %s can only be executed for local "
+ "host, not %s", action, device->id, target);
+ return FALSE;
+ }
+
+ } else if (localhost_is_target && !allow_suicide) {
+ crm_trace("'%s' operation does not support self-fencing", action);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Check if local node is allowed to execute (possibly remapped) action
+ *
+ * \param[in] device Fence device to check
+ * \param[in] action Fence action to check
+ * \param[in] target Node name of fence target
+ * \param[in] allow_self Whether self-fencing is allowed for this operation
+ *
+ * \return true if local node is allowed to execute \p action or any actions it
+ * might be remapped to, otherwise false
+ */
+static bool
+localhost_is_eligible_with_remap(const stonith_device_t *device,
+ const char *action, const char *target,
+ gboolean allow_self)
+{
+ // Check exact action
+ if (localhost_is_eligible(device, action, target, allow_self)) {
+ return true;
+ }
+
+ // Check potential remaps
+
+ if (pcmk__str_eq(action, "reboot", pcmk__str_none)) {
+ /* "reboot" might get remapped to "off" then "on", so even if reboot is
+ * disallowed, return true if either of those is allowed. We'll report
+ * the disallowed actions with the results. We never allow self-fencing
+ * for remapped "on" actions because the target is off at that point.
+ */
+ if (localhost_is_eligible(device, "off", target, allow_self)
+ || localhost_is_eligible(device, "on", target, FALSE)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void
+can_fence_host_with_device(stonith_device_t *dev,
+ struct device_search_s *search)
+{
+ gboolean can = FALSE;
+ const char *check_type = "Internal bug";
+ const char *target = NULL;
+ const char *alias = NULL;
+ const char *dev_id = "Unspecified device";
+ const char *action = (search == NULL)? NULL : search->action;
+
+ CRM_CHECK((dev != NULL) && (action != NULL), goto search_report_results);
+
+ if (dev->id != NULL) {
+ dev_id = dev->id;
+ }
+
+ target = search->host;
+ if (target == NULL) {
+ can = TRUE;
+ check_type = "No target";
+ goto search_report_results;
+ }
+
+ /* Answer immediately if the device does not support the action
+ * or the local node is not allowed to perform it
+ */
+ if (pcmk__str_eq(action, "on", pcmk__str_none)
+ && !pcmk_is_set(dev->flags, st_device_supports_on)) {
+ check_type = "Agent does not support 'on'";
+ goto search_report_results;
+
+ } else if (!localhost_is_eligible_with_remap(dev, action, target,
+ search->allow_suicide)) {
+ check_type = "This node is not allowed to execute action";
+ goto search_report_results;
+ }
+
+ // Check eligibility as specified by pcmk_host_check
+ check_type = target_list_type(dev);
+ alias = g_hash_table_lookup(dev->aliases, target);
+ if (pcmk__str_eq(check_type, PCMK__VALUE_NONE, pcmk__str_casei)) {
+ can = TRUE;
+
+ } else if (pcmk__str_eq(check_type, "static-list", pcmk__str_casei)) {
+ if (pcmk__str_in_list(target, dev->targets, pcmk__str_casei)) {
+ can = TRUE;
+ } else if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_MAP)
+ && g_hash_table_lookup(dev->aliases, target)) {
+ can = TRUE;
+ }
+
+ } else if (pcmk__str_eq(check_type, "dynamic-list", pcmk__str_casei)) {
+ time_t now = time(NULL);
+
+ if (dev->targets == NULL || dev->targets_age + 60 < now) {
+ int device_timeout = get_action_timeout(dev, "list", search->per_device_timeout);
+
+ if (device_timeout > search->per_device_timeout) {
+ crm_notice("Since the pcmk_list_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
+ device_timeout, dev_id, search->per_device_timeout);
+ }
+
+ crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
+ check_type, dev_id, target, action);
+
+ schedule_internal_command(__func__, dev, "list", NULL,
+ search->per_device_timeout, search, dynamic_list_search_cb);
+
+ /* we'll respond to this search request async in the cb */
+ return;
+ }
+
+ if (pcmk__str_in_list(((alias == NULL)? target : alias), dev->targets,
+ pcmk__str_casei)) {
+ can = TRUE;
+ }
+
+ } else if (pcmk__str_eq(check_type, "status", pcmk__str_casei)) {
+ int device_timeout = get_action_timeout(dev, check_type, search->per_device_timeout);
+
+ if (device_timeout > search->per_device_timeout) {
+ crm_notice("Since the pcmk_status_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
+ device_timeout, dev_id, search->per_device_timeout);
+ }
+
+ crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
+ check_type, dev_id, target, action);
+ schedule_internal_command(__func__, dev, "status", target,
+ search->per_device_timeout, search, status_search_cb);
+ /* we'll respond to this search request async in the cb */
+ return;
+ } else {
+ crm_err("Invalid value for " PCMK_STONITH_HOST_CHECK ": %s", check_type);
+ check_type = "Invalid " PCMK_STONITH_HOST_CHECK;
+ }
+
+ search_report_results:
+ crm_info("%s is%s eligible to fence (%s) %s%s%s%s: %s",
+ dev_id, (can? "" : " not"), pcmk__s(action, "unspecified action"),
+ pcmk__s(target, "unspecified target"),
+ (alias == NULL)? "" : " (as '", pcmk__s(alias, ""),
+ (alias == NULL)? "" : "')", check_type);
+ search_devices_record_result(search, ((dev == NULL)? NULL : dev_id), can);
+}
+
+static void
+search_devices(gpointer key, gpointer value, gpointer user_data)
+{
+ stonith_device_t *dev = value;
+ struct device_search_s *search = user_data;
+
+ can_fence_host_with_device(dev, search);
+}
+
+#define DEFAULT_QUERY_TIMEOUT 20
+static void
+get_capable_devices(const char *host, const char *action, int timeout, bool suicide, void *user_data,
+ void (*callback) (GList * devices, void *user_data), uint32_t support_action_only)
+{
+ struct device_search_s *search;
+ guint ndevices = g_hash_table_size(device_list);
+
+ if (ndevices == 0) {
+ callback(NULL, user_data);
+ return;
+ }
+
+ search = calloc(1, sizeof(struct device_search_s));
+ if (!search) {
+ crm_crit("Cannot search for capable fence devices: %s",
+ strerror(ENOMEM));
+ callback(NULL, user_data);
+ return;
+ }
+
+ pcmk__str_update(&search->host, host);
+ pcmk__str_update(&search->action, action);
+ search->per_device_timeout = timeout;
+ search->allow_suicide = suicide;
+ search->callback = callback;
+ search->user_data = user_data;
+ search->support_action_only = support_action_only;
+
+ /* We are guaranteed this many replies, even if a device is
+ * unregistered while the search is in progress.
+ */
+ search->replies_needed = ndevices;
+
+ crm_debug("Searching %d device%s to see which can execute '%s' targeting %s",
+ ndevices, pcmk__plural_s(ndevices),
+ (search->action? search->action : "unknown action"),
+ (search->host? search->host : "any node"));
+ g_hash_table_foreach(device_list, search_devices, search);
+}
+
+struct st_query_data {
+ xmlNode *reply;
+ char *remote_peer;
+ char *client_id;
+ char *target;
+ char *action;
+ int call_options;
+};
+
+/*!
+ * \internal
+ * \brief Add action-specific attributes to query reply XML
+ *
+ * \param[in,out] xml XML to add attributes to
+ * \param[in] action Fence action
+ * \param[in] device Fence device
+ * \param[in] target Fence target
+ */
+static void
+add_action_specific_attributes(xmlNode *xml, const char *action,
+ const stonith_device_t *device,
+ const char *target)
+{
+ int action_specific_timeout;
+ int delay_max;
+ int delay_base;
+
+ CRM_CHECK(xml && action && device, return);
+
+ if (is_action_required(action, device)) {
+ crm_trace("Action '%s' is required using %s", action, device->id);
+ crm_xml_add_int(xml, F_STONITH_DEVICE_REQUIRED, 1);
+ }
+
+ action_specific_timeout = get_action_timeout(device, action, 0);
+ if (action_specific_timeout) {
+ crm_trace("Action '%s' has timeout %dms using %s",
+ action, action_specific_timeout, device->id);
+ crm_xml_add_int(xml, F_STONITH_ACTION_TIMEOUT, action_specific_timeout);
+ }
+
+ delay_max = get_action_delay_max(device, action);
+ if (delay_max > 0) {
+ crm_trace("Action '%s' has maximum random delay %ds using %s",
+ action, delay_max, device->id);
+ crm_xml_add_int(xml, F_STONITH_DELAY_MAX, delay_max);
+ }
+
+ delay_base = get_action_delay_base(device, action, target);
+ if (delay_base > 0) {
+ crm_xml_add_int(xml, F_STONITH_DELAY_BASE, delay_base);
+ }
+
+ if ((delay_max > 0) && (delay_base == 0)) {
+ crm_trace("Action '%s' has maximum random delay %ds using %s",
+ action, delay_max, device->id);
+ } else if ((delay_max == 0) && (delay_base > 0)) {
+ crm_trace("Action '%s' has a static delay of %ds using %s",
+ action, delay_base, device->id);
+ } else if ((delay_max > 0) && (delay_base > 0)) {
+ crm_trace("Action '%s' has a minimum delay of %ds and a randomly chosen "
+ "maximum delay of %ds using %s",
+ action, delay_base, delay_max, device->id);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Add "disallowed" attribute to query reply XML if appropriate
+ *
+ * \param[in,out] xml XML to add attribute to
+ * \param[in] action Fence action
+ * \param[in] device Fence device
+ * \param[in] target Fence target
+ * \param[in] allow_suicide Whether self-fencing is allowed
+ */
+static void
+add_disallowed(xmlNode *xml, const char *action, const stonith_device_t *device,
+ const char *target, gboolean allow_suicide)
+{
+ if (!localhost_is_eligible(device, action, target, allow_suicide)) {
+ crm_trace("Action '%s' using %s is disallowed for local host",
+ action, device->id);
+ pcmk__xe_set_bool_attr(xml, F_STONITH_ACTION_DISALLOWED, true);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Add child element with action-specific values to query reply XML
+ *
+ * \param[in,out] xml XML to add attribute to
+ * \param[in] action Fence action
+ * \param[in] device Fence device
+ * \param[in] target Fence target
+ * \param[in] allow_suicide Whether self-fencing is allowed
+ */
+static void
+add_action_reply(xmlNode *xml, const char *action,
+ const stonith_device_t *device, const char *target,
+ gboolean allow_suicide)
+{
+ xmlNode *child = create_xml_node(xml, F_STONITH_ACTION);
+
+ crm_xml_add(child, XML_ATTR_ID, action);
+ add_action_specific_attributes(child, action, device, target);
+ add_disallowed(child, action, device, target, allow_suicide);
+}
+
+static void
+stonith_query_capable_device_cb(GList * devices, void *user_data)
+{
+ struct st_query_data *query = user_data;
+ int available_devices = 0;
+ xmlNode *dev = NULL;
+ xmlNode *list = NULL;
+ GList *lpc = NULL;
+ pcmk__client_t *client = NULL;
+
+ if (query->client_id != NULL) {
+ client = pcmk__find_client_by_id(query->client_id);
+ if ((client == NULL) && (query->remote_peer == NULL)) {
+ crm_trace("Skipping reply to %s: no longer a client",
+ query->client_id);
+ goto done;
+ }
+ }
+
+ /* Pack the results into XML */
+ list = create_xml_node(NULL, __func__);
+ crm_xml_add(list, F_STONITH_TARGET, query->target);
+ for (lpc = devices; lpc != NULL; lpc = lpc->next) {
+ stonith_device_t *device = g_hash_table_lookup(device_list, lpc->data);
+ const char *action = query->action;
+
+ if (!device) {
+ /* It is possible the device got unregistered while
+ * determining who can fence the target */
+ continue;
+ }
+
+ available_devices++;
+
+ dev = create_xml_node(list, F_STONITH_DEVICE);
+ crm_xml_add(dev, XML_ATTR_ID, device->id);
+ crm_xml_add(dev, "namespace", device->namespace);
+ crm_xml_add(dev, "agent", device->agent);
+ crm_xml_add_int(dev, F_STONITH_DEVICE_VERIFIED, device->verified);
+ crm_xml_add_int(dev, F_STONITH_DEVICE_SUPPORT_FLAGS, device->flags);
+
+ /* If the originating fencer wants to reboot the node, and we have a
+ * capable device that doesn't support "reboot", remap to "off" instead.
+ */
+ if (!pcmk_is_set(device->flags, st_device_supports_reboot)
+ && pcmk__str_eq(query->action, "reboot", pcmk__str_none)) {
+ crm_trace("%s doesn't support reboot, using values for off instead",
+ device->id);
+ action = "off";
+ }
+
+ /* Add action-specific values if available */
+ add_action_specific_attributes(dev, action, device, query->target);
+ if (pcmk__str_eq(query->action, "reboot", pcmk__str_none)) {
+ /* A "reboot" *might* get remapped to "off" then "on", so after
+ * sending the "reboot"-specific values in the main element, we add
+ * sub-elements for "off" and "on" values.
+ *
+ * We short-circuited earlier if "reboot", "off" and "on" are all
+ * disallowed for the local host. However if only one or two are
+ * disallowed, we send back the results and mark which ones are
+ * disallowed. If "reboot" is disallowed, this might cause problems
+ * with older fencer versions, which won't check for it. Older
+ * versions will ignore "off" and "on", so they are not a problem.
+ */
+ add_disallowed(dev, action, device, query->target,
+ pcmk_is_set(query->call_options, st_opt_allow_suicide));
+ add_action_reply(dev, "off", device, query->target,
+ pcmk_is_set(query->call_options, st_opt_allow_suicide));
+ add_action_reply(dev, "on", device, query->target, FALSE);
+ }
+
+ /* A query without a target wants device parameters */
+ if (query->target == NULL) {
+ xmlNode *attrs = create_xml_node(dev, XML_TAG_ATTRS);
+
+ g_hash_table_foreach(device->params, hash2field, attrs);
+ }
+ }
+
+ crm_xml_add_int(list, F_STONITH_AVAILABLE_DEVICES, available_devices);
+ if (query->target) {
+ crm_debug("Found %d matching device%s for target '%s'",
+ available_devices, pcmk__plural_s(available_devices),
+ query->target);
+ } else {
+ crm_debug("%d device%s installed",
+ available_devices, pcmk__plural_s(available_devices));
+ }
+
+ if (list != NULL) {
+ crm_log_xml_trace(list, "Add query results");
+ add_message_xml(query->reply, F_STONITH_CALLDATA, list);
+ }
+
+ stonith_send_reply(query->reply, query->call_options, query->remote_peer,
+ client);
+
+done:
+ free_xml(query->reply);
+ free(query->remote_peer);
+ free(query->client_id);
+ free(query->target);
+ free(query->action);
+ free(query);
+ free_xml(list);
+ g_list_free_full(devices, free);
+}
+
+/*!
+ * \internal
+ * \brief Log the result of an asynchronous command
+ *
+ * \param[in] cmd Command the result is for
+ * \param[in] result Result of command
+ * \param[in] pid Process ID of command, if available
+ * \param[in] next Alternate device that will be tried if command failed
+ * \param[in] op_merged Whether this command was merged with an earlier one
+ */
+static void
+log_async_result(const async_command_t *cmd,
+ const pcmk__action_result_t *result,
+ int pid, const char *next, bool op_merged)
+{
+ int log_level = LOG_ERR;
+ int output_log_level = LOG_NEVER;
+ guint devices_remaining = g_list_length(cmd->next_device_iter);
+
+ GString *msg = g_string_sized_new(80); // Reasonable starting size
+
+ // Choose log levels appropriately if we have a result
+ if (pcmk__result_ok(result)) {
+ log_level = (cmd->target == NULL)? LOG_DEBUG : LOG_NOTICE;
+ if ((result->action_stdout != NULL)
+ && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_none)) {
+ output_log_level = LOG_DEBUG;
+ }
+ next = NULL;
+ } else {
+ log_level = (cmd->target == NULL)? LOG_NOTICE : LOG_ERR;
+ if ((result->action_stdout != NULL)
+ && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_none)) {
+ output_log_level = LOG_WARNING;
+ }
+ }
+
+ // Build the log message piece by piece
+ pcmk__g_strcat(msg, "Operation '", cmd->action, "' ", NULL);
+ if (pid != 0) {
+ g_string_append_printf(msg, "[%d] ", pid);
+ }
+ if (cmd->target != NULL) {
+ pcmk__g_strcat(msg, "targeting ", cmd->target, " ", NULL);
+ }
+ if (cmd->device != NULL) {
+ pcmk__g_strcat(msg, "using ", cmd->device, " ", NULL);
+ }
+
+ // Add exit status or execution status as appropriate
+ if (result->execution_status == PCMK_EXEC_DONE) {
+ g_string_append_printf(msg, "returned %d", result->exit_status);
+ } else {
+ pcmk__g_strcat(msg, "could not be executed: ",
+ pcmk_exec_status_str(result->execution_status), NULL);
+ }
+
+ // Add exit reason and next device if appropriate
+ if (result->exit_reason != NULL) {
+ pcmk__g_strcat(msg, " (", result->exit_reason, ")", NULL);
+ }
+ if (next != NULL) {
+ pcmk__g_strcat(msg, ", retrying with ", next, NULL);
+ }
+ if (devices_remaining > 0) {
+ g_string_append_printf(msg, " (%u device%s remaining)",
+ (unsigned int) devices_remaining,
+ pcmk__plural_s(devices_remaining));
+ }
+ g_string_append_printf(msg, " " CRM_XS " %scall %d from %s",
+ (op_merged? "merged " : ""), cmd->id,
+ cmd->client_name);
+
+ // Log the result
+ do_crm_log(log_level, "%s", msg->str);
+ g_string_free(msg, TRUE);
+
+ // Log the output (which may have multiple lines), if appropriate
+ if (output_log_level != LOG_NEVER) {
+ char *prefix = crm_strdup_printf("%s[%d]", cmd->device, pid);
+
+ crm_log_output(output_log_level, prefix, result->action_stdout);
+ free(prefix);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Reply to requester after asynchronous command completion
+ *
+ * \param[in] cmd Command that completed
+ * \param[in] result Result of command
+ * \param[in] pid Process ID of command, if available
+ * \param[in] merged If true, command was merged with another, not executed
+ */
+static void
+send_async_reply(const async_command_t *cmd, const pcmk__action_result_t *result,
+ int pid, bool merged)
+{
+ xmlNode *reply = NULL;
+ pcmk__client_t *client = NULL;
+
+ CRM_CHECK((cmd != NULL) && (result != NULL), return);
+
+ log_async_result(cmd, result, pid, NULL, merged);
+
+ if (cmd->client != NULL) {
+ client = pcmk__find_client_by_id(cmd->client);
+ if ((client == NULL) && (cmd->origin == NULL)) {
+ crm_trace("Skipping reply to %s: no longer a client", cmd->client);
+ return;
+ }
+ }
+
+ reply = construct_async_reply(cmd, result);
+ if (merged) {
+ pcmk__xe_set_bool_attr(reply, F_STONITH_MERGED, true);
+ }
+
+ if (!stand_alone && pcmk__is_fencing_action(cmd->action)
+ && pcmk__str_eq(cmd->origin, cmd->target, pcmk__str_casei)) {
+ /* The target was also the originator, so broadcast the result on its
+ * behalf (since it will be unable to).
+ */
+ crm_trace("Broadcast '%s' result for %s (target was also originator)",
+ cmd->action, cmd->target);
+ crm_xml_add(reply, F_SUBTYPE, "broadcast");
+ crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY);
+ send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE);
+ } else {
+ // Reply only to the originator
+ stonith_send_reply(reply, cmd->options, cmd->origin, client);
+ }
+
+ crm_log_xml_trace(reply, "Reply");
+ free_xml(reply);
+
+ if (stand_alone) {
+ /* Do notification with a clean data object */
+ xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE);
+
+ stonith__xe_set_result(notify_data, result);
+ crm_xml_add(notify_data, F_STONITH_TARGET, cmd->target);
+ crm_xml_add(notify_data, F_STONITH_OPERATION, cmd->op);
+ crm_xml_add(notify_data, F_STONITH_DELEGATE, "localhost");
+ crm_xml_add(notify_data, F_STONITH_DEVICE, cmd->device);
+ crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
+ crm_xml_add(notify_data, F_STONITH_ORIGIN, cmd->client);
+
+ fenced_send_notification(T_STONITH_NOTIFY_FENCE, result, notify_data);
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
+ }
+}
+
+static void
+cancel_stonith_command(async_command_t * cmd)
+{
+ stonith_device_t *device = cmd_device(cmd);
+
+ if (device) {
+ crm_trace("Cancel scheduled '%s' action using %s",
+ cmd->action, device->id);
+ device->pending_ops = g_list_remove(device->pending_ops, cmd);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Cancel and reply to any duplicates of a just-completed operation
+ *
+ * Check whether any fencing operations are scheduled to do the same thing as
+ * one that just succeeded. If so, rather than performing the same operation
+ * twice, return the result of this operation for all matching pending commands.
+ *
+ * \param[in,out] cmd Fencing operation that just succeeded
+ * \param[in] result Result of \p cmd
+ * \param[in] pid If nonzero, process ID of agent invocation (for logs)
+ *
+ * \note Duplicate merging will do the right thing for either type of remapped
+ * reboot. If the executing fencer remapped an unsupported reboot to off,
+ * then cmd->action will be "reboot" and will be merged with any other
+ * reboot requests. If the originating fencer remapped a topology reboot
+ * to off then on, we will get here once with cmd->action "off" and once
+ * with "on", and they will be merged separately with similar requests.
+ */
+static void
+reply_to_duplicates(async_command_t *cmd, const pcmk__action_result_t *result,
+ int pid)
+{
+ GList *next = NULL;
+
+ for (GList *iter = cmd_list; iter != NULL; iter = next) {
+ async_command_t *cmd_other = iter->data;
+
+ next = iter->next; // We might delete this entry, so grab next now
+
+ if (cmd == cmd_other) {
+ continue;
+ }
+
+ /* A pending operation matches if:
+ * 1. The client connections are different.
+ * 2. The target is the same.
+ * 3. The fencing action is the same.
+ * 4. The device scheduled to execute the action is the same.
+ */
+ if (pcmk__str_eq(cmd->client, cmd_other->client, pcmk__str_casei) ||
+ !pcmk__str_eq(cmd->target, cmd_other->target, pcmk__str_casei) ||
+ !pcmk__str_eq(cmd->action, cmd_other->action, pcmk__str_none) ||
+ !pcmk__str_eq(cmd->device, cmd_other->device, pcmk__str_casei)) {
+
+ continue;
+ }
+
+ crm_notice("Merging fencing action '%s'%s%s originating from "
+ "client %s with identical fencing request from client %s",
+ cmd_other->action,
+ (cmd_other->target == NULL)? "" : " targeting ",
+ pcmk__s(cmd_other->target, ""), cmd_other->client_name,
+ cmd->client_name);
+
+ // Stop tracking the duplicate, send its result, and cancel it
+ cmd_list = g_list_remove_link(cmd_list, iter);
+ send_async_reply(cmd_other, result, pid, true);
+ cancel_stonith_command(cmd_other);
+
+ free_async_command(cmd_other);
+ g_list_free_1(iter);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Return the next required device (if any) for an operation
+ *
+ * \param[in,out] cmd Fencing operation that just succeeded
+ *
+ * \return Next device required for action if any, otherwise NULL
+ */
+static stonith_device_t *
+next_required_device(async_command_t *cmd)
+{
+ for (GList *iter = cmd->next_device_iter; iter != NULL; iter = iter->next) {
+ stonith_device_t *next_device = g_hash_table_lookup(device_list,
+ iter->data);
+
+ if (is_action_required(cmd->action, next_device)) {
+ /* This is only called for successful actions, so it's OK to skip
+ * non-required devices.
+ */
+ cmd->next_device_iter = iter->next;
+ return next_device;
+ }
+ }
+ return NULL;
+}
+
+static void
+st_child_done(int pid, const pcmk__action_result_t *result, void *user_data)
+{
+ async_command_t *cmd = user_data;
+
+ stonith_device_t *device = NULL;
+ stonith_device_t *next_device = NULL;
+
+ CRM_CHECK(cmd != NULL, return);
+
+ device = cmd_device(cmd);
+ cmd->active_on = NULL;
+
+ /* The device is ready to do something else now */
+ if (device) {
+ if (!device->verified && pcmk__result_ok(result) &&
+ (pcmk__strcase_any_of(cmd->action, "list", "monitor", "status", NULL))) {
+
+ device->verified = TRUE;
+ }
+
+ mainloop_set_trigger(device->work);
+ }
+
+ if (pcmk__result_ok(result)) {
+ next_device = next_required_device(cmd);
+
+ } else if ((cmd->next_device_iter != NULL)
+ && !is_action_required(cmd->action, device)) {
+ /* if this device didn't work out, see if there are any others we can try.
+ * if the failed device was 'required', we can't pick another device. */
+ next_device = g_hash_table_lookup(device_list,
+ cmd->next_device_iter->data);
+ cmd->next_device_iter = cmd->next_device_iter->next;
+ }
+
+ if (next_device == NULL) {
+ send_async_reply(cmd, result, pid, false);
+ if (pcmk__result_ok(result)) {
+ reply_to_duplicates(cmd, result, pid);
+ }
+ free_async_command(cmd);
+
+ } else { // This operation requires more fencing
+ log_async_result(cmd, result, pid, next_device->id, false);
+ schedule_stonith_command(cmd, next_device);
+ }
+}
+
+static gint
+sort_device_priority(gconstpointer a, gconstpointer b)
+{
+ const stonith_device_t *dev_a = a;
+ const stonith_device_t *dev_b = b;
+
+ if (dev_a->priority > dev_b->priority) {
+ return -1;
+ } else if (dev_a->priority < dev_b->priority) {
+ return 1;
+ }
+ return 0;
+}
+
+static void
+stonith_fence_get_devices_cb(GList * devices, void *user_data)
+{
+ async_command_t *cmd = user_data;
+ stonith_device_t *device = NULL;
+ guint ndevices = g_list_length(devices);
+
+ crm_info("Found %d matching device%s for target '%s'",
+ ndevices, pcmk__plural_s(ndevices), cmd->target);
+
+ if (devices != NULL) {
+ /* Order based on priority */
+ devices = g_list_sort(devices, sort_device_priority);
+ device = g_hash_table_lookup(device_list, devices->data);
+ }
+
+ if (device == NULL) { // No device found
+ pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+
+ pcmk__format_result(&result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "No device configured for target '%s'",
+ cmd->target);
+ send_async_reply(cmd, &result, 0, false);
+ pcmk__reset_result(&result);
+ free_async_command(cmd);
+ g_list_free_full(devices, free);
+
+ } else { // Device found, schedule it for fencing
+ cmd->device_list = devices;
+ cmd->next_device_iter = devices->next;
+ schedule_stonith_command(cmd, device);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Execute a fence action via the local node
+ *
+ * \param[in] msg Fencing request
+ * \param[out] result Where to store result of fence action
+ */
+static void
+fence_locally(xmlNode *msg, pcmk__action_result_t *result)
+{
+ const char *device_id = NULL;
+ stonith_device_t *device = NULL;
+ async_command_t *cmd = NULL;
+ xmlNode *dev = NULL;
+
+ CRM_CHECK((msg != NULL) && (result != NULL), return);
+
+ dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
+
+ cmd = create_async_command(msg);
+ if (cmd == NULL) {
+ crm_log_xml_warn(msg, "invalid");
+ fenced_set_protocol_error(result);
+ return;
+ }
+
+ device_id = crm_element_value(dev, F_STONITH_DEVICE);
+ if (device_id != NULL) {
+ device = g_hash_table_lookup(device_list, device_id);
+ if (device == NULL) {
+ crm_err("Requested device '%s' is not available", device_id);
+ pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
+ "Requested device '%s' not found", device_id);
+ return;
+ }
+ schedule_stonith_command(cmd, device);
+
+ } else {
+ const char *host = crm_element_value(dev, F_STONITH_TARGET);
+
+ if (pcmk_is_set(cmd->options, st_opt_cs_nodeid)) {
+ int nodeid = 0;
+ crm_node_t *node = NULL;
+
+ pcmk__scan_min_int(host, &nodeid, 0);
+ node = pcmk__search_known_node_cache(nodeid, NULL, CRM_GET_PEER_ANY);
+ if (node != NULL) {
+ host = node->uname;
+ }
+ }
+
+ /* If we get to here, then self-fencing is implicitly allowed */
+ get_capable_devices(host, cmd->action, cmd->default_timeout,
+ TRUE, cmd, stonith_fence_get_devices_cb,
+ fenced_support_flag(cmd->action));
+ }
+
+ pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Build an XML reply for a fencing operation
+ *
+ * \param[in] request Request that reply is for
+ * \param[in] data If not NULL, add to reply as call data
+ * \param[in] result Full result of fencing operation
+ *
+ * \return Newly created XML reply
+ * \note The caller is responsible for freeing the result.
+ * \note This has some overlap with construct_async_reply(), but that copies
+ * values from an async_command_t, whereas this one copies them from the
+ * request.
+ */
+xmlNode *
+fenced_construct_reply(const xmlNode *request, xmlNode *data,
+ const pcmk__action_result_t *result)
+{
+ xmlNode *reply = NULL;
+
+ reply = create_xml_node(NULL, T_STONITH_REPLY);
+
+ crm_xml_add(reply, "st_origin", __func__);
+ crm_xml_add(reply, F_TYPE, T_STONITH_NG);
+ stonith__xe_set_result(reply, result);
+
+ if (request == NULL) {
+ /* Most likely, this is the result of a stonith operation that was
+ * initiated before we came up. Unfortunately that means we lack enough
+ * information to provide clients with a full result.
+ *
+ * @TODO Maybe synchronize this information at start-up?
+ */
+ crm_warn("Missing request information for client notifications for "
+ "operation with result '%s' (initiated before we came up?)",
+ pcmk_exec_status_str(result->execution_status));
+
+ } else {
+ const char *name = NULL;
+ const char *value = NULL;
+
+ // Attributes to copy from request to reply
+ const char *names[] = {
+ F_STONITH_OPERATION,
+ F_STONITH_CALLID,
+ F_STONITH_CLIENTID,
+ F_STONITH_CLIENTNAME,
+ F_STONITH_REMOTE_OP_ID,
+ F_STONITH_CALLOPTS
+ };
+
+ for (int lpc = 0; lpc < PCMK__NELEM(names); lpc++) {
+ name = names[lpc];
+ value = crm_element_value(request, name);
+ crm_xml_add(reply, name, value);
+ }
+ if (data != NULL) {
+ add_message_xml(reply, F_STONITH_CALLDATA, data);
+ }
+ }
+ return reply;
+}
+
+/*!
+ * \internal
+ * \brief Build an XML reply to an asynchronous fencing command
+ *
+ * \param[in] cmd Fencing command that reply is for
+ * \param[in] result Command result
+ */
+static xmlNode *
+construct_async_reply(const async_command_t *cmd,
+ const pcmk__action_result_t *result)
+{
+ xmlNode *reply = create_xml_node(NULL, T_STONITH_REPLY);
+
+ crm_xml_add(reply, "st_origin", __func__);
+ crm_xml_add(reply, F_TYPE, T_STONITH_NG);
+ crm_xml_add(reply, F_STONITH_OPERATION, cmd->op);
+ crm_xml_add(reply, F_STONITH_DEVICE, cmd->device);
+ crm_xml_add(reply, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
+ crm_xml_add(reply, F_STONITH_CLIENTID, cmd->client);
+ crm_xml_add(reply, F_STONITH_CLIENTNAME, cmd->client_name);
+ crm_xml_add(reply, F_STONITH_TARGET, cmd->target);
+ crm_xml_add(reply, F_STONITH_ACTION, cmd->op);
+ crm_xml_add(reply, F_STONITH_ORIGIN, cmd->origin);
+ crm_xml_add_int(reply, F_STONITH_CALLID, cmd->id);
+ crm_xml_add_int(reply, F_STONITH_CALLOPTS, cmd->options);
+
+ stonith__xe_set_result(reply, result);
+ return reply;
+}
+
+bool fencing_peer_active(crm_node_t *peer)
+{
+ if (peer == NULL) {
+ return FALSE;
+ } else if (peer->uname == NULL) {
+ return FALSE;
+ } else if (pcmk_is_set(peer->processes, crm_get_cluster_proc())) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void
+set_fencing_completed(remote_fencing_op_t *op)
+{
+ struct timespec tv;
+
+ qb_util_timespec_from_epoch_get(&tv);
+ op->completed = tv.tv_sec;
+ op->completed_nsec = tv.tv_nsec;
+}
+
+/*!
+ * \internal
+ * \brief Look for alternate node needed if local node shouldn't fence target
+ *
+ * \param[in] target Node that must be fenced
+ *
+ * \return Name of an alternate node that should fence \p target if any,
+ * or NULL otherwise
+ */
+static const char *
+check_alternate_host(const char *target)
+{
+ if (pcmk__str_eq(target, stonith_our_uname, pcmk__str_casei)) {
+ GHashTableIter gIter;
+ crm_node_t *entry = NULL;
+
+ g_hash_table_iter_init(&gIter, crm_peer_cache);
+ while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
+ if (fencing_peer_active(entry)
+ && !pcmk__str_eq(entry->uname, target, pcmk__str_casei)) {
+ crm_notice("Forwarding self-fencing request to %s",
+ entry->uname);
+ return entry->uname;
+ }
+ }
+ crm_warn("Will handle own fencing because no peer can");
+ }
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Send a reply to a CPG peer or IPC client
+ *
+ * \param[in] reply XML reply to send
+ * \param[in] call_options Send synchronously if st_opt_sync_call is set
+ * \param[in] remote_peer If not NULL, name of peer node to send CPG reply
+ * \param[in,out] client If not NULL, client to send IPC reply
+ */
+static void
+stonith_send_reply(xmlNode *reply, int call_options, const char *remote_peer,
+ pcmk__client_t *client)
+{
+ CRM_CHECK((reply != NULL) && ((remote_peer != NULL) || (client != NULL)),
+ return);
+
+ if (remote_peer == NULL) {
+ do_local_reply(reply, client, call_options);
+ } else {
+ send_cluster_message(crm_get_peer(0, remote_peer), crm_msg_stonith_ng,
+ reply, FALSE);
+ }
+}
+
+static void
+remove_relay_op(xmlNode * request)
+{
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_ACTION, request, LOG_TRACE);
+ const char *relay_op_id = NULL;
+ const char *op_id = NULL;
+ const char *client_name = NULL;
+ const char *target = NULL;
+ remote_fencing_op_t *relay_op = NULL;
+
+ if (dev) {
+ target = crm_element_value(dev, F_STONITH_TARGET);
+ }
+
+ relay_op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID_RELAY);
+ op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID);
+ client_name = crm_element_value(request, F_STONITH_CLIENTNAME);
+
+ /* Delete RELAY operation. */
+ if (relay_op_id && target && pcmk__str_eq(target, stonith_our_uname, pcmk__str_casei)) {
+ relay_op = g_hash_table_lookup(stonith_remote_op_list, relay_op_id);
+
+ if (relay_op) {
+ GHashTableIter iter;
+ remote_fencing_op_t *list_op = NULL;
+ g_hash_table_iter_init(&iter, stonith_remote_op_list);
+
+ /* If the operation to be deleted is registered as a duplicate, delete the registration. */
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&list_op)) {
+ GList *dup_iter = NULL;
+ if (list_op != relay_op) {
+ for (dup_iter = list_op->duplicates; dup_iter != NULL; dup_iter = dup_iter->next) {
+ remote_fencing_op_t *other = dup_iter->data;
+ if (other == relay_op) {
+ other->duplicates = g_list_remove(other->duplicates, relay_op);
+ break;
+ }
+ }
+ }
+ }
+ crm_debug("Deleting relay op %s ('%s'%s%s for %s), "
+ "replaced by op %s ('%s'%s%s for %s)",
+ relay_op->id, relay_op->action,
+ (relay_op->target == NULL)? "" : " targeting ",
+ pcmk__s(relay_op->target, ""),
+ relay_op->client_name, op_id, relay_op->action,
+ (target == NULL)? "" : " targeting ", pcmk__s(target, ""),
+ client_name);
+
+ g_hash_table_remove(stonith_remote_op_list, relay_op_id);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Check whether an API request was sent by a privileged user
+ *
+ * API commands related to fencing configuration may be done only by privileged
+ * IPC users (i.e. root or hacluster), because all other users should go through
+ * the CIB to have ACLs applied. If no client was given, this is a peer request,
+ * which is always allowed.
+ *
+ * \param[in] c IPC client that sent request (or NULL if sent by CPG peer)
+ * \param[in] op Requested API operation (for logging only)
+ *
+ * \return true if sender is peer or privileged client, otherwise false
+ */
+static inline bool
+is_privileged(const pcmk__client_t *c, const char *op)
+{
+ if ((c == NULL) || pcmk_is_set(c->flags, pcmk__client_privileged)) {
+ return true;
+ } else {
+ crm_warn("Rejecting IPC request '%s' from unprivileged client %s",
+ pcmk__s(op, ""), pcmk__client_name(c));
+ return false;
+ }
+}
+
+// CRM_OP_REGISTER
+static xmlNode *
+handle_register_request(pcmk__request_t *request)
+{
+ xmlNode *reply = create_xml_node(NULL, "reply");
+
+ CRM_ASSERT(request->ipc_client != NULL);
+ crm_xml_add(reply, F_STONITH_OPERATION, CRM_OP_REGISTER);
+ crm_xml_add(reply, F_STONITH_CLIENTID, request->ipc_client->id);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ pcmk__set_request_flags(request, pcmk__request_reuse_options);
+ return reply;
+}
+
+// STONITH_OP_EXEC
+static xmlNode *
+handle_agent_request(pcmk__request_t *request)
+{
+ execute_agent_action(request->xml, &request->result);
+ if (request->result.execution_status == PCMK_EXEC_PENDING) {
+ return NULL;
+ }
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// STONITH_OP_TIMEOUT_UPDATE
+static xmlNode *
+handle_update_timeout_request(pcmk__request_t *request)
+{
+ const char *call_id = crm_element_value(request->xml, F_STONITH_CALLID);
+ const char *client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
+ int op_timeout = 0;
+
+ crm_element_value_int(request->xml, F_STONITH_TIMEOUT, &op_timeout);
+ do_stonith_async_timeout_update(client_id, call_id, op_timeout);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+}
+
+// STONITH_OP_QUERY
+static xmlNode *
+handle_query_request(pcmk__request_t *request)
+{
+ int timeout = 0;
+ xmlNode *dev = NULL;
+ const char *action = NULL;
+ const char *target = NULL;
+ const char *client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
+ struct st_query_data *query = NULL;
+
+ if (request->peer != NULL) {
+ // Record it for the future notification
+ create_remote_stonith_op(client_id, request->xml, TRUE);
+ }
+
+ /* Delete the DC node RELAY operation. */
+ remove_relay_op(request->xml);
+
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+
+ dev = get_xpath_object("//@" F_STONITH_ACTION, request->xml, LOG_NEVER);
+ if (dev != NULL) {
+ const char *device = crm_element_value(dev, F_STONITH_DEVICE);
+
+ if (pcmk__str_eq(device, "manual_ack", pcmk__str_casei)) {
+ return NULL; // No query or reply necessary
+ }
+ target = crm_element_value(dev, F_STONITH_TARGET);
+ action = crm_element_value(dev, F_STONITH_ACTION);
+ }
+
+ crm_log_xml_trace(request->xml, "Query");
+
+ query = calloc(1, sizeof(struct st_query_data));
+ CRM_ASSERT(query != NULL);
+
+ query->reply = fenced_construct_reply(request->xml, NULL, &request->result);
+ pcmk__str_update(&query->remote_peer, request->peer);
+ pcmk__str_update(&query->client_id, client_id);
+ pcmk__str_update(&query->target, target);
+ pcmk__str_update(&query->action, action);
+ query->call_options = request->call_options;
+
+ crm_element_value_int(request->xml, F_STONITH_TIMEOUT, &timeout);
+ get_capable_devices(target, action, timeout,
+ pcmk_is_set(query->call_options, st_opt_allow_suicide),
+ query, stonith_query_capable_device_cb, st_device_supports_none);
+ return NULL;
+}
+
+// T_STONITH_NOTIFY
+static xmlNode *
+handle_notify_request(pcmk__request_t *request)
+{
+ const char *flag_name = NULL;
+
+ CRM_ASSERT(request->ipc_client != NULL);
+ flag_name = crm_element_value(request->xml, F_STONITH_NOTIFY_ACTIVATE);
+ if (flag_name != NULL) {
+ crm_debug("Enabling %s callbacks for client %s",
+ flag_name, pcmk__request_origin(request));
+ pcmk__set_client_flags(request->ipc_client, get_stonith_flag(flag_name));
+ }
+
+ flag_name = crm_element_value(request->xml, F_STONITH_NOTIFY_DEACTIVATE);
+ if (flag_name != NULL) {
+ crm_debug("Disabling %s callbacks for client %s",
+ flag_name, pcmk__request_origin(request));
+ pcmk__clear_client_flags(request->ipc_client,
+ get_stonith_flag(flag_name));
+ }
+
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ pcmk__set_request_flags(request, pcmk__request_reuse_options);
+
+ return pcmk__ipc_create_ack(request->ipc_flags, "ack", NULL, CRM_EX_OK);
+}
+
+// STONITH_OP_RELAY
+static xmlNode *
+handle_relay_request(pcmk__request_t *request)
+{
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request->xml,
+ LOG_TRACE);
+
+ crm_notice("Received forwarded fencing request from "
+ "%s %s to fence (%s) peer %s",
+ pcmk__request_origin_type(request),
+ pcmk__request_origin(request),
+ crm_element_value(dev, F_STONITH_ACTION),
+ crm_element_value(dev, F_STONITH_TARGET));
+
+ if (initiate_remote_stonith_op(NULL, request->xml, FALSE) == NULL) {
+ fenced_set_protocol_error(&request->result);
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+ }
+
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
+ return NULL;
+}
+
+// STONITH_OP_FENCE
+static xmlNode *
+handle_fence_request(pcmk__request_t *request)
+{
+ if ((request->peer != NULL) || stand_alone) {
+ fence_locally(request->xml, &request->result);
+
+ } else if (pcmk_is_set(request->call_options, st_opt_manual_ack)) {
+ switch (fenced_handle_manual_confirmation(request->ipc_client,
+ request->xml)) {
+ case pcmk_rc_ok:
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE,
+ NULL);
+ break;
+ case EINPROGRESS:
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
+ NULL);
+ break;
+ default:
+ fenced_set_protocol_error(&request->result);
+ break;
+ }
+
+ } else {
+ const char *alternate_host = NULL;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request->xml,
+ LOG_TRACE);
+ const char *target = crm_element_value(dev, F_STONITH_TARGET);
+ const char *action = crm_element_value(dev, F_STONITH_ACTION);
+ const char *device = crm_element_value(dev, F_STONITH_DEVICE);
+
+ if (request->ipc_client != NULL) {
+ int tolerance = 0;
+
+ crm_notice("Client %s wants to fence (%s) %s using %s",
+ pcmk__request_origin(request), action,
+ target, (device? device : "any device"));
+ crm_element_value_int(dev, F_STONITH_TOLERANCE, &tolerance);
+ if (stonith_check_fence_tolerance(tolerance, target, action)) {
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE,
+ NULL);
+ return fenced_construct_reply(request->xml, NULL,
+ &request->result);
+ }
+ alternate_host = check_alternate_host(target);
+
+ } else {
+ crm_notice("Peer %s wants to fence (%s) '%s' with device '%s'",
+ request->peer, action, target,
+ (device == NULL)? "(any)" : device);
+ }
+
+ if (alternate_host != NULL) {
+ const char *client_id = NULL;
+ remote_fencing_op_t *op = NULL;
+
+ if (request->ipc_client->id == 0) {
+ client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
+ } else {
+ client_id = request->ipc_client->id;
+ }
+
+ /* Create a duplicate fencing operation to relay with the client ID.
+ * When a query response is received, this operation should be
+ * deleted to avoid keeping the duplicate around.
+ */
+ op = create_remote_stonith_op(client_id, request->xml, FALSE);
+
+ crm_xml_add(request->xml, F_STONITH_OPERATION, STONITH_OP_RELAY);
+ crm_xml_add(request->xml, F_STONITH_CLIENTID,
+ request->ipc_client->id);
+ crm_xml_add(request->xml, F_STONITH_REMOTE_OP_ID, op->id);
+ send_cluster_message(crm_get_peer(0, alternate_host),
+ crm_msg_stonith_ng, request->xml, FALSE);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
+ NULL);
+
+ } else if (initiate_remote_stonith_op(request->ipc_client, request->xml,
+ FALSE) == NULL) {
+ fenced_set_protocol_error(&request->result);
+
+ } else {
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
+ NULL);
+ }
+ }
+
+ if (request->result.execution_status == PCMK_EXEC_PENDING) {
+ return NULL;
+ }
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// STONITH_OP_FENCE_HISTORY
+static xmlNode *
+handle_history_request(pcmk__request_t *request)
+{
+ xmlNode *reply = NULL;
+ xmlNode *data = NULL;
+
+ stonith_fence_history(request->xml, &data, request->peer,
+ request->call_options);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ if (!pcmk_is_set(request->call_options, st_opt_discard_reply)) {
+ /* When the local node broadcasts its history, it sets
+ * st_opt_discard_reply and doesn't need a reply.
+ */
+ reply = fenced_construct_reply(request->xml, data, &request->result);
+ }
+ free_xml(data);
+ return reply;
+}
+
+// STONITH_OP_DEVICE_ADD
+static xmlNode *
+handle_device_add_request(pcmk__request_t *request)
+{
+ const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
+ xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request->xml,
+ LOG_ERR);
+
+ if (is_privileged(request->ipc_client, op)) {
+ int rc = stonith_device_register(dev, FALSE);
+
+ pcmk__set_result(&request->result,
+ ((rc == pcmk_ok)? CRM_EX_OK : CRM_EX_ERROR),
+ stonith__legacy2status(rc),
+ ((rc == pcmk_ok)? NULL : pcmk_strerror(rc)));
+ } else {
+ pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
+ PCMK_EXEC_INVALID,
+ "Unprivileged users must register device via CIB");
+ }
+ fenced_send_device_notification(op, &request->result,
+ (dev == NULL)? NULL : ID(dev));
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// STONITH_OP_DEVICE_DEL
+static xmlNode *
+handle_device_delete_request(pcmk__request_t *request)
+{
+ xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request->xml,
+ LOG_ERR);
+ const char *device_id = crm_element_value(dev, XML_ATTR_ID);
+ const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
+
+ if (is_privileged(request->ipc_client, op)) {
+ stonith_device_remove(device_id, false);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ } else {
+ pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
+ PCMK_EXEC_INVALID,
+ "Unprivileged users must delete device via CIB");
+ }
+ fenced_send_device_notification(op, &request->result, device_id);
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// STONITH_OP_LEVEL_ADD
+static xmlNode *
+handle_level_add_request(pcmk__request_t *request)
+{
+ char *desc = NULL;
+ const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
+
+ if (is_privileged(request->ipc_client, op)) {
+ fenced_register_level(request->xml, &desc, &request->result);
+ } else {
+ unpack_level_request(request->xml, NULL, NULL, NULL, &desc);
+ pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
+ PCMK_EXEC_INVALID,
+ "Unprivileged users must add level via CIB");
+ }
+ fenced_send_level_notification(op, &request->result, desc);
+ free(desc);
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// STONITH_OP_LEVEL_DEL
+static xmlNode *
+handle_level_delete_request(pcmk__request_t *request)
+{
+ char *desc = NULL;
+ const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
+
+ if (is_privileged(request->ipc_client, op)) {
+ fenced_unregister_level(request->xml, &desc, &request->result);
+ } else {
+ unpack_level_request(request->xml, NULL, NULL, NULL, &desc);
+ pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
+ PCMK_EXEC_INVALID,
+ "Unprivileged users must delete level via CIB");
+ }
+ fenced_send_level_notification(op, &request->result, desc);
+ free(desc);
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+// CRM_OP_RM_NODE_CACHE
+static xmlNode *
+handle_cache_request(pcmk__request_t *request)
+{
+ int node_id = 0;
+ const char *name = NULL;
+
+ crm_element_value_int(request->xml, XML_ATTR_ID, &node_id);
+ name = crm_element_value(request->xml, XML_ATTR_UNAME);
+ reap_crm_member(node_id, name);
+ pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ return NULL;
+}
+
+static xmlNode *
+handle_unknown_request(pcmk__request_t *request)
+{
+ crm_err("Unknown IPC request %s from %s %s",
+ request->op, pcmk__request_origin_type(request),
+ pcmk__request_origin(request));
+ pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
+ "Unknown IPC request type '%s' (bug?)", request->op);
+ return fenced_construct_reply(request->xml, NULL, &request->result);
+}
+
+static void
+fenced_register_handlers(void)
+{
+ pcmk__server_command_t handlers[] = {
+ { CRM_OP_REGISTER, handle_register_request },
+ { STONITH_OP_EXEC, handle_agent_request },
+ { STONITH_OP_TIMEOUT_UPDATE, handle_update_timeout_request },
+ { STONITH_OP_QUERY, handle_query_request },
+ { T_STONITH_NOTIFY, handle_notify_request },
+ { STONITH_OP_RELAY, handle_relay_request },
+ { STONITH_OP_FENCE, handle_fence_request },
+ { STONITH_OP_FENCE_HISTORY, handle_history_request },
+ { STONITH_OP_DEVICE_ADD, handle_device_add_request },
+ { STONITH_OP_DEVICE_DEL, handle_device_delete_request },
+ { STONITH_OP_LEVEL_ADD, handle_level_add_request },
+ { STONITH_OP_LEVEL_DEL, handle_level_delete_request },
+ { CRM_OP_RM_NODE_CACHE, handle_cache_request },
+ { NULL, handle_unknown_request },
+ };
+
+ fenced_handlers = pcmk__register_handlers(handlers);
+}
+
+void
+fenced_unregister_handlers(void)
+{
+ if (fenced_handlers != NULL) {
+ g_hash_table_destroy(fenced_handlers);
+ fenced_handlers = NULL;
+ }
+}
+
+static void
+handle_request(pcmk__request_t *request)
+{
+ xmlNode *reply = NULL;
+ const char *reason = NULL;
+
+ if (fenced_handlers == NULL) {
+ fenced_register_handlers();
+ }
+ reply = pcmk__process_request(request, fenced_handlers);
+ if (reply != NULL) {
+ if (pcmk_is_set(request->flags, pcmk__request_reuse_options)
+ && (request->ipc_client != NULL)) {
+ /* Certain IPC-only commands must reuse the call options from the
+ * original request rather than the ones set by stonith_send_reply()
+ * -> do_local_reply().
+ */
+ pcmk__ipc_send_xml(request->ipc_client, request->ipc_id, reply,
+ request->ipc_flags);
+ request->ipc_client->request_id = 0;
+ } else {
+ stonith_send_reply(reply, request->call_options,
+ request->peer, request->ipc_client);
+ }
+ free_xml(reply);
+ }
+
+ reason = request->result.exit_reason;
+ crm_debug("Processed %s request from %s %s: %s%s%s%s",
+ request->op, pcmk__request_origin_type(request),
+ pcmk__request_origin(request),
+ pcmk_exec_status_str(request->result.execution_status),
+ (reason == NULL)? "" : " (",
+ (reason == NULL)? "" : reason,
+ (reason == NULL)? "" : ")");
+}
+
+static void
+handle_reply(pcmk__client_t *client, xmlNode *request, const char *remote_peer)
+{
+ // Copy, because request might be freed before we want to log this
+ char *op = crm_element_value_copy(request, F_STONITH_OPERATION);
+
+ if (pcmk__str_eq(op, STONITH_OP_QUERY, pcmk__str_none)) {
+ process_remote_stonith_query(request);
+ } else if (pcmk__str_any_of(op, T_STONITH_NOTIFY, STONITH_OP_FENCE, NULL)) {
+ fenced_process_fencing_reply(request);
+ } else {
+ crm_err("Ignoring unknown %s reply from %s %s",
+ pcmk__s(op, "untyped"), ((client == NULL)? "peer" : "client"),
+ ((client == NULL)? remote_peer : pcmk__client_name(client)));
+ crm_log_xml_warn(request, "UnknownOp");
+ free(op);
+ return;
+ }
+ crm_debug("Processed %s reply from %s %s",
+ op, ((client == NULL)? "peer" : "client"),
+ ((client == NULL)? remote_peer : pcmk__client_name(client)));
+ free(op);
+}
+
+/*!
+ * \internal
+ * \brief Handle a message from an IPC client or CPG peer
+ *
+ * \param[in,out] client If not NULL, IPC client that sent message
+ * \param[in] id If from IPC client, IPC message ID
+ * \param[in] flags Message flags
+ * \param[in,out] message Message XML
+ * \param[in] remote_peer If not NULL, CPG peer that sent message
+ */
+void
+stonith_command(pcmk__client_t *client, uint32_t id, uint32_t flags,
+ xmlNode *message, const char *remote_peer)
+{
+ int call_options = st_opt_none;
+ bool is_reply = false;
+
+ CRM_CHECK(message != NULL, return);
+
+ if (get_xpath_object("//" T_STONITH_REPLY, message, LOG_NEVER) != NULL) {
+ is_reply = true;
+ }
+ crm_element_value_int(message, F_STONITH_CALLOPTS, &call_options);
+ crm_debug("Processing %ssynchronous %s %s %u from %s %s",
+ pcmk_is_set(call_options, st_opt_sync_call)? "" : "a",
+ crm_element_value(message, F_STONITH_OPERATION),
+ (is_reply? "reply" : "request"), id,
+ ((client == NULL)? "peer" : "client"),
+ ((client == NULL)? remote_peer : pcmk__client_name(client)));
+
+ if (pcmk_is_set(call_options, st_opt_sync_call)) {
+ CRM_ASSERT(client == NULL || client->request_id == id);
+ }
+
+ if (is_reply) {
+ handle_reply(client, message, remote_peer);
+ } else {
+ pcmk__request_t request = {
+ .ipc_client = client,
+ .ipc_id = id,
+ .ipc_flags = flags,
+ .peer = remote_peer,
+ .xml = message,
+ .call_options = call_options,
+ .result = PCMK__UNKNOWN_RESULT,
+ };
+
+ request.op = crm_element_value_copy(request.xml, F_STONITH_OPERATION);
+ CRM_CHECK(request.op != NULL, return);
+
+ if (pcmk_is_set(request.call_options, st_opt_sync_call)) {
+ pcmk__set_request_flags(&request, pcmk__request_sync);
+ }
+
+ handle_request(&request);
+ pcmk__reset_request(&request);
+ }
+}
diff --git a/daemons/fenced/fenced_history.c b/daemons/fenced/fenced_history.c
new file mode 100644
index 0000000..a766477
--- /dev/null
+++ b/daemons/fenced/fenced_history.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2009-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 <crm_internal.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/cluster/internal.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/fencing/internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+
+#include <pacemaker-fenced.h>
+
+#define MAX_STONITH_HISTORY 500
+
+/*!
+ * \internal
+ * \brief Send a broadcast to all nodes to trigger cleanup or
+ * history synchronisation
+ *
+ * \param[in] history Optional history to be attached
+ * \param[in] callopts We control cleanup via a flag in the callopts
+ * \param[in] target Cleanup can be limited to certain fence-targets
+ */
+static void
+stonith_send_broadcast_history(xmlNode *history,
+ int callopts,
+ const char *target)
+{
+ xmlNode *bcast = create_xml_node(NULL, "stonith_command");
+ xmlNode *data = create_xml_node(NULL, __func__);
+
+ if (target) {
+ crm_xml_add(data, F_STONITH_TARGET, target);
+ }
+ crm_xml_add(bcast, F_TYPE, T_STONITH_NG);
+ crm_xml_add(bcast, F_SUBTYPE, "broadcast");
+ crm_xml_add(bcast, F_STONITH_OPERATION, STONITH_OP_FENCE_HISTORY);
+ crm_xml_add_int(bcast, F_STONITH_CALLOPTS, callopts);
+ if (history) {
+ add_node_copy(data, history);
+ }
+ add_message_xml(bcast, F_STONITH_CALLDATA, data);
+ send_cluster_message(NULL, crm_msg_stonith_ng, bcast, FALSE);
+
+ free_xml(data);
+ free_xml(bcast);
+}
+
+static gboolean
+stonith_remove_history_entry (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ remote_fencing_op_t *op = value;
+ const char *target = (const char *) user_data;
+
+ if ((op->state == st_failed) || (op->state == st_done)) {
+ if ((target) && (strcmp(op->target, target) != 0)) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE; /* don't clean pending operations */
+}
+
+/*!
+ * \internal
+ * \brief Send out a cleanup broadcast or do a local history-cleanup
+ *
+ * \param[in] target Cleanup can be limited to certain fence-targets
+ * \param[in] broadcast Send out a cleanup broadcast
+ */
+static void
+stonith_fence_history_cleanup(const char *target,
+ gboolean broadcast)
+{
+ if (broadcast) {
+ stonith_send_broadcast_history(NULL,
+ st_opt_cleanup | st_opt_discard_reply,
+ target);
+ /* we'll do the local clean when we receive back our own broadcast */
+ } else if (stonith_remote_op_list) {
+ g_hash_table_foreach_remove(stonith_remote_op_list,
+ stonith_remove_history_entry,
+ (gpointer) target);
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
+ }
+}
+
+/* keeping the length of fence-history within bounds
+ * =================================================
+ *
+ * If things are really running wild a lot of fencing-attempts
+ * might fill up the hash-map, eventually using up a lot
+ * of memory and creating huge history-sync messages.
+ * Before the history being synced across nodes at least
+ * the reboot of a cluster-node helped keeping the
+ * history within bounds even though not in a reliable
+ * manner.
+ *
+ * stonith_remote_op_list isn't sorted for time-stamps
+ * thus it would be kind of expensive to delete e.g.
+ * the oldest entry if it would grow past MAX_STONITH_HISTORY
+ * entries.
+ * It is more efficient to purge MAX_STONITH_HISTORY/2
+ * entries whenever the list grows beyond MAX_STONITH_HISTORY.
+ * (sort for age + purge the MAX_STONITH_HISTORY/2 oldest)
+ * That done on a per-node-base might raise the
+ * probability of large syncs to occur.
+ * Things like introducing a broadcast to purge
+ * MAX_STONITH_HISTORY/2 entries or not sync above a certain
+ * threshold coming to mind ...
+ * Simplest thing though is to purge the full history
+ * throughout the cluster once MAX_STONITH_HISTORY is reached.
+ * On the other hand this leads to purging the history in
+ * situations where it would be handy to have it probably.
+ */
+
+
+static int
+op_time_sort(const void *a_voidp, const void *b_voidp)
+{
+ const remote_fencing_op_t **a = (const remote_fencing_op_t **) a_voidp;
+ const remote_fencing_op_t **b = (const remote_fencing_op_t **) b_voidp;
+ gboolean a_pending = ((*a)->state != st_failed) && ((*a)->state != st_done);
+ gboolean b_pending = ((*b)->state != st_failed) && ((*b)->state != st_done);
+
+ if (a_pending && b_pending) {
+ return 0;
+ } else if (a_pending) {
+ return -1;
+ } else if (b_pending) {
+ return 1;
+ } else if ((*b)->completed == (*a)->completed) {
+ if ((*b)->completed_nsec > (*a)->completed_nsec) {
+ return 1;
+ } else if ((*b)->completed_nsec == (*a)->completed_nsec) {
+ return 0;
+ }
+ } else if ((*b)->completed > (*a)->completed) {
+ return 1;
+ }
+
+ return -1;
+}
+
+
+/*!
+ * \internal
+ * \brief Do a local history-trim to MAX_STONITH_HISTORY / 2 entries
+ * once over MAX_STONITH_HISTORY
+ */
+void
+stonith_fence_history_trim(void)
+{
+ guint num_ops;
+
+ if (!stonith_remote_op_list) {
+ return;
+ }
+ num_ops = g_hash_table_size(stonith_remote_op_list);
+ if (num_ops > MAX_STONITH_HISTORY) {
+ remote_fencing_op_t *ops[num_ops];
+ remote_fencing_op_t *op = NULL;
+ GHashTableIter iter;
+ int i;
+
+ crm_trace("Fencing History growing beyond limit of %d so purge "
+ "half of failed/successful attempts", MAX_STONITH_HISTORY);
+
+ /* write all ops into an array */
+ i = 0;
+ g_hash_table_iter_init(&iter, stonith_remote_op_list);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&op)) {
+ ops[i++] = op;
+ }
+ /* run quicksort over the array so that we get pending ops
+ * first and then sorted most recent to oldest
+ */
+ qsort(ops, num_ops, sizeof(remote_fencing_op_t *), op_time_sort);
+ /* purgest oldest half of the history entries */
+ for (i = MAX_STONITH_HISTORY / 2; i < num_ops; i++) {
+ /* keep pending ops even if they shouldn't fill more than
+ * half of our buffer
+ */
+ if ((ops[i]->state == st_failed) || (ops[i]->state == st_done)) {
+ g_hash_table_remove(stonith_remote_op_list, ops[i]->id);
+ }
+ }
+ /* we've just purged valid data from the list so there is no need
+ * to create a notification - if displayed it can stay
+ */
+ }
+}
+
+/*!
+ * \internal
+ * \brief Convert xml fence-history to a hash-table like stonith_remote_op_list
+ *
+ * \param[in] history Fence-history in xml
+ *
+ * \return Fence-history as hash-table
+ */
+static GHashTable *
+stonith_xml_history_to_list(const xmlNode *history)
+{
+ xmlNode *xml_op = NULL;
+ GHashTable *rv = NULL;
+
+ init_stonith_remote_op_hash_table(&rv);
+
+ CRM_LOG_ASSERT(rv != NULL);
+
+ for (xml_op = pcmk__xml_first_child(history); xml_op != NULL;
+ xml_op = pcmk__xml_next(xml_op)) {
+ remote_fencing_op_t *op = NULL;
+ char *id = crm_element_value_copy(xml_op, F_STONITH_REMOTE_OP_ID);
+ int state;
+ int exit_status = CRM_EX_OK;
+ int execution_status = PCMK_EXEC_DONE;
+ long long completed;
+ long long completed_nsec = 0L;
+
+ if (!id) {
+ crm_warn("Malformed fencing history received from peer");
+ continue;
+ }
+
+ crm_trace("Attaching op %s to hashtable", id);
+
+ op = calloc(1, sizeof(remote_fencing_op_t));
+
+ op->id = id;
+ op->target = crm_element_value_copy(xml_op, F_STONITH_TARGET);
+ op->action = crm_element_value_copy(xml_op, F_STONITH_ACTION);
+ op->originator = crm_element_value_copy(xml_op, F_STONITH_ORIGIN);
+ op->delegate = crm_element_value_copy(xml_op, F_STONITH_DELEGATE);
+ op->client_name = crm_element_value_copy(xml_op, F_STONITH_CLIENTNAME);
+ crm_element_value_ll(xml_op, F_STONITH_DATE, &completed);
+ op->completed = (time_t) completed;
+ crm_element_value_ll(xml_op, F_STONITH_DATE_NSEC, &completed_nsec);
+ op->completed_nsec = completed_nsec;
+ crm_element_value_int(xml_op, F_STONITH_STATE, &state);
+ op->state = (enum op_state) state;
+
+ /* @COMPAT We can't use stonith__xe_get_result() here because
+ * fencers <2.1.3 didn't include results, leading it to assume an error
+ * status. Instead, set an unknown status in that case.
+ */
+ if ((crm_element_value_int(xml_op, XML_LRM_ATTR_RC, &exit_status) < 0)
+ || (crm_element_value_int(xml_op, XML_LRM_ATTR_OPSTATUS,
+ &execution_status) < 0)) {
+ exit_status = CRM_EX_INDETERMINATE;
+ execution_status = PCMK_EXEC_UNKNOWN;
+ }
+ pcmk__set_result(&op->result, exit_status, execution_status,
+ crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON));
+ pcmk__set_result_output(&op->result,
+ crm_element_value_copy(xml_op, F_STONITH_OUTPUT),
+ NULL);
+
+
+ g_hash_table_replace(rv, id, op);
+ CRM_LOG_ASSERT(g_hash_table_lookup(rv, id) != NULL);
+ }
+
+ return rv;
+}
+
+/*!
+ * \internal
+ * \brief Craft xml difference between local fence-history and a history
+ * coming from remote, and merge the remote history into the local
+ *
+ * \param[in,out] remote_history Fence-history as hash-table (may be NULL)
+ * \param[in] add_id If crafting the answer for an API
+ * history-request there is no need for the id
+ * \param[in] target Optionally limit to certain fence-target
+ *
+ * \return The fence-history as xml
+ */
+static xmlNode *
+stonith_local_history_diff_and_merge(GHashTable *remote_history,
+ gboolean add_id, const char *target)
+{
+ xmlNode *history = NULL;
+ GHashTableIter iter;
+ remote_fencing_op_t *op = NULL;
+ gboolean updated = FALSE;
+ int cnt = 0;
+
+ if (stonith_remote_op_list) {
+ char *id = NULL;
+
+ history = create_xml_node(NULL, F_STONITH_HISTORY_LIST);
+
+ g_hash_table_iter_init(&iter, stonith_remote_op_list);
+ while (g_hash_table_iter_next(&iter, (void **)&id, (void **)&op)) {
+ xmlNode *entry = NULL;
+
+ if (remote_history) {
+ remote_fencing_op_t *remote_op =
+ g_hash_table_lookup(remote_history, op->id);
+
+ if (remote_op) {
+ if (stonith__op_state_pending(op->state)
+ && !stonith__op_state_pending(remote_op->state)) {
+
+ crm_debug("Updating outdated pending operation %.8s "
+ "(state=%s) according to the one (state=%s) from "
+ "remote peer history",
+ op->id, stonith_op_state_str(op->state),
+ stonith_op_state_str(remote_op->state));
+
+ g_hash_table_steal(remote_history, op->id);
+ op->id = remote_op->id;
+ remote_op->id = id;
+ g_hash_table_iter_replace(&iter, remote_op);
+
+ updated = TRUE;
+ continue; /* skip outdated entries */
+
+ } else if (!stonith__op_state_pending(op->state)
+ && stonith__op_state_pending(remote_op->state)) {
+
+ crm_debug("Broadcasting operation %.8s (state=%s) to "
+ "update the outdated pending one "
+ "(state=%s) in remote peer history",
+ op->id, stonith_op_state_str(op->state),
+ stonith_op_state_str(remote_op->state));
+
+ g_hash_table_remove(remote_history, op->id);
+
+ } else {
+ g_hash_table_remove(remote_history, op->id);
+ continue; /* skip entries broadcasted already */
+ }
+ }
+ }
+
+ if (!pcmk__str_eq(target, op->target, pcmk__str_null_matches)) {
+ continue;
+ }
+
+ cnt++;
+ crm_trace("Attaching op %s", op->id);
+ entry = create_xml_node(history, STONITH_OP_EXEC);
+ if (add_id) {
+ crm_xml_add(entry, F_STONITH_REMOTE_OP_ID, op->id);
+ }
+ crm_xml_add(entry, F_STONITH_TARGET, op->target);
+ crm_xml_add(entry, F_STONITH_ACTION, op->action);
+ crm_xml_add(entry, F_STONITH_ORIGIN, op->originator);
+ crm_xml_add(entry, F_STONITH_DELEGATE, op->delegate);
+ crm_xml_add(entry, F_STONITH_CLIENTNAME, op->client_name);
+ crm_xml_add_ll(entry, F_STONITH_DATE, op->completed);
+ crm_xml_add_ll(entry, F_STONITH_DATE_NSEC, op->completed_nsec);
+ crm_xml_add_int(entry, F_STONITH_STATE, op->state);
+ stonith__xe_set_result(entry, &op->result);
+ }
+ }
+
+ if (remote_history) {
+ init_stonith_remote_op_hash_table(&stonith_remote_op_list);
+
+ updated |= g_hash_table_size(remote_history);
+
+ g_hash_table_iter_init(&iter, remote_history);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&op)) {
+ if (stonith__op_state_pending(op->state) &&
+ pcmk__str_eq(op->originator, stonith_our_uname, pcmk__str_casei)) {
+
+ crm_warn("Failing pending operation %.8s originated by us but "
+ "known only from peer history", op->id);
+ op->state = st_failed;
+ set_fencing_completed(op);
+
+ /* CRM_EX_EXPIRED + PCMK_EXEC_INVALID prevents finalize_op()
+ * from setting a delegate
+ */
+ pcmk__set_result(&op->result, CRM_EX_EXPIRED, PCMK_EXEC_INVALID,
+ "Initiated by earlier fencer "
+ "process and presumed failed");
+ fenced_broadcast_op_result(op, false);
+ }
+
+ g_hash_table_iter_steal(&iter);
+ g_hash_table_replace(stonith_remote_op_list, op->id, op);
+ /* we could trim the history here but if we bail
+ * out after trim we might miss more recent entries
+ * of those that might still be in the list
+ * if we don't bail out trimming once is more
+ * efficient and memory overhead is minimal as
+ * we are just moving pointers from one hash to
+ * another
+ */
+ }
+
+ g_hash_table_destroy(remote_history); /* remove what is left */
+ }
+
+ if (updated) {
+ stonith_fence_history_trim();
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
+ }
+
+ if (cnt == 0) {
+ free_xml(history);
+ return NULL;
+ } else {
+ return history;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Craft xml from the local fence-history
+ *
+ * \param[in] add_id If crafting the answer for an API
+ * history-request there is no need for the id
+ * \param[in] target Optionally limit to certain fence-target
+ *
+ * \return The fence-history as xml
+ */
+static xmlNode *
+stonith_local_history(gboolean add_id, const char *target)
+{
+ return stonith_local_history_diff_and_merge(NULL, add_id, target);
+}
+
+/*!
+ * \internal
+ * \brief Handle fence-history messages (from API or coming in as broadcasts)
+ *
+ * \param[in,out] msg Request XML
+ * \param[out] output Where to set local history, if requested
+ * \param[in] remote_peer If broadcast, peer that sent it
+ * \param[in] options Call options from the request
+ */
+void
+stonith_fence_history(xmlNode *msg, xmlNode **output,
+ const char *remote_peer, int options)
+{
+ const char *target = NULL;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_NEVER);
+ xmlNode *out_history = NULL;
+
+ if (dev) {
+ target = crm_element_value(dev, F_STONITH_TARGET);
+ if (target && (options & st_opt_cs_nodeid)) {
+ int nodeid;
+ crm_node_t *node;
+
+ pcmk__scan_min_int(target, &nodeid, 0);
+ node = pcmk__search_known_node_cache(nodeid, NULL, CRM_GET_PEER_ANY);
+ if (node) {
+ target = node->uname;
+ }
+ }
+ }
+
+ if (options & st_opt_cleanup) {
+ crm_trace("Cleaning up operations on %s in %p", target,
+ stonith_remote_op_list);
+
+ stonith_fence_history_cleanup(target,
+ crm_element_value(msg, F_STONITH_CALLID) != NULL);
+ } else if (options & st_opt_broadcast) {
+ /* there is no clear sign atm for when a history sync
+ is done so send a notification for anything
+ that smells like history-sync
+ */
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY_SYNCED, NULL, NULL);
+ if (crm_element_value(msg, F_STONITH_CALLID)) {
+ /* this is coming from the stonith-API
+ *
+ * craft a broadcast with node's history
+ * so that every node can merge and broadcast
+ * what it has on top
+ */
+ out_history = stonith_local_history(TRUE, NULL);
+ crm_trace("Broadcasting history to peers");
+ stonith_send_broadcast_history(out_history,
+ st_opt_broadcast | st_opt_discard_reply,
+ NULL);
+ } else if (remote_peer &&
+ !pcmk__str_eq(remote_peer, stonith_our_uname, pcmk__str_casei)) {
+ xmlNode *history = get_xpath_object("//" F_STONITH_HISTORY_LIST,
+ msg, LOG_NEVER);
+
+ /* either a broadcast created directly upon stonith-API request
+ * or a diff as response to such a thing
+ *
+ * in both cases it may have a history or not
+ * if we have differential data
+ * merge in what we've received and stop
+ * otherwise broadcast what we have on top
+ * marking as differential and merge in afterwards
+ */
+ if (!history || !pcmk__xe_attr_is_true(history, F_STONITH_DIFFERENTIAL)) {
+ GHashTable *received_history = NULL;
+
+ if (history != NULL) {
+ received_history = stonith_xml_history_to_list(history);
+ }
+ out_history =
+ stonith_local_history_diff_and_merge(received_history, TRUE, NULL);
+ if (out_history) {
+ crm_trace("Broadcasting history-diff to peers");
+ pcmk__xe_set_bool_attr(out_history, F_STONITH_DIFFERENTIAL, true);
+ stonith_send_broadcast_history(out_history,
+ st_opt_broadcast | st_opt_discard_reply,
+ NULL);
+ } else {
+ crm_trace("History-diff is empty - skip broadcast");
+ }
+ }
+ } else {
+ crm_trace("Skipping history-query-broadcast (%s%s)"
+ " we sent ourselves",
+ remote_peer?"remote-peer=":"local-ipc",
+ remote_peer?remote_peer:"");
+ }
+ } else {
+ /* plain history request */
+ crm_trace("Looking for operations on %s in %p", target,
+ stonith_remote_op_list);
+ *output = stonith_local_history(FALSE, target);
+ }
+ free_xml(out_history);
+}
diff --git a/daemons/fenced/fenced_remote.c b/daemons/fenced/fenced_remote.c
new file mode 100644
index 0000000..dc67947
--- /dev/null
+++ b/daemons/fenced/fenced_remote.c
@@ -0,0 +1,2509 @@
+/*
+ * Copyright 2009-2023 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 <crm_internal.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <regex.h>
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/cluster/internal.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/fencing/internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+
+#include <crm/common/util.h>
+#include <pacemaker-fenced.h>
+
+#define TIMEOUT_MULTIPLY_FACTOR 1.2
+
+/* When one fencer queries its peers for devices able to handle a fencing
+ * request, each peer will reply with a list of such devices available to it.
+ * Each reply will be parsed into a peer_device_info_t, with each device's
+ * information kept in a device_properties_t.
+ */
+
+typedef struct device_properties_s {
+ /* Whether access to this device has been verified */
+ gboolean verified;
+
+ /* The remaining members are indexed by the operation's "phase" */
+
+ /* Whether this device has been executed in each phase */
+ gboolean executed[st_phase_max];
+ /* Whether this device is disallowed from executing in each phase */
+ gboolean disallowed[st_phase_max];
+ /* Action-specific timeout for each phase */
+ int custom_action_timeout[st_phase_max];
+ /* Action-specific maximum random delay for each phase */
+ int delay_max[st_phase_max];
+ /* Action-specific base delay for each phase */
+ int delay_base[st_phase_max];
+ /* Group of enum st_device_flags */
+ uint32_t device_support_flags;
+} device_properties_t;
+
+typedef struct {
+ /* Name of peer that sent this result */
+ char *host;
+ /* Only try peers for non-topology based operations once */
+ gboolean tried;
+ /* Number of entries in the devices table */
+ int ndevices;
+ /* Devices available to this host that are capable of fencing the target */
+ GHashTable *devices;
+} peer_device_info_t;
+
+GHashTable *stonith_remote_op_list = NULL;
+
+extern xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
+ int call_options);
+
+static void request_peer_fencing(remote_fencing_op_t *op,
+ peer_device_info_t *peer);
+static void finalize_op(remote_fencing_op_t *op, xmlNode *data, bool dup);
+static void report_timeout_period(remote_fencing_op_t * op, int op_timeout);
+static int get_op_total_timeout(const remote_fencing_op_t *op,
+ const peer_device_info_t *chosen_peer);
+
+static gint
+sort_strings(gconstpointer a, gconstpointer b)
+{
+ return strcmp(a, b);
+}
+
+static void
+free_remote_query(gpointer data)
+{
+ if (data != NULL) {
+ peer_device_info_t *peer = data;
+
+ g_hash_table_destroy(peer->devices);
+ free(peer->host);
+ free(peer);
+ }
+}
+
+void
+free_stonith_remote_op_list(void)
+{
+ if (stonith_remote_op_list != NULL) {
+ g_hash_table_destroy(stonith_remote_op_list);
+ stonith_remote_op_list = NULL;
+ }
+}
+
+struct peer_count_data {
+ const remote_fencing_op_t *op;
+ gboolean verified_only;
+ uint32_t support_action_only;
+ int count;
+};
+
+/*!
+ * \internal
+ * \brief Increment a counter if a device has not been executed yet
+ *
+ * \param[in] key Device ID (ignored)
+ * \param[in] value Device properties
+ * \param[in,out] user_data Peer count data
+ */
+static void
+count_peer_device(gpointer key, gpointer value, gpointer user_data)
+{
+ device_properties_t *props = (device_properties_t*)value;
+ struct peer_count_data *data = user_data;
+
+ if (!props->executed[data->op->phase]
+ && (!data->verified_only || props->verified)
+ && ((data->support_action_only == st_device_supports_none) || pcmk_is_set(props->device_support_flags, data->support_action_only))) {
+ ++(data->count);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Check the number of available devices in a peer's query results
+ *
+ * \param[in] op Operation that results are for
+ * \param[in] peer Peer to count
+ * \param[in] verified_only Whether to count only verified devices
+ * \param[in] support_action_only Whether to count only devices that support action
+ *
+ * \return Number of devices available to peer that were not already executed
+ */
+static int
+count_peer_devices(const remote_fencing_op_t *op,
+ const peer_device_info_t *peer, gboolean verified_only, uint32_t support_on_action_only)
+{
+ struct peer_count_data data;
+
+ data.op = op;
+ data.verified_only = verified_only;
+ data.support_action_only = support_on_action_only;
+ data.count = 0;
+ if (peer) {
+ g_hash_table_foreach(peer->devices, count_peer_device, &data);
+ }
+ return data.count;
+}
+
+/*!
+ * \internal
+ * \brief Search for a device in a query result
+ *
+ * \param[in] op Operation that result is for
+ * \param[in] peer Query result for a peer
+ * \param[in] device Device ID to search for
+ *
+ * \return Device properties if found, NULL otherwise
+ */
+static device_properties_t *
+find_peer_device(const remote_fencing_op_t *op, const peer_device_info_t *peer,
+ const char *device, uint32_t support_action_only)
+{
+ device_properties_t *props = g_hash_table_lookup(peer->devices, device);
+
+ if (props && support_action_only != st_device_supports_none && !pcmk_is_set(props->device_support_flags, support_action_only)) {
+ return NULL;
+ }
+ return (props && !props->executed[op->phase]
+ && !props->disallowed[op->phase])? props : NULL;
+}
+
+/*!
+ * \internal
+ * \brief Find a device in a peer's device list and mark it as executed
+ *
+ * \param[in] op Operation that peer result is for
+ * \param[in,out] peer Peer with results to search
+ * \param[in] device ID of device to mark as done
+ * \param[in] verified_devices_only Only consider verified devices
+ *
+ * \return TRUE if device was found and marked, FALSE otherwise
+ */
+static gboolean
+grab_peer_device(const remote_fencing_op_t *op, peer_device_info_t *peer,
+ const char *device, gboolean verified_devices_only)
+{
+ device_properties_t *props = find_peer_device(op, peer, device,
+ fenced_support_flag(op->action));
+
+ if ((props == NULL) || (verified_devices_only && !props->verified)) {
+ return FALSE;
+ }
+
+ crm_trace("Removing %s from %s (%d remaining)",
+ device, peer->host, count_peer_devices(op, peer, FALSE, st_device_supports_none));
+ props->executed[op->phase] = TRUE;
+ return TRUE;
+}
+
+static void
+clear_remote_op_timers(remote_fencing_op_t * op)
+{
+ if (op->query_timer) {
+ g_source_remove(op->query_timer);
+ op->query_timer = 0;
+ }
+ if (op->op_timer_total) {
+ g_source_remove(op->op_timer_total);
+ op->op_timer_total = 0;
+ }
+ if (op->op_timer_one) {
+ g_source_remove(op->op_timer_one);
+ op->op_timer_one = 0;
+ }
+}
+
+static void
+free_remote_op(gpointer data)
+{
+ remote_fencing_op_t *op = data;
+
+ crm_log_xml_debug(op->request, "Destroying");
+
+ clear_remote_op_timers(op);
+
+ free(op->id);
+ free(op->action);
+ free(op->delegate);
+ free(op->target);
+ free(op->client_id);
+ free(op->client_name);
+ free(op->originator);
+
+ if (op->query_results) {
+ g_list_free_full(op->query_results, free_remote_query);
+ }
+ if (op->request) {
+ free_xml(op->request);
+ op->request = NULL;
+ }
+ if (op->devices_list) {
+ g_list_free_full(op->devices_list, free);
+ op->devices_list = NULL;
+ }
+ g_list_free_full(op->automatic_list, free);
+ g_list_free(op->duplicates);
+
+ pcmk__reset_result(&op->result);
+ free(op);
+}
+
+void
+init_stonith_remote_op_hash_table(GHashTable **table)
+{
+ if (*table == NULL) {
+ *table = pcmk__strkey_table(NULL, free_remote_op);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Return an operation's originally requested action (before any remap)
+ *
+ * \param[in] op Operation to check
+ *
+ * \return Operation's original action
+ */
+static const char *
+op_requested_action(const remote_fencing_op_t *op)
+{
+ return ((op->phase > st_phase_requested)? "reboot" : op->action);
+}
+
+/*!
+ * \internal
+ * \brief Remap a "reboot" operation to the "off" phase
+ *
+ * \param[in,out] op Operation to remap
+ */
+static void
+op_phase_off(remote_fencing_op_t *op)
+{
+ crm_info("Remapping multiple-device reboot targeting %s to 'off' "
+ CRM_XS " id=%.8s", op->target, op->id);
+ op->phase = st_phase_off;
+
+ /* Happily, "off" and "on" are shorter than "reboot", so we can reuse the
+ * memory allocation at each phase.
+ */
+ strcpy(op->action, "off");
+}
+
+/*!
+ * \internal
+ * \brief Advance a remapped reboot operation to the "on" phase
+ *
+ * \param[in,out] op Operation to remap
+ */
+static void
+op_phase_on(remote_fencing_op_t *op)
+{
+ GList *iter = NULL;
+
+ crm_info("Remapped 'off' targeting %s complete, "
+ "remapping to 'on' for %s " CRM_XS " id=%.8s",
+ op->target, op->client_name, op->id);
+ op->phase = st_phase_on;
+ strcpy(op->action, "on");
+
+ /* Skip devices with automatic unfencing, because the cluster will handle it
+ * when the node rejoins.
+ */
+ for (iter = op->automatic_list; iter != NULL; iter = iter->next) {
+ GList *match = g_list_find_custom(op->devices_list, iter->data,
+ sort_strings);
+
+ if (match) {
+ op->devices_list = g_list_remove(op->devices_list, match->data);
+ }
+ }
+ g_list_free_full(op->automatic_list, free);
+ op->automatic_list = NULL;
+
+ /* Rewind device list pointer */
+ op->devices = op->devices_list;
+}
+
+/*!
+ * \internal
+ * \brief Reset a remapped reboot operation
+ *
+ * \param[in,out] op Operation to reset
+ */
+static void
+undo_op_remap(remote_fencing_op_t *op)
+{
+ if (op->phase > 0) {
+ crm_info("Undoing remap of reboot targeting %s for %s "
+ CRM_XS " id=%.8s", op->target, op->client_name, op->id);
+ op->phase = st_phase_requested;
+ strcpy(op->action, "reboot");
+ }
+}
+
+/*!
+ * \internal
+ * \brief Create notification data XML for a fencing operation result
+ *
+ * \param[in] op Fencer operation that completed
+ *
+ * \return Newly created XML to add as notification data
+ * \note The caller is responsible for freeing the result.
+ */
+static xmlNode *
+fencing_result2xml(const remote_fencing_op_t *op)
+{
+ xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE);
+
+ crm_xml_add_int(notify_data, "state", op->state);
+ crm_xml_add(notify_data, F_STONITH_TARGET, op->target);
+ crm_xml_add(notify_data, F_STONITH_ACTION, op->action);
+ crm_xml_add(notify_data, F_STONITH_DELEGATE, op->delegate);
+ crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, op->id);
+ crm_xml_add(notify_data, F_STONITH_ORIGIN, op->originator);
+ crm_xml_add(notify_data, F_STONITH_CLIENTID, op->client_id);
+ crm_xml_add(notify_data, F_STONITH_CLIENTNAME, op->client_name);
+
+ return notify_data;
+}
+
+/*!
+ * \internal
+ * \brief Broadcast a fence result notification to all CPG peers
+ *
+ * \param[in] op Fencer operation that completed
+ * \param[in] op_merged Whether this operation is a duplicate of another
+ */
+void
+fenced_broadcast_op_result(const remote_fencing_op_t *op, bool op_merged)
+{
+ static int count = 0;
+ xmlNode *bcast = create_xml_node(NULL, T_STONITH_REPLY);
+ xmlNode *notify_data = fencing_result2xml(op);
+
+ count++;
+ crm_trace("Broadcasting result to peers");
+ crm_xml_add(bcast, F_TYPE, T_STONITH_NOTIFY);
+ crm_xml_add(bcast, F_SUBTYPE, "broadcast");
+ crm_xml_add(bcast, F_STONITH_OPERATION, T_STONITH_NOTIFY);
+ crm_xml_add_int(bcast, "count", count);
+
+ if (op_merged) {
+ pcmk__xe_set_bool_attr(bcast, F_STONITH_MERGED, true);
+ }
+
+ stonith__xe_set_result(notify_data, &op->result);
+
+ add_message_xml(bcast, F_STONITH_CALLDATA, notify_data);
+ send_cluster_message(NULL, crm_msg_stonith_ng, bcast, FALSE);
+ free_xml(notify_data);
+ free_xml(bcast);
+
+ return;
+}
+
+/*!
+ * \internal
+ * \brief Reply to a local request originator and notify all subscribed clients
+ *
+ * \param[in,out] op Fencer operation that completed
+ * \param[in,out] data Top-level XML to add notification to
+ */
+static void
+handle_local_reply_and_notify(remote_fencing_op_t *op, xmlNode *data)
+{
+ xmlNode *notify_data = NULL;
+ xmlNode *reply = NULL;
+ pcmk__client_t *client = NULL;
+
+ if (op->notify_sent == TRUE) {
+ /* nothing to do */
+ return;
+ }
+
+ /* Do notification with a clean data object */
+ crm_xml_add_int(data, "state", op->state);
+ crm_xml_add(data, F_STONITH_TARGET, op->target);
+ crm_xml_add(data, F_STONITH_OPERATION, op->action);
+
+ reply = fenced_construct_reply(op->request, data, &op->result);
+ crm_xml_add(reply, F_STONITH_DELEGATE, op->delegate);
+
+ /* Send fencing OP reply to local client that initiated fencing */
+ client = pcmk__find_client_by_id(op->client_id);
+ if (client == NULL) {
+ crm_trace("Skipping reply to %s: no longer a client", op->client_id);
+ } else {
+ do_local_reply(reply, client, op->call_options);
+ }
+
+ /* bcast to all local clients that the fencing operation happend */
+ notify_data = fencing_result2xml(op);
+ fenced_send_notification(T_STONITH_NOTIFY_FENCE, &op->result, notify_data);
+ free_xml(notify_data);
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
+
+ /* mark this op as having notify's already sent */
+ op->notify_sent = TRUE;
+ free_xml(reply);
+}
+
+/*!
+ * \internal
+ * \brief Finalize all duplicates of a given fencer operation
+ *
+ * \param[in,out] op Fencer operation that completed
+ * \param[in,out] data Top-level XML to add notification to
+ */
+static void
+finalize_op_duplicates(remote_fencing_op_t *op, xmlNode *data)
+{
+ for (GList *iter = op->duplicates; iter != NULL; iter = iter->next) {
+ remote_fencing_op_t *other = iter->data;
+
+ if (other->state == st_duplicate) {
+ other->state = op->state;
+ crm_debug("Performing duplicate notification for %s@%s: %s "
+ CRM_XS " id=%.8s",
+ other->client_name, other->originator,
+ pcmk_exec_status_str(op->result.execution_status),
+ other->id);
+ pcmk__copy_result(&op->result, &other->result);
+ finalize_op(other, data, true);
+
+ } else {
+ // Possible if (for example) it timed out already
+ crm_err("Skipping duplicate notification for %s@%s "
+ CRM_XS " state=%s id=%.8s",
+ other->client_name, other->originator,
+ stonith_op_state_str(other->state), other->id);
+ }
+ }
+}
+
+static char *
+delegate_from_xml(xmlNode *xml)
+{
+ xmlNode *match = get_xpath_object("//@" F_STONITH_DELEGATE, xml, LOG_NEVER);
+
+ if (match == NULL) {
+ return crm_element_value_copy(xml, F_ORIG);
+ } else {
+ return crm_element_value_copy(match, F_STONITH_DELEGATE);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Finalize a peer fencing operation
+ *
+ * Clean up after a fencing operation completes. This function has two code
+ * paths: the executioner uses it to broadcast the result to CPG peers, and then
+ * each peer (including the executioner) uses it to process that broadcast and
+ * notify its IPC clients of the result.
+ *
+ * \param[in,out] op Fencer operation that completed
+ * \param[in,out] data If not NULL, XML reply of last delegated operation
+ * \param[in] dup Whether this operation is a duplicate of another
+ * (in which case, do not broadcast the result)
+ *
+ * \note The operation result should be set before calling this function.
+ */
+static void
+finalize_op(remote_fencing_op_t *op, xmlNode *data, bool dup)
+{
+ int level = LOG_ERR;
+ const char *subt = NULL;
+ xmlNode *local_data = NULL;
+ gboolean op_merged = FALSE;
+
+ CRM_CHECK((op != NULL), return);
+
+ // This is a no-op if timers have already been cleared
+ clear_remote_op_timers(op);
+
+ if (op->notify_sent) {
+ // Most likely, this is a timed-out action that eventually completed
+ crm_notice("Operation '%s'%s%s by %s for %s@%s%s: "
+ "Result arrived too late " CRM_XS " id=%.8s",
+ op->action, (op->target? " targeting " : ""),
+ (op->target? op->target : ""),
+ (op->delegate? op->delegate : "unknown node"),
+ op->client_name, op->originator,
+ (op_merged? " (merged)" : ""),
+ op->id);
+ return;
+ }
+
+ set_fencing_completed(op);
+ undo_op_remap(op);
+
+ if (data == NULL) {
+ data = create_xml_node(NULL, "remote-op");
+ local_data = data;
+
+ } else if (op->delegate == NULL) {
+ switch (op->result.execution_status) {
+ case PCMK_EXEC_NO_FENCE_DEVICE:
+ break;
+
+ case PCMK_EXEC_INVALID:
+ if (op->result.exit_status != CRM_EX_EXPIRED) {
+ op->delegate = delegate_from_xml(data);
+ }
+ break;
+
+ default:
+ op->delegate = delegate_from_xml(data);
+ break;
+ }
+ }
+
+ if (dup || (crm_element_value(data, F_STONITH_MERGED) != NULL)) {
+ op_merged = true;
+ }
+
+ /* Tell everyone the operation is done, we will continue
+ * with doing the local notifications once we receive
+ * the broadcast back. */
+ subt = crm_element_value(data, F_SUBTYPE);
+ if (!dup && !pcmk__str_eq(subt, "broadcast", pcmk__str_casei)) {
+ /* Defer notification until the bcast message arrives */
+ fenced_broadcast_op_result(op, op_merged);
+ free_xml(local_data);
+ return;
+ }
+
+ if (pcmk__result_ok(&op->result) || dup
+ || !pcmk__str_eq(op->originator, stonith_our_uname, pcmk__str_casei)) {
+ level = LOG_NOTICE;
+ }
+ do_crm_log(level, "Operation '%s'%s%s by %s for %s@%s%s: %s (%s%s%s) "
+ CRM_XS " id=%.8s", op->action, (op->target? " targeting " : ""),
+ (op->target? op->target : ""),
+ (op->delegate? op->delegate : "unknown node"),
+ op->client_name, op->originator,
+ (op_merged? " (merged)" : ""),
+ crm_exit_str(op->result.exit_status),
+ pcmk_exec_status_str(op->result.execution_status),
+ ((op->result.exit_reason == NULL)? "" : ": "),
+ ((op->result.exit_reason == NULL)? "" : op->result.exit_reason),
+ op->id);
+
+ handle_local_reply_and_notify(op, data);
+
+ if (!dup) {
+ finalize_op_duplicates(op, data);
+ }
+
+ /* Free non-essential parts of the record
+ * Keep the record around so we can query the history
+ */
+ if (op->query_results) {
+ g_list_free_full(op->query_results, free_remote_query);
+ op->query_results = NULL;
+ }
+ if (op->request) {
+ free_xml(op->request);
+ op->request = NULL;
+ }
+
+ free_xml(local_data);
+}
+
+/*!
+ * \internal
+ * \brief Finalize a watchdog fencer op after the waiting time expires
+ *
+ * \param[in,out] userdata Fencer operation that completed
+ *
+ * \return G_SOURCE_REMOVE (which tells glib not to restart timer)
+ */
+static gboolean
+remote_op_watchdog_done(gpointer userdata)
+{
+ remote_fencing_op_t *op = userdata;
+
+ op->op_timer_one = 0;
+
+ crm_notice("Self-fencing (%s) by %s for %s assumed complete "
+ CRM_XS " id=%.8s",
+ op->action, op->target, op->client_name, op->id);
+ op->state = st_done;
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ finalize_op(op, NULL, false);
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+remote_op_timeout_one(gpointer userdata)
+{
+ remote_fencing_op_t *op = userdata;
+
+ op->op_timer_one = 0;
+
+ crm_notice("Peer's '%s' action targeting %s for client %s timed out " CRM_XS
+ " id=%.8s", op->action, op->target, op->client_name, op->id);
+ pcmk__set_result(&op->result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT,
+ "Peer did not return fence result within timeout");
+
+ // The requested delay has been applied for the first device
+ if (op->delay > 0) {
+ op->delay = 0;
+ crm_trace("Try another device for '%s' action targeting %s "
+ "for client %s without delay " CRM_XS " id=%.8s",
+ op->action, op->target, op->client_name, op->id);
+ }
+
+ // Try another device, if appropriate
+ request_peer_fencing(op, NULL);
+ return G_SOURCE_REMOVE;
+}
+
+/*!
+ * \internal
+ * \brief Finalize a remote fencer operation that timed out
+ *
+ * \param[in,out] op Fencer operation that timed out
+ * \param[in] reason Readable description of what step timed out
+ */
+static void
+finalize_timed_out_op(remote_fencing_op_t *op, const char *reason)
+{
+ crm_debug("Action '%s' targeting %s for client %s timed out "
+ CRM_XS " id=%.8s",
+ op->action, op->target, op->client_name, op->id);
+
+ if (op->phase == st_phase_on) {
+ /* A remapped reboot operation timed out in the "on" phase, but the
+ * "off" phase completed successfully, so quit trying any further
+ * devices, and return success.
+ */
+ op->state = st_done;
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ } else {
+ op->state = st_failed;
+ pcmk__set_result(&op->result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT, reason);
+ }
+ finalize_op(op, NULL, false);
+}
+
+/*!
+ * \internal
+ * \brief Finalize a remote fencer operation that timed out
+ *
+ * \param[in,out] userdata Fencer operation that timed out
+ *
+ * \return G_SOURCE_REMOVE (which tells glib not to restart timer)
+ */
+static gboolean
+remote_op_timeout(gpointer userdata)
+{
+ remote_fencing_op_t *op = userdata;
+
+ op->op_timer_total = 0;
+
+ if (op->state == st_done) {
+ crm_debug("Action '%s' targeting %s for client %s already completed "
+ CRM_XS " id=%.8s",
+ op->action, op->target, op->client_name, op->id);
+ } else {
+ finalize_timed_out_op(userdata, "Fencing did not complete within a "
+ "total timeout based on the "
+ "configured timeout and retries for "
+ "any devices attempted");
+ }
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+remote_op_query_timeout(gpointer data)
+{
+ remote_fencing_op_t *op = data;
+
+ op->query_timer = 0;
+
+ if (op->state == st_done) {
+ crm_debug("Operation %.8s targeting %s already completed",
+ op->id, op->target);
+ } else if (op->state == st_exec) {
+ crm_debug("Operation %.8s targeting %s already in progress",
+ op->id, op->target);
+ } else if (op->query_results) {
+ // Query succeeded, so attempt the actual fencing
+ crm_debug("Query %.8s targeting %s complete (state=%s)",
+ op->id, op->target, stonith_op_state_str(op->state));
+ request_peer_fencing(op, NULL);
+ } else {
+ crm_debug("Query %.8s targeting %s timed out (state=%s)",
+ op->id, op->target, stonith_op_state_str(op->state));
+ finalize_timed_out_op(op, "No capable peers replied to device query "
+ "within timeout");
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+topology_is_empty(stonith_topology_t *tp)
+{
+ int i;
+
+ if (tp == NULL) {
+ return TRUE;
+ }
+
+ for (i = 0; i < ST_LEVEL_MAX; i++) {
+ if (tp->levels[i] != NULL) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Add a device to an operation's automatic unfencing list
+ *
+ * \param[in,out] op Operation to modify
+ * \param[in] device Device ID to add
+ */
+static void
+add_required_device(remote_fencing_op_t *op, const char *device)
+{
+ GList *match = g_list_find_custom(op->automatic_list, device,
+ sort_strings);
+
+ if (!match) {
+ op->automatic_list = g_list_prepend(op->automatic_list, strdup(device));
+ }
+}
+
+/*!
+ * \internal
+ * \brief Remove a device from the automatic unfencing list
+ *
+ * \param[in,out] op Operation to modify
+ * \param[in] device Device ID to remove
+ */
+static void
+remove_required_device(remote_fencing_op_t *op, const char *device)
+{
+ GList *match = g_list_find_custom(op->automatic_list, device,
+ sort_strings);
+
+ if (match) {
+ op->automatic_list = g_list_remove(op->automatic_list, match->data);
+ }
+}
+
+/* deep copy the device list */
+static void
+set_op_device_list(remote_fencing_op_t * op, GList *devices)
+{
+ GList *lpc = NULL;
+
+ if (op->devices_list) {
+ g_list_free_full(op->devices_list, free);
+ op->devices_list = NULL;
+ }
+ for (lpc = devices; lpc != NULL; lpc = lpc->next) {
+ op->devices_list = g_list_append(op->devices_list, strdup(lpc->data));
+ }
+ op->devices = op->devices_list;
+}
+
+/*!
+ * \internal
+ * \brief Check whether a node matches a topology target
+ *
+ * \param[in] tp Topology table entry to check
+ * \param[in] node Name of node to check
+ *
+ * \return TRUE if node matches topology target
+ */
+static gboolean
+topology_matches(const stonith_topology_t *tp, const char *node)
+{
+ regex_t r_patt;
+
+ CRM_CHECK(node && tp && tp->target, return FALSE);
+ switch (tp->kind) {
+ case fenced_target_by_attribute:
+ /* This level targets by attribute, so tp->target is a NAME=VALUE pair
+ * of a permanent attribute applied to targeted nodes. The test below
+ * relies on the locally cached copy of the CIB, so if fencing needs to
+ * be done before the initial CIB is received or after a malformed CIB
+ * is received, then the topology will be unable to be used.
+ */
+ if (node_has_attr(node, tp->target_attribute, tp->target_value)) {
+ crm_notice("Matched %s with %s by attribute", node, tp->target);
+ return TRUE;
+ }
+ break;
+
+ case fenced_target_by_pattern:
+ /* This level targets node names matching a pattern, so tp->target
+ * (and tp->target_pattern) is a regular expression.
+ */
+ if (regcomp(&r_patt, tp->target_pattern, REG_EXTENDED|REG_NOSUB)) {
+ crm_info("Bad regex '%s' for fencing level", tp->target);
+ } else {
+ int status = regexec(&r_patt, node, 0, NULL, 0);
+
+ regfree(&r_patt);
+ if (status == 0) {
+ crm_notice("Matched %s with %s by name", node, tp->target);
+ return TRUE;
+ }
+ }
+ break;
+
+ case fenced_target_by_name:
+ crm_trace("Testing %s against %s", node, tp->target);
+ return pcmk__str_eq(tp->target, node, pcmk__str_casei);
+
+ default:
+ break;
+ }
+ crm_trace("No match for %s with %s", node, tp->target);
+ return FALSE;
+}
+
+stonith_topology_t *
+find_topology_for_host(const char *host)
+{
+ GHashTableIter tIter;
+ stonith_topology_t *tp = g_hash_table_lookup(topology, host);
+
+ if(tp != NULL) {
+ crm_trace("Found %s for %s in %d entries", tp->target, host, g_hash_table_size(topology));
+ return tp;
+ }
+
+ g_hash_table_iter_init(&tIter, topology);
+ while (g_hash_table_iter_next(&tIter, NULL, (gpointer *) & tp)) {
+ if (topology_matches(tp, host)) {
+ crm_trace("Found %s for %s in %d entries", tp->target, host, g_hash_table_size(topology));
+ return tp;
+ }
+ }
+
+ crm_trace("No matches for %s in %d topology entries", host, g_hash_table_size(topology));
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Set fencing operation's device list to target's next topology level
+ *
+ * \param[in,out] op Remote fencing operation to modify
+ * \param[in] empty_ok If true, an operation without a target (i.e.
+ * queries) or a target without a topology will get a
+ * pcmk_rc_ok return value instead of ENODEV
+ *
+ * \return Standard Pacemaker return value
+ */
+static int
+advance_topology_level(remote_fencing_op_t *op, bool empty_ok)
+{
+ stonith_topology_t *tp = NULL;
+
+ if (op->target) {
+ tp = find_topology_for_host(op->target);
+ }
+ if (topology_is_empty(tp)) {
+ return empty_ok? pcmk_rc_ok : ENODEV;
+ }
+
+ CRM_ASSERT(tp->levels != NULL);
+
+ stonith__set_call_options(op->call_options, op->id, st_opt_topology);
+
+ /* This is a new level, so undo any remapping left over from previous */
+ undo_op_remap(op);
+
+ do {
+ op->level++;
+
+ } while (op->level < ST_LEVEL_MAX && tp->levels[op->level] == NULL);
+
+ if (op->level < ST_LEVEL_MAX) {
+ crm_trace("Attempting fencing level %d targeting %s (%d devices) "
+ "for client %s@%s (id=%.8s)",
+ op->level, op->target, g_list_length(tp->levels[op->level]),
+ op->client_name, op->originator, op->id);
+ set_op_device_list(op, tp->levels[op->level]);
+
+ // The requested delay has been applied for the first fencing level
+ if (op->level > 1 && op->delay > 0) {
+ op->delay = 0;
+ }
+
+ if ((g_list_next(op->devices_list) != NULL)
+ && pcmk__str_eq(op->action, "reboot", pcmk__str_none)) {
+ /* A reboot has been requested for a topology level with multiple
+ * devices. Instead of rebooting the devices sequentially, we will
+ * turn them all off, then turn them all on again. (Think about
+ * switched power outlets for redundant power supplies.)
+ */
+ op_phase_off(op);
+ }
+ return pcmk_rc_ok;
+ }
+
+ crm_info("All %sfencing options targeting %s for client %s@%s failed "
+ CRM_XS " id=%.8s",
+ (stonith_watchdog_timeout_ms > 0)?"non-watchdog ":"",
+ op->target, op->client_name, op->originator, op->id);
+ return ENODEV;
+}
+
+/*!
+ * \internal
+ * \brief If fencing operation is a duplicate, merge it into the other one
+ *
+ * \param[in,out] op Fencing operation to check
+ */
+static void
+merge_duplicates(remote_fencing_op_t *op)
+{
+ GHashTableIter iter;
+ remote_fencing_op_t *other = NULL;
+
+ time_t now = time(NULL);
+
+ g_hash_table_iter_init(&iter, stonith_remote_op_list);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&other)) {
+ const char *other_action = op_requested_action(other);
+
+ if (!strcmp(op->id, other->id)) {
+ continue; // Don't compare against self
+ }
+ if (other->state > st_exec) {
+ crm_trace("%.8s not duplicate of %.8s: not in progress",
+ op->id, other->id);
+ continue;
+ }
+ if (!pcmk__str_eq(op->target, other->target, pcmk__str_casei)) {
+ crm_trace("%.8s not duplicate of %.8s: node %s vs. %s",
+ op->id, other->id, op->target, other->target);
+ continue;
+ }
+ if (!pcmk__str_eq(op->action, other_action, pcmk__str_none)) {
+ crm_trace("%.8s not duplicate of %.8s: action %s vs. %s",
+ op->id, other->id, op->action, other_action);
+ continue;
+ }
+ if (pcmk__str_eq(op->client_name, other->client_name, pcmk__str_casei)) {
+ crm_trace("%.8s not duplicate of %.8s: same client %s",
+ op->id, other->id, op->client_name);
+ continue;
+ }
+ if (pcmk__str_eq(other->target, other->originator, pcmk__str_casei)) {
+ crm_trace("%.8s not duplicate of %.8s: suicide for %s",
+ op->id, other->id, other->target);
+ continue;
+ }
+ if (!fencing_peer_active(crm_get_peer(0, other->originator))) {
+ crm_notice("Failing action '%s' targeting %s originating from "
+ "client %s@%s: Originator is dead " CRM_XS " id=%.8s",
+ other->action, other->target, other->client_name,
+ other->originator, other->id);
+ crm_trace("%.8s not duplicate of %.8s: originator dead",
+ op->id, other->id);
+ other->state = st_failed;
+ continue;
+ }
+ if ((other->total_timeout > 0)
+ && (now > (other->total_timeout + other->created))) {
+ crm_trace("%.8s not duplicate of %.8s: old (%ld vs. %ld + %d)",
+ op->id, other->id, now, other->created,
+ other->total_timeout);
+ continue;
+ }
+
+ /* There is another in-flight request to fence the same host
+ * Piggyback on that instead. If it fails, so do we.
+ */
+ other->duplicates = g_list_append(other->duplicates, op);
+ if (other->total_timeout == 0) {
+ other->total_timeout = op->total_timeout =
+ TIMEOUT_MULTIPLY_FACTOR * get_op_total_timeout(op, NULL);
+ crm_trace("Best guess as to timeout used for %.8s: %d",
+ other->id, other->total_timeout);
+ }
+ crm_notice("Merging fencing action '%s' targeting %s originating from "
+ "client %s with identical request from %s@%s "
+ CRM_XS " original=%.8s duplicate=%.8s total_timeout=%ds",
+ op->action, op->target, op->client_name,
+ other->client_name, other->originator,
+ op->id, other->id, other->total_timeout);
+ report_timeout_period(op, other->total_timeout);
+ op->state = st_duplicate;
+ }
+}
+
+static uint32_t fencing_active_peers(void)
+{
+ uint32_t count = 0;
+ crm_node_t *entry;
+ GHashTableIter gIter;
+
+ g_hash_table_iter_init(&gIter, crm_peer_cache);
+ while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
+ if(fencing_peer_active(entry)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/*!
+ * \internal
+ * \brief Process a manual confirmation of a pending fence action
+ *
+ * \param[in] client IPC client that sent confirmation
+ * \param[in,out] msg Request XML with manual confirmation
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+fenced_handle_manual_confirmation(const pcmk__client_t *client, xmlNode *msg)
+{
+ remote_fencing_op_t *op = NULL;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
+
+ CRM_CHECK(dev != NULL, return EPROTO);
+
+ crm_notice("Received manual confirmation that %s has been fenced",
+ pcmk__s(crm_element_value(dev, F_STONITH_TARGET),
+ "unknown target"));
+ op = initiate_remote_stonith_op(client, msg, TRUE);
+ if (op == NULL) {
+ return EPROTO;
+ }
+ op->state = st_done;
+ set_fencing_completed(op);
+ op->delegate = strdup("a human");
+
+ // For the fencer's purposes, the fencing operation is done
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ finalize_op(op, msg, false);
+
+ /* For the requester's purposes, the operation is still pending. The
+ * actual result will be sent asynchronously via the operation's done_cb().
+ */
+ return EINPROGRESS;
+}
+
+/*!
+ * \internal
+ * \brief Create a new remote stonith operation
+ *
+ * \param[in] client ID of local stonith client that initiated the operation
+ * \param[in] request The request from the client that started the operation
+ * \param[in] peer TRUE if this operation is owned by another stonith peer
+ * (an operation owned by one peer is stored on all peers,
+ * but only the owner executes it; all nodes get the results
+ * once the owner finishes execution)
+ */
+void *
+create_remote_stonith_op(const char *client, xmlNode *request, gboolean peer)
+{
+ remote_fencing_op_t *op = NULL;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_NEVER);
+ int call_options = 0;
+ const char *operation = NULL;
+
+ init_stonith_remote_op_hash_table(&stonith_remote_op_list);
+
+ /* If this operation is owned by another node, check to make
+ * sure we haven't already created this operation. */
+ if (peer && dev) {
+ const char *op_id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
+
+ CRM_CHECK(op_id != NULL, return NULL);
+
+ op = g_hash_table_lookup(stonith_remote_op_list, op_id);
+ if (op) {
+ crm_debug("Reusing existing remote fencing op %.8s for %s",
+ op_id, ((client == NULL)? "unknown client" : client));
+ return op;
+ }
+ }
+
+ op = calloc(1, sizeof(remote_fencing_op_t));
+ CRM_ASSERT(op != NULL);
+
+ crm_element_value_int(request, F_STONITH_TIMEOUT, &(op->base_timeout));
+ // Value -1 means disable any static/random fencing delays
+ crm_element_value_int(request, F_STONITH_DELAY, &(op->delay));
+
+ if (peer && dev) {
+ op->id = crm_element_value_copy(dev, F_STONITH_REMOTE_OP_ID);
+ } else {
+ op->id = crm_generate_uuid();
+ }
+
+ g_hash_table_replace(stonith_remote_op_list, op->id, op);
+
+ op->state = st_query;
+ op->replies_expected = fencing_active_peers();
+ op->action = crm_element_value_copy(dev, F_STONITH_ACTION);
+ op->originator = crm_element_value_copy(dev, F_STONITH_ORIGIN);
+ op->delegate = crm_element_value_copy(dev, F_STONITH_DELEGATE); /* May not be set */
+ op->created = time(NULL);
+
+ if (op->originator == NULL) {
+ /* Local or relayed request */
+ op->originator = strdup(stonith_our_uname);
+ }
+
+ CRM_LOG_ASSERT(client != NULL);
+ if (client) {
+ op->client_id = strdup(client);
+ }
+
+
+ /* For a RELAY operation, set fenced on the client. */
+ operation = crm_element_value(request, F_STONITH_OPERATION);
+
+ if (pcmk__str_eq(operation, STONITH_OP_RELAY, pcmk__str_none)) {
+ op->client_name = crm_strdup_printf("%s.%lu", crm_system_name,
+ (unsigned long) getpid());
+ } else {
+ op->client_name = crm_element_value_copy(request, F_STONITH_CLIENTNAME);
+ }
+
+ op->target = crm_element_value_copy(dev, F_STONITH_TARGET);
+ op->request = copy_xml(request); /* TODO: Figure out how to avoid this */
+ crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
+ op->call_options = call_options;
+
+ crm_element_value_int(request, F_STONITH_CALLID, &(op->client_callid));
+
+ crm_trace("%s new fencing op %s ('%s' targeting %s for client %s, "
+ "base timeout %d, %u %s expected)",
+ (peer && dev)? "Recorded" : "Generated", op->id, op->action,
+ op->target, op->client_name, op->base_timeout,
+ op->replies_expected,
+ pcmk__plural_alt(op->replies_expected, "reply", "replies"));
+
+ if (op->call_options & st_opt_cs_nodeid) {
+ int nodeid;
+ crm_node_t *node;
+
+ pcmk__scan_min_int(op->target, &nodeid, 0);
+ node = pcmk__search_known_node_cache(nodeid, NULL, CRM_GET_PEER_ANY);
+
+ /* Ensure the conversion only happens once */
+ stonith__clear_call_options(op->call_options, op->id, st_opt_cs_nodeid);
+
+ if (node && node->uname) {
+ free(op->target);
+ op->target = strdup(node->uname);
+
+ } else {
+ crm_warn("Could not expand nodeid '%s' into a host name", op->target);
+ }
+ }
+
+ /* check to see if this is a duplicate operation of another in-flight operation */
+ merge_duplicates(op);
+
+ if (op->state != st_duplicate) {
+ /* kick history readers */
+ fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
+ }
+
+ /* safe to trim as long as that doesn't touch pending ops */
+ stonith_fence_history_trim();
+
+ return op;
+}
+
+/*!
+ * \internal
+ * \brief Create a peer fencing operation from a request, and initiate it
+ *
+ * \param[in] client IPC client that made request (NULL to get from request)
+ * \param[in] request Request XML
+ * \param[in] manual_ack Whether this is a manual action confirmation
+ *
+ * \return Newly created operation on success, otherwise NULL
+ */
+remote_fencing_op_t *
+initiate_remote_stonith_op(const pcmk__client_t *client, xmlNode *request,
+ gboolean manual_ack)
+{
+ int query_timeout = 0;
+ xmlNode *query = NULL;
+ const char *client_id = NULL;
+ remote_fencing_op_t *op = NULL;
+ const char *relay_op_id = NULL;
+ const char *operation = NULL;
+
+ if (client) {
+ client_id = client->id;
+ } else {
+ client_id = crm_element_value(request, F_STONITH_CLIENTID);
+ }
+
+ CRM_LOG_ASSERT(client_id != NULL);
+ op = create_remote_stonith_op(client_id, request, FALSE);
+ op->owner = TRUE;
+ if (manual_ack) {
+ return op;
+ }
+
+ CRM_CHECK(op->action, return NULL);
+
+ if (advance_topology_level(op, true) != pcmk_rc_ok) {
+ op->state = st_failed;
+ }
+
+ switch (op->state) {
+ case st_failed:
+ // advance_topology_level() exhausted levels
+ pcmk__set_result(&op->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
+ "All topology levels failed");
+ crm_warn("Could not request peer fencing (%s) targeting %s "
+ CRM_XS " id=%.8s", op->action, op->target, op->id);
+ finalize_op(op, NULL, false);
+ return op;
+
+ case st_duplicate:
+ crm_info("Requesting peer fencing (%s) targeting %s (duplicate) "
+ CRM_XS " id=%.8s", op->action, op->target, op->id);
+ return op;
+
+ default:
+ crm_notice("Requesting peer fencing (%s) targeting %s "
+ CRM_XS " id=%.8s state=%s base_timeout=%d",
+ op->action, op->target, op->id,
+ stonith_op_state_str(op->state), op->base_timeout);
+ }
+
+ query = stonith_create_op(op->client_callid, op->id, STONITH_OP_QUERY,
+ NULL, op->call_options);
+
+ crm_xml_add(query, F_STONITH_REMOTE_OP_ID, op->id);
+ crm_xml_add(query, F_STONITH_TARGET, op->target);
+ crm_xml_add(query, F_STONITH_ACTION, op_requested_action(op));
+ crm_xml_add(query, F_STONITH_ORIGIN, op->originator);
+ crm_xml_add(query, F_STONITH_CLIENTID, op->client_id);
+ crm_xml_add(query, F_STONITH_CLIENTNAME, op->client_name);
+ crm_xml_add_int(query, F_STONITH_TIMEOUT, op->base_timeout);
+
+ /* In case of RELAY operation, RELAY information is added to the query to delete the original operation of RELAY. */
+ operation = crm_element_value(request, F_STONITH_OPERATION);
+ if (pcmk__str_eq(operation, STONITH_OP_RELAY, pcmk__str_none)) {
+ relay_op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID);
+ if (relay_op_id) {
+ crm_xml_add(query, F_STONITH_REMOTE_OP_ID_RELAY, relay_op_id);
+ }
+ }
+
+ send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE);
+ free_xml(query);
+
+ query_timeout = op->base_timeout * TIMEOUT_MULTIPLY_FACTOR;
+ op->query_timer = g_timeout_add((1000 * query_timeout), remote_op_query_timeout, op);
+
+ return op;
+}
+
+enum find_best_peer_options {
+ /*! Skip checking the target peer for capable fencing devices */
+ FIND_PEER_SKIP_TARGET = 0x0001,
+ /*! Only check the target peer for capable fencing devices */
+ FIND_PEER_TARGET_ONLY = 0x0002,
+ /*! Skip peers and devices that are not verified */
+ FIND_PEER_VERIFIED_ONLY = 0x0004,
+};
+
+static peer_device_info_t *
+find_best_peer(const char *device, remote_fencing_op_t * op, enum find_best_peer_options options)
+{
+ GList *iter = NULL;
+ gboolean verified_devices_only = (options & FIND_PEER_VERIFIED_ONLY) ? TRUE : FALSE;
+
+ if (!device && pcmk_is_set(op->call_options, st_opt_topology)) {
+ return NULL;
+ }
+
+ for (iter = op->query_results; iter != NULL; iter = iter->next) {
+ peer_device_info_t *peer = iter->data;
+
+ crm_trace("Testing result from %s targeting %s with %d device%s: %d %x",
+ peer->host, op->target, peer->ndevices,
+ pcmk__plural_s(peer->ndevices), peer->tried, options);
+ if ((options & FIND_PEER_SKIP_TARGET) && pcmk__str_eq(peer->host, op->target, pcmk__str_casei)) {
+ continue;
+ }
+ if ((options & FIND_PEER_TARGET_ONLY) && !pcmk__str_eq(peer->host, op->target, pcmk__str_casei)) {
+ continue;
+ }
+
+ if (pcmk_is_set(op->call_options, st_opt_topology)) {
+
+ if (grab_peer_device(op, peer, device, verified_devices_only)) {
+ return peer;
+ }
+
+ } else if (!peer->tried
+ && count_peer_devices(op, peer, verified_devices_only,
+ fenced_support_flag(op->action))) {
+ /* No topology: Use the current best peer */
+ crm_trace("Simple fencing");
+ return peer;
+ }
+ }
+
+ return NULL;
+}
+
+static peer_device_info_t *
+stonith_choose_peer(remote_fencing_op_t * op)
+{
+ const char *device = NULL;
+ peer_device_info_t *peer = NULL;
+ uint32_t active = fencing_active_peers();
+
+ do {
+ if (op->devices) {
+ device = op->devices->data;
+ crm_trace("Checking for someone to fence (%s) %s using %s",
+ op->action, op->target, device);
+ } else {
+ crm_trace("Checking for someone to fence (%s) %s",
+ op->action, op->target);
+ }
+
+ /* Best choice is a peer other than the target with verified access */
+ peer = find_best_peer(device, op, FIND_PEER_SKIP_TARGET|FIND_PEER_VERIFIED_ONLY);
+ if (peer) {
+ crm_trace("Found verified peer %s for %s", peer->host, device?device:"<any>");
+ return peer;
+ }
+
+ if(op->query_timer != 0 && op->replies < QB_MIN(op->replies_expected, active)) {
+ crm_trace("Waiting before looking for unverified devices to fence %s", op->target);
+ return NULL;
+ }
+
+ /* If no other peer has verified access, next best is unverified access */
+ peer = find_best_peer(device, op, FIND_PEER_SKIP_TARGET);
+ if (peer) {
+ crm_trace("Found best unverified peer %s", peer->host);
+ return peer;
+ }
+
+ /* If no other peer can do it, last option is self-fencing
+ * (which is never allowed for the "on" phase of a remapped reboot)
+ */
+ if (op->phase != st_phase_on) {
+ peer = find_best_peer(device, op, FIND_PEER_TARGET_ONLY);
+ if (peer) {
+ crm_trace("%s will fence itself", peer->host);
+ return peer;
+ }
+ }
+
+ /* Try the next fencing level if there is one (unless we're in the "on"
+ * phase of a remapped "reboot", because we ignore errors in that case)
+ */
+ } while ((op->phase != st_phase_on)
+ && pcmk_is_set(op->call_options, st_opt_topology)
+ && (advance_topology_level(op, false) == pcmk_rc_ok));
+
+ if ((stonith_watchdog_timeout_ms > 0)
+ && pcmk__is_fencing_action(op->action)
+ && pcmk__str_eq(device, STONITH_WATCHDOG_ID, pcmk__str_none)
+ && node_does_watchdog_fencing(op->target)) {
+ crm_info("Couldn't contact watchdog-fencing target-node (%s)",
+ op->target);
+ /* check_watchdog_fencing_and_wait will log additional info */
+ } else {
+ crm_notice("Couldn't find anyone to fence (%s) %s using %s",
+ op->action, op->target, (device? device : "any device"));
+ }
+ return NULL;
+}
+
+static int
+get_device_timeout(const remote_fencing_op_t *op,
+ const peer_device_info_t *peer, const char *device,
+ bool with_delay)
+{
+ device_properties_t *props;
+ int delay = 0;
+
+ if (!peer || !device) {
+ return op->base_timeout;
+ }
+
+ props = g_hash_table_lookup(peer->devices, device);
+ if (!props) {
+ return op->base_timeout;
+ }
+
+ // op->delay < 0 means disable any static/random fencing delays
+ if (with_delay && op->delay >= 0) {
+ // delay_base is eventually limited by delay_max
+ delay = (props->delay_max[op->phase] > 0 ?
+ props->delay_max[op->phase] : props->delay_base[op->phase]);
+ }
+
+ return (props->custom_action_timeout[op->phase]?
+ props->custom_action_timeout[op->phase] : op->base_timeout)
+ + delay;
+}
+
+struct timeout_data {
+ const remote_fencing_op_t *op;
+ const peer_device_info_t *peer;
+ int total_timeout;
+};
+
+/*!
+ * \internal
+ * \brief Add timeout to a total if device has not been executed yet
+ *
+ * \param[in] key GHashTable key (device ID)
+ * \param[in] value GHashTable value (device properties)
+ * \param[in,out] user_data Timeout data
+ */
+static void
+add_device_timeout(gpointer key, gpointer value, gpointer user_data)
+{
+ const char *device_id = key;
+ device_properties_t *props = value;
+ struct timeout_data *timeout = user_data;
+
+ if (!props->executed[timeout->op->phase]
+ && !props->disallowed[timeout->op->phase]) {
+ timeout->total_timeout += get_device_timeout(timeout->op, timeout->peer,
+ device_id, true);
+ }
+}
+
+static int
+get_peer_timeout(const remote_fencing_op_t *op, const peer_device_info_t *peer)
+{
+ struct timeout_data timeout;
+
+ timeout.op = op;
+ timeout.peer = peer;
+ timeout.total_timeout = 0;
+
+ g_hash_table_foreach(peer->devices, add_device_timeout, &timeout);
+
+ return (timeout.total_timeout? timeout.total_timeout : op->base_timeout);
+}
+
+static int
+get_op_total_timeout(const remote_fencing_op_t *op,
+ const peer_device_info_t *chosen_peer)
+{
+ int total_timeout = 0;
+ stonith_topology_t *tp = find_topology_for_host(op->target);
+
+ if (pcmk_is_set(op->call_options, st_opt_topology) && tp) {
+ int i;
+ GList *device_list = NULL;
+ GList *iter = NULL;
+ GList *auto_list = NULL;
+
+ if (pcmk__str_eq(op->action, "on", pcmk__str_none)
+ && (op->automatic_list != NULL)) {
+ auto_list = g_list_copy(op->automatic_list);
+ }
+
+ /* Yep, this looks scary, nested loops all over the place.
+ * Here is what is going on.
+ * Loop1: Iterate through fencing levels.
+ * Loop2: If a fencing level has devices, loop through each device
+ * Loop3: For each device in a fencing level, see what peer owns it
+ * and what that peer has reported the timeout is for the device.
+ */
+ for (i = 0; i < ST_LEVEL_MAX; i++) {
+ if (!tp->levels[i]) {
+ continue;
+ }
+ for (device_list = tp->levels[i]; device_list; device_list = device_list->next) {
+ /* in case of watchdog-device we add the timeout to the budget
+ regardless of if we got a reply or not
+ */
+ if ((stonith_watchdog_timeout_ms > 0)
+ && pcmk__is_fencing_action(op->action)
+ && pcmk__str_eq(device_list->data, STONITH_WATCHDOG_ID,
+ pcmk__str_none)
+ && node_does_watchdog_fencing(op->target)) {
+ total_timeout += stonith_watchdog_timeout_ms / 1000;
+ continue;
+ }
+
+ for (iter = op->query_results; iter != NULL; iter = iter->next) {
+ const peer_device_info_t *peer = iter->data;
+
+ if (auto_list) {
+ GList *match = g_list_find_custom(auto_list, device_list->data,
+ sort_strings);
+ if (match) {
+ auto_list = g_list_remove(auto_list, match->data);
+ }
+ }
+
+ if (find_peer_device(op, peer, device_list->data,
+ fenced_support_flag(op->action))) {
+ total_timeout += get_device_timeout(op, peer,
+ device_list->data,
+ true);
+ break;
+ }
+ } /* End Loop3: match device with peer that owns device, find device's timeout period */
+ } /* End Loop2: iterate through devices at a specific level */
+ } /*End Loop1: iterate through fencing levels */
+
+ //Add only exists automatic_list device timeout
+ if (auto_list) {
+ for (iter = auto_list; iter != NULL; iter = iter->next) {
+ GList *iter2 = NULL;
+
+ for (iter2 = op->query_results; iter2 != NULL; iter = iter2->next) {
+ peer_device_info_t *peer = iter2->data;
+ if (find_peer_device(op, peer, iter->data, st_device_supports_on)) {
+ total_timeout += get_device_timeout(op, peer,
+ iter->data, true);
+ break;
+ }
+ }
+ }
+ }
+
+ g_list_free(auto_list);
+
+ } else if (chosen_peer) {
+ total_timeout = get_peer_timeout(op, chosen_peer);
+ } else {
+ total_timeout = op->base_timeout;
+ }
+
+ /* Take any requested fencing delay into account to prevent it from eating
+ * up the total timeout.
+ */
+ return ((total_timeout ? total_timeout : op->base_timeout)
+ + (op->delay > 0 ? op->delay : 0));
+}
+
+static void
+report_timeout_period(remote_fencing_op_t * op, int op_timeout)
+{
+ GList *iter = NULL;
+ xmlNode *update = NULL;
+ const char *client_node = NULL;
+ const char *client_id = NULL;
+ const char *call_id = NULL;
+
+ if (op->call_options & st_opt_sync_call) {
+ /* There is no reason to report the timeout for a synchronous call. It
+ * is impossible to use the reported timeout to do anything when the client
+ * is blocking for the response. This update is only important for
+ * async calls that require a callback to report the results in. */
+ return;
+ } else if (!op->request) {
+ return;
+ }
+
+ crm_trace("Reporting timeout for %s (id=%.8s)", op->client_name, op->id);
+ client_node = crm_element_value(op->request, F_STONITH_CLIENTNODE);
+ call_id = crm_element_value(op->request, F_STONITH_CALLID);
+ client_id = crm_element_value(op->request, F_STONITH_CLIENTID);
+ if (!client_node || !call_id || !client_id) {
+ return;
+ }
+
+ if (pcmk__str_eq(client_node, stonith_our_uname, pcmk__str_casei)) {
+ // Client is connected to this node, so send update directly to them
+ do_stonith_async_timeout_update(client_id, call_id, op_timeout);
+ return;
+ }
+
+ /* The client is connected to another node, relay this update to them */
+ update = stonith_create_op(op->client_callid, op->id, STONITH_OP_TIMEOUT_UPDATE, NULL, 0);
+ crm_xml_add(update, F_STONITH_REMOTE_OP_ID, op->id);
+ crm_xml_add(update, F_STONITH_CLIENTID, client_id);
+ crm_xml_add(update, F_STONITH_CALLID, call_id);
+ crm_xml_add_int(update, F_STONITH_TIMEOUT, op_timeout);
+
+ send_cluster_message(crm_get_peer(0, client_node), crm_msg_stonith_ng, update, FALSE);
+
+ free_xml(update);
+
+ for (iter = op->duplicates; iter != NULL; iter = iter->next) {
+ remote_fencing_op_t *dup = iter->data;
+
+ crm_trace("Reporting timeout for duplicate %.8s to client %s",
+ dup->id, dup->client_name);
+ report_timeout_period(iter->data, op_timeout);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Advance an operation to the next device in its topology
+ *
+ * \param[in,out] op Fencer operation to advance
+ * \param[in] device ID of device that just completed
+ * \param[in,out] msg If not NULL, XML reply of last delegated operation
+ */
+static void
+advance_topology_device_in_level(remote_fencing_op_t *op, const char *device,
+ xmlNode *msg)
+{
+ /* Advance to the next device at this topology level, if any */
+ if (op->devices) {
+ op->devices = op->devices->next;
+ }
+
+ /* Handle automatic unfencing if an "on" action was requested */
+ if ((op->phase == st_phase_requested)
+ && pcmk__str_eq(op->action, "on", pcmk__str_none)) {
+ /* If the device we just executed was required, it's not anymore */
+ remove_required_device(op, device);
+
+ /* If there are no more devices at this topology level, run through any
+ * remaining devices with automatic unfencing
+ */
+ if (op->devices == NULL) {
+ op->devices = op->automatic_list;
+ }
+ }
+
+ if ((op->devices == NULL) && (op->phase == st_phase_off)) {
+ /* We're done with this level and with required devices, but we had
+ * remapped "reboot" to "off", so start over with "on". If any devices
+ * need to be turned back on, op->devices will be non-NULL after this.
+ */
+ op_phase_on(op);
+ }
+
+ // This function is only called if the previous device succeeded
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+
+ if (op->devices) {
+ /* Necessary devices remain, so execute the next one */
+ crm_trace("Next targeting %s on behalf of %s@%s",
+ op->target, op->client_name, op->originator);
+
+ // The requested delay has been applied for the first device
+ if (op->delay > 0) {
+ op->delay = 0;
+ }
+
+ request_peer_fencing(op, NULL);
+ } else {
+ /* We're done with all devices and phases, so finalize operation */
+ crm_trace("Marking complex fencing op targeting %s as complete",
+ op->target);
+ op->state = st_done;
+ finalize_op(op, msg, false);
+ }
+}
+
+static gboolean
+check_watchdog_fencing_and_wait(remote_fencing_op_t * op)
+{
+ if (node_does_watchdog_fencing(op->target)) {
+
+ crm_notice("Waiting %lds for %s to self-fence (%s) for "
+ "client %s " CRM_XS " id=%.8s",
+ (stonith_watchdog_timeout_ms / 1000),
+ op->target, op->action, op->client_name, op->id);
+
+ if (op->op_timer_one) {
+ g_source_remove(op->op_timer_one);
+ }
+ op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms,
+ remote_op_watchdog_done, op);
+ return TRUE;
+ } else {
+ crm_debug("Skipping fallback to watchdog-fencing as %s is "
+ "not in host-list", op->target);
+ }
+ return FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Ask a peer to execute a fencing operation
+ *
+ * \param[in,out] op Fencing operation to be executed
+ * \param[in,out] peer If NULL or topology is in use, choose best peer to
+ * execute the fencing, otherwise use this peer
+ */
+static void
+request_peer_fencing(remote_fencing_op_t *op, peer_device_info_t *peer)
+{
+ const char *device = NULL;
+ int timeout;
+
+ CRM_CHECK(op != NULL, return);
+
+ crm_trace("Action %.8s targeting %s for %s is %s",
+ op->id, op->target, op->client_name,
+ stonith_op_state_str(op->state));
+
+ if ((op->phase == st_phase_on) && (op->devices != NULL)) {
+ /* We are in the "on" phase of a remapped topology reboot. If this
+ * device has pcmk_reboot_action="off", or doesn't support the "on"
+ * action, skip it.
+ *
+ * We can't check device properties at this point because we haven't
+ * chosen a peer for this stage yet. Instead, we check the local node's
+ * knowledge about the device. If different versions of the fence agent
+ * are installed on different nodes, there's a chance this could be
+ * mistaken, but the worst that could happen is we don't try turning the
+ * node back on when we should.
+ */
+ device = op->devices->data;
+ if (pcmk__str_eq(fenced_device_reboot_action(device), "off",
+ pcmk__str_none)) {
+ crm_info("Not turning %s back on using %s because the device is "
+ "configured to stay off (pcmk_reboot_action='off')",
+ op->target, device);
+ advance_topology_device_in_level(op, device, NULL);
+ return;
+ }
+ if (!fenced_device_supports_on(device)) {
+ crm_info("Not turning %s back on using %s because the agent "
+ "doesn't support 'on'", op->target, device);
+ advance_topology_device_in_level(op, device, NULL);
+ return;
+ }
+ }
+
+ timeout = op->base_timeout;
+ if ((peer == NULL) && !pcmk_is_set(op->call_options, st_opt_topology)) {
+ peer = stonith_choose_peer(op);
+ }
+
+ if (!op->op_timer_total) {
+ op->total_timeout = TIMEOUT_MULTIPLY_FACTOR * get_op_total_timeout(op, peer);
+ op->op_timer_total = g_timeout_add(1000 * op->total_timeout, remote_op_timeout, op);
+ report_timeout_period(op, op->total_timeout);
+ crm_info("Total timeout set to %d for peer's fencing targeting %s for %s"
+ CRM_XS "id=%.8s",
+ op->total_timeout, op->target, op->client_name, op->id);
+ }
+
+ if (pcmk_is_set(op->call_options, st_opt_topology) && op->devices) {
+ /* Ignore the caller's peer preference if topology is in use, because
+ * that peer might not have access to the required device. With
+ * topology, stonith_choose_peer() removes the device from further
+ * consideration, so the timeout must be calculated beforehand.
+ *
+ * @TODO Basing the total timeout on the caller's preferred peer (above)
+ * is less than ideal.
+ */
+ peer = stonith_choose_peer(op);
+
+ device = op->devices->data;
+ /* Fencing timeout sent to peer takes no delay into account.
+ * The peer will add a dedicated timer for any delay upon
+ * schedule_stonith_command().
+ */
+ timeout = get_device_timeout(op, peer, device, false);
+ }
+
+ if (peer) {
+ /* Take any requested fencing delay into account to prevent it from eating
+ * up the timeout.
+ */
+ int timeout_one = (op->delay > 0 ?
+ TIMEOUT_MULTIPLY_FACTOR * op->delay : 0);
+ xmlNode *remote_op = stonith_create_op(op->client_callid, op->id, STONITH_OP_FENCE, NULL, 0);
+
+ crm_xml_add(remote_op, F_STONITH_REMOTE_OP_ID, op->id);
+ crm_xml_add(remote_op, F_STONITH_TARGET, op->target);
+ crm_xml_add(remote_op, F_STONITH_ACTION, op->action);
+ crm_xml_add(remote_op, F_STONITH_ORIGIN, op->originator);
+ crm_xml_add(remote_op, F_STONITH_CLIENTID, op->client_id);
+ crm_xml_add(remote_op, F_STONITH_CLIENTNAME, op->client_name);
+ crm_xml_add_int(remote_op, F_STONITH_TIMEOUT, timeout);
+ crm_xml_add_int(remote_op, F_STONITH_CALLOPTS, op->call_options);
+ crm_xml_add_int(remote_op, F_STONITH_DELAY, op->delay);
+
+ if (device) {
+ timeout_one += TIMEOUT_MULTIPLY_FACTOR *
+ get_device_timeout(op, peer, device, true);
+ crm_notice("Requesting that %s perform '%s' action targeting %s "
+ "using %s " CRM_XS " for client %s (%ds)",
+ peer->host, op->action, op->target, device,
+ op->client_name, timeout_one);
+ crm_xml_add(remote_op, F_STONITH_DEVICE, device);
+
+ } else {
+ timeout_one += TIMEOUT_MULTIPLY_FACTOR * get_peer_timeout(op, peer);
+ crm_notice("Requesting that %s perform '%s' action targeting %s "
+ CRM_XS " for client %s (%ds, %lds)",
+ peer->host, op->action, op->target, op->client_name,
+ timeout_one, stonith_watchdog_timeout_ms);
+ }
+
+ op->state = st_exec;
+ if (op->op_timer_one) {
+ g_source_remove(op->op_timer_one);
+ op->op_timer_one = 0;
+ }
+
+ if (!((stonith_watchdog_timeout_ms > 0)
+ && (pcmk__str_eq(device, STONITH_WATCHDOG_ID, pcmk__str_none)
+ || (pcmk__str_eq(peer->host, op->target, pcmk__str_casei)
+ && pcmk__is_fencing_action(op->action)))
+ && check_watchdog_fencing_and_wait(op))) {
+
+ /* Some thoughts about self-fencing cases reaching this point:
+ - Actually check in check_watchdog_fencing_and_wait
+ shouldn't fail if STONITH_WATCHDOG_ID is
+ chosen as fencing-device and it being present implies
+ watchdog-fencing is enabled anyway
+ - If watchdog-fencing is disabled either in general or for
+ a specific target - detected in check_watchdog_fencing_and_wait -
+ for some other kind of self-fencing we can't expect
+ a success answer but timeout is fine if the node doesn't
+ come back in between
+ - Delicate might be the case where we have watchdog-fencing
+ enabled for a node but the watchdog-fencing-device isn't
+ explicitly chosen for suicide. Local pe-execution in sbd
+ may detect the node as unclean and lead to timely suicide.
+ Otherwise the selection of stonith-watchdog-timeout at
+ least is questionable.
+ */
+
+ /* coming here we're not waiting for watchdog timeout -
+ thus engage timer with timout evaluated before */
+ op->op_timer_one = g_timeout_add((1000 * timeout_one), remote_op_timeout_one, op);
+ }
+
+ send_cluster_message(crm_get_peer(0, peer->host), crm_msg_stonith_ng, remote_op, FALSE);
+ peer->tried = TRUE;
+ free_xml(remote_op);
+ return;
+
+ } else if (op->phase == st_phase_on) {
+ /* A remapped "on" cannot be executed, but the node was already
+ * turned off successfully, so ignore the error and continue.
+ */
+ crm_warn("Ignoring %s 'on' failure (no capable peers) targeting %s "
+ "after successful 'off'", device, op->target);
+ advance_topology_device_in_level(op, device, NULL);
+ return;
+
+ } else if (op->owner == FALSE) {
+ crm_err("Fencing (%s) targeting %s for client %s is not ours to control",
+ op->action, op->target, op->client_name);
+
+ } else if (op->query_timer == 0) {
+ /* We've exhausted all available peers */
+ crm_info("No remaining peers capable of fencing (%s) %s for client %s "
+ CRM_XS " state=%s", op->action, op->target, op->client_name,
+ stonith_op_state_str(op->state));
+ CRM_CHECK(op->state < st_done, return);
+ finalize_timed_out_op(op, "All nodes failed, or are unable, to "
+ "fence target");
+
+ } else if(op->replies >= op->replies_expected || op->replies >= fencing_active_peers()) {
+ /* if the operation never left the query state,
+ * but we have all the expected replies, then no devices
+ * are available to execute the fencing operation. */
+
+ if(stonith_watchdog_timeout_ms > 0 && pcmk__str_eq(device,
+ STONITH_WATCHDOG_ID, pcmk__str_null_matches)) {
+ if (check_watchdog_fencing_and_wait(op)) {
+ return;
+ }
+ }
+
+ if (op->state == st_query) {
+ crm_info("No peers (out of %d) have devices capable of fencing "
+ "(%s) %s for client %s " CRM_XS " state=%s",
+ op->replies, op->action, op->target, op->client_name,
+ stonith_op_state_str(op->state));
+
+ pcmk__reset_result(&op->result);
+ pcmk__set_result(&op->result, CRM_EX_ERROR,
+ PCMK_EXEC_NO_FENCE_DEVICE, NULL);
+ } else {
+ if (pcmk_is_set(op->call_options, st_opt_topology)) {
+ pcmk__reset_result(&op->result);
+ pcmk__set_result(&op->result, CRM_EX_ERROR,
+ PCMK_EXEC_NO_FENCE_DEVICE, NULL);
+ }
+ /* ... else use existing result from previous failed attempt
+ * (topology is not in use, and no devices remain to be attempted).
+ * Overwriting the result with PCMK_EXEC_NO_FENCE_DEVICE would
+ * prevent finalize_op() from setting the correct delegate if
+ * needed.
+ */
+
+ crm_info("No peers (out of %d) are capable of fencing (%s) %s "
+ "for client %s " CRM_XS " state=%s",
+ op->replies, op->action, op->target, op->client_name,
+ stonith_op_state_str(op->state));
+ }
+
+ op->state = st_failed;
+ finalize_op(op, NULL, false);
+
+ } else {
+ crm_info("Waiting for additional peers capable of fencing (%s) %s%s%s "
+ "for client %s " CRM_XS " id=%.8s",
+ op->action, op->target, (device? " using " : ""),
+ (device? device : ""), op->client_name, op->id);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Comparison function for sorting query results
+ *
+ * \param[in] a GList item to compare
+ * \param[in] b GList item to compare
+ *
+ * \return Per the glib documentation, "a negative integer if the first value
+ * comes before the second, 0 if they are equal, or a positive integer
+ * if the first value comes after the second."
+ */
+static gint
+sort_peers(gconstpointer a, gconstpointer b)
+{
+ const peer_device_info_t *peer_a = a;
+ const peer_device_info_t *peer_b = b;
+
+ return (peer_b->ndevices - peer_a->ndevices);
+}
+
+/*!
+ * \internal
+ * \brief Determine if all the devices in the topology are found or not
+ *
+ * \param[in] op Fencing operation with topology to check
+ */
+static gboolean
+all_topology_devices_found(const remote_fencing_op_t *op)
+{
+ GList *device = NULL;
+ GList *iter = NULL;
+ device_properties_t *match = NULL;
+ stonith_topology_t *tp = NULL;
+ gboolean skip_target = FALSE;
+ int i;
+
+ tp = find_topology_for_host(op->target);
+ if (!tp) {
+ return FALSE;
+ }
+ if (pcmk__is_fencing_action(op->action)) {
+ /* Don't count the devices on the target node if we are killing
+ * the target node. */
+ skip_target = TRUE;
+ }
+
+ for (i = 0; i < ST_LEVEL_MAX; i++) {
+ for (device = tp->levels[i]; device; device = device->next) {
+ match = NULL;
+ for (iter = op->query_results; iter && !match; iter = iter->next) {
+ peer_device_info_t *peer = iter->data;
+
+ if (skip_target && pcmk__str_eq(peer->host, op->target, pcmk__str_casei)) {
+ continue;
+ }
+ match = find_peer_device(op, peer, device->data, st_device_supports_none);
+ }
+ if (!match) {
+ return FALSE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Parse action-specific device properties from XML
+ *
+ * \param[in] xml XML element containing the properties
+ * \param[in] peer Name of peer that sent XML (for logs)
+ * \param[in] device Device ID (for logs)
+ * \param[in] action Action the properties relate to (for logs)
+ * \param[in,out] op Fencing operation that properties are being parsed for
+ * \param[in] phase Phase the properties relate to
+ * \param[in,out] props Device properties to update
+ */
+static void
+parse_action_specific(const xmlNode *xml, const char *peer, const char *device,
+ const char *action, remote_fencing_op_t *op,
+ enum st_remap_phase phase, device_properties_t *props)
+{
+ props->custom_action_timeout[phase] = 0;
+ crm_element_value_int(xml, F_STONITH_ACTION_TIMEOUT,
+ &props->custom_action_timeout[phase]);
+ if (props->custom_action_timeout[phase]) {
+ crm_trace("Peer %s with device %s returned %s action timeout %d",
+ peer, device, action, props->custom_action_timeout[phase]);
+ }
+
+ props->delay_max[phase] = 0;
+ crm_element_value_int(xml, F_STONITH_DELAY_MAX, &props->delay_max[phase]);
+ if (props->delay_max[phase]) {
+ crm_trace("Peer %s with device %s returned maximum of random delay %d for %s",
+ peer, device, props->delay_max[phase], action);
+ }
+
+ props->delay_base[phase] = 0;
+ crm_element_value_int(xml, F_STONITH_DELAY_BASE, &props->delay_base[phase]);
+ if (props->delay_base[phase]) {
+ crm_trace("Peer %s with device %s returned base delay %d for %s",
+ peer, device, props->delay_base[phase], action);
+ }
+
+ /* Handle devices with automatic unfencing */
+ if (pcmk__str_eq(action, "on", pcmk__str_none)) {
+ int required = 0;
+
+ crm_element_value_int(xml, F_STONITH_DEVICE_REQUIRED, &required);
+ if (required) {
+ crm_trace("Peer %s requires device %s to execute for action %s",
+ peer, device, action);
+ add_required_device(op, device);
+ }
+ }
+
+ /* If a reboot is remapped to off+on, it's possible that a node is allowed
+ * to perform one action but not another.
+ */
+ if (pcmk__xe_attr_is_true(xml, F_STONITH_ACTION_DISALLOWED)) {
+ props->disallowed[phase] = TRUE;
+ crm_trace("Peer %s is disallowed from executing %s for device %s",
+ peer, action, device);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Parse one device's properties from peer's XML query reply
+ *
+ * \param[in] xml XML node containing device properties
+ * \param[in,out] op Operation that query and reply relate to
+ * \param[in,out] peer Peer's device information
+ * \param[in] device ID of device being parsed
+ */
+static void
+add_device_properties(const xmlNode *xml, remote_fencing_op_t *op,
+ peer_device_info_t *peer, const char *device)
+{
+ xmlNode *child;
+ int verified = 0;
+ device_properties_t *props = calloc(1, sizeof(device_properties_t));
+ int flags = st_device_supports_on; /* Old nodes that don't set the flag assume they support the on action */
+
+ /* Add a new entry to this peer's devices list */
+ CRM_ASSERT(props != NULL);
+ g_hash_table_insert(peer->devices, strdup(device), props);
+
+ /* Peers with verified (monitored) access will be preferred */
+ crm_element_value_int(xml, F_STONITH_DEVICE_VERIFIED, &verified);
+ if (verified) {
+ crm_trace("Peer %s has confirmed a verified device %s",
+ peer->host, device);
+ props->verified = TRUE;
+ }
+
+ crm_element_value_int(xml, F_STONITH_DEVICE_SUPPORT_FLAGS, &flags);
+ props->device_support_flags = flags;
+
+ /* Parse action-specific device properties */
+ parse_action_specific(xml, peer->host, device, op_requested_action(op),
+ op, st_phase_requested, props);
+ for (child = pcmk__xml_first_child(xml); child != NULL;
+ child = pcmk__xml_next(child)) {
+ /* Replies for "reboot" operations will include the action-specific
+ * values for "off" and "on" in child elements, just in case the reboot
+ * winds up getting remapped.
+ */
+ if (pcmk__str_eq(ID(child), "off", pcmk__str_none)) {
+ parse_action_specific(child, peer->host, device, "off",
+ op, st_phase_off, props);
+ } else if (pcmk__str_eq(ID(child), "on", pcmk__str_none)) {
+ parse_action_specific(child, peer->host, device, "on",
+ op, st_phase_on, props);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Parse a peer's XML query reply and add it to operation's results
+ *
+ * \param[in,out] op Operation that query and reply relate to
+ * \param[in] host Name of peer that sent this reply
+ * \param[in] ndevices Number of devices expected in reply
+ * \param[in] xml XML node containing device list
+ *
+ * \return Newly allocated result structure with parsed reply
+ */
+static peer_device_info_t *
+add_result(remote_fencing_op_t *op, const char *host, int ndevices,
+ const xmlNode *xml)
+{
+ peer_device_info_t *peer = calloc(1, sizeof(peer_device_info_t));
+ xmlNode *child;
+
+ // cppcheck seems not to understand the abort logic in CRM_CHECK
+ // cppcheck-suppress memleak
+ CRM_CHECK(peer != NULL, return NULL);
+ peer->host = strdup(host);
+ peer->devices = pcmk__strkey_table(free, free);
+
+ /* Each child element describes one capable device available to the peer */
+ for (child = pcmk__xml_first_child(xml); child != NULL;
+ child = pcmk__xml_next(child)) {
+ const char *device = ID(child);
+
+ if (device) {
+ add_device_properties(child, op, peer, device);
+ }
+ }
+
+ peer->ndevices = g_hash_table_size(peer->devices);
+ CRM_CHECK(ndevices == peer->ndevices,
+ crm_err("Query claimed to have %d device%s but %d found",
+ ndevices, pcmk__plural_s(ndevices), peer->ndevices));
+
+ op->query_results = g_list_insert_sorted(op->query_results, peer, sort_peers);
+ return peer;
+}
+
+/*!
+ * \internal
+ * \brief Handle a peer's reply to our fencing query
+ *
+ * Parse a query result from XML and store it in the remote operation
+ * table, and when enough replies have been received, issue a fencing request.
+ *
+ * \param[in] msg XML reply received
+ *
+ * \return pcmk_ok on success, -errno on error
+ *
+ * \note See initiate_remote_stonith_op() for how the XML query was initially
+ * formed, and stonith_query() for how the peer formed its XML reply.
+ */
+int
+process_remote_stonith_query(xmlNode *msg)
+{
+ int ndevices = 0;
+ gboolean host_is_target = FALSE;
+ gboolean have_all_replies = FALSE;
+ const char *id = NULL;
+ const char *host = NULL;
+ remote_fencing_op_t *op = NULL;
+ peer_device_info_t *peer = NULL;
+ uint32_t replies_expected;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_REMOTE_OP_ID, msg, LOG_ERR);
+
+ CRM_CHECK(dev != NULL, return -EPROTO);
+
+ id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
+ CRM_CHECK(id != NULL, return -EPROTO);
+
+ dev = get_xpath_object("//@" F_STONITH_AVAILABLE_DEVICES, msg, LOG_ERR);
+ CRM_CHECK(dev != NULL, return -EPROTO);
+ crm_element_value_int(dev, F_STONITH_AVAILABLE_DEVICES, &ndevices);
+
+ op = g_hash_table_lookup(stonith_remote_op_list, id);
+ if (op == NULL) {
+ crm_debug("Received query reply for unknown or expired operation %s",
+ id);
+ return -EOPNOTSUPP;
+ }
+
+ replies_expected = fencing_active_peers();
+ if (op->replies_expected < replies_expected) {
+ replies_expected = op->replies_expected;
+ }
+ if ((++op->replies >= replies_expected) && (op->state == st_query)) {
+ have_all_replies = TRUE;
+ }
+ host = crm_element_value(msg, F_ORIG);
+ host_is_target = pcmk__str_eq(host, op->target, pcmk__str_casei);
+
+ crm_info("Query result %d of %d from %s for %s/%s (%d device%s) %s",
+ op->replies, replies_expected, host,
+ op->target, op->action, ndevices, pcmk__plural_s(ndevices), id);
+ if (ndevices > 0) {
+ peer = add_result(op, host, ndevices, dev);
+ }
+
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+
+ if (pcmk_is_set(op->call_options, st_opt_topology)) {
+ /* If we start the fencing before all the topology results are in,
+ * it is possible fencing levels will be skipped because of the missing
+ * query results. */
+ if (op->state == st_query && all_topology_devices_found(op)) {
+ /* All the query results are in for the topology, start the fencing ops. */
+ crm_trace("All topology devices found");
+ request_peer_fencing(op, peer);
+
+ } else if (have_all_replies) {
+ crm_info("All topology query replies have arrived, continuing (%d expected/%d received) ",
+ replies_expected, op->replies);
+ request_peer_fencing(op, NULL);
+ }
+
+ } else if (op->state == st_query) {
+ int nverified = count_peer_devices(op, peer, TRUE,
+ fenced_support_flag(op->action));
+
+ /* We have a result for a non-topology fencing op that looks promising,
+ * go ahead and start fencing before query timeout */
+ if ((peer != NULL) && !host_is_target && nverified) {
+ /* we have a verified device living on a peer that is not the target */
+ crm_trace("Found %d verified device%s",
+ nverified, pcmk__plural_s(nverified));
+ request_peer_fencing(op, peer);
+
+ } else if (have_all_replies) {
+ crm_info("All query replies have arrived, continuing (%d expected/%d received) ",
+ replies_expected, op->replies);
+ request_peer_fencing(op, NULL);
+
+ } else {
+ crm_trace("Waiting for more peer results before launching fencing operation");
+ }
+
+ } else if ((peer != NULL) && (op->state == st_done)) {
+ crm_info("Discarding query result from %s (%d device%s): "
+ "Operation is %s", peer->host,
+ peer->ndevices, pcmk__plural_s(peer->ndevices),
+ stonith_op_state_str(op->state));
+ }
+
+ return pcmk_ok;
+}
+
+/*!
+ * \internal
+ * \brief Handle a peer's reply to a fencing request
+ *
+ * Parse a fencing reply from XML, and either finalize the operation
+ * or attempt another device as appropriate.
+ *
+ * \param[in] msg XML reply received
+ */
+void
+fenced_process_fencing_reply(xmlNode *msg)
+{
+ const char *id = NULL;
+ const char *device = NULL;
+ remote_fencing_op_t *op = NULL;
+ xmlNode *dev = get_xpath_object("//@" F_STONITH_REMOTE_OP_ID, msg, LOG_ERR);
+ pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+
+ CRM_CHECK(dev != NULL, return);
+
+ id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
+ CRM_CHECK(id != NULL, return);
+
+ dev = stonith__find_xe_with_result(msg);
+ CRM_CHECK(dev != NULL, return);
+
+ stonith__xe_get_result(dev, &result);
+
+ device = crm_element_value(dev, F_STONITH_DEVICE);
+
+ if (stonith_remote_op_list) {
+ op = g_hash_table_lookup(stonith_remote_op_list, id);
+ }
+
+ if ((op == NULL) && pcmk__result_ok(&result)) {
+ /* Record successful fencing operations */
+ const char *client_id = crm_element_value(dev, F_STONITH_CLIENTID);
+
+ op = create_remote_stonith_op(client_id, dev, TRUE);
+ }
+
+ if (op == NULL) {
+ /* Could be for an event that began before we started */
+ /* TODO: Record the op for later querying */
+ crm_info("Received peer result of unknown or expired operation %s", id);
+ pcmk__reset_result(&result);
+ return;
+ }
+
+ pcmk__reset_result(&op->result);
+ op->result = result; // The operation takes ownership of the result
+
+ if (op->devices && device && !pcmk__str_eq(op->devices->data, device, pcmk__str_casei)) {
+ crm_err("Received outdated reply for device %s (instead of %s) to "
+ "fence (%s) %s. Operation already timed out at peer level.",
+ device, (const char *) op->devices->data, op->action, op->target);
+ return;
+ }
+
+ if (pcmk__str_eq(crm_element_value(msg, F_SUBTYPE), "broadcast", pcmk__str_casei)) {
+ if (pcmk__result_ok(&op->result)) {
+ op->state = st_done;
+ } else {
+ op->state = st_failed;
+ }
+ finalize_op(op, msg, false);
+ return;
+
+ } else if (!pcmk__str_eq(op->originator, stonith_our_uname, pcmk__str_casei)) {
+ /* If this isn't a remote level broadcast, and we are not the
+ * originator of the operation, we should not be receiving this msg. */
+ crm_err("Received non-broadcast fencing result for operation %.8s "
+ "we do not own (device %s targeting %s)",
+ op->id, device, op->target);
+ return;
+ }
+
+ if (pcmk_is_set(op->call_options, st_opt_topology)) {
+ const char *device = NULL;
+ const char *reason = op->result.exit_reason;
+
+ /* We own the op, and it is complete. broadcast the result to all nodes
+ * and notify our local clients. */
+ if (op->state == st_done) {
+ finalize_op(op, msg, false);
+ return;
+ }
+
+ device = crm_element_value(msg, F_STONITH_DEVICE);
+
+ if ((op->phase == 2) && !pcmk__result_ok(&op->result)) {
+ /* A remapped "on" failed, but the node was already turned off
+ * successfully, so ignore the error and continue.
+ */
+ crm_warn("Ignoring %s 'on' failure (%s%s%s) targeting %s "
+ "after successful 'off'",
+ device, pcmk_exec_status_str(op->result.execution_status),
+ (reason == NULL)? "" : ": ",
+ (reason == NULL)? "" : reason,
+ op->target);
+ pcmk__set_result(&op->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
+ } else {
+ crm_notice("Action '%s' targeting %s%s%s on behalf of %s@%s: "
+ "%s%s%s%s",
+ op->action, op->target,
+ ((device == NULL)? "" : " using "),
+ ((device == NULL)? "" : device),
+ op->client_name,
+ op->originator,
+ pcmk_exec_status_str(op->result.execution_status),
+ (reason == NULL)? "" : " (",
+ (reason == NULL)? "" : reason,
+ (reason == NULL)? "" : ")");
+ }
+
+ if (pcmk__result_ok(&op->result)) {
+ /* An operation completed successfully. Try another device if
+ * necessary, otherwise mark the operation as done. */
+ advance_topology_device_in_level(op, device, msg);
+ return;
+ } else {
+ /* This device failed, time to try another topology level. If no other
+ * levels are available, mark this operation as failed and report results. */
+ if (advance_topology_level(op, false) != pcmk_rc_ok) {
+ op->state = st_failed;
+ finalize_op(op, msg, false);
+ return;
+ }
+ }
+
+ } else if (pcmk__result_ok(&op->result) && (op->devices == NULL)) {
+ op->state = st_done;
+ finalize_op(op, msg, false);
+ return;
+
+ } else if ((op->result.execution_status == PCMK_EXEC_TIMEOUT)
+ && (op->devices == NULL)) {
+ /* If the operation timed out don't bother retrying other peers. */
+ op->state = st_failed;
+ finalize_op(op, msg, false);
+ return;
+
+ } else {
+ /* fall-through and attempt other fencing action using another peer */
+ }
+
+ /* Retry on failure */
+ crm_trace("Next for %s on behalf of %s@%s (result was: %s)",
+ op->target, op->originator, op->client_name,
+ pcmk_exec_status_str(op->result.execution_status));
+ request_peer_fencing(op, NULL);
+}
+
+gboolean
+stonith_check_fence_tolerance(int tolerance, const char *target, const char *action)
+{
+ GHashTableIter iter;
+ time_t now = time(NULL);
+ remote_fencing_op_t *rop = NULL;
+
+ if (tolerance <= 0 || !stonith_remote_op_list || target == NULL ||
+ action == NULL) {
+ return FALSE;
+ }
+
+ g_hash_table_iter_init(&iter, stonith_remote_op_list);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&rop)) {
+ if (strcmp(rop->target, target) != 0) {
+ continue;
+ } else if (rop->state != st_done) {
+ continue;
+ /* We don't have to worry about remapped reboots here
+ * because if state is done, any remapping has been undone
+ */
+ } else if (strcmp(rop->action, action) != 0) {
+ continue;
+ } else if ((rop->completed + tolerance) < now) {
+ continue;
+ }
+
+ crm_notice("Target %s was fenced (%s) less than %ds ago by %s on behalf of %s",
+ target, action, tolerance, rop->delegate, rop->originator);
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c
new file mode 100644
index 0000000..4edda6c
--- /dev/null
+++ b/daemons/fenced/pacemaker-fenced.c
@@ -0,0 +1,1751 @@
+/*
+ * Copyright 2009-2023 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 <crm_internal.h>
+
+#include <sys/param.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h> // PRIu32, PRIx32
+
+#include <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_internal.h>
+#include <crm/common/output_internal.h>
+#include <crm/cluster/internal.h>
+
+#include <crm/stonith-ng.h>
+#include <crm/fencing/internal.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+
+#include <crm/common/mainloop.h>
+
+#include <crm/cib/internal.h>
+#include <crm/pengine/status.h>
+#include <pacemaker-internal.h>
+
+#include <pacemaker-fenced.h>
+
+#define SUMMARY "daemon for executing fencing devices in a Pacemaker cluster"
+
+char *stonith_our_uname = NULL;
+long stonith_watchdog_timeout_ms = 0;
+GList *stonith_watchdog_targets = NULL;
+
+static GMainLoop *mainloop = NULL;
+
+gboolean stand_alone = FALSE;
+static gboolean stonith_shutdown_flag = FALSE;
+
+static qb_ipcs_service_t *ipcs = NULL;
+static xmlNode *local_cib = NULL;
+static pe_working_set_t *fenced_data_set = NULL;
+static const unsigned long long data_set_flags = pe_flag_quick_location
+ | pe_flag_no_compat
+ | pe_flag_no_counts;
+
+static cib_t *cib_api = NULL;
+
+static pcmk__output_t *logger_out = NULL;
+static pcmk__output_t *out = NULL;
+
+pcmk__supported_format_t formats[] = {
+ PCMK__SUPPORTED_FORMAT_NONE,
+ PCMK__SUPPORTED_FORMAT_TEXT,
+ PCMK__SUPPORTED_FORMAT_XML,
+ { NULL, NULL, NULL }
+};
+
+static struct {
+ bool no_cib_connect;
+ gchar **log_files;
+} options;
+
+static crm_exit_t exit_code = CRM_EX_OK;
+
+static void stonith_shutdown(int nsig);
+static void stonith_cleanup(void);
+
+static int32_t
+st_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
+{
+ if (stonith_shutdown_flag) {
+ crm_info("Ignoring new client [%d] during shutdown",
+ pcmk__client_pid(c));
+ return -EPERM;
+ }
+
+ if (pcmk__new_client(c, uid, gid) == NULL) {
+ return -EIO;
+ }
+ return 0;
+}
+
+/* Exit code means? */
+static int32_t
+st_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size)
+{
+ uint32_t id = 0;
+ uint32_t flags = 0;
+ int call_options = 0;
+ xmlNode *request = NULL;
+ pcmk__client_t *c = pcmk__find_client(qbc);
+ const char *op = NULL;
+
+ if (c == NULL) {
+ crm_info("Invalid client: %p", qbc);
+ return 0;
+ }
+
+ request = pcmk__client_data2xml(c, data, &id, &flags);
+ if (request == NULL) {
+ pcmk__ipc_send_ack(c, id, flags, "nack", NULL, CRM_EX_PROTOCOL);
+ return 0;
+ }
+
+
+ op = crm_element_value(request, F_CRM_TASK);
+ if(pcmk__str_eq(op, CRM_OP_RM_NODE_CACHE, pcmk__str_casei)) {
+ crm_xml_add(request, F_TYPE, T_STONITH_NG);
+ crm_xml_add(request, F_STONITH_OPERATION, op);
+ crm_xml_add(request, F_STONITH_CLIENTID, c->id);
+ crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c));
+ crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname);
+
+ send_cluster_message(NULL, crm_msg_stonith_ng, request, FALSE);
+ free_xml(request);
+ return 0;
+ }
+
+ if (c->name == NULL) {
+ const char *value = crm_element_value(request, F_STONITH_CLIENTNAME);
+
+ if (value == NULL) {
+ value = "unknown";
+ }
+ c->name = crm_strdup_printf("%s.%u", value, c->pid);
+ }
+
+ crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
+ crm_trace("Flags %#08" PRIx32 "/%#08x for command %" PRIu32
+ " from client %s", flags, call_options, id, pcmk__client_name(c));
+
+ if (pcmk_is_set(call_options, st_opt_sync_call)) {
+ CRM_ASSERT(flags & crm_ipc_client_response);
+ CRM_LOG_ASSERT(c->request_id == 0); /* This means the client has two synchronous events in-flight */
+ c->request_id = id; /* Reply only to the last one */
+ }
+
+ crm_xml_add(request, F_STONITH_CLIENTID, c->id);
+ crm_xml_add(request, F_STONITH_CLIENTNAME, pcmk__client_name(c));
+ crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname);
+
+ crm_log_xml_trace(request, "ipc-received");
+ stonith_command(c, id, flags, request, NULL);
+
+ free_xml(request);
+ return 0;
+}
+
+/* Error code means? */
+static int32_t
+st_ipc_closed(qb_ipcs_connection_t * c)
+{
+ pcmk__client_t *client = pcmk__find_client(c);
+
+ if (client == NULL) {
+ return 0;
+ }
+
+ crm_trace("Connection %p closed", c);
+ pcmk__free_client(client);
+
+ /* 0 means: yes, go ahead and destroy the connection */
+ return 0;
+}
+
+static void
+st_ipc_destroy(qb_ipcs_connection_t * c)
+{
+ crm_trace("Connection %p destroyed", c);
+ st_ipc_closed(c);
+}
+
+static void
+stonith_peer_callback(xmlNode * msg, void *private_data)
+{
+ const char *remote_peer = crm_element_value(msg, F_ORIG);
+ const char *op = crm_element_value(msg, F_STONITH_OPERATION);
+
+ if (pcmk__str_eq(op, "poke", pcmk__str_none)) {
+ return;
+ }
+
+ crm_log_xml_trace(msg, "Peer[inbound]");
+ stonith_command(NULL, 0, 0, msg, remote_peer);
+}
+
+#if SUPPORT_COROSYNC
+static void
+stonith_peer_ais_callback(cpg_handle_t handle,
+ const struct cpg_name *groupName,
+ uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
+{
+ uint32_t kind = 0;
+ xmlNode *xml = NULL;
+ const char *from = NULL;
+ char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from);
+
+ if(data == NULL) {
+ return;
+ }
+ if (kind == crm_class_cluster) {
+ xml = string2xml(data);
+ if (xml == NULL) {
+ crm_err("Invalid XML: '%.120s'", data);
+ free(data);
+ return;
+ }
+ crm_xml_add(xml, F_ORIG, from);
+ /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */
+ stonith_peer_callback(xml, NULL);
+ }
+
+ free_xml(xml);
+ free(data);
+ return;
+}
+
+static void
+stonith_peer_cs_destroy(gpointer user_data)
+{
+ crm_crit("Lost connection to cluster layer, shutting down");
+ stonith_shutdown(0);
+}
+#endif
+
+void
+do_local_reply(xmlNode *notify_src, pcmk__client_t *client, int call_options)
+{
+ /* send callback to originating child */
+ int local_rc = pcmk_rc_ok;
+ int rid = 0;
+ uint32_t ipc_flags = crm_ipc_server_event;
+
+ if (pcmk_is_set(call_options, st_opt_sync_call)) {
+ CRM_LOG_ASSERT(client->request_id);
+ rid = client->request_id;
+ client->request_id = 0;
+ ipc_flags = crm_ipc_flags_none;
+ }
+
+ local_rc = pcmk__ipc_send_xml(client, rid, notify_src, ipc_flags);
+ if (local_rc == pcmk_rc_ok) {
+ crm_trace("Sent response %d to client %s",
+ rid, pcmk__client_name(client));
+ } else {
+ crm_warn("%synchronous reply to client %s failed: %s",
+ (pcmk_is_set(call_options, st_opt_sync_call)? "S" : "As"),
+ pcmk__client_name(client), pcmk_rc_str(local_rc));
+ }
+}
+
+uint64_t
+get_stonith_flag(const char *name)
+{
+ if (pcmk__str_eq(name, T_STONITH_NOTIFY_FENCE, pcmk__str_casei)) {
+ return st_callback_notify_fence;
+
+ } else if (pcmk__str_eq(name, STONITH_OP_DEVICE_ADD, pcmk__str_casei)) {
+ return st_callback_device_add;
+
+ } else if (pcmk__str_eq(name, STONITH_OP_DEVICE_DEL, pcmk__str_casei)) {
+ return st_callback_device_del;
+
+ } else if (pcmk__str_eq(name, T_STONITH_NOTIFY_HISTORY, pcmk__str_casei)) {
+ return st_callback_notify_history;
+
+ } else if (pcmk__str_eq(name, T_STONITH_NOTIFY_HISTORY_SYNCED, pcmk__str_casei)) {
+ return st_callback_notify_history_synced;
+
+ }
+ return st_callback_unknown;
+}
+
+static void
+stonith_notify_client(gpointer key, gpointer value, gpointer user_data)
+{
+
+ xmlNode *update_msg = user_data;
+ pcmk__client_t *client = value;
+ const char *type = NULL;
+
+ CRM_CHECK(client != NULL, return);
+ CRM_CHECK(update_msg != NULL, return);
+
+ type = crm_element_value(update_msg, F_SUBTYPE);
+ CRM_CHECK(type != NULL, crm_log_xml_err(update_msg, "notify"); return);
+
+ if (client->ipcs == NULL) {
+ crm_trace("Skipping client with NULL channel");
+ return;
+ }
+
+ if (pcmk_is_set(client->flags, get_stonith_flag(type))) {
+ int rc = pcmk__ipc_send_xml(client, 0, update_msg,
+ crm_ipc_server_event);
+
+ if (rc != pcmk_rc_ok) {
+ crm_warn("%s notification of client %s failed: %s "
+ CRM_XS " id=%.8s rc=%d", type, pcmk__client_name(client),
+ pcmk_rc_str(rc), client->id, rc);
+ } else {
+ crm_trace("Sent %s notification to client %s",
+ type, pcmk__client_name(client));
+ }
+ }
+}
+
+void
+do_stonith_async_timeout_update(const char *client_id, const char *call_id, int timeout)
+{
+ pcmk__client_t *client = NULL;
+ xmlNode *notify_data = NULL;
+
+ if (!timeout || !call_id || !client_id) {
+ return;
+ }
+
+ client = pcmk__find_client_by_id(client_id);
+ if (!client) {
+ return;
+ }
+
+ notify_data = create_xml_node(NULL, T_STONITH_TIMEOUT_VALUE);
+ crm_xml_add(notify_data, F_TYPE, T_STONITH_TIMEOUT_VALUE);
+ crm_xml_add(notify_data, F_STONITH_CALLID, call_id);
+ crm_xml_add_int(notify_data, F_STONITH_TIMEOUT, timeout);
+
+ crm_trace("timeout update is %d for client %s and call id %s", timeout, client_id, call_id);
+
+ if (client) {
+ pcmk__ipc_send_xml(client, 0, notify_data, crm_ipc_server_event);
+ }
+
+ free_xml(notify_data);
+}
+
+/*!
+ * \internal
+ * \brief Notify relevant IPC clients of a fencing operation result
+ *
+ * \param[in] type Notification type
+ * \param[in] result Result of fencing operation (assume success if NULL)
+ * \param[in] data If not NULL, add to notification as call data
+ */
+void
+fenced_send_notification(const char *type, const pcmk__action_result_t *result,
+ xmlNode *data)
+{
+ /* TODO: Standardize the contents of data */
+ xmlNode *update_msg = create_xml_node(NULL, "notify");
+
+ CRM_LOG_ASSERT(type != NULL);
+
+ crm_xml_add(update_msg, F_TYPE, T_STONITH_NOTIFY);
+ crm_xml_add(update_msg, F_SUBTYPE, type);
+ crm_xml_add(update_msg, F_STONITH_OPERATION, type);
+ stonith__xe_set_result(update_msg, result);
+
+ if (data != NULL) {
+ add_message_xml(update_msg, F_STONITH_CALLDATA, data);
+ }
+
+ crm_trace("Notifying clients");
+ pcmk__foreach_ipc_client(stonith_notify_client, update_msg);
+ free_xml(update_msg);
+ crm_trace("Notify complete");
+}
+
+/*!
+ * \internal
+ * \brief Send notifications for a configuration change to subscribed clients
+ *
+ * \param[in] op Notification type (STONITH_OP_DEVICE_ADD,
+ * STONITH_OP_DEVICE_DEL, STONITH_OP_LEVEL_ADD, or
+ * STONITH_OP_LEVEL_DEL)
+ * \param[in] result Operation result
+ * \param[in] desc Description of what changed
+ * \param[in] active Current number of devices or topologies in use
+ */
+static void
+send_config_notification(const char *op, const pcmk__action_result_t *result,
+ const char *desc, int active)
+{
+ xmlNode *notify_data = create_xml_node(NULL, op);
+
+ CRM_CHECK(notify_data != NULL, return);
+
+ crm_xml_add(notify_data, F_STONITH_DEVICE, desc);
+ crm_xml_add_int(notify_data, F_STONITH_ACTIVE, active);
+
+ fenced_send_notification(op, result, notify_data);
+ free_xml(notify_data);
+}
+
+/*!
+ * \internal
+ * \brief Send notifications for a device change to subscribed clients
+ *
+ * \param[in] op Notification type (STONITH_OP_DEVICE_ADD or
+ * STONITH_OP_DEVICE_DEL)
+ * \param[in] result Operation result
+ * \param[in] desc ID of device that changed
+ */
+void
+fenced_send_device_notification(const char *op,
+ const pcmk__action_result_t *result,
+ const char *desc)
+{
+ send_config_notification(op, result, desc, g_hash_table_size(device_list));
+}
+
+/*!
+ * \internal
+ * \brief Send notifications for a topology level change to subscribed clients
+ *
+ * \param[in] op Notification type (STONITH_OP_LEVEL_ADD or
+ * STONITH_OP_LEVEL_DEL)
+ * \param[in] result Operation result
+ * \param[in] desc String representation of level (<target>[<level_index>])
+ */
+void
+fenced_send_level_notification(const char *op,
+ const pcmk__action_result_t *result,
+ const char *desc)
+{
+ send_config_notification(op, result, desc, g_hash_table_size(topology));
+}
+
+static void
+topology_remove_helper(const char *node, int level)
+{
+ char *desc = NULL;
+ pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+ xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
+
+ crm_xml_add(data, F_STONITH_ORIGIN, __func__);
+ crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level);
+ crm_xml_add(data, XML_ATTR_STONITH_TARGET, node);
+
+ fenced_unregister_level(data, &desc, &result);
+ fenced_send_level_notification(STONITH_OP_LEVEL_DEL, &result, desc);
+ pcmk__reset_result(&result);
+ free_xml(data);
+ free(desc);
+}
+
+static void
+remove_cib_device(xmlXPathObjectPtr xpathObj)
+{
+ int max = numXpathResults(xpathObj), lpc = 0;
+
+ for (lpc = 0; lpc < max; lpc++) {
+ const char *rsc_id = NULL;
+ const char *standard = NULL;
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+
+ CRM_LOG_ASSERT(match != NULL);
+ if(match != NULL) {
+ standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
+ }
+
+ if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
+ continue;
+ }
+
+ rsc_id = crm_element_value(match, XML_ATTR_ID);
+
+ stonith_device_remove(rsc_id, true);
+ }
+}
+
+static void
+remove_topology_level(xmlNode *match)
+{
+ int index = 0;
+ char *key = NULL;
+
+ CRM_CHECK(match != NULL, return);
+
+ key = stonith_level_key(match, fenced_target_by_unknown);
+ crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
+ topology_remove_helper(key, index);
+ free(key);
+}
+
+static void
+add_topology_level(xmlNode *match)
+{
+ char *desc = NULL;
+ pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
+
+ CRM_CHECK(match != NULL, return);
+
+ fenced_register_level(match, &desc, &result);
+ fenced_send_level_notification(STONITH_OP_LEVEL_ADD, &result, desc);
+ pcmk__reset_result(&result);
+ free(desc);
+}
+
+static void
+remove_fencing_topology(xmlXPathObjectPtr xpathObj)
+{
+ int max = numXpathResults(xpathObj), lpc = 0;
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+
+ CRM_LOG_ASSERT(match != NULL);
+ if (match && crm_element_value(match, XML_DIFF_MARKER)) {
+ /* Deletion */
+ int index = 0;
+ char *target = stonith_level_key(match, fenced_target_by_unknown);
+
+ crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
+ if (target == NULL) {
+ crm_err("Invalid fencing target in element %s", ID(match));
+
+ } else if (index <= 0) {
+ crm_err("Invalid level for %s in element %s", target, ID(match));
+
+ } else {
+ topology_remove_helper(target, index);
+ }
+ /* } else { Deal with modifications during the 'addition' stage */
+ }
+ }
+}
+
+static void
+register_fencing_topology(xmlXPathObjectPtr xpathObj)
+{
+ int max = numXpathResults(xpathObj), lpc = 0;
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+
+ remove_topology_level(match);
+ add_topology_level(match);
+ }
+}
+
+/* Fencing
+<diff crm_feature_set="3.0.6">
+ <diff-removed>
+ <fencing-topology>
+ <fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
+ <fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
+ <fencing-level devices="disk,network" id="f-p2.1"/>
+ </fencing-topology>
+ </diff-removed>
+ <diff-added>
+ <fencing-topology>
+ <fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
+ <fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
+ <fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
+ </fencing-topology>
+ </diff-added>
+</diff>
+*/
+
+static void
+fencing_topology_init(void)
+{
+ xmlXPathObjectPtr xpathObj = NULL;
+ const char *xpath = "//" XML_TAG_FENCING_LEVEL;
+
+ crm_trace("Full topology refresh");
+ free_topology_list();
+ init_topology_list();
+
+ /* Grab everything */
+ xpathObj = xpath_search(local_cib, xpath);
+ register_fencing_topology(xpathObj);
+
+ freeXpathObject(xpathObj);
+}
+
+#define rsc_name(x) x->clone_name?x->clone_name:x->id
+
+/*!
+ * \internal
+ * \brief Check whether our uname is in a resource's allowed node list
+ *
+ * \param[in] rsc Resource to check
+ *
+ * \return Pointer to node object if found, NULL otherwise
+ */
+static pe_node_t *
+our_node_allowed_for(const pe_resource_t *rsc)
+{
+ GHashTableIter iter;
+ pe_node_t *node = NULL;
+
+ if (rsc && stonith_our_uname) {
+ g_hash_table_iter_init(&iter, rsc->allowed_nodes);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
+ if (node && strcmp(node->details->uname, stonith_our_uname) == 0) {
+ break;
+ }
+ node = NULL;
+ }
+ }
+ return node;
+}
+
+static void
+watchdog_device_update(void)
+{
+ if (stonith_watchdog_timeout_ms > 0) {
+ if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) &&
+ !stonith_watchdog_targets) {
+ /* getting here watchdog-fencing enabled, no device there yet
+ and reason isn't stonith_watchdog_targets preventing that
+ */
+ int rc;
+ xmlNode *xml;
+
+ xml = create_device_registration_xml(
+ STONITH_WATCHDOG_ID,
+ st_namespace_internal,
+ STONITH_WATCHDOG_AGENT,
+ NULL, /* stonith_device_register will add our
+ own name as PCMK_STONITH_HOST_LIST param
+ so we can skip that here
+ */
+ NULL);
+ rc = stonith_device_register(xml, TRUE);
+ free_xml(xml);
+ if (rc != pcmk_ok) {
+ rc = pcmk_legacy2rc(rc);
+ exit_code = CRM_EX_FATAL;
+ crm_crit("Cannot register watchdog pseudo fence agent: %s",
+ pcmk_rc_str(rc));
+ stonith_shutdown(0);
+ }
+ }
+
+ } else if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) != NULL) {
+ /* be silent if no device - todo parameter to stonith_device_remove */
+ stonith_device_remove(STONITH_WATCHDOG_ID, true);
+ }
+}
+
+static void
+update_stonith_watchdog_timeout_ms(xmlNode *cib)
+{
+ long timeout_ms = 0;
+ xmlNode *stonith_watchdog_xml = NULL;
+ const char *value = NULL;
+
+ stonith_watchdog_xml = get_xpath_object("//nvpair[@name='stonith-watchdog-timeout']",
+ cib, LOG_NEVER);
+ if (stonith_watchdog_xml) {
+ value = crm_element_value(stonith_watchdog_xml, XML_NVPAIR_ATTR_VALUE);
+ }
+ if (value) {
+ timeout_ms = crm_get_msec(value);
+ }
+
+ if (timeout_ms < 0) {
+ timeout_ms = pcmk__auto_watchdog_timeout();
+ }
+
+ stonith_watchdog_timeout_ms = timeout_ms;
+}
+
+/*!
+ * \internal
+ * \brief If a resource or any of its children are STONITH devices, update their
+ * definitions given a cluster working set.
+ *
+ * \param[in,out] rsc Resource to check
+ * \param[in,out] data_set Cluster working set with device information
+ */
+static void
+cib_device_update(pe_resource_t *rsc, pe_working_set_t *data_set)
+{
+ pe_node_t *node = NULL;
+ const char *value = NULL;
+ const char *rclass = NULL;
+ pe_node_t *parent = NULL;
+
+ /* If this is a complex resource, check children rather than this resource itself. */
+ if(rsc->children) {
+ GList *gIter = NULL;
+ for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
+ cib_device_update(gIter->data, data_set);
+ if(pe_rsc_is_clone(rsc)) {
+ crm_trace("Only processing one copy of the clone %s", rsc->id);
+ break;
+ }
+ }
+ return;
+ }
+
+ /* We only care about STONITH resources. */
+ rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
+ if (!pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
+ return;
+ }
+
+ /* If this STONITH resource is disabled, remove it. */
+ if (pe__resource_is_disabled(rsc)) {
+ crm_info("Device %s has been disabled", rsc->id);
+ return;
+ }
+
+ /* if watchdog-fencing is disabled handle any watchdog-fence
+ resource as if it was disabled
+ */
+ if ((stonith_watchdog_timeout_ms <= 0) &&
+ pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
+ crm_info("Watchdog-fencing disabled thus handling "
+ "device %s as disabled", rsc->id);
+ return;
+ }
+
+ /* Check whether our node is allowed for this resource (and its parent if in a group) */
+ node = our_node_allowed_for(rsc);
+ if (rsc->parent && (rsc->parent->variant == pe_group)) {
+ parent = our_node_allowed_for(rsc->parent);
+ }
+
+ if(node == NULL) {
+ /* Our node is disallowed, so remove the device */
+ GHashTableIter iter;
+
+ crm_info("Device %s has been disabled on %s: unknown", rsc->id, stonith_our_uname);
+ g_hash_table_iter_init(&iter, rsc->allowed_nodes);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
+ crm_trace("Available: %s = %d", pe__node_name(node), node->weight);
+ }
+
+ return;
+
+ } else if(node->weight < 0 || (parent && parent->weight < 0)) {
+ /* Our node (or its group) is disallowed by score, so remove the device */
+ int score = (node->weight < 0)? node->weight : parent->weight;
+
+ crm_info("Device %s has been disabled on %s: score=%s",
+ rsc->id, stonith_our_uname, pcmk_readable_score(score));
+ return;
+
+ } else {
+ /* Our node is allowed, so update the device information */
+ int rc;
+ xmlNode *data;
+ GHashTable *rsc_params = NULL;
+ GHashTableIter gIter;
+ stonith_key_value_t *params = NULL;
+
+ const char *name = NULL;
+ const char *agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE);
+ const char *rsc_provides = NULL;
+
+ crm_debug("Device %s is allowed on %s: score=%d", rsc->id, stonith_our_uname, node->weight);
+ rsc_params = pe_rsc_params(rsc, node, data_set);
+ get_meta_attributes(rsc->meta, rsc, node, data_set);
+
+ rsc_provides = g_hash_table_lookup(rsc->meta, PCMK_STONITH_PROVIDES);
+
+ g_hash_table_iter_init(&gIter, rsc_params);
+ while (g_hash_table_iter_next(&gIter, (gpointer *) & name, (gpointer *) & value)) {
+ if (!name || !value) {
+ continue;
+ }
+ params = stonith_key_value_add(params, name, value);
+ crm_trace(" %s=%s", name, value);
+ }
+
+ data = create_device_registration_xml(rsc_name(rsc), st_namespace_any,
+ agent, params, rsc_provides);
+ stonith_key_value_freeall(params, 1, 1);
+ rc = stonith_device_register(data, TRUE);
+ CRM_ASSERT(rc == pcmk_ok);
+ free_xml(data);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Update all STONITH device definitions based on current CIB
+ */
+static void
+cib_devices_update(void)
+{
+ GHashTableIter iter;
+ stonith_device_t *device = NULL;
+
+ crm_info("Updating devices to version %s.%s.%s",
+ crm_element_value(local_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(local_cib, XML_ATTR_GENERATION),
+ crm_element_value(local_cib, XML_ATTR_NUMUPDATES));
+
+ if (fenced_data_set->now != NULL) {
+ crm_time_free(fenced_data_set->now);
+ fenced_data_set->now = NULL;
+ }
+ fenced_data_set->localhost = stonith_our_uname;
+ pcmk__schedule_actions(local_cib, data_set_flags, fenced_data_set);
+
+ g_hash_table_iter_init(&iter, device_list);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
+ if (device->cib_registered) {
+ device->dirty = TRUE;
+ }
+ }
+
+ /* have list repopulated if cib has a watchdog-fencing-resource
+ TODO: keep a cached list for queries happening while we are refreshing
+ */
+ g_list_free_full(stonith_watchdog_targets, free);
+ stonith_watchdog_targets = NULL;
+ g_list_foreach(fenced_data_set->resources, (GFunc) cib_device_update, fenced_data_set);
+
+ g_hash_table_iter_init(&iter, device_list);
+ while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
+ if (device->dirty) {
+ g_hash_table_iter_remove(&iter);
+ }
+ }
+
+ fenced_data_set->input = NULL; // Wasn't a copy, so don't let API free it
+ pe_reset_working_set(fenced_data_set);
+}
+
+static void
+update_cib_stonith_devices_v2(const char *event, xmlNode * msg)
+{
+ xmlNode *change = NULL;
+ char *reason = NULL;
+ bool needs_update = FALSE;
+ xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
+
+ for (change = pcmk__xml_first_child(patchset); change != NULL;
+ change = pcmk__xml_next(change)) {
+ const char *op = crm_element_value(change, XML_DIFF_OP);
+ const char *xpath = crm_element_value(change, XML_DIFF_PATH);
+ const char *shortpath = NULL;
+
+ if ((op == NULL) ||
+ (strcmp(op, "move") == 0) ||
+ strstr(xpath, "/"XML_CIB_TAG_STATUS)) {
+ continue;
+ } else if (pcmk__str_eq(op, "delete", pcmk__str_casei) && strstr(xpath, "/"XML_CIB_TAG_RESOURCE)) {
+ const char *rsc_id = NULL;
+ char *search = NULL;
+ char *mutable = NULL;
+
+ if (strstr(xpath, XML_TAG_ATTR_SETS) ||
+ strstr(xpath, XML_TAG_META_SETS)) {
+ needs_update = TRUE;
+ pcmk__str_update(&reason,
+ "(meta) attribute deleted from resource");
+ break;
+ }
+ pcmk__str_update(&mutable, xpath);
+ rsc_id = strstr(mutable, "primitive[@" XML_ATTR_ID "=\'");
+ if (rsc_id != NULL) {
+ rsc_id += strlen("primitive[@" XML_ATTR_ID "=\'");
+ search = strchr(rsc_id, '\'');
+ }
+ if (search != NULL) {
+ *search = 0;
+ stonith_device_remove(rsc_id, true);
+ /* watchdog_device_update called afterwards
+ to fall back to implicit definition if needed */
+ } else {
+ crm_warn("Ignoring malformed CIB update (resource deletion)");
+ }
+ free(mutable);
+
+ } else if (strstr(xpath, "/"XML_CIB_TAG_RESOURCES) ||
+ strstr(xpath, "/"XML_CIB_TAG_CONSTRAINTS) ||
+ strstr(xpath, "/"XML_CIB_TAG_RSCCONFIG)) {
+ shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath);
+ reason = crm_strdup_printf("%s %s", op, shortpath+1);
+ needs_update = TRUE;
+ break;
+ }
+ }
+
+ if(needs_update) {
+ crm_info("Updating device list from CIB: %s", reason);
+ cib_devices_update();
+ } else {
+ crm_trace("No updates for device list found in CIB");
+ }
+ free(reason);
+}
+
+
+static void
+update_cib_stonith_devices_v1(const char *event, xmlNode * msg)
+{
+ const char *reason = "none";
+ gboolean needs_update = FALSE;
+ xmlXPathObjectPtr xpath_obj = NULL;
+
+ /* process new constraints */
+ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION);
+ if (numXpathResults(xpath_obj) > 0) {
+ int max = numXpathResults(xpath_obj), lpc = 0;
+
+ /* Safest and simplest to always recompute */
+ needs_update = TRUE;
+ reason = "new location constraint";
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *match = getXpathResult(xpath_obj, lpc);
+
+ crm_log_xml_trace(match, "new constraint");
+ }
+ }
+ freeXpathObject(xpath_obj);
+
+ /* process deletions */
+ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_RESOURCE);
+ if (numXpathResults(xpath_obj) > 0) {
+ remove_cib_device(xpath_obj);
+ }
+ freeXpathObject(xpath_obj);
+
+ /* process additions */
+ xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_RESOURCE);
+ if (numXpathResults(xpath_obj) > 0) {
+ int max = numXpathResults(xpath_obj), lpc = 0;
+
+ for (lpc = 0; lpc < max; lpc++) {
+ const char *rsc_id = NULL;
+ const char *standard = NULL;
+ xmlNode *match = getXpathResult(xpath_obj, lpc);
+
+ rsc_id = crm_element_value(match, XML_ATTR_ID);
+ standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
+
+ if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
+ continue;
+ }
+
+ crm_trace("Fencing resource %s was added or modified", rsc_id);
+ reason = "new resource";
+ needs_update = TRUE;
+ }
+ }
+ freeXpathObject(xpath_obj);
+
+ if(needs_update) {
+ crm_info("Updating device list from CIB: %s", reason);
+ cib_devices_update();
+ }
+}
+
+static void
+update_cib_stonith_devices(const char *event, xmlNode * msg)
+{
+ int format = 1;
+ xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
+
+ CRM_ASSERT(patchset);
+ crm_element_value_int(patchset, "format", &format);
+ switch(format) {
+ case 1:
+ update_cib_stonith_devices_v1(event, msg);
+ break;
+ case 2:
+ update_cib_stonith_devices_v2(event, msg);
+ break;
+ default:
+ crm_warn("Unknown patch format: %d", format);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Check whether a node has a specific attribute name/value
+ *
+ * \param[in] node Name of node to check
+ * \param[in] name Name of an attribute to look for
+ * \param[in] value The value the named attribute needs to be set to in order to be considered a match
+ *
+ * \return TRUE if the locally cached CIB has the specified node attribute
+ */
+gboolean
+node_has_attr(const char *node, const char *name, const char *value)
+{
+ GString *xpath = NULL;
+ xmlNode *match;
+
+ CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL)
+ && (value != NULL), return FALSE);
+
+ /* Search for the node's attributes in the CIB. While the schema allows
+ * multiple sets of instance attributes, and allows instance attributes to
+ * use id-ref to reference values elsewhere, that is intended for resources,
+ * so we ignore that here.
+ */
+ xpath = g_string_sized_new(256);
+ pcmk__g_strcat(xpath,
+ "//" XML_CIB_TAG_NODES "/" XML_CIB_TAG_NODE
+ "[@" XML_ATTR_UNAME "='", node, "']/" XML_TAG_ATTR_SETS
+ "/" XML_CIB_TAG_NVPAIR
+ "[@" XML_NVPAIR_ATTR_NAME "='", name, "' "
+ "and @" XML_NVPAIR_ATTR_VALUE "='", value, "']", NULL);
+
+ match = get_xpath_object((const char *) xpath->str, local_cib, LOG_NEVER);
+
+ g_string_free(xpath, TRUE);
+ return (match != NULL);
+}
+
+/*!
+ * \internal
+ * \brief Check whether a node does watchdog-fencing
+ *
+ * \param[in] node Name of node to check
+ *
+ * \return TRUE if node found in stonith_watchdog_targets
+ * or stonith_watchdog_targets is empty indicating
+ * all nodes are doing watchdog-fencing
+ */
+gboolean
+node_does_watchdog_fencing(const char *node)
+{
+ return ((stonith_watchdog_targets == NULL) ||
+ pcmk__str_in_list(node, stonith_watchdog_targets, pcmk__str_casei));
+}
+
+
+static void
+update_fencing_topology(const char *event, xmlNode * msg)
+{
+ int format = 1;
+ const char *xpath;
+ xmlXPathObjectPtr xpathObj = NULL;
+ xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
+
+ CRM_ASSERT(patchset);
+ crm_element_value_int(patchset, "format", &format);
+
+ if(format == 1) {
+ /* Process deletions (only) */
+ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_FENCING_LEVEL;
+ xpathObj = xpath_search(msg, xpath);
+
+ remove_fencing_topology(xpathObj);
+ freeXpathObject(xpathObj);
+
+ /* Process additions and changes */
+ xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_TAG_FENCING_LEVEL;
+ xpathObj = xpath_search(msg, xpath);
+
+ register_fencing_topology(xpathObj);
+ freeXpathObject(xpathObj);
+
+ } else if(format == 2) {
+ xmlNode *change = NULL;
+ int add[] = { 0, 0, 0 };
+ int del[] = { 0, 0, 0 };
+
+ xml_patch_versions(patchset, add, del);
+
+ for (change = pcmk__xml_first_child(patchset); change != NULL;
+ change = pcmk__xml_next(change)) {
+ const char *op = crm_element_value(change, XML_DIFF_OP);
+ const char *xpath = crm_element_value(change, XML_DIFF_PATH);
+
+ if(op == NULL) {
+ continue;
+
+ } else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) {
+ /* Change to a specific entry */
+
+ crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath);
+ if(strcmp(op, "move") == 0) {
+ continue;
+
+ } else if(strcmp(op, "create") == 0) {
+ add_topology_level(change->children);
+
+ } else if(strcmp(op, "modify") == 0) {
+ xmlNode *match = first_named_child(change, XML_DIFF_RESULT);
+
+ if(match) {
+ remove_topology_level(match->children);
+ add_topology_level(match->children);
+ }
+
+ } else if(strcmp(op, "delete") == 0) {
+ /* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */
+ crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s",
+ op, add[0], add[1], add[2], xpath);
+ fencing_topology_init();
+ return;
+ }
+
+ } else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) {
+ /* Change to the topology in general */
+ crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s",
+ op, add[0], add[1], add[2], xpath);
+ fencing_topology_init();
+ return;
+
+ } else if (strstr(xpath, "/" XML_CIB_TAG_CONFIGURATION)) {
+ /* Changes to the whole config section, possibly including the topology as a whild */
+ if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) {
+ crm_trace("Nothing for us in %s operation %d.%d.%d for %s.",
+ op, add[0], add[1], add[2], xpath);
+
+ } else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) {
+ crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.",
+ op, add[0], add[1], add[2], xpath);
+ fencing_topology_init();
+ return;
+ }
+
+ } else {
+ crm_trace("Nothing for us in %s operation %d.%d.%d for %s",
+ op, add[0], add[1], add[2], xpath);
+ }
+ }
+
+ } else {
+ crm_warn("Unknown patch format: %d", format);
+ }
+}
+static bool have_cib_devices = FALSE;
+
+static void
+update_cib_cache_cb(const char *event, xmlNode * msg)
+{
+ int rc = pcmk_ok;
+ long timeout_ms_saved = stonith_watchdog_timeout_ms;
+ bool need_full_refresh = false;
+
+ if(!have_cib_devices) {
+ crm_trace("Skipping updates until we get a full dump");
+ return;
+
+ } else if(msg == NULL) {
+ crm_trace("Missing %s update", event);
+ return;
+ }
+
+ /* Maintain a local copy of the CIB so that we have full access
+ * to device definitions, location constraints, and node attributes
+ */
+ if (local_cib != NULL) {
+ int rc = pcmk_ok;
+ xmlNode *patchset = NULL;
+
+ crm_element_value_int(msg, F_CIB_RC, &rc);
+ if (rc != pcmk_ok) {
+ return;
+ }
+
+ patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
+ pcmk__output_set_log_level(logger_out, LOG_TRACE);
+ out->message(out, "xml-patchset", patchset);
+ rc = xml_apply_patchset(local_cib, patchset, TRUE);
+ switch (rc) {
+ case pcmk_ok:
+ case -pcmk_err_old_data:
+ break;
+ case -pcmk_err_diff_resync:
+ case -pcmk_err_diff_failed:
+ crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
+ free_xml(local_cib);
+ local_cib = NULL;
+ break;
+ default:
+ crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
+ free_xml(local_cib);
+ local_cib = NULL;
+ }
+ }
+
+ if (local_cib == NULL) {
+ crm_trace("Re-requesting full CIB");
+ rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_scope_local | cib_sync_call);
+ if(rc != pcmk_ok) {
+ crm_err("Couldn't retrieve the CIB: %s (%d)", pcmk_strerror(rc), rc);
+ return;
+ }
+ CRM_ASSERT(local_cib != NULL);
+ need_full_refresh = true;
+ }
+
+ pcmk__refresh_node_caches_from_cib(local_cib);
+ update_stonith_watchdog_timeout_ms(local_cib);
+
+ if (timeout_ms_saved != stonith_watchdog_timeout_ms) {
+ need_full_refresh = true;
+ }
+
+ if (need_full_refresh) {
+ fencing_topology_init();
+ cib_devices_update();
+ } else {
+ // Partial refresh
+ update_fencing_topology(event, msg);
+ update_cib_stonith_devices(event, msg);
+ }
+
+ watchdog_device_update();
+}
+
+static void
+init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
+{
+ crm_info("Updating device list from CIB");
+ have_cib_devices = TRUE;
+ local_cib = copy_xml(output);
+
+ pcmk__refresh_node_caches_from_cib(local_cib);
+ update_stonith_watchdog_timeout_ms(local_cib);
+
+ fencing_topology_init();
+ cib_devices_update();
+ watchdog_device_update();
+}
+
+static void
+stonith_shutdown(int nsig)
+{
+ crm_info("Terminating with %d clients", pcmk__ipc_client_count());
+ stonith_shutdown_flag = TRUE;
+ if (mainloop != NULL && g_main_loop_is_running(mainloop)) {
+ g_main_loop_quit(mainloop);
+ }
+}
+
+static void
+cib_connection_destroy(gpointer user_data)
+{
+ if (stonith_shutdown_flag) {
+ crm_info("Connection to the CIB manager closed");
+ return;
+ } else {
+ crm_crit("Lost connection to the CIB manager, shutting down");
+ }
+ if (cib_api) {
+ cib_api->cmds->signoff(cib_api);
+ }
+ stonith_shutdown(0);
+}
+
+static void
+stonith_cleanup(void)
+{
+ if (cib_api) {
+ cib_api->cmds->del_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb);
+ cib_api->cmds->signoff(cib_api);
+ }
+
+ if (ipcs) {
+ qb_ipcs_destroy(ipcs);
+ }
+
+ crm_peer_destroy();
+ pcmk__client_cleanup();
+ free_stonith_remote_op_list();
+ free_topology_list();
+ free_device_list();
+ free_metadata_cache();
+ fenced_unregister_handlers();
+
+ free(stonith_our_uname);
+ stonith_our_uname = NULL;
+
+ free_xml(local_cib);
+ local_cib = NULL;
+}
+
+static gboolean
+stand_alone_cpg_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ stand_alone = FALSE;
+ options.no_cib_connect = true;
+ return TRUE;
+}
+
+static void
+setup_cib(void)
+{
+ int rc, retries = 0;
+
+ cib_api = cib_new();
+ if (cib_api == NULL) {
+ crm_err("No connection to the CIB manager");
+ return;
+ }
+
+ do {
+ sleep(retries);
+ rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command);
+ } while (rc == -ENOTCONN && ++retries < 5);
+
+ if (rc != pcmk_ok) {
+ crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc);
+
+ } else if (pcmk_ok !=
+ cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) {
+ crm_err("Could not set CIB notification callback");
+
+ } else {
+ rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local);
+ cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb",
+ init_cib_cache_cb);
+ cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
+ crm_info("Watching for fencing topology changes");
+ }
+}
+
+struct qb_ipcs_service_handlers ipc_callbacks = {
+ .connection_accept = st_ipc_accept,
+ .connection_created = NULL,
+ .msg_process = st_ipc_dispatch,
+ .connection_closed = st_ipc_closed,
+ .connection_destroyed = st_ipc_destroy
+};
+
+/*!
+ * \internal
+ * \brief Callback for peer status changes
+ *
+ * \param[in] type What changed
+ * \param[in] node What peer had the change
+ * \param[in] data Previous value of what changed
+ */
+static void
+st_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data)
+{
+ if ((type != crm_status_processes)
+ && !pcmk_is_set(node->flags, crm_remote_node)) {
+ /*
+ * This is a hack until we can send to a nodeid and/or we fix node name lookups
+ * These messages are ignored in stonith_peer_callback()
+ */
+ xmlNode *query = create_xml_node(NULL, "stonith_command");
+
+ crm_xml_add(query, F_XML_TAGNAME, "stonith_command");
+ crm_xml_add(query, F_TYPE, T_STONITH_NG);
+ crm_xml_add(query, F_STONITH_OPERATION, "poke");
+
+ crm_debug("Broadcasting our uname because of node %u", node->id);
+ send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE);
+
+ free_xml(query);
+ }
+}
+
+static pcmk__cluster_option_t fencer_options[] = {
+ /* name, old name, type, allowed values,
+ * default value, validator,
+ * short description,
+ * long description
+ */
+ {
+ PCMK_STONITH_HOST_ARGUMENT, NULL, "string", NULL, "port", NULL,
+ N_("Advanced use only: An alternate parameter to supply instead of 'port'"),
+ N_("some devices do not support the "
+ "standard 'port' parameter or may provide additional ones. Use "
+ "this to specify an alternate, device-specific, parameter "
+ "that should indicate the machine to be fenced. A value of "
+ "none can be used to tell the cluster not to supply any "
+ "additional parameters.")
+ },
+ {
+ PCMK_STONITH_HOST_MAP,NULL, "string", NULL, "", NULL,
+ N_("A mapping of host names to ports numbers for devices that do not support host names."),
+ N_("Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2")
+ },
+ {
+ PCMK_STONITH_HOST_LIST,NULL, "string", NULL, "", NULL,
+ N_("Eg. node1,node2,node3"),
+ N_("A list of machines controlled by "
+ "this device (Optional unless pcmk_host_list=static-list)")
+ },
+ {
+ PCMK_STONITH_HOST_CHECK,NULL, "string", NULL, "dynamic-list", NULL,
+ N_("How to determine which machines are controlled by the device."),
+ N_("Allowed values: dynamic-list "
+ "(query the device via the 'list' command), static-list "
+ "(check the pcmk_host_list attribute), status "
+ "(query the device via the 'status' command), "
+ "none (assume every device can fence every "
+ "machine)")
+ },
+ {
+ PCMK_STONITH_DELAY_MAX,NULL, "time", NULL, "0s", NULL,
+ N_("Enable a base delay for fencing actions and specify base delay value."),
+ N_("Enable a delay of no more than the "
+ "time specified before executing fencing actions. Pacemaker "
+ "derives the overall delay by taking the value of "
+ "pcmk_delay_base and adding a random delay value such "
+ "that the sum is kept below this maximum.")
+ },
+ {
+ PCMK_STONITH_DELAY_BASE,NULL, "string", NULL, "0s", NULL,
+ N_("Enable a base delay for "
+ "fencing actions and specify base delay value."),
+ N_("This enables a static delay for "
+ "fencing actions, which can help avoid \"death matches\" where "
+ "two nodes try to fence each other at the same time. If "
+ "pcmk_delay_max is also used, a random delay will be "
+ "added such that the total delay is kept below that value."
+ "This can be set to a single time value to apply to any node "
+ "targeted by this device (useful if a separate device is "
+ "configured for each target), or to a node map (for example, "
+ "\"node1:1s;node2:5\") to set a different value per target.")
+ },
+ {
+ PCMK_STONITH_ACTION_LIMIT,NULL, "integer", NULL, "1", NULL,
+ N_("The maximum number of actions can be performed in parallel on this device"),
+ N_("Cluster property concurrent-fencing=true needs to be configured first."
+ "Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.")
+ },
+ {
+ "pcmk_reboot_action",NULL, "string", NULL, "reboot", NULL,
+ N_("Advanced use only: An alternate command to run instead of 'reboot'"),
+ N_("Some devices do not support the standard commands or may provide additional ones.\n"
+ "Use this to specify an alternate, device-specific, command that implements the \'reboot\' action.")
+ },
+ {
+ "pcmk_reboot_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for reboot actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal."
+ "Use this to specify an alternate, device-specific, timeout for \'reboot\' actions.")
+ },
+ {
+ "pcmk_reboot_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the 'reboot' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'reboot\' actions before giving up.")
+ },
+ {
+ "pcmk_off_action",NULL, "string", NULL, "off", NULL,
+ N_("Advanced use only: An alternate command to run instead of \'off\'"),
+ N_("Some devices do not support the standard commands or may provide additional ones."
+ "Use this to specify an alternate, device-specific, command that implements the \'off\' action.")
+ },
+ {
+ "pcmk_off_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for off actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal."
+ "Use this to specify an alternate, device-specific, timeout for \'off\' actions.")
+ },
+ {
+ "pcmk_off_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the 'off' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'off\' actions before giving up.")
+ },
+ {
+ "pcmk_on_action",NULL, "string", NULL, "on", NULL,
+ N_("Advanced use only: An alternate command to run instead of 'on'"),
+ N_("Some devices do not support the standard commands or may provide additional ones."
+ "Use this to specify an alternate, device-specific, command that implements the \'on\' action.")
+ },
+ {
+ "pcmk_on_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for on actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal."
+ "Use this to specify an alternate, device-specific, timeout for \'on\' actions.")
+ },
+ {
+ "pcmk_on_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the 'on' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'on\' actions before giving up.")
+ },
+ {
+ "pcmk_list_action",NULL, "string", NULL, "list", NULL,
+ N_("Advanced use only: An alternate command to run instead of \'list\'"),
+ N_("Some devices do not support the standard commands or may provide additional ones."
+ "Use this to specify an alternate, device-specific, command that implements the \'list\' action.")
+ },
+ {
+ "pcmk_list_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for list actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal."
+ "Use this to specify an alternate, device-specific, timeout for \'list\' actions.")
+ },
+ {
+ "pcmk_list_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the \'list\' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'list\' actions before giving up.")
+ },
+ {
+ "pcmk_monitor_action",NULL, "string", NULL, "monitor", NULL,
+ N_("Advanced use only: An alternate command to run instead of \'monitor\'"),
+ N_("Some devices do not support the standard commands or may provide additional ones."
+ "Use this to specify an alternate, device-specific, command that implements the \'monitor\' action.")
+ },
+ {
+ "pcmk_monitor_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for monitor actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal.\n"
+ "Use this to specify an alternate, device-specific, timeout for \'monitor\' actions.")
+ },
+ {
+ "pcmk_monitor_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the \'monitor\' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'monitor\' actions before giving up.")
+ },
+ {
+ "pcmk_status_action",NULL, "string", NULL, "status", NULL,
+ N_("Advanced use only: An alternate command to run instead of \'status\'"),
+ N_("Some devices do not support the standard commands or may provide additional ones."
+ "Use this to specify an alternate, device-specific, command that implements the \'status\' action.")
+ },
+ {
+ "pcmk_status_timeout",NULL, "time", NULL, "60s", NULL,
+ N_("Advanced use only: Specify an alternate timeout to use for status actions instead of stonith-timeout"),
+ N_("Some devices need much more/less time to complete than normal."
+ "Use this to specify an alternate, device-specific, timeout for \'status\' actions.")
+ },
+ {
+ "pcmk_status_retries",NULL, "integer", NULL, "2", NULL,
+ N_("Advanced use only: The maximum number of times to retry the \'status\' command within the timeout period"),
+ N_("Some devices do not support multiple connections."
+ " Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
+ " Use this option to alter the number of times Pacemaker retries \'status\' actions before giving up.")
+ },
+};
+
+void
+fencer_metadata(void)
+{
+ const char *desc_short = N_("Instance attributes available for all "
+ "\"stonith\"-class resources");
+ const char *desc_long = N_("Instance attributes available for all \"stonith\"-"
+ "class resources and used by Pacemaker's fence "
+ "daemon, formerly known as stonithd");
+
+ gchar *s = pcmk__format_option_metadata("pacemaker-fenced", desc_short,
+ desc_long, fencer_options,
+ PCMK__NELEM(fencer_options));
+ printf("%s", s);
+ g_free(s);
+}
+
+static GOptionEntry entries[] = {
+ { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone,
+ "Deprecated (will be removed in a future release)", NULL },
+
+ { "stand-alone-w-cpg", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ stand_alone_cpg_cb, "Intended for use in regression testing only", NULL },
+
+ { "logfile", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY,
+ &options.log_files, "Send logs to the additional named logfile", NULL },
+
+ { NULL }
+};
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
+{
+ GOptionContext *context = NULL;
+
+ context = pcmk__build_arg_context(args, "text (default), xml", group,
+ "[metadata]");
+ pcmk__add_main_args(context, entries);
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc = pcmk_rc_ok;
+ crm_cluster_t *cluster = NULL;
+ crm_ipc_t *old_instance = NULL;
+
+ GError *error = NULL;
+
+ GOptionGroup *output_group = NULL;
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, "l");
+ GOptionContext *context = build_arg_context(args, &output_group);
+
+ crm_log_preinit(NULL, argc, argv);
+
+ pcmk__register_formats(output_group, formats);
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
+ if (rc != pcmk_rc_ok) {
+ exit_code = CRM_EX_ERROR;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Error creating output format %s: %s",
+ args->output_ty, pcmk_rc_str(rc));
+ goto done;
+ }
+
+ if (args->version) {
+ out->version(out, false);
+ goto done;
+ }
+
+ if ((g_strv_length(processed_args) >= 2)
+ && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) {
+ fencer_metadata();
+ goto done;
+ }
+
+ // Open additional log files
+ pcmk__add_logfiles(options.log_files, out);
+
+ crm_log_init(NULL, LOG_INFO + args->verbosity, TRUE,
+ (args->verbosity > 0), argc, argv, FALSE);
+
+ crm_notice("Starting Pacemaker fencer");
+
+ old_instance = crm_ipc_new("stonith-ng", 0);
+ if (old_instance == NULL) {
+ /* crm_ipc_new() will have already logged an error message with
+ * crm_err()
+ */
+ exit_code = CRM_EX_FATAL;
+ goto done;
+ }
+
+ if (crm_ipc_connect(old_instance)) {
+ // IPC endpoint already up
+ crm_ipc_close(old_instance);
+ crm_ipc_destroy(old_instance);
+ crm_err("pacemaker-fenced is already active, aborting startup");
+ goto done;
+ } else {
+ // Not up or not authentic, we'll proceed either way
+ crm_ipc_destroy(old_instance);
+ old_instance = NULL;
+ }
+
+ mainloop_add_signal(SIGTERM, stonith_shutdown);
+
+ crm_peer_init();
+
+ fenced_data_set = pe_new_working_set();
+ CRM_ASSERT(fenced_data_set != NULL);
+
+ cluster = pcmk_cluster_new();
+
+ /* Initialize the logger prior to setup_cib(). update_cib_cache_cb() may
+ * call the "xml-patchset" message function, which needs the logger, after
+ * setup_cib() has run.
+ */
+ rc = pcmk__log_output_new(&logger_out) != pcmk_rc_ok;
+ if (rc != pcmk_rc_ok) {
+ exit_code = CRM_EX_FATAL;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Error creating output format log: %s", pcmk_rc_str(rc));
+ goto done;
+ }
+ pe__register_messages(logger_out);
+ pcmk__register_lib_messages(logger_out);
+ pcmk__output_set_log_level(logger_out, LOG_TRACE);
+ fenced_data_set->priv = logger_out;
+
+ if (!stand_alone) {
+#if SUPPORT_COROSYNC
+ if (is_corosync_cluster()) {
+ cluster->destroy = stonith_peer_cs_destroy;
+ cluster->cpg.cpg_deliver_fn = stonith_peer_ais_callback;
+ cluster->cpg.cpg_confchg_fn = pcmk_cpg_membership;
+ }
+#endif // SUPPORT_COROSYNC
+
+ crm_set_status_callback(&st_peer_update_callback);
+
+ if (crm_cluster_connect(cluster) == FALSE) {
+ exit_code = CRM_EX_FATAL;
+ crm_crit("Cannot sign in to the cluster... terminating");
+ goto done;
+ }
+ pcmk__str_update(&stonith_our_uname, cluster->uname);
+
+ if (!options.no_cib_connect) {
+ setup_cib();
+ }
+
+ } else {
+ pcmk__str_update(&stonith_our_uname, "localhost");
+ crm_warn("Stand-alone mode is deprecated and will be removed "
+ "in a future release");
+ }
+
+ init_device_list();
+ init_topology_list();
+
+ pcmk__serve_fenced_ipc(&ipcs, &ipc_callbacks);
+
+ // Create the mainloop and run it...
+ mainloop = g_main_loop_new(NULL, FALSE);
+ crm_notice("Pacemaker fencer successfully started and accepting connections");
+ g_main_loop_run(mainloop);
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ g_strfreev(options.log_files);
+
+ stonith_cleanup();
+ pcmk_cluster_free(cluster);
+ pe_free_working_set(fenced_data_set);
+
+ pcmk__output_and_clear_error(&error, out);
+
+ if (logger_out != NULL) {
+ logger_out->finish(logger_out, exit_code, true, NULL);
+ pcmk__output_free(logger_out);
+ }
+
+ if (out != NULL) {
+ out->finish(out, exit_code, true, NULL);
+ pcmk__output_free(out);
+ }
+
+ pcmk__unregister_formats();
+ crm_exit(exit_code);
+}
diff --git a/daemons/fenced/pacemaker-fenced.h b/daemons/fenced/pacemaker-fenced.h
new file mode 100644
index 0000000..a3d2e17
--- /dev/null
+++ b/daemons/fenced/pacemaker-fenced.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2009-2023 the Pacemaker project contributors
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include <stdint.h> // uint32_t, uint64_t
+#include <crm/common/mainloop.h>
+
+/*!
+ * \internal
+ * \brief Check whether target has already been fenced recently
+ *
+ * \param[in] tolerance Number of seconds to look back in time
+ * \param[in] target Name of node to search for
+ * \param[in] action Action we want to match
+ *
+ * \return TRUE if an equivalent fencing operation took place in the last
+ * \p tolerance seconds, FALSE otherwise
+ */
+gboolean stonith_check_fence_tolerance(int tolerance, const char *target, const char *action);
+
+typedef struct stonith_device_s {
+ char *id;
+ char *agent;
+ char *namespace;
+
+ /*! list of actions that must execute on the target node. Used for unfencing */
+ GString *on_target_actions;
+ GList *targets;
+ time_t targets_age;
+ gboolean has_attr_map;
+
+ // Whether target's nodeid should be passed as a parameter to the agent
+ gboolean include_nodeid;
+
+ /* whether the cluster should automatically unfence nodes with the device */
+ gboolean automatic_unfencing;
+ guint priority;
+
+ uint32_t flags; // Group of enum st_device_flags
+
+ GHashTable *params;
+ GHashTable *aliases;
+ GList *pending_ops;
+ mainloop_timer_t *timer;
+ crm_trigger_t *work;
+ xmlNode *agent_metadata;
+
+ /*! A verified device is one that has contacted the
+ * agent successfully to perform a monitor operation */
+ gboolean verified;
+
+ gboolean cib_registered;
+ gboolean api_registered;
+ gboolean dirty;
+} stonith_device_t;
+
+/* These values are used to index certain arrays by "phase". Usually an
+ * operation has only one "phase", so phase is always zero. However, some
+ * reboots are remapped to "off" then "on", in which case "reboot" will be
+ * phase 0, "off" will be phase 1 and "on" will be phase 2.
+ */
+enum st_remap_phase {
+ st_phase_requested = 0,
+ st_phase_off = 1,
+ st_phase_on = 2,
+ st_phase_max = 3
+};
+
+typedef struct remote_fencing_op_s {
+ /* The unique id associated with this operation */
+ char *id;
+ /*! The node this operation will fence */
+ char *target;
+ /*! The fencing action to perform on the target. (reboot, on, off) */
+ char *action;
+
+ /*! When was the fencing action recorded (seconds since epoch) */
+ time_t created;
+
+ /*! Marks if the final notifications have been sent to local stonith clients. */
+ gboolean notify_sent;
+ /*! The number of query replies received */
+ guint replies;
+ /*! The number of query replies expected */
+ guint replies_expected;
+ /*! Does this node own control of this operation */
+ gboolean owner;
+ /*! After query is complete, This the high level timer that expires the entire operation */
+ guint op_timer_total;
+ /*! This timer expires the current fencing request. Many fencing
+ * requests may exist in a single operation */
+ guint op_timer_one;
+ /*! This timer expires the query request sent out to determine
+ * what nodes are contain what devices, and who those devices can fence */
+ guint query_timer;
+ /*! This is the default timeout to use for each fencing device if no
+ * custom timeout is received in the query. */
+ gint base_timeout;
+ /*! This is the calculated total timeout an operation can take before
+ * expiring. This is calculated by adding together all the timeout
+ * values associated with the devices this fencing operation may call */
+ gint total_timeout;
+
+ /*! Requested fencing delay.
+ * Value -1 means disable any static/random fencing delays. */
+ int delay;
+
+ /*! Delegate is the node being asked to perform a fencing action
+ * on behalf of the node that owns the remote operation. Some operations
+ * will involve multiple delegates. This value represents the final delegate
+ * that is used. */
+ char *delegate;
+ /*! The point at which the remote operation completed */
+ time_t completed;
+ //! Group of enum stonith_call_options associated with this operation
+ uint32_t call_options;
+
+ /*! The current state of the remote operation. This indicates
+ * what stage the op is in, query, exec, done, duplicate, failed. */
+ enum op_state state;
+ /*! The node that owns the remote operation */
+ char *originator;
+ /*! The local client id that initiated the fencing request */
+ char *client_id;
+ /*! The client's call_id that initiated the fencing request */
+ int client_callid;
+ /*! The name of client that initiated the fencing request */
+ char *client_name;
+ /*! List of the received query results for all the nodes in the cpg group */
+ GList *query_results;
+ /*! The original request that initiated the remote stonith operation */
+ xmlNode *request;
+
+ /*! The current topology level being executed */
+ guint level;
+ /*! The current operation phase being executed */
+ enum st_remap_phase phase;
+
+ /*! Devices with automatic unfencing (always run if "on" requested, never if remapped) */
+ GList *automatic_list;
+ /*! List of all devices at the currently executing topology level */
+ GList *devices_list;
+ /*! Current entry in the topology device list */
+ GList *devices;
+
+ /*! List of duplicate operations attached to this operation. Once this operation
+ * completes, the duplicate operations will be closed out as well. */
+ GList *duplicates;
+
+ /*! The point at which the remote operation completed(nsec) */
+ long long completed_nsec;
+
+ /*! The (potentially intermediate) result of the operation */
+ pcmk__action_result_t result;
+} remote_fencing_op_t;
+
+void fenced_broadcast_op_result(const remote_fencing_op_t *op, bool op_merged);
+
+// Fencer-specific client flags
+enum st_client_flags {
+ st_callback_unknown = UINT64_C(0),
+ st_callback_notify_fence = (UINT64_C(1) << 0),
+ st_callback_device_add = (UINT64_C(1) << 2),
+ st_callback_device_del = (UINT64_C(1) << 4),
+ st_callback_notify_history = (UINT64_C(1) << 5),
+ st_callback_notify_history_synced = (UINT64_C(1) << 6)
+};
+
+// How the user specified the target of a topology level
+enum fenced_target_by {
+ fenced_target_by_unknown = -1, // Invalid or not yet parsed
+ fenced_target_by_name, // By target name
+ fenced_target_by_pattern, // By a pattern matching target names
+ fenced_target_by_attribute, // By a node attribute/value on target
+};
+
+/*
+ * Complex fencing requirements are specified via fencing topologies.
+ * A topology consists of levels; each level is a list of fencing devices.
+ * Topologies are stored in a hash table by node name. When a node needs to be
+ * fenced, if it has an entry in the topology table, the levels are tried
+ * sequentially, and the devices in each level are tried sequentially.
+ * Fencing is considered successful as soon as any level succeeds;
+ * a level is considered successful if all its devices succeed.
+ * Essentially, all devices at a given level are "and-ed" and the
+ * levels are "or-ed".
+ *
+ * This structure is used for the topology table entries.
+ * Topology levels start from 1, so levels[0] is unused and always NULL.
+ */
+typedef struct stonith_topology_s {
+ enum fenced_target_by kind; // How target was specified
+
+ /*! Node name regex or attribute name=value for which topology applies */
+ char *target;
+ char *target_value;
+ char *target_pattern;
+ char *target_attribute;
+
+ /*! Names of fencing devices at each topology level */
+ GList *levels[ST_LEVEL_MAX];
+
+} stonith_topology_t;
+
+void init_device_list(void);
+void free_device_list(void);
+void init_topology_list(void);
+void free_topology_list(void);
+void free_stonith_remote_op_list(void);
+void init_stonith_remote_op_hash_table(GHashTable **table);
+void free_metadata_cache(void);
+void fenced_unregister_handlers(void);
+
+uint64_t get_stonith_flag(const char *name);
+
+void stonith_command(pcmk__client_t *client, uint32_t id, uint32_t flags,
+ xmlNode *op_request, const char *remote_peer);
+
+int stonith_device_register(xmlNode *msg, gboolean from_cib);
+
+void stonith_device_remove(const char *id, bool from_cib);
+
+char *stonith_level_key(const xmlNode *msg, enum fenced_target_by);
+void fenced_register_level(xmlNode *msg, char **desc,
+ pcmk__action_result_t *result);
+void fenced_unregister_level(xmlNode *msg, char **desc,
+ pcmk__action_result_t *result);
+
+stonith_topology_t *find_topology_for_host(const char *host);
+
+void do_local_reply(xmlNode *notify_src, pcmk__client_t *client,
+ int call_options);
+
+xmlNode *fenced_construct_reply(const xmlNode *request, xmlNode *data,
+ const pcmk__action_result_t *result);
+
+void
+ do_stonith_async_timeout_update(const char *client, const char *call_id, int timeout);
+
+void fenced_send_notification(const char *type,
+ const pcmk__action_result_t *result,
+ xmlNode *data);
+void fenced_send_device_notification(const char *op,
+ const pcmk__action_result_t *result,
+ const char *desc);
+void fenced_send_level_notification(const char *op,
+ const pcmk__action_result_t *result,
+ const char *desc);
+
+remote_fencing_op_t *initiate_remote_stonith_op(const pcmk__client_t *client,
+ xmlNode *request,
+ gboolean manual_ack);
+
+void fenced_process_fencing_reply(xmlNode *msg);
+
+int process_remote_stonith_query(xmlNode * msg);
+
+void *create_remote_stonith_op(const char *client, xmlNode * request, gboolean peer);
+
+void stonith_fence_history(xmlNode *msg, xmlNode **output,
+ const char *remote_peer, int options);
+
+void stonith_fence_history_trim(void);
+
+bool fencing_peer_active(crm_node_t *peer);
+
+void set_fencing_completed(remote_fencing_op_t * op);
+
+int fenced_handle_manual_confirmation(const pcmk__client_t *client,
+ xmlNode *msg);
+void fencer_metadata(void);
+
+const char *fenced_device_reboot_action(const char *device_id);
+bool fenced_device_supports_on(const char *device_id);
+
+gboolean node_has_attr(const char *node, const char *name, const char *value);
+
+gboolean node_does_watchdog_fencing(const char *node);
+
+static inline void
+fenced_set_protocol_error(pcmk__action_result_t *result)
+{
+ pcmk__set_result(result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
+ "Fencer API request missing required information (bug?)");
+}
+
+/*!
+ * \internal
+ * \brief Get the device flag to use with a given action when searching devices
+ *
+ * \param[in] action Action to check
+ *
+ * \return st_device_supports_on if \p action is "on", otherwise
+ * st_device_supports_none
+ */
+static inline uint32_t
+fenced_support_flag(const char *action)
+{
+ if (pcmk__str_eq(action, "on", pcmk__str_none)) {
+ return st_device_supports_on;
+ }
+ return st_device_supports_none;
+}
+
+extern char *stonith_our_uname;
+extern gboolean stand_alone;
+extern GHashTable *device_list;
+extern GHashTable *topology;
+extern long stonith_watchdog_timeout_ms;
+extern GList *stonith_watchdog_targets;
+
+extern GHashTable *stonith_remote_op_list;