1    	/*
2    	 * Copyright 2015-2024 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   	#include <crm/crm.h>
12   	#include <crm/common/xml.h>
13   	#include <crm/pengine/rules.h>
14   	#include <crm/common/alerts_internal.h>
15   	#include <crm/common/xml_internal.h>
16   	#include <crm/pengine/rules_internal.h>
17   	
18   	/*!
19   	 * \internal
20   	 * \brief Unpack an alert's or alert recipient's meta attributes
21   	 *
22   	 * \param[in,out] basenode     Alert or recipient XML
23   	 * \param[in,out] entry        Where to store unpacked values
24   	 * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
25   	 *
26   	 * \return Standard Pacemaker return code
27   	 */
28   	static int
29   	get_meta_attrs_from_cib(xmlNode *basenode, pcmk__alert_t *entry,
30   	                        guint *max_timeout)
31   	{
32   	    GHashTable *config_hash = pcmk__strkey_table(free, free);
33   	    crm_time_t *now = crm_time_new(NULL);
34   	    const char *value = NULL;
35   	    int rc = pcmk_rc_ok;
36   	
37   	    pe_unpack_nvpairs(basenode, basenode, PCMK_XE_META_ATTRIBUTES, NULL,
38   	                      config_hash, NULL, FALSE, now, NULL);
39   	    crm_time_free(now);
40   	
41   	    value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED);
42   	    if ((value != NULL) && !crm_is_true(value)) {
43   	        // No need to continue unpacking
44   	        rc = pcmk_rc_disabled;
45   	        goto done;
46   	    }
47   	
48   	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMEOUT);
49   	    if (value) {
50   	        long long timeout_ms = crm_get_msec(value);
51   	
52   	        entry->timeout = (int) QB_MIN(timeout_ms, INT_MAX);
53   	        if (entry->timeout <= 0) {
54   	            if (entry->timeout == 0) {
55   	                crm_trace("Alert %s uses default timeout of %dmsec",
56   	                          entry->id, PCMK__ALERT_DEFAULT_TIMEOUT_MS);
57   	            } else {
58   	                pcmk__config_warn("Alert %s has invalid timeout value '%s', "
59   	                                  "using default (%d ms)",
60   	                                  entry->id, value,
61   	                                  PCMK__ALERT_DEFAULT_TIMEOUT_MS);
62   	            }
63   	            entry->timeout = PCMK__ALERT_DEFAULT_TIMEOUT_MS;
64   	        } else {
65   	            crm_trace("Alert %s uses timeout of %dmsec",
66   	                      entry->id, entry->timeout);
67   	        }
68   	        if (entry->timeout > *max_timeout) {
69   	            *max_timeout = entry->timeout;
70   	        }
71   	    }
72   	    value = g_hash_table_lookup(config_hash, PCMK_META_TIMESTAMP_FORMAT);
73   	    if (value) {
74   	        /* hard to do any checks here as merely anything can
75   	         * can be a valid time-format-string
76   	         */
77   	        entry->tstamp_format = strdup(value);
78   	        crm_trace("Alert %s uses timestamp format '%s'",
79   	                  entry->id, entry->tstamp_format);
80   	    }
81   	
82   	done:
83   	    g_hash_table_destroy(config_hash);
84   	    return rc;
85   	}
86   	
87   	static void
88   	get_envvars_from_cib(xmlNode *basenode, pcmk__alert_t *entry)
89   	{
90   	    xmlNode *child;
91   	
92   	    if ((basenode == NULL) || (entry == NULL)) {
93   	        return;
94   	    }
95   	
96   	    child = pcmk__xe_first_child(basenode, PCMK_XE_INSTANCE_ATTRIBUTES, NULL,
97   	                                 NULL);
98   	    if (child == NULL) {
99   	        return;
100  	    }
101  	
102  	    if (entry->envvars == NULL) {
103  	        entry->envvars = pcmk__strkey_table(free, free);
104  	    }
105  	
106  	    for (child = pcmk__xe_first_child(child, PCMK_XE_NVPAIR, NULL, NULL);
107  	         child != NULL; child = pcmk__xe_next_same(child)) {
108  	
109  	        const char *name = crm_element_value(child, PCMK_XA_NAME);
110  	        const char *value = crm_element_value(child, PCMK_XA_VALUE);
111  	
112  	        if (value == NULL) {
113  	            value = "";
114  	        }
115  	        pcmk__insert_dup(entry->envvars, name, value);
116  	        crm_trace("Alert %s: added environment variable %s='%s'",
117  	                  entry->id, name, value);
118  	    }
119  	}
120  	
121  	static void
122  	unpack_alert_filter(xmlNode *basenode, pcmk__alert_t *entry)
123  	{
124  	    xmlNode *select = pcmk__xe_first_child(basenode, PCMK_XE_SELECT, NULL,
125  	                                           NULL);
126  	    xmlNode *event_type = NULL;
127  	    uint32_t flags = pcmk__alert_none;
128  	
129  	    for (event_type = pcmk__xe_first_child(select, NULL, NULL, NULL);
130  	         event_type != NULL; event_type = pcmk__xe_next(event_type)) {
131  	
132  	        if (pcmk__xe_is(event_type, PCMK_XE_SELECT_FENCING)) {
133  	            flags |= pcmk__alert_fencing;
134  	
135  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_NODES)) {
136  	            flags |= pcmk__alert_node;
137  	
138  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_RESOURCES)) {
139  	            flags |= pcmk__alert_resource;
140  	
141  	        } else if (pcmk__xe_is(event_type, PCMK_XE_SELECT_ATTRIBUTES)) {
142  	            xmlNode *attr;
143  	            const char *attr_name;
144  	            int nattrs = 0;
145  	
146  	            flags |= pcmk__alert_attribute;
147  	            for (attr = pcmk__xe_first_child(event_type, PCMK_XE_ATTRIBUTE,
148  	                                             NULL, NULL);
149  	                 attr != NULL; attr = pcmk__xe_next_same(attr)) {
150  	
151  	                attr_name = crm_element_value(attr, PCMK_XA_NAME);
152  	                if (attr_name) {
153  	                    if (nattrs == 0) {
154  	                        g_strfreev(entry->select_attribute_name);
155  	                        entry->select_attribute_name = NULL;
156  	                    }
157  	                    ++nattrs;
158  	                    entry->select_attribute_name = pcmk__realloc(entry->select_attribute_name,
159  	                                                                 (nattrs + 1) * sizeof(char*));
160  	                    entry->select_attribute_name[nattrs - 1] = strdup(attr_name);
161  	                    entry->select_attribute_name[nattrs] = NULL;
162  	                }
163  	            }
164  	        }
165  	    }
166  	
167  	    if (flags != pcmk__alert_none) {
168  	        entry->flags = flags;
169  	        crm_debug("Alert %s receives events: attributes:%s%s%s%s",
170  	                  entry->id,
171  	                  (pcmk_is_set(flags, pcmk__alert_attribute)?
172  	                   (entry->select_attribute_name? "some" : "all") : "none"),
173  	                  (pcmk_is_set(flags, pcmk__alert_fencing)? " fencing" : ""),
174  	                  (pcmk_is_set(flags, pcmk__alert_node)? " nodes" : ""),
175  	                  (pcmk_is_set(flags, pcmk__alert_resource)? " resources" : ""));
176  	    }
177  	}
178  	
179  	/*!
180  	 * \internal
181  	 * \brief Unpack an alert or an alert recipient
182  	 *
183  	 * \param[in,out] alert        Alert or recipient XML
184  	 * \param[in,out] entry        Where to store unpacked values
185  	 * \param[in,out] max_timeout  Max timeout of all alerts and recipients thus far
186  	 *
187  	 * \return Standard Pacemaker return code
188  	 */
189  	static int
190  	unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout)
191  	{
192  	    int rc = pcmk_rc_ok;
193  	
194  	    get_envvars_from_cib(alert, entry);
195  	    rc = get_meta_attrs_from_cib(alert, entry, max_timeout);
196  	    if (rc == pcmk_rc_ok) {
197  	        unpack_alert_filter(alert, entry);
198  	    }
199  	    return rc;
200  	}
201  	
202  	/*!
203  	 * \internal
204  	 * \brief Unpack a CIB alerts section
205  	 *
206  	 * \param[in] alerts  XML of alerts section
207  	 *
208  	 * \return  List of unpacked alert entries
209  	 *
210  	 * \note Unlike most unpack functions, this is not used by the scheduler itself,
211  	 *       but is supplied for use by daemons that need to send alerts.
212  	 */
213  	GList *
214  	pe_unpack_alerts(const xmlNode *alerts)
215  	{
216  	    xmlNode *alert;
217  	    pcmk__alert_t *entry;
218  	    guint max_timeout = 0;
219  	    GList *alert_list = NULL;
220  	
(1) Event path: Condition "alerts == NULL", taking false branch.
221  	    if (alerts == NULL) {
222  	        return alert_list;
223  	    }
224  	
(2) Event path: Condition "alert != NULL", taking true branch.
225  	    for (alert = pcmk__xe_first_child(alerts, PCMK_XE_ALERT, NULL, NULL);
226  	         alert != NULL; alert = pcmk__xe_next_same(alert)) {
227  	
228  	        xmlNode *recipient;
229  	        int recipients = 0;
230  	        const char *alert_id = pcmk__xe_id(alert);
231  	        const char *alert_path = crm_element_value(alert, PCMK_XA_PATH);
232  	
233  	        /* The schema should enforce this, but to be safe ... */
(3) Event path: Condition "alert_id == NULL", taking false branch.
234  	        if (alert_id == NULL) {
235  	            pcmk__config_warn("Ignoring invalid alert without " PCMK_XA_ID);
236  	            crm_log_xml_info(alert, "missing-id");
237  	            continue;
238  	        }
(4) Event path: Condition "alert_path == NULL", taking false branch.
239  	        if (alert_path == NULL) {
240  	            pcmk__config_warn("Ignoring alert %s: No " PCMK_XA_PATH, alert_id);
241  	            continue;
242  	        }
243  	
244  	        entry = pcmk__alert_new(alert_id, alert_path);
245  	
(5) Event path: Condition "unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok", taking false branch.
246  	        if (unpack_alert(alert, entry, &max_timeout) != pcmk_rc_ok) {
247  	            // Don't allow recipients to override if entire alert is disabled
248  	            crm_debug("Alert %s is disabled", entry->id);
249  	            pcmk__free_alert(entry);
250  	            continue;
251  	        }
252  	
(6) Event path: Condition "entry->tstamp_format == NULL", taking false branch.
253  	        if (entry->tstamp_format == NULL) {
254  	            entry->tstamp_format = strdup(PCMK__ALERT_DEFAULT_TSTAMP_FORMAT);
255  	        }
256  	
(7) Event path: Switch case default.
(8) Event path: Condition "trace_cs == NULL", taking true branch.
(9) Event path: Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch.
(10) Event path: Breaking from switch.
257  	        crm_debug("Alert %s: path=%s timeout=%dms tstamp-format='%s' %u vars",
258  	                  entry->id, entry->path, entry->timeout, entry->tstamp_format,
259  	                  (entry->envvars? g_hash_table_size(entry->envvars) : 0));
260  	
(11) Event path: Condition "recipient != NULL", taking true branch.
261  	        for (recipient = pcmk__xe_first_child(alert, PCMK_XE_RECIPIENT, NULL,
262  	                                              NULL);
263  	             recipient != NULL; recipient = pcmk__xe_next_same(recipient)) {
264  	
(12) Event alloc_arg: "pcmk__dup_alert" allocates memory that is stored into "pcmk__dup_alert(entry)->recipient". [details]
(13) Event var_assign: Assigning: "recipient_entry->recipient" = "pcmk__dup_alert(entry)->recipient".
Also see events: [overwrite_var]
265  	            pcmk__alert_t *recipient_entry = pcmk__dup_alert(entry);
266  	
267  	            recipients++;
CID (unavailable; MK=572212c294aa86235787254f0bb0aa6c) (#1 of 1): Resource leak (RESOURCE_LEAK):
(14) Event overwrite_var: Overwriting "recipient_entry->recipient" in "recipient_entry->recipient = crm_element_value_copy(recipient, "value")" leaks the storage that "recipient_entry->recipient" points to.
Also see events: [alloc_arg][var_assign]
268  	            recipient_entry->recipient = crm_element_value_copy(recipient,
269  	                                                                PCMK_XA_VALUE);
270  	
271  	            if (unpack_alert(recipient, recipient_entry,
272  	                             &max_timeout) != pcmk_rc_ok) {
273  	                crm_debug("Alert %s: recipient %s is disabled",
274  	                          entry->id, recipient_entry->id);
275  	                pcmk__free_alert(recipient_entry);
276  	                continue;
277  	            }
278  	            alert_list = g_list_prepend(alert_list, recipient_entry);
279  	            crm_debug("Alert %s has recipient %s with value %s and %d envvars",
280  	                      entry->id, pcmk__xe_id(recipient),
281  	                      recipient_entry->recipient,
282  	                      (recipient_entry->envvars?
283  	                       g_hash_table_size(recipient_entry->envvars) : 0));
284  	        }
285  	
286  	        if (recipients == 0) {
287  	            alert_list = g_list_prepend(alert_list, entry);
288  	        } else {
289  	            pcmk__free_alert(entry);
290  	        }
291  	    }
292  	    return alert_list;
293  	}
294  	
295  	/*!
296  	 * \internal
297  	 * \brief Free an alert list generated by pe_unpack_alerts()
298  	 *
299  	 * \param[in,out] alert_list  Alert list to free
300  	 */
301  	void
302  	pe_free_alert_list(GList *alert_list)
303  	{
304  	    if (alert_list) {
305  	        g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
306  	    }
307  	}
308