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