/src/pacemaker/lib/common/ipc_pacemakerd.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2020-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 | | #include <stdlib.h> |
14 | | #include <time.h> |
15 | | |
16 | | #include <crm/crm.h> |
17 | | #include <crm/common/xml.h> |
18 | | #include <crm/common/ipc.h> |
19 | | #include <crm/common/ipc_internal.h> |
20 | | #include <crm/common/ipc_pacemakerd.h> |
21 | | #include "crmcommon_private.h" |
22 | | |
23 | | typedef struct pacemakerd_api_private_s { |
24 | | enum pcmk_pacemakerd_state state; |
25 | | char *client_uuid; |
26 | | } pacemakerd_api_private_t; |
27 | | |
28 | | static const char *pacemakerd_state_str[] = { |
29 | | PCMK__VALUE_INIT, |
30 | | PCMK__VALUE_STARTING_DAEMONS, |
31 | | PCMK__VALUE_WAIT_FOR_PING, |
32 | | PCMK__VALUE_RUNNING, |
33 | | PCMK__VALUE_SHUTTING_DOWN, |
34 | | PCMK__VALUE_SHUTDOWN_COMPLETE, |
35 | | PCMK_VALUE_REMOTE, |
36 | | }; |
37 | | |
38 | | enum pcmk_pacemakerd_state |
39 | | pcmk_pacemakerd_api_daemon_state_text2enum(const char *state) |
40 | 0 | { |
41 | 0 | int i; |
42 | |
|
43 | 0 | if (state == NULL) { |
44 | 0 | return pcmk_pacemakerd_state_invalid; |
45 | 0 | } |
46 | 0 | for (i=pcmk_pacemakerd_state_init; i <= pcmk_pacemakerd_state_max; |
47 | 0 | i++) { |
48 | 0 | if (pcmk__str_eq(state, pacemakerd_state_str[i], pcmk__str_none)) { |
49 | 0 | return i; |
50 | 0 | } |
51 | 0 | } |
52 | 0 | return pcmk_pacemakerd_state_invalid; |
53 | 0 | } |
54 | | |
55 | | const char * |
56 | | pcmk_pacemakerd_api_daemon_state_enum2text( |
57 | | enum pcmk_pacemakerd_state state) |
58 | 0 | { |
59 | 0 | if ((state >= pcmk_pacemakerd_state_init) && |
60 | 0 | (state <= pcmk_pacemakerd_state_max)) { |
61 | 0 | return pacemakerd_state_str[state]; |
62 | 0 | } |
63 | 0 | return "invalid"; |
64 | 0 | } |
65 | | |
66 | | /*! |
67 | | * \internal |
68 | | * \brief Return a friendly string representation of a \p pacemakerd state |
69 | | * |
70 | | * \param[in] state \p pacemakerd state |
71 | | * |
72 | | * \return A user-friendly string representation of \p state, or |
73 | | * <tt>"Invalid pacemakerd state"</tt> |
74 | | */ |
75 | | const char * |
76 | | pcmk__pcmkd_state_enum2friendly(enum pcmk_pacemakerd_state state) |
77 | 0 | { |
78 | 0 | switch (state) { |
79 | 0 | case pcmk_pacemakerd_state_init: |
80 | 0 | return "Initializing pacemaker"; |
81 | 0 | case pcmk_pacemakerd_state_starting_daemons: |
82 | 0 | return "Pacemaker daemons are starting"; |
83 | 0 | case pcmk_pacemakerd_state_wait_for_ping: |
84 | 0 | return "Waiting for startup trigger from SBD"; |
85 | 0 | case pcmk_pacemakerd_state_running: |
86 | 0 | return "Pacemaker is running"; |
87 | 0 | case pcmk_pacemakerd_state_shutting_down: |
88 | 0 | return "Pacemaker daemons are shutting down"; |
89 | 0 | case pcmk_pacemakerd_state_shutdown_complete: |
90 | | /* Assuming pacemakerd won't process messages while in |
91 | | * shutdown_complete state unless reporting to SBD |
92 | | */ |
93 | 0 | return "Pacemaker daemons are shut down (reporting to SBD)"; |
94 | 0 | case pcmk_pacemakerd_state_remote: |
95 | 0 | return PCMK__SERVER_REMOTED " is running " |
96 | 0 | "(on a Pacemaker Remote node)"; |
97 | 0 | default: |
98 | 0 | return "Invalid pacemakerd state"; |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | /*! |
103 | | * \internal |
104 | | * \brief Get a string representation of a \p pacemakerd API reply type |
105 | | * |
106 | | * \param[in] reply \p pacemakerd API reply type |
107 | | * |
108 | | * \return String representation of a \p pacemakerd API reply type |
109 | | */ |
110 | | const char * |
111 | | pcmk__pcmkd_api_reply2str(enum pcmk_pacemakerd_api_reply reply) |
112 | 0 | { |
113 | 0 | switch (reply) { |
114 | 0 | case pcmk_pacemakerd_reply_ping: |
115 | 0 | return "ping"; |
116 | 0 | case pcmk_pacemakerd_reply_shutdown: |
117 | 0 | return "shutdown"; |
118 | 0 | default: |
119 | 0 | return "unknown"; |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | | // \return Standard Pacemaker return code |
124 | | static int |
125 | | new_data(pcmk_ipc_api_t *api) |
126 | 0 | { |
127 | 0 | struct pacemakerd_api_private_s *private = NULL; |
128 | |
|
129 | 0 | api->api_data = calloc(1, sizeof(struct pacemakerd_api_private_s)); |
130 | |
|
131 | 0 | if (api->api_data == NULL) { |
132 | 0 | return errno; |
133 | 0 | } |
134 | | |
135 | 0 | private = api->api_data; |
136 | 0 | private->state = pcmk_pacemakerd_state_invalid; |
137 | | /* other as with cib, controld, ... we are addressing pacemakerd just |
138 | | from the local node -> pid is unique and thus sufficient as an ID |
139 | | */ |
140 | 0 | private->client_uuid = pcmk__getpid_s(); |
141 | |
|
142 | 0 | return pcmk_rc_ok; |
143 | 0 | } |
144 | | |
145 | | static void |
146 | | free_data(void *data) |
147 | 0 | { |
148 | 0 | free(((struct pacemakerd_api_private_s *) data)->client_uuid); |
149 | 0 | free(data); |
150 | 0 | } |
151 | | |
152 | | // \return Standard Pacemaker return code |
153 | | static int |
154 | | post_connect(pcmk_ipc_api_t *api) |
155 | 0 | { |
156 | 0 | struct pacemakerd_api_private_s *private = NULL; |
157 | |
|
158 | 0 | if (api->api_data == NULL) { |
159 | 0 | return EINVAL; |
160 | 0 | } |
161 | 0 | private = api->api_data; |
162 | 0 | private->state = pcmk_pacemakerd_state_invalid; |
163 | |
|
164 | 0 | return pcmk_rc_ok; |
165 | 0 | } |
166 | | |
167 | | static void |
168 | | post_disconnect(pcmk_ipc_api_t *api) |
169 | 0 | { |
170 | 0 | struct pacemakerd_api_private_s *private = NULL; |
171 | |
|
172 | 0 | if (api->api_data == NULL) { |
173 | 0 | return; |
174 | 0 | } |
175 | 0 | private = api->api_data; |
176 | 0 | private->state = pcmk_pacemakerd_state_invalid; |
177 | |
|
178 | 0 | return; |
179 | 0 | } |
180 | | |
181 | | static bool |
182 | | reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) |
183 | 0 | { |
184 | 0 | const char *command = pcmk__xe_get(request, PCMK__XA_CRM_TASK); |
185 | |
|
186 | 0 | if (command == NULL) { |
187 | 0 | return false; |
188 | 0 | } |
189 | | |
190 | | // We only need to handle commands that functions in this file can send |
191 | 0 | return pcmk__str_any_of(command, CRM_OP_PING, CRM_OP_QUIT, NULL); |
192 | 0 | } |
193 | | |
194 | | static bool |
195 | | dispatch(pcmk_ipc_api_t *api, xmlNode *reply) |
196 | 0 | { |
197 | 0 | crm_exit_t status = CRM_EX_OK; |
198 | 0 | xmlNode *wrapper = NULL; |
199 | 0 | xmlNode *msg_data = NULL; |
200 | 0 | pcmk_pacemakerd_api_reply_t reply_data = { |
201 | 0 | pcmk_pacemakerd_reply_unknown |
202 | 0 | }; |
203 | 0 | const char *value = NULL; |
204 | |
|
205 | 0 | if (pcmk__xe_is(reply, PCMK__XE_ACK)) { |
206 | 0 | long long int ack_status = 0; |
207 | 0 | const char *status = pcmk__xe_get(reply, PCMK_XA_STATUS); |
208 | 0 | int rc = pcmk__scan_ll(status, &ack_status, CRM_EX_OK); |
209 | |
|
210 | 0 | if (rc != pcmk_rc_ok) { |
211 | 0 | pcmk__warn("Ack reply from %s has invalid " PCMK_XA_STATUS " '%s' " |
212 | 0 | "(bug?)", |
213 | 0 | pcmk_ipc_name(api, true), pcmk__s(status, "")); |
214 | 0 | } |
215 | 0 | return ack_status == CRM_EX_INDETERMINATE; |
216 | 0 | } |
217 | | |
218 | 0 | value = pcmk__xe_get(reply, PCMK__XA_T); |
219 | 0 | if (pcmk__parse_server(value) != pcmk_ipc_pacemakerd) { |
220 | | /* @COMPAT pacemakerd <3.0.0 sets PCMK__VALUE_CRMD as the message type, |
221 | | * so we can't enforce this check until we no longer support |
222 | | * Pacemaker Remote nodes connecting to cluster nodes older than that. |
223 | | */ |
224 | 0 | pcmk__trace("Message from %s has unexpected message type '%s' (bug if " |
225 | 0 | "not from pacemakerd <3.0.0)", |
226 | 0 | pcmk_ipc_name(api, true), pcmk__s(value, "")); |
227 | 0 | } |
228 | | |
229 | 0 | value = pcmk__xe_get(reply, PCMK__XA_SUBT); |
230 | 0 | if (!pcmk__str_eq(value, PCMK__VALUE_RESPONSE, pcmk__str_none)) { |
231 | 0 | pcmk__info("Unrecognizable message from %s: message type '%s' not " |
232 | 0 | "'" PCMK__VALUE_RESPONSE "'", |
233 | 0 | pcmk_ipc_name(api, true), pcmk__s(value, "")); |
234 | 0 | status = CRM_EX_PROTOCOL; |
235 | 0 | goto done; |
236 | 0 | } |
237 | | |
238 | 0 | if (pcmk__str_empty(pcmk__xe_get(reply, PCMK_XA_REFERENCE))) { |
239 | 0 | pcmk__info("Unrecognizable message from %s: no reference", |
240 | 0 | pcmk_ipc_name(api, true)); |
241 | 0 | status = CRM_EX_PROTOCOL; |
242 | 0 | goto done; |
243 | 0 | } |
244 | | |
245 | 0 | value = pcmk__xe_get(reply, PCMK__XA_CRM_TASK); |
246 | | |
247 | | // Parse useful info from reply |
248 | 0 | wrapper = pcmk__xe_first_child(reply, PCMK__XE_CRM_XML, NULL, NULL); |
249 | 0 | msg_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); |
250 | |
|
251 | 0 | if (pcmk__str_eq(value, CRM_OP_PING, pcmk__str_none)) { |
252 | 0 | reply_data.reply_type = pcmk_pacemakerd_reply_ping; |
253 | 0 | reply_data.data.ping.state = |
254 | 0 | pcmk_pacemakerd_api_daemon_state_text2enum( |
255 | 0 | pcmk__xe_get(msg_data, PCMK__XA_PACEMAKERD_STATE)); |
256 | 0 | reply_data.data.ping.status = |
257 | 0 | pcmk__str_eq(pcmk__xe_get(msg_data, PCMK_XA_RESULT), "ok", |
258 | 0 | pcmk__str_casei)?pcmk_rc_ok:pcmk_rc_error; |
259 | |
|
260 | 0 | pcmk__xe_get_time(msg_data, PCMK__XA_CRM_TIMESTAMP, |
261 | 0 | &reply_data.data.ping.last_good); |
262 | 0 | if (reply_data.data.ping.last_good < 0) { |
263 | 0 | reply_data.data.ping.last_good = 0; |
264 | 0 | } |
265 | |
|
266 | 0 | reply_data.data.ping.sys_from = |
267 | 0 | pcmk__xe_get(msg_data, PCMK__XA_CRM_SUBSYSTEM); |
268 | 0 | } else if (pcmk__str_eq(value, CRM_OP_QUIT, pcmk__str_none)) { |
269 | 0 | const char *op_status = pcmk__xe_get(msg_data, PCMK__XA_OP_STATUS); |
270 | |
|
271 | 0 | reply_data.reply_type = pcmk_pacemakerd_reply_shutdown; |
272 | 0 | reply_data.data.shutdown.status = atoi(op_status); |
273 | 0 | } else { |
274 | 0 | pcmk__info("Unrecognizable message from %s: unknown command '%s'", |
275 | 0 | pcmk_ipc_name(api, true), pcmk__s(value, "")); |
276 | 0 | status = CRM_EX_PROTOCOL; |
277 | 0 | goto done; |
278 | 0 | } |
279 | | |
280 | 0 | done: |
281 | 0 | pcmk__call_ipc_callback(api, pcmk_ipc_event_reply, status, &reply_data); |
282 | 0 | return false; |
283 | 0 | } |
284 | | |
285 | | pcmk__ipc_methods_t * |
286 | | pcmk__pacemakerd_api_methods(void) |
287 | 0 | { |
288 | 0 | pcmk__ipc_methods_t *cmds = calloc(1, sizeof(pcmk__ipc_methods_t)); |
289 | |
|
290 | 0 | if (cmds != NULL) { |
291 | 0 | cmds->new_data = new_data; |
292 | 0 | cmds->free_data = free_data; |
293 | 0 | cmds->post_connect = post_connect; |
294 | 0 | cmds->reply_expected = reply_expected; |
295 | 0 | cmds->dispatch = dispatch; |
296 | 0 | cmds->post_disconnect = post_disconnect; |
297 | 0 | } |
298 | 0 | return cmds; |
299 | 0 | } |
300 | | |
301 | | static int |
302 | | do_pacemakerd_api_call(pcmk_ipc_api_t *api, const char *ipc_name, const char *task) |
303 | 0 | { |
304 | 0 | pacemakerd_api_private_t *private; |
305 | 0 | char *sender_system = NULL; |
306 | 0 | xmlNode *cmd; |
307 | 0 | int rc; |
308 | |
|
309 | 0 | if (api == NULL) { |
310 | 0 | return EINVAL; |
311 | 0 | } |
312 | | |
313 | 0 | private = api->api_data; |
314 | 0 | pcmk__assert(private != NULL); |
315 | |
|
316 | 0 | sender_system = pcmk__assert_asprintf("%s_%s", private->client_uuid, |
317 | 0 | pcmk__ipc_sys_name(ipc_name, |
318 | 0 | "client")); |
319 | 0 | cmd = pcmk__new_request(pcmk_ipc_pacemakerd, sender_system, NULL, |
320 | 0 | CRM_SYSTEM_MCP, task, NULL); |
321 | 0 | free(sender_system); |
322 | |
|
323 | 0 | if (cmd) { |
324 | 0 | rc = pcmk__send_ipc_request(api, cmd); |
325 | 0 | if (rc != pcmk_rc_ok) { |
326 | 0 | pcmk__debug("Couldn't send request to %s: %s rc=%d", |
327 | 0 | pcmk_ipc_name(api, true), pcmk_rc_str(rc), rc); |
328 | 0 | } |
329 | 0 | pcmk__xml_free(cmd); |
330 | 0 | } else { |
331 | 0 | rc = ENOMSG; |
332 | 0 | } |
333 | | |
334 | 0 | return rc; |
335 | 0 | } |
336 | | |
337 | | int |
338 | | pcmk_pacemakerd_api_ping(pcmk_ipc_api_t *api, const char *ipc_name) |
339 | 0 | { |
340 | 0 | return do_pacemakerd_api_call(api, ipc_name, CRM_OP_PING); |
341 | 0 | } |
342 | | |
343 | | int |
344 | | pcmk_pacemakerd_api_shutdown(pcmk_ipc_api_t *api, const char *ipc_name) |
345 | 0 | { |
346 | 0 | return do_pacemakerd_api_call(api, ipc_name, CRM_OP_QUIT); |
347 | 0 | } |