Coverage Report

Created: 2025-12-31 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}