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, guint *max_timeout)
146  	{
147  	    GHashTable *config_hash = pcmk__strkey_table(free, free);
148  	    crm_time_t *now = crm_time_new(NULL);
149  	    const char *value = NULL;
150  	    int rc = pcmk_rc_ok;
151  	
152  	    pcmk_rule_input_t rule_input = {
153  	        .now = now,
154  	    };
155  	
156  	    pcmk__unpack_nvpair_blocks(xml, PCMK_XE_META_ATTRIBUTES, NULL, &rule_input,
157  	                               config_hash, NULL, xml->doc);
158  	    crm_time_free(now);
159  	
160  	    value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
161  	    if ((value != NULL) && !pcmk__is_true(value)) {
162  	        // No need to continue unpacking
163  	        rc = pcmk_rc_disabled;
164  	        goto done;
165  	    }
166  	
167  	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
168  	    if (value != NULL) {
169  	        long long timeout_ms = 0;
170  	
171  	        if ((pcmk__parse_ms(value, &timeout_ms) != pcmk_rc_ok)
172  	            || (timeout_ms <= 0)) {
173  	
174  	            entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
175  	
176  	            if (timeout_ms == 0) {
177  	                pcmk__trace("Alert %s uses default timeout (%s)", entry->id,
178  	                            READABLE_DEFAULT);
179  	            } else {
180  	                pcmk__config_warn("Using default timeout (%s) for alert %s "
181  	                                  "because '%s' is not a valid timeout",
182  	                                  entry->id, value, READABLE_DEFAULT);
183  	            }
184  	
185  	        } else {
186  	            entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
187  	            pcmk__trace("Alert %s uses timeout of %s", entry->id,
188  	                        pcmk__readable_interval(entry->timeout));
189  	        }
190  	        if (entry->timeout > *max_timeout) {
191  	            *max_timeout = entry->timeout;
192  	        }
193  	    }
194  	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
195  	    if (value != NULL) {
196  	        /* hard to do any checks here as merely anything can
197  	         * can be a valid time-format-string
198  	         */
199  	        entry->tstamp_format = strdup(value);
200  	        pcmk__trace("Alert %s uses timestamp format '%s'", entry->id,
201  	                    entry->tstamp_format);
202  	    }
203  	
204  	done:
205  	    g_hash_table_destroy(config_hash);
206  	    return rc;
207  	}
208  	
209  	/*!
210  	 * \internal
211  	 * \brief Unpack agent parameters for an alert or alert recipient into an
212  	 *        environment variable list based on its CIB XML configuration
213  	 *
214  	 * \param[in]     xml    Alert or recipient XML
215  	 * \param[in,out] entry  Alert entry to create environment variables for
216  	 */
217  	static void
218  	unpack_alert_parameters(const xmlNode *xml, pcmk__alert_t *entry)
219  	{
220  	    xmlNode *child;
221  	
222  	    if ((xml == NULL) || (entry == NULL)) {
223  	        return;
224  	    }
225  	
226  	    child = pcmk__xe_first_child(xml, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
227  	                                 NULL);
228  	    if (child == NULL) {
229  	        return;
230  	    }
231  	
232  	    if (entry->envvars == NULL) {
233  	        entry->envvars = pcmk__strkey_table(free, free);
234  	    }
235  	
236  	    for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
237  	         child != NULL; child = pcmk__xe_next(child, PCMK_XE_NVPAIR)) {
238  	
239  	        const char *name = pcmk__xe_get(child, PCMK_XA_NAME);
240  	        const char *value = pcmk__xe_get(child, PCMK_XA_VALUE);
241  	
242  	        if (value == NULL) {
243  	            value = "";
244  	        }
245  	        pcmk__insert_dup(entry->envvars, name, value);
246  	        pcmk__trace("Alert %s: added environment variable %s='%s'", entry->id,
247  	                    name, value);
248  	    }
249  	}
250  	
251  	/*!
252  	 * \internal
253  	 * \brief Create filters for an alert or alert recipient based on its
254  	 *        configuration in CIB XML
255  	 *
256  	 * \param[in]     xml    Alert or recipient XML
257  	 * \param[in,out] entry  Alert entry to create filters for
258  	 */
259  	static void
260  	unpack_alert_filter(xmlNode *xml, pcmk__alert_t *entry)
261  	{
262  	    xmlNode *select = pcmk__xe_first_child(xml, PCMK_XE_SELECT, NULL, NULL);
263  	    xmlNode *event_type = NULL;
264  	    uint32_t flags = pcmk__alert_none;
265  	
266  	    for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
267  	         event_type != NULL; event_type = pcmk__xe_next(event_type, NULL)) {
268  	
269  	        if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
270  	            flags |= pcmk__alert_fencing;
271  	
272  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
273  	            flags |= pcmk__alert_node;
274  	
275  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
276  	            flags |= pcmk__alert_resource;
277  	
278  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
279  	            xmlNode *attr;
280  	            const char *attr_name;
281  	            int nattrs = 0;
282  	
283  	            flags |= pcmk__alert_attribute;
284  	            for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
285  	                                             NULL, NULL);
286  	                 attr != NULL; attr = pcmk__xe_next(attr, PCMK_XE_ATTRIBUTE)) {
287  	
288  	                attr_name = pcmk__xe_get(attr, PCMK_XA_NAME);
289  	                if (attr_name) {
290  	                    if (nattrs == 0) {
291  	                        g_clear_pointer(&entry->select_attribute_name,
292  	                                        g_strfreev);
293  	                    }
294  	                    ++nattrs;
295  	                    entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
296  	                                                                 (nattrs + 1) * sizeof(char*));
297  	                    entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
298  	                    entry->select_attribute_name[nattrs] = NULL;
299  	                }
300  	            }
301  	        }
302  	    }
303  	
304  	    if (flags != pcmk__alert_none) {
305  	        const bool attribute = pcmk__is_set(flags, pcmk__alert_attribute);
306  	        const bool fencing = pcmk__is_set(flags, pcmk__alert_fencing);
307  	        const bool node = pcmk__is_set(flags, pcmk__alert_node);
308  	        const bool resource = pcmk__is_set(flags, pcmk__alert_resource);
309  	        const char *which_attrs = "none";
310  	
311  	        if (attribute) {
312  	            if (entry->select_attribute_name != NULL) {
313  	                which_attrs = "some";
314  	            } else {
315  	                which_attrs = "all";
316  	            }
317  	        }
318  	
319  	        entry->flags = flags;
320  	        pcmk__debug("Alert %s receives events: attributes:%s%s%s%s", entry->id,
321  	                    which_attrs, (fencing? " fencing" : ""),
322  	                    (node? " nodes" : ""), (resource? " resources" : ""));
323  	    }
324  	}
325  	
326  	/*!
327  	 * \internal
328  	 * \brief Unpack an alert or an alert recipient
329  	 *
330  	 * \param[in,out] alert        Alert or recipient XML
331  	 * \param[in,out] entry        Where to store unpacked values
332  	 * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
333  	 *
334  	 * \return Standard Pacemaker return code
335  	 */
336  	static int
337  	unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
338  	{
339  	    int rc = pcmk_rc_ok;
340  	
341  	    unpack_alert_parameters(alert, entry);
342  	    rc = unpack_alert_options(alert, entry, max_timeout);
343  	    if (rc == pcmk_rc_ok) {
344  	        unpack_alert_filter(alert, entry);
345  	    }
346  	    return rc;
347  	}
348  	
349  	/*!
350  	 * \internal
351  	 * \brief Unpack a CIB alerts section into a list of alert entries
352  	 *
353  	 * \param[in] alerts  XML of CIB alerts section
354  	 *
355  	 * \return List of unpacked alert entries
356  	 */
357  	GList *
358  	pcmk__unpack_alerts(const xmlNode *alerts)
359  	{
360  	    xmlNode *alert;
361  	    pcmk__alert_t *entry;
362  	    guint max_timeout = 0U;
363  	    GList *alert_list = NULL;
364  	
(1) Event path: Condition "alert != NULL", taking true branch.
365  	    for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
366  	         alert != NULL; alert = pcmk__xe_next(alert, PCMK_XE_ALERT)) {
367  	
368  	        xmlNode *recipient = NULL;
369  	        int recipients = 0;
370  	        const char *alert_id = pcmk__xe_id(alert);
371  	        const char *alert_path = pcmk__xe_get(alert, PCMK_XA_PATH);
372  	
373  	        // Not possible with schema validation enabled
(2) Event path: Condition "alert_id == NULL", taking false branch.
374  	        if (alert_id == NULL) {
375  	            pcmk__config_err("Ignoring invalid alert without " PCMK_XA_ID);
376  	            continue;
377  	        }
(3) Event path: Condition "alert_path == NULL", taking false branch.
378  	        if (alert_path == NULL) {
379  	            pcmk__config_err("Ignoring invalid alert %s without " PCMK_XA_PATH,
380  	                             alert_id);
381  	            continue;
382  	        }
383  	
384  	        entry = pcmk__alert_new(alert_id, alert_path);
385  	
(4) Event path: Condition "unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok", taking false branch.
386  	        if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
387  	            // Don't allow recipients to override if entire alert is disabled
388  	            pcmk__debug("Alert %s is disabled", entry->id);
389  	            pcmk__free_alert(entry);
390  	            continue;
391  	        }
392  	
(5) Event path: Condition "entry->tstamp_format == NULL", taking false branch.
393  	        if (entry->tstamp_format == NULL) {
394  	            entry->tstamp_format =
395  	                pcmk__str_copy(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
396  	        }
397  	
(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.
398  	        pcmk__debug("Alert %s: path=%s timeout=%s tstamp-format='%s'",
399  	                    entry->id, entry->path,
400  	                    pcmk__readable_interval(entry->timeout),
401  	                    entry->tstamp_format);
402  	
(10) Event path: Condition "recipient != NULL", taking true branch.
403  	        for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
404  	                                              NULL);
405  	             recipient != NULL;
406  	             recipient = pcmk__xe_next(recipient, PCMK_XE_RECIPIENT)) {
407  	
(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]
408  	            pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
409  	            guint n_envvars = 0;
410  	
411  	            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]
412  	            recipient_entry->recipient = pcmk__xe_get_copy(recipient,
413  	                                                           PCMK_XA_VALUE);
414  	
415  	            if (unpack_alert(recipient, recipient_entry,
416  	                             &max_timeout) != pcmk_rc_ok) {
417  	                pcmk__debug("Alert %s: recipient %s is disabled", entry->id,
418  	                            recipient_entry->id);
419  	                pcmk__free_alert(recipient_entry);
420  	                continue;
421  	            }
422  	            alert_list = g_list_prepend(alert_list, recipient_entry);
423  	
424  	            if (recipient_entry->envvars != NULL) {
425  	                n_envvars = g_hash_table_size(recipient_entry->envvars);
426  	            }
427  	            pcmk__debug("Alert %s has recipient %s with value %s and %d "
428  	                        "envvars",
429  	                        entry->id, pcmk__xe_id(recipient),
430  	                        recipient_entry->recipient, n_envvars);
431  	        }
432  	
433  	        if (recipients == 0) {
434  	            alert_list = g_list_prepend(alert_list, entry);
435  	        } else { // Recipients were prepended individually above
436  	            pcmk__free_alert(entry);
437  	        }
438  	    }
439  	    return alert_list;
440  	}
441  	
442  	/*!
443  	 * \internal
444  	 * \brief Free an alert list generated by pcmk__unpack_alerts()
445  	 *
446  	 * \param[in,out] alert_list  Alert list to free
447  	 */
448  	void
449  	pcmk__free_alerts(GList *alert_list)
450  	{
451  	    g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
452  	}
453