/src/pacemaker/lib/common/messages.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-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 <stdio.h> |
13 | | #include <time.h> // time() |
14 | | #include <sys/types.h> |
15 | | |
16 | | #include <glib.h> |
17 | | #include <libxml/tree.h> |
18 | | |
19 | | #include <crm/common/xml.h> |
20 | | #include <crm/common/xml_internal.h> |
21 | | |
22 | | /*! |
23 | | * \internal |
24 | | * \brief Create message XML (for IPC or the cluster layer) |
25 | | * |
26 | | * Create standard, generic XML that can be used as a message sent via IPC or |
27 | | * the cluster layer. Currently, not all IPC and cluster layer messaging uses |
28 | | * this, but it should (eventually, keeping backward compatibility in mind). |
29 | | * |
30 | | * \param[in] origin Name of function that called this one (required) |
31 | | * \param[in] server Server whose protocol defines message semantics |
32 | | * \param[in] reply_to If NULL, create message as a request with a |
33 | | * generated message ID, otherwise create message |
34 | | * as a reply to this message ID |
35 | | * \param[in] sender_system Sender's subsystem (required; this is an |
36 | | * arbitrary string that may have meaning between |
37 | | * the sender and recipient) |
38 | | * \param[in] recipient_node If not NULL, add as message's recipient node |
39 | | * (NULL typically indicates a broadcast message) |
40 | | * \param[in] recipient_system If not NULL, add as message's recipient |
41 | | * subsystem (this is an arbitrary string that may |
42 | | * have meaning between the sender and recipient) |
43 | | * \param[in] task Add as message's task (required) |
44 | | * \param[in] data If not NULL, copy as message's data (callers |
45 | | * should not add attributes to the returned |
46 | | * message element, but instead pass any desired |
47 | | * information here, though this is not always |
48 | | * honored currently) |
49 | | * |
50 | | * \return Newly created message XML |
51 | | * |
52 | | * \note This function should usually not be called directly, but via the |
53 | | * pcmk__new_message() wrapper. |
54 | | * \note The caller is responsible for freeing the return value using |
55 | | * \c pcmk__xml_free(). |
56 | | */ |
57 | | xmlNode * |
58 | | pcmk__new_message_as(const char *origin, enum pcmk_ipc_server server, |
59 | | const char *reply_to, const char *sender_system, |
60 | | const char *recipient_node, const char *recipient_system, |
61 | | const char *task, xmlNode *data) |
62 | 0 | { |
63 | 0 | static unsigned int message_counter = 0U; |
64 | |
|
65 | 0 | xmlNode *message = NULL; |
66 | 0 | char *message_id = NULL; |
67 | 0 | const char *subtype = PCMK__VALUE_RESPONSE; |
68 | |
|
69 | 0 | CRM_CHECK(!pcmk__str_empty(origin) |
70 | 0 | && !pcmk__str_empty(sender_system) |
71 | 0 | && !pcmk__str_empty(task), |
72 | 0 | return NULL); |
73 | | |
74 | 0 | if (reply_to == NULL) { |
75 | 0 | subtype = PCMK__VALUE_REQUEST; |
76 | 0 | message_id = pcmk__assert_asprintf("%s-%s-%llu-%u", task, sender_system, |
77 | 0 | (unsigned long long) time(NULL), |
78 | 0 | message_counter++); |
79 | 0 | reply_to = message_id; |
80 | 0 | } |
81 | |
|
82 | 0 | message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE); |
83 | 0 | pcmk__xe_set_props(message, |
84 | 0 | PCMK_XA_ORIGIN, origin, |
85 | 0 | PCMK__XA_T, pcmk__server_message_type(server), |
86 | 0 | PCMK__XA_SUBT, subtype, |
87 | 0 | PCMK_XA_VERSION, CRM_FEATURE_SET, |
88 | 0 | PCMK_XA_REFERENCE, reply_to, |
89 | 0 | PCMK__XA_CRM_SYS_FROM, sender_system, |
90 | 0 | PCMK__XA_CRM_HOST_TO, recipient_node, |
91 | 0 | PCMK__XA_CRM_SYS_TO, recipient_system, |
92 | 0 | PCMK__XA_CRM_TASK, task, |
93 | 0 | NULL); |
94 | 0 | if (data != NULL) { |
95 | 0 | xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML); |
96 | |
|
97 | 0 | pcmk__xml_copy(wrapper, data); |
98 | 0 | } |
99 | 0 | free(message_id); |
100 | 0 | return message; |
101 | 0 | } |
102 | | |
103 | | /*! |
104 | | * \internal |
105 | | * \brief Create a Pacemaker reply (for IPC or cluster layer) |
106 | | * |
107 | | * \param[in] origin Name of function that called this one |
108 | | * \param[in] original_request XML of request being replied to |
109 | | * \param[in] data If not NULL, copy as reply's data (callers |
110 | | * should not add attributes to the returned |
111 | | * message element, but instead pass any desired |
112 | | * information here, though this is not always |
113 | | * honored currently) |
114 | | * |
115 | | * \return Newly created reply XML |
116 | | * |
117 | | * \note This function should not be called directly, but via the |
118 | | * pcmk__new_reply() wrapper. |
119 | | * \note The caller is responsible for freeing the return value using |
120 | | * \c pcmk__xml_free(). |
121 | | */ |
122 | | xmlNode * |
123 | | pcmk__new_reply_as(const char *origin, const xmlNode *original_request, |
124 | | xmlNode *data) |
125 | 0 | { |
126 | 0 | const char *message_type = pcmk__xe_get(original_request, PCMK__XA_T); |
127 | 0 | const char *host_from = pcmk__xe_get(original_request, PCMK__XA_SRC); |
128 | 0 | const char *sys_from = pcmk__xe_get(original_request, |
129 | 0 | PCMK__XA_CRM_SYS_FROM); |
130 | 0 | const char *sys_to = pcmk__xe_get(original_request, PCMK__XA_CRM_SYS_TO); |
131 | 0 | const char *type = pcmk__xe_get(original_request, PCMK__XA_SUBT); |
132 | 0 | const char *operation = pcmk__xe_get(original_request, PCMK__XA_CRM_TASK); |
133 | 0 | const char *crm_msg_reference = pcmk__xe_get(original_request, |
134 | 0 | PCMK_XA_REFERENCE); |
135 | 0 | enum pcmk_ipc_server server = pcmk__parse_server(message_type); |
136 | |
|
137 | 0 | if (server == pcmk_ipc_unknown) { |
138 | | /* @COMPAT Not all requests currently specify a message type, so use a |
139 | | * default that preserves past behavior. |
140 | | * |
141 | | * @TODO Ensure all requests specify a message type, drop this check |
142 | | * after we no longer support rolling upgrades or Pacemaker Remote |
143 | | * connections involving versions before that. |
144 | | */ |
145 | 0 | server = pcmk_ipc_controld; |
146 | 0 | } |
147 | |
|
148 | 0 | if (type == NULL) { |
149 | 0 | pcmk__warn("Cannot reply to invalid message: No message type " |
150 | 0 | "specified"); |
151 | 0 | return NULL; |
152 | 0 | } |
153 | | |
154 | 0 | if (strcmp(type, PCMK__VALUE_REQUEST) != 0) { |
155 | | /* Replies should only be generated for request messages, but it's possible |
156 | | * we expect replies to other messages right now so this can't be enforced. |
157 | | */ |
158 | 0 | pcmk__trace("Creating a reply for a non-request original message"); |
159 | 0 | } |
160 | | |
161 | | // Since this is a reply, we reverse the sender and recipient info |
162 | 0 | return pcmk__new_message_as(origin, server, crm_msg_reference, sys_to, |
163 | 0 | host_from, sys_from, operation, data); |
164 | 0 | } |
165 | | |
166 | | /*! |
167 | | * \internal |
168 | | * \brief Register handlers for server commands |
169 | | * |
170 | | * \param[in] handlers Array of handler functions for supported server commands |
171 | | * (the final entry must have a NULL command name, and if |
172 | | * it has a handler it will be used as the default handler |
173 | | * for unrecognized commands) |
174 | | * |
175 | | * \return Newly created hash table with commands and handlers |
176 | | * \note The caller is responsible for freeing the return value with |
177 | | * g_hash_table_destroy(). |
178 | | */ |
179 | | GHashTable * |
180 | | pcmk__register_handlers(const pcmk__server_command_t handlers[]) |
181 | 0 | { |
182 | 0 | GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal); |
183 | |
|
184 | 0 | if (handlers != NULL) { |
185 | 0 | int i; |
186 | |
|
187 | 0 | for (i = 0; handlers[i].command != NULL; ++i) { |
188 | 0 | g_hash_table_insert(commands, (gpointer) handlers[i].command, |
189 | 0 | handlers[i].handler); |
190 | 0 | } |
191 | 0 | if (handlers[i].handler != NULL) { |
192 | | // g_str_hash() can't handle NULL, so use empty string for default |
193 | 0 | g_hash_table_insert(commands, (gpointer) "", handlers[i].handler); |
194 | 0 | } |
195 | 0 | } |
196 | 0 | return commands; |
197 | 0 | } |
198 | | |
199 | | /*! |
200 | | * \internal |
201 | | * \brief Process an incoming request |
202 | | * |
203 | | * \param[in,out] request Request to process |
204 | | * \param[in] handlers Command table created by pcmk__register_handlers() |
205 | | * |
206 | | * \return XML to send as reply (or NULL if no reply is needed) |
207 | | */ |
208 | | xmlNode * |
209 | | pcmk__process_request(pcmk__request_t *request, GHashTable *handlers) |
210 | 0 | { |
211 | 0 | xmlNode *(*handler)(pcmk__request_t *request) = NULL; |
212 | |
|
213 | 0 | CRM_CHECK((request != NULL) && (request->op != NULL) && (handlers != NULL), |
214 | 0 | return NULL); |
215 | | |
216 | 0 | if (pcmk__is_set(request->flags, pcmk__request_sync) |
217 | 0 | && (request->ipc_client != NULL)) { |
218 | 0 | CRM_CHECK(request->ipc_client->request_id == request->ipc_id, |
219 | 0 | return NULL); |
220 | 0 | } |
221 | | |
222 | 0 | handler = g_hash_table_lookup(handlers, request->op); |
223 | 0 | if (handler == NULL) { |
224 | 0 | handler = g_hash_table_lookup(handlers, ""); // Default handler |
225 | 0 | if (handler == NULL) { |
226 | 0 | pcmk__info("Ignoring %s request from %s %s with no handler", |
227 | 0 | request->op, pcmk__request_origin_type(request), |
228 | 0 | pcmk__request_origin(request)); |
229 | 0 | return NULL; |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | 0 | return (*handler)(request); |
234 | 0 | } |
235 | | |
236 | | /*! |
237 | | * \internal |
238 | | * \brief Free memory used within a request (but not the request itself) |
239 | | * |
240 | | * \param[in,out] request Request to reset |
241 | | */ |
242 | | void |
243 | | pcmk__reset_request(pcmk__request_t *request) |
244 | 0 | { |
245 | 0 | free(request->op); |
246 | 0 | request->op = NULL; |
247 | |
|
248 | 0 | pcmk__reset_result(&(request->result)); |
249 | 0 | } |