1 /*
2 * Copyright 2015-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
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 if (entry->envvars) {
75 g_hash_table_destroy(entry->envvars);
76 }
77 free(entry);
78 }
79 }
80
81 /*!
82 * \internal
83 * \brief Duplicate an alert entry
84 *
85 * \param[in] entry Alert entry to duplicate
86 *
87 * \return Duplicate of alert entry
88 */
89 pcmk__alert_t *
90 pcmk__dup_alert(const pcmk__alert_t *entry)
91 {
92 pcmk__alert_t *new_entry = pcmk__alert_new(entry->id, entry->path);
93
94 new_entry->timeout = entry->timeout;
95 new_entry->flags = entry->flags;
96 new_entry->envvars = pcmk__str_table_dup(entry->envvars);
97 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>, 98U, entry->recipient)". |
| Also see events: |
[return_alloc] |
98 new_entry->recipient = pcmk__str_copy(entry->recipient);
|
(3) Event path: |
Condition "entry->select_attribute_name", taking true branch. |
99 if (entry->select_attribute_name) {
100 new_entry->select_attribute_name = g_strdupv(entry->select_attribute_name);
101 }
|
(4) Event return_alloc: |
Returning "new_entry", where "new_entry->recipient" is allocated memory. |
| Also see events: |
[alloc_fn][assign] |
102 return new_entry;
103 }
104
105 void
106 pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
107 const char *value)
108 {
109 pcmk__assert((table != NULL) && (name >= 0)
110 && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
111 if (value == NULL) {
112 pcmk__trace("Removing alert key %s", pcmk__alert_keys[name]);
113 g_hash_table_remove(table, pcmk__alert_keys[name]);
114 } else {
115 pcmk__trace("Inserting alert key %s = '%s'", pcmk__alert_keys[name],
116 value);
117 pcmk__insert_dup(table, pcmk__alert_keys[name], value);
118 }
119 }
120
121 void
122 pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
123 int value)
124 {
125 pcmk__assert((table != NULL) && (name >= 0)
126 && (name < PCMK__ALERT_INTERNAL_KEY_MAX));
127 pcmk__trace("Inserting alert key %s = %d", pcmk__alert_keys[name], value);
128 g_hash_table_insert(table, pcmk__str_copy(pcmk__alert_keys[name]),
129 pcmk__itoa(value));
130 }
131
132 #define READABLE_DEFAULT pcmk__readable_interval(PCMK__ALERT_DEFAULT_TIMEOUT_MS)
133
134 /*!
135 * \internal
136 * \brief Unpack options for an alert or alert recipient from its
137 * meta-attributes in the CIB XML configuration
138 *
139 * \param[in,out] xml Alert or recipient XML
140 * \param[in,out] entry Where to store unpacked values
141 * \param[in,out] max_timeout Max timeout of all alerts and recipients thus far
142 *
143 * \return Standard Pacemaker return code
144 */
145 static int
146 unpack_alert_options(xmlNode *xml, pcmk__alert_t *entry, guint *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);
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_strfreev(entry->select_attribute_name);
293 entry->select_attribute_name = NULL;
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, guint *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 guint max_timeout = 0U;
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 guint 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 if (alert_list != NULL) {
453 g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert);
454 }
455 }
456