Coverage Report

Created: 2026-03-31 07:01

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