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 General Public License version 2
7 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <errno.h>
13 #include <stdbool.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <inttypes.h> // PRIu32
17 #include <sys/types.h>
18
19 #include <crm/cluster.h>
20 #include <crm/cluster/internal.h>
21 #include <crm/common/logging.h>
22 #include <crm/common/results.h>
23 #include <crm/common/util.h>
24 #include <crm/common/xml.h>
25
26 #include "pacemaker-attrd.h"
27
28 static qb_ipcs_service_t *ipcs = NULL;
29
30 /*!
31 * \internal
32 * \brief Build the XML reply to a client query
33 *
34 * \param[in] attr Name of requested attribute
35 * \param[in] host Name of requested host (or NULL for all hosts)
36 *
37 * \return New XML reply
38 * \note Caller is responsible for freeing the resulting XML
39 */
40 static xmlNode *build_query_reply(const char *attr, const char *host)
41 {
42 xmlNode *reply = pcmk__xe_create(NULL, __func__);
43 attribute_t *a;
44
45 pcmk__xe_set(reply, PCMK__XA_T, PCMK__VALUE_ATTRD);
46 pcmk__xe_set(reply, PCMK__XA_SUBT, PCMK__ATTRD_CMD_QUERY);
47 pcmk__xe_set(reply, PCMK__XA_ATTR_VERSION, ATTRD_PROTOCOL_VERSION);
48
49 /* If desired attribute exists, add its value(s) to the reply */
50 a = g_hash_table_lookup(attributes, attr);
51 if (a) {
52 attribute_value_t *v;
53 xmlNode *host_value;
54
55 pcmk__xe_set(reply, PCMK__XA_ATTR_NAME, attr);
56
57 /* Allow caller to use "localhost" to refer to local node */
58 if (pcmk__str_eq(host, "localhost", pcmk__str_casei)) {
59 host = attrd_cluster->priv->node_name;
60 pcmk__trace("Mapped localhost to %s", host);
61 }
62
63 /* If a specific node was requested, add its value */
64 if (host) {
65 v = g_hash_table_lookup(a->values, host);
66 host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
67 pcmk__xe_set(host_value, PCMK__XA_ATTR_HOST, host);
68 pcmk__xe_set(host_value, PCMK__XA_ATTR_VALUE,
69 ((v != NULL)? v->current : NULL));
70
71 /* Otherwise, add all nodes' values */
72 } else {
73 GHashTableIter iter;
74
75 g_hash_table_iter_init(&iter, a->values);
76 while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &v)) {
77 host_value = pcmk__xe_create(reply, PCMK_XE_NODE);
78 pcmk__xe_set(host_value, PCMK__XA_ATTR_HOST, v->nodename);
79 pcmk__xe_set(host_value, PCMK__XA_ATTR_VALUE, v->current);
80 }
81 }
82 }
83 return reply;
84 }
85
86 void
87 attrd_client_clear_failure(pcmk__request_t *request)
88 {
89 xmlNode *xml = request->xml;
90 const char *rsc, *op, *interval_spec;
91
92 if (minimum_protocol_version >= 2) {
93 /* Propagate to all peers (including ourselves).
94 * This ends up at attrd_peer_message().
95 */
96 attrd_send_message(NULL, xml, false);
97 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
98 return;
99 }
100
101 rsc = pcmk__xe_get(xml, PCMK__XA_ATTR_RESOURCE);
102 op = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_OPERATION);
103 interval_spec = pcmk__xe_get(xml, PCMK__XA_ATTR_CLEAR_INTERVAL);
104
105 /* Map this to an update */
106 pcmk__xe_set(xml, PCMK_XA_TASK, PCMK__ATTRD_CMD_UPDATE);
107
108 /* Add regular expression matching desired attributes */
109
110 if (rsc) {
111 char *pattern;
112
113 if (op == NULL) {
114 pattern = pcmk__assert_asprintf(ATTRD_RE_CLEAR_ONE, rsc);
115
116 } else {
117 guint interval_ms = 0U;
118
119 pcmk_parse_interval_spec(interval_spec, &interval_ms);
120 pattern = pcmk__assert_asprintf(ATTRD_RE_CLEAR_OP, rsc, op,
121 interval_ms);
122 }
123
124 pcmk__xe_set(xml, PCMK__XA_ATTR_REGEX, pattern);
125 free(pattern);
126
127 } else {
128 pcmk__xe_set(xml, PCMK__XA_ATTR_REGEX, ATTRD_RE_CLEAR_ALL);
129 }
130
131 /* Make sure attribute and value are not set, so we delete via regex */
132 pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_NAME);
133 pcmk__xe_remove_attr(xml, PCMK__XA_ATTR_VALUE);
134
135 attrd_client_update(request);
136 }
137
138 void
139 attrd_client_peer_remove(pcmk__request_t *request)
140 {
141 xmlNode *xml = request->xml;
142
143 // Host and ID are not used in combination, rather host has precedence
144 const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
145 char *host_alloc = NULL;
146
147 attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
148
149 if (host == NULL) {
150 int nodeid = 0;
151
152 pcmk__xe_get_int(xml, PCMK__XA_ATTR_HOST_ID, &nodeid);
153 if (nodeid > 0) {
154 pcmk__node_status_t *node = NULL;
155 char *host_alloc = NULL;
156
157 node = pcmk__search_node_caches(nodeid, NULL, NULL,
158 pcmk__node_search_cluster_member);
159 if ((node != NULL) && (node->name != NULL)) {
160 // Use cached name if available
161 host = node->name;
162 } else {
163 // Otherwise ask cluster layer
164 host_alloc = pcmk__cluster_node_name(nodeid);
165 host = host_alloc;
166 }
167 pcmk__xe_set(xml, PCMK__XA_ATTR_HOST, host);
168 }
169 }
170
171 if (host) {
172 pcmk__info("Client %s is requesting all values for %s be removed",
173 pcmk__client_name(request->ipc_client), host);
174 attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
175 free(host_alloc);
176 } else {
177 pcmk__info("Ignoring request by client %s to remove all peer values "
178 "without specifying peer",
179 pcmk__client_name(request->ipc_client));
180 }
181
182 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
183 }
184
185 xmlNode *
186 attrd_client_query(pcmk__request_t *request)
187 {
188 xmlNode *query = request->xml;
189 xmlNode *reply = NULL;
190 const char *attr = NULL;
191
192 pcmk__debug("Query arrived from %s",
193 pcmk__client_name(request->ipc_client));
194
195 /* Request must specify attribute name to query */
196 attr = pcmk__xe_get(query, PCMK__XA_ATTR_NAME);
197 if (attr == NULL) {
198 pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
199 "Ignoring malformed query from %s (no attribute name given)",
200 pcmk__client_name(request->ipc_client));
201 return NULL;
202 }
203
204 /* Build the XML reply */
205 reply = build_query_reply(attr, pcmk__xe_get(query, PCMK__XA_ATTR_HOST));
206 if (reply == NULL) {
207 pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
208 "Could not respond to query from %s: could not create XML reply",
209 pcmk__client_name(request->ipc_client));
210 return NULL;
211 } else {
212 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
213 }
214
215 request->ipc_client->request_id = 0;
216 return reply;
217 }
218
219 void
220 attrd_client_refresh(pcmk__request_t *request)
221 {
222 pcmk__info("Updating all attributes");
223
224 attrd_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags);
225 attrd_write_attributes(attrd_write_all|attrd_write_no_delay);
226
227 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
228 }
229
230 static void
231 handle_missing_host(xmlNode *xml)
232 {
233 if (pcmk__xe_get(xml, PCMK__XA_ATTR_HOST) == NULL) {
234 pcmk__trace("Inferring local node %s with XML ID %s",
235 attrd_cluster->priv->node_name,
236 attrd_cluster->priv->node_xml_id);
237 pcmk__xe_set(xml, PCMK__XA_ATTR_HOST, attrd_cluster->priv->node_name);
238 pcmk__xe_set(xml, PCMK__XA_ATTR_HOST_ID,
239 attrd_cluster->priv->node_xml_id);
240 }
241 }
242
243 /* Convert a single IPC message with a regex into one with multiple children, one
244 * for each regex match.
245 */
246 static int
247 expand_regexes(xmlNode *xml, const char *attr, const char *value, const char *regex)
248 {
249 if (attr == NULL && regex) {
250 bool matched = false;
251 GHashTableIter aIter;
252 regex_t r_patt;
253
254 pcmk__debug("Setting %s to %s", regex, value);
255 if (regcomp(&r_patt, regex, REG_EXTENDED|REG_NOSUB)) {
256 return EINVAL;
257 }
258
259 g_hash_table_iter_init(&aIter, attributes);
260 while (g_hash_table_iter_next(&aIter, (gpointer *) & attr, NULL)) {
261 int status = regexec(&r_patt, attr, 0, NULL, 0);
262
263 if (status == 0) {
264 xmlNode *child = pcmk__xe_create(xml, PCMK_XE_OP);
265
266 pcmk__trace("Matched %s with %s", attr, regex);
267 matched = true;
268
269 /* Copy all the non-conflicting attributes from the parent over,
270 * but remove the regex and replace it with the name.
271 */
272 pcmk__xe_copy_attrs(child, xml, pcmk__xaf_no_overwrite);
273 pcmk__xe_remove_attr(child, PCMK__XA_ATTR_REGEX);
274 pcmk__xe_set(child, PCMK__XA_ATTR_NAME, attr);
275 }
276 }
277
278 regfree(&r_patt);
279
280 /* Return a code if we never matched anything. This should not be treated
281 * as an error. It indicates there was a regex, and it was a valid regex,
282 * but simply did not match anything and the caller should not continue
283 * doing any regex-related processing.
284 */
285 if (!matched) {
286 return pcmk_rc_op_unsatisfied;
287 }
288
289 } else if (attr == NULL) {
290 return pcmk_rc_bad_nvpair;
291 }
292
293 return pcmk_rc_ok;
294 }
295
296 static int
297 handle_regexes(pcmk__request_t *request)
298 {
299 xmlNode *xml = request->xml;
300 int rc = pcmk_rc_ok;
301
302 const char *attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
303 const char *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
304 const char *regex = pcmk__xe_get(xml, PCMK__XA_ATTR_REGEX);
305
306 rc = expand_regexes(xml, attr, value, regex);
307
308 if (rc == EINVAL) {
309 pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
310 "Bad regex '%s' for update from client %s", regex,
311 pcmk__client_name(request->ipc_client));
312
313 } else if (rc == pcmk_rc_bad_nvpair) {
314 pcmk__err("Update request did not specify attribute or regular "
315 "expression");
316 pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
317 "Client %s update request did not specify attribute or regular expression",
318 pcmk__client_name(request->ipc_client));
319 }
320
321 return rc;
322 }
323
324 static int
325 handle_value_expansion(const char **value, xmlNode *xml, const char *op,
326 const char *attr)
327 {
328 attribute_t *a = g_hash_table_lookup(attributes, attr);
329
330 if (a == NULL && pcmk__str_eq(op, PCMK__ATTRD_CMD_UPDATE_DELAY, pcmk__str_none)) {
331 return EINVAL;
332 }
333
334 if (*value && attrd_value_needs_expansion(*value)) {
335 int int_value;
336 attribute_value_t *v = NULL;
337
338 if (a) {
339 const char *host = pcmk__xe_get(xml, PCMK__XA_ATTR_HOST);
340 v = g_hash_table_lookup(a->values, host);
341 }
342
343 int_value = attrd_expand_value(*value, (v? v->current : NULL));
344
345 pcmk__info("Expanded %s=%s to %d", attr, *value, int_value);
346 pcmk__xe_set_int(xml, PCMK__XA_ATTR_VALUE, int_value);
347
348 /* Replacing the value frees the previous memory, so re-query it */
349 *value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
350 }
351
352 return pcmk_rc_ok;
353 }
354
355 static void
356 send_update_msg_to_cluster(pcmk__request_t *request, xmlNode *xml)
357 {
358 if (pcmk__str_eq(attrd_request_sync_point(xml), PCMK__VALUE_CLUSTER, pcmk__str_none)) {
359 /* The client is waiting on the cluster-wide sync point. In this case,
360 * the response ACK is not sent until this attrd broadcasts the update
361 * and receives its own confirmation back from all peers.
362 */
363 attrd_expect_confirmations(request, attrd_cluster_sync_point_update);
364 attrd_send_message(NULL, xml, true); /* ends up at attrd_peer_message() */
365
366 } else {
367 /* The client is either waiting on the local sync point or was not
368 * waiting on any sync point at all. For the local sync point, the
369 * response ACK is sent in attrd_peer_update. For clients not
370 * waiting on any sync point, the response ACK is sent in
371 * handle_update_request immediately before this function was called.
372 */
373 attrd_send_message(NULL, xml, false); /* ends up at attrd_peer_message() */
374 }
375 }
376
377 static int
378 send_child_update(xmlNode *child, void *data)
379 {
380 pcmk__request_t *request = (pcmk__request_t *) data;
381
382 /* Calling pcmk__set_result is handled by one of these calls to
383 * attrd_client_update, so no need to do it again here.
384 */
385 request->xml = child;
386 attrd_client_update(request);
387 return pcmk_rc_ok;
388 }
389
390 void
391 attrd_client_update(pcmk__request_t *request)
392 {
393 xmlNode *xml = NULL;
394 const char *attr, *value, *regex;
395
396 CRM_CHECK((request != NULL) && (request->xml != NULL), return);
397
398 xml = request->xml;
399
400 /* If the message has children, that means it is a message from a newer
401 * client that supports sending multiple operations at a time. There are
402 * two ways we can handle that.
403 */
404 if (xml->children != NULL) {
405 if (ATTRD_SUPPORTS_MULTI_MESSAGE(minimum_protocol_version)) {
406 /* First, if all peers support a certain protocol version, we can
407 * just broadcast the big message and they'll handle it. However,
408 * we also need to apply all the transformations in this function
409 * to the children since they don't happen anywhere else.
410 */
411 for (xmlNode *child = pcmk__xe_first_child(xml, PCMK_XE_OP, NULL,
412 NULL);
413 child != NULL; child = pcmk__xe_next(child, PCMK_XE_OP)) {
414
415 attr = pcmk__xe_get(child, PCMK__XA_ATTR_NAME);
416 value = pcmk__xe_get(child, PCMK__XA_ATTR_VALUE);
417
418 handle_missing_host(child);
419
420 if (handle_value_expansion(&value, child, request->op, attr) == EINVAL) {
421 pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
422 "Attribute %s does not exist", attr);
423 return;
424 }
425 }
426
427 send_update_msg_to_cluster(request, xml);
428 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
429
430 } else {
431 /* Save the original xml node pointer so it can be restored after iterating
432 * over all the children.
433 */
434 xmlNode *orig_xml = request->xml;
435
436 /* Second, if they do not support that protocol version, split it
437 * up into individual messages and call attrd_client_update on
438 * each one.
439 */
440 pcmk__xe_foreach_child(xml, PCMK_XE_OP, send_child_update, request);
441 request->xml = orig_xml;
442 }
443
444 return;
445 }
446
447 attr = pcmk__xe_get(xml, PCMK__XA_ATTR_NAME);
448 value = pcmk__xe_get(xml, PCMK__XA_ATTR_VALUE);
449 regex = pcmk__xe_get(xml, PCMK__XA_ATTR_REGEX);
450
451 if (handle_regexes(request) != pcmk_rc_ok) {
452 /* Error handling was already dealt with in handle_regexes, so just return. */
453 return;
454 }
455
456 if (regex != NULL) {
457 /* Recursively call attrd_client_update on the new message with regexes
458 * expanded. If supported by the attribute daemon, this means that all
459 * matches can also be handled atomically.
460 */
461 attrd_client_update(request);
462 return;
463 }
464
465 handle_missing_host(xml);
466
467 if (handle_value_expansion(&value, xml, request->op, attr) == EINVAL) {
468 pcmk__format_result(&request->result, CRM_EX_NOSUCH, PCMK_EXEC_ERROR,
469 "Attribute %s does not exist", attr);
470 return;
471 }
472
473 pcmk__debug("Broadcasting %s[%s]=%s%s", attr,
474 pcmk__xe_get(xml, PCMK__XA_ATTR_HOST), value,
475 (attrd_election_won()? " (writer)" : ""));
476
477 send_update_msg_to_cluster(request, xml);
478 pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
479 }
480
481 /*!
482 * \internal
483 * \brief Accept a new client IPC connection
484 *
485 * \param[in,out] c New connection
486 * \param[in] uid Client user id
487 * \param[in] gid Client group id
488 *
489 * \return pcmk_ok on success, -errno otherwise
490 */
491 static int32_t
492 attrd_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid)
493 {
494 pcmk__trace("New client connection %p", c);
495 if (attrd_shutting_down()) {
496 pcmk__info("Ignoring new connection from pid %d during shutdown",
497 pcmk__client_pid(c));
498 return -ECONNREFUSED;
499 }
500
501 if (pcmk__new_client(c, uid, gid) == NULL) {
502 return -ENOMEM;
503 }
504 return pcmk_ok;
505 }
506
507 /*!
508 * \internal
509 * \brief Destroy a client IPC connection
510 *
511 * \param[in] c Connection to destroy
512 *
513 * \return 0 (do not re-run this callback)
514 */
515 static int32_t
516 attrd_ipc_closed(qb_ipcs_connection_t *c)
517 {
518 pcmk__client_t *client = pcmk__find_client(c);
519
520 if (client == NULL) {
521 pcmk__trace("Ignoring request to clean up unknown connection %p", c);
522 } else {
523 pcmk__trace("Cleaning up closed client connection %p", c);
524
525 /* Remove the client from the sync point waitlist if it's present. */
526 attrd_remove_client_from_waitlist(client);
527
528 /* And no longer wait for confirmations from any peers. */
529 attrd_do_not_wait_for_client(client);
530
531 pcmk__free_client(client);
532 }
533
534 return 0;
535 }
536
537 /*!
538 * \internal
539 * \brief Destroy a client IPC connection
540 *
541 * \param[in,out] c Connection to destroy
542 *
543 * \note We handle a destroyed connection the same as a closed one,
544 * but we need a separate handler because the return type is different.
545 */
546 static void
547 attrd_ipc_destroy(qb_ipcs_connection_t *c)
548 {
549 pcmk__trace("Destroying client connection %p", c);
550 attrd_ipc_closed(c);
551 }
552
553 static int32_t
554 attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
555 {
556 int rc = pcmk_rc_ok;
557 uint32_t id = 0;
558 uint32_t flags = 0;
559 pcmk__client_t *client = pcmk__find_client(c);
560 xmlNode *xml = NULL;
561
562 // Sanity-check, and parse XML from IPC data
563 CRM_CHECK(client != NULL, return 0);
564 if (data == NULL) {
565 pcmk__debug("No IPC data from PID %d", pcmk__client_pid(c));
566 return 0;
567 }
568
569 rc = pcmk__ipc_msg_append(&client->buffer, data);
570
571 if (rc == pcmk_rc_ipc_more) {
572 /* We haven't read the complete message yet, so just return. */
573 return 0;
574
575 } else if (rc == pcmk_rc_ok) {
576 /* We've read the complete message and there's already a header on
577 * the front. Pass it off for processing.
578 */
579 xml = pcmk__client_data2xml(client, &id, &flags);
580 g_byte_array_free(client->buffer, TRUE);
581 client->buffer = NULL;
582
583 } else {
584 /* Some sort of error occurred reassembling the message. All we can
585 * do is clean up, log an error and return.
586 */
587 pcmk__err("Error when reading IPC message: %s", pcmk_rc_str(rc));
588
589 if (client->buffer != NULL) {
590 g_byte_array_free(client->buffer, TRUE);
591 client->buffer = NULL;
592 }
593
594 return 0;
595 }
596
597 if (xml == NULL) {
598 pcmk__debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c));
599 pcmk__ipc_send_ack(client, id, flags, NULL, CRM_EX_PROTOCOL);
600 return 0;
601
602 } else {
603 pcmk__request_t request = {
604 .ipc_client = client,
605 .ipc_id = id,
606 .ipc_flags = flags,
607 .peer = NULL,
608 .xml = xml,
609 .call_options = 0,
610 .result = PCMK__UNKNOWN_RESULT,
611 };
612
613 pcmk__assert(client->user != NULL);
614 pcmk__update_acl_user(xml, PCMK__XA_ATTR_USER, client->user);
615
616 request.op = pcmk__xe_get_copy(request.xml, PCMK_XA_TASK);
617 CRM_CHECK(request.op != NULL, goto done);
618
619 attrd_handle_request(&request);
620 }
621
622 done:
623 pcmk__xml_free(xml);
624 return 0;
625 }
626
627 static struct qb_ipcs_service_handlers ipc_callbacks = {
628 .connection_accept = attrd_ipc_accept,
629 .connection_created = NULL,
630 .msg_process = attrd_ipc_dispatch,
631 .connection_closed = attrd_ipc_closed,
632 .connection_destroyed = attrd_ipc_destroy
633 };
634
635 /*!
636 * \internal
637 * \brief Clean up attrd IPC communication
638 */
639 void
640 attrd_ipc_cleanup(void)
641 {
|
(1) Event path: |
Condition "ipcs != NULL", taking true branch. |
642 if (ipcs != NULL) {
643 pcmk__drop_all_clients(ipcs);
|
CID (unavailable; MK=19397ca4ee716844aa7198cf2c3e2bef) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(2) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(3) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
644 g_clear_pointer(&ipcs, qb_ipcs_destroy);
645 }
646
647 pcmk__client_cleanup();
648 }
649
650 /*!
651 * \internal
652 * \brief Set up attrd IPC communication
653 */
654 void
655 attrd_ipc_init(void)
656 {
657 pcmk__serve_attrd_ipc(&ipcs, &ipc_callbacks);
658 }
659