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);
|
(2) Event path: |
Condition "_p", taking true branch. |
199 g_clear_pointer(&action->extra, g_hash_table_destroy);
|
CID (unavailable; MK=37424c131c9a01ce29d4118038751c59) (#2 of 2): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(3) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(4) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
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 && g_str_has_prefix(key + possible + 1, matches[i])) {
268
269 return possible;
270 }
271 }
272 }
273 return 0;
274 }
275
276 gboolean
277 parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
278 {
279 guint local_interval_ms = 0;
280 const size_t key_len = (key == NULL)? 0 : strlen(key);
281
282 // Operation keys must be formatted as RSC_ACTION_INTERVAL
283 size_t action_underbar = 0; // Index in key of underbar before ACTION
284 size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
285 size_t possible = 0;
286
287 /* Underbar was a poor choice of separator since both RSC and ACTION can
288 * contain underbars. Here, list action names and name prefixes that can.
289 */
290 const char *actions_with_underbars[] = {
291 PCMK_ACTION_MIGRATE_FROM,
292 PCMK_ACTION_MIGRATE_TO,
293 NULL
294 };
295 const char *action_prefixes_with_underbars[] = {
296 "pre_" PCMK_ACTION_NOTIFY,
297 "post_" PCMK_ACTION_NOTIFY,
298 "confirmed-pre_" PCMK_ACTION_NOTIFY,
299 "confirmed-post_" PCMK_ACTION_NOTIFY,
300 NULL,
301 };
302
303 // Initialize output variables in case of early return
304 if (rsc_id) {
305 *rsc_id = NULL;
306 }
307 if (op_type) {
308 *op_type = NULL;
309 }
310 if (interval_ms) {
311 *interval_ms = 0;
312 }
313
314 // RSC_ACTION_INTERVAL implies a minimum of 5 characters
315 if (key_len < 5) {
316 return FALSE;
317 }
318
319 // Find, parse, and validate interval
320 interval_underbar = key_len - 2;
321 while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
322 --interval_underbar;
323 }
324 if ((interval_underbar == 2)
325 || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
326 return FALSE;
327 }
328
329 // Find the base (OCF) action name, disregarding prefixes
330 action_underbar = match_before(key, interval_underbar,
331 actions_with_underbars);
332 if (action_underbar == 0) {
333 action_underbar = interval_underbar - 2;
334 while ((action_underbar > 0) && (key[action_underbar] != '_')) {
335 --action_underbar;
336 }
337 if (action_underbar == 0) {
338 return FALSE;
339 }
340 }
341 possible = match_before(key, action_underbar,
342 action_prefixes_with_underbars);
343 if (possible != 0) {
344 action_underbar = possible;
345 }
346
347 // Set output variables
348 if (rsc_id != NULL) {
349 *rsc_id = strndup(key, action_underbar);
350 pcmk__mem_assert(*rsc_id);
351 }
352 if (op_type != NULL) {
353 *op_type = strndup(key + action_underbar + 1,
354 interval_underbar - action_underbar - 1);
355 pcmk__mem_assert(*op_type);
356 }
357 if (interval_ms != NULL) {
358 *interval_ms = local_interval_ms;
359 }
360 return TRUE;
361 }
362
363 char *
364 pcmk__notify_key(const char *rsc_id, const char *notify_type,
365 const char *op_type)
366 {
367 CRM_CHECK(rsc_id != NULL, return NULL);
368 CRM_CHECK(op_type != NULL, return NULL);
369 CRM_CHECK(notify_type != NULL, return NULL);
370 return pcmk__assert_asprintf("%s_%s_notify_%s_0",
371 rsc_id, notify_type, op_type);
372 }
373
374 /*!
375 * \brief Parse a transition magic string into its constituent parts
376 *
377 * \param[in] magic Magic string to parse (must be non-NULL)
378 * \param[out] uuid If non-NULL, where to store copy of parsed UUID
379 * \param[out] transition_id If non-NULL, where to store parsed transition ID
380 * \param[out] action_id If non-NULL, where to store parsed action ID
381 * \param[out] op_status If non-NULL, where to store parsed result status
382 * \param[out] op_rc If non-NULL, where to store parsed actual rc
383 * \param[out] target_rc If non-NULL, where to stored parsed target rc
384 *
385 * \return TRUE if key was valid, FALSE otherwise
386 * \note If uuid is supplied and this returns TRUE, the caller is responsible
387 * for freeing the memory for *uuid using free().
388 */
389 gboolean
390 decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
391 int *op_status, int *op_rc, int *target_rc)
392 {
393 int res = 0;
394 char *key = NULL;
395 gboolean result = TRUE;
396 int local_op_status = -1;
397 int local_op_rc = -1;
398
399 CRM_CHECK(magic != NULL, return FALSE);
400
401 #ifdef HAVE_SSCANF_M
402 res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
403 #else
404 // magic must have >=4 other characters
405 key = pcmk__assert_alloc(strlen(magic) - 3, sizeof(char));
406 res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
407 #endif
408 if (res == EOF) {
409 pcmk__err("Could not decode transition information '%s': %s", magic,
410 pcmk_rc_str(errno));
411 result = FALSE;
412 } else if (res < 3) {
413 pcmk__warn("Transition information '%s' incomplete (%d of 3 expected "
414 "items)",
415 magic, res);
416 result = FALSE;
417 } else {
418 if (op_status) {
419 *op_status = local_op_status;
420 }
421 if (op_rc) {
422 *op_rc = local_op_rc;
423 }
424 result = decode_transition_key(key, uuid, transition_id, action_id,
425 target_rc);
426 }
427 free(key);
428 return result;
429 }
430
431 char *
432 pcmk__transition_key(int transition_id, int action_id, int target_rc,
433 const char *node)
434 {
435 CRM_CHECK(node != NULL, return NULL);
436 return pcmk__assert_asprintf("%d:%d:%d:%-*s",
437 action_id, transition_id, target_rc, 36, node);
438 }
439
440 /*!
441 * \brief Parse a transition key into its constituent parts
442 *
443 * \param[in] key Transition key to parse (must be non-NULL)
444 * \param[out] uuid If non-NULL, where to store copy of parsed UUID
445 * \param[out] transition_id If non-NULL, where to store parsed transition ID
446 * \param[out] action_id If non-NULL, where to store parsed action ID
447 * \param[out] target_rc If non-NULL, where to stored parsed target rc
448 *
449 * \return TRUE if key was valid, FALSE otherwise
450 * \note If uuid is supplied and this returns TRUE, the caller is responsible
451 * for freeing the memory for *uuid using free().
452 */
453 gboolean
454 decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
455 int *target_rc)
456 {
457 int local_transition_id = -1;
458 int local_action_id = -1;
459 int local_target_rc = -1;
460 char local_uuid[37] = { '\0' };
461
462 // Initialize any supplied output arguments
463 if (uuid) {
464 *uuid = NULL;
465 }
466 if (transition_id) {
467 *transition_id = -1;
468 }
469 if (action_id) {
470 *action_id = -1;
471 }
472 if (target_rc) {
473 *target_rc = -1;
474 }
475
476 CRM_CHECK(key != NULL, return FALSE);
477 if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
478 &local_target_rc, local_uuid) != 4) {
479 pcmk__err("Invalid transition key '%s'", key);
480 return FALSE;
481 }
482 if (strlen(local_uuid) != 36) {
483 pcmk__warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
484 }
485 if (uuid) {
486 *uuid = pcmk__str_copy(local_uuid);
487 }
488 if (transition_id) {
489 *transition_id = local_transition_id;
490 }
491 if (action_id) {
492 *action_id = local_action_id;
493 }
494 if (target_rc) {
495 *target_rc = local_target_rc;
496 }
497 return TRUE;
498 }
499
500 int
501 rsc_op_expected_rc(const lrmd_event_data_t *op)
502 {
503 int rc = 0;
504
505 if (op && op->user_data) {
506 decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
507 }
508 return rc;
509 }
510
511 gboolean
512 did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
513 {
514 switch (op->op_status) {
515 case PCMK_EXEC_CANCELLED:
516 case PCMK_EXEC_PENDING:
517 return FALSE;
518
519 case PCMK_EXEC_NOT_SUPPORTED:
520 case PCMK_EXEC_TIMEOUT:
521 case PCMK_EXEC_ERROR:
522 case PCMK_EXEC_NOT_CONNECTED:
523 case PCMK_EXEC_NO_FENCE_DEVICE:
524 case PCMK_EXEC_NO_SECRETS:
525 case PCMK_EXEC_INVALID:
526 return TRUE;
527
528 default:
529 if (target_rc != op->rc) {
530 return TRUE;
531 }
532 }
533
534 return FALSE;
535 }
536
537 /*!
538 * \brief Create a CIB XML element for an operation
539 *
540 * \param[in,out] parent If not NULL, make new XML node a child of this
541 * \param[in] prefix Generate an ID using this prefix
542 * \param[in] task Operation task to set
543 * \param[in] interval_spec Operation interval to set
544 * \param[in] timeout If not NULL, operation timeout to set
545 *
546 * \return New XML object on success, NULL otherwise
547 */
548 xmlNode *
549 crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
550 const char *interval_spec, const char *timeout)
551 {
552 xmlNode *xml_op;
553
554 CRM_CHECK(prefix && task && interval_spec, return NULL);
555
556 xml_op = pcmk__xe_create(parent, PCMK_XE_OP);
557 pcmk__xe_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
558 pcmk__xe_set(xml_op, PCMK_META_INTERVAL, interval_spec);
559 pcmk__xe_set(xml_op, PCMK_XA_NAME, task);
560 if (timeout) {
561 pcmk__xe_set(xml_op, PCMK_META_TIMEOUT, timeout);
562 }
563 return xml_op;
564 }
565
566 /*!
567 * \brief Check whether an operation requires resource agent meta-data
568 *
569 * \param[in] rsc_class Resource agent class (or NULL to skip class check)
570 * \param[in] op Operation action (or NULL to skip op check)
571 *
572 * \return true if operation needs meta-data, false otherwise
573 * \note At least one of rsc_class and op must be specified.
574 */
575 bool
576 crm_op_needs_metadata(const char *rsc_class, const char *op)
577 {
578 /* Agent metadata is used to determine whether an agent reload is possible,
579 * so if this op is not relevant to that feature, we don't need metadata.
580 */
581
582 CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
583
584 if ((rsc_class != NULL)
585 && !pcmk__is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
586 // Metadata is needed only for resource classes that use parameters
587 return false;
588 }
589 if (op == NULL) {
590 return true;
591 }
592
593 // Metadata is needed only for these actions
594 return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR,
595 PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
596 PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
597 PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
598 PCMK_ACTION_NOTIFY, NULL);
599 }
600
601 /*!
602 * \internal
603 * \brief Check whether an action name is for a fencing action
604 *
605 * \param[in] action Action name to check
606 *
607 * \return \c true if \p action is \c PCMK_ACTION_OFF or \c PCMK_ACTION_REBOOT,
608 * or \c false otherwise
609 */
610 bool
611 pcmk__is_fencing_action(const char *action)
612 {
613 return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT, NULL);
614 }
615