1    	/*
2    	 * Copyright 2016-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 General Public License version 2
7    	 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	
14   	#include <glib.h>
15   	#include <libxml/tree.h>                // xmlNode
16   	
17   	#include <crm/crm.h>
18   	#include <crm/services.h>
19   	#include <crm/services_internal.h>
20   	#include <crm/common/ipc.h>
21   	#include <crm/common/xml.h>
22   	
23   	#include "pacemaker-execd.h"
24   	
25   	/* Track in-flight alerts so we can wait for them at shutdown */
26   	static GHashTable *inflight_alerts; /* key = call_id, value = timeout */
27   	static gboolean draining_alerts = FALSE;
28   	
29   	static inline void
30   	add_inflight_alert(int call_id, int timeout)
31   	{
32   	    if (inflight_alerts == NULL) {
33   	        inflight_alerts = pcmk__intkey_table(NULL);
34   	    }
35   	    pcmk__intkey_table_insert(inflight_alerts, call_id,
36   	                              GINT_TO_POINTER(timeout));
37   	}
38   	
39   	static inline void
40   	remove_inflight_alert(int call_id)
41   	{
42   	    if (inflight_alerts != NULL) {
43   	        pcmk__intkey_table_remove(inflight_alerts, call_id);
44   	    }
45   	}
46   	
47   	static int
48   	max_inflight_timeout(void)
49   	{
50   	    GHashTableIter iter;
51   	    gpointer timeout;
52   	    int max_timeout = 0;
53   	
54   	    if (inflight_alerts) {
55   	        g_hash_table_iter_init(&iter, inflight_alerts);
56   	        while (g_hash_table_iter_next(&iter, NULL, &timeout)) {
57   	            if (GPOINTER_TO_INT(timeout) > max_timeout) {
58   	                max_timeout = GPOINTER_TO_INT(timeout);
59   	            }
60   	        }
61   	    }
62   	    return max_timeout;
63   	}
64   	
65   	struct alert_cb_s {
66   	    char *client_id;
67   	    int call_id;
68   	};
69   	
70   	static void
71   	alert_complete(svc_action_t *action)
72   	{
73   	    struct alert_cb_s *cb_data = (struct alert_cb_s *) (action->cb_data);
74   	
(1) Event path: Condition "!(cb_data != NULL)", taking false branch.
75   	    CRM_CHECK(cb_data != NULL, return);
76   	
77   	    remove_inflight_alert(cb_data->call_id);
78   	
(2) Event path: Condition "action->status != PCMK_EXEC_DONE", taking true branch.
79   	    if (action->status != PCMK_EXEC_DONE) {
80   	        const char *reason = services__exit_reason(action);
81   	
(3) Event path: Condition "reason != NULL", taking true branch.
(4) Event path: Condition "reason != NULL", taking true branch.
82   	        pcmk__notice("Could not send alert: %s%s%s%s " QB_XS " client=%s",
83   	                     pcmk_exec_status_str(action->status),
84   	                     (reason != NULL)? " (" : "", pcmk__s(reason, ""),
85   	                     (reason != NULL)? ")" : "", cb_data->client_id);
86   	
(5) Event path: Falling through to end of if statement.
87   	    } else if (action->rc != 0) {
88   	        pcmk__notice("Alert [%d] completed but exited with status %d "
89   	                     QB_XS " client=%s",
90   	                     action->pid, action->rc, cb_data->client_id);
91   	
92   	    } else {
93   	        pcmk__debug("Alert [%d] completed " QB_XS " client=%s", action->pid,
94   	                    cb_data->client_id);
95   	    }
96   	
97   	    free(cb_data->client_id);
CID (unavailable; MK=58a5caf6eb6cd81fb2ec4677f85e8f50) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(6) Event assign_union_field: The union field "in" of "_pp" is written.
(7) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
98   	    g_clear_pointer(&action->cb_data, free);
99   	}
100  	
101  	int
102  	execd_process_alert_exec(pcmk__client_t *client, xmlNode *request)
103  	{
104  	    static int alert_sequence_no = 0;
105  	
106  	    xmlNode *alert_xml = pcmk__xpath_find_one(request->doc,
107  	                                              "//" PCMK__XE_LRMD_ALERT,
108  	                                              LOG_ERR);
109  	    const char *alert_id = pcmk__xe_get(alert_xml, PCMK__XA_LRMD_ALERT_ID);
110  	    const char *alert_path = pcmk__xe_get(alert_xml, PCMK__XA_LRMD_ALERT_PATH);
111  	    svc_action_t *action = NULL;
112  	    int alert_timeout = 0;
113  	    int rc = pcmk_rc_ok;
114  	    GHashTable *params = NULL;
115  	    struct alert_cb_s *cb_data = NULL;
116  	
117  	    if ((alert_id == NULL) || (alert_path == NULL) ||
118  	        (client == NULL) || (client->id == NULL)) { /* hint static analyzer */
119  	        rc = EINVAL;
120  	        goto err;
121  	    }
122  	    if (draining_alerts) {
123  	        return pcmk_rc_ok;
124  	    }
125  	
126  	    pcmk__xe_get_int(alert_xml, PCMK__XA_LRMD_TIMEOUT, &alert_timeout);
127  	
128  	    pcmk__info("Executing alert %s for %s", alert_id, client->id);
129  	
130  	    params = xml2list(alert_xml);
131  	    pcmk__add_alert_key_int(params, PCMK__alert_key_node_sequence,
132  	                            ++alert_sequence_no);
133  	
134  	    cb_data = pcmk__assert_alloc(1, sizeof(struct alert_cb_s));
135  	
136  	    cb_data->client_id = pcmk__str_copy(client->id);
137  	
138  	    pcmk__xe_get_int(request, PCMK__XA_LRMD_CALLID, &(cb_data->call_id));
139  	
140  	    action = services_alert_create(alert_id, alert_path, alert_timeout, params,
141  	                                   alert_sequence_no, cb_data);
142  	    if (action->rc != PCMK_OCF_UNKNOWN) {
143  	        rc = E2BIG;
144  	        goto err;
145  	    }
146  	
147  	    rc = services_action_user(action, CRM_DAEMON_USER);
148  	    if (rc < 0) {
149  	        rc = pcmk_legacy2rc(rc);
150  	        goto err;
151  	    }
152  	
153  	    add_inflight_alert(cb_data->call_id, alert_timeout);
154  	    if (services_alert_async(action, alert_complete) == FALSE) {
155  	        services_action_free(action);
156  	    }
157  	    return pcmk_rc_ok;
158  	
159  	err:
160  	    if (cb_data) {
161  	        free(cb_data->client_id);
162  	        free(cb_data);
163  	    }
164  	    services_action_free(action);
165  	    return rc;
166  	}
167  	
168  	static bool
169  	drain_check(guint remaining_timeout_ms)
170  	{
171  	    if (inflight_alerts != NULL) {
172  	        guint count = g_hash_table_size(inflight_alerts);
173  	
174  	        if (count > 0) {
175  	            pcmk__trace("%d alerts pending (%.3fs timeout remaining)",
176  	                        count, (remaining_timeout_ms / 1000.0));
177  	            return TRUE;
178  	        }
179  	    }
180  	    return FALSE;
181  	}
182  	
183  	void
184  	lrmd_drain_alerts(GMainLoop *mloop)
185  	{
186  	    if (inflight_alerts != NULL) {
187  	        guint timer_ms = max_inflight_timeout() + 5000;
188  	
189  	        pcmk__trace("Draining in-flight alerts (timeout %.3fs)",
190  	                    (timer_ms / 1000.0));
191  	        draining_alerts = TRUE;
192  	        pcmk_drain_main_loop(mloop, timer_ms, drain_check);
193  	        g_clear_pointer(&inflight_alerts, g_hash_table_destroy);
194  	    }
195  	}
196