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