1    	/*
2    	 * Copyright 2004-2026 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 Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	#include <stdlib.h>
14   	#include <stdio.h>
15   	#include <string.h>
16   	#include <libgen.h>
17   	#include <inttypes.h>
18   	#include <sys/types.h>
19   	
20   	#include <glib.h>
21   	#include <libxml/tree.h>            // xmlNode
22   	
23   	#include <crm/crm.h>
24   	#include <crm/stonith-ng.h>
25   	#include <crm/fencing/internal.h>
26   	#include <crm/common/xml.h>
27   	#include <crm/services_internal.h>
28   	
29   	#include "fencing_private.h"
30   	
31   	struct stonith_action_s {
32   	    /*! user defined data */
33   	    char *agent;
34   	    char *action;
35   	    GHashTable *args;
36   	    int timeout;
37   	    bool async;
38   	    void *userdata;
39   	    void (*done_cb) (int pid, const pcmk__action_result_t *result,
40   	                     void *user_data);
41   	    void (*fork_cb) (int pid, void *user_data);
42   	
43   	    svc_action_t *svc_action;
44   	
45   	    /*! internal timing information */
46   	    time_t initial_start_time;
47   	    int tries;
48   	    int remaining_timeout;
49   	    int max_retries;
50   	
51   	    int pid;
52   	    pcmk__action_result_t result;
53   	};
54   	
55   	static int internal_stonith_action_execute(stonith_action_t *action);
56   	static void log_action(stonith_action_t *action, pid_t pid);
57   	
58   	/*!
59   	 * \internal
60   	 * \brief Set an action's result based on services library result
61   	 *
62   	 * \param[in,out] action      Fence action to set result for
63   	 * \param[in,out] svc_action  Service action to get result from
64   	 */
65   	static void
66   	set_result_from_svc_action(stonith_action_t *action, svc_action_t *svc_action)
67   	{
68   	    services__copy_result(svc_action, &(action->result));
69   	    pcmk__set_result_output(&(action->result),
70   	                            services__grab_stdout(svc_action),
71   	                            services__grab_stderr(svc_action));
72   	}
73   	
74   	static void
75   	log_action(stonith_action_t *action, pid_t pid)
76   	{
77   	    /* The services library has already logged the output at info or debug
78   	     * level, so just raise to warning for stderr.
79   	     */
80   	    if (action->result.action_stderr != NULL) {
81   	        /* Logging the whole string confuses syslog when the string is xml */
82   	        char *prefix = pcmk__assert_asprintf("%s[%d] stderr:", action->agent,
83   	                                             pid);
84   	
85   	        crm_log_output(LOG_WARNING, prefix, action->result.action_stderr);
86   	        free(prefix);
87   	    }
88   	}
89   	
90   	static void
91   	append_config_arg(gpointer key, gpointer value, gpointer user_data)
92   	{
93   	    /* Filter out parameters handled directly by Pacemaker.
94   	     *
95   	     * STONITH_ATTR_ACTION_OP is added elsewhere and should never be part of the
96   	     * fencing resource's parameter list. We should ignore its value if it is
97   	     * configured there.
98   	     */
99   	    if (!pcmk__str_eq(key, STONITH_ATTR_ACTION_OP, pcmk__str_casei)
100  	        && !pcmk_stonith_param(key)
101  	        && !g_str_has_prefix(key, CRM_META "_")
102  	        && !pcmk__str_eq(key, PCMK_XA_CRM_FEATURE_SET, pcmk__str_none)) {
103  	
104  	        pcmk__trace("Passing %s=%s with fence action", (const char *) key,
105  	                    pcmk__s((const char *) value, ""));
106  	        pcmk__insert_dup((GHashTable *) user_data, key, pcmk__s(value, ""));
107  	    }
108  	}
109  	
110  	/*!
111  	 * \internal
112  	 * \brief Create a table of arguments for a fencing action
113  	 *
114  	 * \param[in] agent             Fencing agent name
115  	 * \param[in] action            Name of fencing action
116  	 * \param[in] target            Name of target node for fencing action
117  	 * \param[in] device_args       Fence device parameters
118  	 * \param[in] port_map          Target node-to-port mapping for fence device
119  	 * \param[in] default_host_arg  Default agent parameter for passing target
120  	 *
121  	 * \return Newly created hash table of arguments for fencing action
122  	 */
123  	static GHashTable *
124  	make_args(const char *agent, const char *action, const char *target,
125  	          GHashTable *device_args, GHashTable *port_map,
126  	          const char *default_host_arg)
127  	{
128  	    GHashTable *arg_list = NULL;
129  	    const char *value = NULL;
130  	
131  	    CRM_CHECK(action != NULL, return NULL);
132  	
133  	    arg_list = pcmk__strkey_table(free, free);
134  	
135  	    // Add action to arguments (using an alias if requested)
136  	    if (device_args) {
137  	        char *buffer = pcmk__assert_asprintf("pcmk_%s_action", action);
138  	
139  	        value = g_hash_table_lookup(device_args, buffer);
140  	        free(buffer);
141  	
142  	        if (value) {
143  	            pcmk__debug("Substituting '%s' for fence action %s targeting %s",
144  	                        value, action, pcmk__s(target, "no node"));
145  	            action = value;
146  	        }
147  	    }
148  	
149  	    // Tell the fence agent what action to perform
150  	    pcmk__insert_dup(arg_list, STONITH_ATTR_ACTION_OP, action);
151  	
152  	    /* If this is a fencing operation against another node, add more standard
153  	     * arguments.
154  	     */
155  	    if ((target != NULL) && (device_args != NULL)) {
156  	        const char *param = NULL;
157  	
158  	        /* Always pass the target's name, per
159  	         * https://github.com/ClusterLabs/fence-agents/blob/main/doc/FenceAgentAPI.md
160  	         */
161  	        pcmk__insert_dup(arg_list, "nodename", target);
162  	
163  	        // Check whether target should be specified as some other argument
164  	        param = g_hash_table_lookup(device_args, PCMK_FENCING_HOST_ARGUMENT);
165  	        if (param == NULL) {
166  	            // Use caller's default (likely from agent metadata)
167  	            param = default_host_arg;
168  	        }
169  	        if ((param != NULL)
170  	            && !pcmk__str_eq(agent, "fence_legacy", pcmk__str_none)
171  	            && !pcmk__str_eq(param, PCMK_VALUE_NONE, pcmk__str_casei)) {
172  	
173  	            value = g_hash_table_lookup(device_args, param);
174  	            if (pcmk__str_eq(value, "dynamic",
175  	                             pcmk__str_casei|pcmk__str_null_matches)) {
176  	                /* If the host argument is "dynamic" or not configured,
177  	                 * reset it to the target
178  	                 */
179  	                const char *alias = NULL;
180  	
181  	                if (port_map) {
182  	                    alias = g_hash_table_lookup(port_map, target);
183  	                }
184  	                if (alias == NULL) {
185  	                    alias = target;
186  	                }
187  	                pcmk__debug("Passing %s='%s' with fence action %s targeting %s",
188  	                            param, alias, action, pcmk__s(target, "no node"));
189  	                pcmk__insert_dup(arg_list, param, alias);
190  	            }
191  	        }
192  	    }
193  	
194  	    if (device_args) {
195  	        g_hash_table_foreach(device_args, append_config_arg, arg_list);
196  	    }
197  	
198  	    return arg_list;
199  	}
200  	
201  	/*!
202  	 * \internal
203  	 * \brief Free all memory used by a fencing action
204  	 *
205  	 * \param[in,out] action  Action to free
206  	 */
207  	void
208  	stonith__destroy_action(stonith_action_t *action)
209  	{
(1) Event path: Condition "action", taking true branch.
210  	    if (action) {
211  	        free(action->agent);
212  	        free(action->action);
CID (unavailable; MK=797220dd1ba896d0d0041bebcf755520) (#1 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(2) Event assign_union_field: The union field "in" of "_pp" is written.
(3) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
213  	        g_clear_pointer(&action->args, g_hash_table_destroy);
214  	        g_clear_pointer(&action->svc_action, services_action_free);
215  	        pcmk__reset_result(&(action->result));
216  	        free(action);
217  	    }
218  	}
219  	
220  	/*!
221  	 * \internal
222  	 * \brief Get the result of an executed fencing action
223  	 *
224  	 * \param[in] action  Executed action
225  	 *
226  	 * \return Pointer to action's result (or NULL if \p action is NULL)
227  	 */
228  	pcmk__action_result_t *
229  	stonith__action_result(stonith_action_t *action)
230  	{
231  	    return (action == NULL)? NULL : &(action->result);
232  	}
233  	
234  	#define FAILURE_MAX_RETRIES 2
235  	
236  	/*!
237  	 * \internal
238  	 * \brief Create a new fencing action to be executed
239  	 *
240  	 * \param[in] agent             Fence agent to use
241  	 * \param[in] action_name       Fencing action to be executed
242  	 * \param[in] target            Name of target of fencing action (if known)
243  	 * \param[in] timeout_sec       Timeout to be used when executing action
244  	 * \param[in] device_args       Parameters to pass to fence agent
245  	 * \param[in] port_map          Mapping of target names to device ports
246  	 * \param[in] default_host_arg  Default agent parameter for passing target
247  	 *
248  	 * \return Newly created fencing action (asserts on error, never NULL)
249  	 */
250  	stonith_action_t *
251  	stonith__action_create(const char *agent, const char *action_name,
252  	                       const char *target, int timeout_sec,
253  	                       GHashTable *device_args, GHashTable *port_map,
254  	                       const char *default_host_arg)
255  	{
256  	    stonith_action_t *action = pcmk__assert_alloc(1, sizeof(stonith_action_t));
257  	
258  	    pcmk__debug("Preparing '%s' action targeting %s using agent %s",
259  	                action_name, pcmk__s(target, "no node"), agent);
260  	
261  	    action->args = make_args(agent, action_name, target, device_args, port_map,
262  	                             default_host_arg);
263  	    action->agent = strdup(agent);
264  	    action->action = strdup(action_name);
265  	    action->timeout = action->remaining_timeout = timeout_sec;
266  	    action->max_retries = FAILURE_MAX_RETRIES;
267  	
268  	    pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN, PCMK_EXEC_UNKNOWN,
269  	                     "Initialization bug in fencing library");
270  	
271  	    if (device_args) {
272  	        char *buffer = pcmk__assert_asprintf("pcmk_%s_retries", action_name);
273  	        const char *value = g_hash_table_lookup(device_args, buffer);
274  	
275  	        free(buffer);
276  	
277  	        if (value) {
278  	            action->max_retries = atoi(value);
279  	        }
280  	    }
281  	
282  	    return action;
283  	}
284  	
285  	static gboolean
286  	update_remaining_timeout(stonith_action_t * action)
287  	{
288  	    int diff = time(NULL) - action->initial_start_time;
289  	
290  	    if (action->tries >= action->max_retries) {
291  	        pcmk__info("Attempted to execute agent %s (%s) the maximum number of "
292  	                   "times (%d) allowed",
293  	                   action->agent, action->action, action->max_retries);
294  	        action->remaining_timeout = 0;
295  	    } else if ((action->result.execution_status != PCMK_EXEC_TIMEOUT)
296  	               && (diff < (action->timeout * 0.7))) {
297  	        /* only set remaining timeout period if there is 30%
298  	         * or greater of the original timeout period left */
299  	        action->remaining_timeout = action->timeout - diff;
300  	    } else {
301  	        action->remaining_timeout = 0;
302  	    }
303  	    return action->remaining_timeout ? TRUE : FALSE;
304  	}
305  	
306  	/*!
307  	 * \internal
308  	 * \brief Map a fencing action result to a standard return code
309  	 *
310  	 * \param[in] result  Fencing action result to map
311  	 *
312  	 * \return Standard Pacemaker return code that best corresponds to \p result
313  	 */
314  	int
315  	stonith__result2rc(const pcmk__action_result_t *result)
316  	{
317  	    if (pcmk__result_ok(result)) {
318  	        return pcmk_rc_ok;
319  	    }
320  	
321  	    switch (result->execution_status) {
322  	        case PCMK_EXEC_PENDING:         return EINPROGRESS;
323  	        case PCMK_EXEC_CANCELLED:       return ECANCELED;
324  	        case PCMK_EXEC_TIMEOUT:         return ETIME;
325  	        case PCMK_EXEC_NOT_INSTALLED:   return ENOENT;
326  	        case PCMK_EXEC_NOT_SUPPORTED:   return EOPNOTSUPP;
327  	        case PCMK_EXEC_NOT_CONNECTED:   return ENOTCONN;
328  	        case PCMK_EXEC_NO_FENCE_DEVICE: return ENODEV;
329  	        case PCMK_EXEC_NO_SECRETS:      return EACCES;
330  	
331  	        /* For the fencing API, PCMK_EXEC_INVALID is used with fencer API
332  	         * operations that don't involve executing an agent (for example,
333  	         * registering devices). This allows us to use the CRM_EX_* codes in the
334  	         * exit status for finer-grained responses.
335  	         */
336  	        case PCMK_EXEC_INVALID:
337  	            switch (result->exit_status) {
338  	                case CRM_EX_INVALID_PARAM:      return EINVAL;
339  	                case CRM_EX_INSUFFICIENT_PRIV:  return EACCES;
340  	                case CRM_EX_PROTOCOL:           return EPROTO;
341  	
342  	               /* CRM_EX_EXPIRED is used for fencing operations left over from a
343  	                * previous instance of the fencer. For API backward
344  	                * compatibility, this is mapped to the previously used code for
345  	                * this case, EHOSTUNREACH.
346  	                */
347  	                case CRM_EX_EXPIRED:            return EHOSTUNREACH;
348  	                default:                        break;
349  	            }
350  	            break;
351  	
352  	        default:
353  	            break;
354  	    }
355  	
356  	    // Try to provide useful error code based on result's error output
357  	
358  	    if (result->action_stderr == NULL) {
359  	        return ENODATA;
360  	
361  	    } else if (strcasestr(result->action_stderr, "timed out")
362  	               || strcasestr(result->action_stderr, "timeout")) {
363  	        return ETIME;
364  	
365  	    } else if (strcasestr(result->action_stderr, "unrecognised action")
366  	               || strcasestr(result->action_stderr, "unrecognized action")
367  	               || strcasestr(result->action_stderr, "unsupported action")) {
368  	        return EOPNOTSUPP;
369  	    }
370  	
371  	    // Oh well, we tried
372  	    return pcmk_rc_error;
373  	}
374  	
375  	/*!
376  	 * \internal
377  	 * \brief Determine execution status equivalent of legacy fencer return code
378  	 *
379  	 * Fence action notifications, and fence action callbacks from older fencers
380  	 * (<=2.1.2) in a rolling upgrade, will have only a legacy return code. Map this
381  	 * to an execution status as best as possible (essentially, the inverse of
382  	 * stonith__result2rc()).
383  	 *
384  	 * \param[in] rc           Legacy return code from fencer
385  	 *
386  	 * \return Execution status best corresponding to \p rc
387  	 */
388  	int
389  	stonith__legacy2status(int rc)
390  	{
391  	    if (rc >= 0) {
392  	        return PCMK_EXEC_DONE;
393  	    }
394  	    switch (-rc) {
395  	        case EACCES:            return PCMK_EXEC_NO_SECRETS;
396  	        case ECANCELED:         return PCMK_EXEC_CANCELLED;
397  	        case EHOSTUNREACH:      return PCMK_EXEC_INVALID;
398  	        case EINPROGRESS:       return PCMK_EXEC_PENDING;
399  	        case ENODEV:            return PCMK_EXEC_NO_FENCE_DEVICE;
400  	        case ENOENT:            return PCMK_EXEC_NOT_INSTALLED;
401  	        case ENOTCONN:          return PCMK_EXEC_NOT_CONNECTED;
402  	        case EOPNOTSUPP:        return PCMK_EXEC_NOT_SUPPORTED;
403  	        case EPROTO:            return PCMK_EXEC_INVALID;
404  	        case EPROTONOSUPPORT:   return PCMK_EXEC_NOT_SUPPORTED;
405  	        case ETIME:             return PCMK_EXEC_TIMEOUT;
406  	        case ETIMEDOUT:         return PCMK_EXEC_TIMEOUT;
407  	        default:                return PCMK_EXEC_ERROR;
408  	    }
409  	}
410  	
411  	/*!
412  	 * \internal
413  	 * \brief Add a fencing result to an XML element as attributes
414  	 *
415  	 * \param[in,out] xml     XML element to add result to
416  	 * \param[in]     result  Fencing result to add (assume success if NULL)
417  	 */
418  	void
419  	stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result)
420  	{
421  	    int exit_status = CRM_EX_OK;
422  	    enum pcmk_exec_status execution_status = PCMK_EXEC_DONE;
423  	    const char *exit_reason = NULL;
424  	    const char *action_stdout = NULL;
425  	    int rc = pcmk_ok;
426  	
427  	    CRM_CHECK(xml != NULL, return);
428  	
429  	    if (result != NULL) {
430  	        exit_status = result->exit_status;
431  	        execution_status = result->execution_status;
432  	        exit_reason = result->exit_reason;
433  	        action_stdout = result->action_stdout;
434  	        rc = pcmk_rc2legacy(stonith__result2rc(result));
435  	    }
436  	
437  	    pcmk__xe_set_int(xml, PCMK__XA_OP_STATUS, (int) execution_status);
438  	    pcmk__xe_set_int(xml, PCMK__XA_RC_CODE, exit_status);
439  	    pcmk__xe_set(xml, PCMK_XA_EXIT_REASON, exit_reason);
440  	    pcmk__xe_set(xml, PCMK__XA_ST_OUTPUT, action_stdout);
441  	
442  	    /* @COMPAT Peers in rolling upgrades, Pacemaker Remote nodes, and external
443  	     * code that use libstonithd <=2.1.2 don't check for the full result, and
444  	     * need a legacy return code instead.
445  	     */
446  	    pcmk__xe_set_int(xml, PCMK__XA_ST_RC, rc);
447  	}
448  	
449  	/*!
450  	 * \internal
451  	 * \brief Find a fencing result beneath an XML element
452  	 *
453  	 * \param[in]  xml     XML element to search
454  	 *
455  	 * \return \p xml or descendant of it that contains a fencing result, else NULL
456  	 */
457  	xmlNode *
458  	stonith__find_xe_with_result(xmlNode *xml)
459  	{
460  	    xmlNode *match = pcmk__xpath_find_one(xml->doc,
461  	                                          "//*[@" PCMK__XA_RC_CODE "]",
462  	                                          PCMK__LOG_NEVER);
463  	
464  	    if (match == NULL) {
465  	        /* @COMPAT Peers <=2.1.2 in a rolling upgrade provide only a legacy
466  	         * return code, not a full result, so check for that.
467  	         */
468  	        match = pcmk__xpath_find_one(xml->doc, "//*[@" PCMK__XA_ST_RC "]",
469  	                                     LOG_ERR);
470  	    }
471  	    return match;
472  	}
473  	
474  	/*!
475  	 * \internal
476  	 * \brief Get a fencing result from an XML element's attributes
477  	 *
478  	 * \param[in]  xml     XML element with fencing result
479  	 * \param[out] result  Where to store fencing result
480  	 */
481  	void
482  	stonith__xe_get_result(const xmlNode *xml, pcmk__action_result_t *result)
483  	{
484  	    int exit_status = CRM_EX_OK;
485  	    int execution_status = PCMK_EXEC_DONE;
486  	    const char *exit_reason = NULL;
487  	    char *action_stdout = NULL;
488  	
489  	    CRM_CHECK((xml != NULL) && (result != NULL), return);
490  	
491  	    exit_reason = pcmk__xe_get(xml, PCMK_XA_EXIT_REASON);
492  	    action_stdout = pcmk__xe_get_copy(xml, PCMK__XA_ST_OUTPUT);
493  	
494  	    // A result must include an exit status and execution status
495  	    if ((pcmk__xe_get_int(xml, PCMK__XA_RC_CODE, &exit_status) != pcmk_rc_ok)
496  	        || (pcmk__xe_get_int(xml, PCMK__XA_OP_STATUS,
497  	                             &execution_status) != pcmk_rc_ok)) {
498  	        int rc = pcmk_ok;
499  	        exit_status = CRM_EX_ERROR;
500  	
501  	        /* @COMPAT Peers <=2.1.2 in rolling upgrades provide only a legacy
502  	         * return code, not a full result, so check for that.
503  	         */
504  	        if (pcmk__xe_get_int(xml, PCMK__XA_ST_RC, &rc) == pcmk_rc_ok) {
505  	            if ((rc == pcmk_ok) || (rc == -EINPROGRESS)) {
506  	                exit_status = CRM_EX_OK;
507  	            }
508  	            execution_status = stonith__legacy2status(rc);
509  	            exit_reason = pcmk_strerror(rc);
510  	
511  	        } else {
512  	            execution_status = PCMK_EXEC_ERROR;
513  	            exit_reason = "Fencer reply contained neither a full result "
514  	                          "nor a legacy return code (bug?)";
515  	        }
516  	    }
517  	    pcmk__set_result(result, exit_status, execution_status, exit_reason);
518  	    pcmk__set_result_output(result, action_stdout, NULL);
519  	}
520  	
521  	static void
522  	stonith_action_async_done(svc_action_t *svc_action)
523  	{
524  	    stonith_action_t *action = (stonith_action_t *) svc_action->cb_data;
525  	
526  	    set_result_from_svc_action(action, svc_action);
527  	    svc_action->params = NULL;
528  	    log_action(action, action->pid);
529  	
530  	    if (!pcmk__result_ok(&(action->result))
531  	        && update_remaining_timeout(action)) {
532  	
533  	        int rc = internal_stonith_action_execute(action);
534  	        if (rc == pcmk_ok) {
535  	            return;
536  	        }
537  	    }
538  	
539  	    if (action->done_cb) {
540  	        action->done_cb(action->pid, &(action->result), action->userdata);
541  	    }
542  	
543  	    action->svc_action = NULL; // don't remove our caller
544  	    stonith__destroy_action(action);
545  	}
546  	
547  	static void
548  	stonith_action_async_forked(svc_action_t *svc_action)
549  	{
550  	    stonith_action_t *action = (stonith_action_t *) svc_action->cb_data;
551  	
552  	    action->pid = svc_action->pid;
553  	    action->svc_action = svc_action;
554  	
555  	    if (action->fork_cb) {
556  	        (action->fork_cb) (svc_action->pid, action->userdata);
557  	    }
558  	
559  	    pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING,
560  	                     NULL);
561  	
562  	    pcmk__trace("Child process %d performing action '%s' successfully forked",
563  	                action->pid, action->action);
564  	}
565  	
566  	/*!
567  	 * \internal
568  	 * \brief Convert a fencing library action to a services library action
569  	 *
570  	 * \param[in,out] action  Fencing library action to convert
571  	 *
572  	 * \return Services library action equivalent to \p action on success; on error,
573  	 *         NULL will be returned and \p action's result will be set
574  	 */
575  	static svc_action_t *
576  	stonith_action_to_svc(stonith_action_t *action)
577  	{
578  	    static int stonith_sequence = 0;
579  	
580  	    char *path = pcmk__assert_asprintf(PCMK__FENCE_BINDIR "/%s", action->agent);
581  	    svc_action_t *svc_action = services_action_create_generic(path, NULL);
582  	
583  	    free(path);
584  	    if (svc_action->rc != PCMK_OCF_UNKNOWN) {
585  	        set_result_from_svc_action(action, svc_action);
586  	        services_action_free(svc_action);
587  	        return NULL;
588  	    }
589  	
590  	    svc_action->timeout = action->remaining_timeout * 1000;
591  	    svc_action->standard = pcmk__str_copy(PCMK_RESOURCE_CLASS_STONITH);
592  	    svc_action->id = pcmk__assert_asprintf("%s_%s_%dof%d", action->agent,
593  	                                           action->action, action->tries,
594  	                                           action->max_retries);
595  	    svc_action->agent = pcmk__str_copy(action->agent);
596  	    svc_action->sequence = stonith_sequence++;
597  	    svc_action->params = action->args;
598  	    svc_action->cb_data = (void *) action;
599  	    svc_action->flags = pcmk__set_flags_as(__func__, __LINE__,
600  	                                           LOG_TRACE, "Action",
601  	                                           svc_action->id, svc_action->flags,
602  	                                           SVC_ACTION_NON_BLOCKED,
603  	                                           "SVC_ACTION_NON_BLOCKED");
604  	
605  	    return svc_action;
606  	}
607  	
608  	static int
609  	internal_stonith_action_execute(stonith_action_t * action)
610  	{
611  	    int rc = pcmk_ok;
612  	    int is_retry = 0;
613  	    svc_action_t *svc_action = NULL;
614  	
615  	    CRM_CHECK(action != NULL, return -EINVAL);
616  	
617  	    if ((action->action == NULL) || (action->args == NULL)
618  	        || (action->agent == NULL)) {
619  	        pcmk__set_result(&(action->result), PCMK_OCF_UNKNOWN_ERROR,
620  	                         PCMK_EXEC_ERROR_FATAL, "Bug in fencing library");
621  	        return -EINVAL;
622  	    }
623  	
624  	    if (action->tries++ == 0) {
625  	        // First attempt of the desired action
626  	        action->initial_start_time = time(NULL);
627  	    } else {
628  	        // Later attempt after earlier failure
629  	        pcmk__info("Attempt %d to execute '%s' action of agent %s (%ds timeout "
630  	                   "remaining)",
631  	                   action->tries, action->action, action->agent,
632  	                   action->remaining_timeout);
633  	        is_retry = 1;
634  	    }
635  	
636  	    svc_action = stonith_action_to_svc(action);
637  	    if (svc_action == NULL) {
638  	        // The only possible errors are out-of-memory and too many arguments
639  	        return -E2BIG;
640  	    }
641  	
642  	    /* keep retries from executing out of control and free previous results */
643  	    if (is_retry) {
644  	        pcmk__reset_result(&(action->result));
645  	        // @TODO This should be nonblocking via timer if mainloop is used
646  	        sleep(1);
647  	    }
648  	
649  	    if (action->async) {
650  	        // We never create a recurring action, so this should always return TRUE
651  	        CRM_LOG_ASSERT(services_action_async_fork_notify(svc_action,
652  	                                              &stonith_action_async_done,
653  	                                              &stonith_action_async_forked));
654  	        return pcmk_ok;
655  	
656  	    } else if (!services_action_sync(svc_action)) {
657  	        rc = -ECONNABORTED; // @TODO Update API to return more useful error
658  	    }
659  	
660  	    set_result_from_svc_action(action, svc_action);
661  	    svc_action->params = NULL;
662  	    services_action_free(svc_action);
663  	    return rc;
664  	}
665  	
666  	/*!
667  	 * \internal
668  	 * \brief Kick off execution of an async fencing action
669  	 *
670  	 * \param[in,out] action        Action to be executed
671  	 * \param[in,out] userdata      Datapointer to be passed to callbacks
672  	 * \param[in]     done          Callback to notify action has failed/succeeded
673  	 * \param[in]     fork_callback Callback to notify successful fork of child
674  	 *
675  	 * \return pcmk_ok if ownership of action has been taken, -errno otherwise
676  	 */
677  	int
678  	stonith__execute_async(stonith_action_t * action, void *userdata,
679  	                       void (*done) (int pid,
680  	                                     const pcmk__action_result_t *result,
681  	                                     void *user_data),
682  	                       void (*fork_cb) (int pid, void *user_data))
683  	{
684  	    if (!action) {
685  	        return -EINVAL;
686  	    }
687  	
688  	    action->userdata = userdata;
689  	    action->done_cb = done;
690  	    action->fork_cb = fork_cb;
691  	    action->async = true;
692  	
693  	    return internal_stonith_action_execute(action);
694  	}
695  	
696  	/*!
697  	 * \internal
698  	 * \brief Execute a fencing action
699  	 *
700  	 * \param[in,out] action  Action to execute
701  	 *
702  	 * \return pcmk_ok on success, -errno otherwise
703  	 */
704  	int
705  	stonith__execute(stonith_action_t *action)
706  	{
707  	    int rc = pcmk_ok;
708  	
709  	    CRM_CHECK(action != NULL, return -EINVAL);
710  	
711  	    // Keep trying until success, max retries, or timeout
712  	    do {
713  	        rc = internal_stonith_action_execute(action);
714  	    } while ((rc != pcmk_ok) && update_remaining_timeout(action));
715  	
716  	    return rc;
717  	}
718