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