1 /*
2 * Copyright 2004-2024 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,
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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 {
63 static unsigned int message_counter = 0U;
64
65 xmlNode *message = NULL;
66 char *message_id = NULL;
67 const char *subtype = PCMK__VALUE_RESPONSE;
68
69 CRM_CHECK(!pcmk__str_empty(origin)
70 && !pcmk__str_empty(sender_system)
71 && !pcmk__str_empty(task),
72 return NULL);
73
74 if (reply_to == NULL) {
75 subtype = PCMK__VALUE_REQUEST;
76 message_id = crm_strdup_printf("%s-%s-%llu-%u", task, sender_system,
77 (unsigned long long) time(NULL),
78 message_counter++);
79 reply_to = message_id;
80 }
81
82 message = pcmk__xe_create(NULL, PCMK__XE_MESSAGE);
83 pcmk__xe_set_props(message,
84 PCMK_XA_ORIGIN, origin,
85 PCMK__XA_T, pcmk__server_message_type(server),
86 PCMK__XA_SUBT, subtype,
87 PCMK_XA_VERSION, CRM_FEATURE_SET,
88 PCMK_XA_REFERENCE, reply_to,
89 PCMK__XA_CRM_SYS_FROM, sender_system,
90 PCMK__XA_CRM_HOST_TO, recipient_node,
91 PCMK__XA_CRM_SYS_TO, recipient_system,
92 PCMK__XA_CRM_TASK, task,
93 NULL);
94 if (data != NULL) {
95 xmlNode *wrapper = pcmk__xe_create(message, PCMK__XE_CRM_XML);
96
97 pcmk__xml_copy(wrapper, data);
98 }
99 free(message_id);
100 return message;
101 }
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,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
124 xmlNode *data)
125 {
126 const char *message_type = crm_element_value(original_request, PCMK__XA_T);
127 const char *host_from = crm_element_value(original_request, PCMK__XA_SRC);
128 const char *sys_from = crm_element_value(original_request,
129 PCMK__XA_CRM_SYS_FROM);
130 const char *sys_to = crm_element_value(original_request,
131 PCMK__XA_CRM_SYS_TO);
132 const char *type = crm_element_value(original_request, PCMK__XA_SUBT);
133 const char *operation = crm_element_value(original_request,
134 PCMK__XA_CRM_TASK);
135 const char *crm_msg_reference = crm_element_value(original_request,
136 PCMK_XA_REFERENCE);
137 enum pcmk_ipc_server server = pcmk__parse_server(message_type);
138
139 if (server == pcmk_ipc_unknown) {
140 /* @COMPAT Not all requests currently specify a message type, so use a
141 * default that preserves past behavior.
142 *
143 * @TODO Ensure all requests specify a message type, drop this check
144 * after we no longer support rolling upgrades or Pacemaker Remote
145 * connections involving versions before that.
146 */
147 server = pcmk_ipc_controld;
148 }
149
150 if (type == NULL) {
151 crm_warn("Cannot reply to invalid message: No message type specified");
152 return NULL;
153 }
154
155 if (strcmp(type, PCMK__VALUE_REQUEST) != 0) {
156 /* Replies should only be generated for request messages, but it's possible
157 * we expect replies to other messages right now so this can't be enforced.
158 */
159 crm_trace("Creating a reply for a non-request original message");
160 }
161
162 // Since this is a reply, we reverse the sender and recipient info
163 return pcmk__new_message_as(origin, server, crm_msg_reference, sys_to,
164 host_from, sys_from, operation, data);
165 }
166
167 /*!
168 * \internal
169 * \brief Register handlers for server commands
170 *
171 * \param[in] handlers Array of handler functions for supported server commands
172 * (the final entry must have a NULL command name, and if
173 * it has a handler it will be used as the default handler
174 * for unrecognized commands)
175 *
176 * \return Newly created hash table with commands and handlers
177 * \note The caller is responsible for freeing the return value with
178 * g_hash_table_destroy().
179 */
180 GHashTable *
181 pcmk__register_handlers(const pcmk__server_command_t handlers[])
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
182 {
183 GHashTable *commands = g_hash_table_new(g_str_hash, g_str_equal);
184
185 if (handlers != NULL) {
186 int i;
187
188 for (i = 0; handlers[i].command != NULL; ++i) {
189 g_hash_table_insert(commands, (gpointer) handlers[i].command,
190 handlers[i].handler);
191 }
192 if (handlers[i].handler != NULL) {
193 // g_str_hash() can't handle NULL, so use empty string for default
194 g_hash_table_insert(commands, (gpointer) "", handlers[i].handler);
195 }
196 }
197 return commands;
198 }
199
200 /*!
201 * \internal
202 * \brief Process an incoming request
203 *
204 * \param[in,out] request Request to process
205 * \param[in] handlers Command table created by pcmk__register_handlers()
206 *
207 * \return XML to send as reply (or NULL if no reply is needed)
208 */
209 xmlNode *
210 pcmk__process_request(pcmk__request_t *request, GHashTable *handlers)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
211 {
212 xmlNode *(*handler)(pcmk__request_t *request) = NULL;
213
214 CRM_CHECK((request != NULL) && (request->op != NULL) && (handlers != NULL),
215 return NULL);
216
217 if (pcmk_is_set(request->flags, pcmk__request_sync)
218 && (request->ipc_client != NULL)) {
219 CRM_CHECK(request->ipc_client->request_id == request->ipc_id,
220 return NULL);
221 }
222
223 handler = g_hash_table_lookup(handlers, request->op);
224 if (handler == NULL) {
225 handler = g_hash_table_lookup(handlers, ""); // Default handler
226 if (handler == NULL) {
227 crm_info("Ignoring %s request from %s %s with no handler",
228 request->op, pcmk__request_origin_type(request),
229 pcmk__request_origin(request));
230 return NULL;
231 }
232 }
233
234 return (*handler)(request);
235 }
236
237 /*!
238 * \internal
239 * \brief Free memory used within a request (but not the request itself)
240 *
241 * \param[in,out] request Request to reset
242 */
243 void
244 pcmk__reset_request(pcmk__request_t *request)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
245 {
246 free(request->op);
247 request->op = NULL;
248
249 pcmk__reset_result(&(request->result));
250 }