root/daemons/fenced/fenced_commands.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. is_action_required
  2. get_action_delay_max
  3. get_action_delay_base
  4. get_action_timeout
  5. cmd_device
  6. fenced_device_reboot_action
  7. fenced_device_supports_on
  8. free_async_command
  9. create_async_command
  10. get_action_limit
  11. get_active_cmds
  12. fork_cb
  13. get_agent_metadata_cb
  14. report_internal_result
  15. stonith_device_execute
  16. stonith_device_dispatch
  17. start_delay_helper
  18. schedule_stonith_command
  19. free_device
  20. free_device_list
  21. init_device_list
  22. build_port_aliases
  23. free_metadata_cache
  24. init_metadata_cache
  25. get_agent_metadata
  26. is_nodeid_required
  27. read_action_metadata
  28. map_action
  29. xml2device_params
  30. target_list_type
  31. build_device_from_xml
  32. schedule_internal_command
  33. status_search_cb
  34. dynamic_list_search_cb
  35. device_params_diff
  36. device_has_duplicate
  37. stonith_device_register
  38. stonith_device_remove
  39. count_active_levels
  40. free_topology_entry
  41. free_topology_list
  42. init_topology_list
  43. stonith_level_key
  44. unpack_level_kind
  45. parse_device_list
  46. unpack_level_request
  47. fenced_register_level
  48. fenced_unregister_level
  49. list_to_string
  50. execute_agent_action
  51. search_devices_record_result
  52. localhost_is_eligible
  53. localhost_is_eligible_with_remap
  54. can_fence_host_with_device
  55. search_devices
  56. get_capable_devices
  57. add_action_specific_attributes
  58. add_disallowed
  59. add_action_reply
  60. stonith_send_reply
  61. stonith_query_capable_device_cb
  62. log_async_result
  63. send_async_reply
  64. cancel_stonith_command
  65. reply_to_duplicates
  66. next_required_device
  67. st_child_done
  68. sort_device_priority
  69. stonith_fence_get_devices_cb
  70. fence_locally
  71. fenced_construct_reply
  72. construct_async_reply
  73. fencing_peer_active
  74. set_fencing_completed
  75. check_alternate_host
  76. remove_relay_op
  77. is_privileged
  78. handle_register_request
  79. handle_agent_request
  80. handle_update_timeout_request
  81. handle_query_request
  82. handle_notify_request
  83. handle_relay_request
  84. handle_fence_request
  85. handle_history_request
  86. handle_device_add_request
  87. handle_device_delete_request
  88. handle_level_add_request
  89. handle_level_delete_request
  90. handle_cache_request
  91. handle_unknown_request
  92. fenced_register_handlers
  93. fenced_unregister_handlers
  94. handle_request
  95. handle_reply
  96. stonith_command

   1 /*
   2  * Copyright 2009-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU General Public License version 2
   7  * or later (GPLv2+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <sys/param.h>
  13 #include <stdio.h>
  14 #include <sys/types.h>
  15 #include <sys/wait.h>
  16 #include <sys/stat.h>
  17 #include <unistd.h>
  18 #include <sys/utsname.h>
  19 
  20 #include <stdlib.h>
  21 #include <errno.h>
  22 #include <fcntl.h>
  23 #include <ctype.h>
  24 
  25 #include <crm/crm.h>
  26 #include <crm/msg_xml.h>
  27 #include <crm/common/ipc.h>
  28 #include <crm/common/ipc_internal.h>
  29 #include <crm/cluster/internal.h>
  30 #include <crm/common/mainloop.h>
  31 
  32 #include <crm/stonith-ng.h>
  33 #include <crm/fencing/internal.h>
  34 #include <crm/common/xml.h>
  35 
  36 #include <pacemaker-fenced.h>
  37 
  38 GHashTable *device_list = NULL;
  39 GHashTable *topology = NULL;
  40 static GList *cmd_list = NULL;
  41 
  42 static GHashTable *fenced_handlers = NULL;
  43 
  44 struct device_search_s {
  45     /* target of fence action */
  46     char *host;
  47     /* requested fence action */
  48     char *action;
  49     /* timeout to use if a device is queried dynamically for possible targets */
  50     int per_device_timeout;
  51     /* number of registered fencing devices at time of request */
  52     int replies_needed;
  53     /* number of device replies received so far */
  54     int replies_received;
  55     /* whether the target is eligible to perform requested action (or off) */
  56     bool allow_suicide;
  57 
  58     /* private data to pass to search callback function */
  59     void *user_data;
  60     /* function to call when all replies have been received */
  61     void (*callback) (GList * devices, void *user_data);
  62     /* devices capable of performing requested action (or off if remapping) */
  63     GList *capable;
  64     /* Whether to perform searches that support the action */
  65     uint32_t support_action_only;
  66 };
  67 
  68 static gboolean stonith_device_dispatch(gpointer user_data);
  69 static void st_child_done(int pid, const pcmk__action_result_t *result,
  70                           void *user_data);
  71 
  72 static void search_devices_record_result(struct device_search_s *search, const char *device,
  73                                          gboolean can_fence);
  74 
  75 static int get_agent_metadata(const char *agent, xmlNode **metadata);
  76 static void read_action_metadata(stonith_device_t *device);
  77 static enum fenced_target_by unpack_level_kind(const xmlNode *level);
  78 
  79 typedef struct async_command_s {
  80 
  81     int id;
  82     int pid;
  83     int fd_stdout;
  84     int options;
  85     int default_timeout; /* seconds */
  86     int timeout; /* seconds */
  87 
  88     int start_delay; // seconds (-1 means disable static/random fencing delays)
  89     int delay_id;
  90 
  91     char *op;
  92     char *origin;
  93     char *client;
  94     char *client_name;
  95     char *remote_op_id;
  96 
  97     char *target;
  98     uint32_t target_nodeid;
  99     char *action;
 100     char *device;
 101 
 102     GList *device_list;
 103     GList *next_device_iter; // device_list entry for next device to execute
 104 
 105     void *internal_user_data;
 106     void (*done_cb) (int pid, const pcmk__action_result_t *result,
 107                      void *user_data);
 108     guint timer_sigterm;
 109     guint timer_sigkill;
 110     /*! If the operation timed out, this is the last signal
 111      *  we sent to the process to get it to terminate */
 112     int last_timeout_signo;
 113 
 114     stonith_device_t *active_on;
 115     stonith_device_t *activating_on;
 116 } async_command_t;
 117 
 118 static xmlNode *construct_async_reply(const async_command_t *cmd,
 119                                       const pcmk__action_result_t *result);
 120 
 121 static gboolean
 122 is_action_required(const char *action, const stonith_device_t *device)
     /* [previous][next][first][last][top][bottom][index][help] */
 123 {
 124     return (device != NULL) && device->automatic_unfencing
 125            && pcmk__str_eq(action, PCMK_ACTION_ON, pcmk__str_none);
 126 }
 127 
 128 static int
 129 get_action_delay_max(const stonith_device_t *device, const char *action)
     /* [previous][next][first][last][top][bottom][index][help] */
 130 {
 131     const char *value = NULL;
 132     int delay_max = 0;
 133 
 134     if (!pcmk__is_fencing_action(action)) {
 135         return 0;
 136     }
 137 
 138     value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_MAX);
 139     if (value) {
 140        delay_max = crm_parse_interval_spec(value) / 1000;
 141     }
 142 
 143     return delay_max;
 144 }
 145 
 146 static int
 147 get_action_delay_base(const stonith_device_t *device, const char *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 148                       const char *target)
 149 {
 150     char *hash_value = NULL;
 151     int delay_base = 0;
 152 
 153     if (!pcmk__is_fencing_action(action)) {
 154         return 0;
 155     }
 156 
 157     hash_value = g_hash_table_lookup(device->params, PCMK_STONITH_DELAY_BASE);
 158 
 159     if (hash_value) {
 160         char *value = strdup(hash_value);
 161         char *valptr = value;
 162 
 163         CRM_ASSERT(value != NULL);
 164 
 165         if (target != NULL) {
 166             for (char *val = strtok(value, "; \t"); val != NULL; val = strtok(NULL, "; \t")) {
 167                 char *mapval = strchr(val, ':');
 168 
 169                 if (mapval == NULL || mapval[1] == 0) {
 170                     crm_err("pcmk_delay_base: empty value in mapping", val);
 171                     continue;
 172                 }
 173 
 174                 if (mapval != val && strncasecmp(target, val, (size_t)(mapval - val)) == 0) {
 175                     value = mapval + 1;
 176                     crm_debug("pcmk_delay_base mapped to %s for %s",
 177                               value, target);
 178                     break;
 179                 }
 180             }
 181         }
 182 
 183         if (strchr(value, ':') == 0) {
 184            delay_base = crm_parse_interval_spec(value) / 1000;
 185         }
 186 
 187         free(valptr);
 188     }
 189 
 190     return delay_base;
 191 }
 192 
 193 /*!
 194  * \internal
 195  * \brief Override STONITH timeout with pcmk_*_timeout if available
 196  *
 197  * \param[in] device           STONITH device to use
 198  * \param[in] action           STONITH action name
 199  * \param[in] default_timeout  Timeout to use if device does not have
 200  *                             a pcmk_*_timeout parameter for action
 201  *
 202  * \return Value of pcmk_(action)_timeout if available, otherwise default_timeout
 203  * \note For consistency, it would be nice if reboot/off/on timeouts could be
 204  *       set the same way as start/stop/monitor timeouts, i.e. with an
 205  *       <operation> entry in the fencing resource configuration. However that
 206  *       is insufficient because fencing devices may be registered directly via
 207  *       the fencer's register_device() API instead of going through the CIB
 208  *       (e.g. stonith_admin uses it for its -R option, and the executor uses it
 209  *       to ensure a device is registered when a command is issued). As device
 210  *       properties, pcmk_*_timeout parameters can be grabbed by the fencer when
 211  *       the device is registered, whether by CIB change or API call.
 212  */
 213 static int
 214 get_action_timeout(const stonith_device_t *device, const char *action,
     /* [previous][next][first][last][top][bottom][index][help] */
 215                    int default_timeout)
 216 {
 217     if (action && device && device->params) {
 218         char buffer[64] = { 0, };
 219         const char *value = NULL;
 220 
 221         /* If "reboot" was requested but the device does not support it,
 222          * we will remap to "off", so check timeout for "off" instead
 223          */
 224         if (pcmk__str_eq(action, PCMK_ACTION_REBOOT, pcmk__str_none)
 225             && !pcmk_is_set(device->flags, st_device_supports_reboot)) {
 226             crm_trace("%s doesn't support reboot, using timeout for off instead",
 227                       device->id);
 228             action = PCMK_ACTION_OFF;
 229         }
 230 
 231         /* If the device config specified an action-specific timeout, use it */
 232         snprintf(buffer, sizeof(buffer), "pcmk_%s_timeout", action);
 233         value = g_hash_table_lookup(device->params, buffer);
 234         if (value) {
 235             return atoi(value);
 236         }
 237     }
 238     return default_timeout;
 239 }
 240 
 241 /*!
 242  * \internal
 243  * \brief Get the currently executing device for a fencing operation
 244  *
 245  * \param[in] cmd  Fencing operation to check
 246  *
 247  * \return Currently executing device for \p cmd if any, otherwise NULL
 248  */
 249 static stonith_device_t *
 250 cmd_device(const async_command_t *cmd)
     /* [previous][next][first][last][top][bottom][index][help] */
 251 {
 252     if ((cmd == NULL) || (cmd->device == NULL) || (device_list == NULL)) {
 253         return NULL;
 254     }
 255     return g_hash_table_lookup(device_list, cmd->device);
 256 }
 257 
 258 /*!
 259  * \internal
 260  * \brief Return the configured reboot action for a given device
 261  *
 262  * \param[in] device_id  Device ID
 263  *
 264  * \return Configured reboot action for \p device_id
 265  */
 266 const char *
 267 fenced_device_reboot_action(const char *device_id)
     /* [previous][next][first][last][top][bottom][index][help] */
 268 {
 269     const char *action = NULL;
 270 
 271     if ((device_list != NULL) && (device_id != NULL)) {
 272         stonith_device_t *device = g_hash_table_lookup(device_list, device_id);
 273 
 274         if ((device != NULL) && (device->params != NULL)) {
 275             action = g_hash_table_lookup(device->params, "pcmk_reboot_action");
 276         }
 277     }
 278     return pcmk__s(action, PCMK_ACTION_REBOOT);
 279 }
 280 
 281 /*!
 282  * \internal
 283  * \brief Check whether a given device supports the "on" action
 284  *
 285  * \param[in] device_id  Device ID
 286  *
 287  * \return true if \p device_id supports "on", otherwise false
 288  */
 289 bool
 290 fenced_device_supports_on(const char *device_id)
     /* [previous][next][first][last][top][bottom][index][help] */
 291 {
 292     if ((device_list != NULL) && (device_id != NULL)) {
 293         stonith_device_t *device = g_hash_table_lookup(device_list, device_id);
 294 
 295         if (device != NULL) {
 296             return pcmk_is_set(device->flags, st_device_supports_on);
 297         }
 298     }
 299     return false;
 300 }
 301 
 302 static void
 303 free_async_command(async_command_t * cmd)
     /* [previous][next][first][last][top][bottom][index][help] */
 304 {
 305     if (!cmd) {
 306         return;
 307     }
 308 
 309     if (cmd->delay_id) {
 310         g_source_remove(cmd->delay_id);
 311     }
 312 
 313     cmd_list = g_list_remove(cmd_list, cmd);
 314 
 315     g_list_free_full(cmd->device_list, free);
 316     free(cmd->device);
 317     free(cmd->action);
 318     free(cmd->target);
 319     free(cmd->remote_op_id);
 320     free(cmd->client);
 321     free(cmd->client_name);
 322     free(cmd->origin);
 323     free(cmd->op);
 324     free(cmd);
 325 }
 326 
 327 /*!
 328  * \internal
 329  * \brief Create a new asynchronous fencing operation from request XML
 330  *
 331  * \param[in] msg  Fencing request XML (from IPC or CPG)
 332  *
 333  * \return Newly allocated fencing operation on success, otherwise NULL
 334  *
 335  * \note This asserts on memory errors, so a NULL return indicates an
 336  *       unparseable message.
 337  */
 338 static async_command_t *
 339 create_async_command(xmlNode *msg)
     /* [previous][next][first][last][top][bottom][index][help] */
 340 {
 341     xmlNode *op = NULL;
 342     async_command_t *cmd = NULL;
 343 
 344     if (msg == NULL) {
 345         return NULL;
 346     }
 347 
 348     op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR);
 349     if (op == NULL) {
 350         return NULL;
 351     }
 352 
 353     cmd = calloc(1, sizeof(async_command_t));
 354     CRM_ASSERT(cmd != NULL);
 355 
 356     // All messages must include these
 357     cmd->action = crm_element_value_copy(op, F_STONITH_ACTION);
 358     cmd->op = crm_element_value_copy(msg, F_STONITH_OPERATION);
 359     cmd->client = crm_element_value_copy(msg, F_STONITH_CLIENTID);
 360     if ((cmd->action == NULL) || (cmd->op == NULL) || (cmd->client == NULL)) {
 361         free_async_command(cmd);
 362         return NULL;
 363     }
 364 
 365     crm_element_value_int(msg, F_STONITH_CALLID, &(cmd->id));
 366     crm_element_value_int(msg, F_STONITH_CALLOPTS, &(cmd->options));
 367     crm_element_value_int(msg, F_STONITH_DELAY, &(cmd->start_delay));
 368     crm_element_value_int(msg, F_STONITH_TIMEOUT, &(cmd->default_timeout));
 369     cmd->timeout = cmd->default_timeout;
 370 
 371     cmd->origin = crm_element_value_copy(msg, F_ORIG);
 372     cmd->remote_op_id = crm_element_value_copy(msg, F_STONITH_REMOTE_OP_ID);
 373     cmd->client_name = crm_element_value_copy(msg, F_STONITH_CLIENTNAME);
 374     cmd->target = crm_element_value_copy(op, F_STONITH_TARGET);
 375     cmd->device = crm_element_value_copy(op, F_STONITH_DEVICE);
 376 
 377     cmd->done_cb = st_child_done;
 378 
 379     // Track in global command list
 380     cmd_list = g_list_append(cmd_list, cmd);
 381 
 382     return cmd;
 383 }
 384 
 385 static int
 386 get_action_limit(stonith_device_t * device)
     /* [previous][next][first][last][top][bottom][index][help] */
 387 {
 388     const char *value = NULL;
 389     int action_limit = 1;
 390 
 391     value = g_hash_table_lookup(device->params, PCMK_STONITH_ACTION_LIMIT);
 392     if ((value == NULL)
 393         || (pcmk__scan_min_int(value, &action_limit, INT_MIN) != pcmk_rc_ok)
 394         || (action_limit == 0)) {
 395         action_limit = 1;
 396     }
 397     return action_limit;
 398 }
 399 
 400 static int
 401 get_active_cmds(stonith_device_t * device)
     /* [previous][next][first][last][top][bottom][index][help] */
 402 {
 403     int counter = 0;
 404     GList *gIter = NULL;
 405     GList *gIterNext = NULL;
 406 
 407     CRM_CHECK(device != NULL, return 0);
 408 
 409     for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) {
 410         async_command_t *cmd = gIter->data;
 411 
 412         gIterNext = gIter->next;
 413 
 414         if (cmd->active_on == device) {
 415             counter++;
 416         }
 417     }
 418 
 419     return counter;
 420 }
 421 
 422 static void
 423 fork_cb(int pid, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 424 {
 425     async_command_t *cmd = (async_command_t *) user_data;
 426     stonith_device_t * device =
 427         /* in case of a retry we've done the move from
 428            activating_on to active_on already
 429          */
 430         cmd->activating_on?cmd->activating_on:cmd->active_on;
 431 
 432     CRM_ASSERT(device);
 433     crm_debug("Operation '%s' [%d]%s%s using %s now running with %ds timeout",
 434               cmd->action, pid,
 435               ((cmd->target == NULL)? "" : " targeting "),
 436               pcmk__s(cmd->target, ""), device->id, cmd->timeout);
 437     cmd->active_on = device;
 438     cmd->activating_on = NULL;
 439 }
 440 
 441 static int
 442 get_agent_metadata_cb(gpointer data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 443     stonith_device_t *device = data;
 444     guint period_ms;
 445 
 446     switch (get_agent_metadata(device->agent, &device->agent_metadata)) {
 447         case pcmk_rc_ok:
 448             if (device->agent_metadata) {
 449                 read_action_metadata(device);
 450                 stonith__device_parameter_flags(&(device->flags), device->id,
 451                                         device->agent_metadata);
 452             }
 453             return G_SOURCE_REMOVE;
 454 
 455         case EAGAIN:
 456             period_ms = pcmk__mainloop_timer_get_period(device->timer);
 457             if (period_ms < 160 * 1000) {
 458                 mainloop_timer_set_period(device->timer, 2 * period_ms);
 459             }
 460             return G_SOURCE_CONTINUE;
 461 
 462         default:
 463             return G_SOURCE_REMOVE;
 464     }
 465 }
 466 
 467 /*!
 468  * \internal
 469  * \brief Call a command's action callback for an internal (not library) result
 470  *
 471  * \param[in,out] cmd               Command to report result for
 472  * \param[in]     execution_status  Execution status to use for result
 473  * \param[in]     exit_status       Exit status to use for result
 474  * \param[in]     exit_reason       Exit reason to use for result
 475  */
 476 static void
 477 report_internal_result(async_command_t *cmd, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 478                        int execution_status, const char *exit_reason)
 479 {
 480     pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
 481 
 482     pcmk__set_result(&result, exit_status, execution_status, exit_reason);
 483     cmd->done_cb(0, &result, cmd);
 484     pcmk__reset_result(&result);
 485 }
 486 
 487 static gboolean
 488 stonith_device_execute(stonith_device_t * device)
     /* [previous][next][first][last][top][bottom][index][help] */
 489 {
 490     int exec_rc = 0;
 491     const char *action_str = NULL;
 492     const char *host_arg = NULL;
 493     async_command_t *cmd = NULL;
 494     stonith_action_t *action = NULL;
 495     int active_cmds = 0;
 496     int action_limit = 0;
 497     GList *gIter = NULL;
 498     GList *gIterNext = NULL;
 499 
 500     CRM_CHECK(device != NULL, return FALSE);
 501 
 502     active_cmds = get_active_cmds(device);
 503     action_limit = get_action_limit(device);
 504     if (action_limit > -1 && active_cmds >= action_limit) {
 505         crm_trace("%s is over its action limit of %d (%u active action%s)",
 506                   device->id, action_limit, active_cmds,
 507                   pcmk__plural_s(active_cmds));
 508         return TRUE;
 509     }
 510 
 511     for (gIter = device->pending_ops; gIter != NULL; gIter = gIterNext) {
 512         async_command_t *pending_op = gIter->data;
 513 
 514         gIterNext = gIter->next;
 515 
 516         if (pending_op && pending_op->delay_id) {
 517             crm_trace("Operation '%s'%s%s using %s was asked to run too early, "
 518                       "waiting for start delay of %ds",
 519                       pending_op->action,
 520                       ((pending_op->target == NULL)? "" : " targeting "),
 521                       pcmk__s(pending_op->target, ""),
 522                       device->id, pending_op->start_delay);
 523             continue;
 524         }
 525 
 526         device->pending_ops = g_list_remove_link(device->pending_ops, gIter);
 527         g_list_free_1(gIter);
 528 
 529         cmd = pending_op;
 530         break;
 531     }
 532 
 533     if (cmd == NULL) {
 534         crm_trace("No actions using %s are needed", device->id);
 535         return TRUE;
 536     }
 537 
 538     if (pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
 539                          STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
 540         if (pcmk__is_fencing_action(cmd->action)) {
 541             if (node_does_watchdog_fencing(stonith_our_uname)) {
 542                 pcmk__panic(__func__);
 543                 goto done;
 544             }
 545         } else {
 546             crm_info("Faking success for %s watchdog operation", cmd->action);
 547             report_internal_result(cmd, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
 548             goto done;
 549         }
 550     }
 551 
 552 #if SUPPORT_CIBSECRETS
 553     exec_rc = pcmk__substitute_secrets(device->id, device->params);
 554     if (exec_rc != pcmk_rc_ok) {
 555         if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_none)) {
 556             crm_info("Proceeding with stop operation for %s "
 557                      "despite being unable to load CIB secrets (%s)",
 558                      device->id, pcmk_rc_str(exec_rc));
 559         } else {
 560             crm_err("Considering %s unconfigured "
 561                     "because unable to load CIB secrets: %s",
 562                      device->id, pcmk_rc_str(exec_rc));
 563             report_internal_result(cmd, CRM_EX_ERROR, PCMK_EXEC_NO_SECRETS,
 564                                    "Failed to get CIB secrets");
 565             goto done;
 566         }
 567     }
 568 #endif
 569 
 570     action_str = cmd->action;
 571     if (pcmk__str_eq(cmd->action, PCMK_ACTION_REBOOT, pcmk__str_none)
 572         && !pcmk_is_set(device->flags, st_device_supports_reboot)) {
 573 
 574         crm_notice("Remapping 'reboot' action%s%s using %s to 'off' "
 575                    "because agent '%s' does not support reboot",
 576                    ((cmd->target == NULL)? "" : " targeting "),
 577                    pcmk__s(cmd->target, ""), device->id, device->agent);
 578         action_str = PCMK_ACTION_OFF;
 579     }
 580 
 581     if (pcmk_is_set(device->flags, st_device_supports_parameter_port)) {
 582         host_arg = "port";
 583 
 584     } else if (pcmk_is_set(device->flags, st_device_supports_parameter_plug)) {
 585         host_arg = "plug";
 586     }
 587 
 588     action = stonith__action_create(device->agent, action_str, cmd->target,
 589                                     cmd->target_nodeid, cmd->timeout,
 590                                     device->params, device->aliases, host_arg);
 591 
 592     /* for async exec, exec_rc is negative for early error exit
 593        otherwise handling of success/errors is done via callbacks */
 594     cmd->activating_on = device;
 595     exec_rc = stonith__execute_async(action, (void *)cmd, cmd->done_cb,
 596                                      fork_cb);
 597     if (exec_rc < 0) {
 598         cmd->activating_on = NULL;
 599         cmd->done_cb(0, stonith__action_result(action), cmd);
 600         stonith__destroy_action(action);
 601     }
 602 
 603 done:
 604     /* Device might get triggered to work by multiple fencing commands
 605      * simultaneously. Trigger the device again to make sure any
 606      * remaining concurrent commands get executed. */
 607     if (device->pending_ops) {
 608         mainloop_set_trigger(device->work);
 609     }
 610     return TRUE;
 611 }
 612 
 613 static gboolean
 614 stonith_device_dispatch(gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 615 {
 616     return stonith_device_execute(user_data);
 617 }
 618 
 619 static gboolean
 620 start_delay_helper(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 621 {
 622     async_command_t *cmd = data;
 623     stonith_device_t *device = cmd_device(cmd);
 624 
 625     cmd->delay_id = 0;
 626     if (device) {
 627         mainloop_set_trigger(device->work);
 628     }
 629 
 630     return FALSE;
 631 }
 632 
 633 static void
 634 schedule_stonith_command(async_command_t * cmd, stonith_device_t * device)
     /* [previous][next][first][last][top][bottom][index][help] */
 635 {
 636     int delay_max = 0;
 637     int delay_base = 0;
 638     int requested_delay = cmd->start_delay;
 639 
 640     CRM_CHECK(cmd != NULL, return);
 641     CRM_CHECK(device != NULL, return);
 642 
 643     if (cmd->device) {
 644         free(cmd->device);
 645     }
 646 
 647     if (device->include_nodeid && (cmd->target != NULL)) {
 648         crm_node_t *node = crm_get_peer(0, cmd->target);
 649 
 650         cmd->target_nodeid = node->id;
 651     }
 652 
 653     cmd->device = strdup(device->id);
 654     cmd->timeout = get_action_timeout(device, cmd->action, cmd->default_timeout);
 655 
 656     if (cmd->remote_op_id) {
 657         crm_debug("Scheduling '%s' action%s%s using %s for remote peer %s "
 658                   "with op id %.8s and timeout %ds",
 659                   cmd->action,
 660                   (cmd->target == NULL)? "" : " targeting ",
 661                   pcmk__s(cmd->target, ""),
 662                   device->id, cmd->origin, cmd->remote_op_id, cmd->timeout);
 663     } else {
 664         crm_debug("Scheduling '%s' action%s%s using %s for %s with timeout %ds",
 665                   cmd->action,
 666                   (cmd->target == NULL)? "" : " targeting ",
 667                   pcmk__s(cmd->target, ""),
 668                   device->id, cmd->client, cmd->timeout);
 669     }
 670 
 671     device->pending_ops = g_list_append(device->pending_ops, cmd);
 672     mainloop_set_trigger(device->work);
 673 
 674     // Value -1 means disable any static/random fencing delays
 675     if (requested_delay < 0) {
 676         return;
 677     }
 678 
 679     delay_max = get_action_delay_max(device, cmd->action);
 680     delay_base = get_action_delay_base(device, cmd->action, cmd->target);
 681     if (delay_max == 0) {
 682         delay_max = delay_base;
 683     }
 684     if (delay_max < delay_base) {
 685         crm_warn(PCMK_STONITH_DELAY_BASE " (%ds) is larger than "
 686                  PCMK_STONITH_DELAY_MAX " (%ds) for %s using %s "
 687                  "(limiting to maximum delay)",
 688                  delay_base, delay_max, cmd->action, device->id);
 689         delay_base = delay_max;
 690     }
 691     if (delay_max > 0) {
 692         // coverity[dontcall] It doesn't matter here if rand() is predictable
 693         cmd->start_delay +=
 694             ((delay_max != delay_base)?(rand() % (delay_max - delay_base)):0)
 695             + delay_base;
 696     }
 697 
 698     if (cmd->start_delay > 0) {
 699         crm_notice("Delaying '%s' action%s%s using %s for %ds " CRM_XS
 700                    " timeout=%ds requested_delay=%ds base=%ds max=%ds",
 701                    cmd->action,
 702                    (cmd->target == NULL)? "" : " targeting ",
 703                    pcmk__s(cmd->target, ""),
 704                    device->id, cmd->start_delay, cmd->timeout,
 705                    requested_delay, delay_base, delay_max);
 706         cmd->delay_id =
 707             g_timeout_add_seconds(cmd->start_delay, start_delay_helper, cmd);
 708     }
 709 }
 710 
 711 static void
 712 free_device(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 713 {
 714     GList *gIter = NULL;
 715     stonith_device_t *device = data;
 716 
 717     g_hash_table_destroy(device->params);
 718     g_hash_table_destroy(device->aliases);
 719 
 720     for (gIter = device->pending_ops; gIter != NULL; gIter = gIter->next) {
 721         async_command_t *cmd = gIter->data;
 722 
 723         crm_warn("Removal of device '%s' purged operation '%s'", device->id, cmd->action);
 724         report_internal_result(cmd, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
 725                                "Device was removed before action could be executed");
 726     }
 727     g_list_free(device->pending_ops);
 728 
 729     g_list_free_full(device->targets, free);
 730 
 731     if (device->timer) {
 732         mainloop_timer_stop(device->timer);
 733         mainloop_timer_del(device->timer);
 734     }
 735 
 736     mainloop_destroy_trigger(device->work);
 737 
 738     free_xml(device->agent_metadata);
 739     free(device->namespace);
 740     if (device->on_target_actions != NULL) {
 741         g_string_free(device->on_target_actions, TRUE);
 742     }
 743     free(device->agent);
 744     free(device->id);
 745     free(device);
 746 }
 747 
 748 void free_device_list(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 749 {
 750     if (device_list != NULL) {
 751         g_hash_table_destroy(device_list);
 752         device_list = NULL;
 753     }
 754 }
 755 
 756 void
 757 init_device_list(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 758 {
 759     if (device_list == NULL) {
 760         device_list = pcmk__strkey_table(NULL, free_device);
 761     }
 762 }
 763 
 764 static GHashTable *
 765 build_port_aliases(const char *hostmap, GList ** targets)
     /* [previous][next][first][last][top][bottom][index][help] */
 766 {
 767     char *name = NULL;
 768     int last = 0, lpc = 0, max = 0, added = 0;
 769     GHashTable *aliases = pcmk__strikey_table(free, free);
 770 
 771     if (hostmap == NULL) {
 772         return aliases;
 773     }
 774 
 775     max = strlen(hostmap);
 776     for (; lpc <= max; lpc++) {
 777         switch (hostmap[lpc]) {
 778                 /* Skip escaped chars */
 779             case '\\':
 780                 lpc++;
 781                 break;
 782 
 783                 /* Assignment chars */
 784             case '=':
 785             case ':':
 786                 if (lpc > last) {
 787                     free(name);
 788                     name = calloc(1, 1 + lpc - last);
 789                     memcpy(name, hostmap + last, lpc - last);
 790                 }
 791                 last = lpc + 1;
 792                 break;
 793 
 794                 /* Delimeter chars */
 795                 /* case ',': Potentially used to specify multiple ports */
 796             case 0:
 797             case ';':
 798             case ' ':
 799             case '\t':
 800                 if (name) {
 801                     char *value = NULL;
 802                     int k = 0;
 803 
 804                     value = calloc(1, 1 + lpc - last);
 805                     memcpy(value, hostmap + last, lpc - last);
 806 
 807                     for (int i = 0; value[i] != '\0'; i++) {
 808                         if (value[i] != '\\') {
 809                             value[k++] = value[i];
 810                         }
 811                     }
 812                     value[k] = '\0';
 813 
 814                     crm_debug("Adding alias '%s'='%s'", name, value);
 815                     g_hash_table_replace(aliases, name, value);
 816                     if (targets) {
 817                         *targets = g_list_append(*targets, strdup(value));
 818                     }
 819                     value = NULL;
 820                     name = NULL;
 821                     added++;
 822 
 823                 } else if (lpc > last) {
 824                     crm_debug("Parse error at offset %d near '%s'", lpc - last, hostmap + last);
 825                 }
 826 
 827                 last = lpc + 1;
 828                 break;
 829         }
 830 
 831         if (hostmap[lpc] == 0) {
 832             break;
 833         }
 834     }
 835 
 836     if (added == 0) {
 837         crm_info("No host mappings detected in '%s'", hostmap);
 838     }
 839 
 840     free(name);
 841     return aliases;
 842 }
 843 
 844 GHashTable *metadata_cache = NULL;
 845 
 846 void
 847 free_metadata_cache(void) {
     /* [previous][next][first][last][top][bottom][index][help] */
 848     if (metadata_cache != NULL) {
 849         g_hash_table_destroy(metadata_cache);
 850         metadata_cache = NULL;
 851     }
 852 }
 853 
 854 static void
 855 init_metadata_cache(void) {
     /* [previous][next][first][last][top][bottom][index][help] */
 856     if (metadata_cache == NULL) {
 857         metadata_cache = pcmk__strkey_table(free, free);
 858     }
 859 }
 860 
 861 int
 862 get_agent_metadata(const char *agent, xmlNode ** metadata)
     /* [previous][next][first][last][top][bottom][index][help] */
 863 {
 864     char *buffer = NULL;
 865 
 866     if (metadata == NULL) {
 867         return EINVAL;
 868     }
 869     *metadata = NULL;
 870     if (pcmk__str_eq(agent, STONITH_WATCHDOG_AGENT_INTERNAL, pcmk__str_none)) {
 871         return pcmk_rc_ok;
 872     }
 873     init_metadata_cache();
 874     buffer = g_hash_table_lookup(metadata_cache, agent);
 875     if (buffer == NULL) {
 876         stonith_t *st = stonith_api_new();
 877         int rc;
 878 
 879         if (st == NULL) {
 880             crm_warn("Could not get agent meta-data: "
 881                      "API memory allocation failed");
 882             return EAGAIN;
 883         }
 884         rc = st->cmds->metadata(st, st_opt_sync_call, agent,
 885                                 NULL, &buffer, 10);
 886         stonith_api_delete(st);
 887         if (rc || !buffer) {
 888             crm_err("Could not retrieve metadata for fencing agent %s", agent);
 889             return EAGAIN;
 890         }
 891         g_hash_table_replace(metadata_cache, strdup(agent), buffer);
 892     }
 893 
 894     *metadata = string2xml(buffer);
 895     return pcmk_rc_ok;
 896 }
 897 
 898 static gboolean
 899 is_nodeid_required(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 900 {
 901     xmlXPathObjectPtr xpath = NULL;
 902 
 903     if (stand_alone) {
 904         return FALSE;
 905     }
 906 
 907     if (!xml) {
 908         return FALSE;
 909     }
 910 
 911     xpath = xpath_search(xml, "//parameter[@name='nodeid']");
 912     if (numXpathResults(xpath)  <= 0) {
 913         freeXpathObject(xpath);
 914         return FALSE;
 915     }
 916 
 917     freeXpathObject(xpath);
 918     return TRUE;
 919 }
 920 
 921 static void
 922 read_action_metadata(stonith_device_t *device)
     /* [previous][next][first][last][top][bottom][index][help] */
 923 {
 924     xmlXPathObjectPtr xpath = NULL;
 925     int max = 0;
 926     int lpc = 0;
 927 
 928     if (device->agent_metadata == NULL) {
 929         return;
 930     }
 931 
 932     xpath = xpath_search(device->agent_metadata, "//action");
 933     max = numXpathResults(xpath);
 934 
 935     if (max <= 0) {
 936         freeXpathObject(xpath);
 937         return;
 938     }
 939 
 940     for (lpc = 0; lpc < max; lpc++) {
 941         const char *action = NULL;
 942         xmlNode *match = getXpathResult(xpath, lpc);
 943 
 944         CRM_LOG_ASSERT(match != NULL);
 945         if(match == NULL) { continue; };
 946 
 947         action = crm_element_value(match, "name");
 948 
 949         if (pcmk__str_eq(action, PCMK_ACTION_LIST, pcmk__str_none)) {
 950             stonith__set_device_flags(device->flags, device->id,
 951                                       st_device_supports_list);
 952         } else if (pcmk__str_eq(action, PCMK_ACTION_STATUS, pcmk__str_none)) {
 953             stonith__set_device_flags(device->flags, device->id,
 954                                       st_device_supports_status);
 955         } else if (pcmk__str_eq(action, PCMK_ACTION_REBOOT, pcmk__str_none)) {
 956             stonith__set_device_flags(device->flags, device->id,
 957                                       st_device_supports_reboot);
 958         } else if (pcmk__str_eq(action, PCMK_ACTION_ON, pcmk__str_none)) {
 959             /* "automatic" means the cluster will unfence node when it joins */
 960             /* "required" is a deprecated synonym for "automatic" */
 961             if (pcmk__xe_attr_is_true(match, "automatic") || pcmk__xe_attr_is_true(match, "required")) {
 962                 device->automatic_unfencing = TRUE;
 963             }
 964             stonith__set_device_flags(device->flags, device->id,
 965                                       st_device_supports_on);
 966         }
 967 
 968         if ((action != NULL) && pcmk__xe_attr_is_true(match, "on_target")) {
 969             pcmk__add_word(&(device->on_target_actions), 64, action);
 970         }
 971     }
 972 
 973     freeXpathObject(xpath);
 974 }
 975 
 976 /*!
 977  * \internal
 978  * \brief Set a pcmk_*_action parameter if not already set
 979  *
 980  * \param[in,out] params  Device parameters
 981  * \param[in]     action  Name of action
 982  * \param[in]     value   Value to use if action is not already set
 983  */
 984 static void
 985 map_action(GHashTable *params, const char *action, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
 986 {
 987     char *key = crm_strdup_printf("pcmk_%s_action", action);
 988 
 989     if (g_hash_table_lookup(params, key)) {
 990         crm_warn("Ignoring %s='%s', see %s instead",
 991                  STONITH_ATTR_ACTION_OP, value, key);
 992         free(key);
 993     } else {
 994         crm_warn("Mapping %s='%s' to %s='%s'",
 995                  STONITH_ATTR_ACTION_OP, value, key, value);
 996         g_hash_table_insert(params, key, strdup(value));
 997     }
 998 }
 999 
1000 /*!
1001  * \internal
1002  * \brief Create device parameter table from XML
1003  *
1004  * \param[in]  name    Device name (used for logging only)
1005  * \param[in]  dev     XML containing device parameters
1006  */
1007 static GHashTable *
1008 xml2device_params(const char *name, const xmlNode *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
1009 {
1010     GHashTable *params = xml2list(dev);
1011     const char *value;
1012 
1013     /* Action should never be specified in the device configuration,
1014      * but we support it for users who are familiar with other software
1015      * that worked that way.
1016      */
1017     value = g_hash_table_lookup(params, STONITH_ATTR_ACTION_OP);
1018     if (value != NULL) {
1019         crm_warn("%s has '%s' parameter, which should never be specified in configuration",
1020                  name, STONITH_ATTR_ACTION_OP);
1021 
1022         if (*value == '\0') {
1023             crm_warn("Ignoring empty '%s' parameter", STONITH_ATTR_ACTION_OP);
1024 
1025         } else if (strcmp(value, PCMK_ACTION_REBOOT) == 0) {
1026             crm_warn("Ignoring %s='reboot' (see stonith-action cluster property instead)",
1027                      STONITH_ATTR_ACTION_OP);
1028 
1029         } else if (strcmp(value, PCMK_ACTION_OFF) == 0) {
1030             map_action(params, PCMK_ACTION_REBOOT, value);
1031 
1032         } else {
1033             map_action(params, PCMK_ACTION_OFF, value);
1034             map_action(params, PCMK_ACTION_REBOOT, value);
1035         }
1036 
1037         g_hash_table_remove(params, STONITH_ATTR_ACTION_OP);
1038     }
1039 
1040     return params;
1041 }
1042 
1043 static const char *
1044 target_list_type(stonith_device_t * dev)
     /* [previous][next][first][last][top][bottom][index][help] */
1045 {
1046     const char *check_type = NULL;
1047 
1048     check_type = g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK);
1049 
1050     if (check_type == NULL) {
1051 
1052         if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_LIST)) {
1053             check_type = "static-list";
1054         } else if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_MAP)) {
1055             check_type = "static-list";
1056         } else if (pcmk_is_set(dev->flags, st_device_supports_list)) {
1057             check_type = "dynamic-list";
1058         } else if (pcmk_is_set(dev->flags, st_device_supports_status)) {
1059             check_type = "status";
1060         } else {
1061             check_type = PCMK__VALUE_NONE;
1062         }
1063     }
1064 
1065     return check_type;
1066 }
1067 
1068 static stonith_device_t *
1069 build_device_from_xml(xmlNode *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
1070 {
1071     const char *value;
1072     stonith_device_t *device = NULL;
1073     char *agent = crm_element_value_copy(dev, "agent");
1074 
1075     CRM_CHECK(agent != NULL, return device);
1076 
1077     device = calloc(1, sizeof(stonith_device_t));
1078 
1079     CRM_CHECK(device != NULL, {free(agent); return device;});
1080 
1081     device->id = crm_element_value_copy(dev, XML_ATTR_ID);
1082     device->agent = agent;
1083     device->namespace = crm_element_value_copy(dev, "namespace");
1084     device->params = xml2device_params(device->id, dev);
1085 
1086     value = g_hash_table_lookup(device->params, PCMK_STONITH_HOST_LIST);
1087     if (value) {
1088         device->targets = stonith__parse_targets(value);
1089     }
1090 
1091     value = g_hash_table_lookup(device->params, PCMK_STONITH_HOST_MAP);
1092     device->aliases = build_port_aliases(value, &(device->targets));
1093 
1094     value = target_list_type(device);
1095     if (!pcmk__str_eq(value, "static-list", pcmk__str_casei) && device->targets) {
1096         /* Other than "static-list", dev-> targets is unnecessary. */
1097         g_list_free_full(device->targets, free);
1098         device->targets = NULL;
1099     }
1100     switch (get_agent_metadata(device->agent, &device->agent_metadata)) {
1101         case pcmk_rc_ok:
1102             if (device->agent_metadata) {
1103                 read_action_metadata(device);
1104                 stonith__device_parameter_flags(&(device->flags), device->id,
1105                                                 device->agent_metadata);
1106             }
1107             break;
1108 
1109         case EAGAIN:
1110             if (device->timer == NULL) {
1111                 device->timer = mainloop_timer_add("get_agent_metadata", 10 * 1000,
1112                                            TRUE, get_agent_metadata_cb, device);
1113             }
1114             if (!mainloop_timer_running(device->timer)) {
1115                 mainloop_timer_start(device->timer);
1116             }
1117             break;
1118 
1119         default:
1120             break;
1121     }
1122 
1123     value = g_hash_table_lookup(device->params, "nodeid");
1124     if (!value) {
1125         device->include_nodeid = is_nodeid_required(device->agent_metadata);
1126     }
1127 
1128     value = crm_element_value(dev, "rsc_provides");
1129     if (pcmk__str_eq(value, PCMK__VALUE_UNFENCING, pcmk__str_casei)) {
1130         device->automatic_unfencing = TRUE;
1131     }
1132 
1133     if (is_action_required(PCMK_ACTION_ON, device)) {
1134         crm_info("Fencing device '%s' requires unfencing", device->id);
1135     }
1136 
1137     if (device->on_target_actions != NULL) {
1138         crm_info("Fencing device '%s' requires actions (%s) to be executed "
1139                  "on target", device->id,
1140                  (const char *) device->on_target_actions->str);
1141     }
1142 
1143     device->work = mainloop_add_trigger(G_PRIORITY_HIGH, stonith_device_dispatch, device);
1144     /* TODO: Hook up priority */
1145 
1146     return device;
1147 }
1148 
1149 static void
1150 schedule_internal_command(const char *origin,
     /* [previous][next][first][last][top][bottom][index][help] */
1151                           stonith_device_t * device,
1152                           const char *action,
1153                           const char *target,
1154                           int timeout,
1155                           void *internal_user_data,
1156                           void (*done_cb) (int pid,
1157                                            const pcmk__action_result_t *result,
1158                                            void *user_data))
1159 {
1160     async_command_t *cmd = NULL;
1161 
1162     cmd = calloc(1, sizeof(async_command_t));
1163 
1164     cmd->id = -1;
1165     cmd->default_timeout = timeout ? timeout : 60;
1166     cmd->timeout = cmd->default_timeout;
1167     cmd->action = strdup(action);
1168     pcmk__str_update(&cmd->target, target);
1169     cmd->device = strdup(device->id);
1170     cmd->origin = strdup(origin);
1171     cmd->client = strdup(crm_system_name);
1172     cmd->client_name = strdup(crm_system_name);
1173 
1174     cmd->internal_user_data = internal_user_data;
1175     cmd->done_cb = done_cb; /* cmd, not internal_user_data, is passed to 'done_cb' as the userdata */
1176 
1177     schedule_stonith_command(cmd, device);
1178 }
1179 
1180 // Fence agent status commands use custom exit status codes
1181 enum fence_status_code {
1182     fence_status_invalid    = -1,
1183     fence_status_active     = 0,
1184     fence_status_unknown    = 1,
1185     fence_status_inactive   = 2,
1186 };
1187 
1188 static void
1189 status_search_cb(int pid, const pcmk__action_result_t *result, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1190 {
1191     async_command_t *cmd = user_data;
1192     struct device_search_s *search = cmd->internal_user_data;
1193     stonith_device_t *dev = cmd_device(cmd);
1194     gboolean can = FALSE;
1195 
1196     free_async_command(cmd);
1197 
1198     if (!dev) {
1199         search_devices_record_result(search, NULL, FALSE);
1200         return;
1201     }
1202 
1203     mainloop_set_trigger(dev->work);
1204 
1205     if (result->execution_status != PCMK_EXEC_DONE) {
1206         crm_warn("Assuming %s cannot fence %s "
1207                  "because status could not be executed: %s%s%s%s",
1208                  dev->id, search->host,
1209                  pcmk_exec_status_str(result->execution_status),
1210                  ((result->exit_reason == NULL)? "" : " ("),
1211                  ((result->exit_reason == NULL)? "" : result->exit_reason),
1212                  ((result->exit_reason == NULL)? "" : ")"));
1213         search_devices_record_result(search, dev->id, FALSE);
1214         return;
1215     }
1216 
1217     switch (result->exit_status) {
1218         case fence_status_unknown:
1219             crm_trace("%s reported it cannot fence %s", dev->id, search->host);
1220             break;
1221 
1222         case fence_status_active:
1223         case fence_status_inactive:
1224             crm_trace("%s reported it can fence %s", dev->id, search->host);
1225             can = TRUE;
1226             break;
1227 
1228         default:
1229             crm_warn("Assuming %s cannot fence %s "
1230                      "(status returned unknown code %d)",
1231                      dev->id, search->host, result->exit_status);
1232             break;
1233     }
1234     search_devices_record_result(search, dev->id, can);
1235 }
1236 
1237 static void
1238 dynamic_list_search_cb(int pid, const pcmk__action_result_t *result,
     /* [previous][next][first][last][top][bottom][index][help] */
1239                        void *user_data)
1240 {
1241     async_command_t *cmd = user_data;
1242     struct device_search_s *search = cmd->internal_user_data;
1243     stonith_device_t *dev = cmd_device(cmd);
1244     gboolean can_fence = FALSE;
1245 
1246     free_async_command(cmd);
1247 
1248     /* Host/alias must be in the list output to be eligible to be fenced
1249      *
1250      * Will cause problems if down'd nodes aren't listed or (for virtual nodes)
1251      *  if the guest is still listed despite being moved to another machine
1252      */
1253     if (!dev) {
1254         search_devices_record_result(search, NULL, FALSE);
1255         return;
1256     }
1257 
1258     mainloop_set_trigger(dev->work);
1259 
1260     if (pcmk__result_ok(result)) {
1261         crm_info("Refreshing target list for %s", dev->id);
1262         g_list_free_full(dev->targets, free);
1263         dev->targets = stonith__parse_targets(result->action_stdout);
1264         dev->targets_age = time(NULL);
1265 
1266     } else if (dev->targets != NULL) {
1267         if (result->execution_status == PCMK_EXEC_DONE) {
1268             crm_info("Reusing most recent target list for %s "
1269                      "because list returned error code %d",
1270                      dev->id, result->exit_status);
1271         } else {
1272             crm_info("Reusing most recent target list for %s "
1273                      "because list could not be executed: %s%s%s%s",
1274                      dev->id, pcmk_exec_status_str(result->execution_status),
1275                      ((result->exit_reason == NULL)? "" : " ("),
1276                      ((result->exit_reason == NULL)? "" : result->exit_reason),
1277                      ((result->exit_reason == NULL)? "" : ")"));
1278         }
1279 
1280     } else { // We have never successfully executed list
1281         if (result->execution_status == PCMK_EXEC_DONE) {
1282             crm_warn("Assuming %s cannot fence %s "
1283                      "because list returned error code %d",
1284                      dev->id, search->host, result->exit_status);
1285         } else {
1286             crm_warn("Assuming %s cannot fence %s "
1287                      "because list could not be executed: %s%s%s%s",
1288                      dev->id, search->host,
1289                      pcmk_exec_status_str(result->execution_status),
1290                      ((result->exit_reason == NULL)? "" : " ("),
1291                      ((result->exit_reason == NULL)? "" : result->exit_reason),
1292                      ((result->exit_reason == NULL)? "" : ")"));
1293         }
1294 
1295         /* Fall back to pcmk_host_check="status" if the user didn't explicitly
1296          * specify "dynamic-list".
1297          */
1298         if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_CHECK) == NULL) {
1299             crm_notice("Switching to pcmk_host_check='status' for %s", dev->id);
1300             g_hash_table_replace(dev->params, strdup(PCMK_STONITH_HOST_CHECK),
1301                                  strdup("status"));
1302         }
1303     }
1304 
1305     if (dev->targets) {
1306         const char *alias = g_hash_table_lookup(dev->aliases, search->host);
1307 
1308         if (!alias) {
1309             alias = search->host;
1310         }
1311         if (pcmk__str_in_list(alias, dev->targets, pcmk__str_casei)) {
1312             can_fence = TRUE;
1313         }
1314     }
1315     search_devices_record_result(search, dev->id, can_fence);
1316 }
1317 
1318 /*!
1319  * \internal
1320  * \brief Returns true if any key in first is not in second or second has a different value for key
1321  */
1322 static int
1323 device_params_diff(GHashTable *first, GHashTable *second) {
     /* [previous][next][first][last][top][bottom][index][help] */
1324     char *key = NULL;
1325     char *value = NULL;
1326     GHashTableIter gIter;
1327 
1328     g_hash_table_iter_init(&gIter, first);
1329     while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&value)) {
1330 
1331         if(strstr(key, "CRM_meta") == key) {
1332             continue;
1333         } else if(strcmp(key, "crm_feature_set") == 0) {
1334             continue;
1335         } else {
1336             char *other_value = g_hash_table_lookup(second, key);
1337 
1338             if (!other_value || !pcmk__str_eq(other_value, value, pcmk__str_casei)) {
1339                 crm_trace("Different value for %s: %s != %s", key, other_value, value);
1340                 return 1;
1341             }
1342         }
1343     }
1344 
1345     return 0;
1346 }
1347 
1348 /*!
1349  * \internal
1350  * \brief Checks to see if an identical device already exists in the device_list
1351  */
1352 static stonith_device_t *
1353 device_has_duplicate(const stonith_device_t *device)
     /* [previous][next][first][last][top][bottom][index][help] */
1354 {
1355     stonith_device_t *dup = g_hash_table_lookup(device_list, device->id);
1356 
1357     if (!dup) {
1358         crm_trace("No match for %s", device->id);
1359         return NULL;
1360 
1361     } else if (!pcmk__str_eq(dup->agent, device->agent, pcmk__str_casei)) {
1362         crm_trace("Different agent: %s != %s", dup->agent, device->agent);
1363         return NULL;
1364     }
1365 
1366     /* Use calculate_operation_digest() here? */
1367     if (device_params_diff(device->params, dup->params) ||
1368         device_params_diff(dup->params, device->params)) {
1369         return NULL;
1370     }
1371 
1372     crm_trace("Match");
1373     return dup;
1374 }
1375 
1376 int
1377 stonith_device_register(xmlNode *dev, gboolean from_cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1378 {
1379     stonith_device_t *dup = NULL;
1380     stonith_device_t *device = build_device_from_xml(dev);
1381     guint ndevices = 0;
1382     int rv = pcmk_ok;
1383 
1384     CRM_CHECK(device != NULL, return -ENOMEM);
1385 
1386     /* do we have a watchdog-device? */
1387     if (pcmk__str_eq(device->id, STONITH_WATCHDOG_ID, pcmk__str_none) ||
1388         pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
1389                      STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) do {
1390         if (stonith_watchdog_timeout_ms <= 0) {
1391             crm_err("Ignoring watchdog fence device without "
1392                     "stonith-watchdog-timeout set.");
1393             rv = -ENODEV;
1394             /* fall through to cleanup & return */
1395         } else if (!pcmk__str_any_of(device->agent, STONITH_WATCHDOG_AGENT,
1396                                  STONITH_WATCHDOG_AGENT_INTERNAL, NULL)) {
1397             crm_err("Ignoring watchdog fence device with unknown "
1398                     "agent '%s' unequal '" STONITH_WATCHDOG_AGENT "'.",
1399                     device->agent?device->agent:"");
1400             rv = -ENODEV;
1401             /* fall through to cleanup & return */
1402         } else if (!pcmk__str_eq(device->id, STONITH_WATCHDOG_ID,
1403                                  pcmk__str_none)) {
1404             crm_err("Ignoring watchdog fence device "
1405                     "named %s !='"STONITH_WATCHDOG_ID"'.",
1406                     device->id?device->id:"");
1407             rv = -ENODEV;
1408             /* fall through to cleanup & return */
1409         } else {
1410             if (pcmk__str_eq(device->agent, STONITH_WATCHDOG_AGENT,
1411                              pcmk__str_none)) {
1412                 /* this either has an empty list or the targets
1413                    configured for watchdog-fencing
1414                  */
1415                 g_list_free_full(stonith_watchdog_targets, free);
1416                 stonith_watchdog_targets = device->targets;
1417                 device->targets = NULL;
1418             }
1419             if (node_does_watchdog_fencing(stonith_our_uname)) {
1420                 g_list_free_full(device->targets, free);
1421                 device->targets = stonith__parse_targets(stonith_our_uname);
1422                 g_hash_table_replace(device->params,
1423                                      strdup(PCMK_STONITH_HOST_LIST),
1424                                      strdup(stonith_our_uname));
1425                 /* proceed as with any other stonith-device */
1426                 break;
1427             }
1428 
1429             crm_debug("Skip registration of watchdog fence device on node not in host-list.");
1430             /* cleanup and fall through to more cleanup and return */
1431             device->targets = NULL;
1432             stonith_device_remove(device->id, from_cib);
1433         }
1434         free_device(device);
1435         return rv;
1436     } while (0);
1437 
1438     dup = device_has_duplicate(device);
1439     if (dup) {
1440         ndevices = g_hash_table_size(device_list);
1441         crm_debug("Device '%s' already in device list (%d active device%s)",
1442                   device->id, ndevices, pcmk__plural_s(ndevices));
1443         free_device(device);
1444         device = dup;
1445         dup = g_hash_table_lookup(device_list, device->id);
1446         dup->dirty = FALSE;
1447 
1448     } else {
1449         stonith_device_t *old = g_hash_table_lookup(device_list, device->id);
1450 
1451         if (from_cib && old && old->api_registered) {
1452             /* If the cib is writing over an entry that is shared with a stonith client,
1453              * copy any pending ops that currently exist on the old entry to the new one.
1454              * Otherwise the pending ops will be reported as failures
1455              */
1456             crm_info("Overwriting existing entry for %s from CIB", device->id);
1457             device->pending_ops = old->pending_ops;
1458             device->api_registered = TRUE;
1459             old->pending_ops = NULL;
1460             if (device->pending_ops) {
1461                 mainloop_set_trigger(device->work);
1462             }
1463         }
1464         g_hash_table_replace(device_list, device->id, device);
1465 
1466         ndevices = g_hash_table_size(device_list);
1467         crm_notice("Added '%s' to device list (%d active device%s)",
1468                    device->id, ndevices, pcmk__plural_s(ndevices));
1469     }
1470 
1471     if (from_cib) {
1472         device->cib_registered = TRUE;
1473     } else {
1474         device->api_registered = TRUE;
1475     }
1476 
1477     return pcmk_ok;
1478 }
1479 
1480 void
1481 stonith_device_remove(const char *id, bool from_cib)
     /* [previous][next][first][last][top][bottom][index][help] */
1482 {
1483     stonith_device_t *device = g_hash_table_lookup(device_list, id);
1484     guint ndevices = 0;
1485 
1486     if (!device) {
1487         ndevices = g_hash_table_size(device_list);
1488         crm_info("Device '%s' not found (%d active device%s)",
1489                  id, ndevices, pcmk__plural_s(ndevices));
1490         return;
1491     }
1492 
1493     if (from_cib) {
1494         device->cib_registered = FALSE;
1495     } else {
1496         device->verified = FALSE;
1497         device->api_registered = FALSE;
1498     }
1499 
1500     if (!device->cib_registered && !device->api_registered) {
1501         g_hash_table_remove(device_list, id);
1502         ndevices = g_hash_table_size(device_list);
1503         crm_info("Removed '%s' from device list (%d active device%s)",
1504                  id, ndevices, pcmk__plural_s(ndevices));
1505     } else {
1506         crm_trace("Not removing '%s' from device list (%d active) because "
1507                   "still registered via:%s%s",
1508                   id, g_hash_table_size(device_list),
1509                   (device->cib_registered? " cib" : ""),
1510                   (device->api_registered? " api" : ""));
1511     }
1512 }
1513 
1514 /*!
1515  * \internal
1516  * \brief Return the number of stonith levels registered for a node
1517  *
1518  * \param[in] tp  Node's topology table entry
1519  *
1520  * \return Number of non-NULL levels in topology entry
1521  * \note This function is used only for log messages.
1522  */
1523 static int
1524 count_active_levels(const stonith_topology_t *tp)
     /* [previous][next][first][last][top][bottom][index][help] */
1525 {
1526     int lpc = 0;
1527     int count = 0;
1528 
1529     for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
1530         if (tp->levels[lpc] != NULL) {
1531             count++;
1532         }
1533     }
1534     return count;
1535 }
1536 
1537 static void
1538 free_topology_entry(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
1539 {
1540     stonith_topology_t *tp = data;
1541 
1542     int lpc = 0;
1543 
1544     for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
1545         if (tp->levels[lpc] != NULL) {
1546             g_list_free_full(tp->levels[lpc], free);
1547         }
1548     }
1549     free(tp->target);
1550     free(tp->target_value);
1551     free(tp->target_pattern);
1552     free(tp->target_attribute);
1553     free(tp);
1554 }
1555 
1556 void
1557 free_topology_list(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1558 {
1559     if (topology != NULL) {
1560         g_hash_table_destroy(topology);
1561         topology = NULL;
1562     }
1563 }
1564 
1565 void
1566 init_topology_list(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1567 {
1568     if (topology == NULL) {
1569         topology = pcmk__strkey_table(NULL, free_topology_entry);
1570     }
1571 }
1572 
1573 char *
1574 stonith_level_key(const xmlNode *level, enum fenced_target_by mode)
     /* [previous][next][first][last][top][bottom][index][help] */
1575 {
1576     if (mode == fenced_target_by_unknown) {
1577         mode = unpack_level_kind(level);
1578     }
1579     switch (mode) {
1580         case fenced_target_by_name:
1581             return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET);
1582 
1583         case fenced_target_by_pattern:
1584             return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
1585 
1586         case fenced_target_by_attribute:
1587             return crm_strdup_printf("%s=%s",
1588                 crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE),
1589                 crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE));
1590 
1591         default:
1592             return crm_strdup_printf("unknown-%s", ID(level));
1593     }
1594 }
1595 
1596 /*!
1597  * \internal
1598  * \brief Parse target identification from topology level XML
1599  *
1600  * \param[in] level  Topology level XML to parse
1601  *
1602  * \return How to identify target of \p level
1603  */
1604 static enum fenced_target_by
1605 unpack_level_kind(const xmlNode *level)
     /* [previous][next][first][last][top][bottom][index][help] */
1606 {
1607     if (crm_element_value(level, XML_ATTR_STONITH_TARGET) != NULL) {
1608         return fenced_target_by_name;
1609     }
1610     if (crm_element_value(level, XML_ATTR_STONITH_TARGET_PATTERN) != NULL) {
1611         return fenced_target_by_pattern;
1612     }
1613     if (!stand_alone /* if standalone, there's no attribute manager */
1614         && (crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE) != NULL)
1615         && (crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE) != NULL)) {
1616         return fenced_target_by_attribute;
1617     }
1618     return fenced_target_by_unknown;
1619 }
1620 
1621 static stonith_key_value_t *
1622 parse_device_list(const char *devices)
     /* [previous][next][first][last][top][bottom][index][help] */
1623 {
1624     int lpc = 0;
1625     int max = 0;
1626     int last = 0;
1627     stonith_key_value_t *output = NULL;
1628 
1629     if (devices == NULL) {
1630         return output;
1631     }
1632 
1633     max = strlen(devices);
1634     for (lpc = 0; lpc <= max; lpc++) {
1635         if (devices[lpc] == ',' || devices[lpc] == 0) {
1636             char *line = strndup(devices + last, lpc - last);
1637 
1638             output = stonith_key_value_add(output, NULL, line);
1639             free(line);
1640 
1641             last = lpc + 1;
1642         }
1643     }
1644 
1645     return output;
1646 }
1647 
1648 /*!
1649  * \internal
1650  * \brief Unpack essential information from topology request XML
1651  *
1652  * \param[in]  xml     Request XML to search
1653  * \param[out] mode    If not NULL, where to store level kind
1654  * \param[out] target  If not NULL, where to store representation of target
1655  * \param[out] id      If not NULL, where to store level number
1656  * \param[out] desc    If not NULL, where to store log-friendly level description
1657  *
1658  * \return Topology level XML from within \p xml, or NULL if not found
1659  * \note The caller is responsible for freeing \p *target and \p *desc if set.
1660  */
1661 static xmlNode *
1662 unpack_level_request(xmlNode *xml, enum fenced_target_by *mode, char **target,
     /* [previous][next][first][last][top][bottom][index][help] */
1663                      int *id, char **desc)
1664 {
1665     enum fenced_target_by local_mode = fenced_target_by_unknown;
1666     char *local_target = NULL;
1667     int local_id = 0;
1668 
1669     /* The level element can be the top element or lower. If top level, don't
1670      * search by xpath, because it might give multiple hits if the XML is the
1671      * CIB.
1672      */
1673     if ((xml != NULL) && !pcmk__xe_is(xml, XML_TAG_FENCING_LEVEL)) {
1674         xml = get_xpath_object("//" XML_TAG_FENCING_LEVEL, xml, LOG_WARNING);
1675     }
1676 
1677     if (xml == NULL) {
1678         if (desc != NULL) {
1679             *desc = crm_strdup_printf("missing");
1680         }
1681     } else {
1682         local_mode = unpack_level_kind(xml);
1683         local_target = stonith_level_key(xml, local_mode);
1684         crm_element_value_int(xml, XML_ATTR_STONITH_INDEX, &local_id);
1685         if (desc != NULL) {
1686             *desc = crm_strdup_printf("%s[%d]", local_target, local_id);
1687         }
1688     }
1689 
1690     if (mode != NULL) {
1691         *mode = local_mode;
1692     }
1693     if (id != NULL) {
1694         *id = local_id;
1695     }
1696 
1697     if (target != NULL) {
1698         *target = local_target;
1699     } else {
1700         free(local_target);
1701     }
1702 
1703     return xml;
1704 }
1705 
1706 /*!
1707  * \internal
1708  * \brief Register a fencing topology level for a target
1709  *
1710  * Given an XML request specifying the target name, level index, and device IDs
1711  * for the level, this will create an entry for the target in the global topology
1712  * table if one does not already exist, then append the specified device IDs to
1713  * the entry's device list for the specified level.
1714  *
1715  * \param[in]  msg     XML request for STONITH level registration
1716  * \param[out] desc    If not NULL, set to string representation "TARGET[LEVEL]"
1717  * \param[out] result  Where to set result of registration
1718  */
1719 void
1720 fenced_register_level(xmlNode *msg, char **desc, pcmk__action_result_t *result)
     /* [previous][next][first][last][top][bottom][index][help] */
1721 {
1722     int id = 0;
1723     xmlNode *level;
1724     enum fenced_target_by mode;
1725     char *target;
1726 
1727     stonith_topology_t *tp;
1728     stonith_key_value_t *dIter = NULL;
1729     stonith_key_value_t *devices = NULL;
1730 
1731     CRM_CHECK((msg != NULL) && (result != NULL), return);
1732 
1733     level = unpack_level_request(msg, &mode, &target, &id, desc);
1734     if (level == NULL) {
1735         fenced_set_protocol_error(result);
1736         return;
1737     }
1738 
1739     // Ensure an ID was given (even the client API adds an ID)
1740     if (pcmk__str_empty(ID(level))) {
1741         crm_warn("Ignoring registration for topology level without ID");
1742         free(target);
1743         crm_log_xml_trace(level, "Bad level");
1744         pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
1745                             "Topology level is invalid without ID");
1746         return;
1747     }
1748 
1749     // Ensure a valid target was specified
1750     if (mode == fenced_target_by_unknown) {
1751         crm_warn("Ignoring registration for topology level '%s' "
1752                  "without valid target", ID(level));
1753         free(target);
1754         crm_log_xml_trace(level, "Bad level");
1755         pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
1756                             "Invalid target for topology level '%s'",
1757                             ID(level));
1758         return;
1759     }
1760 
1761     // Ensure level ID is in allowed range
1762     if ((id <= 0) || (id >= ST_LEVEL_MAX)) {
1763         crm_warn("Ignoring topology registration for %s with invalid level %d",
1764                   target, id);
1765         free(target);
1766         crm_log_xml_trace(level, "Bad level");
1767         pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
1768                             "Invalid level number '%s' for topology level '%s'",
1769                             pcmk__s(crm_element_value(level,
1770                                                       XML_ATTR_STONITH_INDEX),
1771                                     ""),
1772                             ID(level));
1773         return;
1774     }
1775 
1776     /* Find or create topology table entry */
1777     tp = g_hash_table_lookup(topology, target);
1778     if (tp == NULL) {
1779         tp = calloc(1, sizeof(stonith_topology_t));
1780         if (tp == NULL) {
1781             pcmk__set_result(result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
1782                              strerror(ENOMEM));
1783             free(target);
1784             return;
1785         }
1786         tp->kind = mode;
1787         tp->target = target;
1788         tp->target_value = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_VALUE);
1789         tp->target_pattern = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
1790         tp->target_attribute = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE);
1791 
1792         g_hash_table_replace(topology, tp->target, tp);
1793         crm_trace("Added %s (%d) to the topology (%d active entries)",
1794                   target, (int) mode, g_hash_table_size(topology));
1795     } else {
1796         free(target);
1797     }
1798 
1799     if (tp->levels[id] != NULL) {
1800         crm_info("Adding to the existing %s[%d] topology entry",
1801                  tp->target, id);
1802     }
1803 
1804     devices = parse_device_list(crm_element_value(level, XML_ATTR_STONITH_DEVICES));
1805     for (dIter = devices; dIter; dIter = dIter->next) {
1806         const char *device = dIter->value;
1807 
1808         crm_trace("Adding device '%s' for %s[%d]", device, tp->target, id);
1809         tp->levels[id] = g_list_append(tp->levels[id], strdup(device));
1810     }
1811     stonith_key_value_freeall(devices, 1, 1);
1812 
1813     {
1814         int nlevels = count_active_levels(tp);
1815 
1816         crm_info("Target %s has %d active fencing level%s",
1817                  tp->target, nlevels, pcmk__plural_s(nlevels));
1818     }
1819 
1820     pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
1821 }
1822 
1823 /*!
1824  * \internal
1825  * \brief Unregister a fencing topology level for a target
1826  *
1827  * Given an XML request specifying the target name and level index (or 0 for all
1828  * levels), this will remove any corresponding entry for the target from the
1829  * global topology table.
1830  *
1831  * \param[in]  msg     XML request for STONITH level registration
1832  * \param[out] desc    If not NULL, set to string representation "TARGET[LEVEL]"
1833  * \param[out] result  Where to set result of unregistration
1834  */
1835 void
1836 fenced_unregister_level(xmlNode *msg, char **desc,
     /* [previous][next][first][last][top][bottom][index][help] */
1837                         pcmk__action_result_t *result)
1838 {
1839     int id = -1;
1840     stonith_topology_t *tp;
1841     char *target;
1842     xmlNode *level = NULL;
1843 
1844     CRM_CHECK(result != NULL, return);
1845 
1846     level = unpack_level_request(msg, NULL, &target, &id, desc);
1847     if (level == NULL) {
1848         fenced_set_protocol_error(result);
1849         return;
1850     }
1851 
1852     // Ensure level ID is in allowed range
1853     if ((id < 0) || (id >= ST_LEVEL_MAX)) {
1854         crm_warn("Ignoring topology unregistration for %s with invalid level %d",
1855                   target, id);
1856         free(target);
1857         crm_log_xml_trace(level, "Bad level");
1858         pcmk__format_result(result, CRM_EX_INVALID_PARAM, PCMK_EXEC_INVALID,
1859                             "Invalid level number '%s' for topology level %s",
1860                             pcmk__s(crm_element_value(level,
1861                                                       XML_ATTR_STONITH_INDEX),
1862                                     "<null>"),
1863 
1864                             // Client API doesn't add ID to unregistration XML
1865                             pcmk__s(ID(level), ""));
1866         return;
1867     }
1868 
1869     tp = g_hash_table_lookup(topology, target);
1870     if (tp == NULL) {
1871         guint nentries = g_hash_table_size(topology);
1872 
1873         crm_info("No fencing topology found for %s (%d active %s)",
1874                  target, nentries,
1875                  pcmk__plural_alt(nentries, "entry", "entries"));
1876 
1877     } else if (id == 0 && g_hash_table_remove(topology, target)) {
1878         guint nentries = g_hash_table_size(topology);
1879 
1880         crm_info("Removed all fencing topology entries related to %s "
1881                  "(%d active %s remaining)", target, nentries,
1882                  pcmk__plural_alt(nentries, "entry", "entries"));
1883 
1884     } else if (tp->levels[id] != NULL) {
1885         guint nlevels;
1886 
1887         g_list_free_full(tp->levels[id], free);
1888         tp->levels[id] = NULL;
1889 
1890         nlevels = count_active_levels(tp);
1891         crm_info("Removed level %d from fencing topology for %s "
1892                  "(%d active level%s remaining)",
1893                  id, target, nlevels, pcmk__plural_s(nlevels));
1894     }
1895 
1896     free(target);
1897     pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
1898 }
1899 
1900 static char *
1901 list_to_string(GList *list, const char *delim, gboolean terminate_with_delim)
     /* [previous][next][first][last][top][bottom][index][help] */
1902 {
1903     int max = g_list_length(list);
1904     size_t delim_len = delim?strlen(delim):0;
1905     size_t alloc_size = 1 + (max?((max-1+(terminate_with_delim?1:0))*delim_len):0);
1906     char *rv;
1907     GList *gIter;
1908 
1909     for (gIter = list; gIter != NULL; gIter = gIter->next) {
1910         const char *value = (const char *) gIter->data;
1911 
1912         alloc_size += strlen(value);
1913     }
1914     rv = calloc(alloc_size, sizeof(char));
1915     if (rv) {
1916         char *pos = rv;
1917         const char *lead_delim = "";
1918 
1919         for (gIter = list; gIter != NULL; gIter = gIter->next) {
1920             const char *value = (const char *) gIter->data;
1921 
1922             pos = &pos[sprintf(pos, "%s%s", lead_delim, value)];
1923             lead_delim = delim;
1924         }
1925         if (max && terminate_with_delim) {
1926             sprintf(pos, "%s", delim);
1927         }
1928     }
1929     return rv;
1930 }
1931 
1932 /*!
1933  * \internal
1934  * \brief Execute a fence agent action directly (and asynchronously)
1935  *
1936  * Handle a STONITH_OP_EXEC API message by scheduling a requested agent action
1937  * directly on a specified device. Only list, monitor, and status actions are
1938  * expected to use this call, though it should work with any agent command.
1939  *
1940  * \param[in]  msg     Request XML specifying action
1941  * \param[out] result  Where to store result of action
1942  *
1943  * \note If the action is monitor, the device must be registered via the API
1944  *       (CIB registration is not sufficient), because monitor should not be
1945  *       possible unless the device is "started" (API registered).
1946  */
1947 static void
1948 execute_agent_action(xmlNode *msg, pcmk__action_result_t *result)
     /* [previous][next][first][last][top][bottom][index][help] */
1949 {
1950     xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR);
1951     xmlNode *op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR);
1952     const char *id = crm_element_value(dev, F_STONITH_DEVICE);
1953     const char *action = crm_element_value(op, F_STONITH_ACTION);
1954     async_command_t *cmd = NULL;
1955     stonith_device_t *device = NULL;
1956 
1957     if ((id == NULL) || (action == NULL)) {
1958         crm_info("Malformed API action request: device %s, action %s",
1959                  (id? id : "not specified"),
1960                  (action? action : "not specified"));
1961         fenced_set_protocol_error(result);
1962         return;
1963     }
1964 
1965     if (pcmk__str_eq(id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
1966         // Watchdog agent actions are implemented internally
1967         if (stonith_watchdog_timeout_ms <= 0) {
1968             pcmk__set_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
1969                              "Watchdog fence device not configured");
1970             return;
1971 
1972         } else if (pcmk__str_eq(action, PCMK_ACTION_LIST, pcmk__str_none)) {
1973             pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
1974             pcmk__set_result_output(result,
1975                                     list_to_string(stonith_watchdog_targets,
1976                                                    "\n", TRUE),
1977                                     NULL);
1978             return;
1979 
1980         } else if (pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_none)) {
1981             pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
1982             return;
1983         }
1984     }
1985 
1986     device = g_hash_table_lookup(device_list, id);
1987     if (device == NULL) {
1988         crm_info("Ignoring API '%s' action request because device %s not found",
1989                  action, id);
1990         pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
1991                             "'%s' not found", id);
1992         return;
1993 
1994     } else if (!device->api_registered
1995                && (strcmp(action, PCMK_ACTION_MONITOR) == 0)) {
1996         // Monitors may run only on "started" (API-registered) devices
1997         crm_info("Ignoring API '%s' action request because device %s not active",
1998                  action, id);
1999         pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
2000                             "'%s' not active", id);
2001         return;
2002     }
2003 
2004     cmd = create_async_command(msg);
2005     if (cmd == NULL) {
2006         crm_log_xml_warn(msg, "invalid");
2007         fenced_set_protocol_error(result);
2008         return;
2009     }
2010 
2011     schedule_stonith_command(cmd, device);
2012     pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
2013 }
2014 
2015 static void
2016 search_devices_record_result(struct device_search_s *search, const char *device, gboolean can_fence)
     /* [previous][next][first][last][top][bottom][index][help] */
2017 {
2018     search->replies_received++;
2019     if (can_fence && device) {
2020         if (search->support_action_only != st_device_supports_none) {
2021             stonith_device_t *dev = g_hash_table_lookup(device_list, device);
2022             if (dev && !pcmk_is_set(dev->flags, search->support_action_only)) {
2023                 return;
2024             }
2025         }
2026         search->capable = g_list_append(search->capable, strdup(device));
2027     }
2028 
2029     if (search->replies_needed == search->replies_received) {
2030 
2031         guint ndevices = g_list_length(search->capable);
2032 
2033         crm_debug("Search found %d device%s that can perform '%s' targeting %s",
2034                   ndevices, pcmk__plural_s(ndevices),
2035                   (search->action? search->action : "unknown action"),
2036                   (search->host? search->host : "any node"));
2037 
2038         search->callback(search->capable, search->user_data);
2039         free(search->host);
2040         free(search->action);
2041         free(search);
2042     }
2043 }
2044 
2045 /*!
2046  * \internal
2047  * \brief Check whether the local host is allowed to execute a fencing action
2048  *
2049  * \param[in] device         Fence device to check
2050  * \param[in] action         Fence action to check
2051  * \param[in] target         Hostname of fence target
2052  * \param[in] allow_suicide  Whether self-fencing is allowed for this operation
2053  *
2054  * \return TRUE if local host is allowed to execute action, FALSE otherwise
2055  */
2056 static gboolean
2057 localhost_is_eligible(const stonith_device_t *device, const char *action,
     /* [previous][next][first][last][top][bottom][index][help] */
2058                       const char *target, gboolean allow_suicide)
2059 {
2060     gboolean localhost_is_target = pcmk__str_eq(target, stonith_our_uname,
2061                                                 pcmk__str_casei);
2062 
2063     if ((device != NULL) && (action != NULL)
2064         && (device->on_target_actions != NULL)
2065         && (strstr((const char*) device->on_target_actions->str,
2066                    action) != NULL)) {
2067 
2068         if (!localhost_is_target) {
2069             crm_trace("Operation '%s' using %s can only be executed for local "
2070                       "host, not %s", action, device->id, target);
2071             return FALSE;
2072         }
2073 
2074     } else if (localhost_is_target && !allow_suicide) {
2075         crm_trace("'%s' operation does not support self-fencing", action);
2076         return FALSE;
2077     }
2078     return TRUE;
2079 }
2080 
2081 /*!
2082  * \internal
2083  * \brief Check if local node is allowed to execute (possibly remapped) action
2084  *
2085  * \param[in] device      Fence device to check
2086  * \param[in] action      Fence action to check
2087  * \param[in] target      Node name of fence target
2088  * \param[in] allow_self  Whether self-fencing is allowed for this operation
2089  *
2090  * \return true if local node is allowed to execute \p action or any actions it
2091  *         might be remapped to, otherwise false
2092  */
2093 static bool
2094 localhost_is_eligible_with_remap(const stonith_device_t *device,
     /* [previous][next][first][last][top][bottom][index][help] */
2095                                  const char *action, const char *target,
2096                                  gboolean allow_self)
2097 {
2098     // Check exact action
2099     if (localhost_is_eligible(device, action, target, allow_self)) {
2100         return true;
2101     }
2102 
2103     // Check potential remaps
2104 
2105     if (pcmk__str_eq(action, PCMK_ACTION_REBOOT, pcmk__str_none)) {
2106         /* "reboot" might get remapped to "off" then "on", so even if reboot is
2107          * disallowed, return true if either of those is allowed. We'll report
2108          * the disallowed actions with the results. We never allow self-fencing
2109          * for remapped "on" actions because the target is off at that point.
2110          */
2111         if (localhost_is_eligible(device, PCMK_ACTION_OFF, target, allow_self)
2112             || localhost_is_eligible(device, PCMK_ACTION_ON, target, FALSE)) {
2113             return true;
2114         }
2115     }
2116 
2117     return false;
2118 }
2119 
2120 static void
2121 can_fence_host_with_device(stonith_device_t *dev,
     /* [previous][next][first][last][top][bottom][index][help] */
2122                            struct device_search_s *search)
2123 {
2124     gboolean can = FALSE;
2125     const char *check_type = "Internal bug";
2126     const char *target = NULL;
2127     const char *alias = NULL;
2128     const char *dev_id = "Unspecified device";
2129     const char *action = (search == NULL)? NULL : search->action;
2130 
2131     CRM_CHECK((dev != NULL) && (action != NULL), goto search_report_results);
2132 
2133     if (dev->id != NULL) {
2134         dev_id = dev->id;
2135     }
2136 
2137     target = search->host;
2138     if (target == NULL) {
2139         can = TRUE;
2140         check_type = "No target";
2141         goto search_report_results;
2142     }
2143 
2144     /* Answer immediately if the device does not support the action
2145      * or the local node is not allowed to perform it
2146      */
2147     if (pcmk__str_eq(action, PCMK_ACTION_ON, pcmk__str_none)
2148         && !pcmk_is_set(dev->flags, st_device_supports_on)) {
2149         check_type = "Agent does not support 'on'";
2150         goto search_report_results;
2151 
2152     } else if (!localhost_is_eligible_with_remap(dev, action, target,
2153                                                  search->allow_suicide)) {
2154         check_type = "This node is not allowed to execute action";
2155         goto search_report_results;
2156     }
2157 
2158     // Check eligibility as specified by pcmk_host_check
2159     check_type = target_list_type(dev);
2160     alias = g_hash_table_lookup(dev->aliases, target);
2161     if (pcmk__str_eq(check_type, PCMK__VALUE_NONE, pcmk__str_casei)) {
2162         can = TRUE;
2163 
2164     } else if (pcmk__str_eq(check_type, "static-list", pcmk__str_casei)) {
2165         if (pcmk__str_in_list(target, dev->targets, pcmk__str_casei)) {
2166             can = TRUE;
2167         } else if (g_hash_table_lookup(dev->params, PCMK_STONITH_HOST_MAP)
2168                    && g_hash_table_lookup(dev->aliases, target)) {
2169             can = TRUE;
2170         }
2171 
2172     } else if (pcmk__str_eq(check_type, "dynamic-list", pcmk__str_casei)) {
2173         time_t now = time(NULL);
2174 
2175         if (dev->targets == NULL || dev->targets_age + 60 < now) {
2176             int device_timeout = get_action_timeout(dev, PCMK_ACTION_LIST,
2177                                                     search->per_device_timeout);
2178 
2179             if (device_timeout > search->per_device_timeout) {
2180                 crm_notice("Since the pcmk_list_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
2181                     device_timeout, dev_id, search->per_device_timeout);
2182             }
2183 
2184             crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
2185                       check_type, dev_id, target, action);
2186 
2187             schedule_internal_command(__func__, dev, PCMK_ACTION_LIST, NULL,
2188                                       search->per_device_timeout, search, dynamic_list_search_cb);
2189 
2190             /* we'll respond to this search request async in the cb */
2191             return;
2192         }
2193 
2194         if (pcmk__str_in_list(((alias == NULL)? target : alias), dev->targets,
2195                               pcmk__str_casei)) {
2196             can = TRUE;
2197         }
2198 
2199     } else if (pcmk__str_eq(check_type, "status", pcmk__str_casei)) {
2200         int device_timeout = get_action_timeout(dev, check_type, search->per_device_timeout);
2201 
2202         if (device_timeout > search->per_device_timeout) {
2203             crm_notice("Since the pcmk_status_timeout(%ds) parameter of %s is larger than stonith-timeout(%ds), timeout may occur",
2204                 device_timeout, dev_id, search->per_device_timeout);
2205         }
2206 
2207         crm_trace("Running '%s' to check whether %s is eligible to fence %s (%s)",
2208                   check_type, dev_id, target, action);
2209         schedule_internal_command(__func__, dev, PCMK_ACTION_STATUS, target,
2210                                   search->per_device_timeout, search, status_search_cb);
2211         /* we'll respond to this search request async in the cb */
2212         return;
2213     } else {
2214         crm_err("Invalid value for " PCMK_STONITH_HOST_CHECK ": %s", check_type);
2215         check_type = "Invalid " PCMK_STONITH_HOST_CHECK;
2216     }
2217 
2218   search_report_results:
2219     crm_info("%s is%s eligible to fence (%s) %s%s%s%s: %s",
2220              dev_id, (can? "" : " not"), pcmk__s(action, "unspecified action"),
2221              pcmk__s(target, "unspecified target"),
2222              (alias == NULL)? "" : " (as '", pcmk__s(alias, ""),
2223              (alias == NULL)? "" : "')", check_type);
2224     search_devices_record_result(search, ((dev == NULL)? NULL : dev_id), can);
2225 }
2226 
2227 static void
2228 search_devices(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2229 {
2230     stonith_device_t *dev = value;
2231     struct device_search_s *search = user_data;
2232 
2233     can_fence_host_with_device(dev, search);
2234 }
2235 
2236 #define DEFAULT_QUERY_TIMEOUT 20
2237 static void
2238 get_capable_devices(const char *host, const char *action, int timeout, bool suicide, void *user_data,
     /* [previous][next][first][last][top][bottom][index][help] */
2239                     void (*callback) (GList * devices, void *user_data), uint32_t support_action_only)
2240 {
2241     struct device_search_s *search;
2242     guint ndevices = g_hash_table_size(device_list);
2243 
2244     if (ndevices == 0) {
2245         callback(NULL, user_data);
2246         return;
2247     }
2248 
2249     search = calloc(1, sizeof(struct device_search_s));
2250     if (!search) {
2251         crm_crit("Cannot search for capable fence devices: %s",
2252                  strerror(ENOMEM));
2253         callback(NULL, user_data);
2254         return;
2255     }
2256 
2257     pcmk__str_update(&search->host, host);
2258     pcmk__str_update(&search->action, action);
2259     search->per_device_timeout = timeout;
2260     search->allow_suicide = suicide;
2261     search->callback = callback;
2262     search->user_data = user_data;
2263     search->support_action_only = support_action_only;
2264 
2265     /* We are guaranteed this many replies, even if a device is
2266      * unregistered while the search is in progress.
2267      */
2268     search->replies_needed = ndevices;
2269 
2270     crm_debug("Searching %d device%s to see which can execute '%s' targeting %s",
2271               ndevices, pcmk__plural_s(ndevices),
2272               (search->action? search->action : "unknown action"),
2273               (search->host? search->host : "any node"));
2274     g_hash_table_foreach(device_list, search_devices, search);
2275 }
2276 
2277 struct st_query_data {
2278     xmlNode *reply;
2279     char *remote_peer;
2280     char *client_id;
2281     char *target;
2282     char *action;
2283     int call_options;
2284 };
2285 
2286 /*!
2287  * \internal
2288  * \brief Add action-specific attributes to query reply XML
2289  *
2290  * \param[in,out] xml     XML to add attributes to
2291  * \param[in]     action  Fence action
2292  * \param[in]     device  Fence device
2293  * \param[in]     target  Fence target
2294  */
2295 static void
2296 add_action_specific_attributes(xmlNode *xml, const char *action,
     /* [previous][next][first][last][top][bottom][index][help] */
2297                                const stonith_device_t *device,
2298                                const char *target)
2299 {
2300     int action_specific_timeout;
2301     int delay_max;
2302     int delay_base;
2303 
2304     CRM_CHECK(xml && action && device, return);
2305 
2306     if (is_action_required(action, device)) {
2307         crm_trace("Action '%s' is required using %s", action, device->id);
2308         crm_xml_add_int(xml, F_STONITH_DEVICE_REQUIRED, 1);
2309     }
2310 
2311     action_specific_timeout = get_action_timeout(device, action, 0);
2312     if (action_specific_timeout) {
2313         crm_trace("Action '%s' has timeout %dms using %s",
2314                   action, action_specific_timeout, device->id);
2315         crm_xml_add_int(xml, F_STONITH_ACTION_TIMEOUT, action_specific_timeout);
2316     }
2317 
2318     delay_max = get_action_delay_max(device, action);
2319     if (delay_max > 0) {
2320         crm_trace("Action '%s' has maximum random delay %ds using %s",
2321                   action, delay_max, device->id);
2322         crm_xml_add_int(xml, F_STONITH_DELAY_MAX, delay_max);
2323     }
2324 
2325     delay_base = get_action_delay_base(device, action, target);
2326     if (delay_base > 0) {
2327         crm_xml_add_int(xml, F_STONITH_DELAY_BASE, delay_base);
2328     }
2329 
2330     if ((delay_max > 0) && (delay_base == 0)) {
2331         crm_trace("Action '%s' has maximum random delay %ds using %s",
2332                   action, delay_max, device->id);
2333     } else if ((delay_max == 0) && (delay_base > 0)) {
2334         crm_trace("Action '%s' has a static delay of %ds using %s",
2335                   action, delay_base, device->id);
2336     } else if ((delay_max > 0) && (delay_base > 0)) {
2337         crm_trace("Action '%s' has a minimum delay of %ds and a randomly chosen "
2338                   "maximum delay of %ds using %s",
2339                   action, delay_base, delay_max, device->id);
2340     }
2341 }
2342 
2343 /*!
2344  * \internal
2345  * \brief Add "disallowed" attribute to query reply XML if appropriate
2346  *
2347  * \param[in,out] xml            XML to add attribute to
2348  * \param[in]     action         Fence action
2349  * \param[in]     device         Fence device
2350  * \param[in]     target         Fence target
2351  * \param[in]     allow_suicide  Whether self-fencing is allowed
2352  */
2353 static void
2354 add_disallowed(xmlNode *xml, const char *action, const stonith_device_t *device,
     /* [previous][next][first][last][top][bottom][index][help] */
2355                const char *target, gboolean allow_suicide)
2356 {
2357     if (!localhost_is_eligible(device, action, target, allow_suicide)) {
2358         crm_trace("Action '%s' using %s is disallowed for local host",
2359                   action, device->id);
2360         pcmk__xe_set_bool_attr(xml, F_STONITH_ACTION_DISALLOWED, true);
2361     }
2362 }
2363 
2364 /*!
2365  * \internal
2366  * \brief Add child element with action-specific values to query reply XML
2367  *
2368  * \param[in,out] xml            XML to add attribute to
2369  * \param[in]     action         Fence action
2370  * \param[in]     device         Fence device
2371  * \param[in]     target         Fence target
2372  * \param[in]     allow_suicide  Whether self-fencing is allowed
2373  */
2374 static void
2375 add_action_reply(xmlNode *xml, const char *action,
     /* [previous][next][first][last][top][bottom][index][help] */
2376                  const stonith_device_t *device, const char *target,
2377                  gboolean allow_suicide)
2378 {
2379     xmlNode *child = create_xml_node(xml, F_STONITH_ACTION);
2380 
2381     crm_xml_add(child, XML_ATTR_ID, action);
2382     add_action_specific_attributes(child, action, device, target);
2383     add_disallowed(child, action, device, target, allow_suicide);
2384 }
2385 
2386 /*!
2387  * \internal
2388  * \brief Send a reply to a CPG peer or IPC client
2389  *
2390  * \param[in]     reply         XML reply to send
2391  * \param[in]     call_options  Send synchronously if st_opt_sync_call is set
2392  * \param[in]     remote_peer   If not NULL, name of peer node to send CPG reply
2393  * \param[in,out] client        If not NULL, client to send IPC reply
2394  */
2395 static void
2396 stonith_send_reply(const xmlNode *reply, int call_options,
     /* [previous][next][first][last][top][bottom][index][help] */
2397                    const char *remote_peer, pcmk__client_t *client)
2398 {
2399     CRM_CHECK((reply != NULL) && ((remote_peer != NULL) || (client != NULL)),
2400               return);
2401 
2402     if (remote_peer == NULL) {
2403         do_local_reply(reply, client, call_options);
2404     } else {
2405         send_cluster_message(crm_get_peer(0, remote_peer), crm_msg_stonith_ng,
2406                              reply, FALSE);
2407     }
2408 }
2409 
2410 static void
2411 stonith_query_capable_device_cb(GList * devices, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2412 {
2413     struct st_query_data *query = user_data;
2414     int available_devices = 0;
2415     xmlNode *dev = NULL;
2416     xmlNode *list = NULL;
2417     GList *lpc = NULL;
2418     pcmk__client_t *client = NULL;
2419 
2420     if (query->client_id != NULL) {
2421         client = pcmk__find_client_by_id(query->client_id);
2422         if ((client == NULL) && (query->remote_peer == NULL)) {
2423             crm_trace("Skipping reply to %s: no longer a client",
2424                       query->client_id);
2425             goto done;
2426         }
2427     }
2428 
2429     /* Pack the results into XML */
2430     list = create_xml_node(NULL, __func__);
2431     crm_xml_add(list, F_STONITH_TARGET, query->target);
2432     for (lpc = devices; lpc != NULL; lpc = lpc->next) {
2433         stonith_device_t *device = g_hash_table_lookup(device_list, lpc->data);
2434         const char *action = query->action;
2435 
2436         if (!device) {
2437             /* It is possible the device got unregistered while
2438              * determining who can fence the target */
2439             continue;
2440         }
2441 
2442         available_devices++;
2443 
2444         dev = create_xml_node(list, F_STONITH_DEVICE);
2445         crm_xml_add(dev, XML_ATTR_ID, device->id);
2446         crm_xml_add(dev, "namespace", device->namespace);
2447         crm_xml_add(dev, "agent", device->agent);
2448         crm_xml_add_int(dev, F_STONITH_DEVICE_VERIFIED, device->verified);
2449         crm_xml_add_int(dev, F_STONITH_DEVICE_SUPPORT_FLAGS, device->flags);
2450 
2451         /* If the originating fencer wants to reboot the node, and we have a
2452          * capable device that doesn't support "reboot", remap to "off" instead.
2453          */
2454         if (!pcmk_is_set(device->flags, st_device_supports_reboot)
2455             && pcmk__str_eq(query->action, PCMK_ACTION_REBOOT,
2456                             pcmk__str_none)) {
2457             crm_trace("%s doesn't support reboot, using values for off instead",
2458                       device->id);
2459             action = PCMK_ACTION_OFF;
2460         }
2461 
2462         /* Add action-specific values if available */
2463         add_action_specific_attributes(dev, action, device, query->target);
2464         if (pcmk__str_eq(query->action, PCMK_ACTION_REBOOT, pcmk__str_none)) {
2465             /* A "reboot" *might* get remapped to "off" then "on", so after
2466              * sending the "reboot"-specific values in the main element, we add
2467              * sub-elements for "off" and "on" values.
2468              *
2469              * We short-circuited earlier if "reboot", "off" and "on" are all
2470              * disallowed for the local host. However if only one or two are
2471              * disallowed, we send back the results and mark which ones are
2472              * disallowed. If "reboot" is disallowed, this might cause problems
2473              * with older fencer versions, which won't check for it. Older
2474              * versions will ignore "off" and "on", so they are not a problem.
2475              */
2476             add_disallowed(dev, action, device, query->target,
2477                            pcmk_is_set(query->call_options, st_opt_allow_suicide));
2478             add_action_reply(dev, PCMK_ACTION_OFF, device, query->target,
2479                              pcmk_is_set(query->call_options, st_opt_allow_suicide));
2480             add_action_reply(dev, PCMK_ACTION_ON, device, query->target, FALSE);
2481         }
2482 
2483         /* A query without a target wants device parameters */
2484         if (query->target == NULL) {
2485             xmlNode *attrs = create_xml_node(dev, XML_TAG_ATTRS);
2486 
2487             g_hash_table_foreach(device->params, hash2field, attrs);
2488         }
2489     }
2490 
2491     crm_xml_add_int(list, F_STONITH_AVAILABLE_DEVICES, available_devices);
2492     if (query->target) {
2493         crm_debug("Found %d matching device%s for target '%s'",
2494                   available_devices, pcmk__plural_s(available_devices),
2495                   query->target);
2496     } else {
2497         crm_debug("%d device%s installed",
2498                   available_devices, pcmk__plural_s(available_devices));
2499     }
2500 
2501     if (list != NULL) {
2502         crm_log_xml_trace(list, "Add query results");
2503         add_message_xml(query->reply, F_STONITH_CALLDATA, list);
2504     }
2505 
2506     stonith_send_reply(query->reply, query->call_options, query->remote_peer,
2507                        client);
2508 
2509 done:
2510     free_xml(query->reply);
2511     free(query->remote_peer);
2512     free(query->client_id);
2513     free(query->target);
2514     free(query->action);
2515     free(query);
2516     free_xml(list);
2517     g_list_free_full(devices, free);
2518 }
2519 
2520 /*!
2521  * \internal
2522  * \brief Log the result of an asynchronous command
2523  *
2524  * \param[in] cmd        Command the result is for
2525  * \param[in] result     Result of command
2526  * \param[in] pid        Process ID of command, if available
2527  * \param[in] next       Alternate device that will be tried if command failed
2528  * \param[in] op_merged  Whether this command was merged with an earlier one
2529  */
2530 static void
2531 log_async_result(const async_command_t *cmd,
     /* [previous][next][first][last][top][bottom][index][help] */
2532                  const pcmk__action_result_t *result,
2533                  int pid, const char *next, bool op_merged)
2534 {
2535     int log_level = LOG_ERR;
2536     int output_log_level = LOG_NEVER;
2537     guint devices_remaining = g_list_length(cmd->next_device_iter);
2538 
2539     GString *msg = g_string_sized_new(80); // Reasonable starting size
2540 
2541     // Choose log levels appropriately if we have a result
2542     if (pcmk__result_ok(result)) {
2543         log_level = (cmd->target == NULL)? LOG_DEBUG : LOG_NOTICE;
2544         if ((result->action_stdout != NULL)
2545             && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_none)) {
2546             output_log_level = LOG_DEBUG;
2547         }
2548         next = NULL;
2549     } else {
2550         log_level = (cmd->target == NULL)? LOG_NOTICE : LOG_ERR;
2551         if ((result->action_stdout != NULL)
2552             && !pcmk__str_eq(cmd->action, "metadata", pcmk__str_none)) {
2553             output_log_level = LOG_WARNING;
2554         }
2555     }
2556 
2557     // Build the log message piece by piece
2558     pcmk__g_strcat(msg, "Operation '", cmd->action, "' ", NULL);
2559     if (pid != 0) {
2560         g_string_append_printf(msg, "[%d] ", pid);
2561     }
2562     if (cmd->target != NULL) {
2563         pcmk__g_strcat(msg, "targeting ", cmd->target, " ", NULL);
2564     }
2565     if (cmd->device != NULL) {
2566         pcmk__g_strcat(msg, "using ", cmd->device, " ", NULL);
2567     }
2568 
2569     // Add exit status or execution status as appropriate
2570     if (result->execution_status == PCMK_EXEC_DONE) {
2571         g_string_append_printf(msg, "returned %d", result->exit_status);
2572     } else {
2573         pcmk__g_strcat(msg, "could not be executed: ",
2574                        pcmk_exec_status_str(result->execution_status), NULL);
2575     }
2576 
2577     // Add exit reason and next device if appropriate
2578     if (result->exit_reason != NULL) {
2579         pcmk__g_strcat(msg, " (", result->exit_reason, ")", NULL);
2580     }
2581     if (next != NULL) {
2582         pcmk__g_strcat(msg, ", retrying with ", next, NULL);
2583     }
2584     if (devices_remaining > 0) {
2585         g_string_append_printf(msg, " (%u device%s remaining)",
2586                                (unsigned int) devices_remaining,
2587                                pcmk__plural_s(devices_remaining));
2588     }
2589     g_string_append_printf(msg, " " CRM_XS " %scall %d from %s",
2590                            (op_merged? "merged " : ""), cmd->id,
2591                            cmd->client_name);
2592 
2593     // Log the result
2594     do_crm_log(log_level, "%s", msg->str);
2595     g_string_free(msg, TRUE);
2596 
2597     // Log the output (which may have multiple lines), if appropriate
2598     if (output_log_level != LOG_NEVER) {
2599         char *prefix = crm_strdup_printf("%s[%d]", cmd->device, pid);
2600 
2601         crm_log_output(output_log_level, prefix, result->action_stdout);
2602         free(prefix);
2603     }
2604 }
2605 
2606 /*!
2607  * \internal
2608  * \brief Reply to requester after asynchronous command completion
2609  *
2610  * \param[in] cmd      Command that completed
2611  * \param[in] result   Result of command
2612  * \param[in] pid      Process ID of command, if available
2613  * \param[in] merged   If true, command was merged with another, not executed
2614  */
2615 static void
2616 send_async_reply(const async_command_t *cmd, const pcmk__action_result_t *result,
     /* [previous][next][first][last][top][bottom][index][help] */
2617                  int pid, bool merged)
2618 {
2619     xmlNode *reply = NULL;
2620     pcmk__client_t *client = NULL;
2621 
2622     CRM_CHECK((cmd != NULL) && (result != NULL), return);
2623 
2624     log_async_result(cmd, result, pid, NULL, merged);
2625 
2626     if (cmd->client != NULL) {
2627         client = pcmk__find_client_by_id(cmd->client);
2628         if ((client == NULL) && (cmd->origin == NULL)) {
2629             crm_trace("Skipping reply to %s: no longer a client", cmd->client);
2630             return;
2631         }
2632     }
2633 
2634     reply = construct_async_reply(cmd, result);
2635     if (merged) {
2636         pcmk__xe_set_bool_attr(reply, F_STONITH_MERGED, true);
2637     }
2638 
2639     if (!stand_alone && pcmk__is_fencing_action(cmd->action)
2640         && pcmk__str_eq(cmd->origin, cmd->target, pcmk__str_casei)) {
2641         /* The target was also the originator, so broadcast the result on its
2642          * behalf (since it will be unable to).
2643          */
2644         crm_trace("Broadcast '%s' result for %s (target was also originator)",
2645                   cmd->action, cmd->target);
2646         crm_xml_add(reply, F_SUBTYPE, "broadcast");
2647         crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY);
2648         send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE);
2649     } else {
2650         // Reply only to the originator
2651         stonith_send_reply(reply, cmd->options, cmd->origin, client);
2652     }
2653 
2654     crm_log_xml_trace(reply, "Reply");
2655     free_xml(reply);
2656 
2657     if (stand_alone) {
2658         /* Do notification with a clean data object */
2659         xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE);
2660 
2661         stonith__xe_set_result(notify_data, result);
2662         crm_xml_add(notify_data, F_STONITH_TARGET, cmd->target);
2663         crm_xml_add(notify_data, F_STONITH_OPERATION, cmd->op);
2664         crm_xml_add(notify_data, F_STONITH_DELEGATE, "localhost");
2665         crm_xml_add(notify_data, F_STONITH_DEVICE, cmd->device);
2666         crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
2667         crm_xml_add(notify_data, F_STONITH_ORIGIN, cmd->client);
2668 
2669         fenced_send_notification(T_STONITH_NOTIFY_FENCE, result, notify_data);
2670         fenced_send_notification(T_STONITH_NOTIFY_HISTORY, NULL, NULL);
2671     }
2672 }
2673 
2674 static void
2675 cancel_stonith_command(async_command_t * cmd)
     /* [previous][next][first][last][top][bottom][index][help] */
2676 {
2677     stonith_device_t *device = cmd_device(cmd);
2678 
2679     if (device) {
2680         crm_trace("Cancel scheduled '%s' action using %s",
2681                   cmd->action, device->id);
2682         device->pending_ops = g_list_remove(device->pending_ops, cmd);
2683     }
2684 }
2685 
2686 /*!
2687  * \internal
2688  * \brief Cancel and reply to any duplicates of a just-completed operation
2689  *
2690  * Check whether any fencing operations are scheduled to do the same thing as
2691  * one that just succeeded. If so, rather than performing the same operation
2692  * twice, return the result of this operation for all matching pending commands.
2693  *
2694  * \param[in,out] cmd     Fencing operation that just succeeded
2695  * \param[in]     result  Result of \p cmd
2696  * \param[in]     pid     If nonzero, process ID of agent invocation (for logs)
2697  *
2698  * \note Duplicate merging will do the right thing for either type of remapped
2699  *       reboot. If the executing fencer remapped an unsupported reboot to off,
2700  *       then cmd->action will be "reboot" and will be merged with any other
2701  *       reboot requests. If the originating fencer remapped a topology reboot
2702  *       to off then on, we will get here once with cmd->action "off" and once
2703  *       with "on", and they will be merged separately with similar requests.
2704  */
2705 static void
2706 reply_to_duplicates(async_command_t *cmd, const pcmk__action_result_t *result,
     /* [previous][next][first][last][top][bottom][index][help] */
2707                     int pid)
2708 {
2709     GList *next = NULL;
2710 
2711     for (GList *iter = cmd_list; iter != NULL; iter = next) {
2712         async_command_t *cmd_other = iter->data;
2713 
2714         next = iter->next; // We might delete this entry, so grab next now
2715 
2716         if (cmd == cmd_other) {
2717             continue;
2718         }
2719 
2720         /* A pending operation matches if:
2721          * 1. The client connections are different.
2722          * 2. The target is the same.
2723          * 3. The fencing action is the same.
2724          * 4. The device scheduled to execute the action is the same.
2725          */
2726         if (pcmk__str_eq(cmd->client, cmd_other->client, pcmk__str_casei) ||
2727             !pcmk__str_eq(cmd->target, cmd_other->target, pcmk__str_casei) ||
2728             !pcmk__str_eq(cmd->action, cmd_other->action, pcmk__str_none) ||
2729             !pcmk__str_eq(cmd->device, cmd_other->device, pcmk__str_casei)) {
2730 
2731             continue;
2732         }
2733 
2734         crm_notice("Merging fencing action '%s'%s%s originating from "
2735                    "client %s with identical fencing request from client %s",
2736                    cmd_other->action,
2737                    (cmd_other->target == NULL)? "" : " targeting ",
2738                    pcmk__s(cmd_other->target, ""), cmd_other->client_name,
2739                    cmd->client_name);
2740 
2741         // Stop tracking the duplicate, send its result, and cancel it
2742         cmd_list = g_list_remove_link(cmd_list, iter);
2743         send_async_reply(cmd_other, result, pid, true);
2744         cancel_stonith_command(cmd_other);
2745 
2746         free_async_command(cmd_other);
2747         g_list_free_1(iter);
2748     }
2749 }
2750 
2751 /*!
2752  * \internal
2753  * \brief Return the next required device (if any) for an operation
2754  *
2755  * \param[in,out] cmd  Fencing operation that just succeeded
2756  *
2757  * \return Next device required for action if any, otherwise NULL
2758  */
2759 static stonith_device_t *
2760 next_required_device(async_command_t *cmd)
     /* [previous][next][first][last][top][bottom][index][help] */
2761 {
2762     for (GList *iter = cmd->next_device_iter; iter != NULL; iter = iter->next) {
2763         stonith_device_t *next_device = g_hash_table_lookup(device_list,
2764                                                             iter->data);
2765 
2766         if (is_action_required(cmd->action, next_device)) {
2767             /* This is only called for successful actions, so it's OK to skip
2768              * non-required devices.
2769              */
2770             cmd->next_device_iter = iter->next;
2771             return next_device;
2772         }
2773     }
2774     return NULL;
2775 }
2776 
2777 static void
2778 st_child_done(int pid, const pcmk__action_result_t *result, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2779 {
2780     async_command_t *cmd = user_data;
2781 
2782     stonith_device_t *device = NULL;
2783     stonith_device_t *next_device = NULL;
2784 
2785     CRM_CHECK(cmd != NULL, return);
2786 
2787     device = cmd_device(cmd);
2788     cmd->active_on = NULL;
2789 
2790     /* The device is ready to do something else now */
2791     if (device) {
2792         if (!device->verified && pcmk__result_ok(result)
2793             && pcmk__strcase_any_of(cmd->action, PCMK_ACTION_LIST,
2794                                     PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS,
2795                                     NULL)) {
2796 
2797             device->verified = TRUE;
2798         }
2799 
2800         mainloop_set_trigger(device->work);
2801     }
2802 
2803     if (pcmk__result_ok(result)) {
2804         next_device = next_required_device(cmd);
2805 
2806     } else if ((cmd->next_device_iter != NULL)
2807                && !is_action_required(cmd->action, device)) {
2808         /* if this device didn't work out, see if there are any others we can try.
2809          * if the failed device was 'required', we can't pick another device. */
2810         next_device = g_hash_table_lookup(device_list,
2811                                           cmd->next_device_iter->data);
2812         cmd->next_device_iter = cmd->next_device_iter->next;
2813     }
2814 
2815     if (next_device == NULL) {
2816         send_async_reply(cmd, result, pid, false);
2817         if (pcmk__result_ok(result)) {
2818             reply_to_duplicates(cmd, result, pid);
2819         }
2820         free_async_command(cmd);
2821 
2822     } else { // This operation requires more fencing
2823         log_async_result(cmd, result, pid, next_device->id, false);
2824         schedule_stonith_command(cmd, next_device);
2825     }
2826 }
2827 
2828 static gint
2829 sort_device_priority(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
2830 {
2831     const stonith_device_t *dev_a = a;
2832     const stonith_device_t *dev_b = b;
2833 
2834     if (dev_a->priority > dev_b->priority) {
2835         return -1;
2836     } else if (dev_a->priority < dev_b->priority) {
2837         return 1;
2838     }
2839     return 0;
2840 }
2841 
2842 static void
2843 stonith_fence_get_devices_cb(GList * devices, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
2844 {
2845     async_command_t *cmd = user_data;
2846     stonith_device_t *device = NULL;
2847     guint ndevices = g_list_length(devices);
2848 
2849     crm_info("Found %d matching device%s for target '%s'",
2850              ndevices, pcmk__plural_s(ndevices), cmd->target);
2851 
2852     if (devices != NULL) {
2853         /* Order based on priority */
2854         devices = g_list_sort(devices, sort_device_priority);
2855         device = g_hash_table_lookup(device_list, devices->data);
2856     }
2857 
2858     if (device == NULL) { // No device found
2859         pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
2860 
2861         pcmk__format_result(&result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
2862                             "No device configured for target '%s'",
2863                             cmd->target);
2864         send_async_reply(cmd, &result, 0, false);
2865         pcmk__reset_result(&result);
2866         free_async_command(cmd);
2867         g_list_free_full(devices, free);
2868 
2869     } else { // Device found, schedule it for fencing
2870         cmd->device_list = devices;
2871         cmd->next_device_iter = devices->next;
2872         schedule_stonith_command(cmd, device);
2873     }
2874 }
2875 
2876 /*!
2877  * \internal
2878  * \brief Execute a fence action via the local node
2879  *
2880  * \param[in]  msg     Fencing request
2881  * \param[out] result  Where to store result of fence action
2882  */
2883 static void
2884 fence_locally(xmlNode *msg, pcmk__action_result_t *result)
     /* [previous][next][first][last][top][bottom][index][help] */
2885 {
2886     const char *device_id = NULL;
2887     stonith_device_t *device = NULL;
2888     async_command_t *cmd = NULL;
2889     xmlNode *dev = NULL;
2890 
2891     CRM_CHECK((msg != NULL) && (result != NULL), return);
2892 
2893     dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
2894 
2895     cmd = create_async_command(msg);
2896     if (cmd == NULL) {
2897         crm_log_xml_warn(msg, "invalid");
2898         fenced_set_protocol_error(result);
2899         return;
2900     }
2901 
2902     device_id = crm_element_value(dev, F_STONITH_DEVICE);
2903     if (device_id != NULL) {
2904         device = g_hash_table_lookup(device_list, device_id);
2905         if (device == NULL) {
2906             crm_err("Requested device '%s' is not available", device_id);
2907             pcmk__format_result(result, CRM_EX_ERROR, PCMK_EXEC_NO_FENCE_DEVICE,
2908                                 "Requested device '%s' not found", device_id);
2909             return;
2910         }
2911         schedule_stonith_command(cmd, device);
2912 
2913     } else {
2914         const char *host = crm_element_value(dev, F_STONITH_TARGET);
2915 
2916         if (pcmk_is_set(cmd->options, st_opt_cs_nodeid)) {
2917             int nodeid = 0;
2918             crm_node_t *node = NULL;
2919 
2920             pcmk__scan_min_int(host, &nodeid, 0);
2921             node = pcmk__search_known_node_cache(nodeid, NULL, CRM_GET_PEER_ANY);
2922             if (node != NULL) {
2923                 host = node->uname;
2924             }
2925         }
2926 
2927         /* If we get to here, then self-fencing is implicitly allowed */
2928         get_capable_devices(host, cmd->action, cmd->default_timeout,
2929                             TRUE, cmd, stonith_fence_get_devices_cb,
2930                             fenced_support_flag(cmd->action));
2931     }
2932 
2933     pcmk__set_result(result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
2934 }
2935 
2936 /*!
2937  * \internal
2938  * \brief Build an XML reply for a fencing operation
2939  *
2940  * \param[in] request  Request that reply is for
2941  * \param[in] data     If not NULL, add to reply as call data
2942  * \param[in] result   Full result of fencing operation
2943  *
2944  * \return Newly created XML reply
2945  * \note The caller is responsible for freeing the result.
2946  * \note This has some overlap with construct_async_reply(), but that copies
2947  *       values from an async_command_t, whereas this one copies them from the
2948  *       request.
2949  */
2950 xmlNode *
2951 fenced_construct_reply(const xmlNode *request, xmlNode *data,
     /* [previous][next][first][last][top][bottom][index][help] */
2952                        const pcmk__action_result_t *result)
2953 {
2954     xmlNode *reply = NULL;
2955 
2956     reply = create_xml_node(NULL, T_STONITH_REPLY);
2957 
2958     crm_xml_add(reply, "st_origin", __func__);
2959     crm_xml_add(reply, F_TYPE, T_STONITH_NG);
2960     stonith__xe_set_result(reply, result);
2961 
2962     if (request == NULL) {
2963         /* Most likely, this is the result of a stonith operation that was
2964          * initiated before we came up. Unfortunately that means we lack enough
2965          * information to provide clients with a full result.
2966          *
2967          * @TODO Maybe synchronize this information at start-up?
2968          */
2969         crm_warn("Missing request information for client notifications for "
2970                  "operation with result '%s' (initiated before we came up?)",
2971                  pcmk_exec_status_str(result->execution_status));
2972 
2973     } else {
2974         const char *name = NULL;
2975         const char *value = NULL;
2976 
2977         // Attributes to copy from request to reply
2978         const char *names[] = {
2979             F_STONITH_OPERATION,
2980             F_STONITH_CALLID,
2981             F_STONITH_CLIENTID,
2982             F_STONITH_CLIENTNAME,
2983             F_STONITH_REMOTE_OP_ID,
2984             F_STONITH_CALLOPTS
2985         };
2986 
2987         for (int lpc = 0; lpc < PCMK__NELEM(names); lpc++) {
2988             name = names[lpc];
2989             value = crm_element_value(request, name);
2990             crm_xml_add(reply, name, value);
2991         }
2992         if (data != NULL) {
2993             add_message_xml(reply, F_STONITH_CALLDATA, data);
2994         }
2995     }
2996     return reply;
2997 }
2998 
2999 /*!
3000  * \internal
3001  * \brief Build an XML reply to an asynchronous fencing command
3002  *
3003  * \param[in] cmd     Fencing command that reply is for
3004  * \param[in] result  Command result
3005  */
3006 static xmlNode *
3007 construct_async_reply(const async_command_t *cmd,
     /* [previous][next][first][last][top][bottom][index][help] */
3008                       const pcmk__action_result_t *result)
3009 {
3010     xmlNode *reply = create_xml_node(NULL, T_STONITH_REPLY);
3011 
3012     crm_xml_add(reply, "st_origin", __func__);
3013     crm_xml_add(reply, F_TYPE, T_STONITH_NG);
3014     crm_xml_add(reply, F_STONITH_OPERATION, cmd->op);
3015     crm_xml_add(reply, F_STONITH_DEVICE, cmd->device);
3016     crm_xml_add(reply, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
3017     crm_xml_add(reply, F_STONITH_CLIENTID, cmd->client);
3018     crm_xml_add(reply, F_STONITH_CLIENTNAME, cmd->client_name);
3019     crm_xml_add(reply, F_STONITH_TARGET, cmd->target);
3020     crm_xml_add(reply, F_STONITH_ACTION, cmd->op);
3021     crm_xml_add(reply, F_STONITH_ORIGIN, cmd->origin);
3022     crm_xml_add_int(reply, F_STONITH_CALLID, cmd->id);
3023     crm_xml_add_int(reply, F_STONITH_CALLOPTS, cmd->options);
3024 
3025     stonith__xe_set_result(reply, result);
3026     return reply;
3027 }
3028 
3029 bool fencing_peer_active(crm_node_t *peer)
     /* [previous][next][first][last][top][bottom][index][help] */
3030 {
3031     if (peer == NULL) {
3032         return FALSE;
3033     } else if (peer->uname == NULL) {
3034         return FALSE;
3035     } else if (pcmk_is_set(peer->processes, crm_get_cluster_proc())) {
3036         return TRUE;
3037     }
3038     return FALSE;
3039 }
3040 
3041 void
3042 set_fencing_completed(remote_fencing_op_t *op)
     /* [previous][next][first][last][top][bottom][index][help] */
3043 {
3044     struct timespec tv;
3045 
3046     qb_util_timespec_from_epoch_get(&tv);
3047     op->completed = tv.tv_sec;
3048     op->completed_nsec = tv.tv_nsec;
3049 }
3050 
3051 /*!
3052  * \internal
3053  * \brief Look for alternate node needed if local node shouldn't fence target
3054  *
3055  * \param[in] target  Node that must be fenced
3056  *
3057  * \return Name of an alternate node that should fence \p target if any,
3058  *         or NULL otherwise
3059  */
3060 static const char *
3061 check_alternate_host(const char *target)
     /* [previous][next][first][last][top][bottom][index][help] */
3062 {
3063     if (pcmk__str_eq(target, stonith_our_uname, pcmk__str_casei)) {
3064         GHashTableIter gIter;
3065         crm_node_t *entry = NULL;
3066 
3067         g_hash_table_iter_init(&gIter, crm_peer_cache);
3068         while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
3069             if (fencing_peer_active(entry)
3070                 && !pcmk__str_eq(entry->uname, target, pcmk__str_casei)) {
3071                 crm_notice("Forwarding self-fencing request to %s",
3072                            entry->uname);
3073                 return entry->uname;
3074             }
3075         }
3076         crm_warn("Will handle own fencing because no peer can");
3077     }
3078     return NULL;
3079 }
3080 
3081 static void 
3082 remove_relay_op(xmlNode * request)
     /* [previous][next][first][last][top][bottom][index][help] */
3083 {
3084     xmlNode *dev = get_xpath_object("//@" F_STONITH_ACTION, request, LOG_TRACE);
3085     const char *relay_op_id = NULL; 
3086     const char *op_id = NULL;
3087     const char *client_name = NULL;
3088     const char *target = NULL; 
3089     remote_fencing_op_t *relay_op = NULL; 
3090 
3091     if (dev) { 
3092         target = crm_element_value(dev, F_STONITH_TARGET); 
3093     }
3094 
3095     relay_op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID_RELAY);
3096     op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID);
3097     client_name = crm_element_value(request, F_STONITH_CLIENTNAME);
3098 
3099     /* Delete RELAY operation. */
3100     if (relay_op_id && target && pcmk__str_eq(target, stonith_our_uname, pcmk__str_casei)) {
3101         relay_op = g_hash_table_lookup(stonith_remote_op_list, relay_op_id);
3102 
3103         if (relay_op) {
3104             GHashTableIter iter;
3105             remote_fencing_op_t *list_op = NULL; 
3106             g_hash_table_iter_init(&iter, stonith_remote_op_list);
3107 
3108             /* If the operation to be deleted is registered as a duplicate, delete the registration. */
3109             while (g_hash_table_iter_next(&iter, NULL, (void **)&list_op)) {
3110                 GList *dup_iter = NULL;
3111                 if (list_op != relay_op) {
3112                     for (dup_iter = list_op->duplicates; dup_iter != NULL; dup_iter = dup_iter->next) {
3113                         remote_fencing_op_t *other = dup_iter->data;
3114                         if (other == relay_op) {
3115                             other->duplicates = g_list_remove(other->duplicates, relay_op);
3116                             break;
3117                         }
3118                     }
3119                 }
3120             }
3121             crm_debug("Deleting relay op %s ('%s'%s%s for %s), "
3122                       "replaced by op %s ('%s'%s%s for %s)",
3123                       relay_op->id, relay_op->action,
3124                       (relay_op->target == NULL)? "" : " targeting ",
3125                       pcmk__s(relay_op->target, ""),
3126                       relay_op->client_name, op_id, relay_op->action,
3127                       (target == NULL)? "" : " targeting ", pcmk__s(target, ""),
3128                       client_name);
3129 
3130             g_hash_table_remove(stonith_remote_op_list, relay_op_id);
3131         }
3132     }
3133 }
3134 
3135 /*!
3136  * \internal
3137  * \brief Check whether an API request was sent by a privileged user
3138  *
3139  * API commands related to fencing configuration may be done only by privileged
3140  * IPC users (i.e. root or hacluster), because all other users should go through
3141  * the CIB to have ACLs applied. If no client was given, this is a peer request,
3142  * which is always allowed.
3143  *
3144  * \param[in] c   IPC client that sent request (or NULL if sent by CPG peer)
3145  * \param[in] op  Requested API operation (for logging only)
3146  *
3147  * \return true if sender is peer or privileged client, otherwise false
3148  */
3149 static inline bool
3150 is_privileged(const pcmk__client_t *c, const char *op)
     /* [previous][next][first][last][top][bottom][index][help] */
3151 {
3152     if ((c == NULL) || pcmk_is_set(c->flags, pcmk__client_privileged)) {
3153         return true;
3154     } else {
3155         crm_warn("Rejecting IPC request '%s' from unprivileged client %s",
3156                  pcmk__s(op, ""), pcmk__client_name(c));
3157         return false;
3158     }
3159 }
3160 
3161 // CRM_OP_REGISTER
3162 static xmlNode *
3163 handle_register_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3164 {
3165     xmlNode *reply = create_xml_node(NULL, "reply");
3166 
3167     CRM_ASSERT(request->ipc_client != NULL);
3168     crm_xml_add(reply, F_STONITH_OPERATION, CRM_OP_REGISTER);
3169     crm_xml_add(reply, F_STONITH_CLIENTID, request->ipc_client->id);
3170     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3171     pcmk__set_request_flags(request, pcmk__request_reuse_options);
3172     return reply;
3173 }
3174 
3175 // STONITH_OP_EXEC
3176 static xmlNode *
3177 handle_agent_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3178 {
3179     execute_agent_action(request->xml, &request->result);
3180     if (request->result.execution_status == PCMK_EXEC_PENDING) {
3181         return NULL;
3182     }
3183     return fenced_construct_reply(request->xml, NULL, &request->result);
3184 }
3185 
3186 // STONITH_OP_TIMEOUT_UPDATE
3187 static xmlNode *
3188 handle_update_timeout_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3189 {
3190     const char *call_id = crm_element_value(request->xml, F_STONITH_CALLID);
3191     const char *client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
3192     int op_timeout = 0;
3193 
3194     crm_element_value_int(request->xml, F_STONITH_TIMEOUT, &op_timeout);
3195     do_stonith_async_timeout_update(client_id, call_id, op_timeout);
3196     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3197     return NULL;
3198 }
3199 
3200 // STONITH_OP_QUERY
3201 static xmlNode *
3202 handle_query_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3203 {
3204     int timeout = 0;
3205     xmlNode *dev = NULL;
3206     const char *action = NULL;
3207     const char *target = NULL;
3208     const char *client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
3209     struct st_query_data *query = NULL;
3210 
3211     if (request->peer != NULL) {
3212         // Record it for the future notification
3213         create_remote_stonith_op(client_id, request->xml, TRUE);
3214     }
3215 
3216     /* Delete the DC node RELAY operation. */
3217     remove_relay_op(request->xml);
3218 
3219     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3220 
3221     dev = get_xpath_object("//@" F_STONITH_ACTION, request->xml, LOG_NEVER);
3222     if (dev != NULL) {
3223         const char *device = crm_element_value(dev, F_STONITH_DEVICE);
3224 
3225         if (pcmk__str_eq(device, "manual_ack", pcmk__str_casei)) {
3226             return NULL; // No query or reply necessary
3227         }
3228         target = crm_element_value(dev, F_STONITH_TARGET);
3229         action = crm_element_value(dev, F_STONITH_ACTION);
3230     }
3231 
3232     crm_log_xml_trace(request->xml, "Query");
3233 
3234     query = calloc(1, sizeof(struct st_query_data));
3235     CRM_ASSERT(query != NULL);
3236 
3237     query->reply = fenced_construct_reply(request->xml, NULL, &request->result);
3238     pcmk__str_update(&query->remote_peer, request->peer);
3239     pcmk__str_update(&query->client_id, client_id);
3240     pcmk__str_update(&query->target, target);
3241     pcmk__str_update(&query->action, action);
3242     query->call_options = request->call_options;
3243 
3244     crm_element_value_int(request->xml, F_STONITH_TIMEOUT, &timeout);
3245     get_capable_devices(target, action, timeout,
3246                         pcmk_is_set(query->call_options, st_opt_allow_suicide),
3247                         query, stonith_query_capable_device_cb, st_device_supports_none);
3248     return NULL;
3249 }
3250 
3251 // T_STONITH_NOTIFY
3252 static xmlNode *
3253 handle_notify_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3254 {
3255     const char *flag_name = NULL;
3256 
3257     CRM_ASSERT(request->ipc_client != NULL);
3258     flag_name = crm_element_value(request->xml, F_STONITH_NOTIFY_ACTIVATE);
3259     if (flag_name != NULL) {
3260         crm_debug("Enabling %s callbacks for client %s",
3261                   flag_name, pcmk__request_origin(request));
3262         pcmk__set_client_flags(request->ipc_client, get_stonith_flag(flag_name));
3263     }
3264 
3265     flag_name = crm_element_value(request->xml, F_STONITH_NOTIFY_DEACTIVATE);
3266     if (flag_name != NULL) {
3267         crm_debug("Disabling %s callbacks for client %s",
3268                   flag_name, pcmk__request_origin(request));
3269         pcmk__clear_client_flags(request->ipc_client,
3270                                  get_stonith_flag(flag_name));
3271     }
3272 
3273     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3274     pcmk__set_request_flags(request, pcmk__request_reuse_options);
3275 
3276     return pcmk__ipc_create_ack(request->ipc_flags, "ack", NULL, CRM_EX_OK);
3277 }
3278 
3279 // STONITH_OP_RELAY
3280 static xmlNode *
3281 handle_relay_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3282 {
3283     xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request->xml,
3284                                     LOG_TRACE);
3285 
3286     crm_notice("Received forwarded fencing request from "
3287                "%s %s to fence (%s) peer %s",
3288                pcmk__request_origin_type(request),
3289                pcmk__request_origin(request),
3290                crm_element_value(dev, F_STONITH_ACTION),
3291                crm_element_value(dev, F_STONITH_TARGET));
3292 
3293     if (initiate_remote_stonith_op(NULL, request->xml, FALSE) == NULL) {
3294         fenced_set_protocol_error(&request->result);
3295         return fenced_construct_reply(request->xml, NULL, &request->result);
3296     }
3297 
3298     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING, NULL);
3299     return NULL;
3300 }
3301 
3302 // STONITH_OP_FENCE
3303 static xmlNode *
3304 handle_fence_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3305 {
3306     if ((request->peer != NULL) || stand_alone) {
3307         fence_locally(request->xml, &request->result);
3308 
3309     } else if (pcmk_is_set(request->call_options, st_opt_manual_ack)) {
3310         switch (fenced_handle_manual_confirmation(request->ipc_client,
3311                                                   request->xml)) {
3312             case pcmk_rc_ok:
3313                 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE,
3314                                  NULL);
3315                 break;
3316             case EINPROGRESS:
3317                 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
3318                                  NULL);
3319                 break;
3320             default:
3321                 fenced_set_protocol_error(&request->result);
3322                 break;
3323         }
3324 
3325     } else {
3326         const char *alternate_host = NULL;
3327         xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request->xml,
3328                                         LOG_TRACE);
3329         const char *target = crm_element_value(dev, F_STONITH_TARGET);
3330         const char *action = crm_element_value(dev, F_STONITH_ACTION);
3331         const char *device = crm_element_value(dev, F_STONITH_DEVICE);
3332 
3333         if (request->ipc_client != NULL) {
3334             int tolerance = 0;
3335 
3336             crm_notice("Client %s wants to fence (%s) %s using %s",
3337                        pcmk__request_origin(request), action,
3338                        target, (device? device : "any device"));
3339             crm_element_value_int(dev, F_STONITH_TOLERANCE, &tolerance);
3340             if (stonith_check_fence_tolerance(tolerance, target, action)) {
3341                 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE,
3342                                  NULL);
3343                 return fenced_construct_reply(request->xml, NULL,
3344                                               &request->result);
3345             }
3346             alternate_host = check_alternate_host(target);
3347 
3348         } else {
3349             crm_notice("Peer %s wants to fence (%s) '%s' with device '%s'",
3350                        request->peer, action, target,
3351                        (device == NULL)? "(any)" : device);
3352         }
3353 
3354         if (alternate_host != NULL) {
3355             const char *client_id = NULL;
3356             remote_fencing_op_t *op = NULL;
3357 
3358             if (request->ipc_client->id == 0) {
3359                 client_id = crm_element_value(request->xml, F_STONITH_CLIENTID);
3360             } else {
3361                 client_id = request->ipc_client->id;
3362             }
3363 
3364             /* Create a duplicate fencing operation to relay with the client ID.
3365              * When a query response is received, this operation should be
3366              * deleted to avoid keeping the duplicate around.
3367              */
3368             op = create_remote_stonith_op(client_id, request->xml, FALSE);
3369 
3370             crm_xml_add(request->xml, F_STONITH_OPERATION, STONITH_OP_RELAY);
3371             crm_xml_add(request->xml, F_STONITH_CLIENTID,
3372                         request->ipc_client->id);
3373             crm_xml_add(request->xml, F_STONITH_REMOTE_OP_ID, op->id);
3374             send_cluster_message(crm_get_peer(0, alternate_host),
3375                                  crm_msg_stonith_ng, request->xml, FALSE);
3376             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
3377                              NULL);
3378 
3379         } else if (initiate_remote_stonith_op(request->ipc_client, request->xml,
3380                                               FALSE) == NULL) {
3381             fenced_set_protocol_error(&request->result);
3382 
3383         } else {
3384             pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_PENDING,
3385                              NULL);
3386         }
3387     }
3388 
3389     if (request->result.execution_status == PCMK_EXEC_PENDING) {
3390         return NULL;
3391     }
3392     return fenced_construct_reply(request->xml, NULL, &request->result);
3393 }
3394 
3395 // STONITH_OP_FENCE_HISTORY
3396 static xmlNode *
3397 handle_history_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3398 {
3399     xmlNode *reply = NULL;
3400     xmlNode *data = NULL;
3401 
3402     stonith_fence_history(request->xml, &data, request->peer,
3403                           request->call_options);
3404     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3405     if (!pcmk_is_set(request->call_options, st_opt_discard_reply)) {
3406         /* When the local node broadcasts its history, it sets
3407          * st_opt_discard_reply and doesn't need a reply.
3408          */
3409         reply = fenced_construct_reply(request->xml, data, &request->result);
3410     }
3411     free_xml(data);
3412     return reply;
3413 }
3414 
3415 // STONITH_OP_DEVICE_ADD
3416 static xmlNode *
3417 handle_device_add_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3418 {
3419     const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
3420     xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request->xml,
3421                                     LOG_ERR);
3422 
3423     if (is_privileged(request->ipc_client, op)) {
3424         int rc = stonith_device_register(dev, FALSE);
3425 
3426         pcmk__set_result(&request->result,
3427                          ((rc == pcmk_ok)? CRM_EX_OK : CRM_EX_ERROR),
3428                          stonith__legacy2status(rc),
3429                          ((rc == pcmk_ok)? NULL : pcmk_strerror(rc)));
3430     } else {
3431         pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
3432                          PCMK_EXEC_INVALID,
3433                          "Unprivileged users must register device via CIB");
3434     }
3435     fenced_send_device_notification(op, &request->result,
3436                                     (dev == NULL)? NULL : ID(dev));
3437     return fenced_construct_reply(request->xml, NULL, &request->result);
3438 }
3439 
3440 // STONITH_OP_DEVICE_DEL
3441 static xmlNode *
3442 handle_device_delete_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3443 {
3444     xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request->xml,
3445                                     LOG_ERR);
3446     const char *device_id = crm_element_value(dev, XML_ATTR_ID);
3447     const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
3448 
3449     if (is_privileged(request->ipc_client, op)) {
3450         stonith_device_remove(device_id, false);
3451         pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3452     } else {
3453         pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
3454                          PCMK_EXEC_INVALID,
3455                          "Unprivileged users must delete device via CIB");
3456     }
3457     fenced_send_device_notification(op, &request->result, device_id);
3458     return fenced_construct_reply(request->xml, NULL, &request->result);
3459 }
3460 
3461 // STONITH_OP_LEVEL_ADD
3462 static xmlNode *
3463 handle_level_add_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3464 {
3465     char *desc = NULL;
3466     const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
3467 
3468     if (is_privileged(request->ipc_client, op)) {
3469         fenced_register_level(request->xml, &desc, &request->result);
3470     } else {
3471         unpack_level_request(request->xml, NULL, NULL, NULL, &desc);
3472         pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
3473                          PCMK_EXEC_INVALID,
3474                          "Unprivileged users must add level via CIB");
3475     }
3476     fenced_send_level_notification(op, &request->result, desc);
3477     free(desc);
3478     return fenced_construct_reply(request->xml, NULL, &request->result);
3479 }
3480 
3481 // STONITH_OP_LEVEL_DEL
3482 static xmlNode *
3483 handle_level_delete_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3484 {
3485     char *desc = NULL;
3486     const char *op = crm_element_value(request->xml, F_STONITH_OPERATION);
3487 
3488     if (is_privileged(request->ipc_client, op)) {
3489         fenced_unregister_level(request->xml, &desc, &request->result);
3490     } else {
3491         unpack_level_request(request->xml, NULL, NULL, NULL, &desc);
3492         pcmk__set_result(&request->result, CRM_EX_INSUFFICIENT_PRIV,
3493                          PCMK_EXEC_INVALID,
3494                          "Unprivileged users must delete level via CIB");
3495     }
3496     fenced_send_level_notification(op, &request->result, desc);
3497     free(desc);
3498     return fenced_construct_reply(request->xml, NULL, &request->result);
3499 }
3500 
3501 // CRM_OP_RM_NODE_CACHE
3502 static xmlNode *
3503 handle_cache_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3504 {
3505     int node_id = 0;
3506     const char *name = NULL;
3507 
3508     crm_element_value_int(request->xml, XML_ATTR_ID, &node_id);
3509     name = crm_element_value(request->xml, XML_ATTR_UNAME);
3510     reap_crm_member(node_id, name);
3511     pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
3512     return NULL;
3513 }
3514 
3515 static xmlNode *
3516 handle_unknown_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3517 {
3518     crm_err("Unknown IPC request %s from %s %s",
3519             request->op, pcmk__request_origin_type(request),
3520             pcmk__request_origin(request));
3521     pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
3522                         "Unknown IPC request type '%s' (bug?)", request->op);
3523     return fenced_construct_reply(request->xml, NULL, &request->result);
3524 }
3525 
3526 static void
3527 fenced_register_handlers(void)
     /* [previous][next][first][last][top][bottom][index][help] */
3528 {
3529     pcmk__server_command_t handlers[] = {
3530         { CRM_OP_REGISTER, handle_register_request },
3531         { STONITH_OP_EXEC, handle_agent_request },
3532         { STONITH_OP_TIMEOUT_UPDATE, handle_update_timeout_request },
3533         { STONITH_OP_QUERY, handle_query_request },
3534         { T_STONITH_NOTIFY, handle_notify_request },
3535         { STONITH_OP_RELAY, handle_relay_request },
3536         { STONITH_OP_FENCE, handle_fence_request },
3537         { STONITH_OP_FENCE_HISTORY, handle_history_request },
3538         { STONITH_OP_DEVICE_ADD, handle_device_add_request },
3539         { STONITH_OP_DEVICE_DEL, handle_device_delete_request },
3540         { STONITH_OP_LEVEL_ADD, handle_level_add_request },
3541         { STONITH_OP_LEVEL_DEL, handle_level_delete_request },
3542         { CRM_OP_RM_NODE_CACHE, handle_cache_request },
3543         { NULL, handle_unknown_request },
3544     };
3545 
3546     fenced_handlers = pcmk__register_handlers(handlers);
3547 }
3548 
3549 void
3550 fenced_unregister_handlers(void)
     /* [previous][next][first][last][top][bottom][index][help] */
3551 {
3552     if (fenced_handlers != NULL) {
3553         g_hash_table_destroy(fenced_handlers);
3554         fenced_handlers = NULL;
3555     }
3556 }
3557 
3558 static void
3559 handle_request(pcmk__request_t *request)
     /* [previous][next][first][last][top][bottom][index][help] */
3560 {
3561     xmlNode *reply = NULL;
3562     const char *reason = NULL;
3563 
3564     if (fenced_handlers == NULL) {
3565         fenced_register_handlers();
3566     }
3567     reply = pcmk__process_request(request, fenced_handlers);
3568     if (reply != NULL) {
3569         if (pcmk_is_set(request->flags, pcmk__request_reuse_options)
3570             && (request->ipc_client != NULL)) {
3571             /* Certain IPC-only commands must reuse the call options from the
3572              * original request rather than the ones set by stonith_send_reply()
3573              * -> do_local_reply().
3574              */
3575             pcmk__ipc_send_xml(request->ipc_client, request->ipc_id, reply,
3576                                request->ipc_flags);
3577             request->ipc_client->request_id = 0;
3578         } else {
3579             stonith_send_reply(reply, request->call_options,
3580                                request->peer, request->ipc_client);
3581         }
3582         free_xml(reply);
3583     }
3584 
3585     reason = request->result.exit_reason;
3586     crm_debug("Processed %s request from %s %s: %s%s%s%s",
3587               request->op, pcmk__request_origin_type(request),
3588               pcmk__request_origin(request),
3589               pcmk_exec_status_str(request->result.execution_status),
3590               (reason == NULL)? "" : " (",
3591               (reason == NULL)? "" : reason,
3592               (reason == NULL)? "" : ")");
3593 }
3594 
3595 static void
3596 handle_reply(pcmk__client_t *client, xmlNode *request, const char *remote_peer)
     /* [previous][next][first][last][top][bottom][index][help] */
3597 {
3598     // Copy, because request might be freed before we want to log this
3599     char *op = crm_element_value_copy(request, F_STONITH_OPERATION);
3600 
3601     if (pcmk__str_eq(op, STONITH_OP_QUERY, pcmk__str_none)) {
3602         process_remote_stonith_query(request);
3603     } else if (pcmk__str_any_of(op, T_STONITH_NOTIFY, STONITH_OP_FENCE, NULL)) {
3604         fenced_process_fencing_reply(request);
3605     } else {
3606         crm_err("Ignoring unknown %s reply from %s %s",
3607                 pcmk__s(op, "untyped"), ((client == NULL)? "peer" : "client"),
3608                 ((client == NULL)? remote_peer : pcmk__client_name(client)));
3609         crm_log_xml_warn(request, "UnknownOp");
3610         free(op);
3611         return;
3612     }
3613     crm_debug("Processed %s reply from %s %s",
3614               op, ((client == NULL)? "peer" : "client"),
3615               ((client == NULL)? remote_peer : pcmk__client_name(client)));
3616     free(op);
3617 }
3618 
3619 /*!
3620  * \internal
3621  * \brief Handle a message from an IPC client or CPG peer
3622  *
3623  * \param[in,out] client      If not NULL, IPC client that sent message
3624  * \param[in]     id          If from IPC client, IPC message ID
3625  * \param[in]     flags       Message flags
3626  * \param[in,out] message     Message XML
3627  * \param[in]     remote_peer If not NULL, CPG peer that sent message
3628  */
3629 void
3630 stonith_command(pcmk__client_t *client, uint32_t id, uint32_t flags,
     /* [previous][next][first][last][top][bottom][index][help] */
3631                 xmlNode *message, const char *remote_peer)
3632 {
3633     int call_options = st_opt_none;
3634     bool is_reply = false;
3635 
3636     CRM_CHECK(message != NULL, return);
3637 
3638     if (get_xpath_object("//" T_STONITH_REPLY, message, LOG_NEVER) != NULL) {
3639         is_reply = true;
3640     }
3641     crm_element_value_int(message, F_STONITH_CALLOPTS, &call_options);
3642     crm_debug("Processing %ssynchronous %s %s %u from %s %s",
3643               pcmk_is_set(call_options, st_opt_sync_call)? "" : "a",
3644               crm_element_value(message, F_STONITH_OPERATION),
3645               (is_reply? "reply" : "request"), id,
3646               ((client == NULL)? "peer" : "client"),
3647               ((client == NULL)? remote_peer : pcmk__client_name(client)));
3648 
3649     if (pcmk_is_set(call_options, st_opt_sync_call)) {
3650         CRM_ASSERT(client == NULL || client->request_id == id);
3651     }
3652 
3653     if (is_reply) {
3654         handle_reply(client, message, remote_peer);
3655     } else {
3656         pcmk__request_t request = {
3657             .ipc_client     = client,
3658             .ipc_id         = id,
3659             .ipc_flags      = flags,
3660             .peer           = remote_peer,
3661             .xml            = message,
3662             .call_options   = call_options,
3663             .result         = PCMK__UNKNOWN_RESULT,
3664         };
3665 
3666         request.op = crm_element_value_copy(request.xml, F_STONITH_OPERATION);
3667         CRM_CHECK(request.op != NULL, return);
3668 
3669         if (pcmk_is_set(request.call_options, st_opt_sync_call)) {
3670             pcmk__set_request_flags(&request, pcmk__request_sync);
3671         }
3672 
3673         handle_request(&request);
3674         pcmk__reset_request(&request);
3675     }
3676 }

/* [previous][next][first][last][top][bottom][index][help] */