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