diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c
index 047af3cc5a..a59dee2356 100644
--- a/daemons/execd/execd_commands.c
+++ b/daemons/execd/execd_commands.c
@@ -1,2010 +1,2010 @@
 /*
  * Copyright 2012-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/fencing/internal.h>
 
 #include <glib.h>
 #include <libxml/tree.h>                // xmlNode
 
 // Check whether we have a high-resolution monotonic clock
 #undef PCMK__TIME_USE_CGT
 #if HAVE_DECL_CLOCK_MONOTONIC && defined(CLOCK_MONOTONIC)
 #  define PCMK__TIME_USE_CGT
 #  include <time.h>  /* clock_gettime */
 #endif
 
 #include <unistd.h>
 
 #include <crm/crm.h>
 #include <crm/fencing/internal.h>
 #include <crm/services.h>
 #include <crm/services_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/ipc.h>
 #include <crm/common/ipc_internal.h>
 #include <crm/common/xml.h>
 
 #include "pacemaker-execd.h"
 
 GHashTable *rsc_list = NULL;
 
 typedef struct lrmd_cmd_s {
     int timeout;
     guint interval_ms;
     int start_delay;
     int timeout_orig;
 
     int call_id;
 
     int call_opts;
     /* Timer ids, must be removed on cmd destruction. */
     int delay_id;
     int stonith_recurring_id;
 
     int rsc_deleted;
 
     int service_flags;
 
     char *client_id;
     char *origin;
     char *rsc_id;
     char *action;
     char *real_action;
     char *userdata_str;
 
     pcmk__action_result_t result;
 
     /* We can track operation queue time and run time, to be saved with the CIB
      * resource history (and displayed in cluster status). We need
      * high-resolution monotonic time for this purpose, so we use
      * clock_gettime(CLOCK_MONOTONIC, ...) (if available, otherwise this feature
      * is disabled).
      *
      * However, we also need epoch timestamps for recording the time the command
      * last ran and the time its return value last changed, for use in time
      * displays (as opposed to interval calculations). We keep time_t values for
      * this purpose.
      *
      * The last run time is used for both purposes, so we keep redundant
      * monotonic and epoch values for this. Technically the two could represent
      * different times, but since time_t has only second resolution and the
      * values are used for distinct purposes, that is not significant.
      */
 #ifdef PCMK__TIME_USE_CGT
     /* Recurring and systemd operations may involve more than one executor
      * command per operation, so they need info about the original and the most
      * recent.
      */
     struct timespec t_first_run;    // When op first ran
     struct timespec t_run;          // When op most recently ran
     struct timespec t_first_queue;  // When op was first queued
     struct timespec t_queue;        // When op was most recently queued
 #endif
     time_t epoch_last_run;          // Epoch timestamp of when op last ran
     time_t epoch_rcchange;          // Epoch timestamp of when rc last changed
 
     bool first_notify_sent;
     int last_notify_rc;
     int last_notify_op_status;
     int last_pid;
 
     GHashTable *params;
 } lrmd_cmd_t;
 
 static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc);
 static gboolean execute_resource_action(gpointer user_data);
 static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id);
 
 #ifdef PCMK__TIME_USE_CGT
 
 /*!
  * \internal
  * \brief Check whether a struct timespec has been set
  *
  * \param[in] timespec  Time to check
  *
  * \return true if timespec has been set (i.e. is nonzero), false otherwise
  */
 static inline bool
 time_is_set(const struct timespec *timespec)
 {
     return (timespec != NULL) &&
            ((timespec->tv_sec != 0) || (timespec->tv_nsec != 0));
 }
 
 /*
  * \internal
  * \brief Set a timespec (and its original if unset) to the current time
  *
  * \param[out] t_current  Where to store current time
  * \param[out] t_orig     Where to copy t_current if unset
  */
 static void
 get_current_time(struct timespec *t_current, struct timespec *t_orig)
 {
     clock_gettime(CLOCK_MONOTONIC, t_current);
     if ((t_orig != NULL) && !time_is_set(t_orig)) {
         *t_orig = *t_current;
     }
 }
 
 /*!
  * \internal
  * \brief Return difference between two times in milliseconds
  *
  * \param[in] now  More recent time (or NULL to use current time)
  * \param[in] old  Earlier time
  *
  * \return milliseconds difference (or 0 if old is NULL or unset)
  *
  * \note Can overflow on 32bit machines when the differences is around
  *       24 days or more.
  */
 static int
 time_diff_ms(const struct timespec *now, const struct timespec *old)
 {
     int diff_ms = 0;
 
     if (time_is_set(old)) {
         struct timespec local_now = { 0, };
 
         if (now == NULL) {
             clock_gettime(CLOCK_MONOTONIC, &local_now);
             now = &local_now;
         }
         diff_ms = (now->tv_sec - old->tv_sec) * 1000
                   + (now->tv_nsec - old->tv_nsec) / 1000000;
     }
     return diff_ms;
 }
 
 /*!
  * \internal
  * \brief Reset a command's operation times to their original values.
  *
  * Reset a command's run and queued timestamps to the timestamps of the original
  * command, so we report the entire time since then and not just the time since
  * the most recent command (for recurring and systemd operations).
  *
  * \param[in,out] cmd  Executor command object to reset
  *
  * \note It's not obvious what the queued time should be for a systemd
  *       start/stop operation, which might go like this:
  *         initial command queued 5ms, runs 3s
  *         monitor command queued 10ms, runs 10s
  *         monitor command queued 10ms, runs 10s
  *       Is the queued time for that operation 5ms, 10ms or 25ms? The current
  *       implementation will report 5ms. If it's 25ms, then we need to
  *       subtract 20ms from the total exec time so as not to count it twice.
  *       We can implement that later if it matters to anyone ...
  */
 static void
 cmd_original_times(lrmd_cmd_t * cmd)
 {
     cmd->t_run = cmd->t_first_run;
     cmd->t_queue = cmd->t_first_queue;
 }
 #endif
 
 static inline bool
 action_matches(const lrmd_cmd_t *cmd, const char *action, guint interval_ms)
 {
     return (cmd->interval_ms == interval_ms)
            && pcmk__str_eq(cmd->action, action, pcmk__str_casei);
 }
 
 /*!
  * \internal
  * \brief Log the result of an asynchronous command
  *
  * \param[in] cmd            Command to log result for
  * \param[in] exec_time_ms   Execution time in milliseconds, if known
  * \param[in] queue_time_ms  Queue time in milliseconds, if known
  */
 static void
 log_finished(const lrmd_cmd_t *cmd, int exec_time_ms, int queue_time_ms)
 {
     int log_level = LOG_INFO;
     GString *str = g_string_sized_new(100); // reasonable starting size
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         log_level = LOG_DEBUG;
     }
 
     g_string_append_printf(str, "%s %s (call %d",
                            cmd->rsc_id, cmd->action, cmd->call_id);
     if (cmd->last_pid != 0) {
         g_string_append_printf(str, ", PID %d", cmd->last_pid);
     }
     switch (cmd->result.execution_status) {
         case PCMK_EXEC_DONE:
             g_string_append_printf(str, ") exited with status %d",
                                    cmd->result.exit_status);
             break;
         case PCMK_EXEC_CANCELLED:
             g_string_append_printf(str, ") cancelled");
             break;
         default:
             pcmk__g_strcat(str, ") could not be executed: ",
                            pcmk_exec_status_str(cmd->result.execution_status),
                            NULL);
             break;
     }
     if (cmd->result.exit_reason != NULL) {
         pcmk__g_strcat(str, " (", cmd->result.exit_reason, ")", NULL);
     }
 
 #ifdef PCMK__TIME_USE_CGT
     pcmk__g_strcat(str, " (execution time ",
                    pcmk__readable_interval(exec_time_ms), NULL);
     if (queue_time_ms > 0) {
         pcmk__g_strcat(str, " after being queued ",
                        pcmk__readable_interval(queue_time_ms), NULL);
     }
     g_string_append_c(str, ')');
 #endif
 
     do_crm_log(log_level, "%s", str->str);
     g_string_free(str, TRUE);
 }
 
 static void
 log_execute(lrmd_cmd_t * cmd)
 {
     int log_level = LOG_INFO;
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         log_level = LOG_DEBUG;
     }
 
     do_crm_log(log_level, "executing - rsc:%s action:%s call_id:%d",
                cmd->rsc_id, cmd->action, cmd->call_id);
 }
 
 static const char *
 normalize_action_name(lrmd_rsc_t * rsc, const char *action)
 {
     if (pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei) &&
         pcmk_is_set(pcmk_get_ra_caps(rsc->class), pcmk_ra_cap_status)) {
         return PCMK_ACTION_STATUS;
     }
     return action;
 }
 
 static lrmd_rsc_t *
 build_rsc_from_xml(xmlNode * msg)
 {
     xmlNode *rsc_xml = pcmk__xpath_find_one(msg->doc, "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     lrmd_rsc_t *rsc = NULL;
 
     rsc = pcmk__assert_alloc(1, sizeof(lrmd_rsc_t));
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &rsc->call_opts);
 
     rsc->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     rsc->class = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_CLASS);
     rsc->provider = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_PROVIDER);
     rsc->type = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_TYPE);
     rsc->work = mainloop_add_trigger(G_PRIORITY_HIGH, execute_resource_action,
                                      rsc);
 
     // Initialize fence device probes (to return "not running")
     pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                      PCMK_EXEC_NO_FENCE_DEVICE, NULL);
     return rsc;
 }
 
 static lrmd_cmd_t *
 create_lrmd_cmd(xmlNode *msg, pcmk__client_t *client)
 {
     int call_options = 0;
     xmlNode *rsc_xml = pcmk__xpath_find_one(msg->doc, "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     lrmd_cmd_t *cmd = NULL;
 
     cmd = pcmk__assert_alloc(1, sizeof(lrmd_cmd_t));
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &call_options);
     cmd->call_opts = call_options;
     cmd->client_id = pcmk__str_copy(client->id);
 
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLID, &cmd->call_id);
     crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL,
                          &cmd->interval_ms);
     crm_element_value_int(rsc_xml, PCMK__XA_LRMD_TIMEOUT, &cmd->timeout);
     crm_element_value_int(rsc_xml, PCMK__XA_LRMD_RSC_START_DELAY,
                           &cmd->start_delay);
     cmd->timeout_orig = cmd->timeout;
 
     cmd->origin = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_ORIGIN);
     cmd->action = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
     cmd->userdata_str = crm_element_value_copy(rsc_xml,
                                                PCMK__XA_LRMD_RSC_USERDATA_STR);
     cmd->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
 
     cmd->params = xml2list(rsc_xml);
 
     if (pcmk__str_eq(g_hash_table_lookup(cmd->params, "CRM_meta_on_fail"),
                      PCMK_VALUE_BLOCK, pcmk__str_casei)) {
         crm_debug("Setting flag to leave pid group on timeout and "
                   "only kill action pid for " PCMK__OP_FMT,
                   cmd->rsc_id, cmd->action, cmd->interval_ms);
         cmd->service_flags = pcmk__set_flags_as(__func__, __LINE__,
                                                 LOG_TRACE, "Action",
                                                 cmd->action, 0,
                                                 SVC_ACTION_LEAVE_GROUP,
                                                 "SVC_ACTION_LEAVE_GROUP");
     }
     return cmd;
 }
 
 static void
 stop_recurring_timer(lrmd_cmd_t *cmd)
 {
     if (cmd) {
         if (cmd->stonith_recurring_id) {
             g_source_remove(cmd->stonith_recurring_id);
         }
         cmd->stonith_recurring_id = 0;
     }
 }
 
 static void
 free_lrmd_cmd(lrmd_cmd_t * cmd)
 {
     stop_recurring_timer(cmd);
     if (cmd->delay_id) {
         g_source_remove(cmd->delay_id);
     }
     if (cmd->params) {
         g_hash_table_destroy(cmd->params);
     }
     pcmk__reset_result(&(cmd->result));
     free(cmd->origin);
     free(cmd->action);
     free(cmd->real_action);
     free(cmd->userdata_str);
     free(cmd->rsc_id);
     free(cmd->client_id);
     free(cmd);
 }
 
 static gboolean
 stonith_recurring_op_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc;
 
     cmd->stonith_recurring_id = 0;
 
     if (!cmd->rsc_id) {
         return FALSE;
     }
 
     rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     pcmk__assert(rsc != NULL);
     /* take it out of recurring_ops list, and put it in the pending ops
      * to be executed */
     rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef PCMK__TIME_USE_CGT
     get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
 #endif
     mainloop_set_trigger(rsc->work);
 
     return FALSE;
 }
 
 static inline void
 start_recurring_timer(lrmd_cmd_t *cmd)
 {
     if (!cmd || (cmd->interval_ms <= 0)) {
         return;
     }
 
     cmd->stonith_recurring_id = pcmk__create_timer(cmd->interval_ms,
                                                    stonith_recurring_op_helper,
                                                    cmd);
 }
 
 static gboolean
 start_delay_helper(gpointer data)
 {
     lrmd_cmd_t *cmd = data;
     lrmd_rsc_t *rsc = NULL;
 
     cmd->delay_id = 0;
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
     if (rsc) {
         mainloop_set_trigger(rsc->work);
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Check whether a list already contains the equivalent of a given action
  *
  * \param[in] action_list  List to search
  * \param[in] cmd          Action to search for
  */
 static lrmd_cmd_t *
 find_duplicate_action(const GList *action_list, const lrmd_cmd_t *cmd)
 {
     for (const GList *item = action_list; item != NULL; item = item->next) {
         lrmd_cmd_t *dup = item->data;
 
         if (action_matches(cmd, dup->action, dup->interval_ms)) {
             return dup;
         }
     }
     return NULL;
 }
 
 static bool
 merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     lrmd_cmd_t * dup = NULL;
     bool dup_pending = true;
 
     if (cmd->interval_ms == 0) {
         return false;
     }
 
     // Search for a duplicate of this action (in-flight or not)
     dup = find_duplicate_action(rsc->pending_ops, cmd);
     if (dup == NULL) {
         dup_pending = false;
         dup = find_duplicate_action(rsc->recurring_ops, cmd);
         if (dup == NULL) {
             return false;
         }
     }
 
     /* Do not merge fencing monitors marked for cancellation, so we can reply to
      * the cancellation separately.
      */
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                      pcmk__str_casei)
         && (dup->result.execution_status == PCMK_EXEC_CANCELLED)) {
         return false;
     }
 
     /* This should not occur. If it does, we need to investigate how something
      * like this is possible in the controller.
      */
     crm_warn("Duplicate recurring op entry detected (" PCMK__OP_FMT
              "), merging with previous op entry",
              rsc->rsc_id, normalize_action_name(rsc, dup->action),
              dup->interval_ms);
 
     // Merge new action's call ID and user data into existing action
     dup->first_notify_sent = false;
     free(dup->userdata_str);
     dup->userdata_str = cmd->userdata_str;
     cmd->userdata_str = NULL;
     dup->call_id = cmd->call_id;
     free_lrmd_cmd(cmd);
     cmd = NULL;
 
     /* If dup is not pending, that means it has already executed at least once
      * and is waiting in the interval. In that case, stop waiting and initiate
      * a new instance now.
      */
     if (!dup_pending) {
         if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                          pcmk__str_casei)) {
             stop_recurring_timer(dup);
             stonith_recurring_op_helper(dup);
         } else {
             services_action_kick(rsc->rsc_id,
                                  normalize_action_name(rsc, dup->action),
                                  dup->interval_ms);
         }
     }
     return true;
 }
 
 static void
 schedule_lrmd_cmd(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
 {
     CRM_CHECK(cmd != NULL, return);
     CRM_CHECK(rsc != NULL, return);
 
     crm_trace("Scheduling %s on %s", cmd->action, rsc->rsc_id);
 
     if (merge_recurring_duplicate(rsc, cmd)) {
         // Equivalent of cmd has already been scheduled
         return;
     }
 
     /* The controller expects the executor to automatically cancel
      * recurring operations before a resource stops.
      */
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
         cancel_all_recurring(rsc, NULL);
     }
 
     rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
 #ifdef PCMK__TIME_USE_CGT
     get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
 #endif
     mainloop_set_trigger(rsc->work);
 
     if (cmd->start_delay) {
         cmd->delay_id = pcmk__create_timer(cmd->start_delay, start_delay_helper, cmd);
     }
 }
 
 static xmlNode *
 create_lrmd_reply(const char *origin, int rc, int call_id)
 {
     xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_LRMD_REPLY);
 
     crm_xml_add(reply, PCMK__XA_LRMD_ORIGIN, origin);
     crm_xml_add_int(reply, PCMK__XA_LRMD_RC, rc);
     crm_xml_add_int(reply, PCMK__XA_LRMD_CALLID, call_id);
     return reply;
 }
 
 static void
 send_client_notify(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNode *update_msg = user_data;
     pcmk__client_t *client = value;
     int rc;
     int log_level = LOG_WARNING;
     const char *msg = NULL;
 
     CRM_CHECK(client != NULL, return);
     if (client->name == NULL) {
         crm_trace("Skipping notification to client without name");
         return;
     }
     if (pcmk_is_set(client->flags, pcmk__client_to_proxy)) {
         /* We only want to notify clients of the executor IPC API. If we are
          * running as Pacemaker Remote, we may have clients proxied to other
          * IPC services in the cluster, so skip those.
          */
         crm_trace("Skipping executor API notification to client %s",
                   pcmk__client_name(client));
         return;
     }
 
     rc = lrmd_server_send_notify(client, update_msg);
     if (rc == pcmk_rc_ok) {
         return;
     }
 
     switch (rc) {
         case ENOTCONN:
         case EPIPE: // Client exited without waiting for notification
             log_level = LOG_INFO;
             msg = "Disconnected";
             break;
 
         default:
             msg = pcmk_rc_str(rc);
             break;
     }
     do_crm_log(log_level, "Could not notify client %s: %s " QB_XS " rc=%d",
                pcmk__client_name(client), msg, rc);
 }
 
 static void
 send_cmd_complete_notify(lrmd_cmd_t * cmd)
 {
     xmlNode *notify = NULL;
     int exec_time = 0;
     int queue_time = 0;
 
 #ifdef PCMK__TIME_USE_CGT
     exec_time = time_diff_ms(NULL, &(cmd->t_run));
     queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue));
 #endif
     log_finished(cmd, exec_time, queue_time);
 
     /* If the originator requested to be notified only for changes in recurring
      * operation results, skip the notification if the result hasn't changed.
      */
     if (cmd->first_notify_sent
         && pcmk_is_set(cmd->call_opts, lrmd_opt_notify_changes_only)
         && (cmd->last_notify_rc == cmd->result.exit_status)
         && (cmd->last_notify_op_status == cmd->result.execution_status)) {
         return;
     }
 
     cmd->first_notify_sent = true;
     cmd->last_notify_rc = cmd->result.exit_status;
     cmd->last_notify_op_status = cmd->result.execution_status;
 
     notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
 
     crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add_int(notify, PCMK__XA_LRMD_TIMEOUT, cmd->timeout);
     crm_xml_add_ms(notify, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
     crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_START_DELAY, cmd->start_delay);
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_RC, cmd->result.exit_status);
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_OP_STATUS,
                     cmd->result.execution_status);
     crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, cmd->call_id);
     crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_DELETED, cmd->rsc_deleted);
 
     crm_xml_add_ll(notify, PCMK__XA_LRMD_RUN_TIME,
                    (long long) cmd->epoch_last_run);
     crm_xml_add_ll(notify, PCMK__XA_LRMD_RCCHANGE_TIME,
                    (long long) cmd->epoch_rcchange);
 #ifdef PCMK__TIME_USE_CGT
     crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_TIME, exec_time);
     crm_xml_add_int(notify, PCMK__XA_LRMD_QUEUE_TIME, queue_time);
 #endif
 
     crm_xml_add(notify, PCMK__XA_LRMD_OP, LRMD_OP_RSC_EXEC);
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, cmd->rsc_id);
     if(cmd->real_action) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->real_action);
     } else {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->action);
     }
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_USERDATA_STR, cmd->userdata_str);
     crm_xml_add(notify, PCMK__XA_LRMD_RSC_EXIT_REASON, cmd->result.exit_reason);
 
     if (cmd->result.action_stderr != NULL) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
                     cmd->result.action_stderr);
 
     } else if (cmd->result.action_stdout != NULL) {
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
                     cmd->result.action_stdout);
     }
 
     if (cmd->params) {
         char *key = NULL;
         char *value = NULL;
         GHashTableIter iter;
 
         xmlNode *args = pcmk__xe_create(notify, PCMK__XE_ATTRIBUTES);
 
         g_hash_table_iter_init(&iter, cmd->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             hash2smartfield((gpointer) key, (gpointer) value, args);
         }
     }
     if ((cmd->client_id != NULL)
         && pcmk_is_set(cmd->call_opts, lrmd_opt_notify_orig_only)) {
 
         pcmk__client_t *client = pcmk__find_client_by_id(cmd->client_id);
 
         if (client != NULL) {
             send_client_notify(client->id, client, notify);
         }
     } else {
         pcmk__foreach_ipc_client(send_client_notify, notify);
     }
 
     pcmk__xml_free(notify);
 }
 
 static void
 send_generic_notify(int rc, xmlNode * request)
 {
     if (pcmk__ipc_client_count() != 0) {
         int call_id = 0;
         xmlNode *notify = NULL;
         xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
                                                 "//" PCMK__XE_LRMD_RSC,
                                                 LOG_ERR);
         const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
         const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
 
         crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
 
         notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
         crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
         crm_xml_add_int(notify, PCMK__XA_LRMD_RC, rc);
         crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, call_id);
         crm_xml_add(notify, PCMK__XA_LRMD_OP, op);
         crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, rsc_id);
 
         pcmk__foreach_ipc_client(send_client_notify, notify);
 
         pcmk__xml_free(notify);
     }
 }
 
 static void
 cmd_reset(lrmd_cmd_t * cmd)
 {
     cmd->last_pid = 0;
 #ifdef PCMK__TIME_USE_CGT
     memset(&cmd->t_run, 0, sizeof(cmd->t_run));
     memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
 #endif
     cmd->epoch_last_run = 0;
 
     pcmk__reset_result(&(cmd->result));
     cmd->result.execution_status = PCMK_EXEC_DONE;
 }
 
 static void
 cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
 {
     crm_trace("Resource operation rsc:%s action:%s completed (%p %p)", cmd->rsc_id, cmd->action,
               rsc ? rsc->active : NULL, cmd);
 
     if (rsc && (rsc->active == cmd)) {
         rsc->active = NULL;
         mainloop_set_trigger(rsc->work);
     }
 
     if (!rsc) {
         cmd->rsc_deleted = 1;
     }
 
     /* reset original timeout so client notification has correct information */
     cmd->timeout = cmd->timeout_orig;
 
     send_cmd_complete_notify(cmd);
 
     if ((cmd->interval_ms != 0)
         && (cmd->result.execution_status == PCMK_EXEC_CANCELLED)) {
 
         if (rsc) {
             rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else if (cmd->interval_ms == 0) {
         if (rsc) {
             rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
         }
         free_lrmd_cmd(cmd);
     } else {
         /* Clear all the values pertaining just to the last iteration of a recurring op. */
         cmd_reset(cmd);
     }
 }
 
 struct notify_new_client_data {
     xmlNode *notify;
     pcmk__client_t *new_client;
 };
 
 static void
 notify_one_client(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk__client_t *client = value;
     struct notify_new_client_data *data = user_data;
 
     if (!pcmk__str_eq(client->id, data->new_client->id, pcmk__str_casei)) {
         send_client_notify(key, (gpointer) client, (gpointer) data->notify);
     }
 }
 
 void
 notify_of_new_client(pcmk__client_t *new_client)
 {
     struct notify_new_client_data data;
 
     data.new_client = new_client;
     data.notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
     crm_xml_add(data.notify, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data.notify, PCMK__XA_LRMD_OP, LRMD_OP_NEW_CLIENT);
     pcmk__foreach_ipc_client(notify_one_client, &data);
     pcmk__xml_free(data.notify);
 }
 
 void
 client_disconnect_cleanup(const char *client_id)
 {
     GHashTableIter iter;
     lrmd_rsc_t *rsc = NULL;
     char *key = NULL;
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) {
         if (pcmk_all_flags_set(rsc->call_opts, lrmd_opt_drop_recurring)) {
             /* This client is disconnecting, drop any recurring operations
              * it may have initiated on the resource */
             cancel_all_recurring(rsc, client_id);
         }
     }
 }
 
 static void
 action_complete(svc_action_t * action)
 {
     lrmd_rsc_t *rsc;
     lrmd_cmd_t *cmd = action->cb_data;
     enum ocf_exitcode code;
 
 #ifdef PCMK__TIME_USE_CGT
     const char *rclass = NULL;
     bool goagain = false;
     int time_sum = 0;
     int timeout_left = 0;
     int delay = 0;
 #endif
 
     if (!cmd) {
         crm_err("Completed executor action (%s) does not match any known operations",
                 action->id);
         return;
     }
 
 #ifdef PCMK__TIME_USE_CGT
     if (cmd->result.exit_status != action->rc) {
         cmd->epoch_rcchange = time(NULL);
     }
 #endif
 
     cmd->last_pid = action->pid;
 
     // Cast variable instead of function return to keep compilers happy
     code = services_result2ocf(action->standard, cmd->action, action->rc);
     pcmk__set_result(&(cmd->result), (int) code,
                      action->status, services__exit_reason(action));
 
     rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
 
 #ifdef PCMK__TIME_USE_CGT
     if (rsc != NULL) {
         rclass = rsc->class;
 #if PCMK__ENABLE_SERVICE
         if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SERVICE,
                          pcmk__str_casei)) {
             rclass = resources_find_service_class(rsc->type);
         }
 #endif
     }
 
     if (!pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
         goto finalize;
     }
 
     if (pcmk__result_ok(&(cmd->result))
         && pcmk__strcase_any_of(cmd->action, PCMK_ACTION_START,
                                 PCMK_ACTION_STOP, NULL)) {
         /* Getting results for when a start or stop action completes is now
          * handled by watching for JobRemoved() signals from systemd and
          * reacting to them. So, we can bypass the rest of the code in this
          * function for those actions, and simply finalize cmd.
          *
          * @TODO When monitors are handled in the same way, this function
          * can either be drastically simplified or done away with entirely.
          */
         services__copy_result(action, &(cmd->result));
         goto finalize;
 
     } else if (cmd->result.execution_status == PCMK_EXEC_PENDING &&
                pcmk__str_any_of(cmd->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS, NULL) &&
                cmd->interval_ms == 0 &&
                cmd->real_action == NULL) {
         /* If the state is Pending at the time of probe, execute follow-up monitor. */
         goagain = true;
         cmd->real_action = cmd->action;
         cmd->action = pcmk__str_copy(PCMK_ACTION_MONITOR);
     } else if (cmd->real_action != NULL) {
         // This is follow-up monitor to check whether start/stop/probe(monitor) completed
         if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
             goagain = true;
 
         } else if (pcmk__result_ok(&(cmd->result))
                    && pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
                                    pcmk__str_casei)) {
             goagain = true;
 
         } else {
             int time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
             int timeout_left = cmd->timeout_orig - time_sum;
 
             crm_debug("%s systemd %s is now complete (elapsed=%dms, "
                       "remaining=%dms): %s (%d)",
                       cmd->rsc_id, cmd->real_action, time_sum, timeout_left,
                       crm_exit_str(cmd->result.exit_status),
                       cmd->result.exit_status);
             cmd_original_times(cmd);
 
             // Monitors may return "not running", but start/stop shouldn't
             if ((cmd->result.execution_status == PCMK_EXEC_DONE)
                 && (cmd->result.exit_status == PCMK_OCF_NOT_RUNNING)) {
 
                 if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_START,
                                  pcmk__str_casei)) {
                     cmd->result.exit_status = PCMK_OCF_UNKNOWN_ERROR;
                 } else if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
                                         pcmk__str_casei)) {
                     cmd->result.exit_status = PCMK_OCF_OK;
                 }
             }
         }
     } else if (pcmk__str_any_of(cmd->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS, NULL)
                && (cmd->interval_ms > 0)) {
         /* For monitors, excluding follow-up monitors,                                  */
         /* if the pending state persists from the first notification until its timeout, */
         /* it will be treated as a timeout.                                             */
 
         if ((cmd->result.execution_status == PCMK_EXEC_PENDING) &&
             (cmd->last_notify_op_status == PCMK_EXEC_PENDING)) {
             int time_left = time(NULL) - (cmd->epoch_rcchange + (cmd->timeout_orig/1000));
 
             if (time_left >= 0) {
                 crm_notice("Giving up on %s %s (rc=%d): monitor pending timeout "
                            "(first pending notification=%s timeout=%ds)",
                            cmd->rsc_id, cmd->action, cmd->result.exit_status,
                            pcmk__trim(ctime(&cmd->epoch_rcchange)), cmd->timeout_orig);
                 pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
                                  PCMK_EXEC_TIMEOUT,
                                  "Investigate reason for timeout, and adjust "
                                  "configured operation timeout if necessary");
                 cmd_original_times(cmd);
             }
         }
     }
 
     if (!goagain) {
         goto finalize;
     }
 
     time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
     timeout_left = cmd->timeout_orig - time_sum;
     delay = cmd->timeout_orig / 10;
 
     if (delay >= timeout_left && timeout_left > 20) {
         delay = timeout_left/2;
     }
 
     delay = QB_MIN(2000, delay);
     if (delay < timeout_left) {
         cmd->start_delay = delay;
         cmd->timeout = timeout_left;
 
         if (pcmk__result_ok(&(cmd->result))) {
             crm_debug("%s %s may still be in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
                       cmd->rsc_id, cmd->real_action, time_sum, timeout_left, delay);
 
         } else if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
             crm_info("%s %s is still in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
                      cmd->rsc_id, cmd->action, time_sum, timeout_left, delay);
 
         } else {
             crm_notice("%s %s failed: %s: Re-scheduling (remaining "
                        "timeout %s) " QB_XS
                        " exitstatus=%d elapsed=%dms start_delay=%dms)",
                        cmd->rsc_id, cmd->action,
                        crm_exit_str(cmd->result.exit_status),
                        pcmk__readable_interval(timeout_left),
                        cmd->result.exit_status, time_sum, delay);
         }
 
         cmd_reset(cmd);
         if (rsc) {
             rsc->active = NULL;
         }
         schedule_lrmd_cmd(rsc, cmd);
 
         /* Don't finalize cmd, we're not done with it yet */
         return;
 
     } else {
         crm_notice("Giving up on %s %s (rc=%d): timeout (elapsed=%dms, remaining=%dms)",
                    cmd->rsc_id,
                    (cmd->real_action? cmd->real_action : cmd->action),
                    cmd->result.exit_status, time_sum, timeout_left);
         pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
                          PCMK_EXEC_TIMEOUT,
                          "Investigate reason for timeout, and adjust "
                          "configured operation timeout if necessary");
         cmd_original_times(cmd);
     }
 #endif
 
 finalize:
     pcmk__set_result_output(&(cmd->result), services__grab_stdout(action),
                             services__grab_stderr(action));
     cmd_finalize(cmd, rsc);
 }
 
 /*!
  * \internal
  * \brief Process the result of a fence device action (start, stop, or monitor)
  *
  * \param[in,out] cmd               Fence device action that completed
  * \param[in]     exit_status       Fencer API exit status for action
  * \param[in]     execution_status  Fencer API execution status for action
  * \param[in]     exit_reason       Human-friendly detail, if action failed
  */
 static void
 stonith_action_complete(lrmd_cmd_t *cmd, int exit_status,
                         enum pcmk_exec_status execution_status,
                         const char *exit_reason)
 {
     // This can be NULL if resource was removed before command completed
     lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
 
     // Simplify fencer exit status to uniform exit status
     if (exit_status != CRM_EX_OK) {
         exit_status = PCMK_OCF_UNKNOWN_ERROR;
     }
 
     if (cmd->result.execution_status == PCMK_EXEC_CANCELLED) {
         /* An in-flight fence action was cancelled. The execution status is
          * already correct, so don't overwrite it.
          */
         execution_status = PCMK_EXEC_CANCELLED;
 
     } else {
         /* Some execution status codes have specific meanings for the fencer
          * that executor clients may not expect, so map them to a simple error
          * status.
          */
         switch (execution_status) {
             case PCMK_EXEC_NOT_CONNECTED:
             case PCMK_EXEC_INVALID:
                 execution_status = PCMK_EXEC_ERROR;
                 break;
 
             case PCMK_EXEC_NO_FENCE_DEVICE:
                 /* This should be possible only for probes in practice, but
                  * interpret for all actions to be safe.
                  */
                 if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
                                  pcmk__str_none)) {
                     exit_status = PCMK_OCF_NOT_RUNNING;
 
                 } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
                                         pcmk__str_none)) {
                     exit_status = PCMK_OCF_OK;
 
                 } else {
                     exit_status = PCMK_OCF_NOT_INSTALLED;
                 }
                 execution_status = PCMK_EXEC_ERROR;
                 break;
 
             case PCMK_EXEC_NOT_SUPPORTED:
                 exit_status = PCMK_OCF_UNIMPLEMENT_FEATURE;
                 break;
 
             default:
                 break;
         }
     }
 
     pcmk__set_result(&cmd->result, exit_status, execution_status, exit_reason);
 
     // Certain successful actions change the known state of the resource
     if ((rsc != NULL) && pcmk__result_ok(&(cmd->result))) {
 
         if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_OK,
                              PCMK_EXEC_DONE, NULL); // "running"
 
         } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
                                 pcmk__str_casei)) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                              PCMK_EXEC_NO_FENCE_DEVICE, NULL); // "not running"
         }
     }
 
     /* The recurring timer should not be running at this point in any case, but
      * as a failsafe, stop it if it is.
      */
     stop_recurring_timer(cmd);
 
     /* Reschedule this command if appropriate. If a recurring command is *not*
      * rescheduled, its status must be PCMK_EXEC_CANCELLED, otherwise it will
      * not be removed from recurring_ops by cmd_finalize().
      */
     if (rsc && (cmd->interval_ms > 0)
         && (cmd->result.execution_status != PCMK_EXEC_CANCELLED)) {
         start_recurring_timer(cmd);
     }
 
     cmd_finalize(cmd, rsc);
 }
 
 static void
 lrmd_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data)
 {
     if ((data == NULL) || (data->userdata == NULL)) {
         crm_err("Ignoring fence action result: "
                 "Invalid callback arguments (bug?)");
     } else {
         stonith_action_complete((lrmd_cmd_t *) data->userdata,
                                 stonith__exit_status(data),
                                 stonith__execution_status(data),
                                 stonith__exit_reason(data));
     }
 }
 
 void
 stonith_connection_failed(void)
 {
     GHashTableIter iter;
     lrmd_rsc_t *rsc = NULL;
 
     crm_warn("Connection to fencer lost (any pending operations for "
              "fence devices will be considered failed)");
 
     g_hash_table_iter_init(&iter, rsc_list);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &rsc)) {
         if (!pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                           pcmk__str_none)) {
             continue;
         }
 
         /* If we registered this fence device, we don't know whether the
          * fencer still has the registration or not. Cause future probes to
          * return an error until the resource is stopped or started
          * successfully. This is especially important if the controller also
          * went away (possibly due to a cluster layer restart) and won't
          * receive our client notification of any monitors finalized below.
          */
         if (rsc->fence_probe_result.execution_status == PCMK_EXEC_DONE) {
             pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
                              PCMK_EXEC_NOT_CONNECTED,
                              "Lost connection to fencer");
         }
 
         // Consider any active, pending, or recurring operations as failed
 
         for (GList *op = rsc->recurring_ops; op != NULL; op = op->next) {
             lrmd_cmd_t *cmd = op->data;
 
             /* This won't free a recurring op but instead restart its timer.
              * If cmd is rsc->active, this will set rsc->active to NULL, so we
              * don't have to worry about finalizing it a second time below.
              */
             stonith_action_complete(cmd,
                                     CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
                                     "Lost connection to fencer");
         }
 
         if (rsc->active != NULL) {
             rsc->pending_ops = g_list_prepend(rsc->pending_ops, rsc->active);
         }
         while (rsc->pending_ops != NULL) {
             // This will free the op and remove it from rsc->pending_ops
             stonith_action_complete((lrmd_cmd_t *) rsc->pending_ops->data,
                                     CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
                                     "Lost connection to fencer");
         }
     }
 }
 
 /*!
  * \internal
  * \brief Execute a stonith resource "start" action
  *
  * Start a stonith resource by registering it with the fencer.
  * (Stonith agents don't have a start command.)
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in]     rsc          Stonith resource to start
  * \param[in]     cmd          Start command to execute
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 static int
 execd_stonith_start(stonith_t *stonith_api, const lrmd_rsc_t *rsc,
                     const lrmd_cmd_t *cmd)
 {
     char *key = NULL;
     char *value = NULL;
     stonith_key_value_t *device_params = NULL;
     int rc = pcmk_ok;
 
     // Convert command parameters to stonith API key/values
     if (cmd->params) {
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, cmd->params);
         while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
             device_params = stonith__key_value_add(device_params, key, value);
         }
     }
 
     /* The fencer will automatically register devices via CIB notifications
      * when the CIB changes, but to avoid a possible race condition between
      * the fencer receiving the notification and the executor requesting that
      * resource, the executor registers the device as well. The fencer knows how
      * to handle duplicate registrations.
      */
     rc = stonith_api->cmds->register_device(stonith_api, st_opt_sync_call,
                                             cmd->rsc_id, rsc->provider,
                                             rsc->type, device_params);
 
-    stonith_key_value_freeall(device_params, 1, 1);
+    stonith__key_value_freeall(device_params, true, true);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Execute a stonith resource "stop" action
  *
  * Stop a stonith resource by unregistering it with the fencer.
  * (Stonith agents don't have a stop command.)
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in]     rsc          Stonith resource to stop
  *
  * \return pcmk_ok on success, -errno otherwise
  */
 static inline int
 execd_stonith_stop(stonith_t *stonith_api, const lrmd_rsc_t *rsc)
 {
     /* @TODO Failure would indicate a problem communicating with fencer;
      * perhaps we should try reconnecting and retrying a few times?
      */
     return stonith_api->cmds->remove_device(stonith_api, st_opt_sync_call,
                                             rsc->rsc_id);
 }
 
 /*!
  * \internal
  * \brief Initiate a stonith resource agent recurring "monitor" action
  *
  * \param[in,out] stonith_api  Connection to fencer
  * \param[in,out] rsc          Stonith resource to monitor
  * \param[in]     cmd          Monitor command being executed
  *
  * \return pcmk_ok if monitor was successfully initiated, -errno otherwise
  */
 static inline int
 execd_stonith_monitor(stonith_t *stonith_api, lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     int rc = stonith_api->cmds->monitor(stonith_api, 0, cmd->rsc_id,
                                         pcmk__timeout_ms2s(cmd->timeout));
 
     rc = stonith_api->cmds->register_callback(stonith_api, rc, 0, 0, cmd,
                                               "lrmd_stonith_callback",
                                               lrmd_stonith_callback);
     if (rc == TRUE) {
         rsc->active = cmd;
         rc = pcmk_ok;
     } else {
         rc = -pcmk_err_generic;
     }
     return rc;
 }
 
 static void
 execute_stonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     int rc = pcmk_ok;
     const char *rc_s = NULL;
     bool do_monitor = false;
 
     // Don't free; belongs to pacemaker-execd.c
     stonith_t *stonith_api = get_stonith_connection();
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)
         && (cmd->interval_ms == 0)) {
         // Probes don't require a fencer connection
         stonith_action_complete(cmd, rsc->fence_probe_result.exit_status,
                                 rsc->fence_probe_result.execution_status,
                                 rsc->fence_probe_result.exit_reason);
         return;
     }
 
     if (stonith_api == NULL) {
         stonith_action_complete(cmd, PCMK_OCF_UNKNOWN_ERROR,
                                 PCMK_EXEC_NOT_CONNECTED,
                                 "No connection to fencer");
         return;
     }
 
     if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
         rc = execd_stonith_start(stonith_api, rsc, cmd);
         if (rc == pcmk_ok) {
             do_monitor = true;
         }
 
     } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
         rc = execd_stonith_stop(stonith_api, rsc);
 
     } else if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
                             pcmk__str_casei)) {
         do_monitor = true;
 
     } else {
         stonith_action_complete(cmd, PCMK_OCF_UNIMPLEMENT_FEATURE,
                                 PCMK_EXEC_ERROR,
                                 "Invalid fence device action (bug?)");
         return;
     }
 
     if (do_monitor) {
         rc = execd_stonith_monitor(stonith_api, rsc, cmd);
         if (rc == pcmk_ok) {
             // Don't clean up yet. We will get the result of the monitor later.
             return;
         }
     }
 
     if (rc != -pcmk_err_generic) {
         rc_s = pcmk_strerror(rc);
     }
     stonith_action_complete(cmd,
                             ((rc == pcmk_rc_ok)? CRM_EX_OK : CRM_EX_ERROR),
                             stonith__legacy2status(rc), rc_s);
 }
 
 static void
 execute_nonstonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
 {
     svc_action_t *action = NULL;
     GHashTable *params_copy = NULL;
 
     pcmk__assert((rsc != NULL) && (cmd != NULL));
 
     crm_trace("Creating action, resource:%s action:%s class:%s provider:%s agent:%s",
               rsc->rsc_id, cmd->action, rsc->class, rsc->provider, rsc->type);
 
     params_copy = pcmk__str_table_dup(cmd->params);
 
     action = services__create_resource_action(rsc->rsc_id, rsc->class, rsc->provider,
                                      rsc->type,
                                      normalize_action_name(rsc, cmd->action),
                                      cmd->interval_ms, cmd->timeout,
                                      params_copy, cmd->service_flags);
 
     if (action == NULL) {
         pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
                          PCMK_EXEC_ERROR, strerror(ENOMEM));
         cmd_finalize(cmd, rsc);
         return;
     }
 
     if (action->rc != PCMK_OCF_UNKNOWN) {
         services__copy_result(action, &(cmd->result));
         services_action_free(action);
         cmd_finalize(cmd, rsc);
         return;
     }
 
     action->cb_data = cmd;
 
     if (services_action_async(action, action_complete)) {
         /* The services library has taken responsibility for the action. It
          * could be pending, blocked, or merged into a duplicate recurring
          * action, in which case the action callback (action_complete())
          * will be called when the action completes, otherwise the callback has
          * already been called.
          *
          * action_complete() calls cmd_finalize() which can free cmd, so cmd
          * cannot be used here.
          */
     } else {
         /* This is a recurring action that is not being cancelled and could not
          * be initiated. It has been rescheduled, and the action callback
          * (action_complete()) has been called, which in this case has already
          * called cmd_finalize(), which in this case should only reset (not
          * free) cmd.
          */
         services__copy_result(action, &(cmd->result));
         services_action_free(action);
     }
 }
 
 static gboolean
 execute_resource_action(gpointer user_data)
 {
     lrmd_rsc_t *rsc = (lrmd_rsc_t *) user_data;
     lrmd_cmd_t *cmd = NULL;
 
     CRM_CHECK(rsc != NULL, return FALSE);
 
     if (rsc->active) {
         crm_trace("%s is still active", rsc->rsc_id);
         return TRUE;
     }
 
     if (rsc->pending_ops) {
         GList *first = rsc->pending_ops;
 
         cmd = first->data;
         if (cmd->delay_id) {
             crm_trace
                 ("Command %s %s was asked to run too early, waiting for start_delay timeout of %dms",
                  cmd->rsc_id, cmd->action, cmd->start_delay);
             return TRUE;
         }
         rsc->pending_ops = g_list_remove_link(rsc->pending_ops, first);
         g_list_free_1(first);
 
 #ifdef PCMK__TIME_USE_CGT
         get_current_time(&(cmd->t_run), &(cmd->t_first_run));
 #endif
         cmd->epoch_last_run = time(NULL);
     }
 
     if (!cmd) {
         crm_trace("Nothing further to do for %s", rsc->rsc_id);
         return TRUE;
     }
 
     rsc->active = cmd;          /* only one op at a time for a rsc */
     if (cmd->interval_ms) {
         rsc->recurring_ops = g_list_append(rsc->recurring_ops, cmd);
     }
 
     log_execute(cmd);
 
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         execute_stonith_action(rsc, cmd);
     } else {
         execute_nonstonith_action(rsc, cmd);
     }
 
     return TRUE;
 }
 
 void
 free_rsc(gpointer data)
 {
     GList *gIter = NULL;
     lrmd_rsc_t *rsc = data;
     int is_stonith = pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
                                   pcmk__str_casei);
 
     gIter = rsc->pending_ops;
     while (gIter != NULL) {
         GList *next = gIter->next;
         lrmd_cmd_t *cmd = gIter->data;
 
         /* command was never executed */
         cmd->result.execution_status = PCMK_EXEC_CANCELLED;
         cmd_finalize(cmd, NULL);
 
         gIter = next;
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->pending_ops);
 
     gIter = rsc->recurring_ops;
     while (gIter != NULL) {
         GList *next = gIter->next;
         lrmd_cmd_t *cmd = gIter->data;
 
         if (is_stonith) {
             cmd->result.execution_status = PCMK_EXEC_CANCELLED;
             /* If a stonith command is in-flight, just mark it as cancelled;
              * it is not safe to finalize/free the cmd until the stonith api
              * says it has either completed or timed out.
              */
             if (rsc->active != cmd) {
                 cmd_finalize(cmd, NULL);
             }
         } else {
             /* This command is already handed off to service library,
              * let service library cancel it and tell us via the callback
              * when it is cancelled. The rsc can be safely destroyed
              * even if we are waiting for the cancel result */
             services_action_cancel(rsc->rsc_id,
                                    normalize_action_name(rsc, cmd->action),
                                    cmd->interval_ms);
         }
 
         gIter = next;
     }
     /* frees list, but not list elements. */
     g_list_free(rsc->recurring_ops);
 
     free(rsc->rsc_id);
     free(rsc->class);
     free(rsc->provider);
     free(rsc->type);
     mainloop_destroy_trigger(rsc->work);
 
     free(rsc);
 }
 
 static int
 process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id,
                     xmlNode **reply)
 {
     int rc = pcmk_ok;
     time_t now = time(NULL);
     const char *protocol_version =
         crm_element_value(request, PCMK__XA_LRMD_PROTOCOL_VERSION);
     const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE);
 
     if (compare_version(protocol_version, LRMD_COMPATIBLE_PROTOCOL) < 0) {
         crm_err("Cluster API version must be greater than or equal to %s, not %s",
                 LRMD_COMPATIBLE_PROTOCOL, protocol_version);
         rc = -EPROTO;
     }
 
     if (pcmk__xe_attr_is_true(request, PCMK__XA_LRMD_IS_IPC_PROVIDER)) {
 #ifdef PCMK__COMPILE_REMOTE
         if ((client->remote != NULL)
             && pcmk_is_set(client->flags,
                            pcmk__client_tls_handshake_complete)) {
             const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
 
             // This is a remote connection from a cluster node's controller
             ipc_proxy_add_provider(client);
 
             /* @TODO Allowing multiple proxies makes no sense given that clients
              * have no way to choose between them. Maybe always use the most
              * recent one and switch any existing IPC connections to use it,
              * by iterating over ipc_clients here, and if client->id doesn't
              * match the client's userdata, replace the userdata with the new
              * ID. After the iteration, call lrmd_remote_client_destroy() on any
              * of the replaced values in ipc_providers.
              */
 
             /* If this was a register operation, also ask for new schema files but
              * only if it's supported by the protocol version.
              */
             if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none) &&
                 LRMD_SUPPORTS_SCHEMA_XFER(protocol_version)) {
                 remoted_request_cib_schema_files();
             }
         } else {
             rc = -EACCES;
         }
 #else
         rc = -EPROTONOSUPPORT;
 #endif
     }
 
     *reply = create_lrmd_reply(__func__, rc, call_id);
     crm_xml_add(*reply, PCMK__XA_LRMD_OP, CRM_OP_REGISTER);
     crm_xml_add(*reply, PCMK__XA_LRMD_CLIENTID, client->id);
     crm_xml_add(*reply, PCMK__XA_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
     crm_xml_add_ll(*reply, PCMK__XA_UPTIME, now - start_time);
 
     if (start_state) {
         crm_xml_add(*reply, PCMK__XA_NODE_START_STATE, start_state);
     }
 
     return rc;
 }
 
 static int
 process_lrmd_rsc_register(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = build_rsc_from_xml(request);
     lrmd_rsc_t *dup = g_hash_table_lookup(rsc_list, rsc->rsc_id);
 
     if (dup &&
         pcmk__str_eq(rsc->class, dup->class, pcmk__str_casei) &&
         pcmk__str_eq(rsc->provider, dup->provider, pcmk__str_casei) && pcmk__str_eq(rsc->type, dup->type, pcmk__str_casei)) {
 
         crm_notice("Ignoring duplicate registration of '%s'", rsc->rsc_id);
         free_rsc(rsc);
         return rc;
     }
 
     g_hash_table_replace(rsc_list, rsc->rsc_id, rsc);
     crm_info("Cached agent information for '%s'", rsc->rsc_id);
     return rc;
 }
 
 static xmlNode *
 process_lrmd_get_rsc_info(xmlNode *request, int call_id)
 {
     int rc = pcmk_ok;
     xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
                                             "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     xmlNode *reply = NULL;
     lrmd_rsc_t *rsc = NULL;
 
     if (rsc_id == NULL) {
         rc = -ENODEV;
     } else {
         rsc = g_hash_table_lookup(rsc_list, rsc_id);
         if (rsc == NULL) {
             crm_info("Agent information for '%s' not in cache", rsc_id);
             rc = -ENODEV;
         }
     }
 
     reply = create_lrmd_reply(__func__, rc, call_id);
     if (rsc) {
         crm_xml_add(reply, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
         crm_xml_add(reply, PCMK__XA_LRMD_CLASS, rsc->class);
         crm_xml_add(reply, PCMK__XA_LRMD_PROVIDER, rsc->provider);
         crm_xml_add(reply, PCMK__XA_LRMD_TYPE, rsc->type);
     }
     return reply;
 }
 
 static int
 process_lrmd_rsc_unregister(pcmk__client_t *client, uint32_t id,
                             xmlNode *request)
 {
     int rc = pcmk_ok;
     lrmd_rsc_t *rsc = NULL;
     xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
                                             "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
 
     if (!rsc_id) {
         return -ENODEV;
     }
 
     rsc = g_hash_table_lookup(rsc_list, rsc_id);
     if (rsc == NULL) {
         crm_info("Ignoring unregistration of resource '%s', which is not registered",
                  rsc_id);
         return pcmk_ok;
     }
 
     if (rsc->active) {
         /* let the caller know there are still active ops on this rsc to watch for */
         crm_trace("Operation (%p) still in progress for unregistered resource %s",
                   rsc->active, rsc_id);
         rc = -EINPROGRESS;
     }
 
     g_hash_table_remove(rsc_list, rsc_id);
 
     return rc;
 }
 
 static int
 process_lrmd_rsc_exec(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     lrmd_rsc_t *rsc = NULL;
     lrmd_cmd_t *cmd = NULL;
     xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
                                             "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     int call_id;
 
     if (!rsc_id) {
         return -EINVAL;
     }
     if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
         crm_info("Resource '%s' not found (%d active resources)",
                  rsc_id, g_hash_table_size(rsc_list));
         return -ENODEV;
     }
 
     cmd = create_lrmd_cmd(request, client);
     call_id = cmd->call_id;
 
     /* Don't reference cmd after handing it off to be scheduled.
      * The cmd could get merged and freed. */
     schedule_lrmd_cmd(rsc, cmd);
 
     return call_id;
 }
 
 static int
 cancel_op(const char *rsc_id, const char *action, guint interval_ms)
 {
     GList *gIter = NULL;
     lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, rsc_id);
 
     /* How to cancel an action.
      * 1. Check pending ops list, if it hasn't been handed off
      *    to the service library or stonith recurring list remove
      *    it there and that will stop it.
      * 2. If it isn't in the pending ops list, then it's either a
      *    recurring op in the stonith recurring list, or the service
      *    library's recurring list.  Stop it there
      * 3. If not found in any lists, then this operation has either
      *    been executed already and is not a recurring operation, or
      *    never existed.
      */
     if (!rsc) {
         return -ENODEV;
     }
 
     for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
         lrmd_cmd_t *cmd = gIter->data;
 
         if (action_matches(cmd, action, interval_ms)) {
             cmd->result.execution_status = PCMK_EXEC_CANCELLED;
             cmd_finalize(cmd, rsc);
             return pcmk_ok;
         }
     }
 
     if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         /* The service library does not handle stonith operations.
          * We have to handle recurring stonith operations ourselves. */
         for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
             lrmd_cmd_t *cmd = gIter->data;
 
             if (action_matches(cmd, action, interval_ms)) {
                 cmd->result.execution_status = PCMK_EXEC_CANCELLED;
                 if (rsc->active != cmd) {
                     cmd_finalize(cmd, rsc);
                 }
                 return pcmk_ok;
             }
         }
     } else if (services_action_cancel(rsc_id,
                                       normalize_action_name(rsc, action),
                                       interval_ms) == TRUE) {
         /* The service library will tell the action_complete callback function
          * this action was cancelled, which will destroy the cmd and remove
          * it from the recurring_op list. Do not do that in this function
          * if the service library says it cancelled it. */
         return pcmk_ok;
     }
 
     return -EOPNOTSUPP;
 }
 
 static void
 cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id)
 {
     GList *cmd_list = NULL;
     GList *cmd_iter = NULL;
 
     /* Notice a copy of each list is created when concat is called.
      * This prevents odd behavior from occurring when the cmd_list
      * is iterated through later on.  It is possible the cancel_op
      * function may end up modifying the recurring_ops and pending_ops
      * lists.  If we did not copy those lists, our cmd_list iteration
      * could get messed up.*/
     if (rsc->recurring_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->recurring_ops));
     }
     if (rsc->pending_ops) {
         cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->pending_ops));
     }
     if (!cmd_list) {
         return;
     }
 
     for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) {
         lrmd_cmd_t *cmd = cmd_iter->data;
 
         if (cmd->interval_ms == 0) {
             continue;
         }
 
         if (client_id && !pcmk__str_eq(cmd->client_id, client_id, pcmk__str_casei)) {
             continue;
         }
 
         cancel_op(rsc->rsc_id, cmd->action, cmd->interval_ms);
     }
     /* frees only the copied list data, not the cmds */
     g_list_free(cmd_list);
 }
 
 static int
 process_lrmd_rsc_cancel(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
                                             "//" PCMK__XE_LRMD_RSC,
                                             LOG_ERR);
     const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     const char *action = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
     guint interval_ms = 0;
 
     crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL, &interval_ms);
 
     if (!rsc_id || !action) {
         return -EINVAL;
     }
 
     return cancel_op(rsc_id, action, interval_ms);
 }
 
 static void
 add_recurring_op_xml(xmlNode *reply, lrmd_rsc_t *rsc)
 {
     xmlNode *rsc_xml = pcmk__xe_create(reply, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(rsc_xml, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
     for (GList *item = rsc->recurring_ops; item != NULL; item = item->next) {
         lrmd_cmd_t *cmd = item->data;
         xmlNode *op_xml = pcmk__xe_create(rsc_xml, PCMK__XE_LRMD_RSC_OP);
 
         crm_xml_add(op_xml, PCMK__XA_LRMD_RSC_ACTION,
                     pcmk__s(cmd->real_action, cmd->action));
         crm_xml_add_ms(op_xml, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
         crm_xml_add_int(op_xml, PCMK__XA_LRMD_TIMEOUT, cmd->timeout_orig);
     }
 }
 
 static xmlNode *
 process_lrmd_get_recurring(xmlNode *request, int call_id)
 {
     int rc = pcmk_ok;
     const char *rsc_id = NULL;
     lrmd_rsc_t *rsc = NULL;
     xmlNode *reply = NULL;
     xmlNode *rsc_xml = NULL;
 
     // Resource ID is optional
     rsc_xml = pcmk__xe_first_child(request, PCMK__XE_LRMD_CALLDATA, NULL, NULL);
     if (rsc_xml) {
         rsc_xml = pcmk__xe_first_child(rsc_xml, PCMK__XE_LRMD_RSC, NULL, NULL);
     }
     if (rsc_xml) {
         rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
     }
 
     // If resource ID is specified, resource must exist
     if (rsc_id != NULL) {
         rsc = g_hash_table_lookup(rsc_list, rsc_id);
         if (rsc == NULL) {
             crm_info("Resource '%s' not found (%d active resources)",
                      rsc_id, g_hash_table_size(rsc_list));
             rc = -ENODEV;
         }
     }
 
     reply = create_lrmd_reply(__func__, rc, call_id);
 
     // If resource ID is not specified, check all resources
     if (rsc_id == NULL) {
         GHashTableIter iter;
         char *key = NULL;
 
         g_hash_table_iter_init(&iter, rsc_list);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key,
                                       (gpointer *) &rsc)) {
             add_recurring_op_xml(reply, rsc);
         }
     } else if (rsc) {
         add_recurring_op_xml(reply, rsc);
     }
     return reply;
 }
 
 void
 process_lrmd_message(pcmk__client_t *client, uint32_t id, xmlNode *request)
 {
     int rc = pcmk_ok;
     int call_id = 0;
     const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
     int do_reply = 0;
     int do_notify = 0;
     xmlNode *reply = NULL;
 
     /* Certain IPC commands may be done only by privileged users (i.e. root or
      * hacluster), because they would otherwise provide a means of bypassing
      * ACLs.
      */
     bool allowed = pcmk_is_set(client->flags, pcmk__client_privileged);
 
     crm_trace("Processing %s operation from %s", op, client->id);
     crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
 
     if (pcmk__str_eq(op, CRM_OP_IPC_FWD, pcmk__str_none)) {
 #ifdef PCMK__COMPILE_REMOTE
         if (allowed) {
             ipc_proxy_forward_client(client, request);
         } else {
             rc = -EACCES;
         }
 #else
         rc = -EPROTONOSUPPORT;
 #endif
         do_reply = 1;
     } else if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) {
         rc = process_lrmd_signon(client, request, call_id, &reply);
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_REG, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_register(client, id, request);
             do_notify = 1;
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_INFO, pcmk__str_none)) {
         if (allowed) {
             reply = process_lrmd_get_rsc_info(request, call_id);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_unregister(client, id, request);
             /* don't notify anyone about failed un-registers */
             if (rc == pcmk_ok || rc == -EINPROGRESS) {
                 do_notify = 1;
             }
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_EXEC, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_exec(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_RSC_CANCEL, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_rsc_cancel(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_POKE, pcmk__str_none)) {
         do_notify = 1;
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_CHECK, pcmk__str_none)) {
         if (allowed) {
             xmlNode *wrapper = pcmk__xe_first_child(request,
                                                     PCMK__XE_LRMD_CALLDATA,
                                                     NULL, NULL);
             xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
 
             const char *timeout = NULL;
 
             CRM_LOG_ASSERT(data != NULL);
             timeout = crm_element_value(data, PCMK__XA_LRMD_WATCHDOG);
             pcmk__valid_stonith_watchdog_timeout(timeout);
         } else {
             rc = -EACCES;
         }
     } else if (pcmk__str_eq(op, LRMD_OP_ALERT_EXEC, pcmk__str_none)) {
         if (allowed) {
             rc = process_lrmd_alert_exec(client, id, request);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else if (pcmk__str_eq(op, LRMD_OP_GET_RECURRING, pcmk__str_none)) {
         if (allowed) {
             reply = process_lrmd_get_recurring(request, call_id);
         } else {
             rc = -EACCES;
         }
         do_reply = 1;
     } else {
         rc = -EOPNOTSUPP;
         do_reply = 1;
         crm_err("Unknown IPC request '%s' from client %s",
                 op, pcmk__client_name(client));
     }
 
     if (rc == -EACCES) {
         crm_warn("Rejecting IPC request '%s' from unprivileged client %s",
                  op, pcmk__client_name(client));
     }
 
     crm_debug("Processed %s operation from %s: rc=%d, reply=%d, notify=%d",
               op, client->id, rc, do_reply, do_notify);
 
     if (do_reply) {
         int send_rc = pcmk_rc_ok;
 
         if (reply == NULL) {
             reply = create_lrmd_reply(__func__, rc, call_id);
         }
         send_rc = lrmd_server_send_reply(client, id, reply);
         pcmk__xml_free(reply);
         if (send_rc != pcmk_rc_ok) {
             crm_warn("Reply to client %s failed: %s " QB_XS " rc=%d",
                      pcmk__client_name(client), pcmk_rc_str(send_rc), send_rc);
         }
     }
 
     if (do_notify) {
         send_generic_notify(rc, request);
     }
 }
diff --git a/daemons/fenced/cts-fence-helper.c b/daemons/fenced/cts-fence-helper.c
index d05eb09e73..5d69f1444b 100644
--- a/daemons/fenced/cts-fence-helper.c
+++ b/daemons/fenced/cts-fence-helper.c
@@ -1,664 +1,664 @@
 /*
  * Copyright 2009-2025 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/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_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;
     }
 
     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,
     },
 
     { 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__api_dispatch(st) != pcmk_rc_ok) {
                 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"));
 }
 
 #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", PCMK_ACTION_OFF,
                                 3, 0),
                 "Fence failure results off", 1, -ENODATA);
 
     single_test(st->cmds->fence(st, st_opts, "false_1_node2",
                                 PCMK_ACTION_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);
+    stonith__key_value_freeall(params, true, true);
 }
 
 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);
+    stonith__key_value_freeall(params, true, true);
     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", PCMK_ACTION_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", PCMK_ACTION_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);
+    stonith__key_value_freeall(params, true, true);
 }
 
 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);
+    stonith__key_value_freeall(params, true, true);
     params = NULL;
 
     single_test(st->cmds->list(st, st_opts, "test-id", NULL, 1),
                 PCMK_ACTION_LIST, 0, 0);
 
     single_test(st->cmds->monitor(st, st_opts, "test-id", 1), "Monitor", 0, 0);
 
     single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node2", 1),
                 "Status false_1_node2", 0, 0);
 
     single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node1", 1),
                 "Status false_1_node1", 0, 0);
 
     single_test(st->cmds->fence(st, st_opts, "unknown-host", PCMK_ACTION_OFF,
                                 1, 0),
                 "Fence unknown-host (expected failure)", 0, -ENODEV);
 
     single_test(st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_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", PCMK_ACTION_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);
+    stonith__key_value_freeall(params, true, true);
 }
 
 static void
 sanity_tests(void)
 {
     int rc = 0;
 
     rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
     if (rc != pcmk_ok) {
         stonith__api_free(st);
         crm_exit(CRM_EX_DISCONNECT);
     }
     st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_DISCONNECT,
                                     st_callback);
     st->cmds->register_notification(st, PCMK__VALUE_ST_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_free(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", PCMK_ACTION_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", PCMK_ACTION_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", PCMK_ACTION_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", PCMK_ACTION_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", PCMK_ACTION_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);
+    stonith__key_value_freeall(params, true, true);
 }
 
 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", PCMK_ACTION_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", PCMK_ACTION_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", PCMK_ACTION_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);
+    stonith__key_value_freeall(params, true, true);
 
     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);
+    stonith__key_value_freeall(params, true, true);
 
     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);
+    stonith__key_value_freeall(params, true, true);
 
     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_free(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_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_scheduler.c b/daemons/fenced/fenced_scheduler.c
index 14e3c7e2b0..00a1952c17 100644
--- a/daemons/fenced/fenced_scheduler.c
+++ b/daemons/fenced/fenced_scheduler.c
@@ -1,258 +1,258 @@
 /*
  * Copyright 2009-2025 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 <errno.h>
 #include <glib.h>
 
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 
 #include <pacemaker-internal.h>
 #include <pacemaker-fenced.h>
 
 // fenced_scheduler_run() assumes it's the only place scheduler->input gets set
 static pcmk_scheduler_t *scheduler = NULL;
 
 /*!
  * \internal
  * \brief Initialize scheduler data for fencer purposes
  *
  * \return Standard Pacemaker return code
  */
 int
 fenced_scheduler_init(void)
 {
     pcmk__output_t *logger = NULL;
     int rc = pcmk__log_output_new(&logger);
 
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     scheduler = pcmk_new_scheduler();
     if (scheduler == NULL) {
         pcmk__output_free(logger);
         return ENOMEM;
     }
 
     pe__register_messages(logger);
     pcmk__register_lib_messages(logger);
     pcmk__output_set_log_level(logger, LOG_TRACE);
     scheduler->priv->out = logger;
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Set the local node name for scheduling purposes
  *
  * \param[in] node_name  Name to set as local node name
  */
 void
 fenced_set_local_node(const char *node_name)
 {
     pcmk__assert(scheduler != NULL);
 
     scheduler->priv->local_node_name = pcmk__str_copy(node_name);
 }
 
 /*!
  * \internal
  * \brief Get the local node name
  *
  * \return Local node name
  */
 const char *
 fenced_get_local_node(void)
 {
     if (scheduler == NULL) {
         return NULL;
     }
     return scheduler->priv->local_node_name;
 }
 
 /*!
  * \internal
  * \brief Free all scheduler-related resources
  */
 void
 fenced_scheduler_cleanup(void)
 {
     if (scheduler != NULL) {
         pcmk__output_t *logger = scheduler->priv->out;
 
         if (logger != NULL) {
             logger->finish(logger, CRM_EX_OK, true, NULL);
             pcmk__output_free(logger);
             scheduler->priv->out = NULL;
         }
         pcmk_free_scheduler(scheduler);
         scheduler = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Check whether the local node is in a resource's allowed node list
  *
  * \param[in] rsc  Resource to check
  *
  * \return Pointer to node if found, otherwise NULL
  */
 static pcmk_node_t *
 local_node_allowed_for(const pcmk_resource_t *rsc)
 {
     if ((rsc != NULL) && (scheduler->priv->local_node_name != NULL)) {
         GHashTableIter iter;
         pcmk_node_t *node = NULL;
 
         g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             if (pcmk__str_eq(node->priv->name, scheduler->priv->local_node_name,
                              pcmk__str_casei)) {
                 return node;
             }
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief If a given resource or any of its children are fencing devices,
  *        register the devices
  *
  * \param[in,out] data       Resource to check
  * \param[in,out] user_data  Ignored
  */
 static void
 register_if_fencing_device(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
     const char *rsc_id = pcmk__s(rsc->priv->history_id, rsc->id);
 
     xmlNode *xml = NULL;
     GHashTableIter hash_iter;
     pcmk_node_t *node = NULL;
     const char *name = NULL;
     const char *value = NULL;
     const char *agent = NULL;
     const char *rsc_provides = NULL;
     stonith_key_value_t *params = NULL;
 
     // If this is a collective resource, check children instead
     if (rsc->priv->children != NULL) {
 
         for (GList *iter = rsc->priv->children;
              iter != NULL; iter = iter->next) {
 
             register_if_fencing_device(iter->data, NULL);
             if (pcmk__is_clone(rsc)) {
                 return; // Only one instance needs to be checked for clones
             }
         }
         return;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
         return; // Not a fencing device
     }
 
     if (pe__resource_is_disabled(rsc)) {
         crm_info("Ignoring fencing device %s because it is disabled", rsc->id);
         return;
     }
 
     if ((stonith_watchdog_timeout_ms <= 0) &&
         pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
         crm_info("Ignoring fencing device %s "
                  "because watchdog fencing is disabled", rsc->id);
         return;
     }
 
     // Check whether local node is allowed to run resource
     node = local_node_allowed_for(rsc);
     if (node == NULL) {
         crm_info("Ignoring fencing device %s "
                  "because local node is not allowed to run it", rsc->id);
         return;
     }
     if (node->assign->score < 0) {
         crm_info("Ignoring fencing device %s "
                  "because local node has preference %s for it",
                  rsc->id, pcmk_readable_score(node->assign->score));
         return;
     }
 
     // If device is in a group, check whether local node is allowed for group
     if (pcmk__is_group(rsc->priv->parent)) {
         pcmk_node_t *group_node = local_node_allowed_for(rsc->priv->parent);
 
         if ((group_node != NULL) && (group_node->assign->score < 0)) {
             crm_info("Ignoring fencing device %s "
                      "because local node has preference %s for its group",
                      rsc->id, pcmk_readable_score(group_node->assign->score));
             return;
         }
     }
 
     crm_debug("Reloading configuration of fencing device %s", rsc->id);
 
     agent = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE);
 
     get_meta_attributes(rsc->priv->meta, rsc, NULL, scheduler);
     rsc_provides = g_hash_table_lookup(rsc->priv->meta,
                                        PCMK_STONITH_PROVIDES);
 
     g_hash_table_iter_init(&hash_iter, pe_rsc_params(rsc, node, scheduler));
     while (g_hash_table_iter_next(&hash_iter, (gpointer *) &name,
                                   (gpointer *) &value)) {
         if ((name == NULL) || (value == NULL)) {
             continue;
         }
         params = stonith__key_value_add(params, name, value);
     }
 
     xml = create_device_registration_xml(rsc_id, st_namespace_any, agent,
                                          params, rsc_provides);
-    stonith_key_value_freeall(params, 1, 1);
+    stonith__key_value_freeall(params, true, true);
     pcmk__assert(fenced_device_register(xml, true) == pcmk_rc_ok);
     pcmk__xml_free(xml);
 }
 
 /*!
  * \internal
  * \brief Run the scheduler for fencer purposes
  *
  * \param[in] cib  CIB to use as scheduler input
  *
  * \note Scheduler object is reset before returning, but \p cib is not freed.
  */
 void
 fenced_scheduler_run(xmlNode *cib)
 {
     CRM_CHECK((cib != NULL) && (scheduler != NULL)
               && (scheduler->input == NULL), return);
 
     pcmk_reset_scheduler(scheduler);
 
     scheduler->input = cib;
     pcmk__set_scheduler_flags(scheduler,
                               pcmk__sched_location_only|pcmk__sched_no_counts);
     pcmk__schedule_actions(scheduler);
     g_list_foreach(scheduler->priv->resources, register_if_fencing_device,
                    NULL);
 
     scheduler->input = NULL; // Wasn't a copy, so don't let API free it
     pcmk_reset_scheduler(scheduler);
 }
diff --git a/include/crm/fencing/internal.h b/include/crm/fencing/internal.h
index 14d1a6d66b..b4048e64d0 100644
--- a/include/crm/fencing/internal.h
+++ b/include/crm/fencing/internal.h
@@ -1,186 +1,188 @@
 /*
  * Copyright 2011-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_FENCING_INTERNAL__H
 #define PCMK__CRM_FENCING_INTERNAL__H
 
 #include <glib.h>
 #include <crm/common/ipc.h>
 #include <crm/common/xml.h>
 #include <crm/common/output_internal.h>
 #include <crm/common/results_internal.h>
 #include <crm/stonith-ng.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 stonith_t *stonith__api_new(void);
 void stonith__api_free(stonith_t *stonith_api);
 int stonith__api_dispatch(stonith_t *stonith_api);
 
 stonith_key_value_t *stonith__key_value_add(stonith_key_value_t *head,
                                             const char *key, const char *value);
+void stonith__key_value_freeall(stonith_key_value_t *head, bool keys,
+                                bool values);
 
 #define stonith__set_call_options(st_call_opts, call_for, flags_to_set) do { \
         st_call_opts = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,     \
                                           "Fencer call", (call_for),         \
                                           (st_call_opts), (flags_to_set),    \
                                           #flags_to_set);                    \
     } while (0)
 
 #define stonith__clear_call_options(st_call_opts, call_for, flags_to_clear) do { \
         st_call_opts = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,     \
                                             "Fencer call", (call_for),         \
                                             (st_call_opts), (flags_to_clear),  \
                                             #flags_to_clear);                  \
     } while (0)
 
 struct stonith_action_s;
 typedef struct stonith_action_s stonith_action_t;
 
 stonith_action_t *stonith__action_create(const char *agent,
                                          const char *action_name,
                                          const char *target,
                                          int timeout_sec,
                                          GHashTable *device_args,
                                          GHashTable *port_map,
                                          const char *host_arg);
 void stonith__destroy_action(stonith_action_t *action);
 pcmk__action_result_t *stonith__action_result(stonith_action_t *action);
 int stonith__result2rc(const pcmk__action_result_t *result);
 void stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result);
 void stonith__xe_get_result(const xmlNode *xml, pcmk__action_result_t *result);
 xmlNode *stonith__find_xe_with_result(xmlNode *xml);
 
 int stonith__execute_async(stonith_action_t *action, void *userdata,
                            void (*done) (int pid,
                                          const pcmk__action_result_t *result,
                                          void *user_data),
                            void (*fork_cb) (int pid, void *user_data));
 
 int stonith__metadata_async(const char *agent, int timeout_sec,
                             void (*callback)(int pid,
                                              const pcmk__action_result_t *result,
                                              void *user_data),
                             void *user_data);
 
 xmlNode *create_level_registration_xml(const char *node, const char *pattern,
                                        const char *attr, const char *value,
                                        int level,
                                        const stonith_key_value_t *device_list);
 
 xmlNode *create_device_registration_xml(const char *id,
                                         enum stonith_namespace standard,
                                         const char *agent,
                                         const stonith_key_value_t *params,
                                         const char *rsc_provides);
 
 void stonith__register_messages(pcmk__output_t *out);
 
 GList *stonith__parse_targets(const char *hosts);
 
 const char *stonith__later_succeeded(const stonith_history_t *event,
                                      const stonith_history_t *top_history);
 stonith_history_t *stonith__sort_history(stonith_history_t *history);
 
 const char *stonith__default_host_arg(xmlNode *metadata);
 
 /* Only 1-9 is allowed for fencing topology levels,
  * however, 0 is used to unregister all levels in
  * unregister requests.
  */
 #  define ST__LEVEL_COUNT 10
 
 #  define STONITH_ATTR_ACTION_OP   "action"
 
 #  define STONITH_OP_EXEC        "st_execute"
 #  define STONITH_OP_TIMEOUT_UPDATE        "st_timeout_update"
 #  define STONITH_OP_QUERY       "st_query"
 #  define STONITH_OP_FENCE       "st_fence"
 #  define STONITH_OP_RELAY       "st_relay"
 #  define STONITH_OP_DEVICE_ADD      "st_device_register"
 #  define STONITH_OP_DEVICE_DEL      "st_device_remove"
 #  define STONITH_OP_FENCE_HISTORY   "st_fence_history"
 #  define STONITH_OP_LEVEL_ADD       "st_level_add"
 #  define STONITH_OP_LEVEL_DEL       "st_level_remove"
 #  define STONITH_OP_NOTIFY          "st_notify"
 #  define STONITH_OP_POKE            "poke"
 
 
 #  define STONITH_WATCHDOG_AGENT          "fence_watchdog"
 /* Don't change 2 below as it would break rolling upgrade */
 #  define STONITH_WATCHDOG_AGENT_INTERNAL "#watchdog"
 #  define STONITH_WATCHDOG_ID             "watchdog"
 
 stonith_history_t *stonith__first_matching_event(stonith_history_t *history,
                                                  bool (*matching_fn)(stonith_history_t *, void *),
                                                  void *user_data);
 bool stonith__event_state_pending(stonith_history_t *history, void *user_data);
 bool stonith__event_state_eq(stonith_history_t *history, void *user_data);
 bool stonith__event_state_neq(stonith_history_t *history, void *user_data);
 
 int stonith__legacy2status(int rc);
 
 int stonith__exit_status(const stonith_callback_data_t *data);
 int stonith__execution_status(const stonith_callback_data_t *data);
 const char *stonith__exit_reason(const stonith_callback_data_t *data);
 
 int stonith__event_exit_status(const stonith_event_t *event);
 int stonith__event_execution_status(const stonith_event_t *event);
 const char *stonith__event_exit_reason(const stonith_event_t *event);
 char *stonith__event_description(const stonith_event_t *event);
 gchar *stonith__history_description(const stonith_history_t *event,
                                     bool full_history,
                                     const char *later_succeeded,
                                     uint32_t show_opts);
 
 /*!
  * \internal
  * \brief Is a fencing operation in pending state?
  *
  * \param[in] state     State as enum op_state value
  *
  * \return A boolean
  */
 static inline bool
 stonith__op_state_pending(enum op_state state)
 {
     return state != st_failed && state != st_done;
 }
 
 gboolean stonith__watchdog_fencing_enabled_for_node(const char *node);
 gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node);
 
 /*!
  * \internal
  * \brief Validate a fencing configuration
  *
  * \param[in,out] st            Fencer connection to use
  * \param[in]     call_options  Group of enum stonith_call_options
  * \param[in]     rsc_id        Resource to validate
  * \param[in]     namespace_s   Type of fence agent to search for
  * \param[in]     agent         Fence agent to validate
  * \param[in,out] params        Fence device configuration parameters
  * \param[in]     timeout_sec   How long to wait for operation to complete
  * \param[in,out] output        If non-NULL, where to store any agent output
  * \param[in,out] error_output  If non-NULL, where to store agent error output
  *
  * \return Standard Pacemaker return code
  */
 int stonith__validate(stonith_t *st, int call_options, const char *rsc_id,
                       const char *namespace_s, const char *agent,
                       GHashTable *params, int timeout_sec, char **output,
                       char **error_output);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_FENCING_INTERNAL__H
diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h
index 6f0e8d8777..83ca9eb826 100644
--- a/include/crm/stonith-ng.h
+++ b/include/crm/stonith-ng.h
@@ -1,772 +1,772 @@
 /*
  * Copyright 2004-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #ifndef PCMK__CRM_STONITH_NG__H
 #  define PCMK__CRM_STONITH_NG__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Fencing aka. STONITH
  * \ingroup fencing
  */
 
 /* IMPORTANT: dlm source code includes this file directly. Until dlm v4.2.0
  * (commit 5afd9fdc), dlm did not have access to other Pacemaker headers on its
  * include path. This file should *not* include any other Pacemaker headers
  * until we decide that we no longer need to support dlm versions older than
  * v4.2.0.
  *
  * @COMPAT Remove this restriction and take any opportunities to simplify code
  * when possible.
  */
 
 #  include <dlfcn.h>
 #  include <errno.h>
 #  include <stdbool.h>  // bool
 #  include <stdint.h>   // uint32_t
 #  include <time.h>     // time_t
 
 // @TODO Keep this definition but make it internal
 /*!
  * \brief Fencer API connection state
  * \deprecated Do not use
  */
 enum stonith_state {
     stonith_connected_command,
     stonith_connected_query,
     stonith_disconnected,
 };
 
 //! Flags that can be set in call options for API requests
 enum stonith_call_options {
     //! No options
     st_opt_none                 = 0,
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Unused
     st_opt_verbose              = (1 << 0),
 #endif
 
     //! The fencing target is allowed to execute the request
     st_opt_allow_self_fencing   = (1 << 1),
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Use st_opt_allow_self_fencing instead
     st_opt_allow_suicide        = st_opt_allow_self_fencing,
 #endif
 
     // Used internally to indicate that request is manual fence confirmation
     //! \internal Do not use
     st_opt_manual_ack           = (1 << 3),
 
     //! Do not return any reply from server
     st_opt_discard_reply        = (1 << 4),
 
     // Used internally to indicate that request requires a fencing topology
     //! \internal Do not use
     st_opt_topology             = (1 << 6),
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
     //! \deprecated Unused
     st_opt_scope_local          = (1 << 8),
 #endif
 
     //! Interpret target as node cluster layer ID instead of name
     st_opt_cs_nodeid            = (1 << 9),
 
     //! Wait for request to be completed before returning
     st_opt_sync_call            = (1 << 12),
 
     //! Request that server send an update with optimal callback timeout
     st_opt_timeout_updates      = (1 << 13),
 
     //! Invoke callback only if request succeeded
     st_opt_report_only_success  = (1 << 14),
 
     //! For a fence history request, request that the history be cleared
     st_opt_cleanup              = (1 << 19),
 
     //! For a fence history request, broadcast the request to all nodes
     st_opt_broadcast            = (1 << 20),
 };
 
 /*! Order matters here, do not change values */
 enum op_state
 {
     st_query,
     st_exec,
     st_done,
     st_duplicate,
     st_failed,
 };
 
 // Supported fence agent interface standards
 enum stonith_namespace {
     st_namespace_invalid,
     st_namespace_any,
     st_namespace_internal,  // Implemented internally by Pacemaker
 
     /* Neither of these projects are active any longer, but the fence agent
      * interfaces they created are still in use and supported by Pacemaker.
      */
     st_namespace_rhcs,      // Red Hat Cluster Suite compatible
     st_namespace_lha,       // Linux-HA compatible
 };
 
 enum stonith_namespace stonith_text2namespace(const char *namespace_s);
 const char *stonith_namespace2text(enum stonith_namespace st_namespace);
 enum stonith_namespace stonith_get_namespace(const char *agent,
                                              const char *namespace_s);
 
 typedef struct stonith_key_value_s {
     char *key;
     char *value;
         struct stonith_key_value_s *next;
 } stonith_key_value_t;
 
 typedef struct stonith_history_s {
     char *target;
     char *action;
     char *origin;
     char *delegate;
     char *client;
     int state;
     time_t completed;
     struct stonith_history_s *next;
     long completed_nsec;
     char *exit_reason;
 } stonith_history_t;
 
 // @TODO Keep this typedef but rename it and make it internal
 typedef struct stonith_s stonith_t;
 
 typedef struct stonith_event_s {
     char *id;
     char *operation;
     int result;
     char *origin;
     char *target;
     char *action;
     char *executioner;
 
     char *device;
 
     /*! The name of the client that initiated the action. */
     char *client_origin;
 
     //! \internal This field should be treated as internal to Pacemaker
     void *opaque;
 } stonith_event_t;
 
 typedef struct stonith_callback_data_s {
     int rc;
     int call_id;
     void *userdata;
 
     //! \internal This field should be treated as internal to Pacemaker
     void *opaque;
 } stonith_callback_data_t;
 
 // @TODO Keep this object but make it internal
 /*!
  * \brief Fencer API operations
  * \deprecated Use appropriate functions in libpacemaker instead
  */
 typedef struct stonith_api_operations_s {
     /*!
      * \brief Destroy a fencer connection
      *
      * \param[in,out] st  Fencer connection to destroy
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*free) (stonith_t *st);
 
     /*!
      * \brief Connect to the local fencer
      *
      * \param[in,out] st          Fencer connection to connect
      * \param[in]     name        Client name to use
      * \param[out]    stonith_fd  If NULL, use a main loop, otherwise
      *                            store IPC file descriptor here
      *
      * \return Legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*connect) (stonith_t *st, const char *name, int *stonith_fd);
 
     /*!
      * \brief Disconnect from the local stonith daemon.
      *
      * \param[in,out] st  Fencer connection to disconnect
      *
      * \return Legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*disconnect)(stonith_t *st);
 
     /*!
      * \brief Unregister a fence device with the local fencer
      *
      * \param[in,out] st       Fencer connection to disconnect
      * \param[in]     options  Group of enum stonith_call_options
      * \param[in]     name     ID of fence device to unregister
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*remove_device)(stonith_t *st, int options, const char *name);
 
     /*!
      * \brief Register a fence device with the local fencer
      *
      * \param[in,out] st           Fencer connection to use
      * \param[in]     options      Group of enum stonith_call_options
      * \param[in]     id           ID of fence device to register
      * \param[in]     namespace_s  Type of fence agent to search for ("redhat"
      *                             or "stonith-ng" for RHCS-style, "internal"
      *                             for Pacemaker-internal devices, "heartbeat"
      *                             for LHA-style, or "any" or NULL for any)
      * \param[in]     agent        Name of fence agent for device
      * \param[in]     params       Fence agent parameters for device
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*register_device)(stonith_t *st, int options, const char *id,
                            const char *namespace_s, const char *agent,
                            const stonith_key_value_t *params);
 
     /*!
      * \brief Unregister a fencing level for specified node with local fencer
      *
      * \param[in,out] st       Fencer connection to use
      * \param[in]     options  Group of enum stonith_call_options
      * \param[in]     node     Target node to unregister level for
      * \param[in]     level    Topology level number to unregister
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*remove_level)(stonith_t *st, int options, const char *node,
                         int level);
 
     /*!
      * \brief Register a fencing level for specified node with local fencer
      *
      * \param[in,out] st           Fencer connection to use
      * \param[in]     options      Group of enum stonith_call_options
      * \param[in]     node         Target node to register level for
      * \param[in]     level        Topology level number to register
      * \param[in]     device_list  Devices to register in level
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*register_level)(stonith_t *st, int options, const char *node,
                           int level, const stonith_key_value_t *device_list);
 
     /*!
      * \brief Retrieve a fence agent's metadata
      *
      * \param[in,out] stonith       Fencer connection
      * \param[in]     call_options  Group of enum stonith_call_options
      *                              (currently ignored)
      * \param[in]     agent         Fence agent to query
      * \param[in]     namespace_s   Type of fence agent to search for ("redhat"
      *                              or "stonith-ng" for RHCS-style, "internal"
      *                              for Pacemaker-internal devices, "heartbeat"
      *                              for LHA-style, or "any" or NULL for any)
      * \param[out]    output        Where to store metadata
      * \param[in]     timeout_sec   Error if not complete within this time
      *
      * \return Legacy Pacemaker return code
      * \note The caller is responsible for freeing *output using free().
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*metadata)(stonith_t *stonith, int call_options, const char *agent,
                     const char *namespace_s, char **output, int timeout_sec);
 
     /*!
      * \brief Retrieve a list of installed fence agents
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      *                              (currently ignored)
      * \param[in]     namespace_s   Type of fence agents to list ("redhat"
      *                              or "stonith-ng" for RHCS-style, "internal" for
      *                              Pacemaker-internal devices, "heartbeat" for
      *                              LHA-style, or "any" or NULL for all)
      * \param[out]    devices       Where to store agent list
      * \param[in]     timeout       Error if unable to complete within this
      *                              (currently ignored)
      *
      * \return Number of items in list on success, or negative errno otherwise
      * \note The caller is responsible for freeing the returned list with
-     *       stonith_key_value_freeall().
+     *       \c stonith__key_value_freeall().
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*list_agents)(stonith_t *stonith, int call_options,
                        const char *namespace_s, stonith_key_value_t **devices,
                        int timeout);
 
     /*!
      * \brief Get the output of a fence device's list action
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     id            Fence device ID to run list for
      * \param[out]    list_info     Where to store list output
      * \param[in]     timeout       Error if unable to complete within this
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*list)(stonith_t *stonith, int call_options, const char *id,
                 char **list_info, int timeout);
 
     /*!
      * \brief Check whether a fence device is reachable by monitor action
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     id            Fence device ID to run monitor for
      * \param[in]     timeout       Error if unable to complete within this
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*monitor)(stonith_t *stonith, int call_options, const char *id,
                    int timeout);
 
     /*!
      * \brief Check whether a fence device target is reachable by status action
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     id            Fence device ID to run status for
      * \param[in]     port          Fence target to run status for
      * \param[in]     timeout       Error if unable to complete within this
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*status)(stonith_t *stonith, int call_options, const char *id,
                   const char *port, int timeout);
 
     /*!
      * \brief List registered fence devices
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     target        Fence target to run status for
      * \param[out]    devices       Where to store list of fence devices
      * \param[in]     timeout       Error if unable to complete within this
      *
      * \note If node is provided, only devices that can fence the node id
      *       will be returned.
      *
      * \return Number of items in list on success, or negative errno otherwise
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*query)(stonith_t *stonith, int call_options, const char *target,
                  stonith_key_value_t **devices, int timeout);
 
     /*!
      * \brief Request that a target get fenced
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     node          Fence target
      * \param[in]     action        "on", "off", or "reboot"
      * \param[in]     timeout       Default per-device timeout to use with
      *                              each executed device
      * \param[in]     tolerance     Accept result of identical fence action
      *                              completed within this time
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*fence)(stonith_t *stonith, int call_options, const char *node,
                  const char *action, int timeout, int tolerance);
 
     /*!
      * \brief Manually confirm that a node has been fenced
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     target        Fence target
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*confirm)(stonith_t *stonith, int call_options, const char *target);
 
     /*!
      * \brief List fencing actions that have occurred for a target
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     node          Fence target
      * \param[out]    history       Where to store list of fencing actions
      * \param[in]     timeout       Error if unable to complete within this
      *
      * \return Legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*history)(stonith_t *stonith, int call_options, const char *node,
                    stonith_history_t **history, int timeout);
 
     /*!
      * \brief Register a callback for fence notifications
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     event         Event to register for
      * \param[in]     callback      Callback to register
      *
      * \return Legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*register_notification)(stonith_t *stonith, const char *event,
                                  void (*callback)(stonith_t *st,
                                                   stonith_event_t *e));
 
     /*!
      * \brief Unregister callbacks for fence notifications
      *
      * \param[in,out] stonith  Fencer connection to use
      * \param[in]     event    Event to unregister callbacks for (NULL for all)
      *
      * \return Legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*remove_notification)(stonith_t *stonith, const char *event);
 
     /*!
      * \brief Register a callback for an asynchronous fencing result
      *
      * \param[in,out] stonith        Fencer connection to use
      * \param[in]     call_id        Call ID to register callback for
      * \param[in]     timeout        Error if result not received in this time
      * \param[in]     options        Group of enum stonith_call_options
      *                               (respects \c st_opt_timeout_updates and
      *                               \c st_opt_report_only_success)
      * \param[in,out] user_data      Pointer to pass to callback
      * \param[in]     callback_name  Unique identifier for callback
      * \param[in]     callback       Callback to register (may be called
      *                               immediately if \p call_id indicates error)
      *
      * \return \c TRUE on success, \c FALSE if call_id indicates error,
      *         or -EINVAL if \p stonith is not valid
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*register_callback)(stonith_t *stonith, int call_id, int timeout,
                              int options, void *user_data,
                              const char *callback_name,
                              void (*callback)(stonith_t *st,
                                               stonith_callback_data_t *data));
 
     /*!
      * \brief Unregister callbacks for asynchronous fencing results
      *
      * \param[in,out] stonith        Fencer connection to use
      * \param[in]     call_id        If \p all_callbacks is false, call ID
      *                               to unregister callback for
      * \param[in]     all_callbacks  If true, unregister all callbacks
      *
      * \return pcmk_ok
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*remove_callback)(stonith_t *stonith, int call_id, bool all_callbacks);
 
     /*!
      * \brief Unregister fencing level for specified node, pattern or attribute
      *
      * \param[in,out] st       Fencer connection to use
      * \param[in]     options  Group of enum stonith_call_options
      * \param[in]     node     If not NULL, unregister level targeting this node
      * \param[in]     pattern  If not NULL, unregister level targeting nodes
      *                         whose names match this regular expression
      * \param[in]     attr     If this and \p value are not NULL, unregister
      *                         level targeting nodes with this node attribute
      *                         set to \p value
      * \param[in]     value    If this and \p attr are not NULL, unregister
      *                         level targeting nodes with node attribute \p attr
      *                         set to this
      * \param[in]     level    Topology level number to remove
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \note The caller should set only one of \p node, \p pattern, or \p attr
      *       and \p value.
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*remove_level_full)(stonith_t *st, int options,
                              const char *node, const char *pattern,
                              const char *attr, const char *value, int level);
 
     /*!
      * \brief Register fencing level for specified node, pattern or attribute
      *
      * \param[in,out] st           Fencer connection to use
      * \param[in]     options      Group of enum stonith_call_options
      * \param[in]     node         If not NULL, register level targeting this
      *                             node by name
      * \param[in]     pattern      If not NULL, register level targeting nodes
      *                             whose names match this regular expression
      * \param[in]     attr         If this and \p value are not NULL, register
      *                             level targeting nodes with this node
      *                             attribute set to \p value
      * \param[in]     value        If this and \p attr are not NULL, register
      *                             level targeting nodes with node attribute
      *                             \p attr set to this
      * \param[in]     level        Topology level number to remove
      * \param[in]     device_list  Devices to use in level
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      *
      * \note The caller should set only one of node, pattern or attr/value.
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*register_level_full)(stonith_t *st, int options,
                                const char *node, const char *pattern,
                                const char *attr, const char *value, int level,
                                const stonith_key_value_t *device_list);
 
     /*!
      * \brief Validate an arbitrary stonith device configuration
      *
      * \param[in,out] st            Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     rsc_id        ID used to replace CIB secrets in \p params
      * \param[in]     namespace_s   Type of fence agent to validate ("redhat"
      *                              or "stonith-ng" for RHCS-style, "internal"
      *                              for Pacemaker-internal devices, "heartbeat"
      *                              for LHA-style, or "any" or NULL for any)
      * \param[in]     agent         Fence agent to validate
      * \param[in]     params        Configuration parameters to pass to agent
      * \param[in]     timeout       Fail if no response within this many seconds
      * \param[out]    output        If non-NULL, where to store any agent output
      * \param[out]    error_output  If non-NULL, where to store agent error output
      *
      * \return pcmk_ok if validation succeeds, -errno otherwise
      * \note If pcmk_ok is returned, the caller is responsible for freeing
      *       the output (if requested) with free().
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*validate)(stonith_t *st, int call_options, const char *rsc_id,
                     const char *namespace_s, const char *agent,
                     const stonith_key_value_t *params, int timeout,
                     char **output, char **error_output);
 
     /*!
      * \brief Request delayed fencing of a target
      *
      * \param[in,out] stonith       Fencer connection to use
      * \param[in]     call_options  Group of enum stonith_call_options
      * \param[in]     node          Fence target
      * \param[in]     action        "on", "off", or "reboot"
      * \param[in]     timeout       Default per-device timeout to use with
      *                              each executed device
      * \param[in]     tolerance     Accept result of identical fence action
      *                              completed within this time
      * \param[in]     delay         Execute fencing after this delay (-1
      *                              disables any delay from pcmk_delay_base
      *                              and pcmk_delay_max)
      *
      * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
      *         on success, otherwise a negative legacy Pacemaker return code
      * \deprecated \c stonith_api_operations_t is deprecated for external use
      */
     int (*fence_with_delay)(stonith_t *stonith, int call_options,
                             const char *node, const char *action, int timeout,
                             int tolerance, int delay);
 
 } stonith_api_operations_t;
 
 // @TODO Keep this object but make it internal
 /*!
  * \brief Fencer API connection object
  * \deprecated Use appropriate functions in libpacemaker instead
  */
 struct stonith_s {
     enum stonith_state state;
     int call_id;
     void *st_private;
     stonith_api_operations_t *cmds;
 };
 
 /* Core functions */
 void stonith_key_value_freeall(stonith_key_value_t * kvp, int keys, int values);
 
 void stonith_history_free(stonith_history_t *history);
 
 // Convenience functions
 int stonith_api_connect_retry(stonith_t *st, const char *name,
                               int max_attempts);
 const char *stonith_op_state_str(enum op_state state);
 
 /* Basic helpers that allows nodes to be fenced and the history to be
  * queried without mainloop or the caller understanding the full API
  *
  * At least one of nodeid and uname are required
  *
  * NOTE: DLM uses both of these
  */
 int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off);
 time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress);
 
 /*
  * Helpers for using the above functions without install-time dependencies
  *
  * Usage:
  *  #include <crm/stonith-ng.h>
  *
  * To turn a node off by corosync nodeid:
  *  stonith_api_kick_helper(nodeid, 120, 1);
  *
  * To check the last fence date/time (also by nodeid):
  *  last = stonith_api_time_helper(nodeid, 0);
  *
  * To check if fencing is in progress:
  *  if(stonith_api_time_helper(nodeid, 1) > 0) { ... }
  *
  * eg.
 
  #include <stdio.h>
  #include <time.h>
  #include <crm/stonith-ng.h>
  int
  main(int argc, char ** argv)
  {
      int rc = 0;
      int nodeid = 102;
 
      rc = stonith_api_time_helper(nodeid, 0);
      printf("%d last fenced at %s\n", nodeid, ctime(rc));
 
      rc = stonith_api_kick_helper(nodeid, 120, 1);
      printf("%d fence result: %d\n", nodeid, rc);
 
      rc = stonith_api_time_helper(nodeid, 0);
      printf("%d last fenced at %s\n", nodeid, ctime(rc));
 
      return 0;
  }
 
  */
 
 #define STONITH_LIBRARY "libstonithd.so.56"
 
 typedef int (*st_api_kick_fn) (int nodeid, const char *uname, int timeout, bool off);
 typedef time_t (*st_api_time_fn) (int nodeid, const char *uname, bool in_progress);
 
 static inline int
 stonith_api_kick_helper(uint32_t nodeid, int timeout, bool off)
 {
     static void *st_library = NULL;
     static st_api_kick_fn st_kick_fn;
 
     if (st_library == NULL) {
         st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
     }
     if (st_library && st_kick_fn == NULL) {
         st_kick_fn = (st_api_kick_fn) dlsym(st_library, "stonith_api_kick");
     }
     if (st_kick_fn == NULL) {
 #ifdef ELIBACC
         return -ELIBACC;
 #else
         return -ENOSYS;
 #endif
     }
 
     return (*st_kick_fn) (nodeid, NULL, timeout, off);
 }
 
 static inline time_t
 stonith_api_time_helper(uint32_t nodeid, bool in_progress)
 {
     static void *st_library = NULL;
     static st_api_time_fn st_time_fn;
 
     if (st_library == NULL) {
         st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
     }
     if (st_library && st_time_fn == NULL) {
         st_time_fn = (st_api_time_fn) dlsym(st_library, "stonith_api_time");
     }
     if (st_time_fn == NULL) {
         return 0;
     }
 
     return (*st_time_fn) (nodeid, NULL, in_progress);
 }
 
 /**
  * Does the given agent describe a stonith resource that can exist?
  *
  * \param[in] agent     What is the name of the agent?
  * \param[in] timeout   Timeout to use when querying.  If 0 is given,
  *                      use a default of 120.
  *
  * \return A boolean
  */
 bool stonith_agent_exists(const char *agent, int timeout);
 
 /*!
  * \brief Turn fence action into a more readable string
  *
  * \param[in] action  Fence action
  */
 const char *stonith_action_str(const char *action);
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 
 /* Normally we'd put this section in a separate file (crm/fencing/compat.h), but
  * we can't do that for the reason noted at the top of this file. That does mean
  * we have to duplicate these declarations where they're implemented.
  */
 
 //! \deprecated Use appropriate functions in libpacemaker
 stonith_t *stonith_api_new(void);
 
 //! \deprecated Use appropriate functions in libpacemaker
 void stonith_api_delete(stonith_t *stonith);
 
 //! \deprecated Do not use
 void stonith_dump_pending_callbacks(stonith_t *stonith);
 
 //! \deprecated Do not use
 bool stonith_dispatch(stonith_t *stonith_api);
 
 //! \deprecated Do not use
 stonith_key_value_t *stonith_key_value_add(stonith_key_value_t *kvp,
                                            const char *key, const char *value);
 
 #endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c
index 9bf5c3d8a6..1df406243b 100644
--- a/lib/fencing/st_client.c
+++ b/lib/fencing/st_client.c
@@ -1,2818 +1,2834 @@
 /*
  * Copyright 2004-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <stdbool.h>
 #include <string.h>
 #include <ctype.h>
 #include <inttypes.h>
 #include <sys/types.h>
 
 #include <glib.h>
 #include <libxml/tree.h>            // xmlNode
 #include <libxml/xpath.h>           // xmlXPathObject, etc.
 
 #include <crm/crm.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 #include <crm/common/xml.h>
 
 #include <crm/common/mainloop.h>
 
 #include "fencing_private.h"
 
 CRM_TRACE_INIT_DATA(stonith);
 
 // Used as stonith_t:st_private
 typedef struct stonith_private_s {
     char *token;
     crm_ipc_t *ipc;
     mainloop_io_t *source;
     GHashTable *stonith_op_callback_table;
     GList *notify_list;
     int notify_refcnt;
     bool notify_deletes;
 
     void (*op_callback) (stonith_t * st, stonith_callback_data_t * data);
 
 } stonith_private_t;
 
 // Used as stonith_event_t:opaque
 struct event_private {
     pcmk__action_result_t result;
 };
 
 typedef struct stonith_notify_client_s {
     const char *event;
     const char *obj_id;         /* implement one day */
     const char *obj_type;       /* implement one day */
     void (*notify) (stonith_t * st, stonith_event_t * e);
     bool delete;
 
 } stonith_notify_client_t;
 
 typedef struct stonith_callback_client_s {
     void (*callback) (stonith_t * st, stonith_callback_data_t * data);
     const char *id;
     void *user_data;
     gboolean only_success;
     gboolean allow_timeout_updates;
     struct timer_rec_s *timer;
 
 } stonith_callback_client_t;
 
 struct notify_blob_s {
     stonith_t *stonith;
     xmlNode *xml;
 };
 
 struct timer_rec_s {
     int call_id;
     int timeout;
     guint ref;
     stonith_t *stonith;
 };
 
 typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *,
                              xmlNode *, xmlNode *, xmlNode **, xmlNode **);
 
 xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
                            int call_options);
 static int stonith_send_command(stonith_t *stonith, const char *op,
                                 xmlNode *data, xmlNode **output_data,
                                 int call_options, int timeout);
 
 static void stonith_connection_destroy(gpointer user_data);
 static void stonith_send_notification(gpointer data, gpointer user_data);
 static int stonith_api_del_notification(stonith_t *stonith,
                                         const char *event);
 /*!
  * \brief Get agent namespace by name
  *
  * \param[in] namespace_s  Name of namespace as string
  *
  * \return Namespace as enum value
  */
 enum stonith_namespace
 stonith_text2namespace(const char *namespace_s)
 {
     if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) {
         return st_namespace_any;
 
     } else if (!strcmp(namespace_s, "redhat")
                || !strcmp(namespace_s, "stonith-ng")) {
         return st_namespace_rhcs;
 
     } else if (!strcmp(namespace_s, "internal")) {
         return st_namespace_internal;
 
     } else if (!strcmp(namespace_s, "heartbeat")) {
         return st_namespace_lha;
     }
     return st_namespace_invalid;
 }
 
 /*!
  * \brief Get agent namespace name
  *
  * \param[in] namespace  Namespace as enum value
  *
  * \return Namespace name as string
  */
 const char *
 stonith_namespace2text(enum stonith_namespace st_namespace)
 {
     switch (st_namespace) {
         case st_namespace_any:      return "any";
         case st_namespace_rhcs:     return "stonith-ng";
         case st_namespace_internal: return "internal";
         case st_namespace_lha:      return "heartbeat";
         default:                    break;
     }
     return "unsupported";
 }
 
 /*!
  * \brief Determine namespace of a fence agent
  *
  * \param[in] agent        Fence agent type
  * \param[in] namespace_s  Name of agent namespace as string, if known
  *
  * \return Namespace of specified agent, as enum value
  */
 enum stonith_namespace
 stonith_get_namespace(const char *agent, const char *namespace_s)
 {
     if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) {
         return st_namespace_internal;
     }
 
     if (stonith__agent_is_rhcs(agent)) {
         return st_namespace_rhcs;
     }
 
 #if HAVE_STONITH_STONITH_H
     if (stonith__agent_is_lha(agent)) {
         return st_namespace_lha;
     }
 #endif
 
     return st_namespace_invalid;
 }
 
 gboolean
 stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node)
 {
     gboolean rv = FALSE;
     stonith_t *stonith_api = (st != NULL)? st : stonith__api_new();
     char *list = NULL;
 
     if(stonith_api) {
         if (stonith_api->state == stonith_disconnected) {
             int rc = stonith_api->cmds->connect(stonith_api, "stonith-api", NULL);
 
             if (rc != pcmk_ok) {
                 crm_err("Failed connecting to Stonith-API for watchdog-fencing-query.");
             }
         }
 
         if (stonith_api->state != stonith_disconnected) {
             /* caveat!!!
              * this might fail when when stonithd is just updating the device-list
              * probably something we should fix as well for other api-calls */
             int rc = stonith_api->cmds->list(stonith_api, st_opt_sync_call, STONITH_WATCHDOG_ID, &list, 0);
             if ((rc != pcmk_ok) || (list == NULL)) {
                 /* due to the race described above it can happen that
                  * we drop in here - so as not to make remote nodes
                  * panic on that answer
                  */
                 if (rc == -ENODEV) {
                     crm_notice("Cluster does not have watchdog fencing device");
                 } else {
                     crm_warn("Could not check for watchdog fencing device: %s",
                              pcmk_strerror(rc));
                 }
             } else if (list[0] == '\0') {
                 rv = TRUE;
             } else {
                 GList *targets = stonith__parse_targets(list);
                 rv = pcmk__str_in_list(node, targets, pcmk__str_casei);
                 g_list_free_full(targets, free);
             }
             free(list);
             if (!st) {
                 /* if we're provided the api we still might have done the
                  * connection - but let's assume the caller won't bother
                  */
                 stonith_api->cmds->disconnect(stonith_api);
             }
         }
 
         if (!st) {
             stonith__api_free(stonith_api);
         }
     } else {
         crm_err("Stonith-API for watchdog-fencing-query couldn't be created.");
     }
     crm_trace("Pacemaker assumes node %s %sto do watchdog-fencing.",
               node, rv?"":"not ");
     return rv;
 }
 
 gboolean
 stonith__watchdog_fencing_enabled_for_node(const char *node)
 {
     return stonith__watchdog_fencing_enabled_for_node_api(NULL, node);
 }
 
 /* when cycling through the list we don't want to delete items
    so just mark them and when we know nobody is using the list
    loop over it to remove the marked items
  */
 static void
 foreach_notify_entry (stonith_private_t *private,
                 GFunc func,
                 gpointer user_data)
 {
     private->notify_refcnt++;
     g_list_foreach(private->notify_list, func, user_data);
     private->notify_refcnt--;
     if ((private->notify_refcnt == 0) &&
         private->notify_deletes) {
         GList *list_item = private->notify_list;
 
         private->notify_deletes = FALSE;
         while (list_item != NULL)
         {
             stonith_notify_client_t *list_client = list_item->data;
             GList *next = g_list_next(list_item);
 
             if (list_client->delete) {
                 free(list_client);
                 private->notify_list =
                     g_list_delete_link(private->notify_list, list_item);
             }
             list_item = next;
         }
     }
 }
 
 static void
 stonith_connection_destroy(gpointer user_data)
 {
     stonith_t *stonith = user_data;
     stonith_private_t *native = NULL;
     struct notify_blob_s blob;
 
     crm_trace("Sending destroyed notification");
     blob.stonith = stonith;
     blob.xml = pcmk__xe_create(NULL, PCMK__XE_NOTIFY);
 
     native = stonith->st_private;
     native->ipc = NULL;
     native->source = NULL;
 
     free(native->token); native->token = NULL;
     stonith->state = stonith_disconnected;
     crm_xml_add(blob.xml, PCMK__XA_T, PCMK__VALUE_ST_NOTIFY);
     crm_xml_add(blob.xml, PCMK__XA_SUBT, PCMK__VALUE_ST_NOTIFY_DISCONNECT);
 
     foreach_notify_entry(native, stonith_send_notification, &blob);
     pcmk__xml_free(blob.xml);
 }
 
 xmlNode *
 create_device_registration_xml(const char *id, enum stonith_namespace standard,
                                const char *agent,
                                const stonith_key_value_t *params,
                                const char *rsc_provides)
 {
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
     xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
 
 #if HAVE_STONITH_STONITH_H
     if (standard == st_namespace_any) {
         standard = stonith_get_namespace(agent, NULL);
     }
     if (standard == st_namespace_lha) {
         hash2field((gpointer) "plugin", (gpointer) agent, args);
         agent = "fence_legacy";
     }
 #endif
 
     crm_xml_add(data, PCMK_XA_ID, id);
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
     crm_xml_add(data, PCMK_XA_AGENT, agent);
     if ((standard != st_namespace_any) && (standard != st_namespace_invalid)) {
         crm_xml_add(data, PCMK__XA_NAMESPACE,
                     stonith_namespace2text(standard));
     }
     if (rsc_provides) {
         crm_xml_add(data, PCMK__XA_RSC_PROVIDES, rsc_provides);
     }
 
     for (; params; params = params->next) {
         hash2field((gpointer) params->key, (gpointer) params->value, args);
     }
 
     return data;
 }
 
 static int
 stonith_api_register_device(stonith_t *st, int call_options,
                             const char *id, const char *namespace_s,
                             const char *agent,
                             const stonith_key_value_t *params)
 {
     int rc = 0;
     xmlNode *data = NULL;
 
     data = create_device_registration_xml(id,
                                           stonith_text2namespace(namespace_s),
                                           agent, params, NULL);
 
     rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_remove_device(stonith_t * st, int call_options, const char *name)
 {
     int rc = 0;
     xmlNode *data = NULL;
 
     data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
     crm_xml_add(data, PCMK_XA_ID, name);
     rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_remove_level_full(stonith_t *st, int options,
                               const char *node, const char *pattern,
                               const char *attr, const char *value, int level)
 {
     int rc = 0;
     xmlNode *data = NULL;
 
     CRM_CHECK(node || pattern || (attr && value), return -EINVAL);
 
     data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
 
     if (node) {
         crm_xml_add(data, PCMK_XA_TARGET, node);
 
     } else if (pattern) {
         crm_xml_add(data, PCMK_XA_TARGET_PATTERN, pattern);
 
     } else {
         crm_xml_add(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
         crm_xml_add(data, PCMK_XA_TARGET_VALUE, value);
     }
 
     crm_xml_add_int(data, PCMK_XA_INDEX, level);
     rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_remove_level(stonith_t * st, int options, const char *node, int level)
 {
     return stonith_api_remove_level_full(st, options, node,
                                          NULL, NULL, NULL, level);
 }
 
 /*!
  * \internal
  * \brief Create XML for fence topology level registration request
  *
  * \param[in] node        If not NULL, target level by this node name
  * \param[in] pattern     If not NULL, target by node name using this regex
  * \param[in] attr        If not NULL, target by this node attribute
  * \param[in] value       If not NULL, target by this node attribute value
  * \param[in] level       Index number of level to register
  * \param[in] device_list List of devices in level
  *
  * \return Newly allocated XML tree on success, NULL otherwise
  *
  * \note The caller should set only one of node, pattern or attr/value.
  */
 xmlNode *
 create_level_registration_xml(const char *node, const char *pattern,
                               const char *attr, const char *value,
                               int level, const stonith_key_value_t *device_list)
 {
     GString *list = NULL;
     xmlNode *data;
 
     CRM_CHECK(node || pattern || (attr && value), return NULL);
 
     data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
 
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
     crm_xml_add_int(data, PCMK_XA_ID, level);
     crm_xml_add_int(data, PCMK_XA_INDEX, level);
 
     if (node) {
         crm_xml_add(data, PCMK_XA_TARGET, node);
 
     } else if (pattern) {
         crm_xml_add(data, PCMK_XA_TARGET_PATTERN, pattern);
 
     } else {
         crm_xml_add(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
         crm_xml_add(data, PCMK_XA_TARGET_VALUE, value);
     }
 
     for (; device_list; device_list = device_list->next) {
         pcmk__add_separated_word(&list, 1024, device_list->value, ",");
     }
 
     if (list != NULL) {
         crm_xml_add(data, PCMK_XA_DEVICES, (const char *) list->str);
         g_string_free(list, TRUE);
     }
     return data;
 }
 
 static int
 stonith_api_register_level_full(stonith_t *st, int options, const char *node,
                                 const char *pattern, const char *attr,
                                 const char *value, int level,
                                 const stonith_key_value_t *device_list)
 {
     int rc = 0;
     xmlNode *data = create_level_registration_xml(node, pattern, attr, value,
                                                   level, device_list);
     CRM_CHECK(data != NULL, return -EINVAL);
 
     rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_register_level(stonith_t * st, int options, const char *node, int level,
                            const stonith_key_value_t * device_list)
 {
     return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL,
                                            level, device_list);
 }
 
 static int
 stonith_api_device_list(stonith_t *stonith, int call_options,
                         const char *namespace_s, stonith_key_value_t **devices,
                         int timeout)
 {
     int count = 0;
     enum stonith_namespace ns = stonith_text2namespace(namespace_s);
 
     if (devices == NULL) {
         crm_err("Parameter error: stonith_api_device_list");
         return -EFAULT;
     }
 
 #if HAVE_STONITH_STONITH_H
     // Include Linux-HA agents if requested
     if ((ns == st_namespace_any) || (ns == st_namespace_lha)) {
         count += stonith__list_lha_agents(devices);
     }
 #endif
 
     // Include Red Hat agents if requested
     if ((ns == st_namespace_any) || (ns == st_namespace_rhcs)) {
         count += stonith__list_rhcs_agents(devices);
     }
 
     return count;
 }
 
 // See stonith_api_operations_t:metadata() documentation
 static int
 stonith_api_device_metadata(stonith_t *stonith, int call_options,
                             const char *agent, const char *namespace_s,
                             char **output, int timeout_sec)
 {
     /* By executing meta-data directly, we can get it from stonith_admin when
      * the cluster is not running, which is important for higher-level tools.
      */
 
     enum stonith_namespace ns = stonith_get_namespace(agent, namespace_s);
 
     if (timeout_sec <= 0) {
         timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
     }
 
     crm_trace("Looking up metadata for %s agent %s",
               stonith_namespace2text(ns), agent);
 
     switch (ns) {
         case st_namespace_rhcs:
             return stonith__rhcs_metadata(agent, timeout_sec, output);
 
 #if HAVE_STONITH_STONITH_H
         case st_namespace_lha:
             return stonith__lha_metadata(agent, timeout_sec, output);
 #endif
 
         default:
             crm_err("Can't get fence agent '%s' meta-data: No such agent",
                     agent);
             break;
     }
     return -ENODEV;
 }
 
 static int
 stonith_api_query(stonith_t * stonith, int call_options, const char *target,
                   stonith_key_value_t ** devices, int timeout)
 {
     int rc = 0, lpc = 0, max = 0;
 
     xmlNode *data = NULL;
     xmlNode *output = NULL;
     xmlXPathObject *xpathObj = NULL;
 
     CRM_CHECK(devices != NULL, return -EINVAL);
 
     data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_ST_TARGET, target);
     crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, PCMK_ACTION_OFF);
     rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout);
 
     if (rc < 0) {
         return rc;
     }
 
     xpathObj = pcmk__xpath_search(output->doc, "//*[@" PCMK_XA_AGENT "]");
     if (xpathObj) {
         max = pcmk__xpath_num_results(xpathObj);
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
 
             CRM_LOG_ASSERT(match != NULL);
             if(match != NULL) {
                 const char *match_id = crm_element_value(match, PCMK_XA_ID);
                 xmlChar *match_path = xmlGetNodePath(match);
 
                 crm_info("//*[@" PCMK_XA_AGENT "][%d] = %s", lpc, match_path);
                 free(match_path);
                 *devices = stonith__key_value_add(*devices, NULL, match_id);
             }
         }
 
         xmlXPathFreeObject(xpathObj);
     }
 
     pcmk__xml_free(output);
     pcmk__xml_free(data);
     return max;
 }
 
 /*!
  * \internal
  * \brief Make a STONITH_OP_EXEC request
  *
  * \param[in,out] stonith       Fencer connection
  * \param[in]     call_options  Bitmask of \c stonith_call_options
  * \param[in]     id            Fence device ID that request is for
  * \param[in]     action        Agent action to request (list, status, monitor)
  * \param[in]     target        Name of target node for requested action
  * \param[in]     timeout_sec   Error if not completed within this many seconds
  * \param[out]    output        Where to set agent output
  */
 static int
 stonith_api_call(stonith_t *stonith, int call_options, const char *id,
                  const char *action, const char *target, int timeout_sec,
                  xmlNode **output)
 {
     int rc = 0;
     xmlNode *data = NULL;
 
     data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
     crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_ST_DEVICE_ID, id);
     crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, action);
     crm_xml_add(data, PCMK__XA_ST_TARGET, target);
 
     rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output,
                               call_options, timeout_sec);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info,
                  int timeout)
 {
     int rc;
     xmlNode *output = NULL;
 
     rc = stonith_api_call(stonith, call_options, id, PCMK_ACTION_LIST, NULL,
                           timeout, &output);
 
     if (output && list_info) {
         const char *list_str;
 
         list_str = crm_element_value(output, PCMK__XA_ST_OUTPUT);
 
         if (list_str) {
             *list_info = strdup(list_str);
         }
     }
 
     if (output) {
         pcmk__xml_free(output);
     }
 
     return rc;
 }
 
 static int
 stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout)
 {
     return stonith_api_call(stonith, call_options, id, PCMK_ACTION_MONITOR,
                             NULL, timeout, NULL);
 }
 
 static int
 stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port,
                    int timeout)
 {
     return stonith_api_call(stonith, call_options, id, PCMK_ACTION_STATUS, port,
                             timeout, NULL);
 }
 
 static int
 stonith_api_fence_with_delay(stonith_t * stonith, int call_options, const char *node,
                              const char *action, int timeout, int tolerance, int delay)
 {
     int rc = 0;
     xmlNode *data = NULL;
 
     data = pcmk__xe_create(NULL, __func__);
     crm_xml_add(data, PCMK__XA_ST_TARGET, node);
     crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, action);
     crm_xml_add_int(data, PCMK__XA_ST_TIMEOUT, timeout);
     crm_xml_add_int(data, PCMK__XA_ST_TOLERANCE, tolerance);
     crm_xml_add_int(data, PCMK__XA_ST_DELAY, delay);
 
     rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action,
                   int timeout, int tolerance)
 {
     return stonith_api_fence_with_delay(stonith, call_options, node, action,
                                         timeout, tolerance, 0);
 }
 
 static int
 stonith_api_confirm(stonith_t * stonith, int call_options, const char *target)
 {
     stonith__set_call_options(call_options, target, st_opt_manual_ack);
     return stonith_api_fence(stonith, call_options, target, PCMK_ACTION_OFF, 0,
                              0);
 }
 
 static int
 stonith_api_history(stonith_t * stonith, int call_options, const char *node,
                     stonith_history_t ** history, int timeout)
 {
     int rc = 0;
     xmlNode *data = NULL;
     xmlNode *output = NULL;
     stonith_history_t *last = NULL;
 
     *history = NULL;
 
     if (node) {
         data = pcmk__xe_create(NULL, __func__);
         crm_xml_add(data, PCMK__XA_ST_TARGET, node);
     }
 
     stonith__set_call_options(call_options, node, st_opt_sync_call);
     rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output,
                               call_options, timeout);
     pcmk__xml_free(data);
 
     if (rc == 0) {
         xmlNode *op = NULL;
         xmlNode *reply = pcmk__xpath_find_one(output->doc,
                                               "//" PCMK__XE_ST_HISTORY,
                                               LOG_NEVER);
 
         for (op = pcmk__xe_first_child(reply, NULL, NULL, NULL); op != NULL;
              op = pcmk__xe_next(op, NULL)) {
             stonith_history_t *kvp;
             long long completed;
             long long completed_nsec = 0L;
 
             kvp = pcmk__assert_alloc(1, sizeof(stonith_history_t));
             kvp->target = crm_element_value_copy(op, PCMK__XA_ST_TARGET);
             kvp->action = crm_element_value_copy(op, PCMK__XA_ST_DEVICE_ACTION);
             kvp->origin = crm_element_value_copy(op, PCMK__XA_ST_ORIGIN);
             kvp->delegate = crm_element_value_copy(op, PCMK__XA_ST_DELEGATE);
             kvp->client = crm_element_value_copy(op, PCMK__XA_ST_CLIENTNAME);
             crm_element_value_ll(op, PCMK__XA_ST_DATE, &completed);
             kvp->completed = (time_t) completed;
             crm_element_value_ll(op, PCMK__XA_ST_DATE_NSEC, &completed_nsec);
             kvp->completed_nsec = completed_nsec;
             crm_element_value_int(op, PCMK__XA_ST_STATE, &kvp->state);
             kvp->exit_reason = crm_element_value_copy(op, PCMK_XA_EXIT_REASON);
 
             if (last) {
                 last->next = kvp;
             } else {
                 *history = kvp;
             }
             last = kvp;
         }
     }
 
     pcmk__xml_free(output);
 
     return rc;
 }
 
 void stonith_history_free(stonith_history_t *history)
 {
     stonith_history_t *hp, *hp_old;
 
     for (hp = history; hp; hp_old = hp, hp = hp->next, free(hp_old)) {
         free(hp->target);
         free(hp->action);
         free(hp->origin);
         free(hp->delegate);
         free(hp->client);
         free(hp->exit_reason);
     }
 }
 
 static gint
 stonithlib_GCompareFunc(gconstpointer a, gconstpointer b)
 {
     int rc = 0;
     const stonith_notify_client_t *a_client = a;
     const stonith_notify_client_t *b_client = b;
 
     if (a_client->delete || b_client->delete) {
         /* make entries marked for deletion not findable */
         return -1;
     }
     CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
     rc = strcmp(a_client->event, b_client->event);
     if (rc == 0) {
         if (a_client->notify == NULL || b_client->notify == NULL) {
             return 0;
 
         } else if (a_client->notify == b_client->notify) {
             return 0;
 
         } else if (((long)a_client->notify) < ((long)b_client->notify)) {
             crm_err("callbacks for %s are not equal: %p vs. %p",
                     a_client->event, a_client->notify, b_client->notify);
             return -1;
         }
         crm_err("callbacks for %s are not equal: %p vs. %p",
                 a_client->event, a_client->notify, b_client->notify);
         return 1;
     }
     return rc;
 }
 
 xmlNode *
 stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options)
 {
     xmlNode *op_msg = NULL;
 
     CRM_CHECK(token != NULL, return NULL);
 
     op_msg = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
     crm_xml_add(op_msg, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
     crm_xml_add(op_msg, PCMK__XA_ST_OP, op);
     crm_xml_add_int(op_msg, PCMK__XA_ST_CALLID, call_id);
     crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
     crm_xml_add_int(op_msg, PCMK__XA_ST_CALLOPT, call_options);
 
     if (data != NULL) {
         xmlNode *wrapper = pcmk__xe_create(op_msg, PCMK__XE_ST_CALLDATA);
 
         pcmk__xml_copy(wrapper, data);
     }
 
     return op_msg;
 }
 
 static void
 stonith_destroy_op_callback(gpointer data)
 {
     stonith_callback_client_t *blob = data;
 
     if (blob->timer && blob->timer->ref > 0) {
         g_source_remove(blob->timer->ref);
     }
     free(blob->timer);
     free(blob);
 }
 
 static int
 stonith_api_signoff(stonith_t * stonith)
 {
     stonith_private_t *native = stonith->st_private;
 
     crm_debug("Disconnecting from the fencer");
 
     if (native->source != NULL) {
         /* Attached to mainloop */
         mainloop_del_ipc_client(native->source);
         native->source = NULL;
         native->ipc = NULL;
 
     } else if (native->ipc) {
         /* Not attached to mainloop */
         crm_ipc_t *ipc = native->ipc;
 
         native->ipc = NULL;
         crm_ipc_close(ipc);
         crm_ipc_destroy(ipc);
     }
 
     free(native->token); native->token = NULL;
     stonith->state = stonith_disconnected;
     return pcmk_ok;
 }
 
 static int
 stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks)
 {
     stonith_private_t *private = stonith->st_private;
 
     if (all_callbacks) {
         private->op_callback = NULL;
         g_hash_table_destroy(private->stonith_op_callback_table);
         private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
 
     } else if (call_id == 0) {
         private->op_callback = NULL;
 
     } else {
         pcmk__intkey_table_remove(private->stonith_op_callback_table, call_id);
     }
     return pcmk_ok;
 }
 
 /*!
  * \internal
  * \brief Invoke a (single) specified fence action callback
  *
  * \param[in,out] st        Fencer API connection
  * \param[in]     call_id   If positive, call ID of completed fence action,
  *                          otherwise legacy return code for early failure
  * \param[in,out] result    Full result for action
  * \param[in,out] userdata  User data to pass to callback
  * \param[in]     callback  Fence action callback to invoke
  */
 static void
 invoke_fence_action_callback(stonith_t *st, int call_id,
                              pcmk__action_result_t *result,
                              void *userdata,
                              void (*callback) (stonith_t *st,
                                                stonith_callback_data_t *data))
 {
     stonith_callback_data_t data = { 0, };
 
     data.call_id = call_id;
     data.rc = pcmk_rc2legacy(stonith__result2rc(result));
     data.userdata = userdata;
     data.opaque = (void *) result;
 
     callback(st, &data);
 }
 
 /*!
  * \internal
  * \brief Invoke any callbacks registered for a specified fence action result
  *
  * Given a fence action result from the fencer, invoke any callback registered
  * for that action, as well as any global callback registered.
  *
  * \param[in,out] stonith   Fencer API connection
  * \param[in]     msg       If non-NULL, fencer reply
  * \param[in]     call_id   If \p msg is NULL, call ID of action that timed out
  */
 static void
 invoke_registered_callbacks(stonith_t *stonith, const xmlNode *msg, int call_id)
 {
     stonith_private_t *private = NULL;
     stonith_callback_client_t *cb_info = NULL;
     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
     CRM_CHECK(stonith != NULL, return);
     CRM_CHECK(stonith->st_private != NULL, return);
 
     private = stonith->st_private;
 
     if (msg == NULL) {
         // Fencer didn't reply in time
         pcmk__set_result(&result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT,
                          "Fencer accepted request but did not reply in time");
         CRM_LOG_ASSERT(call_id > 0);
 
     } else {
         // We have the fencer reply
         if ((crm_element_value_int(msg, PCMK__XA_ST_CALLID, &call_id) != 0)
             || (call_id <= 0)) {
             crm_log_xml_warn(msg, "Bad fencer reply");
         }
         stonith__xe_get_result(msg, &result);
     }
 
     if (call_id > 0) {
         cb_info = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
                                             call_id);
     }
 
     if ((cb_info != NULL) && (cb_info->callback != NULL)
         && (pcmk__result_ok(&result) || !(cb_info->only_success))) {
         crm_trace("Invoking callback %s for call %d",
                   pcmk__s(cb_info->id, "without ID"), call_id);
         invoke_fence_action_callback(stonith, call_id, &result,
                                      cb_info->user_data, cb_info->callback);
 
     } else if ((private->op_callback == NULL) && !pcmk__result_ok(&result)) {
         crm_warn("Fencing action without registered callback failed: %d (%s%s%s)",
                  result.exit_status,
                  pcmk_exec_status_str(result.execution_status),
                  ((result.exit_reason == NULL)? "" : ": "),
                  ((result.exit_reason == NULL)? "" : result.exit_reason));
         crm_log_xml_debug(msg, "Failed fence update");
     }
 
     if (private->op_callback != NULL) {
         crm_trace("Invoking global callback for call %d", call_id);
         invoke_fence_action_callback(stonith, call_id, &result, NULL,
                                      private->op_callback);
     }
 
     if (cb_info != NULL) {
         stonith_api_del_callback(stonith, call_id, FALSE);
     }
     pcmk__reset_result(&result);
 }
 
 static gboolean
 stonith_async_timeout_handler(gpointer data)
 {
     struct timer_rec_s *timer = data;
 
     crm_err("Async call %d timed out after %dms", timer->call_id, timer->timeout);
     invoke_registered_callbacks(timer->stonith, NULL, timer->call_id);
 
     /* Always return TRUE, never remove the handler
      * We do that in stonith_del_callback()
      */
     return TRUE;
 }
 
 static void
 set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id,
                      int timeout)
 {
     struct timer_rec_s *async_timer = callback->timer;
 
     if (timeout <= 0) {
         return;
     }
 
     if (!async_timer) {
         async_timer = pcmk__assert_alloc(1, sizeof(struct timer_rec_s));
         callback->timer = async_timer;
     }
 
     async_timer->stonith = stonith;
     async_timer->call_id = call_id;
     /* Allow a fair bit of grace to allow the server to tell us of a timeout
      * This is only a fallback
      */
     async_timer->timeout = (timeout + 60) * 1000;
     if (async_timer->ref) {
         g_source_remove(async_timer->ref);
     }
     async_timer->ref =
         pcmk__create_timer(async_timer->timeout, stonith_async_timeout_handler,
                            async_timer);
 }
 
 static void
 update_callback_timeout(int call_id, int timeout, stonith_t * st)
 {
     stonith_callback_client_t *callback = NULL;
     stonith_private_t *private = st->st_private;
 
     callback = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
                                          call_id);
     if (!callback || !callback->allow_timeout_updates) {
         return;
     }
 
     set_callback_timeout(callback, st, call_id, timeout);
 }
 
 static int
 stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata)
 {
     const char *type = NULL;
     struct notify_blob_s blob;
 
     stonith_t *st = userdata;
     stonith_private_t *private = NULL;
 
     pcmk__assert(st != NULL);
     private = st->st_private;
 
     blob.stonith = st;
     blob.xml = pcmk__xml_parse(buffer);
     if (blob.xml == NULL) {
         crm_warn("Received malformed message from fencer: %s", buffer);
         return 0;
     }
 
     /* do callbacks */
     type = crm_element_value(blob.xml, PCMK__XA_T);
     crm_trace("Activating %s callbacks...", type);
 
     if (pcmk__str_eq(type, PCMK__VALUE_STONITH_NG, pcmk__str_none)) {
         invoke_registered_callbacks(st, blob.xml, 0);
 
     } else if (pcmk__str_eq(type, PCMK__VALUE_ST_NOTIFY, pcmk__str_none)) {
         foreach_notify_entry(private, stonith_send_notification, &blob);
 
     } else if (pcmk__str_eq(type, PCMK__VALUE_ST_ASYNC_TIMEOUT_VALUE,
                             pcmk__str_none)) {
         int call_id = 0;
         int timeout = 0;
 
         crm_element_value_int(blob.xml, PCMK__XA_ST_TIMEOUT, &timeout);
         crm_element_value_int(blob.xml, PCMK__XA_ST_CALLID, &call_id);
 
         update_callback_timeout(call_id, timeout, st);
     } else {
         crm_err("Unknown message type: %s", type);
         crm_log_xml_warn(blob.xml, "BadReply");
     }
 
     pcmk__xml_free(blob.xml);
     return 1;
 }
 
 static int
 stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd)
 {
     int rc = pcmk_ok;
     stonith_private_t *native = NULL;
     const char *display_name = name? name : "client";
 
     struct ipc_client_callbacks st_callbacks = {
         .dispatch = stonith_dispatch_internal,
         .destroy = stonith_connection_destroy
     };
 
     CRM_CHECK(stonith != NULL, return -EINVAL);
 
     native = stonith->st_private;
     pcmk__assert(native != NULL);
 
     crm_debug("Attempting fencer connection by %s with%s mainloop",
               display_name, (stonith_fd? "out" : ""));
 
     stonith->state = stonith_connected_command;
     if (stonith_fd) {
         /* No mainloop */
         native->ipc = crm_ipc_new("stonith-ng", 0);
         if (native->ipc != NULL) {
             rc = pcmk__connect_generic_ipc(native->ipc);
             if (rc == pcmk_rc_ok) {
                 rc = pcmk__ipc_fd(native->ipc, stonith_fd);
                 if (rc != pcmk_rc_ok) {
                     crm_debug("Couldn't get file descriptor for IPC: %s",
                               pcmk_rc_str(rc));
                 }
             }
             if (rc != pcmk_rc_ok) {
                 crm_ipc_close(native->ipc);
                 crm_ipc_destroy(native->ipc);
                 native->ipc = NULL;
             }
         }
 
     } else {
         /* With mainloop */
         native->source =
             mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks);
         native->ipc = mainloop_get_ipc_client(native->source);
     }
 
     if (native->ipc == NULL) {
         rc = -ENOTCONN;
     } else {
         xmlNode *reply = NULL;
         xmlNode *hello = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
 
         crm_xml_add(hello, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
         crm_xml_add(hello, PCMK__XA_ST_OP, CRM_OP_REGISTER);
         crm_xml_add(hello, PCMK__XA_ST_CLIENTNAME, name);
         rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply);
 
         if (rc < 0) {
             crm_debug("Couldn't register with the fencer: %s "
                       QB_XS " rc=%d", pcmk_strerror(rc), rc);
             rc = -ECOMM;
 
         } else if (reply == NULL) {
             crm_debug("Couldn't register with the fencer: no reply");
             rc = -EPROTO;
 
         } else {
             const char *msg_type = crm_element_value(reply, PCMK__XA_ST_OP);
 
             native->token = crm_element_value_copy(reply, PCMK__XA_ST_CLIENTID);
             if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_none)) {
                 crm_debug("Couldn't register with the fencer: invalid reply type '%s'",
                           (msg_type? msg_type : "(missing)"));
                 crm_log_xml_debug(reply, "Invalid fencer reply");
                 rc = -EPROTO;
 
             } else if (native->token == NULL) {
                 crm_debug("Couldn't register with the fencer: no token in reply");
                 crm_log_xml_debug(reply, "Invalid fencer reply");
                 rc = -EPROTO;
 
             } else {
                 crm_debug("Connection to fencer by %s succeeded (registration token: %s)",
                           display_name, native->token);
                 rc = pcmk_ok;
             }
         }
 
         pcmk__xml_free(reply);
         pcmk__xml_free(hello);
     }
 
     if (rc != pcmk_ok) {
         crm_debug("Connection attempt to fencer by %s failed: %s "
                   QB_XS " rc=%d", display_name, pcmk_strerror(rc), rc);
         stonith->cmds->disconnect(stonith);
     }
     return rc;
 }
 
 static int
 stonith_set_notification(stonith_t * stonith, const char *callback, int enabled)
 {
     int rc = pcmk_ok;
     xmlNode *notify_msg = pcmk__xe_create(NULL, __func__);
     stonith_private_t *native = stonith->st_private;
 
     if (stonith->state != stonith_disconnected) {
 
         crm_xml_add(notify_msg, PCMK__XA_ST_OP, STONITH_OP_NOTIFY);
         if (enabled) {
             crm_xml_add(notify_msg, PCMK__XA_ST_NOTIFY_ACTIVATE, callback);
         } else {
             crm_xml_add(notify_msg, PCMK__XA_ST_NOTIFY_DEACTIVATE, callback);
         }
 
         rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL);
         if (rc < 0) {
             crm_perror(LOG_DEBUG, "Couldn't register for fencing notifications: %d", rc);
             rc = -ECOMM;
         } else {
             rc = pcmk_ok;
         }
     }
 
     pcmk__xml_free(notify_msg);
     return rc;
 }
 
 static int
 stonith_api_add_notification(stonith_t * stonith, const char *event,
                              void (*callback) (stonith_t * stonith, stonith_event_t * e))
 {
     GList *list_item = NULL;
     stonith_notify_client_t *new_client = NULL;
     stonith_private_t *private = NULL;
 
     private = stonith->st_private;
     crm_trace("Adding callback for %s events (%d)", event, g_list_length(private->notify_list));
 
     new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
     new_client->event = event;
     new_client->notify = callback;
 
     list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
 
     if (list_item != NULL) {
         crm_warn("Callback already present");
         free(new_client);
         return -ENOTUNIQ;
 
     } else {
         private->notify_list = g_list_append(private->notify_list, new_client);
 
         stonith_set_notification(stonith, event, 1);
 
         crm_trace("Callback added (%d)", g_list_length(private->notify_list));
     }
     return pcmk_ok;
 }
 
 static void
 del_notify_entry(gpointer data, gpointer user_data)
 {
     stonith_notify_client_t *entry = data;
     stonith_t * stonith = user_data;
 
     if (!entry->delete) {
         crm_debug("Removing callback for %s events", entry->event);
         stonith_api_del_notification(stonith, entry->event);
     }
 }
 
 static int
 stonith_api_del_notification(stonith_t * stonith, const char *event)
 {
     GList *list_item = NULL;
     stonith_notify_client_t *new_client = NULL;
     stonith_private_t *private = stonith->st_private;
 
     if (event == NULL) {
         foreach_notify_entry(private, del_notify_entry, stonith);
         crm_trace("Removed callback");
 
         return pcmk_ok;
     }
 
     crm_debug("Removing callback for %s events", event);
 
     new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
     new_client->event = event;
     new_client->notify = NULL;
 
     list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
 
     stonith_set_notification(stonith, event, 0);
 
     if (list_item != NULL) {
         stonith_notify_client_t *list_client = list_item->data;
 
         if (private->notify_refcnt) {
             list_client->delete = TRUE;
             private->notify_deletes = TRUE;
         } else {
             private->notify_list = g_list_remove(private->notify_list, list_client);
             free(list_client);
         }
 
         crm_trace("Removed callback");
 
     } else {
         crm_trace("Callback not present");
     }
     free(new_client);
     return pcmk_ok;
 }
 
 static int
 stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options,
                          void *user_data, const char *callback_name,
                          void (*callback) (stonith_t * st, stonith_callback_data_t * data))
 {
     stonith_callback_client_t *blob = NULL;
     stonith_private_t *private = NULL;
 
     CRM_CHECK(stonith != NULL, return -EINVAL);
     CRM_CHECK(stonith->st_private != NULL, return -EINVAL);
     private = stonith->st_private;
 
     if (call_id == 0) { // Add global callback
         private->op_callback = callback;
 
     } else if (call_id < 0) { // Call failed immediately, so call callback now
         if (!(options & st_opt_report_only_success)) {
             pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
             crm_trace("Call failed, calling %s: %s", callback_name, pcmk_strerror(call_id));
             pcmk__set_result(&result, CRM_EX_ERROR,
                              stonith__legacy2status(call_id), NULL);
             invoke_fence_action_callback(stonith, call_id, &result,
                                          user_data, callback);
         } else {
             crm_warn("Fencer call failed: %s", pcmk_strerror(call_id));
         }
         return FALSE;
     }
 
     blob = pcmk__assert_alloc(1, sizeof(stonith_callback_client_t));
     blob->id = callback_name;
     blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE;
     blob->user_data = user_data;
     blob->callback = callback;
     blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE;
 
     if (timeout > 0) {
         set_callback_timeout(blob, stonith, call_id, timeout);
     }
 
     pcmk__intkey_table_insert(private->stonith_op_callback_table, call_id,
                               blob);
     crm_trace("Added callback to %s for call %d", callback_name, call_id);
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Get the data section of a fencer notification
  *
  * \param[in] msg    Notification XML
  * \param[in] ntype  Notification type
  */
 static xmlNode *
 get_event_data_xml(xmlNode *msg, const char *ntype)
 {
     char *data_addr = crm_strdup_printf("//%s", ntype);
     xmlNode *data = pcmk__xpath_find_one(msg->doc, data_addr, LOG_DEBUG);
 
     free(data_addr);
     return data;
 }
 
 /*
  <notify t="st_notify" subt="st_device_register" st_op="st_device_register" st_rc="0" >
    <st_calldata >
      <stonith_command t="stonith-ng" st_async_id="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_op="st_device_register" st_callid="2" st_callopt="4096" st_timeout="0" st_clientid="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_clientname="cts-fence-helper" >
        <st_calldata >
          <st_device_id id="test-id" origin="create_device_registration_xml" agent="fence_virsh" namespace="stonith-ng" >
            <attributes ipaddr="localhost" pcmk-portmal="some-host=pcmk-1 pcmk-3=3,4" login="root" identity_file="/root/.ssh/id_dsa" />
          </st_device_id>
        </st_calldata>
      </stonith_command>
    </st_calldata>
  </notify>
 
  <notify t="st_notify" subt="st_notify_fence" st_op="st_notify_fence" st_rc="0" >
    <st_calldata >
      <st_notify_fence st_rc="0" st_target="some-host" st_op="st_fence" st_delegate="test-id" st_origin="61dd7759-e229-4be7-b1f8-ef49dd14d9f0" />
    </st_calldata>
  </notify>
 */
 static stonith_event_t *
 xml_to_event(xmlNode *msg)
 {
     stonith_event_t *event = pcmk__assert_alloc(1, sizeof(stonith_event_t));
     struct event_private *event_private = NULL;
 
     event->opaque = pcmk__assert_alloc(1, sizeof(struct event_private));
     event_private = (struct event_private *) event->opaque;
 
     crm_log_xml_trace(msg, "stonith_notify");
 
     // All notification types have the operation result and notification subtype
     stonith__xe_get_result(msg, &event_private->result);
     event->operation = crm_element_value_copy(msg, PCMK__XA_ST_OP);
 
     // @COMPAT The API originally provided the result as a legacy return code
     event->result = pcmk_rc2legacy(stonith__result2rc(&event_private->result));
 
     // Some notification subtypes have additional information
 
     if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_FENCE,
                      pcmk__str_none)) {
         xmlNode *data = get_event_data_xml(msg, event->operation);
 
         if (data == NULL) {
             crm_err("No data for %s event", event->operation);
             crm_log_xml_notice(msg, "BadEvent");
         } else {
             event->origin = crm_element_value_copy(data, PCMK__XA_ST_ORIGIN);
             event->action = crm_element_value_copy(data,
                                                    PCMK__XA_ST_DEVICE_ACTION);
             event->target = crm_element_value_copy(data, PCMK__XA_ST_TARGET);
             event->executioner = crm_element_value_copy(data,
                                                         PCMK__XA_ST_DELEGATE);
             event->id = crm_element_value_copy(data, PCMK__XA_ST_REMOTE_OP);
             event->client_origin =
                 crm_element_value_copy(data, PCMK__XA_ST_CLIENTNAME);
             event->device = crm_element_value_copy(data, PCMK__XA_ST_DEVICE_ID);
         }
 
     } else if (pcmk__str_any_of(event->operation,
                                 STONITH_OP_DEVICE_ADD, STONITH_OP_DEVICE_DEL,
                                 STONITH_OP_LEVEL_ADD, STONITH_OP_LEVEL_DEL,
                                 NULL)) {
         xmlNode *data = get_event_data_xml(msg, event->operation);
 
         if (data == NULL) {
             crm_err("No data for %s event", event->operation);
             crm_log_xml_notice(msg, "BadEvent");
         } else {
             event->device = crm_element_value_copy(data, PCMK__XA_ST_DEVICE_ID);
         }
     }
 
     return event;
 }
 
 static void
 event_free(stonith_event_t * event)
 {
     struct event_private *event_private = event->opaque;
 
     free(event->id);
     free(event->operation);
     free(event->origin);
     free(event->action);
     free(event->target);
     free(event->executioner);
     free(event->device);
     free(event->client_origin);
     pcmk__reset_result(&event_private->result);
     free(event->opaque);
     free(event);
 }
 
 static void
 stonith_send_notification(gpointer data, gpointer user_data)
 {
     struct notify_blob_s *blob = user_data;
     stonith_notify_client_t *entry = data;
     stonith_event_t *st_event = NULL;
     const char *event = NULL;
 
     if (blob->xml == NULL) {
         crm_warn("Skipping callback - NULL message");
         return;
     }
 
     event = crm_element_value(blob->xml, PCMK__XA_SUBT);
 
     if (entry == NULL) {
         crm_warn("Skipping callback - NULL callback client");
         return;
 
     } else if (entry->delete) {
         crm_trace("Skipping callback - marked for deletion");
         return;
 
     } else if (entry->notify == NULL) {
         crm_warn("Skipping callback - NULL callback");
         return;
 
     } else if (!pcmk__str_eq(entry->event, event, pcmk__str_none)) {
         crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
         return;
     }
 
     st_event = xml_to_event(blob->xml);
 
     crm_trace("Invoking callback for %p/%s event...", entry, event);
     entry->notify(blob->stonith, st_event);
     crm_trace("Callback invoked...");
 
     event_free(st_event);
 }
 
 /*!
  * \internal
  * \brief Create and send an API request
  *
  * \param[in,out] stonith       Stonith connection
  * \param[in]     op            API operation to request
  * \param[in]     data          Data to attach to request
  * \param[out]    output_data   If not NULL, will be set to reply if synchronous
  * \param[in]     call_options  Bitmask of stonith_call_options to use
  * \param[in]     timeout       Error if not completed within this many seconds
  *
  * \return pcmk_ok (for synchronous requests) or positive call ID
  *         (for asynchronous requests) on success, -errno otherwise
  */
 static int
 stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data,
                      int call_options, int timeout)
 {
     int rc = 0;
     int reply_id = -1;
 
     xmlNode *op_msg = NULL;
     xmlNode *op_reply = NULL;
     stonith_private_t *native = NULL;
 
     pcmk__assert((stonith != NULL) && (stonith->st_private != NULL)
                  && (op != NULL));
     native = stonith->st_private;
 
     if (output_data != NULL) {
         *output_data = NULL;
     }
 
     if ((stonith->state == stonith_disconnected) || (native->token == NULL)) {
         return -ENOTCONN;
     }
 
     /* Increment the call ID, which must be positive to avoid conflicting with
      * error codes. This shouldn't be a problem unless the client mucked with
      * it or the counter wrapped around.
      */
     stonith->call_id++;
     if (stonith->call_id < 1) {
         stonith->call_id = 1;
     }
 
     op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options);
     if (op_msg == NULL) {
         return -EINVAL;
     }
 
     crm_xml_add_int(op_msg, PCMK__XA_ST_TIMEOUT, timeout);
     crm_trace("Sending %s message to fencer with timeout %ds", op, timeout);
 
     if (data) {
         const char *delay_s = crm_element_value(data, PCMK__XA_ST_DELAY);
 
         if (delay_s) {
             crm_xml_add(op_msg, PCMK__XA_ST_DELAY, delay_s);
         }
     }
 
     {
         enum crm_ipc_flags ipc_flags = crm_ipc_flags_none;
 
         if (call_options & st_opt_sync_call) {
             pcmk__set_ipc_flags(ipc_flags, "stonith command",
                                 crm_ipc_client_response);
         }
         rc = crm_ipc_send(native->ipc, op_msg, ipc_flags,
                           1000 * (timeout + 60), &op_reply);
     }
     pcmk__xml_free(op_msg);
 
     if (rc < 0) {
         crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%ds): %d", op, timeout, rc);
         rc = -ECOMM;
         goto done;
     }
 
     crm_log_xml_trace(op_reply, "Reply");
 
     if (!(call_options & st_opt_sync_call)) {
         crm_trace("Async call %d, returning", stonith->call_id);
         pcmk__xml_free(op_reply);
         return stonith->call_id;
     }
 
     crm_element_value_int(op_reply, PCMK__XA_ST_CALLID, &reply_id);
 
     if (reply_id == stonith->call_id) {
         pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
         crm_trace("Synchronous reply %d received", reply_id);
 
         stonith__xe_get_result(op_reply, &result);
         rc = pcmk_rc2legacy(stonith__result2rc(&result));
         pcmk__reset_result(&result);
 
         if ((call_options & st_opt_discard_reply) || output_data == NULL) {
             crm_trace("Discarding reply");
 
         } else {
             *output_data = op_reply;
             op_reply = NULL;    /* Prevent subsequent free */
         }
 
     } else if (reply_id <= 0) {
         crm_err("Received bad reply: No id set");
         crm_log_xml_err(op_reply, "Bad reply");
         pcmk__xml_free(op_reply);
         op_reply = NULL;
         rc = -ENOMSG;
 
     } else {
         crm_err("Received bad reply: %d (wanted %d)", reply_id, stonith->call_id);
         crm_log_xml_err(op_reply, "Old reply");
         pcmk__xml_free(op_reply);
         op_reply = NULL;
         rc = -ENOMSG;
     }
 
   done:
     if (!crm_ipc_connected(native->ipc)) {
         crm_err("Fencer disconnected");
         free(native->token); native->token = NULL;
         stonith->state = stonith_disconnected;
     }
 
     pcmk__xml_free(op_reply);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Process IPC messages for a fencer API connection
  *
  * This is used for testing purposes in scenarios that don't use a mainloop to
  * dispatch messages automatically.
  *
  * \param[in,out] stonith_api  Fencer API connetion object
  *
  * \return Standard Pacemaker return code
  */
 int
 stonith__api_dispatch(stonith_t *stonith_api)
 {
     stonith_private_t *private = NULL;
 
     pcmk__assert(stonith_api != NULL);
     private = stonith_api->st_private;
 
     while (crm_ipc_ready(private->ipc)) {
         if (crm_ipc_read(private->ipc) > 0) {
             const char *msg = crm_ipc_buffer(private->ipc);
 
             stonith_dispatch_internal(msg, strlen(msg), stonith_api);
         }
 
         if (!crm_ipc_connected(private->ipc)) {
             crm_err("Connection closed");
             return ENOTCONN;
         }
     }
 
     return pcmk_rc_ok;
 }
 
 static int
 free_stonith_api(stonith_t *stonith)
 {
     int rc = pcmk_ok;
 
     crm_trace("Destroying %p", stonith);
 
     if (stonith->state != stonith_disconnected) {
         crm_trace("Unregistering notifications and disconnecting %p first",
                   stonith);
         stonith->cmds->remove_notification(stonith, NULL);
         rc = stonith->cmds->disconnect(stonith);
     }
 
     if (stonith->state == stonith_disconnected) {
         stonith_private_t *private = stonith->st_private;
 
         crm_trace("Removing %d callbacks", g_hash_table_size(private->stonith_op_callback_table));
         g_hash_table_destroy(private->stonith_op_callback_table);
 
         crm_trace("Destroying %d notification clients", g_list_length(private->notify_list));
         g_list_free_full(private->notify_list, free);
 
         free(stonith->st_private);
         free(stonith->cmds);
         free(stonith);
 
     } else {
         crm_err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc), rc);
     }
 
     return rc;
 }
 
 static gboolean
 is_stonith_param(gpointer key, gpointer value, gpointer user_data)
 {
     return pcmk_stonith_param(key);
 }
 
 int
 stonith__validate(stonith_t *st, int call_options, const char *rsc_id,
                   const char *namespace_s, const char *agent,
                   GHashTable *params, int timeout_sec, char **output,
                   char **error_output)
 {
     int rc = pcmk_rc_ok;
 
     /* Use a dummy node name in case the agent requires a target. We assume the
      * actual target doesn't matter for validation purposes (if in practice,
      * that is incorrect, we will need to allow the caller to pass the target).
      */
     const char *target = "node1";
     char *host_arg = NULL;
 
     if (params != NULL) {
         host_arg = pcmk__str_copy(g_hash_table_lookup(params, PCMK_STONITH_HOST_ARGUMENT));
 
         /* Remove special stonith params from the table before doing anything else */
         g_hash_table_foreach_remove(params, is_stonith_param, NULL);
     }
 
 #if PCMK__ENABLE_CIBSECRETS
     rc = pcmk__substitute_secrets(rsc_id, params);
     if (rc != pcmk_rc_ok) {
         crm_warn("Could not replace secret parameters for validation of %s: %s",
                  agent, pcmk_rc_str(rc));
         // rc is standard return value, don't return it in this function
     }
 #endif
 
     if (output) {
         *output = NULL;
     }
     if (error_output) {
         *error_output = NULL;
     }
 
     if (timeout_sec <= 0) {
         timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
     }
 
     switch (stonith_get_namespace(agent, namespace_s)) {
         case st_namespace_rhcs:
             rc = stonith__rhcs_validate(st, call_options, target, agent,
                                         params, host_arg, timeout_sec,
                                         output, error_output);
             rc = pcmk_legacy2rc(rc);
             break;
 
 #if HAVE_STONITH_STONITH_H
         case st_namespace_lha:
             rc = stonith__lha_validate(st, call_options, target, agent,
                                        params, timeout_sec, output,
                                        error_output);
             rc = pcmk_legacy2rc(rc);
             break;
 #endif
 
         case st_namespace_invalid:
             errno = ENOENT;
             rc = errno;
 
             if (error_output) {
                 *error_output = crm_strdup_printf("Agent %s not found", agent);
             } else {
                 crm_err("Agent %s not found", agent);
             }
 
             break;
 
         default:
             errno = EOPNOTSUPP;
             rc = errno;
 
             if (error_output) {
                 *error_output = crm_strdup_printf("Agent %s does not support validation",
                                                   agent);
             } else {
                 crm_err("Agent %s does not support validation", agent);
             }
 
             break;
     }
 
     free(host_arg);
     return rc;
 }
 
 static int
 stonith_api_validate(stonith_t *st, int call_options, const char *rsc_id,
                      const char *namespace_s, const char *agent,
                      const stonith_key_value_t *params, int timeout_sec,
                      char **output, char **error_output)
 {
     /* Validation should be done directly via the agent, so we can get it from
      * stonith_admin when the cluster is not running, which is important for
      * higher-level tools.
      */
 
     int rc = pcmk_ok;
 
     GHashTable *params_table = pcmk__strkey_table(free, free);
 
     // Convert parameter list to a hash table
     for (; params; params = params->next) {
         if (!pcmk_stonith_param(params->key)) {
             pcmk__insert_dup(params_table, params->key, params->value);
         }
     }
 
     rc = stonith__validate(st, call_options, rsc_id, namespace_s, agent,
                            params_table, timeout_sec, output, error_output);
 
     g_hash_table_destroy(params_table);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Create a new fencer API connection object
  *
  * \return Newly allocated fencer API connection object, or \c NULL on
  *         allocation failure
  */
 stonith_t *
 stonith__api_new(void)
 {
     stonith_t *new_stonith = NULL;
     stonith_private_t *private = NULL;
 
     new_stonith = calloc(1, sizeof(stonith_t));
     if (new_stonith == NULL) {
         return NULL;
     }
 
     private = calloc(1, sizeof(stonith_private_t));
     if (private == NULL) {
         free(new_stonith);
         return NULL;
     }
     new_stonith->st_private = private;
 
     private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
     private->notify_list = NULL;
     private->notify_refcnt = 0;
     private->notify_deletes = FALSE;
 
     new_stonith->call_id = 1;
     new_stonith->state = stonith_disconnected;
 
     new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t));
     if (new_stonith->cmds == NULL) {
         free(new_stonith->st_private);
         free(new_stonith);
         return NULL;
     }
 
     new_stonith->cmds->free       = free_stonith_api;
     new_stonith->cmds->connect    = stonith_api_signon;
     new_stonith->cmds->disconnect = stonith_api_signoff;
 
     new_stonith->cmds->list       = stonith_api_list;
     new_stonith->cmds->monitor    = stonith_api_monitor;
     new_stonith->cmds->status     = stonith_api_status;
     new_stonith->cmds->fence      = stonith_api_fence;
     new_stonith->cmds->fence_with_delay = stonith_api_fence_with_delay;
     new_stonith->cmds->confirm    = stonith_api_confirm;
     new_stonith->cmds->history    = stonith_api_history;
 
     new_stonith->cmds->list_agents  = stonith_api_device_list;
     new_stonith->cmds->metadata     = stonith_api_device_metadata;
 
     new_stonith->cmds->query           = stonith_api_query;
     new_stonith->cmds->remove_device   = stonith_api_remove_device;
     new_stonith->cmds->register_device = stonith_api_register_device;
 
     new_stonith->cmds->remove_level          = stonith_api_remove_level;
     new_stonith->cmds->remove_level_full     = stonith_api_remove_level_full;
     new_stonith->cmds->register_level        = stonith_api_register_level;
     new_stonith->cmds->register_level_full   = stonith_api_register_level_full;
 
     new_stonith->cmds->remove_callback       = stonith_api_del_callback;
     new_stonith->cmds->register_callback     = stonith_api_add_callback;
     new_stonith->cmds->remove_notification   = stonith_api_del_notification;
     new_stonith->cmds->register_notification = stonith_api_add_notification;
 
     new_stonith->cmds->validate              = stonith_api_validate;
 
     return new_stonith;
 }
 
 /*!
  * \internal
  * \brief Free a fencer API connection object
  *
  * \param[in,out] stonith_api  Fencer API connection object
  */
 void
 stonith__api_free(stonith_t *stonith_api)
 {
     crm_trace("Destroying %p", stonith_api);
     if (stonith_api != NULL) {
         stonith_api->cmds->free(stonith_api);
     }
 }
 
 /*!
  * \brief Make a blocking connection attempt to the fencer
  *
  * \param[in,out] st            Fencer API object
  * \param[in]     name          Client name to use with fencer
  * \param[in]     max_attempts  Return error if this many attempts fail
  *
  * \return pcmk_ok on success, result of last attempt otherwise
  */
 int
 stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts)
 {
     int rc = -EINVAL; // if max_attempts is not positive
 
     for (int attempt = 1; attempt <= max_attempts; attempt++) {
         rc = st->cmds->connect(st, name, NULL);
         if (rc == pcmk_ok) {
             return pcmk_ok;
         } else if (attempt < max_attempts) {
             crm_notice("Fencer connection attempt %d of %d failed (retrying in 2s): %s "
                        QB_XS " rc=%d",
                        attempt, max_attempts, pcmk_strerror(rc), rc);
             sleep(2);
         }
     }
     crm_notice("Could not connect to fencer: %s " QB_XS " rc=%d",
                pcmk_strerror(rc), rc);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Append a newly allocated STONITH key-value pair to a list
  *
  * \param[in,out] head   Head of key-value pair list (\c NULL for new list)
  * \param[in]     key    Key to add
  * \param[in]     value  Value to add
  *
  * \return Head of appended-to list (equal to \p head if \p head is not \c NULL)
  * \note The caller is responsible for freeing the return value using
- *       \c stonith_key_value_freeall().
+ *       \c stonith__key_value_freeall().
  */
 stonith_key_value_t *
 stonith__key_value_add(stonith_key_value_t *head, const char *key,
                        const char *value)
 {
     /* @COMPAT Replace this function with pcmk_prepend_nvpair(), and reverse the
      * list when finished adding to it; or with a hash table where order does
      * not matter
      */
     stonith_key_value_t *pair = pcmk__assert_alloc(1,
                                                    sizeof(stonith_key_value_t));
 
     pair->key = pcmk__str_copy(key);
     pair->value = pcmk__str_copy(value);
 
     if (head != NULL) {
         stonith_key_value_t *end = head;
 
         for (; end->next != NULL; end = end->next);
         end->next = pair;
 
     } else {
         head = pair;
     }
 
     return head;
 }
 
+/*!
+ * \internal
+ * \brief Free all items in a \c stonith_key_value_t list
+ *
+ * This means freeing the list itself with all of its nodes. Keys and values may
+ * be freed depending on arguments.
+ *
+ * \param[in,out] head    Head of list
+ * \param[in]     keys    If \c true, free all keys
+ * \param[in]     values  If \c true, free all values
+ */
 void
-stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values)
+stonith__key_value_freeall(stonith_key_value_t *head, bool keys, bool values)
 {
-    stonith_key_value_t *p;
+    while (head != NULL) {
+        stonith_key_value_t *next = head->next;
 
-    while (head) {
-        p = head->next;
         if (keys) {
             free(head->key);
         }
         if (values) {
             free(head->value);
         }
         free(head);
-        head = p;
+        head = next;
     }
 }
 
+void
+stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values)
+{
+    stonith__key_value_freeall(head, (keys != 0), (values != 0));
+}
+
 #define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON)
 #define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __func__, args)
 
 int
 stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off)
 {
     int rc = pcmk_ok;
     stonith_t *st = stonith__api_new();
     const char *action = off? PCMK_ACTION_OFF : PCMK_ACTION_REBOOT;
 
     api_log_open();
     if (st == NULL) {
         api_log(LOG_ERR, "API initialization failed, could not kick (%s) node %u/%s",
                 action, nodeid, uname);
         return -EPROTO;
     }
 
     rc = st->cmds->connect(st, "stonith-api", NULL);
     if (rc != pcmk_ok) {
         api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)",
                 action, nodeid, uname, pcmk_strerror(rc), rc);
     } else {
         char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
         int opts = 0;
 
         stonith__set_call_options(opts, name,
                                   st_opt_sync_call|st_opt_allow_self_fencing);
         if ((uname == NULL) && (nodeid > 0)) {
             stonith__set_call_options(opts, name, st_opt_cs_nodeid);
         }
         rc = st->cmds->fence(st, opts, name, action, timeout, 0);
         free(name);
 
         if (rc != pcmk_ok) {
             api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)",
                     action, nodeid, uname, pcmk_strerror(rc), rc);
         } else {
             api_log(LOG_NOTICE, "Node %u/%s kicked: %s", nodeid, uname, action);
         }
     }
 
     stonith__api_free(st);
     return rc;
 }
 
 time_t
 stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress)
 {
     int rc = pcmk_ok;
     time_t when = 0;
     stonith_t *st = stonith__api_new();
     stonith_history_t *history = NULL, *hp = NULL;
 
     if (st == NULL) {
         api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: "
                 "API initialization failed", nodeid, uname);
         return when;
     }
 
     rc = st->cmds->connect(st, "stonith-api", NULL);
     if (rc != pcmk_ok) {
         api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc);
     } else {
         int entries = 0;
         int progress = 0;
         int completed = 0;
         int opts = 0;
         char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
 
         stonith__set_call_options(opts, name, st_opt_sync_call);
         if ((uname == NULL) && (nodeid > 0)) {
             stonith__set_call_options(opts, name, st_opt_cs_nodeid);
         }
         rc = st->cmds->history(st, opts, name, &history, 120);
         free(name);
 
         for (hp = history; hp; hp = hp->next) {
             entries++;
             if (in_progress) {
                 progress++;
                 if (hp->state != st_done && hp->state != st_failed) {
                     when = time(NULL);
                 }
 
             } else if (hp->state == st_done) {
                 completed++;
                 if (hp->completed > when) {
                     when = hp->completed;
                 }
             }
         }
 
         stonith_history_free(history);
 
         if(rc == pcmk_ok) {
             api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed);
         } else {
             api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc);
         }
     }
 
     stonith__api_free(st);
 
     if(when) {
         api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when);
     }
     return when;
 }
 
 bool
 stonith_agent_exists(const char *agent, int timeout)
 {
     stonith_t *st = NULL;
     stonith_key_value_t *devices = NULL;
     stonith_key_value_t *dIter = NULL;
     bool rc = FALSE;
 
     if (agent == NULL) {
         return rc;
     }
 
     st = stonith__api_new();
     if (st == NULL) {
         crm_err("Could not list fence agents: API memory allocation failed");
         return FALSE;
     }
     st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout == 0 ? 120 : timeout);
 
     for (dIter = devices; dIter != NULL; dIter = dIter->next) {
         if (pcmk__str_eq(dIter->value, agent, pcmk__str_none)) {
             rc = TRUE;
             break;
         }
     }
 
-    stonith_key_value_freeall(devices, 1, 1);
+    stonith__key_value_freeall(devices, true, true);
     stonith__api_free(st);
     return rc;
 }
 
 const char *
 stonith_action_str(const char *action)
 {
     if (action == NULL) {
         return "fencing";
     } else if (strcmp(action, PCMK_ACTION_ON) == 0) {
         return "unfencing";
     } else if (strcmp(action, PCMK_ACTION_OFF) == 0) {
         return "turning off";
     } else {
         return action;
     }
 }
 
 /*!
  * \internal
  * \brief Parse a target name from one line of a target list string
  *
  * \param[in]     line    One line of a target list string
  * \param[in]     len     String length of line
  * \param[in,out] output  List to add newly allocated target name to
  */
 static void
 parse_list_line(const char *line, int len, GList **output)
 {
     size_t i = 0;
     size_t entry_start = 0;
 
     /* Skip complaints about additional parameters device doesn't understand
      *
      * @TODO Document or eliminate the implied restriction of target names
      */
     if (strstr(line, "invalid") || strstr(line, "variable")) {
         crm_debug("Skipping list output line: %s", line);
         return;
     }
 
     // Process line content, character by character
     for (i = 0; i <= len; i++) {
 
         if (isspace(line[i]) || (line[i] == ',') || (line[i] == ';')
             || (line[i] == '\0')) {
             // We've found a separator (i.e. the end of an entry)
 
             int rc = 0;
             char *entry = NULL;
 
             if (i == entry_start) {
                 // Skip leading and sequential separators
                 entry_start = i + 1;
                 continue;
             }
 
             entry = pcmk__assert_alloc(i - entry_start + 1, sizeof(char));
 
             /* Read entry, stopping at first separator
              *
              * @TODO Document or eliminate these character restrictions
              */
             rc = sscanf(line + entry_start, "%[a-zA-Z0-9_-.]", entry);
             if (rc != 1) {
                 crm_warn("Could not parse list output entry: %s "
                          QB_XS " entry_start=%d position=%d",
                          line + entry_start, entry_start, i);
                 free(entry);
 
             } else if (pcmk__strcase_any_of(entry, PCMK_ACTION_ON,
                                             PCMK_ACTION_OFF, NULL)) {
                 /* Some agents print the target status in the list output,
                  * though none are known now (the separate list-status command
                  * is used for this, but it can also print "UNKNOWN"). To handle
                  * this possibility, skip such entries.
                  *
                  * @TODO Document or eliminate the implied restriction of target
                  * names.
                  */
                 free(entry);
 
             } else {
                 // We have a valid entry
                 *output = g_list_append(*output, entry);
             }
             entry_start = i + 1;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Parse a list of targets from a string
  *
  * \param[in] list_output  Target list as a string
  *
  * \return List of target names
  * \note The target list string format is flexible, to allow for user-specified
  *       lists such pcmk_host_list and the output of an agent's list action
  *       (whether direct or via the API, which escapes newlines). There may be
  *       multiple lines, separated by either a newline or an escaped newline
  *       (backslash n). Each line may have one or more target names, separated
  *       by any combination of whitespace, commas, and semi-colons. Lines
  *       containing "invalid" or "variable" will be ignored entirely. Target
  *       names "on" or "off" (case-insensitive) will be ignored. Target names
  *       may contain only alphanumeric characters, underbars (_), dashes (-),
  *       and dots (.) (if any other character occurs in the name, it and all
  *       subsequent characters in the name will be ignored).
  * \note The caller is responsible for freeing the result with
  *       g_list_free_full(result, free).
  */
 GList *
 stonith__parse_targets(const char *target_spec)
 {
     GList *targets = NULL;
 
     if (target_spec != NULL) {
         size_t out_len = strlen(target_spec);
         size_t line_start = 0; // Starting index of line being processed
 
         for (size_t i = 0; i <= out_len; ++i) {
             if ((target_spec[i] == '\n') || (target_spec[i] == '\0')
                 || ((target_spec[i] == '\\') && (target_spec[i + 1] == 'n'))) {
                 // We've reached the end of one line of output
 
                 int len = i - line_start;
 
                 if (len > 0) {
                     char *line = strndup(target_spec + line_start, len);
 
                     line[len] = '\0'; // Because it might be a newline
                     parse_list_line(line, len, &targets);
                     free(line);
                 }
                 if (target_spec[i] == '\\') {
                     ++i; // backslash-n takes up two positions
                 }
                 line_start = i + 1;
             }
         }
     }
     return targets;
 }
 
 /*!
  * \internal
  * \brief Check whether a fencing failure was followed by an equivalent success
  *
  * \param[in] event        Fencing failure
  * \param[in] top_history  Complete fencing history (must be sorted by
  *                         stonith__sort_history() beforehand)
  *
  * \return The name of the node that executed the fencing if a later successful
  *         event exists, or NULL if no such event exists
  */
 const char *
 stonith__later_succeeded(const stonith_history_t *event,
                          const stonith_history_t *top_history)
 {
     const char *other = NULL;
 
      for (const stonith_history_t *prev_hp = top_history;
           prev_hp != NULL; prev_hp = prev_hp->next) {
         if (prev_hp == event) {
             break;
         }
         if ((prev_hp->state == st_done) &&
             pcmk__str_eq(event->target, prev_hp->target, pcmk__str_casei) &&
             pcmk__str_eq(event->action, prev_hp->action, pcmk__str_none) &&
             ((event->completed < prev_hp->completed) ||
              ((event->completed == prev_hp->completed) && (event->completed_nsec < prev_hp->completed_nsec)))) {
 
             if ((event->delegate == NULL)
                 || pcmk__str_eq(event->delegate, prev_hp->delegate,
                                 pcmk__str_casei)) {
                 // Prefer equivalent fencing by same executioner
                 return prev_hp->delegate;
 
             } else if (other == NULL) {
                 // Otherwise remember first successful executioner
                 other = (prev_hp->delegate == NULL)? "some node" : prev_hp->delegate;
             }
         }
     }
     return other;
 }
 
 /*!
  * \internal
  * \brief Sort fencing history, pending first then by most recently completed
  *
  * \param[in,out] history    List of stonith actions
  *
  * \return New head of sorted \p history
  */
 stonith_history_t *
 stonith__sort_history(stonith_history_t *history)
 {
     stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
 
     for (hp = history; hp; ) {
         tmp = hp->next;
         if ((hp->state == st_done) || (hp->state == st_failed)) {
             /* sort into new */
             if ((!new) || (hp->completed > new->completed) || 
                 ((hp->completed == new->completed) && (hp->completed_nsec > new->completed_nsec))) {
                 hp->next = new;
                 new = hp;
             } else {
                 np = new;
                 do {
                     if ((!np->next) || (hp->completed > np->next->completed) ||
                         ((hp->completed == np->next->completed) && (hp->completed_nsec > np->next->completed_nsec))) {
                         hp->next = np->next;
                         np->next = hp;
                         break;
                     }
                     np = np->next;
                 } while (1);
             }
         } else {
             /* put into pending */
             hp->next = pending;
             pending = hp;
         }
         hp = tmp;
     }
 
     /* pending actions don't have a completed-stamp so make them go front */
     if (pending) {
         stonith_history_t *last_pending = pending;
 
         while (last_pending->next) {
             last_pending = last_pending->next;
         }
 
         last_pending->next = new;
         new = pending;
     }
     return new;
 }
 
 /*!
  * \brief Return string equivalent of an operation state value
  *
  * \param[in] state  Fencing operation state value
  *
  * \return Human-friendly string equivalent of state
  */
 const char *
 stonith_op_state_str(enum op_state state)
 {
     switch (state) {
         case st_query:      return "querying";
         case st_exec:       return "executing";
         case st_done:       return "completed";
         case st_duplicate:  return "duplicate";
         case st_failed:     return "failed";
     }
     return "unknown";
 }
 
 stonith_history_t *
 stonith__first_matching_event(stonith_history_t *history,
                               bool (*matching_fn)(stonith_history_t *, void *),
                               void *user_data)
 {
     for (stonith_history_t *hp = history; hp; hp = hp->next) {
         if (matching_fn(hp, user_data)) {
             return hp;
         }
     }
 
     return NULL;
 }
 
 bool
 stonith__event_state_pending(stonith_history_t *history, void *user_data)
 {
     return history->state != st_failed && history->state != st_done;
 }
 
 bool
 stonith__event_state_eq(stonith_history_t *history, void *user_data)
 {
     return history->state == GPOINTER_TO_INT(user_data);
 }
 
 bool
 stonith__event_state_neq(stonith_history_t *history, void *user_data)
 {
     return history->state != GPOINTER_TO_INT(user_data);
 }
 
 /*!
  * \internal
  * \brief Check whether a given parameter exists in a fence agent's metadata
  *
  * \param[in] metadata  Agent metadata
  * \param[in] name      Parameter name
  *
  * \retval \c true   If \p name exists as a parameter in \p metadata
  * \retval \c false  Otherwise
  */
 static bool
 param_is_supported(xmlNode *metadata, const char *name)
 {
     char *xpath_s = crm_strdup_printf("//" PCMK_XE_PARAMETER
                                       "[@" PCMK_XA_NAME "='%s']",
                                       name);
     xmlXPathObject *xpath = pcmk__xpath_search(metadata->doc, xpath_s);
     bool supported = (pcmk__xpath_num_results(xpath) > 0);
 
     free(xpath_s);
     xmlXPathFreeObject(xpath);
     return supported;
 }
 
 /*!
  * \internal
  * \brief Get the default host argument based on a device's agent metadata
  *
  * If an agent supports the "plug" parameter, default to that. Otherwise default
  * to the "port" parameter if supported. Otherwise return \c NULL.
  *
  * \param[in] metadata  Agent metadata
  *
  * \return Parameter name for default host argument
  */
 const char *
 stonith__default_host_arg(xmlNode *metadata)
 {
     CRM_CHECK(metadata != NULL, return NULL);
 
     if (param_is_supported(metadata, "plug")) {
         return "plug";
     }
     if (param_is_supported(metadata, "port")) {
         return "port";
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Retrieve fence agent meta-data asynchronously
  *
  * \param[in]     agent        Agent to execute
  * \param[in]     timeout_sec  Error if not complete within this time
  * \param[in]     callback     Function to call with result (this will always be
  *                             called, whether by this function directly or
  *                             later via the main loop, and on success the
  *                             metadata will be in its result argument's
  *                             action_stdout)
  * \param[in,out] user_data    User data to pass to callback
  *
  * \return Standard Pacemaker return code
  * \note The caller must use a main loop. This function is not a
  *       stonith_api_operations_t method because it does not need a stonith_t
  *       object and does not go through the fencer, but executes the agent
  *       directly.
  */
 int
 stonith__metadata_async(const char *agent, int timeout_sec,
                         void (*callback)(int pid,
                                          const pcmk__action_result_t *result,
                                          void *user_data),
                         void *user_data)
 {
     switch (stonith_get_namespace(agent, NULL)) {
         case st_namespace_rhcs:
             {
                 stonith_action_t *action = NULL;
                 int rc = pcmk_ok;
 
                 action = stonith__action_create(agent, PCMK_ACTION_METADATA,
                                                 NULL, timeout_sec, NULL, NULL,
                                                 NULL);
 
                 rc = stonith__execute_async(action, user_data, callback, NULL);
                 if (rc != pcmk_ok) {
                     callback(0, stonith__action_result(action), user_data);
                     stonith__destroy_action(action);
                 }
                 return pcmk_legacy2rc(rc);
             }
 
 #if HAVE_STONITH_STONITH_H
         case st_namespace_lha:
             // LHA metadata is simply synthesized, so simulate async
             {
                 pcmk__action_result_t result = {
                     .exit_status = CRM_EX_OK,
                     .execution_status = PCMK_EXEC_DONE,
                     .exit_reason = NULL,
                     .action_stdout = NULL,
                     .action_stderr = NULL,
                 };
 
                 stonith__lha_metadata(agent, timeout_sec,
                                       &result.action_stdout);
                 callback(0, &result, user_data);
                 pcmk__reset_result(&result);
                 return pcmk_rc_ok;
             }
 #endif
 
         default:
             {
                 pcmk__action_result_t result = {
                     .exit_status = CRM_EX_NOSUCH,
                     .execution_status = PCMK_EXEC_ERROR_HARD,
                     .exit_reason = crm_strdup_printf("No such agent '%s'",
                                                      agent),
                     .action_stdout = NULL,
                     .action_stderr = NULL,
                 };
 
                 callback(0, &result, user_data);
                 pcmk__reset_result(&result);
                 return ENOENT;
             }
     }
 }
 
 /*!
  * \internal
  * \brief Return the exit status from an async action callback
  *
  * \param[in] data  Callback data
  *
  * \return Exit status from callback data
  */
 int
 stonith__exit_status(const stonith_callback_data_t *data)
 {
     if ((data == NULL) || (data->opaque == NULL)) {
         return CRM_EX_ERROR;
     }
     return ((pcmk__action_result_t *) data->opaque)->exit_status;
 }
 
 /*!
  * \internal
  * \brief Return the execution status from an async action callback
  *
  * \param[in] data  Callback data
  *
  * \return Execution status from callback data
  */
 int
 stonith__execution_status(const stonith_callback_data_t *data)
 {
     if ((data == NULL) || (data->opaque == NULL)) {
         return PCMK_EXEC_UNKNOWN;
     }
     return ((pcmk__action_result_t *) data->opaque)->execution_status;
 }
 
 /*!
  * \internal
  * \brief Return the exit reason from an async action callback
  *
  * \param[in] data  Callback data
  *
  * \return Exit reason from callback data
  */
 const char *
 stonith__exit_reason(const stonith_callback_data_t *data)
 {
     if ((data == NULL) || (data->opaque == NULL)) {
         return NULL;
     }
     return ((pcmk__action_result_t *) data->opaque)->exit_reason;
 }
 
 /*!
  * \internal
  * \brief Return the exit status from an event notification
  *
  * \param[in] event  Event
  *
  * \return Exit status from event
  */
 int
 stonith__event_exit_status(const stonith_event_t *event)
 {
     if ((event == NULL) || (event->opaque == NULL)) {
         return CRM_EX_ERROR;
     } else {
         struct event_private *event_private = event->opaque;
 
         return event_private->result.exit_status;
     }
 }
 
 /*!
  * \internal
  * \brief Return the execution status from an event notification
  *
  * \param[in] event  Event
  *
  * \return Execution status from event
  */
 int
 stonith__event_execution_status(const stonith_event_t *event)
 {
     if ((event == NULL) || (event->opaque == NULL)) {
         return PCMK_EXEC_UNKNOWN;
     } else {
         struct event_private *event_private = event->opaque;
 
         return event_private->result.execution_status;
     }
 }
 
 /*!
  * \internal
  * \brief Return the exit reason from an event notification
  *
  * \param[in] event  Event
  *
  * \return Exit reason from event
  */
 const char *
 stonith__event_exit_reason(const stonith_event_t *event)
 {
     if ((event == NULL) || (event->opaque == NULL)) {
         return NULL;
     } else {
         struct event_private *event_private = event->opaque;
 
         return event_private->result.exit_reason;
     }
 }
 
 /*!
  * \internal
  * \brief Return a human-friendly description of a fencing event
  *
  * \param[in] event  Event to describe
  *
  * \return Newly allocated string with description of \p event
  * \note The caller is responsible for freeing the return value.
  *       This function asserts on memory errors and never returns NULL.
  */
 char *
 stonith__event_description(const stonith_event_t *event)
 {
     // Use somewhat readable defaults
     const char *origin = pcmk__s(event->client_origin, "a client");
     const char *origin_node = pcmk__s(event->origin, "a node");
     const char *executioner = pcmk__s(event->executioner, "the cluster");
     const char *device = pcmk__s(event->device, "unknown");
     const char *action = pcmk__s(event->action, event->operation);
     const char *target = pcmk__s(event->target, "no node");
     const char *reason = stonith__event_exit_reason(event);
     const char *status;
 
     if (action == NULL) {
         action = "(unknown)";
     }
 
     if (stonith__event_execution_status(event) != PCMK_EXEC_DONE) {
         status = pcmk_exec_status_str(stonith__event_execution_status(event));
     } else if (stonith__event_exit_status(event) != CRM_EX_OK) {
         status = pcmk_exec_status_str(PCMK_EXEC_ERROR);
     } else {
         status = crm_exit_str(CRM_EX_OK);
     }
 
     if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_HISTORY,
                      pcmk__str_none)) {
         return crm_strdup_printf("Fencing history may have changed");
 
     } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_ADD,
                             pcmk__str_none)) {
         return crm_strdup_printf("A fencing device (%s) was added", device);
 
     } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_DEL,
                             pcmk__str_none)) {
         return crm_strdup_printf("A fencing device (%s) was removed", device);
 
     } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_ADD,
                             pcmk__str_none)) {
         return crm_strdup_printf("A fencing topology level (%s) was added",
                                  device);
 
     } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_DEL,
                             pcmk__str_none)) {
         return crm_strdup_printf("A fencing topology level (%s) was removed",
                                  device);
     }
 
     // event->operation should be PCMK__VALUE_ST_NOTIFY_FENCE at this point
 
     return crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s%s%s%s (ref=%s)",
                              action, target, executioner, origin, origin_node,
                              status,
                              ((reason == NULL)? "" : " ("), pcmk__s(reason, ""),
                              ((reason == NULL)? "" : ")"),
                              pcmk__s(event->id, "(none)"));
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 // See comments in stonith-ng.h for why we re-declare before defining
 
 stonith_t *stonith_api_new(void);
 
 stonith_t *
 stonith_api_new(void)
 {
     return stonith__api_new();
 }
 
 void stonith_api_delete(stonith_t *stonith);
 
 void
 stonith_api_delete(stonith_t *stonith)
 {
     stonith__api_free(stonith);
 }
 
 static void
 stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
 {
     int call = GPOINTER_TO_INT(key);
     stonith_callback_client_t *blob = value;
 
     crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "no ID"));
 }
 
 void stonith_dump_pending_callbacks(stonith_t *stonith);
 
 void
 stonith_dump_pending_callbacks(stonith_t *stonith)
 {
     stonith_private_t *private = stonith->st_private;
 
     if (private->stonith_op_callback_table == NULL) {
         return;
     }
     return g_hash_table_foreach(private->stonith_op_callback_table,
                                 stonith_dump_pending_op, NULL);
 }
 
 bool stonith_dispatch(stonith_t *stonith_api);
 
 bool
 stonith_dispatch(stonith_t *stonith_api)
 {
     return (stonith__api_dispatch(stonith_api) == pcmk_rc_ok);
 }
 
 stonith_key_value_t *stonith_key_value_add(stonith_key_value_t *head,
                                            const char *key, const char *value);
 
 stonith_key_value_t *
 stonith_key_value_add(stonith_key_value_t *head, const char *key,
                       const char *value)
 {
     return stonith__key_value_add(head, key, value);
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/lrmd/lrmd_client.c b/lib/lrmd/lrmd_client.c
index d8f259d4db..b0d81bab00 100644
--- a/lib/lrmd/lrmd_client.c
+++ b/lib/lrmd/lrmd_client.c
@@ -1,2681 +1,2681 @@
 /*
  * Copyright 2012-2025 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <stdint.h>         // uint32_t, uint64_t
 #include <stdarg.h>
 #include <string.h>
 #include <ctype.h>
 #include <errno.h>
 
 #include <sys/types.h>
 #include <sys/wait.h>
 
 #include <glib.h>
 #include <dirent.h>
 
 #include <crm/crm.h>
 #include <crm/lrmd.h>
 #include <crm/lrmd_internal.h>
 #include <crm/services.h>
 #include <crm/services_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/ipc_internal.h>
 #include <crm/common/remote_internal.h>
 #include <crm/common/tls_internal.h>
 #include <crm/common/xml.h>
 
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>   // stonith__*
 
 #include <gnutls/gnutls.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 
 #define MAX_TLS_RECV_WAIT 10000
 
 CRM_TRACE_INIT_DATA(lrmd);
 
 static int lrmd_api_disconnect(lrmd_t * lrmd);
 static int lrmd_api_is_connected(lrmd_t * lrmd);
 
 /* IPC proxy functions */
 int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
 static void lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg);
 void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
 
 // GnuTLS client handshake timeout in seconds
 #define TLS_HANDSHAKE_TIMEOUT 5
 
 static void lrmd_tls_disconnect(lrmd_t * lrmd);
 static int global_remote_msg_id = 0;
 static void lrmd_tls_connection_destroy(gpointer userdata);
 static int add_tls_to_mainloop(lrmd_t *lrmd, bool do_api_handshake);
 
 typedef struct lrmd_private_s {
     uint64_t type;
     char *token;
     mainloop_io_t *source;
 
     /* IPC parameters */
     crm_ipc_t *ipc;
 
     pcmk__remote_t *remote;
 
     /* Extra TLS parameters */
     char *remote_nodename;
     char *server;
     int port;
     pcmk__tls_t *tls;
 
     /* while the async connection is occurring, this is the id
      * of the connection timeout timer. */
     int async_timer;
     int sock;
     /* since tls requires a round trip across the network for a
      * request/reply, there are times where we just want to be able
      * to send a request from the client and not wait around (or even care
      * about) what the reply is. */
     int expected_late_replies;
     GList *pending_notify;
     crm_trigger_t *process_notify;
     crm_trigger_t *handshake_trigger;
 
     lrmd_event_callback callback;
 
     /* Internal IPC proxy msg passing for remote guests */
     void (*proxy_callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg);
     void *proxy_callback_userdata;
     char *peer_version;
 } lrmd_private_t;
 
 static int process_lrmd_handshake_reply(xmlNode *reply, lrmd_private_t *native);
 static void report_async_connection_result(lrmd_t * lrmd, int rc);
 
 static lrmd_list_t *
 lrmd_list_add(lrmd_list_t * head, const char *value)
 {
     lrmd_list_t *p, *end;
 
     p = pcmk__assert_alloc(1, sizeof(lrmd_list_t));
     p->val = strdup(value);
 
     end = head;
     while (end && end->next) {
         end = end->next;
     }
 
     if (end) {
         end->next = p;
     } else {
         head = p;
     }
 
     return head;
 }
 
 void
 lrmd_list_freeall(lrmd_list_t * head)
 {
     lrmd_list_t *p;
 
     while (head) {
         char *val = (char *)head->val;
 
         p = head->next;
         free(val);
         free(head);
         head = p;
     }
 }
 
 lrmd_key_value_t *
 lrmd_key_value_add(lrmd_key_value_t * head, const char *key, const char *value)
 {
     lrmd_key_value_t *p, *end;
 
     p = pcmk__assert_alloc(1, sizeof(lrmd_key_value_t));
     p->key = strdup(key);
     p->value = strdup(value);
 
     end = head;
     while (end && end->next) {
         end = end->next;
     }
 
     if (end) {
         end->next = p;
     } else {
         head = p;
     }
 
     return head;
 }
 
 void
 lrmd_key_value_freeall(lrmd_key_value_t * head)
 {
     lrmd_key_value_t *p;
 
     while (head) {
         p = head->next;
         free(head->key);
         free(head->value);
         free(head);
         head = p;
     }
 }
 
 /*!
  * \brief Create a new lrmd_event_data_t object
  *
  * \param[in] rsc_id       ID of resource involved in event
  * \param[in] task         Action name
  * \param[in] interval_ms  Action interval
  *
  * \return Newly allocated and initialized lrmd_event_data_t
  * \note This functions asserts on memory errors, so the return value is
  *       guaranteed to be non-NULL. The caller is responsible for freeing the
  *       result with lrmd_free_event().
  */
 lrmd_event_data_t *
 lrmd_new_event(const char *rsc_id, const char *task, guint interval_ms)
 {
     lrmd_event_data_t *event = pcmk__assert_alloc(1, sizeof(lrmd_event_data_t));
 
     // lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
     event->rsc_id = pcmk__str_copy(rsc_id);
     event->op_type = pcmk__str_copy(task);
     event->interval_ms = interval_ms;
     return event;
 }
 
 lrmd_event_data_t *
 lrmd_copy_event(lrmd_event_data_t * event)
 {
     lrmd_event_data_t *copy = NULL;
 
     copy = pcmk__assert_alloc(1, sizeof(lrmd_event_data_t));
 
     copy->type = event->type;
 
     // lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
     copy->rsc_id = pcmk__str_copy(event->rsc_id);
     copy->op_type = pcmk__str_copy(event->op_type);
     copy->user_data = pcmk__str_copy(event->user_data);
     copy->output = pcmk__str_copy(event->output);
     copy->remote_nodename = pcmk__str_copy(event->remote_nodename);
     copy->exit_reason = pcmk__str_copy(event->exit_reason);
 
     copy->call_id = event->call_id;
     copy->timeout = event->timeout;
     copy->interval_ms = event->interval_ms;
     copy->start_delay = event->start_delay;
     copy->rsc_deleted = event->rsc_deleted;
     copy->rc = event->rc;
     copy->op_status = event->op_status;
     copy->t_run = event->t_run;
     copy->t_rcchange = event->t_rcchange;
     copy->exec_time = event->exec_time;
     copy->queue_time = event->queue_time;
     copy->connection_rc = event->connection_rc;
     copy->params = pcmk__str_table_dup(event->params);
 
     return copy;
 }
 
 /*!
  * \brief Free an executor event
  *
  * \param[in,out]  Executor event object to free
  */
 void
 lrmd_free_event(lrmd_event_data_t *event)
 {
     if (event == NULL) {
         return;
     }
     // @TODO Why are these const char *?
     free((void *) event->rsc_id);
     free((void *) event->op_type);
     free((void *) event->user_data);
     free((void *) event->remote_nodename);
     lrmd__reset_result(event);
     if (event->params != NULL) {
         g_hash_table_destroy(event->params);
     }
     free(event);
 }
 
 static void
 lrmd_dispatch_internal(gpointer data, gpointer user_data)
 {
     xmlNode *msg = data;
     lrmd_t *lrmd = user_data;
 
     const char *type;
     const char *proxy_session = crm_element_value(msg,
                                                   PCMK__XA_LRMD_IPC_SESSION);
     lrmd_private_t *native = lrmd->lrmd_private;
     lrmd_event_data_t event = { 0, };
 
     if (proxy_session != NULL) {
         /* this is proxy business */
         lrmd_internal_proxy_dispatch(lrmd, msg);
         return;
     } else if (!native->callback) {
         /* no callback set */
         crm_trace("notify event received but client has not set callback");
         return;
     }
 
     event.remote_nodename = native->remote_nodename;
     type = crm_element_value(msg, PCMK__XA_LRMD_OP);
     crm_element_value_int(msg, PCMK__XA_LRMD_CALLID, &event.call_id);
     event.rsc_id = crm_element_value(msg, PCMK__XA_LRMD_RSC_ID);
 
     if (pcmk__str_eq(type, LRMD_OP_RSC_REG, pcmk__str_none)) {
         event.type = lrmd_event_register;
     } else if (pcmk__str_eq(type, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
         event.type = lrmd_event_unregister;
     } else if (pcmk__str_eq(type, LRMD_OP_RSC_EXEC, pcmk__str_none)) {
         int rc = 0;
         int exec_time = 0;
         int queue_time = 0;
         time_t epoch = 0;
 
         crm_element_value_int(msg, PCMK__XA_LRMD_TIMEOUT, &event.timeout);
         crm_element_value_ms(msg, PCMK__XA_LRMD_RSC_INTERVAL,
                              &event.interval_ms);
         crm_element_value_int(msg, PCMK__XA_LRMD_RSC_START_DELAY,
                               &event.start_delay);
 
         crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_RC, &rc);
         event.rc = (enum ocf_exitcode) rc;
 
         crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_OP_STATUS,
                               &event.op_status);
         crm_element_value_int(msg, PCMK__XA_LRMD_RSC_DELETED,
                               &event.rsc_deleted);
 
         crm_element_value_epoch(msg, PCMK__XA_LRMD_RUN_TIME, &epoch);
         event.t_run = epoch;
 
         crm_element_value_epoch(msg, PCMK__XA_LRMD_RCCHANGE_TIME, &epoch);
         event.t_rcchange = epoch;
 
         crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_TIME, &exec_time);
         CRM_LOG_ASSERT(exec_time >= 0);
         event.exec_time = QB_MAX(0, exec_time);
 
         crm_element_value_int(msg, PCMK__XA_LRMD_QUEUE_TIME, &queue_time);
         CRM_LOG_ASSERT(queue_time >= 0);
         event.queue_time = QB_MAX(0, queue_time);
 
         event.op_type = crm_element_value(msg, PCMK__XA_LRMD_RSC_ACTION);
         event.user_data = crm_element_value(msg,
                                             PCMK__XA_LRMD_RSC_USERDATA_STR);
         event.type = lrmd_event_exec_complete;
 
         /* output and exit_reason may be freed by a callback */
         event.output = crm_element_value_copy(msg, PCMK__XA_LRMD_RSC_OUTPUT);
         lrmd__set_result(&event, event.rc, event.op_status,
                          crm_element_value(msg, PCMK__XA_LRMD_RSC_EXIT_REASON));
 
         event.params = xml2list(msg);
     } else if (pcmk__str_eq(type, LRMD_OP_NEW_CLIENT, pcmk__str_none)) {
         event.type = lrmd_event_new_client;
     } else if (pcmk__str_eq(type, LRMD_OP_POKE, pcmk__str_none)) {
         event.type = lrmd_event_poke;
     } else {
         return;
     }
 
     crm_trace("op %s notify event received", type);
     native->callback(&event);
 
     if (event.params) {
         g_hash_table_destroy(event.params);
     }
     lrmd__reset_result(&event);
 }
 
 // \return Always 0, to indicate that IPC mainloop source should be kept
 static int
 lrmd_ipc_dispatch(const char *buffer, ssize_t length, gpointer userdata)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->callback != NULL) {
         xmlNode *msg = pcmk__xml_parse(buffer);
 
         lrmd_dispatch_internal(msg, lrmd);
         pcmk__xml_free(msg);
     }
     return 0;
 }
 
 static void
 lrmd_free_xml(gpointer userdata)
 {
     pcmk__xml_free((xmlNode *) userdata);
 }
 
 static bool
 remote_executor_connected(lrmd_t * lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     return (native->remote->tls_session != NULL);
 }
 
 static void
 handle_remote_msg(xmlNode *xml, lrmd_t *lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
     const char *msg_type = NULL;
 
     msg_type = crm_element_value(xml, PCMK__XA_LRMD_REMOTE_MSG_TYPE);
     if (pcmk__str_eq(msg_type, "notify", pcmk__str_casei)) {
         lrmd_dispatch_internal(xml, lrmd);
     } else if (pcmk__str_eq(msg_type, "reply", pcmk__str_casei)) {
         const char *op = crm_element_value(xml, PCMK__XA_LRMD_OP);
 
         if (native->expected_late_replies > 0) {
             native->expected_late_replies--;
 
             /* The register op message we get as a response to lrmd_handshake_async
              * is a reply, so we have to handle that here.
              */
             if (pcmk__str_eq(op, "register", pcmk__str_casei)) {
                 int rc = process_lrmd_handshake_reply(xml, native);
                 report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
             }
         } else {
             int reply_id = 0;
             crm_element_value_int(xml, PCMK__XA_LRMD_CALLID, &reply_id);
             /* if this happens, we want to know about it */
             crm_err("Got outdated Pacemaker Remote reply %d", reply_id);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Notify trigger handler
  *
  * \param[in,out] userdata API connection
  *
  * \return Always return G_SOURCE_CONTINUE to leave this trigger handler in the
  *         mainloop
  */
 static int
 process_pending_notifies(gpointer userdata)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->pending_notify == NULL) {
         return G_SOURCE_CONTINUE;
     }
 
     crm_trace("Processing pending notifies");
     g_list_foreach(native->pending_notify, lrmd_dispatch_internal, lrmd);
     g_list_free_full(native->pending_notify, lrmd_free_xml);
     native->pending_notify = NULL;
     return G_SOURCE_CONTINUE;
 }
 
 /*!
  * \internal
  * \brief TLS dispatch function for file descriptor sources
  *
  * \param[in,out] userdata  API connection
  *
  * \return -1 on error to remove the source from the mainloop, or 0 otherwise
  *         to leave it in the mainloop
  */
 static int
 lrmd_tls_dispatch(gpointer userdata)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *xml = NULL;
     int rc = pcmk_rc_ok;
 
     if (!remote_executor_connected(lrmd)) {
         crm_trace("TLS dispatch triggered after disconnect");
         return -1;
     }
 
     crm_trace("TLS dispatch triggered");
 
     rc = pcmk__remote_ready(native->remote, 0);
     if (rc == pcmk_rc_ok) {
         rc = pcmk__read_remote_message(native->remote, -1);
     }
 
     if (rc != pcmk_rc_ok && rc != ETIME) {
         crm_info("Lost %s executor connection while reading data",
                  (native->remote_nodename? native->remote_nodename : "local"));
         lrmd_tls_disconnect(lrmd);
         return -1;
     }
 
     /* If rc is ETIME, there was nothing to read but we may already have a
      * full message in the buffer
      */
     xml = pcmk__remote_message_xml(native->remote);
 
     if (xml == NULL) {
         return 0;
     }
 
     handle_remote_msg(xml, lrmd);
     pcmk__xml_free(xml);
     return 0;
 }
 
 /* Not used with mainloop */
 int
 lrmd_poll(lrmd_t * lrmd, int timeout)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             return crm_ipc_ready(native->ipc);
 
         case pcmk__client_tls:
             if (native->pending_notify) {
                 return 1;
             } else {
                 int rc = pcmk__remote_ready(native->remote, 0);
 
                 switch (rc) {
                     case pcmk_rc_ok:
                         return 1;
                     case ETIME:
                         return 0;
                     default:
                         return pcmk_rc2legacy(rc);
                 }
             }
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             return -EPROTONOSUPPORT;
     }
 }
 
 /* Not used with mainloop */
 bool
 lrmd_dispatch(lrmd_t * lrmd)
 {
     lrmd_private_t *private = NULL;
 
     pcmk__assert(lrmd != NULL);
 
     private = lrmd->lrmd_private;
     switch (private->type) {
         case pcmk__client_ipc:
             while (crm_ipc_ready(private->ipc)) {
                 if (crm_ipc_read(private->ipc) > 0) {
                     const char *msg = crm_ipc_buffer(private->ipc);
 
                     lrmd_ipc_dispatch(msg, strlen(msg), lrmd);
                 }
             }
             break;
         case pcmk__client_tls:
             lrmd_tls_dispatch(lrmd);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     private->type);
     }
 
     if (lrmd_api_is_connected(lrmd) == FALSE) {
         crm_err("Connection closed");
         return FALSE;
     }
 
     return TRUE;
 }
 
 static xmlNode *
 lrmd_create_op(const char *token, const char *op, xmlNode *data, int timeout,
                enum lrmd_call_options options)
 {
     xmlNode *op_msg = NULL;
 
     CRM_CHECK(token != NULL, return NULL);
 
     op_msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_COMMAND);
     crm_xml_add(op_msg, PCMK__XA_T, PCMK__VALUE_LRMD);
     crm_xml_add(op_msg, PCMK__XA_LRMD_OP, op);
     crm_xml_add_int(op_msg, PCMK__XA_LRMD_TIMEOUT, timeout);
     crm_xml_add_int(op_msg, PCMK__XA_LRMD_CALLOPT, options);
 
     if (data != NULL) {
         xmlNode *wrapper = pcmk__xe_create(op_msg, PCMK__XE_LRMD_CALLDATA);
 
         pcmk__xml_copy(wrapper, data);
     }
 
     crm_trace("Created executor %s command with call options %.8lx (%d)",
               op, (long)options, options);
     return op_msg;
 }
 
 static void
 lrmd_ipc_connection_destroy(gpointer userdata)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             crm_info("Disconnected from local executor");
             break;
         case pcmk__client_tls:
             crm_info("Disconnected from remote executor on %s",
                      native->remote_nodename);
             break;
         default:
             crm_err("Unsupported executor connection type %d (bug?)",
                     native->type);
     }
 
     /* Prevent these from being cleaned up in lrmd_api_disconnect() */
     native->ipc = NULL;
     native->source = NULL;
 
     if (native->callback) {
         lrmd_event_data_t event = { 0, };
         event.type = lrmd_event_disconnect;
         event.remote_nodename = native->remote_nodename;
         native->callback(&event);
     }
 }
 
 static void
 lrmd_tls_connection_destroy(gpointer userdata)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     crm_info("TLS connection destroyed");
 
     if (native->remote->tls_session) {
         gnutls_bye(native->remote->tls_session, GNUTLS_SHUT_RDWR);
         gnutls_deinit(native->remote->tls_session);
         native->remote->tls_session = NULL;
     }
     if (native->tls) {
         pcmk__free_tls(native->tls);
         native->tls = NULL;
     }
     if (native->sock >= 0) {
         close(native->sock);
     }
     if (native->process_notify) {
         mainloop_destroy_trigger(native->process_notify);
         native->process_notify = NULL;
     }
     if (native->pending_notify) {
         g_list_free_full(native->pending_notify, lrmd_free_xml);
         native->pending_notify = NULL;
     }
     if (native->handshake_trigger != NULL) {
         mainloop_destroy_trigger(native->handshake_trigger);
         native->handshake_trigger = NULL;
     }
 
     free(native->remote->buffer);
     free(native->remote->start_state);
     native->remote->buffer = NULL;
     native->remote->start_state = NULL;
     native->source = 0;
     native->sock = -1;
 
     if (native->callback) {
         lrmd_event_data_t event = { 0, };
         event.remote_nodename = native->remote_nodename;
         event.type = lrmd_event_disconnect;
         native->callback(&event);
     }
     return;
 }
 
 // \return Standard Pacemaker return code
 int
 lrmd__remote_send_xml(pcmk__remote_t *session, xmlNode *msg, uint32_t id,
                       const char *msg_type)
 {
     crm_xml_add_int(msg, PCMK__XA_LRMD_REMOTE_MSG_ID, id);
     crm_xml_add(msg, PCMK__XA_LRMD_REMOTE_MSG_TYPE, msg_type);
     return pcmk__remote_send_xml(session, msg);
 }
 
 // \return Standard Pacemaker return code
 static int
 read_remote_reply(lrmd_t *lrmd, int total_timeout, int expected_reply_id,
                   xmlNode **reply)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
     time_t start = time(NULL);
     const char *msg_type = NULL;
     int reply_id = 0;
     int remaining_timeout = 0;
     int rc = pcmk_rc_ok;
 
     /* A timeout of 0 here makes no sense.  We have to wait a period of time
      * for the response to come back.  If -1 or 0, default to 10 seconds. */
     if (total_timeout <= 0 || total_timeout > MAX_TLS_RECV_WAIT) {
         total_timeout = MAX_TLS_RECV_WAIT;
     }
 
     for (*reply = NULL; *reply == NULL; ) {
 
         *reply = pcmk__remote_message_xml(native->remote);
         if (*reply == NULL) {
             /* read some more off the tls buffer if we still have time left. */
             if (remaining_timeout) {
                 remaining_timeout = total_timeout - ((time(NULL) - start) * 1000);
             } else {
                 remaining_timeout = total_timeout;
             }
             if (remaining_timeout <= 0) {
                 return ETIME;
             }
 
             rc = pcmk__read_remote_message(native->remote, remaining_timeout);
             if (rc != pcmk_rc_ok) {
                 return rc;
             }
 
             *reply = pcmk__remote_message_xml(native->remote);
             if (*reply == NULL) {
                 return ENOMSG;
             }
         }
 
         crm_element_value_int(*reply, PCMK__XA_LRMD_REMOTE_MSG_ID, &reply_id);
         msg_type = crm_element_value(*reply, PCMK__XA_LRMD_REMOTE_MSG_TYPE);
 
         if (!msg_type) {
             crm_err("Empty msg type received while waiting for reply");
             pcmk__xml_free(*reply);
             *reply = NULL;
         } else if (pcmk__str_eq(msg_type, "notify", pcmk__str_casei)) {
             /* got a notify while waiting for reply, trigger the notify to be processed later */
             crm_info("queueing notify");
             native->pending_notify = g_list_append(native->pending_notify, *reply);
             if (native->process_notify) {
                 crm_info("notify trigger set.");
                 mainloop_set_trigger(native->process_notify);
             }
             *reply = NULL;
         } else if (!pcmk__str_eq(msg_type, "reply", pcmk__str_casei)) {
             /* msg isn't a reply, make some noise */
             crm_err("Expected a reply, got %s", msg_type);
             pcmk__xml_free(*reply);
             *reply = NULL;
         } else if (reply_id != expected_reply_id) {
             if (native->expected_late_replies > 0) {
                 native->expected_late_replies--;
             } else {
                 crm_err("Got outdated reply, expected id %d got id %d", expected_reply_id, reply_id);
             }
             pcmk__xml_free(*reply);
             *reply = NULL;
         }
     }
 
     if (native->remote->buffer && native->process_notify) {
         mainloop_set_trigger(native->process_notify);
     }
 
     return rc;
 }
 
 // \return Standard Pacemaker return code
 static int
 send_remote_message(lrmd_t *lrmd, xmlNode *msg)
 {
     int rc = pcmk_rc_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     global_remote_msg_id++;
     if (global_remote_msg_id <= 0) {
         global_remote_msg_id = 1;
     }
 
     rc = lrmd__remote_send_xml(native->remote, msg, global_remote_msg_id,
                                "request");
     if (rc != pcmk_rc_ok) {
         crm_err("Disconnecting because TLS message could not be sent to "
                 "Pacemaker Remote: %s", pcmk_rc_str(rc));
         lrmd_tls_disconnect(lrmd);
     }
     return rc;
 }
 
 static int
 lrmd_tls_send_recv(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
 {
     int rc = 0;
     xmlNode *xml = NULL;
 
     if (!remote_executor_connected(lrmd)) {
         return -ENOTCONN;
     }
 
     rc = send_remote_message(lrmd, msg);
     if (rc != pcmk_rc_ok) {
         return pcmk_rc2legacy(rc);
     }
 
     rc = read_remote_reply(lrmd, timeout, global_remote_msg_id, &xml);
     if (rc != pcmk_rc_ok) {
         crm_err("Disconnecting remote after request %d reply not received: %s "
                 QB_XS " rc=%d timeout=%dms",
                 global_remote_msg_id, pcmk_rc_str(rc), rc, timeout);
         lrmd_tls_disconnect(lrmd);
     }
 
     if (reply) {
         *reply = xml;
     } else {
         pcmk__xml_free(xml);
     }
 
     return pcmk_rc2legacy(rc);
 }
 
 static int
 lrmd_send_xml(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
 {
     int rc = pcmk_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             rc = crm_ipc_send(native->ipc, msg, crm_ipc_client_response, timeout, reply);
             break;
         case pcmk__client_tls:
             rc = lrmd_tls_send_recv(lrmd, msg, timeout, reply);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             rc = -EPROTONOSUPPORT;
     }
 
     return rc;
 }
 
 static int
 lrmd_send_xml_no_reply(lrmd_t * lrmd, xmlNode * msg)
 {
     int rc = pcmk_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             rc = crm_ipc_send(native->ipc, msg, crm_ipc_flags_none, 0, NULL);
             break;
         case pcmk__client_tls:
             rc = send_remote_message(lrmd, msg);
             if (rc == pcmk_rc_ok) {
                 /* we don't want to wait around for the reply, but
                  * since the request/reply protocol needs to behave the same
                  * as libqb, a reply will eventually come later anyway. */
                 native->expected_late_replies++;
             }
             rc = pcmk_rc2legacy(rc);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             rc = -EPROTONOSUPPORT;
     }
 
     return rc;
 }
 
 static int
 lrmd_api_is_connected(lrmd_t * lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             return crm_ipc_connected(native->ipc);
         case pcmk__client_tls:
             return remote_executor_connected(lrmd);
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             return 0;
     }
 }
 
 /*!
  * \internal
  * \brief Send a prepared API command to the executor
  *
  * \param[in,out] lrmd          Existing connection to the executor
  * \param[in]     op            Name of API command to send
  * \param[in]     data          Command data XML to add to the sent command
  * \param[out]    output_data   If expecting a reply, it will be stored here
  * \param[in]     timeout       Timeout in milliseconds (if 0, defaults to
  *                              a sensible value per the type of connection,
  *                              standard vs. pacemaker remote);
  *                              also propagated to the command XML
  * \param[in]     call_options  Call options to pass to server when sending
  * \param[in]     expect_reply  If true, wait for a reply from the server;
  *                              must be true for IPC (as opposed to TLS) clients
  *
  * \return pcmk_ok on success, -errno on error
  */
 static int
 lrmd_send_command(lrmd_t *lrmd, const char *op, xmlNode *data,
                   xmlNode **output_data, int timeout,
                   enum lrmd_call_options options, bool expect_reply)
 {
     int rc = pcmk_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *op_msg = NULL;
     xmlNode *op_reply = NULL;
 
     if (!lrmd_api_is_connected(lrmd)) {
         return -ENOTCONN;
     }
 
     if (op == NULL) {
         crm_err("No operation specified");
         return -EINVAL;
     }
 
     CRM_LOG_ASSERT(native->token != NULL);
     crm_trace("Sending %s op to executor", op);
 
     op_msg = lrmd_create_op(native->token, op, data, timeout, options);
 
     if (op_msg == NULL) {
         return -EINVAL;
     }
 
     if (expect_reply) {
         rc = lrmd_send_xml(lrmd, op_msg, timeout, &op_reply);
     } else {
         rc = lrmd_send_xml_no_reply(lrmd, op_msg);
         goto done;
     }
 
     if (rc < 0) {
         crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%d): %d", op, timeout, rc);
         goto done;
 
     } else if (op_reply == NULL) {
         rc = -ENOMSG;
         goto done;
     }
 
     rc = pcmk_ok;
     crm_trace("%s op reply received", op);
     if (crm_element_value_int(op_reply, PCMK__XA_LRMD_RC, &rc) != 0) {
         rc = -ENOMSG;
         goto done;
     }
 
     crm_log_xml_trace(op_reply, "Reply");
 
     if (output_data) {
         *output_data = op_reply;
         op_reply = NULL;        /* Prevent subsequent free */
     }
 
   done:
     if (lrmd_api_is_connected(lrmd) == FALSE) {
         crm_err("Executor disconnected");
     }
 
     pcmk__xml_free(op_msg);
     pcmk__xml_free(op_reply);
     return rc;
 }
 
 static int
 lrmd_api_poke_connection(lrmd_t * lrmd)
 {
     int rc;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     rc = lrmd_send_command(lrmd, LRMD_OP_POKE, data, NULL, 0, 0,
                            (native->type == pcmk__client_ipc));
     pcmk__xml_free(data);
 
     return rc < 0 ? rc : pcmk_ok;
 }
 
 // \return Standard Pacemaker return code
 int
 lrmd__validate_remote_settings(lrmd_t *lrmd, GHashTable *hash)
 {
     int rc = pcmk_rc_ok;
     const char *value;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XA_LRMD_OP);
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
 
     value = g_hash_table_lookup(hash, PCMK_OPT_STONITH_WATCHDOG_TIMEOUT);
     if ((value) &&
         (stonith__watchdog_fencing_enabled_for_node(native->remote_nodename))) {
        crm_xml_add(data, PCMK__XA_LRMD_WATCHDOG, value);
     }
 
     rc = lrmd_send_command(lrmd, LRMD_OP_CHECK, data, NULL, 0, 0,
                            (native->type == pcmk__client_ipc));
     pcmk__xml_free(data);
     return (rc < 0)? pcmk_legacy2rc(rc) : pcmk_rc_ok;
 }
 
 static xmlNode *
 lrmd_handshake_hello_msg(const char *name, bool is_proxy)
 {
     xmlNode *hello = pcmk__xe_create(NULL, PCMK__XE_LRMD_COMMAND);
 
     crm_xml_add(hello, PCMK__XA_T, PCMK__VALUE_LRMD);
     crm_xml_add(hello, PCMK__XA_LRMD_OP, CRM_OP_REGISTER);
     crm_xml_add(hello, PCMK__XA_LRMD_CLIENTNAME, name);
     crm_xml_add(hello, PCMK__XA_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
 
     /* advertise that we are a proxy provider */
     if (is_proxy) {
         pcmk__xe_set_bool_attr(hello, PCMK__XA_LRMD_IS_IPC_PROVIDER, true);
     }
 
     return hello;
 }
 
 static int
 process_lrmd_handshake_reply(xmlNode *reply, lrmd_private_t *native)
 {
     int rc = pcmk_rc_ok;
     const char *version = crm_element_value(reply, PCMK__XA_LRMD_PROTOCOL_VERSION);
     const char *msg_type = crm_element_value(reply, PCMK__XA_LRMD_OP);
     const char *tmp_ticket = crm_element_value(reply, PCMK__XA_LRMD_CLIENTID);
     const char *start_state = crm_element_value(reply, PCMK__XA_NODE_START_STATE);
     long long uptime = -1;
 
     crm_element_value_int(reply, PCMK__XA_LRMD_RC, &rc);
     rc = pcmk_legacy2rc(rc);
 
     /* The remote executor may add its uptime to the XML reply, which is useful
      * in handling transient attributes when the connection to the remote node
      * unexpectedly drops.  If no parameter is given, just default to -1.
      */
     crm_element_value_ll(reply, PCMK__XA_UPTIME, &uptime);
     native->remote->uptime = uptime;
 
     if (start_state) {
         native->remote->start_state = strdup(start_state);
     }
 
     if (rc == EPROTO) {
         crm_err("Executor protocol version mismatch between client (%s) and server (%s)",
                 LRMD_PROTOCOL_VERSION, version);
         crm_log_xml_err(reply, "Protocol Error");
     } else if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
         crm_err("Invalid registration message: %s", msg_type);
         crm_log_xml_err(reply, "Bad reply");
         rc = EPROTO;
     } else if (tmp_ticket == NULL) {
         crm_err("No registration token provided");
         crm_log_xml_err(reply, "Bad reply");
         rc = EPROTO;
     } else {
         crm_trace("Obtained registration token: %s", tmp_ticket);
         native->token = strdup(tmp_ticket);
         native->peer_version = strdup(version?version:"1.0"); /* Included since 1.1 */
         rc = pcmk_rc_ok;
     }
 
     return rc;
 }
 
 static int
 lrmd_handshake(lrmd_t * lrmd, const char *name)
 {
     int rc = pcmk_rc_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *reply = NULL;
     xmlNode *hello = lrmd_handshake_hello_msg(name, native->proxy_callback != NULL);
 
     rc = lrmd_send_xml(lrmd, hello, -1, &reply);
 
     if (rc < 0) {
         crm_perror(LOG_DEBUG, "Couldn't complete registration with the executor API: %d", rc);
         rc = ECOMM;
     } else if (reply == NULL) {
         crm_err("Did not receive registration reply");
         rc = EPROTO;
     } else {
         rc = process_lrmd_handshake_reply(reply, native);
     }
 
     pcmk__xml_free(reply);
     pcmk__xml_free(hello);
 
     if (rc != pcmk_rc_ok) {
         lrmd_api_disconnect(lrmd);
     }
 
     return rc;
 }
 
 static int
 lrmd_handshake_async(lrmd_t * lrmd, const char *name)
 {
     int rc = pcmk_rc_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
     xmlNode *hello = lrmd_handshake_hello_msg(name, native->proxy_callback != NULL);
 
     rc = send_remote_message(lrmd, hello);
 
     if (rc == pcmk_rc_ok) {
         native->expected_late_replies++;
     } else {
         lrmd_api_disconnect(lrmd);
     }
 
     pcmk__xml_free(hello);
     return rc;
 }
 
 static int
 lrmd_ipc_connect(lrmd_t * lrmd, int *fd)
 {
     int rc = pcmk_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     struct ipc_client_callbacks lrmd_callbacks = {
         .dispatch = lrmd_ipc_dispatch,
         .destroy = lrmd_ipc_connection_destroy
     };
 
     crm_info("Connecting to executor");
 
     if (fd) {
         /* No mainloop */
         native->ipc = crm_ipc_new(CRM_SYSTEM_LRMD, 0);
         if (native->ipc != NULL) {
             rc = pcmk__connect_generic_ipc(native->ipc);
             if (rc == pcmk_rc_ok) {
                 rc = pcmk__ipc_fd(native->ipc, fd);
             }
             if (rc != pcmk_rc_ok) {
                 crm_err("Connection to executor failed: %s", pcmk_rc_str(rc));
                 rc = -ENOTCONN;
             }
         }
     } else {
         native->source = mainloop_add_ipc_client(CRM_SYSTEM_LRMD, G_PRIORITY_HIGH, 0, lrmd, &lrmd_callbacks);
         native->ipc = mainloop_get_ipc_client(native->source);
     }
 
     if (native->ipc == NULL) {
         crm_debug("Could not connect to the executor API");
         rc = -ENOTCONN;
     }
 
     return rc;
 }
 
 static void
 copy_gnutls_datum(gnutls_datum_t *dest, gnutls_datum_t *source)
 {
     pcmk__assert((dest != NULL) && (source != NULL) && (source->data != NULL));
 
     dest->data = gnutls_malloc(source->size);
     pcmk__mem_assert(dest->data);
 
     memcpy(dest->data, source->data, source->size);
     dest->size = source->size;
 }
 
 static void
 clear_gnutls_datum(gnutls_datum_t *datum)
 {
     gnutls_free(datum->data);
     datum->data = NULL;
     datum->size = 0;
 }
 
 #define KEY_READ_LEN 256    // Chunk size for reading key from file
 
 // \return Standard Pacemaker return code
 static int
 read_gnutls_key(const char *location, gnutls_datum_t *key)
 {
     FILE *stream = NULL;
     size_t buf_len = KEY_READ_LEN;
 
     if ((location == NULL) || (key == NULL)) {
         return EINVAL;
     }
 
     stream = fopen(location, "r");
     if (stream == NULL) {
         return errno;
     }
 
     key->data = gnutls_malloc(buf_len);
     key->size = 0;
     while (!feof(stream)) {
         int next = fgetc(stream);
 
         if (next == EOF) {
             if (!feof(stream)) {
                 crm_warn("Pacemaker Remote key read was partially successful "
                          "(copy in memory may be corrupted)");
             }
             break;
         }
         if (key->size == buf_len) {
             buf_len = key->size + KEY_READ_LEN;
             key->data = gnutls_realloc(key->data, buf_len);
             pcmk__assert(key->data);
         }
         key->data[key->size++] = (unsigned char) next;
     }
     fclose(stream);
 
     if (key->size == 0) {
         clear_gnutls_datum(key);
         return ENOKEY;
     }
     return pcmk_rc_ok;
 }
 
 // Cache the most recently used Pacemaker Remote authentication key
 
 struct key_cache_s {
     time_t updated;         // When cached key was read (valid for 1 minute)
     const char *location;   // Where cached key was read from
     gnutls_datum_t key;     // Cached key
 };
 
 static bool
 key_is_cached(struct key_cache_s *key_cache)
 {
     return key_cache->updated != 0;
 }
 
 static bool
 key_cache_expired(struct key_cache_s *key_cache)
 {
     return (time(NULL) - key_cache->updated) >= 60;
 }
 
 static void
 clear_key_cache(struct key_cache_s *key_cache)
 {
     clear_gnutls_datum(&(key_cache->key));
     if ((key_cache->updated != 0) || (key_cache->location != NULL)) {
         key_cache->updated = 0;
         key_cache->location = NULL;
         crm_debug("Cleared Pacemaker Remote key cache");
     }
 }
 
 static void
 get_cached_key(struct key_cache_s *key_cache, gnutls_datum_t *key)
 {
     copy_gnutls_datum(key, &(key_cache->key));
     crm_debug("Using cached Pacemaker Remote key from %s",
               pcmk__s(key_cache->location, "unknown location"));
 }
 
 static void
 cache_key(struct key_cache_s *key_cache, gnutls_datum_t *key,
           const char *location)
 {
     key_cache->updated = time(NULL);
     key_cache->location = location;
     copy_gnutls_datum(&(key_cache->key), key);
     crm_debug("Using (and cacheing) Pacemaker Remote key from %s",
               pcmk__s(location, "unknown location"));
 }
 
 /*!
  * \internal
  * \brief Get Pacemaker Remote authentication key from file or cache
  *
  * \param[in]  location         Path to key file to try (this memory must
  *                              persist across all calls of this function)
  * \param[out] key              Key from location or cache
  *
  * \return Standard Pacemaker return code
  */
 static int
 get_remote_key(const char *location, gnutls_datum_t *key)
 {
     static struct key_cache_s key_cache = { 0, };
     int rc = pcmk_rc_ok;
 
     if ((location == NULL) || (key == NULL)) {
         return EINVAL;
     }
 
     if (key_is_cached(&key_cache)) {
         if (key_cache_expired(&key_cache)) {
             clear_key_cache(&key_cache);
         } else {
             get_cached_key(&key_cache, key);
             return pcmk_rc_ok;
         }
     }
 
     rc = read_gnutls_key(location, key);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
     cache_key(&key_cache, key, location);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Initialize the Pacemaker Remote authentication key
  *
  * Try loading the Pacemaker Remote authentication key from cache if available,
  * otherwise from these locations, in order of preference:
  *
  * - The value of the PCMK_authkey_location environment variable, if set
  * - The Pacemaker default key file location
  *
  * \param[out] key  Where to store key
  *
  * \return Standard Pacemaker return code
  */
 int
 lrmd__init_remote_key(gnutls_datum_t *key)
 {
     static const char *env_location = NULL;
     static bool need_env = true;
 
     int rc = pcmk_rc_ok;
 
     if (need_env) {
         env_location = pcmk__env_option(PCMK__ENV_AUTHKEY_LOCATION);
         need_env = false;
     }
 
     // Try location in environment variable, if set
     if (env_location != NULL) {
         rc = get_remote_key(env_location, key);
         if (rc == pcmk_rc_ok) {
             return pcmk_rc_ok;
         }
 
         crm_warn("Could not read Pacemaker Remote key from %s: %s",
                  env_location, pcmk_rc_str(rc));
         return ENOKEY;
     }
 
     // Try default location, if environment wasn't explicitly set to it
     rc = get_remote_key(DEFAULT_REMOTE_KEY_LOCATION, key);
     if (rc == pcmk_rc_ok) {
         return pcmk_rc_ok;
     }
 
     crm_warn("Could not read Pacemaker Remote key from default location %s: %s",
              DEFAULT_REMOTE_KEY_LOCATION, pcmk_rc_str(rc));
     return ENOKEY;
 }
 
 static void
 report_async_connection_result(lrmd_t * lrmd, int rc)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->callback) {
         lrmd_event_data_t event = { 0, };
         event.type = lrmd_event_connect;
         event.remote_nodename = native->remote_nodename;
         event.connection_rc = rc;
         native->callback(&event);
     }
 }
 
 static void
 tls_handshake_failed(lrmd_t *lrmd, int tls_rc, int rc)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     crm_warn("Disconnecting after TLS handshake with "
              "Pacemaker Remote server %s:%d failed: %s",
              native->server, native->port,
              (rc == EPROTO)? gnutls_strerror(tls_rc) : pcmk_rc_str(rc));
     report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
 
     gnutls_deinit(native->remote->tls_session);
     native->remote->tls_session = NULL;
     lrmd_tls_connection_destroy(lrmd);
 }
 
 static void
 tls_handshake_succeeded(lrmd_t *lrmd)
 {
     int rc = pcmk_rc_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     /* Now that the handshake is done, see if any client TLS certificate is
      * close to its expiration date and log if so.  If a TLS certificate is not
      * in use, this function will just return so we don't need to check for the
      * session type here.
      */
     pcmk__tls_check_cert_expiration(native->remote->tls_session);
 
     crm_info("TLS connection to Pacemaker Remote server %s:%d succeeded",
              native->server, native->port);
     rc = add_tls_to_mainloop(lrmd, true);
 
     /* If add_tls_to_mainloop failed, report that right now.  Otherwise, we have
      * to wait until we read the async reply to report anything.
      */
     if (rc != pcmk_rc_ok) {
         report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
     }
 }
 
 /*!
  * \internal
  * \brief Perform a TLS client handshake with a Pacemaker Remote server
  *
  * \param[in] lrmd  Newly established Pacemaker Remote executor connection
  *
  * \return Standard Pacemaker return code
  */
 static int
 tls_client_handshake(lrmd_t *lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
     int tls_rc = GNUTLS_E_SUCCESS;
     int rc = pcmk__tls_client_handshake(native->remote, TLS_HANDSHAKE_TIMEOUT,
                                         &tls_rc);
 
     if (rc != pcmk_rc_ok) {
         tls_handshake_failed(lrmd, tls_rc, rc);
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Add trigger and file descriptor mainloop sources for TLS
  *
  * \param[in,out] lrmd              API connection with established TLS session
  * \param[in]     do_api_handshake  Whether to perform executor handshake
  *
  * \return Standard Pacemaker return code
  */
 static int
 add_tls_to_mainloop(lrmd_t *lrmd, bool do_api_handshake)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
     int rc = pcmk_rc_ok;
 
     char *name = crm_strdup_printf("pacemaker-remote-%s:%d",
                                    native->server, native->port);
 
     struct mainloop_fd_callbacks tls_fd_callbacks = {
         .dispatch = lrmd_tls_dispatch,
         .destroy = lrmd_tls_connection_destroy,
     };
 
     native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH,
                                                   process_pending_notifies, lrmd);
     native->source = mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd,
                                      &tls_fd_callbacks);
 
     /* Async connections lose the client name provided by the API caller, so we
      * have to use our generated name here to perform the executor handshake.
      *
      * @TODO Keep track of the caller-provided name. Perhaps we should be using
      * that name in this function instead of generating one anyway.
      */
     if (do_api_handshake) {
         rc = lrmd_handshake_async(lrmd, name);
     }
     free(name);
     return rc;
 }
 
 struct handshake_data_s {
     lrmd_t *lrmd;
     time_t start_time;
     int timeout_sec;
 };
 
 static gboolean
 try_handshake_cb(gpointer user_data)
 {
     struct handshake_data_s *hs = user_data;
     lrmd_t *lrmd = hs->lrmd;
     lrmd_private_t *native = lrmd->lrmd_private;
     pcmk__remote_t *remote = native->remote;
 
     int rc = pcmk_rc_ok;
     int tls_rc = GNUTLS_E_SUCCESS;
 
     if (time(NULL) >= hs->start_time + hs->timeout_sec) {
         rc = ETIME;
 
         tls_handshake_failed(lrmd, GNUTLS_E_TIMEDOUT, rc);
         free(hs);
         return 0;
     }
 
     rc = pcmk__tls_client_try_handshake(remote, &tls_rc);
 
     if (rc == pcmk_rc_ok) {
         tls_handshake_succeeded(lrmd);
         free(hs);
         return 0;
     } else if (rc == EAGAIN) {
         mainloop_set_trigger(native->handshake_trigger);
         return 1;
     } else {
         rc = EKEYREJECTED;
         tls_handshake_failed(lrmd, tls_rc, rc);
         free(hs);
         return 0;
     }
 }
 
 static void
 lrmd_tcp_connect_cb(void *userdata, int rc, int sock)
 {
     lrmd_t *lrmd = userdata;
     lrmd_private_t *native = lrmd->lrmd_private;
     int tls_rc = GNUTLS_E_SUCCESS;
     bool use_cert = pcmk__x509_enabled();
 
     native->async_timer = 0;
 
     if (rc != pcmk_rc_ok) {
         lrmd_tls_connection_destroy(lrmd);
         crm_info("Could not connect to Pacemaker Remote at %s:%d: %s "
                  QB_XS " rc=%d",
                  native->server, native->port, pcmk_rc_str(rc), rc);
         report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
         return;
     }
 
     /* The TCP connection was successful, so establish the TLS connection. */
 
     native->sock = sock;
 
     if (native->tls == NULL) {
         rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK);
 
         if (rc != pcmk_rc_ok) {
             lrmd_tls_connection_destroy(lrmd);
             report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
             return;
         }
     }
 
     if (!use_cert) {
         gnutls_datum_t psk_key = { NULL, 0 };
 
         rc = lrmd__init_remote_key(&psk_key);
         if (rc != pcmk_rc_ok) {
             crm_info("Could not connect to Pacemaker Remote at %s:%d: %s "
                      QB_XS " rc=%d",
                      native->server, native->port, pcmk_rc_str(rc), rc);
             lrmd_tls_connection_destroy(lrmd);
             report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
             return;
         }
 
         pcmk__tls_add_psk_key(native->tls, &psk_key);
         gnutls_free(psk_key.data);
     }
 
     native->remote->tls_session = pcmk__new_tls_session(native->tls, sock);
     if (native->remote->tls_session == NULL) {
         lrmd_tls_connection_destroy(lrmd);
         report_async_connection_result(lrmd, -EPROTO);
         return;
     }
 
     /* If the TLS handshake immediately succeeds or fails, we can handle that
      * now without having to deal with mainloops and retries.  Otherwise, add a
      * trigger to keep trying until we get a result (or it times out).
      */
     rc = pcmk__tls_client_try_handshake(native->remote, &tls_rc);
     if (rc == EAGAIN) {
         struct handshake_data_s *hs = NULL;
 
         if (native->handshake_trigger != NULL) {
             return;
         }
 
         hs = pcmk__assert_alloc(1, sizeof(struct handshake_data_s));
         hs->lrmd = lrmd;
         hs->start_time = time(NULL);
         hs->timeout_sec = TLS_HANDSHAKE_TIMEOUT;
 
         native->handshake_trigger = mainloop_add_trigger(G_PRIORITY_LOW, try_handshake_cb, hs);
         mainloop_set_trigger(native->handshake_trigger);
 
     } else if (rc == pcmk_rc_ok) {
         tls_handshake_succeeded(lrmd);
 
     } else {
         tls_handshake_failed(lrmd, tls_rc, rc);
     }
 }
 
 static int
 lrmd_tls_connect_async(lrmd_t * lrmd, int timeout /*ms */ )
 {
     int rc = pcmk_rc_ok;
     int timer_id = 0;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     native->sock = -1;
     rc = pcmk__connect_remote(native->server, native->port, timeout, &timer_id,
                               &(native->sock), lrmd, lrmd_tcp_connect_cb);
     if (rc != pcmk_rc_ok) {
         crm_warn("Pacemaker Remote connection to %s:%d failed: %s "
                  QB_XS " rc=%d",
                  native->server, native->port, pcmk_rc_str(rc), rc);
         return rc;
     }
     native->async_timer = timer_id;
     return rc;
 }
 
 static int
 lrmd_tls_connect(lrmd_t * lrmd, int *fd)
 {
     int rc = pcmk_rc_ok;
     bool use_cert = pcmk__x509_enabled();
     lrmd_private_t *native = lrmd->lrmd_private;
 
     native->sock = -1;
     rc = pcmk__connect_remote(native->server, native->port, 0, NULL,
                               &(native->sock), NULL, NULL);
     if (rc != pcmk_rc_ok) {
         crm_warn("Pacemaker Remote connection to %s:%d failed: %s "
                  QB_XS " rc=%d",
                  native->server, native->port, pcmk_rc_str(rc), rc);
         lrmd_tls_connection_destroy(lrmd);
         return ENOTCONN;
     }
 
     if (native->tls == NULL) {
         rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK);
 
         if (rc != pcmk_rc_ok) {
             lrmd_tls_connection_destroy(lrmd);
             return rc;
         }
     }
 
     if (!use_cert) {
         gnutls_datum_t psk_key = { NULL, 0 };
 
         rc = lrmd__init_remote_key(&psk_key);
         if (rc != pcmk_rc_ok) {
             lrmd_tls_connection_destroy(lrmd);
             return rc;
         }
 
         pcmk__tls_add_psk_key(native->tls, &psk_key);
         gnutls_free(psk_key.data);
     }
 
     native->remote->tls_session = pcmk__new_tls_session(native->tls, native->sock);
     if (native->remote->tls_session == NULL) {
         lrmd_tls_connection_destroy(lrmd);
         return EPROTO;
     }
 
     if (tls_client_handshake(lrmd) != pcmk_rc_ok) {
         return EKEYREJECTED;
     }
 
     crm_info("Client TLS connection established with Pacemaker Remote server %s:%d", native->server,
              native->port);
 
     if (fd) {
         *fd = native->sock;
     } else {
         rc = add_tls_to_mainloop(lrmd, false);
     }
     return rc;
 }
 
 static int
 lrmd_api_connect(lrmd_t * lrmd, const char *name, int *fd)
 {
     int rc = -ENOTCONN;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     switch (native->type) {
         case pcmk__client_ipc:
             rc = lrmd_ipc_connect(lrmd, fd);
             break;
         case pcmk__client_tls:
             rc = lrmd_tls_connect(lrmd, fd);
             rc = pcmk_rc2legacy(rc);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             rc = -EPROTONOSUPPORT;
     }
 
     if (rc == pcmk_ok) {
         rc = lrmd_handshake(lrmd, name);
         rc = pcmk_rc2legacy(rc);
     }
 
     return rc;
 }
 
 static int
 lrmd_api_connect_async(lrmd_t * lrmd, const char *name, int timeout)
 {
     int rc = pcmk_ok;
     lrmd_private_t *native = lrmd->lrmd_private;
 
     CRM_CHECK(native && native->callback, return -EINVAL);
 
     switch (native->type) {
         case pcmk__client_ipc:
             /* fake async connection with ipc.  it should be fast
              * enough that we gain very little from async */
             rc = lrmd_api_connect(lrmd, name, NULL);
             if (!rc) {
                 report_async_connection_result(lrmd, rc);
             }
             break;
         case pcmk__client_tls:
             rc = lrmd_tls_connect_async(lrmd, timeout);
             rc = pcmk_rc2legacy(rc);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             rc = -EPROTONOSUPPORT;
     }
 
     return rc;
 }
 
 static void
 lrmd_ipc_disconnect(lrmd_t * lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->source != NULL) {
         /* Attached to mainloop */
         mainloop_del_ipc_client(native->source);
         native->source = NULL;
         native->ipc = NULL;
 
     } else if (native->ipc) {
         /* Not attached to mainloop */
         crm_ipc_t *ipc = native->ipc;
 
         native->ipc = NULL;
         crm_ipc_close(ipc);
         crm_ipc_destroy(ipc);
     }
 }
 
 static void
 lrmd_tls_disconnect(lrmd_t * lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->remote->tls_session) {
         gnutls_bye(native->remote->tls_session, GNUTLS_SHUT_RDWR);
         gnutls_deinit(native->remote->tls_session);
         native->remote->tls_session = NULL;
     }
 
     if (native->async_timer) {
         g_source_remove(native->async_timer);
         native->async_timer = 0;
     }
 
     if (native->source != NULL) {
         /* Attached to mainloop */
         mainloop_del_ipc_client(native->source);
         native->source = NULL;
 
     } else if (native->sock >= 0) {
         close(native->sock);
         native->sock = -1;
     }
 
     if (native->pending_notify) {
         g_list_free_full(native->pending_notify, lrmd_free_xml);
         native->pending_notify = NULL;
     }
 }
 
 static int
 lrmd_api_disconnect(lrmd_t * lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
     int rc = pcmk_ok;
 
     switch (native->type) {
         case pcmk__client_ipc:
             crm_debug("Disconnecting from local executor");
             lrmd_ipc_disconnect(lrmd);
             break;
         case pcmk__client_tls:
             crm_debug("Disconnecting from remote executor on %s",
                       native->remote_nodename);
             lrmd_tls_disconnect(lrmd);
             break;
         default:
             crm_err("Unsupported executor connection type (bug?): %d",
                     native->type);
             rc = -EPROTONOSUPPORT;
     }
 
     free(native->token);
     native->token = NULL;
 
     free(native->peer_version);
     native->peer_version = NULL;
     return rc;
 }
 
 static int
 lrmd_api_register_rsc(lrmd_t * lrmd,
                       const char *rsc_id,
                       const char *class,
                       const char *provider, const char *type, enum lrmd_call_options options)
 {
     int rc = pcmk_ok;
     xmlNode *data = NULL;
 
     if (!class || !type || !rsc_id) {
         return -EINVAL;
     }
     if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)
         && (provider == NULL)) {
         return -EINVAL;
     }
 
     data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     crm_xml_add(data, PCMK__XA_LRMD_CLASS, class);
     crm_xml_add(data, PCMK__XA_LRMD_PROVIDER, provider);
     crm_xml_add(data, PCMK__XA_LRMD_TYPE, type);
     rc = lrmd_send_command(lrmd, LRMD_OP_RSC_REG, data, NULL, 0, options, true);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 static int
 lrmd_api_unregister_rsc(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
 {
     int rc = pcmk_ok;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     rc = lrmd_send_command(lrmd, LRMD_OP_RSC_UNREG, data, NULL, 0, options, true);
     pcmk__xml_free(data);
 
     return rc;
 }
 
 lrmd_rsc_info_t *
 lrmd_new_rsc_info(const char *rsc_id, const char *standard,
                   const char *provider, const char *type)
 {
     lrmd_rsc_info_t *rsc_info = pcmk__assert_alloc(1, sizeof(lrmd_rsc_info_t));
 
     rsc_info->id = pcmk__str_copy(rsc_id);
     rsc_info->standard = pcmk__str_copy(standard);
     rsc_info->provider = pcmk__str_copy(provider);
     rsc_info->type = pcmk__str_copy(type);
     return rsc_info;
 }
 
 lrmd_rsc_info_t *
 lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info)
 {
     return lrmd_new_rsc_info(rsc_info->id, rsc_info->standard,
                              rsc_info->provider, rsc_info->type);
 }
 
 void
 lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info)
 {
     if (!rsc_info) {
         return;
     }
     free(rsc_info->id);
     free(rsc_info->type);
     free(rsc_info->standard);
     free(rsc_info->provider);
     free(rsc_info);
 }
 
 static lrmd_rsc_info_t *
 lrmd_api_get_rsc_info(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
 {
     lrmd_rsc_info_t *rsc_info = NULL;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
     xmlNode *output = NULL;
     const char *class = NULL;
     const char *provider = NULL;
     const char *type = NULL;
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     lrmd_send_command(lrmd, LRMD_OP_RSC_INFO, data, &output, 0, options, true);
     pcmk__xml_free(data);
 
     if (!output) {
         return NULL;
     }
 
     class = crm_element_value(output, PCMK__XA_LRMD_CLASS);
     provider = crm_element_value(output, PCMK__XA_LRMD_PROVIDER);
     type = crm_element_value(output, PCMK__XA_LRMD_TYPE);
 
     if (!class || !type) {
         pcmk__xml_free(output);
         return NULL;
     } else if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)
                && !provider) {
         pcmk__xml_free(output);
         return NULL;
     }
 
     rsc_info = lrmd_new_rsc_info(rsc_id, class, provider, type);
     pcmk__xml_free(output);
     return rsc_info;
 }
 
 void
 lrmd_free_op_info(lrmd_op_info_t *op_info)
 {
     if (op_info) {
         free(op_info->rsc_id);
         free(op_info->action);
         free(op_info->interval_ms_s);
         free(op_info->timeout_ms_s);
         free(op_info);
     }
 }
 
 static int
 lrmd_api_get_recurring_ops(lrmd_t *lrmd, const char *rsc_id, int timeout_ms,
                            enum lrmd_call_options options, GList **output)
 {
     xmlNode *data = NULL;
     xmlNode *output_xml = NULL;
     int rc = pcmk_ok;
 
     if (output == NULL) {
         return -EINVAL;
     }
     *output = NULL;
 
     // Send request
     if (rsc_id) {
         data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
         crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
         crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     }
     rc = lrmd_send_command(lrmd, LRMD_OP_GET_RECURRING, data, &output_xml,
                            timeout_ms, options, true);
     if (data) {
         pcmk__xml_free(data);
     }
 
     // Process reply
     if ((rc != pcmk_ok) || (output_xml == NULL)) {
         return rc;
     }
     for (const xmlNode *rsc_xml = pcmk__xe_first_child(output_xml,
                                                        PCMK__XE_LRMD_RSC, NULL,
                                                        NULL);
          (rsc_xml != NULL) && (rc == pcmk_ok);
          rsc_xml = pcmk__xe_next(rsc_xml, PCMK__XE_LRMD_RSC)) {
 
         rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
         if (rsc_id == NULL) {
             crm_err("Could not parse recurring operation information from executor");
             continue;
         }
         for (const xmlNode *op_xml = pcmk__xe_first_child(rsc_xml,
                                                           PCMK__XE_LRMD_RSC_OP,
                                                           NULL, NULL);
              op_xml != NULL;
              op_xml = pcmk__xe_next(op_xml, PCMK__XE_LRMD_RSC_OP)) {
 
             lrmd_op_info_t *op_info = calloc(1, sizeof(lrmd_op_info_t));
 
             if (op_info == NULL) {
                 rc = -ENOMEM;
                 break;
             }
             op_info->rsc_id = strdup(rsc_id);
             op_info->action = crm_element_value_copy(op_xml,
                                                      PCMK__XA_LRMD_RSC_ACTION);
             op_info->interval_ms_s =
                 crm_element_value_copy(op_xml, PCMK__XA_LRMD_RSC_INTERVAL);
             op_info->timeout_ms_s =
                 crm_element_value_copy(op_xml, PCMK__XA_LRMD_TIMEOUT);
             *output = g_list_prepend(*output, op_info);
         }
     }
     pcmk__xml_free(output_xml);
     return rc;
 }
 
 
 static void
 lrmd_api_set_callback(lrmd_t * lrmd, lrmd_event_callback callback)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     native->callback = callback;
 }
 
 void
 lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg))
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     native->proxy_callback = callback;
     native->proxy_callback_userdata = userdata;
 }
 
 void
 lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->proxy_callback) {
         crm_log_xml_trace(msg, "PROXY_INBOUND");
         native->proxy_callback(lrmd, native->proxy_callback_userdata, msg);
     }
 }
 
 int
 lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg)
 {
     if (lrmd == NULL) {
         return -ENOTCONN;
     }
     crm_xml_add(msg, PCMK__XA_LRMD_OP, CRM_OP_IPC_FWD);
 
     crm_log_xml_trace(msg, "PROXY_OUTBOUND");
     return lrmd_send_xml_no_reply(lrmd, msg);
 }
 
 static int
 stonith_get_metadata(const char *provider, const char *type, char **output)
 {
     int rc = pcmk_ok;
     stonith_t *stonith_api = stonith__api_new();
 
     if (stonith_api == NULL) {
         crm_err("Could not get fence agent meta-data: API memory allocation failed");
         return -ENOMEM;
     }
 
     rc = stonith_api->cmds->metadata(stonith_api, st_opt_sync_call, type,
                                      provider, output, 0);
     if ((rc == pcmk_ok) && (*output == NULL)) {
         rc = -EIO;
     }
     stonith_api->cmds->free(stonith_api);
     return rc;
 }
 
 static int
 lrmd_api_get_metadata(lrmd_t *lrmd, const char *standard, const char *provider,
                       const char *type, char **output,
                       enum lrmd_call_options options)
 {
     return lrmd->cmds->get_metadata_params(lrmd, standard, provider, type,
                                            output, options, NULL);
 }
 
 static int
 lrmd_api_get_metadata_params(lrmd_t *lrmd, const char *standard,
                              const char *provider, const char *type,
                              char **output, enum lrmd_call_options options,
                              lrmd_key_value_t *params)
 {
     svc_action_t *action = NULL;
     GHashTable *params_table = NULL;
 
     if (!standard || !type) {
         lrmd_key_value_freeall(params);
         return -EINVAL;
     }
 
     if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         lrmd_key_value_freeall(params);
         return stonith_get_metadata(provider, type, output);
     }
 
     params_table = pcmk__strkey_table(free, free);
     for (const lrmd_key_value_t *param = params; param; param = param->next) {
         pcmk__insert_dup(params_table, param->key, param->value);
     }
     action = services__create_resource_action(type, standard, provider, type,
                                               PCMK_ACTION_META_DATA, 0,
                                               PCMK_DEFAULT_ACTION_TIMEOUT_MS,
                                               params_table, 0);
     lrmd_key_value_freeall(params);
 
     if (action == NULL) {
         return -ENOMEM;
     }
     if (action->rc != PCMK_OCF_UNKNOWN) {
         services_action_free(action);
         return -EINVAL;
     }
 
     if (!services_action_sync(action)) {
         crm_err("Failed to retrieve meta-data for %s:%s:%s",
                 standard, provider, type);
         services_action_free(action);
         return -EIO;
     }
 
     if (!action->stdout_data) {
         crm_err("Failed to receive meta-data for %s:%s:%s",
                 standard, provider, type);
         services_action_free(action);
         return -EIO;
     }
 
     *output = strdup(action->stdout_data);
     services_action_free(action);
 
     return pcmk_ok;
 }
 
 static int
 lrmd_api_exec(lrmd_t *lrmd, const char *rsc_id, const char *action,
               const char *userdata, guint interval_ms,
               int timeout,      /* ms */
               int start_delay,  /* ms */
               enum lrmd_call_options options, lrmd_key_value_t * params)
 {
     int rc = pcmk_ok;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
     xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
     lrmd_key_value_t *tmp = NULL;
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ACTION, action);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_USERDATA_STR, userdata);
     crm_xml_add_ms(data, PCMK__XA_LRMD_RSC_INTERVAL, interval_ms);
     crm_xml_add_int(data, PCMK__XA_LRMD_TIMEOUT, timeout);
     crm_xml_add_int(data, PCMK__XA_LRMD_RSC_START_DELAY, start_delay);
 
     for (tmp = params; tmp; tmp = tmp->next) {
         hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
     }
 
     rc = lrmd_send_command(lrmd, LRMD_OP_RSC_EXEC, data, NULL, timeout, options, true);
     pcmk__xml_free(data);
 
     lrmd_key_value_freeall(params);
     return rc;
 }
 
 /* timeout is in ms */
 static int
 lrmd_api_exec_alert(lrmd_t *lrmd, const char *alert_id, const char *alert_path,
                     int timeout, lrmd_key_value_t *params)
 {
     int rc = pcmk_ok;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_ALERT);
     xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
     lrmd_key_value_t *tmp = NULL;
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_ALERT_ID, alert_id);
     crm_xml_add(data, PCMK__XA_LRMD_ALERT_PATH, alert_path);
     crm_xml_add_int(data, PCMK__XA_LRMD_TIMEOUT, timeout);
 
     for (tmp = params; tmp; tmp = tmp->next) {
         hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
     }
 
     rc = lrmd_send_command(lrmd, LRMD_OP_ALERT_EXEC, data, NULL, timeout,
                            lrmd_opt_notify_orig_only, true);
     pcmk__xml_free(data);
 
     lrmd_key_value_freeall(params);
     return rc;
 }
 
 static int
 lrmd_api_cancel(lrmd_t *lrmd, const char *rsc_id, const char *action,
                 guint interval_ms)
 {
     int rc = pcmk_ok;
     xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
 
     crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ACTION, action);
     crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
     crm_xml_add_ms(data, PCMK__XA_LRMD_RSC_INTERVAL, interval_ms);
     rc = lrmd_send_command(lrmd, LRMD_OP_RSC_CANCEL, data, NULL, 0, 0, true);
     pcmk__xml_free(data);
     return rc;
 }
 
 static int
 list_stonith_agents(lrmd_list_t ** resources)
 {
     int rc = 0;
     stonith_t *stonith_api = stonith__api_new();
     stonith_key_value_t *stonith_resources = NULL;
     stonith_key_value_t *dIter = NULL;
 
     if (stonith_api == NULL) {
         crm_err("Could not list fence agents: API memory allocation failed");
         return -ENOMEM;
     }
     stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL,
                                    &stonith_resources, 0);
     stonith_api->cmds->free(stonith_api);
 
     for (dIter = stonith_resources; dIter; dIter = dIter->next) {
         rc++;
         if (resources) {
             *resources = lrmd_list_add(*resources, dIter->value);
         }
     }
 
-    stonith_key_value_freeall(stonith_resources, 1, 0);
+    stonith__key_value_freeall(stonith_resources, true, false);
     return rc;
 }
 
 static int
 lrmd_api_list_agents(lrmd_t * lrmd, lrmd_list_t ** resources, const char *class,
                      const char *provider)
 {
     int rc = 0;
     int stonith_count = 0; // Initially, whether to include stonith devices
 
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
         stonith_count = 1;
 
     } else {
         GList *gIter = NULL;
         GList *agents = resources_list_agents(class, provider);
 
         for (gIter = agents; gIter != NULL; gIter = gIter->next) {
             *resources = lrmd_list_add(*resources, (const char *)gIter->data);
             rc++;
         }
         g_list_free_full(agents, free);
 
         if (!class) {
             stonith_count = 1;
         }
     }
 
     if (stonith_count) {
         // Now, if stonith devices are included, how many there are
         stonith_count = list_stonith_agents(resources);
         if (stonith_count > 0) {
             rc += stonith_count;
         }
     }
     if (rc == 0) {
         crm_notice("No agents found for class %s", class);
         rc = -EPROTONOSUPPORT;
     }
     return rc;
 }
 
 static bool
 does_provider_have_agent(const char *agent, const char *provider, const char *class)
 {
     bool found = false;
     GList *agents = NULL;
     GList *gIter2 = NULL;
 
     agents = resources_list_agents(class, provider);
     for (gIter2 = agents; gIter2 != NULL; gIter2 = gIter2->next) {
         if (pcmk__str_eq(agent, gIter2->data, pcmk__str_casei)) {
             found = true;
         }
     }
     g_list_free_full(agents, free);
     return found;
 }
 
 static int
 lrmd_api_list_ocf_providers(lrmd_t * lrmd, const char *agent, lrmd_list_t ** providers)
 {
     int rc = pcmk_ok;
     char *provider = NULL;
     GList *ocf_providers = NULL;
     GList *gIter = NULL;
 
     ocf_providers = resources_list_providers(PCMK_RESOURCE_CLASS_OCF);
 
     for (gIter = ocf_providers; gIter != NULL; gIter = gIter->next) {
         provider = gIter->data;
         if (!agent || does_provider_have_agent(agent, provider,
                                                PCMK_RESOURCE_CLASS_OCF)) {
             *providers = lrmd_list_add(*providers, (const char *)gIter->data);
             rc++;
         }
     }
 
     g_list_free_full(ocf_providers, free);
     return rc;
 }
 
 static int
 lrmd_api_list_standards(lrmd_t * lrmd, lrmd_list_t ** supported)
 {
     int rc = 0;
     GList *standards = NULL;
     GList *gIter = NULL;
 
     standards = resources_list_standards();
 
     for (gIter = standards; gIter != NULL; gIter = gIter->next) {
         *supported = lrmd_list_add(*supported, (const char *)gIter->data);
         rc++;
     }
 
     if (list_stonith_agents(NULL) > 0) {
         *supported = lrmd_list_add(*supported, PCMK_RESOURCE_CLASS_STONITH);
         rc++;
     }
 
     g_list_free_full(standards, free);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Create an executor API object
  *
  * \param[out] api       Will be set to newly created API object (it is the
  *                       caller's responsibility to free this value with
  *                       lrmd_api_delete() if this function succeeds)
  * \param[in]  nodename  If the object will be used for a remote connection,
  *                       the node name to use in cluster for remote executor
  * \param[in]  server    If the object will be used for a remote connection,
  *                       the resolvable host name to connect to
  * \param[in]  port      If the object will be used for a remote connection,
  *                       port number on \p server to connect to
  *
  * \return Standard Pacemaker return code
  * \note If the caller leaves one of \p nodename or \p server NULL, the other's
  *       value will be used for both. If the caller leaves both NULL, an API
  *       object will be created for a local executor connection.
  */
 int
 lrmd__new(lrmd_t **api, const char *nodename, const char *server, int port)
 {
     lrmd_private_t *pvt = NULL;
 
     if (api == NULL) {
         return EINVAL;
     }
     *api = NULL;
 
     // Allocate all memory needed
 
     *api = calloc(1, sizeof(lrmd_t));
     if (*api == NULL) {
         return ENOMEM;
     }
 
     pvt = calloc(1, sizeof(lrmd_private_t));
     if (pvt == NULL) {
         lrmd_api_delete(*api);
         *api = NULL;
         return ENOMEM;
     }
     (*api)->lrmd_private = pvt;
 
     // @TODO Do we need to do this for local connections?
     pvt->remote = calloc(1, sizeof(pcmk__remote_t));
 
     (*api)->cmds = calloc(1, sizeof(lrmd_api_operations_t));
 
     if ((pvt->remote == NULL) || ((*api)->cmds == NULL)) {
         lrmd_api_delete(*api);
         *api = NULL;
         return ENOMEM;
     }
 
     // Set methods
     (*api)->cmds->connect = lrmd_api_connect;
     (*api)->cmds->connect_async = lrmd_api_connect_async;
     (*api)->cmds->is_connected = lrmd_api_is_connected;
     (*api)->cmds->poke_connection = lrmd_api_poke_connection;
     (*api)->cmds->disconnect = lrmd_api_disconnect;
     (*api)->cmds->register_rsc = lrmd_api_register_rsc;
     (*api)->cmds->unregister_rsc = lrmd_api_unregister_rsc;
     (*api)->cmds->get_rsc_info = lrmd_api_get_rsc_info;
     (*api)->cmds->get_recurring_ops = lrmd_api_get_recurring_ops;
     (*api)->cmds->set_callback = lrmd_api_set_callback;
     (*api)->cmds->get_metadata = lrmd_api_get_metadata;
     (*api)->cmds->exec = lrmd_api_exec;
     (*api)->cmds->cancel = lrmd_api_cancel;
     (*api)->cmds->list_agents = lrmd_api_list_agents;
     (*api)->cmds->list_ocf_providers = lrmd_api_list_ocf_providers;
     (*api)->cmds->list_standards = lrmd_api_list_standards;
     (*api)->cmds->exec_alert = lrmd_api_exec_alert;
     (*api)->cmds->get_metadata_params = lrmd_api_get_metadata_params;
 
     if ((nodename == NULL) && (server == NULL)) {
         pvt->type = pcmk__client_ipc;
     } else {
         if (nodename == NULL) {
             nodename = server;
         } else if (server == NULL) {
             server = nodename;
         }
         pvt->type = pcmk__client_tls;
         pvt->remote_nodename = strdup(nodename);
         pvt->server = strdup(server);
         if ((pvt->remote_nodename == NULL) || (pvt->server == NULL)) {
             lrmd_api_delete(*api);
             *api = NULL;
             return ENOMEM;
         }
         pvt->port = port;
         if (pvt->port == 0) {
             pvt->port = crm_default_remote_port();
         }
     }
     return pcmk_rc_ok;
 }
 
 lrmd_t *
 lrmd_api_new(void)
 {
     lrmd_t *api = NULL;
 
     pcmk__assert(lrmd__new(&api, NULL, NULL, 0) == pcmk_rc_ok);
     return api;
 }
 
 lrmd_t *
 lrmd_remote_api_new(const char *nodename, const char *server, int port)
 {
     lrmd_t *api = NULL;
 
     pcmk__assert(lrmd__new(&api, nodename, server, port) == pcmk_rc_ok);
     return api;
 }
 
 void
 lrmd_api_delete(lrmd_t * lrmd)
 {
     if (lrmd == NULL) {
         return;
     }
     if (lrmd->cmds != NULL) { // Never NULL, but make static analysis happy
         if (lrmd->cmds->disconnect != NULL) { // Also never really NULL
             lrmd->cmds->disconnect(lrmd); // No-op if already disconnected
         }
         free(lrmd->cmds);
     }
     if (lrmd->lrmd_private != NULL) {
         lrmd_private_t *native = lrmd->lrmd_private;
 
         free(native->server);
         free(native->remote_nodename);
         free(native->remote);
         free(native->token);
         free(native->peer_version);
         free(lrmd->lrmd_private);
     }
     free(lrmd);
 }
 
 struct metadata_cb {
      void (*callback)(int pid, const pcmk__action_result_t *result,
                       void *user_data);
      void *user_data;
 };
 
 /*!
  * \internal
  * \brief Process asynchronous metadata completion
  *
  * \param[in,out] action  Metadata action that completed
  */
 static void
 metadata_complete(svc_action_t *action)
 {
     struct metadata_cb *metadata_cb = (struct metadata_cb *) action->cb_data;
     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
     services__copy_result(action, &result);
     pcmk__set_result_output(&result, action->stdout_data, action->stderr_data);
 
     metadata_cb->callback(0, &result, metadata_cb->user_data);
     result.action_stdout = NULL; // Prevent free, because action owns it
     result.action_stderr = NULL; // Prevent free, because action owns it
     pcmk__reset_result(&result);
     free(metadata_cb);
 }
 
 /*!
  * \internal
  * \brief Retrieve agent metadata asynchronously
  *
  * \param[in]     rsc        Resource agent specification
  * \param[in]     callback   Function to call with result (this will always be
  *                           called, whether by this function directly or later
  *                           via the main loop, and on success the metadata will
  *                           be in its result argument's action_stdout)
  * \param[in,out] user_data  User data to pass to callback
  *
  * \return Standard Pacemaker return code
  * \note This function is not a lrmd_api_operations_t method because it does not
  *       need an lrmd_t object and does not go through the executor, but
  *       executes the agent directly.
  */
 int
 lrmd__metadata_async(const lrmd_rsc_info_t *rsc,
                      void (*callback)(int pid,
                                       const pcmk__action_result_t *result,
                                       void *user_data),
                      void *user_data)
 {
     svc_action_t *action = NULL;
     struct metadata_cb *metadata_cb = NULL;
     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 
     CRM_CHECK(callback != NULL, return EINVAL);
 
     if ((rsc == NULL) || (rsc->standard == NULL) || (rsc->type == NULL)) {
         pcmk__set_result(&result, PCMK_OCF_NOT_CONFIGURED,
                          PCMK_EXEC_ERROR_FATAL,
                          "Invalid resource specification");
         callback(0, &result, user_data);
         pcmk__reset_result(&result);
         return EINVAL;
     }
 
     if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_STONITH) == 0) {
         return stonith__metadata_async(rsc->type,
                                        pcmk__timeout_ms2s(PCMK_DEFAULT_ACTION_TIMEOUT_MS),
                                        callback, user_data);
     }
 
     action = services__create_resource_action(pcmk__s(rsc->id, rsc->type),
                                               rsc->standard, rsc->provider,
                                               rsc->type,
                                               PCMK_ACTION_META_DATA, 0,
                                               PCMK_DEFAULT_ACTION_TIMEOUT_MS,
                                               NULL, 0);
     if (action == NULL) {
         pcmk__set_result(&result, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
                          "Out of memory");
         callback(0, &result, user_data);
         pcmk__reset_result(&result);
         return ENOMEM;
     }
     if (action->rc != PCMK_OCF_UNKNOWN) {
         services__copy_result(action, &result);
         callback(0, &result, user_data);
         pcmk__reset_result(&result);
         services_action_free(action);
         return EINVAL;
     }
 
     action->cb_data = calloc(1, sizeof(struct metadata_cb));
     if (action->cb_data == NULL) {
         services_action_free(action);
         pcmk__set_result(&result, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
                          "Out of memory");
         callback(0, &result, user_data);
         pcmk__reset_result(&result);
         return ENOMEM;
     }
 
     metadata_cb = (struct metadata_cb *) action->cb_data;
     metadata_cb->callback = callback;
     metadata_cb->user_data = user_data;
     if (!services_action_async(action, metadata_complete)) {
         services_action_free(action);
         return pcmk_rc_error; // @TODO Derive from action->rc and ->status
     }
 
     // The services library has taken responsibility for action
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Set the result of an executor event
  *
  * \param[in,out] event        Executor event to set
  * \param[in]     rc           OCF exit status of event
  * \param[in]     op_status    Executor status of event
  * \param[in]     exit_reason  Human-friendly description of event
  */
 void
 lrmd__set_result(lrmd_event_data_t *event, enum ocf_exitcode rc, int op_status,
                  const char *exit_reason)
 {
     if (event == NULL) {
         return;
     }
 
     event->rc = rc;
     event->op_status = op_status;
 
     // lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
     pcmk__str_update((char **) &event->exit_reason, exit_reason);
 }
 
 /*!
  * \internal
  * \brief Clear an executor event's exit reason, output, and error output
  *
  * \param[in,out] event  Executor event to reset
  */
 void
 lrmd__reset_result(lrmd_event_data_t *event)
 {
     if (event == NULL) {
         return;
     }
 
     free((void *) event->exit_reason);
     event->exit_reason = NULL;
 
     free((void *) event->output);
     event->output = NULL;
 }
 
 /*!
  * \internal
  * \brief Get the uptime of a remote resource connection
  *
  * When the cluster connects to a remote resource, part of that resource's
  * handshake includes the uptime of the remote resource's connection.  This
  * uptime is stored in the lrmd_t object.
  *
  * \return The connection's uptime, or -1 if unknown
  */
 time_t
 lrmd__uptime(lrmd_t *lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->remote == NULL) {
         return -1;
     } else {
         return native->remote->uptime;
     }
 }
 
 const char *
 lrmd__node_start_state(lrmd_t *lrmd)
 {
     lrmd_private_t *native = lrmd->lrmd_private;
 
     if (native->remote == NULL) {
         return NULL;
     } else {
         return native->remote->start_state;
     }
 }
diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c
index 5bc2ce6ec6..fc389c2e57 100644
--- a/lib/pacemaker/pcmk_fence.c
+++ b/lib/pacemaker/pcmk_fence.c
@@ -1,677 +1,677 @@
 /*
  * Copyright 2009-2025 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 <crm/common/mainloop.h>
 #include <crm/common/results.h>
 #include <crm/common/output.h>
 #include <crm/common/output_internal.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>   // stonith__*
 
 #include <glib.h>
 #include <libxml/tree.h>
 #include <pacemaker.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 static const int st_opts = st_opt_sync_call|st_opt_allow_self_fencing;
 
 static GMainLoop *mainloop = NULL;
 
 static struct {
     stonith_t *st;
     const char *target;
     const char *action;
     char *name;
     unsigned int timeout;
     unsigned int tolerance;
     int delay;
     pcmk__action_result_t result;
 } async_fence_data = { NULL, };
 
 static int
 handle_level(stonith_t *st, const char *target, int fence_level, GList *devices,
              bool added)
 {
     const char *node = NULL;
     const char *pattern = NULL;
     const char *name = NULL;
     char *value = NULL;
     int rc = pcmk_rc_ok;
 
     if (target == NULL) {
         // Not really possible, but makes static analysis happy
         return EINVAL;
     }
 
     /* Determine if targeting by attribute, node name pattern or node name */
     value = strchr(target, '=');
     if (value != NULL)  {
         name = target;
         *value++ = '\0';
     } else if (*target == '@') {
         pattern = target + 1;
     } else {
         node = target;
     }
 
     /* Register or unregister level as appropriate */
     if (added) {
         stonith_key_value_t *kvs = NULL;
 
         for (GList *iter = devices; iter != NULL; iter = iter->next) {
             kvs = stonith__key_value_add(kvs, NULL, iter->data);
         }
 
         rc = st->cmds->register_level_full(st, st_opts, node, pattern, name,
                                            value, fence_level, kvs);
-        stonith_key_value_freeall(kvs, 0, 1);
+        stonith__key_value_freeall(kvs, false, true);
     } else {
         rc = st->cmds->remove_level_full(st, st_opts, node, pattern,
                                          name, value, fence_level);
     }
 
     return pcmk_legacy2rc(rc);
 }
 
 static stonith_history_t *
 reduce_fence_history(stonith_history_t *history)
 {
     stonith_history_t *new, *hp, *np;
 
     if (!history) {
         return history;
     }
 
     new = history;
     hp = new->next;
     new->next = NULL;
 
     while (hp) {
         stonith_history_t *hp_next = hp->next;
 
         hp->next = NULL;
 
         for (np = new; ; np = np->next) {
             if ((hp->state == st_done) || (hp->state == st_failed)) {
                 /* action not in progress */
                 if (pcmk__str_eq(hp->target, np->target, pcmk__str_casei)
                     && pcmk__str_eq(hp->action, np->action, pcmk__str_none)
                     && (hp->state == np->state)
                     && ((hp->state == st_done)
                         || pcmk__str_eq(hp->delegate, np->delegate,
                                         pcmk__str_casei))) {
                         /* purge older hp */
                         stonith_history_free(hp);
                         break;
                 }
             }
 
             if (!np->next) {
                 np->next = hp;
                 break;
             }
         }
         hp = hp_next;
     }
 
     return new;
 }
 
 static void
 notify_callback(stonith_t * st, stonith_event_t * e)
 {
     if (pcmk__str_eq(async_fence_data.target, e->target, pcmk__str_casei)
         && pcmk__str_eq(async_fence_data.action, e->action, pcmk__str_none)) {
 
         pcmk__set_result(&async_fence_data.result,
                          stonith__event_exit_status(e),
                          stonith__event_execution_status(e),
                          stonith__event_exit_reason(e));
         g_main_loop_quit(mainloop);
     }
 }
 
 static void
 fence_callback(stonith_t * stonith, stonith_callback_data_t * data)
 {
     pcmk__set_result(&async_fence_data.result, stonith__exit_status(data),
                      stonith__execution_status(data),
                      stonith__exit_reason(data));
     g_main_loop_quit(mainloop);
 }
 
 static gboolean
 async_fence_helper(gpointer user_data)
 {
     stonith_t *st = async_fence_data.st;
     int call_id = 0;
     int rc = stonith_api_connect_retry(st, async_fence_data.name, 10);
     int timeout = 0;
 
     if (rc != pcmk_ok) {
         g_main_loop_quit(mainloop);
         pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR,
                          PCMK_EXEC_NOT_CONNECTED, pcmk_strerror(rc));
         return TRUE;
     }
 
     st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_FENCE,
                                     notify_callback);
 
     call_id = st->cmds->fence_with_delay(st,
                                          st_opt_allow_self_fencing,
                                          async_fence_data.target,
                                          async_fence_data.action,
                                          pcmk__timeout_ms2s(async_fence_data.timeout),
                                          pcmk__timeout_ms2s(async_fence_data.tolerance),
                                          async_fence_data.delay);
 
     if (call_id < 0) {
         g_main_loop_quit(mainloop);
         pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR,
                          PCMK_EXEC_ERROR, pcmk_strerror(call_id));
         return TRUE;
     }
 
     timeout = pcmk__timeout_ms2s(async_fence_data.timeout);
     if (async_fence_data.delay > 0) {
         timeout += async_fence_data.delay;
     }
     st->cmds->register_callback(st, call_id, timeout, st_opt_timeout_updates,
                                 NULL, "callback", fence_callback);
     return TRUE;
 }
 
 int
 pcmk__request_fencing(stonith_t *st, const char *target, const char *action,
                       const char *name, unsigned int timeout,
                       unsigned int tolerance, int delay, char **reason)
 {
     crm_trigger_t *trig;
     int rc = pcmk_rc_ok;
 
     async_fence_data.st = st;
     async_fence_data.name = strdup(name);
     async_fence_data.target = target;
     async_fence_data.action = action;
     async_fence_data.timeout = timeout;
     async_fence_data.tolerance = tolerance;
     async_fence_data.delay = delay;
     pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR, PCMK_EXEC_UNKNOWN,
                      NULL);
 
     trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL);
     mainloop_set_trigger(trig);
 
     mainloop = g_main_loop_new(NULL, FALSE);
     g_main_loop_run(mainloop);
 
     free(async_fence_data.name);
 
     if (reason != NULL) {
         // Give the caller ownership of the exit reason
         *reason = async_fence_data.result.exit_reason;
         async_fence_data.result.exit_reason = NULL;
     }
     rc = stonith__result2rc(&async_fence_data.result);
     pcmk__reset_result(&async_fence_data.result);
     return rc;
 }
 
 int
 pcmk_request_fencing(xmlNodePtr *xml, const char *target, const char *action,
                      const char *name, unsigned int timeout,
                      unsigned int tolerance, int delay, char **reason)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__request_fencing(st, target, action, name, timeout, tolerance,
                                delay, reason);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_history(pcmk__output_t *out, stonith_t *st, const char *target,
                     unsigned int timeout, int verbose, bool broadcast,
                     bool cleanup)
 {
     stonith_history_t *history = NULL;
     stonith_history_t *latest = NULL;
     int rc = pcmk_rc_ok;
     int opts = 0;
 
     if (cleanup) {
         out->info(out, "cleaning up fencing-history%s%s",
                   target ? " for node " : "", target ? target : "");
     }
 
     if (broadcast) {
         out->info(out, "gather fencing-history from all nodes");
     }
 
     stonith__set_call_options(opts, target, st_opts);
 
     if (cleanup) {
         stonith__set_call_options(opts, target, st_opt_cleanup);
     }
 
     if (broadcast) {
         stonith__set_call_options(opts, target, st_opt_broadcast);
     }
 
     if (pcmk__str_eq(target, "*", pcmk__str_none)) {
         target = NULL;
     }
 
     rc = st->cmds->history(st, opts, target, &history, pcmk__timeout_ms2s(timeout));
 
     if (cleanup) {
         // Cleanup doesn't return a history list
         stonith_history_free(history);
         return pcmk_legacy2rc(rc);
     }
 
     out->begin_list(out, "event", "events", "Fencing history");
 
     history = stonith__sort_history(history);
     for (stonith_history_t *hp = history; hp != NULL; hp = hp->next) {
         if (hp->state == st_done) {
             latest = hp;
         }
 
         if (out->is_quiet(out) || !verbose) {
             continue;
         }
 
         out->message(out, "stonith-event", hp, true, false,
                      stonith__later_succeeded(hp, history),
                      (uint32_t) pcmk_show_failed_detail);
         out->increment_list(out);
     }
 
     if (latest) {
         if (out->is_quiet(out)) {
             out->message(out, "stonith-event", latest, false, true, NULL,
                          (uint32_t) pcmk_show_failed_detail);
         } else if (!verbose) { // already printed if verbose
             out->message(out, "stonith-event", latest, false, false, NULL,
                          (uint32_t) pcmk_show_failed_detail);
             out->increment_list(out);
         }
     }
 
     out->end_list(out);
 
     stonith_history_free(history);
     return pcmk_legacy2rc(rc);
 }
 
 int
 pcmk_fence_history(xmlNodePtr *xml, const char *target, unsigned int timeout,
                    bool quiet, int verbose, bool broadcast, bool cleanup)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     out->quiet = quiet;
 
     rc = pcmk__fence_history(out, st, target, timeout, verbose, broadcast,
                              cleanup);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_installed(pcmk__output_t *out, stonith_t *st, unsigned int timeout)
 {
     stonith_key_value_t *devices = NULL;
     int rc = pcmk_rc_ok;
 
     rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices,
                                pcmk__timeout_ms2s(timeout));
     // rc is a negative error code or a positive number of agents
     if (rc < 0) {
         return pcmk_legacy2rc(rc);
     }
 
     out->begin_list(out, "fence device", "fence devices",
                     "Installed fence devices");
     for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) {
         out->list_item(out, "device", "%s", iter->value);
     }
     out->end_list(out);
 
-    stonith_key_value_freeall(devices, 1, 1);
+    stonith__key_value_freeall(devices, true, true);
     return pcmk_rc_ok;
 }
 
 int
 pcmk_fence_installed(xmlNodePtr *xml, unsigned int timeout)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_installed(out, st, timeout);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_last(pcmk__output_t *out, const char *target, bool as_nodeid)
 {
     time_t when = 0;
 
     if (target == NULL) {
         return pcmk_rc_ok;
     }
 
     if (as_nodeid) {
         when = stonith_api_time(atol(target), NULL, FALSE);
     } else {
         when = stonith_api_time(0, target, FALSE);
     }
 
     return out->message(out, "last-fenced", target, when);
 }
 
 int
 pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     stonith__register_messages(out);
 
     rc = pcmk__fence_last(out, target, as_nodeid);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
     return rc;
 }
 
 int
 pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st,
                          const char *device_id, unsigned int timeout)
 {
     GList *targets = NULL;
     char *lists = NULL;
     int rc = pcmk_rc_ok;
 
     rc = st->cmds->list(st, st_opts, device_id, &lists, pcmk__timeout_ms2s(timeout));
     if (rc != pcmk_rc_ok) {
         return pcmk_legacy2rc(rc);
     }
 
     targets = stonith__parse_targets(lists);
 
     out->begin_list(out, "fence target", "fence targets", "Fence Targets");
     while (targets != NULL) {
         out->list_item(out, NULL, "%s", (const char *) targets->data);
         targets = targets->next;
     }
     out->end_list(out);
 
     free(lists);
     return rc;
 }
 
 int
 pcmk_fence_list_targets(xmlNodePtr *xml, const char *device_id, unsigned int timeout)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_list_targets(out, st, device_id, timeout);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, const char *agent,
                      unsigned int timeout)
 {
     char *buffer = NULL;
     int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer,
                                 pcmk__timeout_ms2s(timeout));
 
     if (rc != pcmk_rc_ok) {
         return pcmk_legacy2rc(rc);
     }
 
     out->output_xml(out, PCMK_XE_METADATA, buffer);
     free(buffer);
     return rc;
 }
 
 int
 pcmk_fence_metadata(xmlNodePtr *xml, const char *agent, unsigned int timeout)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_metadata(out, st, agent, timeout);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, const char *target,
                        unsigned int timeout)
 {
     stonith_key_value_t *devices = NULL;
     int rc = pcmk_rc_ok;
 
     rc = st->cmds->query(st, st_opts, target, &devices, pcmk__timeout_ms2s(timeout));
     /* query returns a negative error code or a positive number of results. */
     if (rc < 0) {
         return pcmk_legacy2rc(rc);
     }
 
     out->begin_list(out, "fence device", "fence devices",
                     "Registered fence devices");
     for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) {
         out->list_item(out, "device", "%s", iter->value);
     }
     out->end_list(out);
 
-    stonith_key_value_freeall(devices, 1, 1);
+    stonith__key_value_freeall(devices, true, true);
 
     /* Return pcmk_rc_ok here, not the number of results.  Callers probably
      * don't care.
      */
     return pcmk_rc_ok;
 }
 
 int
 pcmk_fence_registered(xmlNodePtr *xml, const char *target, unsigned int timeout)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_registered(out, st, target, timeout);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_register_level(stonith_t *st, const char *target, int fence_level,
                            GList *devices)
 {
     return handle_level(st, target, fence_level, devices, true);
 }
 
 int
 pcmk_fence_register_level(xmlNodePtr *xml, const char *target, int fence_level,
                           GList *devices)
 {
     stonith_t* st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_register_level(st, target, fence_level, devices);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_unregister_level(stonith_t *st, const char *target, int fence_level)
 {
     return handle_level(st, target, fence_level, NULL, false);
 }
 
 int
 pcmk_fence_unregister_level(xmlNodePtr *xml, const char *target, int fence_level)
 {
     stonith_t* st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_unregister_level(st, target, fence_level);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__fence_validate(pcmk__output_t *out, stonith_t *st, const char *agent,
                      const char *id, GHashTable *params, unsigned int timeout)
 {
     char *output = NULL;
     char *error_output = NULL;
     int rc;
 
     rc  = stonith__validate(st, st_opt_sync_call, id, NULL, agent, params,
                             pcmk__timeout_ms2s(timeout), &output, &error_output);
     out->message(out, "validate", agent, id, output, error_output, rc);
     return pcmk_legacy2rc(rc);
 }
 
 int
 pcmk_fence_validate(xmlNodePtr *xml, const char *agent, const char *id,
                     GHashTable *params, unsigned int timeout)
 {
     stonith_t *st = NULL;
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__setup_output_fencing(&out, &st, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = pcmk__fence_validate(out, st, agent, id, params, timeout);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
 
     st->cmds->disconnect(st);
     stonith__api_free(st);
     return rc;
 }
 
 int
 pcmk__get_fencing_history(stonith_t *st, stonith_history_t **stonith_history,
                           enum pcmk__fence_history fence_history)
 {
     int rc = pcmk_rc_ok;
 
     if ((st == NULL) || (st->state == stonith_disconnected)) {
         rc = ENOTCONN;
     } else if (fence_history != pcmk__fence_history_none) {
         rc = st->cmds->history(st, st_opt_sync_call, NULL, stonith_history,
                                120);
 
         rc = pcmk_legacy2rc(rc);
         if (rc != pcmk_rc_ok) {
             return rc;
         }
 
         *stonith_history = stonith__sort_history(*stonith_history);
         if (fence_history == pcmk__fence_history_reduced) {
             *stonith_history = reduce_fence_history(*stonith_history);
         }
     }
 
     return rc;
 }
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index fa50d108cb..bbbd7af001 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,726 +1,726 @@
 /*
  * Copyright 2009-2025 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 <errno.h>
 #include <fcntl.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <glib.h>                   // gboolean, gchar, etc.
 
 #include <crm/crm.h>
 #include <crm/common/ipc.h>
 #include <crm/cluster/internal.h>
 #include <crm/common/cmdline_internal.h>
 #include <crm/common/output_internal.h>
 
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>   // stonith__register_messages()
 #include <crm/cib.h>
 #include <crm/pengine/status.h>
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
 
 char action = 0;
 
 struct {
     gboolean as_nodeid;
     gboolean broadcast;
     gboolean cleanup;
     gboolean installed;
     gboolean metadata;
     gboolean registered;
     gboolean validate_cfg;
     GList *devices;
     GHashTable *params;
     int fence_level;
     int timeout ;
     long long tolerance_ms;
     int delay;
     char *agent;
     char *confirm_host;
     char *fence_host;
     char *history;
     char *last_fenced;
     char *query;
     char *reboot_host;
     char *register_dev;
     char *register_level;
     char *targets;
     char *terminate;
     char *unfence_host;
     char *unregister_dev;
     char *unregister_level;
 } options = {
     .timeout = 120,
     .delay = 0
 };
 
 gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
 
 #define INDENT "                                    "
 
 /* *INDENT-OFF* */
 static GOptionEntry defn_entries[] = {
     { "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
       "Register the named stonith device. Requires: --agent.\n"
       INDENT "Optional: --option, --env-option.",
       "DEVICE" },
     { "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
       "De-register the named stonith device.",
       "DEVICE" },
     { "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
       "Register a stonith level for the named target,\n"
       INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
       INDENT "Requires: --index and one or more --device entries.",
       "TARGET" },
     { "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
       "Unregister a stonith level for the named target,\n"
       INDENT "specified as for --register-level. Requires: --index",
       "TARGET" },
 
     { NULL }
 };
 
 static GOptionEntry query_entries[] = {
     { "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
       "List devices that can terminate the specified host.\n"
       INDENT "Optional: --timeout",
       "HOST" },
     { "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
       "List all registered devices. Optional: --timeout.",
       NULL },
     { "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
       "List all installed devices. Optional: --timeout.",
       NULL },
     { "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
       "List the targets that can be fenced by the\n"
       INDENT "named device. Optional: --timeout.",
       "DEVICE" },
     { "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
       "Show agent metadata. Requires: --agent.\n"
       INDENT "Optional: --timeout.",
       NULL },
     { "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
       "Check the named device's status. Optional: --timeout.",
       "DEVICE" },
     { "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
       "Show last successful fencing operation for named node\n"
       INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
       INDENT "--quiet (show only the operation's epoch timestamp),\n"
       INDENT "--verbose (show all recorded and pending operations),\n"
       INDENT "--broadcast (update history from all nodes available).",
       "NODE" },
     { "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
       "Indicate when the named node was last fenced.\n"
       INDENT "Optional: --as-node-id.",
       "NODE" },
     { "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
       "Validate a fence device configuration.\n"
       INDENT "Requires: --agent. Optional: --option, --env-option,\n"
       INDENT "--quiet (print no output, only return status).",
       NULL },
 
     { NULL }
 };
 
 static GOptionEntry fence_entries[] = {
     { "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
       "Fence named host. Optional: --timeout, --tolerance, --delay.",
       "HOST" },
     { "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
       "Unfence named host. Optional: --timeout, --tolerance, --delay.",
       "HOST" },
     { "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
       "Reboot named host. Optional: --timeout, --tolerance, --delay.",
       "HOST" },
     { "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
       "Tell cluster that named host is now safely down.",
       "HOST", },
 
     { NULL }
 };
 
 static GOptionEntry addl_entries[] = {
     { "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
       "Cleanup wherever appropriate. Requires --history.",
       NULL },
     { "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
       "Broadcast wherever appropriate.",
       NULL },
     { "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
       "The agent to use (for example, fence_xvm;\n"
       INDENT "with --register, --metadata, --validate).",
       "AGENT" },
     { "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
       "Specify a device configuration parameter as NAME=VALUE\n"
       INDENT "(may be specified multiple times; with --register,\n"
       INDENT "--validate).",
       "PARAM" },
     { "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
       "Specify a device configuration parameter with the\n"
       INDENT "specified name, using the value of the\n"
       INDENT "environment variable of the same name prefixed with\n"
       INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
       INDENT "with --register, --validate).",
       "PARAM" },
     { "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
       "Identify fencing operations in logs with the specified\n"
       INDENT "tag; useful when multiple entities might invoke\n"
       INDENT "stonith_admin (used with most commands).",
       "TAG" },
     { "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
       "Device ID (with --register-level, device to associate with\n"
       INDENT "a given host and level; may be specified multiple times)"
 #if PCMK__ENABLE_CIBSECRETS
       "\n" INDENT "(with --validate, name to use to load CIB secrets)"
 #endif
       ".",
       "DEVICE" },
     { "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
       "The stonith level (1-9) (with --register-level,\n"
       INDENT "--deregister-level).",
       "LEVEL" },
     { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
       "Operation timeout in seconds (default 120;\n"
       INDENT "used with most commands).",
       "SECONDS" },
     { "delay", 'y', 0, G_OPTION_ARG_INT, &options.delay,
       "Apply a fencing delay in seconds. Any static/random delays from\n"
       INDENT "pcmk_delay_base/max will be added, otherwise all\n"
       INDENT "disabled with the value -1\n"
       INDENT "(default 0; with --fence, --reboot, --unfence).",
       "SECONDS" },
     { "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
       "(Advanced) The supplied node is the corosync node ID\n"
       INDENT "(with --last).",
       NULL },
     { "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
       "(Advanced) Do nothing if an equivalent --fence request\n"
       INDENT "succeeded less than this many seconds earlier\n"
       INDENT "(with --fence, --unfence, --reboot).",
       "SECONDS" },
 
     { NULL }
 };
 /* *INDENT-ON* */
 
 static pcmk__supported_format_t formats[] = {
     PCMK__SUPPORTED_FORMAT_HTML,
     PCMK__SUPPORTED_FORMAT_NONE,
     PCMK__SUPPORTED_FORMAT_TEXT,
     PCMK__SUPPORTED_FORMAT_XML,
     { NULL, NULL, NULL }
 };
 
 static const int st_opts = st_opt_sync_call|st_opt_allow_self_fencing;
 
 static char *name = NULL;
 
 gboolean
 add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
     const char *env = getenv(key);
     gboolean retval = TRUE;
 
     if (env == NULL) {
         g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
         retval = FALSE;
     } else {
         crm_info("Got: '%s'='%s'", optarg, env);
 
         if (options.params != NULL) {
             options.params = pcmk__strkey_table(free, free);
         }
 
         pcmk__insert_dup(options.params, optarg, env);
     }
 
     free(key);
     return retval;
 }
 
 gboolean
 add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     options.devices = g_list_append(options.devices, pcmk__str_copy(optarg));
     return TRUE;
 }
 
 gboolean
 add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     // pcmk__request_fencing() expects an unsigned int
     options.tolerance_ms = crm_get_msec(optarg);
 
     if (options.tolerance_ms < 0) {
         crm_warn("Ignoring invalid tolerance '%s'", optarg);
         options.tolerance_ms = 0;
     } else {
         options.tolerance_ms = QB_MIN(options.tolerance_ms, UINT_MAX);
     }
     return TRUE;
 }
 
 gboolean
 add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     gchar *name = NULL;
     gchar *value = NULL;
     int rc = 0;
     gboolean retval = TRUE;
 
     crm_info("Scanning: -o %s", optarg);
 
     rc = pcmk__scan_nvpair(optarg, &name, &value);
 
     if (rc != pcmk_rc_ok) {
         g_set_error(error, PCMK__RC_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_rc_str(rc));
         retval = FALSE;
     } else {
         crm_info("Got: '%s'='%s'", name, value);
 
         if (options.params == NULL) {
             options.params = pcmk__strkey_table(free, free);
         }
 
         pcmk__insert_dup(options.params, name, value);
     }
 
     g_free(name);
     g_free(value);
     return retval;
 }
 
 gboolean
 set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     free(name);
     name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
     return TRUE;
 }
 
 static GOptionContext *
 build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
     GOptionContext *context = NULL;
 
     GOptionEntry extra_prog_entries[] = {
         { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
           "Be less descriptive in output.",
           NULL },
 
         { NULL }
     };
 
     context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL);
 
     /* Add the -q option, which cannot be part of the globally supported options
      * because some tools use that flag for something else.
      */
     pcmk__add_main_args(context, extra_prog_entries);
 
     pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
                         "Show device definition help", defn_entries);
     pcmk__add_arg_group(context, "queries", "Queries:",
                         "Show query help", query_entries);
     pcmk__add_arg_group(context, "fence", "Fencing Commands:",
                         "Show fence help", fence_entries);
     pcmk__add_arg_group(context, "additional", "Additional Options:",
                         "Show additional options", addl_entries);
     return context;
 }
 
 // \return Standard Pacemaker return code
 static int
 request_fencing(stonith_t *st, const char *target, const char *command,
                 GError **error)
 {
     char *reason = NULL;
     int rc = pcmk__request_fencing(st, target, command, name,
                                    options.timeout * 1000,
                                    options.tolerance_ms, options.delay,
                                    &reason);
 
     if (rc != pcmk_rc_ok) {
         const char *rc_str = pcmk_rc_str(rc);
         const char *what = "fence";
 
         if (strcmp(command, PCMK_ACTION_ON) == 0) {
             what = "unfence";
         }
 
         // If reason is identical to return code string, don't display it twice
         if (pcmk__str_eq(rc_str, reason, pcmk__str_none)) {
             free(reason);
             reason = NULL;
         }
 
         g_set_error(error, PCMK__RC_ERROR, rc,
                     "Couldn't %s %s: %s%s%s%s",
                     what, target, rc_str,
                     ((reason == NULL)? "" : " ("),
                     ((reason == NULL)? "" : reason),
                     ((reason == NULL)? "" : ")"));
     }
     free(reason);
     return rc;
 }
 
 int
 main(int argc, char **argv)
 {
     int rc = 0;
     crm_exit_t exit_code = CRM_EX_OK;
     bool no_connect = false;
     bool required_agent = false;
 
     char *target = NULL;
     const char *device = NULL;
     stonith_t *st = NULL;
 
     GError *error = NULL;
 
     pcmk__output_t *out = NULL;
 
     GOptionGroup *output_group = NULL;
     pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
     gchar **processed_args = pcmk__cmdline_preproc(argv, "adehilorstvyBCDFHQRTU");
     GOptionContext *context = build_arg_context(args, &output_group);
 
     pcmk__register_formats(output_group, formats);
     if (!g_option_context_parse_strv(context, &processed_args, &error)) {
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     pcmk__cli_init_logging("stonith_admin", args->verbosity);
 
     if (name == NULL) {
         name = strdup(crm_system_name);
     }
 
     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;
     }
 
     pcmk__output_enable_list_element(out);
 
     stonith__register_messages(out);
 
     if (args->version) {
         out->version(out, false);
         goto done;
     }
 
     if (options.validate_cfg) {
         required_agent = true;
         no_connect = true;
         action = 'K';
     }
 
     if (options.installed) {
         no_connect = true;
         action = 'I';
     }
 
     if (options.registered) {
         action = 'L';
     }
 
     if (options.register_dev != NULL) {
         required_agent = true;
         action = 'R';
         device = options.register_dev;
     }
 
     if (options.query != NULL) {
         action = 'Q';
         device = options.query;
     }
 
     if (options.unregister_dev != NULL) {
         action = 'D';
         device = options.unregister_dev;
     }
 
     if (options.targets != NULL) {
         action = 's';
         device = options.targets;
     }
 
     if (options.terminate != NULL) {
         action = 'L';
         target = options.terminate;
     }
 
     if (options.metadata) {
         no_connect = true;
         required_agent = true;
         action = 'M';
     }
 
     if (options.reboot_host != NULL) {
         no_connect = true;
         action = 'B';
         target = options.reboot_host;
         crm_log_args(argc, argv);
     }
 
     if (options.fence_host != NULL) {
         no_connect = true;
         action = 'F';
         target = options.fence_host;
         crm_log_args(argc, argv);
     }
 
     if (options.unfence_host != NULL) {
         no_connect = true;
         action = 'U';
         target = options.unfence_host;
         crm_log_args(argc, argv);
     }
 
     if (options.confirm_host != NULL) {
         action = 'C';
         target = options.confirm_host;
         crm_log_args(argc, argv);
     }
 
     if (options.last_fenced != NULL) {
         action = 'h';
         target = options.last_fenced;
     }
 
     if (options.history != NULL) {
         action = 'H';
         target = options.history;
     }
 
     if (options.register_level != NULL) {
         action = 'r';
         target = options.register_level;
     }
 
     if (options.unregister_level != NULL) {
         action = 'd';
         target = options.unregister_level;
     }
 
     if ((options.timeout > (UINT_MAX / 1000)) || (options.timeout < 0)) {
         out->err(out, "Integer value \"%d\" for -t out of range", options.timeout);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     if (action == 0) {
         char *help = g_option_context_get_help(context, TRUE, NULL);
 
         out->err(out, "%s", help);
         g_free(help);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     if (required_agent && options.agent == NULL) {
         char *help = g_option_context_get_help(context, TRUE, NULL);
 
         out->err(out, "Please specify an agent to query using -a,--agent [value]");
         out->err(out, "%s", help);
         g_free(help);
         exit_code = CRM_EX_USAGE;
         goto done;
     }
 
     out->quiet = args->quiet;
 
     st = stonith__api_new();
     if (st == NULL) {
         rc = -ENOMEM;
     } else if (!no_connect) {
         rc = st->cmds->connect(st, name, NULL);
     }
     if (rc < 0) {
         out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
         exit_code = CRM_EX_DISCONNECT;
         goto done;
     }
 
     switch (action) {
         case 'I':
             rc = pcmk__fence_installed(out, st, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Failed to list installed devices: %s", pcmk_rc_str(rc));
             }
 
             break;
 
         case 'L':
             rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Failed to list registered devices: %s", pcmk_rc_str(rc));
             }
 
             break;
 
         case 'Q':
             rc = st->cmds->monitor(st, st_opts, device, options.timeout);
             if (rc != pcmk_rc_ok) {
                 rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
             }
             rc = pcmk_legacy2rc(rc);
             break;
 
         case 's':
             rc = pcmk__fence_list_targets(out, st, device, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Couldn't list targets: %s", pcmk_rc_str(rc));
             }
 
             break;
 
         case 'R': {
             /* register_device wants a stonith_key_value_t instead of a GHashTable */
             stonith_key_value_t *params = NULL;
             GHashTableIter iter;
             gpointer key, val;
 
             if (options.params != NULL) {
                 g_hash_table_iter_init(&iter, options.params);
                 while (g_hash_table_iter_next(&iter, &key, &val)) {
                     params = stonith__key_value_add(params, key, val);
                 }
             }
             rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
                                            params);
-            stonith_key_value_freeall(params, 1, 1);
+            stonith__key_value_freeall(params, true, true);
 
             rc = pcmk_legacy2rc(rc);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't register device %s using agent %s: %s",
                          device, options.agent, pcmk_rc_str(rc));
             }
             break;
         }
 
         case 'D':
             rc = st->cmds->remove_device(st, st_opts, device);
             rc = pcmk_legacy2rc(rc);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't unregister device %s: %s",
                          device, pcmk_rc_str(rc));
             }
             break;
 
         case 'd':
             rc = pcmk__fence_unregister_level(st, target, options.fence_level);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't unregister topology level %d for %s: %s",
                          options.fence_level, target, pcmk_rc_str(rc));
             }
             break;
 
         case 'r':
             rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't register topology level %d for %s: %s",
                          options.fence_level, target, pcmk_rc_str(rc));
             }
             break;
 
         case 'M':
             rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
             if (rc != pcmk_rc_ok) {
                 out->err(out, "Can't get fence agent meta-data: %s",
                          pcmk_rc_str(rc));
             }
 
             break;
 
         case 'C':
             rc = st->cmds->confirm(st, st_opts, target);
             rc = pcmk_legacy2rc(rc);
             break;
 
         case 'B':
             rc = request_fencing(st, target, PCMK_ACTION_REBOOT, &error);
             break;
 
         case 'F':
             rc = request_fencing(st, target, PCMK_ACTION_OFF, &error);
             break;
 
         case 'U':
             rc = request_fencing(st, target, PCMK_ACTION_ON, &error);
             break;
 
         case 'h':
             rc = pcmk__fence_last(out, target, options.as_nodeid);
             break;
 
         case 'H':
             rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->verbosity,
                                      options.broadcast, options.cleanup);
             break;
 
         case 'K':
             device = NULL;
             if (options.devices != NULL) {
                 device = g_list_nth_data(options.devices, 0);
             }
 
             rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
                                         options.timeout*1000);
             break;
     }
 
     crm_info("Command returned: %s (%d)", pcmk_rc_str(rc), rc);
     exit_code = pcmk_rc2exitc(rc);
 
   done:
     g_strfreev(processed_args);
     pcmk__free_arg_context(context);
 
     pcmk__output_and_clear_error(&error, out);
 
     if (out != NULL) {
         out->finish(out, exit_code, true, NULL);
         pcmk__output_free(out);
     }
     pcmk__unregister_formats();
     free(name);
     g_list_free_full(options.devices, free);
 
     if (options.params != NULL) {
         g_hash_table_destroy(options.params);
     }
 
     if (st != NULL) {
         st->cmds->disconnect(st);
         stonith__api_free(st);
     }
 
     return exit_code;
 }