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