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

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