root/daemons/fenced/fenced_commands.c

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

DEFINITIONS

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

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

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