/src/pacemaker/lib/common/ipc_common.c
Line | Count | Source |
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 <errno.h> // EBADMSG, EINVAL |
13 | | #include <inttypes.h> // PRId32, PRIu16, uint64_t |
14 | | #include <stdbool.h> // bool, false, true |
15 | | #include <stdlib.h> // NULL |
16 | | |
17 | | #include <glib.h> // GByteArray, g_byte_array_append, guint8 |
18 | | #include <qb/qbipc_common.h> // qb_ipc_response_header |
19 | | |
20 | | #include <crm/common/ipc.h> // crm_ipc_flags |
21 | | #include <crm/common/logging.h> // CRM_CHECK |
22 | | #include <crm/common/results.h> // pcmk_rc_* |
23 | | |
24 | | #include "crmcommon_private.h" |
25 | | |
26 | | /* The IPC buffer is always 128k. If we are asked to send a message larger |
27 | | * than that size, it will be split into multiple messages that must be |
28 | | * reassembled on the other end. |
29 | | */ |
30 | 0 | #define BUFFER_SIZE (128*1024) // 128k |
31 | | |
32 | | /*! |
33 | | * \brief Return pacemaker's IPC buffer size |
34 | | * |
35 | | * \return IPC buffer size in bytes |
36 | | */ |
37 | | unsigned int |
38 | | crm_ipc_default_buffer_size(void) |
39 | 0 | { |
40 | 0 | return BUFFER_SIZE; |
41 | 0 | } |
42 | | |
43 | | /*! |
44 | | * \internal |
45 | | * \brief Check whether an IPC header is valid |
46 | | * |
47 | | * \param[in] header IPC header to check |
48 | | * |
49 | | * \return true if IPC header has a supported version, false otherwise |
50 | | */ |
51 | | bool |
52 | | pcmk__valid_ipc_header(const pcmk__ipc_header_t *header) |
53 | 0 | { |
54 | 0 | if (header == NULL) { |
55 | 0 | pcmk__err("IPC message without header"); |
56 | 0 | return false; |
57 | 0 | } |
58 | | |
59 | 0 | if (header->version > PCMK__IPC_VERSION) { |
60 | 0 | pcmk__err("Filtering incompatible v%d IPC message (only versions <= %d " |
61 | 0 | "supported)", |
62 | 0 | header->version, PCMK__IPC_VERSION); |
63 | 0 | return false; |
64 | 0 | } |
65 | | |
66 | 0 | return true; |
67 | 0 | } |
68 | | |
69 | | const char * |
70 | | pcmk__client_type_str(uint64_t client_type) |
71 | 0 | { |
72 | 0 | switch (client_type) { |
73 | 0 | case pcmk__client_ipc: |
74 | 0 | return "IPC"; |
75 | 0 | case pcmk__client_tcp: |
76 | 0 | return "TCP"; |
77 | 0 | case pcmk__client_tls: |
78 | 0 | return "TLS"; |
79 | 0 | default: |
80 | 0 | return "unknown"; |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | | /*! |
85 | | * \internal |
86 | | * \brief Add more data to a received partial IPC message |
87 | | * |
88 | | * This function can be called repeatedly to build up a complete IPC message |
89 | | * from smaller parts. It does this by inspecting flags on the message. |
90 | | * Most of the time, IPC messages will be small enough where this function |
91 | | * won't get called more than once, but more complex clusters can end up with |
92 | | * very large IPC messages that don't fit in a single buffer. |
93 | | * |
94 | | * Important return values: |
95 | | * |
96 | | * - EBADMSG - Something was wrong with the data. |
97 | | * - pcmk_rc_ipc_more - \p data was a chunk of a partial message and there is |
98 | | * more to come. The caller should not process the message |
99 | | * yet and should continue reading from the IPC connection. |
100 | | * - pcmk_rc_ok - We have the complete message. The caller should process |
101 | | * it and free the buffer to prepare for the next message. |
102 | | * |
103 | | * \param[in,out] buffer The buffer to add this data to |
104 | | * \param[in] data The received IPC message or message portion. |
105 | | * |
106 | | * \return Standard Pacemaker return code |
107 | | */ |
108 | | int |
109 | | pcmk__ipc_msg_append(GByteArray **buffer, guint8 *data) |
110 | 0 | { |
111 | 0 | pcmk__ipc_header_t *full_header = NULL; |
112 | 0 | pcmk__ipc_header_t *header = (void *) data; |
113 | 0 | const guint8 *payload = (guint8 *) data + sizeof(pcmk__ipc_header_t); |
114 | 0 | int rc = pcmk_rc_ok; |
115 | |
|
116 | 0 | if (!pcmk__valid_ipc_header(header)) { |
117 | 0 | return EBADMSG; |
118 | 0 | } |
119 | | |
120 | 0 | if (pcmk__is_set(header->flags, crm_ipc_multipart_end)) { |
121 | 0 | CRM_CHECK(buffer != NULL && *buffer != NULL && header->part_id != 0, |
122 | 0 | return EINVAL); |
123 | | |
124 | 0 | full_header = (void *) (*buffer)->data; |
125 | | |
126 | | /* This is the end of a multipart IPC message. Add the payload of the |
127 | | * received data (so, don't include the header) to the partial buffer. |
128 | | * Remember that this needs to include the null terminating character. |
129 | | */ |
130 | 0 | CRM_CHECK(full_header->qb.id == header->qb.id, return EBADMSG); |
131 | 0 | g_byte_array_append(*buffer, payload, header->size); |
132 | |
|
133 | 0 | pcmk__trace("Received IPC message %" PRId32 " (final part %" PRIu16 ") " |
134 | 0 | "of %" PRId32 " bytes", |
135 | 0 | header->qb.id, header->part_id, header->qb.size); |
136 | |
|
137 | 0 | } else if (pcmk__is_set(header->flags, crm_ipc_multipart)) { |
138 | 0 | if (header->part_id == 0) { |
139 | | /* This is the first part of a multipart IPC message. Initialize |
140 | | * the buffer with the entire message, including its header. Do |
141 | | * not include the null terminating character. |
142 | | */ |
143 | 0 | CRM_CHECK(buffer != NULL && *buffer == NULL, return EINVAL); |
144 | 0 | *buffer = g_byte_array_new(); |
145 | | |
146 | | /* Clear any multipart flags from the header of the incoming part |
147 | | * so they'll be clear in the fully reassembled message. This |
148 | | * message is passed to pcmk__client_data2xml, which will extract |
149 | | * the header flags and return them. Those flags can then be used |
150 | | * when constructing a reply, including ACKs. We don't want these |
151 | | * specific incoming flags to influence the reply. |
152 | | */ |
153 | 0 | pcmk__clear_ipc_flags(header->flags, "server", crm_ipc_multipart); |
154 | |
|
155 | 0 | g_byte_array_append(*buffer, data, |
156 | 0 | sizeof(pcmk__ipc_header_t) + header->size - 1); |
157 | |
|
158 | 0 | } else { |
159 | 0 | CRM_CHECK(buffer != NULL && *buffer != NULL, return EINVAL); |
160 | | |
161 | 0 | full_header = (void *) (*buffer)->data; |
162 | | |
163 | | /* This is some intermediate part of a multipart message. Add |
164 | | * the payload of the received data (so, don't include the header) |
165 | | * to the partial buffer and return. Do not include the null |
166 | | * terminating character. |
167 | | */ |
168 | 0 | CRM_CHECK(full_header->qb.id == header->qb.id, return EBADMSG); |
169 | 0 | g_byte_array_append(*buffer, payload, header->size - 1); |
170 | 0 | } |
171 | | |
172 | 0 | pcmk__trace("Received IPC message %" PRId32 " (part %" PRIu16 ") " |
173 | 0 | "of %" PRId32 " bytes", |
174 | 0 | header->qb.id, header->part_id, header->qb.size); |
175 | | |
176 | 0 | rc = pcmk_rc_ipc_more; |
177 | |
|
178 | 0 | } else { |
179 | | /* This is a standalone IPC message. For simplicity in the caller, |
180 | | * copy the entire message over into a byte array so it can be handled |
181 | | * the same as a multipart message. |
182 | | */ |
183 | 0 | CRM_CHECK(buffer != NULL && *buffer == NULL, return EINVAL); |
184 | 0 | *buffer = g_byte_array_new(); |
185 | 0 | g_byte_array_append(*buffer, data, |
186 | 0 | sizeof(pcmk__ipc_header_t) + header->size); |
187 | |
|
188 | 0 | pcmk__trace("Received IPC message %" PRId32 " of %" PRId32 " bytes", |
189 | 0 | header->qb.id, header->qb.size); |
190 | 0 | } |
191 | | |
192 | | /* Ensure that a complete message always ends with a null terminating |
193 | | * character. |
194 | | */ |
195 | 0 | if ((rc == pcmk_rc_ok) && ((*buffer)->data[(*buffer)->len - 1] != '\0')) { |
196 | 0 | const guint8 zero = 0; |
197 | 0 | g_byte_array_append(*buffer, &zero, 1); |
198 | 0 | } |
199 | |
|
200 | 0 | pcmk__trace("Text = %s", payload); |
201 | 0 | pcmk__trace("Buffer = %s", (*buffer)->data + sizeof(pcmk__ipc_header_t)); |
202 | | |
203 | | /* The buffer's header should have a size that matches the full size of |
204 | | * the received message, not just the last chunk of it. |
205 | | */ |
206 | 0 | full_header = (void *) (*buffer)->data; |
207 | 0 | full_header->size = (*buffer)->len - sizeof(pcmk__ipc_header_t); |
208 | |
|
209 | 0 | return rc; |
210 | 0 | } |