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

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