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 <stdio.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <sys/types.h>
17 #include <ctype.h>
18
19 #include <crm/crm.h>
20 #include <crm/lrmd.h>
21 #include <crm/common/xml.h>
22 #include <crm/common/util.h>
23 #include <crm/common/scheduler.h>
24
25 /*!
26 * \internal
27 * \brief Get string equivalent of an action type
28 *
29 * \param[in] action Action type
30 *
31 * \return Static string describing \p action
32 */
33 const char *
34 pcmk__action_text(enum pcmk__action_type action)
35 {
36 switch (action) {
37 case pcmk__action_stop:
38 return PCMK_ACTION_STOP;
39
40 case pcmk__action_stopped:
41 return PCMK_ACTION_STOPPED;
42
43 case pcmk__action_start:
44 return PCMK_ACTION_START;
45
46 case pcmk__action_started:
47 return PCMK_ACTION_RUNNING;
48
49 case pcmk__action_shutdown:
50 return PCMK_ACTION_DO_SHUTDOWN;
51
52 case pcmk__action_fence:
53 return PCMK_ACTION_STONITH;
54
55 case pcmk__action_monitor:
56 return PCMK_ACTION_MONITOR;
57
58 case pcmk__action_notify:
59 return PCMK_ACTION_NOTIFY;
60
61 case pcmk__action_notified:
62 return PCMK_ACTION_NOTIFIED;
63
64 case pcmk__action_promote:
65 return PCMK_ACTION_PROMOTE;
66
67 case pcmk__action_promoted:
68 return PCMK_ACTION_PROMOTED;
69
70 case pcmk__action_demote:
71 return PCMK_ACTION_DEMOTE;
72
73 case pcmk__action_demoted:
74 return PCMK_ACTION_DEMOTED;
75
76 default: // pcmk__action_unspecified or invalid
77 return "no_action";
78 }
79 }
80
81 /*!
82 * \internal
83 * \brief Parse an action type from an action name
84 *
85 * \param[in] action_name Action name
86 *
87 * \return Action type corresponding to \p action_name
88 */
89 enum pcmk__action_type
90 pcmk__parse_action(const char *action_name)
91 {
92 if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
93 return pcmk__action_stop;
94
95 } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOPPED, pcmk__str_none)) {
96 return pcmk__action_stopped;
97
98 } else if (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
99 return pcmk__action_start;
100
101 } else if (pcmk__str_eq(action_name, PCMK_ACTION_RUNNING, pcmk__str_none)) {
102 return pcmk__action_started;
103
104 } else if (pcmk__str_eq(action_name, PCMK_ACTION_DO_SHUTDOWN,
105 pcmk__str_none)) {
106 return pcmk__action_shutdown;
107
108 } else if (pcmk__str_eq(action_name, PCMK_ACTION_STONITH, pcmk__str_none)) {
109 return pcmk__action_fence;
110
111 } else if (pcmk__str_eq(action_name, PCMK_ACTION_MONITOR, pcmk__str_none)) {
112 return pcmk__action_monitor;
113
114 } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
115 return pcmk__action_notify;
116
117 } else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFIED,
118 pcmk__str_none)) {
119 return pcmk__action_notified;
120
121 } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
122 return pcmk__action_promote;
123
124 } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
125 return pcmk__action_demote;
126
127 } else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTED,
128 pcmk__str_none)) {
129 return pcmk__action_promoted;
130
131 } else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTED, pcmk__str_none)) {
132 return pcmk__action_demoted;
133 }
134 return pcmk__action_unspecified;
135 }
136
137 /*!
138 * \internal
139 * \brief Get string equivalent of a failure handling type
140 *
141 * \param[in] on_fail Failure handling type
142 *
143 * \return Static string describing \p on_fail
144 */
145 const char *
146 pcmk__on_fail_text(enum pcmk__on_fail on_fail)
147 {
148 switch (on_fail) {
149 case pcmk__on_fail_ignore:
150 return "ignore";
151
152 case pcmk__on_fail_demote:
153 return "demote";
154
155 case pcmk__on_fail_block:
156 return "block";
157
158 case pcmk__on_fail_restart:
159 return "recover";
160
161 case pcmk__on_fail_ban:
162 return "migrate";
163
164 case pcmk__on_fail_stop:
165 return "stop";
166
167 case pcmk__on_fail_fence_node:
168 return "fence";
169
170 case pcmk__on_fail_standby_node:
171 return "standby";
172
173 case pcmk__on_fail_restart_container:
174 return "restart-container";
175
176 case pcmk__on_fail_reset_remote:
177 return "reset-remote";
178 }
179 return "<unknown>";
180 }
181
182 /*!
183 * \internal
184 * \brief Free an action object
185 *
186 * \param[in,out] user_data Action object to free
187 */
188 void
189 pcmk__free_action(gpointer user_data)
190 {
191 pcmk_action_t *action = user_data;
192
|
(1) Event path: |
Condition "action == NULL", taking false branch. |
193 if (action == NULL) {
194 return;
195 }
196
197 g_list_free_full(action->actions_before, free);
198 g_list_free_full(action->actions_after, free);
|
CID (unavailable; MK=37424c131c9a01ce29d4118038751c59) (#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". |
199 g_clear_pointer(&action->extra, g_hash_table_destroy);
200 g_clear_pointer(&action->meta, g_hash_table_destroy);
201
202 pcmk__free_node_copy(action->node);
203 free(action->cancel_task);
204 free(action->reason);
205 free(action->task);
206 free(action->uuid);
207 free(action);
208 }
209
210 /*!
211 * \brief Generate an operation key (RESOURCE_ACTION_INTERVAL)
212 *
213 * \param[in] rsc_id ID of resource being operated on
214 * \param[in] op_type Operation name
215 * \param[in] interval_ms Operation interval
216 *
217 * \return Newly allocated memory containing operation key as string
218 *
219 * \note This function asserts on errors, so it will never return NULL.
220 * The caller is responsible for freeing the result with free().
221 */
222 char *
223 pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
224 {
225 pcmk__assert((rsc_id != NULL) && (op_type != NULL));
226 return pcmk__assert_asprintf(PCMK__OP_FMT, rsc_id, op_type, interval_ms);
227 }
228
229 static inline gboolean
230 convert_interval(const char *s, guint *interval_ms)
231 {
232 unsigned long l;
233
234 errno = 0;
235 l = strtoul(s, NULL, 10);
236
237 if (errno != 0) {
238 return FALSE;
239 }
240
241 *interval_ms = (guint) l;
242 return TRUE;
243 }
244
245 /*!
246 * \internal
247 * \brief Check for underbar-separated substring match
248 *
249 * \param[in] key Overall string being checked
250 * \param[in] position Match before underbar at this \p key index
251 * \param[in] matches Substrings to match (may contain underbars)
252 *
253 * \return \p key index of underbar before any matching substring,
254 * or 0 if none
255 */
256 static size_t
257 match_before(const char *key, size_t position, const char **matches)
258 {
259 for (int i = 0; matches[i] != NULL; ++i) {
260 const size_t match_len = strlen(matches[i]);
261
262 // Must have at least X_MATCH before position
263 if (position > (match_len + 1)) {
264 const size_t possible = position - match_len - 1;
265
266 if ((key[possible] == '_')
267 && (strncmp(key + possible + 1, matches[i], match_len) == 0)) {
268 return possible;
269 }
270 }
271 }
272 return 0;
273 }
274
275 gboolean
276 parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
277 {
278 guint local_interval_ms = 0;
279 const size_t key_len = (key == NULL)? 0 : strlen(key);
280
281 // Operation keys must be formatted as RSC_ACTION_INTERVAL
282 size_t action_underbar = 0; // Index in key of underbar before ACTION
283 size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
284 size_t possible = 0;
285
286 /* Underbar was a poor choice of separator since both RSC and ACTION can
287 * contain underbars. Here, list action names and name prefixes that can.
288 */
289 const char *actions_with_underbars[] = {
290 PCMK_ACTION_MIGRATE_FROM,
291 PCMK_ACTION_MIGRATE_TO,
292 NULL
293 };
294 const char *action_prefixes_with_underbars[] = {
295 "pre_" PCMK_ACTION_NOTIFY,
296 "post_" PCMK_ACTION_NOTIFY,
297 "confirmed-pre_" PCMK_ACTION_NOTIFY,
298 "confirmed-post_" PCMK_ACTION_NOTIFY,
299 NULL,
300 };
301
302 // Initialize output variables in case of early return
303 if (rsc_id) {
304 *rsc_id = NULL;
305 }
306 if (op_type) {
307 *op_type = NULL;
308 }
309 if (interval_ms) {
310 *interval_ms = 0;
311 }
312
313 // RSC_ACTION_INTERVAL implies a minimum of 5 characters
314 if (key_len < 5) {
315 return FALSE;
316 }
317
318 // Find, parse, and validate interval
319 interval_underbar = key_len - 2;
320 while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
321 --interval_underbar;
322 }
323 if ((interval_underbar == 2)
324 || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
325 return FALSE;
326 }
327
328 // Find the base (OCF) action name, disregarding prefixes
329 action_underbar = match_before(key, interval_underbar,
330 actions_with_underbars);
331 if (action_underbar == 0) {
332 action_underbar = interval_underbar - 2;
333 while ((action_underbar > 0) && (key[action_underbar] != '_')) {
334 --action_underbar;
335 }
336 if (action_underbar == 0) {
337 return FALSE;
338 }
339 }
340 possible = match_before(key, action_underbar,
341 action_prefixes_with_underbars);
342 if (possible != 0) {
343 action_underbar = possible;
344 }
345
346 // Set output variables
347 if (rsc_id != NULL) {
348 *rsc_id = strndup(key, action_underbar);
349 pcmk__mem_assert(*rsc_id);
350 }
351 if (op_type != NULL) {
352 *op_type = strndup(key + action_underbar + 1,
353 interval_underbar - action_underbar - 1);
354 pcmk__mem_assert(*op_type);
355 }
356 if (interval_ms != NULL) {
357 *interval_ms = local_interval_ms;
358 }
359 return TRUE;
360 }
361
362 char *
363 pcmk__notify_key(const char *rsc_id, const char *notify_type,
364 const char *op_type)
365 {
366 CRM_CHECK(rsc_id != NULL, return NULL);
367 CRM_CHECK(op_type != NULL, return NULL);
368 CRM_CHECK(notify_type != NULL, return NULL);
369 return pcmk__assert_asprintf("%s_%s_notify_%s_0",
370 rsc_id, notify_type, op_type);
371 }
372
373 /*!
374 * \brief Parse a transition magic string into its constituent parts
375 *
376 * \param[in] magic Magic string to parse (must be non-NULL)
377 * \param[out] uuid If non-NULL, where to store copy of parsed UUID
378 * \param[out] transition_id If non-NULL, where to store parsed transition ID
379 * \param[out] action_id If non-NULL, where to store parsed action ID
380 * \param[out] op_status If non-NULL, where to store parsed result status
381 * \param[out] op_rc If non-NULL, where to store parsed actual rc
382 * \param[out] target_rc If non-NULL, where to stored parsed target rc
383 *
384 * \return TRUE if key was valid, FALSE otherwise
385 * \note If uuid is supplied and this returns TRUE, the caller is responsible
386 * for freeing the memory for *uuid using free().
387 */
388 gboolean
389 decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
390 int *op_status, int *op_rc, int *target_rc)
391 {
392 int res = 0;
393 char *key = NULL;
394 gboolean result = TRUE;
395 int local_op_status = -1;
396 int local_op_rc = -1;
397
398 CRM_CHECK(magic != NULL, return FALSE);
399
400 #ifdef HAVE_SSCANF_M
401 res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
402 #else
403 // magic must have >=4 other characters
404 key = pcmk__assert_alloc(strlen(magic) - 3, sizeof(char));
405 res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
406 #endif
407 if (res == EOF) {
408 pcmk__err("Could not decode transition information '%s': %s", magic,
409 pcmk_rc_str(errno));
410 result = FALSE;
411 } else if (res < 3) {
412 pcmk__warn("Transition information '%s' incomplete (%d of 3 expected "
413 "items)",
414 magic, res);
415 result = FALSE;
416 } else {
417 if (op_status) {
418 *op_status = local_op_status;
419 }
420 if (op_rc) {
421 *op_rc = local_op_rc;
422 }
423 result = decode_transition_key(key, uuid, transition_id, action_id,
424 target_rc);
425 }
426 free(key);
427 return result;
428 }
429
430 char *
431 pcmk__transition_key(int transition_id, int action_id, int target_rc,
432 const char *node)
433 {
434 CRM_CHECK(node != NULL, return NULL);
435 return pcmk__assert_asprintf("%d:%d:%d:%-*s",
436 action_id, transition_id, target_rc, 36, node);
437 }
438
439 /*!
440 * \brief Parse a transition key into its constituent parts
441 *
442 * \param[in] key Transition key to parse (must be non-NULL)
443 * \param[out] uuid If non-NULL, where to store copy of parsed UUID
444 * \param[out] transition_id If non-NULL, where to store parsed transition ID
445 * \param[out] action_id If non-NULL, where to store parsed action ID
446 * \param[out] target_rc If non-NULL, where to stored parsed target rc
447 *
448 * \return TRUE if key was valid, FALSE otherwise
449 * \note If uuid is supplied and this returns TRUE, the caller is responsible
450 * for freeing the memory for *uuid using free().
451 */
452 gboolean
453 decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
454 int *target_rc)
455 {
456 int local_transition_id = -1;
457 int local_action_id = -1;
458 int local_target_rc = -1;
459 char local_uuid[37] = { '\0' };
460
461 // Initialize any supplied output arguments
462 if (uuid) {
463 *uuid = NULL;
464 }
465 if (transition_id) {
466 *transition_id = -1;
467 }
468 if (action_id) {
469 *action_id = -1;
470 }
471 if (target_rc) {
472 *target_rc = -1;
473 }
474
475 CRM_CHECK(key != NULL, return FALSE);
476 if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
477 &local_target_rc, local_uuid) != 4) {
478 pcmk__err("Invalid transition key '%s'", key);
479 return FALSE;
480 }
481 if (strlen(local_uuid) != 36) {
482 pcmk__warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
483 }
484 if (uuid) {
485 *uuid = pcmk__str_copy(local_uuid);
486 }
487 if (transition_id) {
488 *transition_id = local_transition_id;
489 }
490 if (action_id) {
491 *action_id = local_action_id;
492 }
493 if (target_rc) {
494 *target_rc = local_target_rc;
495 }
496 return TRUE;
497 }
498
499 int
500 rsc_op_expected_rc(const lrmd_event_data_t *op)
501 {
502 int rc = 0;
503
504 if (op && op->user_data) {
505 decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
506 }
507 return rc;
508 }
509
510 gboolean
511 did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
512 {
513 switch (op->op_status) {
514 case PCMK_EXEC_CANCELLED:
515 case PCMK_EXEC_PENDING:
516 return FALSE;
517
518 case PCMK_EXEC_NOT_SUPPORTED:
519 case PCMK_EXEC_TIMEOUT:
520 case PCMK_EXEC_ERROR:
521 case PCMK_EXEC_NOT_CONNECTED:
522 case PCMK_EXEC_NO_FENCE_DEVICE:
523 case PCMK_EXEC_NO_SECRETS:
524 case PCMK_EXEC_INVALID:
525 return TRUE;
526
527 default:
528 if (target_rc != op->rc) {
529 return TRUE;
530 }
531 }
532
533 return FALSE;
534 }
535
536 /*!
537 * \brief Create a CIB XML element for an operation
538 *
539 * \param[in,out] parent If not NULL, make new XML node a child of this
540 * \param[in] prefix Generate an ID using this prefix
541 * \param[in] task Operation task to set
542 * \param[in] interval_spec Operation interval to set
543 * \param[in] timeout If not NULL, operation timeout to set
544 *
545 * \return New XML object on success, NULL otherwise
546 */
547 xmlNode *
548 crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
549 const char *interval_spec, const char *timeout)
550 {
551 xmlNode *xml_op;
552
553 CRM_CHECK(prefix && task && interval_spec, return NULL);
554
555 xml_op = pcmk__xe_create(parent, PCMK_XE_OP);
556 pcmk__xe_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
557 pcmk__xe_set(xml_op, PCMK_META_INTERVAL, interval_spec);
558 pcmk__xe_set(xml_op, PCMK_XA_NAME, task);
559 if (timeout) {
560 pcmk__xe_set(xml_op, PCMK_META_TIMEOUT, timeout);
561 }
562 return xml_op;
563 }
564
565 /*!
566 * \brief Check whether an operation requires resource agent meta-data
567 *
568 * \param[in] rsc_class Resource agent class (or NULL to skip class check)
569 * \param[in] op Operation action (or NULL to skip op check)
570 *
571 * \return true if operation needs meta-data, false otherwise
572 * \note At least one of rsc_class and op must be specified.
573 */
574 bool
575 crm_op_needs_metadata(const char *rsc_class, const char *op)
576 {
577 /* Agent metadata is used to determine whether an agent reload is possible,
578 * so if this op is not relevant to that feature, we don't need metadata.
579 */
580
581 CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
582
583 if ((rsc_class != NULL)
584 && !pcmk__is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
585 // Metadata is needed only for resource classes that use parameters
586 return false;
587 }
588 if (op == NULL) {
589 return true;
590 }
591
592 // Metadata is needed only for these actions
593 return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR,
594 PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
595 PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
596 PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
597 PCMK_ACTION_NOTIFY, NULL);
598 }
599
600 /*!
601 * \internal
602 * \brief Check whether an action name is for a fencing action
603 *
604 * \param[in] action Action name to check
605 *
606 * \return \c true if \p action is \c PCMK_ACTION_OFF or \c PCMK_ACTION_REBOOT,
607 * or \c false otherwise
608 */
609 bool
610 pcmk__is_fencing_action(const char *action)
611 {
612 return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT, NULL);
613 }
614