1 /*
2 * Copyright 2009-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 <errno.h> // ECONNREFUSED, ENOMEM
13 #include <inttypes.h> // int32_t, uint32_t, PRIu32
14 #include <stdio.h> // NULL, size_t
15 #include <sys/types.h> // gid_t, uid_t
16
17 #include <libxml/tree.h> // xmlNode
18 #include <glib.h> // g_byte_array_free, TRUE
19 #include <qb/qbipcs.h> // for qb_ipcs_connection_t
20
21 #include "pacemaker-fenced.h" // fenced_get_local_node
22
23 #include <crm/common/ipc.h> // crm_ipc_flags, pcmk_ipc_fenced
24 #include <crm/common/results.h> // pcmk_rc_*, pcmk_rc_str
25 #include <crm/fencing/internal.h> // STONITH_OP_*
26 #include <crm/crm.h> // CRM_OP_RM_NODE_CACHE
27 #include <crm/stonith-ng.h> // stonith_call_options
28
29 static qb_ipcs_service_t *ipcs = NULL;
30
31 static void
32 handle_ipc_reply(pcmk__client_t *client, xmlNode *request)
33 {
34 const char *op = pcmk__xe_get(request, PCMK__XA_ST_OP);
35
36 if (pcmk__str_eq(op, STONITH_OP_QUERY, pcmk__str_none)) {
37 process_remote_stonith_query(request);
38
39 } else if (pcmk__str_any_of(op, STONITH_OP_NOTIFY, STONITH_OP_FENCE,
40 NULL)) {
41 fenced_process_fencing_reply(request);
42
43 } else {
44 pcmk__err("Ignoring unknown %s reply from client %s",
45 pcmk__s(op, "untyped"), pcmk__client_name(client));
46 pcmk__log_xml_warn(request, "UnknownOp");
47 return;
48 }
49
50 pcmk__debug("Processed %s reply from client %s", op,
51 pcmk__client_name(client));
52 }
53
54 /*!
55 * \internal
56 * \brief Accept a new client IPC connection
57 *
58 * \param[in,out] c New connection
59 * \param[in] uid Client user id
60 * \param[in] gid Client group id
61 *
62 * \return 0 on success, -errno otherwise
63 */
64 static int32_t
65 fenced_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
66 {
67 pcmk__trace("New client connection %p", c);
68 if (stonith_shutdown_flag) {
69 pcmk__info("Ignoring new connection from pid %d during shutdown",
70 pcmk__client_pid(c));
71 return -ECONNREFUSED;
72 }
73
74 if (pcmk__new_client(c, uid, gid) == NULL) {
75 return -ENOMEM;
76 }
77 return 0;
78 }
79
80 /*!
81 * \internal
82 * \brief Handle a message from an IPC connection
83 *
84 * \param[in,out] c Established IPC connection
85 * \param[in] data The message data read from the connection - this can be
86 * a complete IPC message or just a part of one if it's
87 * very large
88 * \param[in] size Unused
89 *
90 * \return 0 in all cases
91 */
92 static int32_t
93 fenced_ipc_dispatch(qb_ipcs_connection_t *c, void *data, size_t size)
94 {
95 uint32_t id = 0;
96 uint32_t flags = 0;
97 uint32_t call_options = st_opt_none;
98 xmlNode *msg = NULL;
99 pcmk__client_t *client = pcmk__find_client(c);
100 const char *op = NULL;
101 int rc = pcmk_rc_ok;
102
103 // Sanity-check, and parse XML from IPC data
104 CRM_CHECK(client != NULL, return 0);
105 if (data == NULL) {
106 pcmk__debug("No IPC data from PID %d", pcmk__client_pid(c));
107 return 0;
108 }
109
110 rc = pcmk__ipc_msg_append(&client->buffer, data);
111
112 if (rc == pcmk_rc_ipc_more) {
113 /* We haven't read the complete message yet, so just return. */
114 return 0;
115
116 } else if (rc == pcmk_rc_ok) {
117 /* We've read the complete message and there's already a header on
118 * the front. Pass it off for processing.
119 */
120 msg = pcmk__client_data2xml(client, &id, &flags);
121 g_byte_array_free(client->buffer, TRUE);
122 client->buffer = NULL;
123
124 } else {
125 /* Some sort of error occurred reassembling the message. All we can
126 * do is clean up, log an error and return.
127 */
128 pcmk__err("Error when reading IPC message: %s", pcmk_rc_str(rc));
129
130 if (client->buffer != NULL) {
131 g_byte_array_free(client->buffer, TRUE);
132 client->buffer = NULL;
133 }
134
135 return 0;
136 }
137
138 if (msg == NULL) {
139 pcmk__debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
140 pcmk__ipc_send_ack(client, id, flags, NULL, CRM_EX_PROTOCOL);
141 return 0;
142 }
143
144 op = pcmk__xe_get(msg, PCMK__XA_CRM_TASK);
145 if (pcmk__str_eq(op, CRM_OP_RM_NODE_CACHE, pcmk__str_casei)) {
146 pcmk__xe_set(msg, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
147 pcmk__xe_set(msg, PCMK__XA_ST_OP, op);
148 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTID, client->id);
149 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTNAME, pcmk__client_name(client));
150 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTNODE, fenced_get_local_node());
151
152 pcmk__cluster_send_message(NULL, pcmk_ipc_fenced, msg);
153 goto done;
154 }
155
156 if (client->name == NULL) {
157 const char *value = pcmk__xe_get(msg, PCMK__XA_ST_CLIENTNAME);
158
159 client->name = pcmk__assert_asprintf("%s.%u", pcmk__s(value, "unknown"),
160 client->pid);
161 }
162
163 rc = pcmk__xe_get_flags(msg, PCMK__XA_ST_CALLOPT, &call_options,
164 st_opt_none);
165 if (rc != pcmk_rc_ok) {
166 pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc));
167 }
168
169 pcmk__trace("Flags %#08" PRIx32 "/%#08x for command %" PRIu32
170 " from client %s",
171 flags, call_options, id, pcmk__client_name(client));
172
173 if (pcmk__is_set(call_options, st_opt_sync_call)) {
174 pcmk__assert(pcmk__is_set(flags, crm_ipc_client_response));
175 /* This means the client has two synchronous events in-flight */
176 CRM_LOG_ASSERT(client->request_id == 0);
177 /* Reply only to the last one */
178 client->request_id = id;
179 }
180
181 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTID, client->id);
182 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTNAME, pcmk__client_name(client));
183 pcmk__xe_set(msg, PCMK__XA_ST_CLIENTNODE, fenced_get_local_node());
184
185 if (pcmk__xpath_find_one(msg->doc, "//" PCMK__XE_ST_REPLY,
186 PCMK__LOG_NEVER) != NULL) {
187 handle_ipc_reply(client, msg);
188
189 } else {
190 pcmk__request_t request = {
191 .ipc_client = client,
192 .ipc_id = id,
193 .ipc_flags = flags,
194 .peer = NULL,
195 .xml = msg,
196 .call_options = call_options,
197 .result = PCMK__UNKNOWN_RESULT,
198 };
199
200 request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_ST_OP);
201 CRM_CHECK(request.op != NULL, goto done);
202
203 if (pcmk__is_set(request.call_options, st_opt_sync_call)) {
204 pcmk__set_request_flags(&request, pcmk__request_sync);
205 }
206
207 fenced_handle_request(&request);
208 }
209
210 done:
211 pcmk__xml_free(msg);
212 return 0;
213 }
214
215 /*!
216 * \internal
217 * \brief Destroy a client IPC connection
218 *
219 * \param[in] c Connection to destroy
220 *
221 * \return 0 (i.e. do not re-run this callback)
222 */
223 static int32_t
224 fenced_ipc_closed(qb_ipcs_connection_t *c)
225 {
226 pcmk__client_t *client = pcmk__find_client(c);
227
228 if (client == NULL) {
229 pcmk__trace("Ignoring request to clean up unknown connection %p", c);
230 } else {
231 pcmk__trace("Cleaning up closed client connection %p", c);
232 pcmk__free_client(client);
233 }
234
235 return 0;
236 }
237
238 /*!
239 * \internal
240 * \brief Destroy a client IPC connection
241 *
242 * \param[in] c Connection to destroy
243 *
244 * \note We handle a destroyed connection the same as a closed one,
245 * but we need a separate handler because the return type is different.
246 */
247 static void
248 fenced_ipc_destroy(qb_ipcs_connection_t *c)
249 {
250 pcmk__trace("Destroying client connection %p", c);
251 fenced_ipc_closed(c);
252 }
253
254 static struct qb_ipcs_service_handlers ipc_callbacks = {
255 .connection_accept = fenced_ipc_accept,
256 .connection_created = NULL,
257 .msg_process = fenced_ipc_dispatch,
258 .connection_closed = fenced_ipc_closed,
259 .connection_destroyed = fenced_ipc_destroy
260 };
261
262 /*!
263 * \internal
264 * \brief Clean up fenced IPC communication
265 */
266 void
267 fenced_ipc_cleanup(void)
268 {
|
(1) Event path: |
Condition "ipcs != NULL", taking true branch. |
269 if (ipcs != NULL) {
270 pcmk__drop_all_clients(ipcs);
|
CID (unavailable; MK=1e0e556b7a5e2d9bcebbb6f6ce62deed) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(2) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(3) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
271 g_clear_pointer(&ipcs, qb_ipcs_destroy);
272 }
273
274 pcmk__client_cleanup();
275 }
276
277 /*!
278 * \internal
279 * \brief Set up fenced IPC communication
280 */
281 void
282 fenced_ipc_init(void)
283 {
284 pcmk__serve_fenced_ipc(&ipcs, &ipc_callbacks);
285 }
286