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

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