1    	/*
2    	 * Copyright 2015-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   	
14   	#include <crm/crm.h>
15   	#include <crm/lrmd.h>
16   	#include <crm/common/xml.h>
17   	
18   	const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX] = {
19   	    [PCMK__alert_key_recipient] = "CRM_alert_recipient",
20   	    [PCMK__alert_key_node] = "CRM_alert_node",
21   	    [PCMK__alert_key_nodeid] = "CRM_alert_nodeid",
22   	    [PCMK__alert_key_rsc] = "CRM_alert_rsc",
23   	    [PCMK__alert_key_task] = "CRM_alert_task",
24   	    [PCMK__alert_key_interval] = "CRM_alert_interval",
25   	    [PCMK__alert_key_desc] = "CRM_alert_desc",
26   	    [PCMK__alert_key_status] = "CRM_alert_status",
27   	    [PCMK__alert_key_target_rc] = "CRM_alert_target_rc",
28   	    [PCMK__alert_key_rc] = "CRM_alert_rc",
29   	    [PCMK__alert_key_kind] = "CRM_alert_kind",
30   	    [PCMK__alert_key_version] = "CRM_alert_version",
31   	    [PCMK__alert_key_node_sequence] = PCMK__ALERT_NODE_SEQUENCE,
32   	    [PCMK__alert_key_timestamp] = "CRM_alert_timestamp",
33   	    [PCMK__alert_key_attribute_name] = "CRM_alert_attribute_name",
34   	    [PCMK__alert_key_attribute_value] = "CRM_alert_attribute_value",
35   	    [PCMK__alert_key_timestamp_epoch] = "CRM_alert_timestamp_epoch",
36   	    [PCMK__alert_key_timestamp_usec] = "CRM_alert_timestamp_usec",
37   	    [PCMK__alert_key_exec_time] = "CRM_alert_exec_time",
38   	};
39   	
40   	/*!
41   	 * \brief Create a new alert entry structure
42   	 *
43   	 * \param[in] id  ID to use
44   	 * \param[in] path  Path to alert agent executable
45   	 *
46   	 * \return Pointer to newly allocated alert entry
47   	 * \note Non-string fields will be filled in with defaults.
48   	 *       It is the caller's responsibility to free the result,
49   	 *       using pcmk__free_alert().
50   	 */
51   	pcmk__alert_t *
52   	pcmk__alert_new(const char *id, const char *path)
53   	{
54   	    pcmk__alert_t *entry = pcmk__assert_alloc(1, sizeof(pcmk__alert_t));
55   	
56   	    pcmk__assert((id != NULL) && (path != NULL));
57   	    entry->id = pcmk__str_copy(id);
58   	    entry->path = pcmk__str_copy(path);
59   	    entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
60   	    entry->flags = pcmk__alert_default;
61   	    return entry;
62   	}
63   	
64   	void
65   	pcmk__free_alert(pcmk__alert_t *entry)
66   	{
67   	    if (entry) {
68   	        free(entry->id);
69   	        free(entry->path);
70   	        free(entry->tstamp_format);
71   	        free(entry->recipient);
72   	
73   	        g_strfreev(entry->select_attribute_name);
74   	        g_clear_pointer(&entry->envvars, g_hash_table_destroy);
75   	
76   	        free(entry);
77   	    }
78   	}
79   	
80   	/*!
81   	 * \internal
82   	 * \brief Duplicate an alert entry
83   	 *
84   	 * \param[in] entry  Alert entry to duplicate
85   	 *
86   	 * \return Duplicate of alert entry
87   	 */
88   	pcmk__alert_t *
89   	pcmk__dup_alert(const pcmk__alert_t *entry)
90   	{
91   	    pcmk__alert_t *new_entry = pcmk__alert_new(entry->id, entry->path);
92   	
93   	    new_entry->timeout = entry->timeout;
94   	    new_entry->flags = entry->flags;
95   	    new_entry->envvars = pcmk__str_table_dup(entry->envvars);
96   	    new_entry->tstamp_format = pcmk__str_copy(entry->tstamp_format);
(1) Event alloc_fn: Storage is returned from allocation function "pcmk__str_copy_as". [details]
(2) Event assign: Assigning: "new_entry->recipient" = "pcmk__str_copy_as("alerts.c", <anonymous>, 97U, entry->recipient)".
Also see events: [return_alloc]
97   	    new_entry->recipient = pcmk__str_copy(entry->recipient);
(3) Event path: Condition "entry->select_attribute_name", taking true branch.
98   	    if (entry->select_attribute_name) {
99   	        new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name);
100  	    }
(4) Event return_alloc: Returning "new_entry", where "new_entry->recipient" is allocated memory.
Also see events: [alloc_fn][assign]
101  	    return new_entry;
102  	}
103  	
104  	void
105  	pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
106  	                    const char *value)
107  	{
108  	    pcmk__assert((table != NULL) && (name >= 0)
109  	                 && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
110  	    if (value == NULL) {
111  	        pcmk__trace("Removing alert key %s", pcmk__alert_keys[name]);
112  	        g_hash_table_remove(table, pcmk__alert_keys[name]);
113  	    } else {
114  	        pcmk__trace("Inserting alert key %s = '%s'", pcmk__alert_keys[name],
115  	                    value);
116  	        pcmk__insert_dup(table, pcmk__alert_keys[name], value);
117  	    }
118  	}
119  	
120  	void
121  	pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
122  	                        int value)
123  	{
124  	    pcmk__assert((table != NULL) && (name >= 0)
125  	                 && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
126  	    pcmk__trace("Inserting alert key %s = %d", pcmk__alert_keys[name], value);
127  	    g_hash_table_insert(table, pcmk__str_copy(pcmk__alert_keys[name]),
128  	                        pcmk__itoa(value));
129  	}
130  	
131  	#define READABLE_DEFAULT pcmk__readable_interval(PCMK__ALERT_DEFAULT_TIMEOUT_MS)
132  	
133  	/*!
134  	 * \internal
135  	 * \brief Unpack options for an alert or alert recipient from its
136  	 *        meta-attributes in the CIB XML configuration
137  	 *
138  	 * \param[in,out] xml          Alert or recipient XML
139  	 * \param[in,out] entry        Where to store unpacked values
140  	 * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
141  	 *
142  	 * \return Standard Pacemaker return code
143  	 */
144  	static int
145  	unpack_alert_options(xmlNode *xml, pcmk__alert_t *entry,
146  	                     unsigned int *max_timeout)
147  	{
148  	    GHashTable *config_hash = pcmk__strkey_table(free, free);
149  	    crm_time_t *now = crm_time_new(NULL);
150  	    const char *value = NULL;
151  	    int rc = pcmk_rc_ok;
152  	
153  	    pcmk_rule_input_t rule_input = {
154  	        .now = now,
155  	    };
156  	
157  	    pcmk__unpack_nvpair_blocks(xml, PCMK_XE_META_ATTRIBUTES, NULL, &rule_input,
158  	                               config_hash, NULL, xml->doc);
159  	    crm_time_free(now);
160  	
161  	    value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
162  	    if ((value != NULL) && !pcmk__is_true(value)) {
163  	        // No need to continue unpacking
164  	        rc = pcmk_rc_disabled;
165  	        goto done;
166  	    }
167  	
168  	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
169  	    if (value != NULL) {
170  	        long long timeout_ms = 0;
171  	
172  	        if ((pcmk__parse_ms(value, &timeout_ms) != pcmk_rc_ok)
173  	            || (timeout_ms <= 0)) {
174  	
175  	            entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
176  	
177  	            if (timeout_ms == 0) {
178  	                pcmk__trace("Alert %s uses default timeout (%s)", entry->id,
179  	                            READABLE_DEFAULT);
180  	            } else {
181  	                pcmk__config_warn("Using default timeout (%s) for alert %s "
182  	                                  "because '%s' is not a valid timeout",
183  	                                  entry->id, value, READABLE_DEFAULT);
184  	            }
185  	
186  	        } else {
187  	            entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
188  	            pcmk__trace("Alert %s uses timeout of %s", entry->id,
189  	                        pcmk__readable_interval(entry->timeout));
190  	        }
191  	        if (entry->timeout > *max_timeout) {
192  	            *max_timeout = entry->timeout;
193  	        }
194  	    }
195  	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
196  	    if (value != NULL) {
197  	        /* hard to do any checks here as merely anything can
198  	         * can be a valid time-format-string
199  	         */
200  	        entry->tstamp_format = strdup(value);
201  	        pcmk__trace("Alert %s uses timestamp format '%s'", entry->id,
202  	                    entry->tstamp_format);
203  	    }
204  	
205  	done:
206  	    g_hash_table_destroy(config_hash);
207  	    return rc;
208  	}
209  	
210  	/*!
211  	 * \internal
212  	 * \brief Unpack agent parameters for an alert or alert recipient into an
213  	 *        environment variable list based on its CIB XML configuration
214  	 *
215  	 * \param[in]     xml    Alert or recipient XML
216  	 * \param[in,out] entry  Alert entry to create environment variables for
217  	 */
218  	static void
219  	unpack_alert_parameters(const xmlNode *xml, pcmk__alert_t *entry)
220  	{
221  	    xmlNode *child;
222  	
223  	    if ((xml == NULL) || (entry == NULL)) {
224  	        return;
225  	    }
226  	
227  	    child = pcmk__xe_first_child(xml, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
228  	                                 NULL);
229  	    if (child == NULL) {
230  	        return;
231  	    }
232  	
233  	    if (entry->envvars == NULL) {
234  	        entry->envvars = pcmk__strkey_table(free, free);
235  	    }
236  	
237  	    for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
238  	         child != NULL; child = pcmk__xe_next(child, PCMK_XE_NVPAIR)) {
239  	
240  	        const char *name = pcmk__xe_get(child, PCMK_XA_NAME);
241  	        const char *value = pcmk__xe_get(child, PCMK_XA_VALUE);
242  	
243  	        if (value == NULL) {
244  	            value = "";
245  	        }
246  	        pcmk__insert_dup(entry->envvars, name, value);
247  	        pcmk__trace("Alert %s: added environment variable %s='%s'", entry->id,
248  	                    name, value);
249  	    }
250  	}
251  	
252  	/*!
253  	 * \internal
254  	 * \brief Create filters for an alert or alert recipient based on its
255  	 *        configuration in CIB XML
256  	 *
257  	 * \param[in]     xml    Alert or recipient XML
258  	 * \param[in,out] entry  Alert entry to create filters for
259  	 */
260  	static void
261  	unpack_alert_filter(xmlNode *xml, pcmk__alert_t *entry)
262  	{
263  	    xmlNode *select = pcmk__xe_first_child(xml, PCMK_XE_SELECT, NULL, NULL);
264  	    xmlNode *event_type = NULL;
265  	    uint32_t flags = pcmk__alert_none;
266  	
267  	    for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
268  	         event_type != NULL; event_type = pcmk__xe_next(event_type, NULL)) {
269  	
270  	        if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
271  	            flags |= pcmk__alert_fencing;
272  	
273  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
274  	            flags |= pcmk__alert_node;
275  	
276  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
277  	            flags |= pcmk__alert_resource;
278  	
279  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
280  	            xmlNode *attr;
281  	            const char *attr_name;
282  	            int nattrs = 0;
283  	
284  	            flags |= pcmk__alert_attribute;
285  	            for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
286  	                                             NULL, NULL);
287  	                 attr != NULL; attr = pcmk__xe_next(attr, PCMK_XE_ATTRIBUTE)) {
288  	
289  	                attr_name = pcmk__xe_get(attr, PCMK_XA_NAME);
290  	                if (attr_name) {
291  	                    if (nattrs == 0) {
292  	                        g_clear_pointer(&entry->select_attribute_name,
293  	                                        g_strfreev);
294  	                    }
295  	                    ++nattrs;
296  	                    entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
297  	                                                                 (nattrs + 1) * sizeof(char*));
298  	                    entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
299  	                    entry->select_attribute_name[nattrs] = NULL;
300  	                }
301  	            }
302  	        }
303  	    }
304  	
305  	    if (flags != pcmk__alert_none) {
306  	        const bool attribute = pcmk__is_set(flags, pcmk__alert_attribute);
307  	        const bool fencing = pcmk__is_set(flags, pcmk__alert_fencing);
308  	        const bool node = pcmk__is_set(flags, pcmk__alert_node);
309  	        const bool resource = pcmk__is_set(flags, pcmk__alert_resource);
310  	        const char *which_attrs = "none";
311  	
312  	        if (attribute) {
313  	            if (entry->select_attribute_name != NULL) {
314  	                which_attrs = "some";
315  	            } else {
316  	                which_attrs = "all";
317  	            }
318  	        }
319  	
320  	        entry->flags = flags;
321  	        pcmk__debug("Alert %s receives events: attributes:%s%s%s%s", entry->id,
322  	                    which_attrs, (fencing? " fencing" : ""),
323  	                    (node? " nodes" : ""), (resource? " resources" : ""));
324  	    }
325  	}
326  	
327  	/*!
328  	 * \internal
329  	 * \brief Unpack an alert or an alert recipient
330  	 *
331  	 * \param[in,out] alert        Alert or recipient XML
332  	 * \param[in,out] entry        Where to store unpacked values
333  	 * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
334  	 *
335  	 * \return Standard Pacemaker return code
336  	 */
337  	static int
338  	unpack_alert(xmlNode *alert, pcmk__alert_t *entry, unsigned int *max_timeout)
339  	{
340  	    int rc = pcmk_rc_ok;
341  	
342  	    unpack_alert_parameters(alert, entry);
343  	    rc = unpack_alert_options(alert, entry, max_timeout);
344  	    if (rc == pcmk_rc_ok) {
345  	        unpack_alert_filter(alert, entry);
346  	    }
347  	    return rc;
348  	}
349  	
350  	/*!
351  	 * \internal
352  	 * \brief Unpack a CIB alerts section into a list of alert entries
353  	 *
354  	 * \param[in] alerts  XML of CIB alerts section
355  	 *
356  	 * \return List of unpacked alert entries
357  	 */
358  	GList *
359  	pcmk__unpack_alerts(const xmlNode *alerts)
360  	{
361  	    xmlNode *alert;
362  	    pcmk__alert_t *entry;
363  	    unsigned int max_timeout = 0;
364  	    GList *alert_list = NULL;
365  	
(1) Event path: Condition "alert != NULL", taking true branch.
366  	    for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
367  	         alert != NULL; alert = pcmk__xe_next(alert, PCMK_XE_ALERT)) {
368  	
369  	        xmlNode *recipient = NULL;
370  	        int recipients = 0;
371  	        const char *alert_id = pcmk__xe_id(alert);
372  	        const char *alert_path = pcmk__xe_get(alert, PCMK_XA_PATH);
373  	
374  	        // Not possible with schema validation enabled
(2) Event path: Condition "alert_id == NULL", taking false branch.
375  	        if (alert_id == NULL) {
376  	            pcmk__config_err("Ignoring invalid alert without " PCMK_XA_ID);
377  	            continue;
378  	        }
(3) Event path: Condition "alert_path == NULL", taking false branch.
379  	        if (alert_path == NULL) {
380  	            pcmk__config_err("Ignoring invalid alert %s without " PCMK_XA_PATH,
381  	                             alert_id);
382  	            continue;
383  	        }
384  	
385  	        entry = pcmk__alert_new(alert_id, alert_path);
386  	
(4) Event path: Condition "unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok", taking false branch.
387  	        if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
388  	            // Don't allow recipients to override if entire alert is disabled
389  	            pcmk__debug("Alert %s is disabled", entry->id);
390  	            pcmk__free_alert(entry);
391  	            continue;
392  	        }
393  	
(5) Event path: Condition "entry->tstamp_format == NULL", taking false branch.
394  	        if (entry->tstamp_format == NULL) {
395  	            entry->tstamp_format =
396  	                pcmk__str_copy(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
397  	        }
398  	
(6) Event path: Switch case default.
(7) Event path: Condition "trace_cs == NULL", taking true branch.
(8) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(9) Event path: Breaking from switch.
399  	        pcmk__debug("Alert %s: path=%s timeout=%s tstamp-format='%s'",
400  	                    entry->id, entry->path,
401  	                    pcmk__readable_interval(entry->timeout),
402  	                    entry->tstamp_format);
403  	
(10) Event path: Condition "recipient != NULL", taking true branch.
404  	        for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
405  	                                              NULL);
406  	             recipient != NULL;
407  	             recipient = pcmk__xe_next(recipient, PCMK_XE_RECIPIENT)) {
408  	
(11) Event alloc_arg: "pcmk__dup_alert" allocates memory that is stored into "pcmk__dup_alert(entry)->recipient". [details]
(12) Event var_assign: Assigning: "recipient_entry->recipient" = "pcmk__dup_alert(entry)->recipient".
Also see events: [overwrite_var]
409  	            pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
410  	            unsigned int n_envvars = 0;
411  	
412  	            recipients++;
CID (unavailable; MK=be89d0e3920821991e8c99c5f071f003) (#1 of 1): Resource leak (RESOURCE_LEAK):
(13) Event overwrite_var: Overwriting "recipient_entry->recipient" in "recipient_entry->recipient = pcmk__xe_get_copy(recipient, "value")" leaks the storage that "recipient_entry->recipient" points to.
Also see events: [alloc_arg][var_assign]
413  	            recipient_entry->recipient = pcmk__xe_get_copy(recipient,
414  	                                                           PCMK_XA_VALUE);
415  	
416  	            if (unpack_alert(recipient, recipient_entry,
417  	                             &max_timeout) != pcmk_rc_ok) {
418  	                pcmk__debug("Alert %s: recipient %s is disabled", entry->id,
419  	                            recipient_entry->id);
420  	                pcmk__free_alert(recipient_entry);
421  	                continue;
422  	            }
423  	            alert_list = g_list_prepend(alert_list, recipient_entry);
424  	
425  	            if (recipient_entry->envvars != NULL) {
426  	                n_envvars = g_hash_table_size(recipient_entry->envvars);
427  	            }
428  	            pcmk__debug("Alert %s has recipient %s with value %s and %d "
429  	                        "envvars",
430  	                        entry->id, pcmk__xe_id(recipient),
431  	                        recipient_entry->recipient, n_envvars);
432  	        }
433  	
434  	        if (recipients == 0) {
435  	            alert_list = g_list_prepend(alert_list, entry);
436  	        } else { // Recipients were prepended individually above
437  	            pcmk__free_alert(entry);
438  	        }
439  	    }
440  	    return alert_list;
441  	}
442  	
443  	/*!
444  	 * \internal
445  	 * \brief Free an alert list generated by pcmk__unpack_alerts()
446  	 *
447  	 * \param[in,out] alert_list  Alert list to free
448  	 */
449  	void
450  	pcmk__free_alerts(GList *alert_list)
451  	{
452  	    g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
453  	}
454