1 /*
2 * Original copyright 2004 International Business Machines
3 * Later changes copyright 2008-2026 the Pacemaker project contributors
4 *
5 * The version control history for this file may have further details.
6 *
7 * This source code is licensed under the GNU Lesser General Public License
8 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
9 */
10 #include <crm_internal.h>
11 #include <unistd.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <stdarg.h>
16 #include <string.h>
17 #include <sys/utsname.h>
18
19 #include <glib.h>
20
21 #include <crm/crm.h>
22 #include <crm/cib/internal.h>
23 #include <crm/common/xml.h>
24
25 gboolean
26 cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
27 {
28 *epoch = -1;
29 *updates = -1;
30 *admin_epoch = -1;
31
32 if (cib == NULL) {
33 return FALSE;
34 }
35
36 pcmk__xe_get_int(cib, PCMK_XA_EPOCH, epoch);
37 pcmk__xe_get_int(cib, PCMK_XA_NUM_UPDATES, updates);
38 pcmk__xe_get_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
39 return TRUE;
40 }
41
42 /*!
43 * \internal
44 * \brief Get the XML patchset from a CIB diff notification
45 *
46 * \param[in] msg CIB diff notification
47 * \param[out] patchset Where to store XML patchset
48 *
49 * \return Standard Pacemaker return code
50 */
51 int
52 cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
53 {
54 int rc = pcmk_err_generic;
55 xmlNode *wrapper = NULL;
56
57 pcmk__assert(patchset != NULL);
58 *patchset = NULL;
59
60 if (msg == NULL) {
61 pcmk__err("CIB diff notification received with no XML");
62 return ENOMSG;
63 }
64
65 if ((pcmk__xe_get_int(msg, PCMK__XA_CIB_RC, &rc) != pcmk_rc_ok)
66 || (rc != pcmk_ok)) {
67
68 pcmk__warn("Ignore failed CIB update: %s " QB_XS " rc=%d",
69 pcmk_strerror(rc), rc);
70 pcmk__log_xml_debug(msg, "failed");
71 return pcmk_legacy2rc(rc);
72 }
73
74 wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
75 *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
76
77 if (*patchset == NULL) {
78 pcmk__err("CIB diff notification received with no patchset");
79 return ENOMSG;
80 }
81 return pcmk_rc_ok;
82 }
83
84 /*!
85 * \brief Create XML for a new (empty) CIB
86 *
87 * \param[in] cib_epoch What to use as \c PCMK_XA_EPOCH CIB attribute
88 *
89 * \return Newly created XML for empty CIB
90 *
91 * \note It is the caller's responsibility to free the result with
92 * \c pcmk__xml_free().
93 */
94 xmlNode *
95 createEmptyCib(int cib_epoch)
96 {
97 xmlNode *cib_root = NULL, *config = NULL;
98
99 cib_root = pcmk__xe_create(NULL, PCMK_XE_CIB);
100 pcmk__xe_set(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
101 pcmk__xe_set(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name());
102
103 pcmk__xe_set_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
104 pcmk__xe_set_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
105 pcmk__xe_set_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
106
107 config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION);
108 pcmk__xe_create(cib_root, PCMK_XE_STATUS);
109
110 pcmk__xe_create(config, PCMK_XE_CRM_CONFIG);
111 pcmk__xe_create(config, PCMK_XE_NODES);
112 pcmk__xe_create(config, PCMK_XE_RESOURCES);
113 pcmk__xe_create(config, PCMK_XE_CONSTRAINTS);
114
115 #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
116 {
117 xmlNode *rsc_defaults = pcmk__xe_create(config, PCMK_XE_RSC_DEFAULTS);
118 xmlNode *meta = pcmk__xe_create(rsc_defaults, PCMK_XE_META_ATTRIBUTES);
119 xmlNode *nvpair = pcmk__xe_create(meta, PCMK_XE_NVPAIR);
120
121 pcmk__xe_set(meta, PCMK_XA_ID, "build-resource-defaults");
122 pcmk__xe_set(nvpair, PCMK_XA_ID,
123 "build-" PCMK_META_RESOURCE_STICKINESS);
124 pcmk__xe_set(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
125 pcmk__xe_set_int(nvpair, PCMK_XA_VALUE,
126 PCMK__RESOURCE_STICKINESS_DEFAULT);
127 }
128 #endif
129 return cib_root;
130 }
131
132 static void
133 read_config(GHashTable *options, xmlNode *current_cib)
134 {
135 crm_time_t *now = NULL;
136 pcmk_rule_input_t rule_input = { 0, };
137 xmlNode *config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG);
138
139 if (config == NULL) {
140 return;
141 }
142
143 now = crm_time_new(NULL);
144 rule_input.now = now;
145
146 pcmk_unpack_nvpair_blocks(config, PCMK_XE_CLUSTER_PROPERTY_SET,
147 PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, &rule_input,
148 options, NULL);
149 crm_time_free(now);
150 }
151
152 static bool
153 cib_acl_enabled(xmlNode *xml, const char *user)
154 {
155 const char *value = NULL;
156 GHashTable *options = NULL;
157 bool rc = false;
158
159 if ((xml == NULL) || !pcmk_acl_required(user)) {
160 return false;
161 }
162
163 options = pcmk__strkey_table(free, free);
164 read_config(options, xml);
165 value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL);
166
167 rc = pcmk__is_true(value);
168 g_hash_table_destroy(options);
169 return rc;
170 }
171
172 /*!
173 * \internal
174 * \brief Get input data from a CIB request
175 *
176 * \param[in] request CIB request XML
177 */
178 static xmlNode *
179 get_op_input(const xmlNode *request)
180 {
181 xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA,
182 NULL, NULL);
183
184 return pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
185 }
186
187 int
188 cib__perform_query(cib__op_fn_t fn, xmlNode *req, xmlNode **current_cib,
189 xmlNode **output)
190 {
191 int rc = pcmk_rc_ok;
192 const char *op = NULL;
193 const char *section = NULL;
194 const char *user = NULL;
195 uint32_t call_options = cib_none;
196 xmlNode *input = NULL;
197
198 xmlNode *cib = NULL;
199 xmlNode *cib_filtered = NULL;
200
201 pcmk__assert((fn != NULL) && (req != NULL)
202 && (current_cib != NULL) && (*current_cib != NULL)
203 && (output != NULL) && (*output == NULL));
204
205 op = pcmk__xe_get(req, PCMK__XA_CIB_OP);
206 section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION);
207 user = pcmk__xe_get(req, PCMK__XA_CIB_USER);
208 pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &call_options, cib_none);
209
210 input = get_op_input(req);
211 cib = *current_cib;
212
213 if (cib_acl_enabled(*current_cib, user)
214 && xml_acl_filtered_copy(user, *current_cib, *current_cib,
215 &cib_filtered)) {
216
217 if (cib_filtered == NULL) {
218 pcmk__debug("Pre-filtered the entire cib");
219 return EACCES;
220 }
221 cib = cib_filtered;
222 pcmk__log_xml_trace(cib, "filtered");
223 }
224
225 pcmk__trace("Processing %s for section '%s', user '%s'", op,
226 pcmk__s(section, "(null)"), pcmk__s(user, "(null)"));
227 pcmk__log_xml_trace(req, "request");
228
229 rc = fn(req, input, &cib, output);
230
231 if (*output == NULL) {
232 // Do nothing
233
234 } else if (cib_filtered == *output) {
235 // Let them have this copy
236 cib_filtered = NULL;
237
238 } else if (*output == *current_cib) {
239 // They already know not to free it
240
241 } else if ((cib_filtered != NULL)
242 && ((*output)->doc == cib_filtered->doc)) {
243 // We're about to free the document of which *output is a part
244 *output = pcmk__xml_copy(NULL, *output);
245
246 } else if ((*output)->doc == (*current_cib)->doc) {
247 // Give them a copy they can free
248 *output = pcmk__xml_copy(NULL, *output);
249 }
250
251 pcmk__xml_free(cib_filtered);
252 return rc;
253 }
254
255 /*!
256 * \internal
257 * \brief Determine whether to perform operations on a scratch copy of the CIB
258 *
259 * \param[in] op CIB operation
260 * \param[in] section CIB section
261 * \param[in] call_options CIB call options
262 *
263 * \return \p true if we should make a copy of the CIB, or \p false otherwise
264 */
265 static bool
266 should_copy_cib(const char *op, const char *section, int call_options)
267 {
268 if (pcmk__is_set(call_options, cib_dryrun)) {
269 // cib_dryrun implies a scratch copy by definition; no side effects
270 return true;
271 }
272
273 if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
274 /* Commit-transaction must make a copy for atomicity. We must revert to
275 * the original CIB if the entire transaction cannot be applied
276 * successfully.
277 */
278 return true;
279 }
280
281 if (pcmk__is_set(call_options, cib_transaction)) {
282 /* If cib_transaction is set, then we're in the process of committing a
283 * transaction. The commit-transaction request already made a scratch
284 * copy, and we're accumulating changes in that copy.
285 */
286 return false;
287 }
288
289 if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
290 /* Copying large CIBs accounts for a huge percentage of our CIB usage,
291 * and this avoids some of it.
292 *
293 * @TODO: Is this safe? See discussion at
294 * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
295 */
296 return false;
297 }
298
299 // Default behavior is to operate on a scratch copy
300 return true;
301 }
302
303 /*!
304 * \internal
305 * \brief Validate that a new CIB's feature set is not newer than ours
306 *
307 * Return an error if the new CIB's feature set is newer than ours.
308 *
309 * \param[in] new_cib Result CIB after performing operation
310 *
311 * \return Standard Pacemaker return code
312 */
313 static int
314 check_new_feature_set(const xmlNode *new_cib)
315 {
316 const char *new_version = pcmk__xe_get(new_cib, PCMK_XA_CRM_FEATURE_SET);
317 int rc = pcmk__check_feature_set(new_version);
318
319 if (rc == pcmk_rc_ok) {
320 return pcmk_rc_ok;
321 }
322
323 pcmk__err("Discarding update with feature set %s greater than our own (%s)",
324 new_version, CRM_FEATURE_SET);
325 return rc;
326 }
327
328 /*!
329 * \internal
330 * \brief Validate that a new CIB has a newer version attribute than an old CIB
331 *
332 * Return an error if the value of the given attribute is higher in the old CIB
333 * than in the new CIB.
334 *
335 * \param[in] attr Name of version attribute to check
336 * \param[in] old_cib \c PCMK_XE_CIB element before performing operation
337 * \param[in] new_cib \c PCMK_XE_CIB element from result of operation
338 * \param[in] request CIB request
339 * \param[in] input Input data for CIB request
340 *
341 * \return Standard Pacemaker return code
342 *
343 * \note \p old_cib only has to contain the top-level \c PCMK_XE_CIB element. It
344 * might not be a full CIB.
345 */
346 static int
347 check_cib_version_attr(const char *attr, const xmlNode *old_cib,
348 const xmlNode *new_cib, const xmlNode *request,
349 const xmlNode *input)
350 {
351 const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
352 int old_version = 0;
353 int new_version = 0;
354
355 pcmk__xe_get_int(old_cib, attr, &old_version);
356 pcmk__xe_get_int(new_cib, attr, &new_version);
357
358 if (old_version < new_version) {
359 return pcmk_rc_ok;
360 }
361
362 if (old_version == new_version) {
363 return pcmk_rc_undetermined;
364 }
365
366 pcmk__err("%s went backwards in %s request: %d -> %d", attr, op,
367 old_version, new_version);
368 pcmk__log_xml_warn(request, "bad-request");
369 pcmk__log_xml_warn(input, "bad-input");
370
371 return pcmk_rc_old_data;
372 }
373
374 /*!
375 * \internal
376 * \brief Validate that a new CIB has newer versions than an old CIB
377 *
378 * Return an error if:
379 * - \c PCMK_XA_ADMIN_EPOCH is newer in the old CIB than in the new CIB; or
380 * - The \c PCMK_XA_ADMIN_EPOCH attributes are equal and \c PCMK_XA_EPOCH is
381 * newer in the old CIB than in the new CIB.
382 *
383 * \param[in] old_cib \c PCMK_XE_CIB element before performing operation
384 * \param[in] new_cib \c PCMK_XE_CIB element from result of operation
385 * \param[in] request CIB request
386 * \param[in] input Input data for CIB request
387 *
388 * \return Standard Pacemaker return code
389 *
390 * \note \p old_cib only has to contain the top-level \c PCMK_XE_CIB element. It
391 * might not be a full CIB.
392 */
393 static int
394 check_cib_versions(const xmlNode *old_cib, const xmlNode *new_cib,
395 const xmlNode *request, const xmlNode *input)
396 {
397 int rc = check_cib_version_attr(PCMK_XA_ADMIN_EPOCH, old_cib, new_cib,
398 request, input);
399
400 if (rc != pcmk_rc_undetermined) {
401 return rc;
402 }
403
404 // @TODO Why aren't we checking PCMK_XA_NUM_UPDATES if epochs are equal?
405 rc = check_cib_version_attr(PCMK_XA_EPOCH, old_cib, new_cib, request,
406 input);
407 if (rc == pcmk_rc_undetermined) {
408 rc = pcmk_rc_ok;
409 }
410
411 return rc;
412 }
413
414 /*!
415 * \internal
416 * \brief Set values for update origin host, client, and user in new CIB
417 *
418 * \param[in,out] new_cib Result CIB after performing operation
419 * \param[in] request CIB request (source of origin info)
420 *
421 * \return Standard Pacemaker return code
422 */
423 static int
424 set_update_origin(xmlNode *new_cib, const xmlNode *request)
425 {
426 const char *origin = pcmk__xe_get(request, PCMK__XA_SRC);
427 const char *client = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME);
428 const char *user = pcmk__xe_get(request, PCMK__XA_CIB_USER);
429 const char *schema = pcmk__xe_get(new_cib, PCMK_XA_VALIDATE_WITH);
430
431 if (schema == NULL) {
432 return pcmk_rc_cib_corrupt;
433 }
434
435 pcmk__xe_add_last_written(new_cib);
436 pcmk__warn_if_schema_deprecated(schema);
437
438 // pacemaker-1.2 is the earliest schema version that allow these attributes
439 if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") < 0) {
440 return pcmk_rc_ok;
441 }
442
443 if (origin != NULL) {
444 pcmk__xe_set(new_cib, PCMK_XA_UPDATE_ORIGIN, origin);
445 } else {
446 pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_ORIGIN);
447 }
448
449 if (client != NULL) {
450 pcmk__xe_set(new_cib, PCMK_XA_UPDATE_CLIENT, client);
451 } else {
452 pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_CLIENT);
453 }
454
455 if (user != NULL) {
456 pcmk__xe_set(new_cib, PCMK_XA_UPDATE_USER, user);
457 } else {
458 pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_USER);
459 }
460
461 return pcmk_rc_ok;
462 }
463
464 int
465 cib_perform_op(enum cib_variant variant, cib__op_fn_t fn, xmlNode *req,
466 bool *config_changed, xmlNode **cib, xmlNode **diff,
467 xmlNode **output)
468 {
469 int rc = pcmk_rc_ok;
470
471 const char *op = NULL;
472 const char *section = NULL;
473 const char *user = NULL;
474 uint32_t call_options = cib_none;
475 xmlNode *input = NULL;
476 bool enable_acl = false;
477 bool manage_version = true;
478
479 /* PCMK_XE_CIB element containing version numbers from before the operation.
480 * This may or may not point to a full CIB XML tree. Do not free, as this
481 * will be used as an alias for another pointer.
482 */
483 xmlNode *old_versions = NULL;
484
485 xmlNode *top = NULL;
486
487 pcmk__assert((fn != NULL) && (req != NULL)
488 && (config_changed != NULL) && (!*config_changed)
489 && (cib != NULL) && (*cib != NULL)
490 && (diff != NULL) && (*diff == NULL)
491 && (output != NULL) && (*output == NULL));
492
493 op = pcmk__xe_get(req, PCMK__XA_CIB_OP);
494 section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION);
495 user = pcmk__xe_get(req, PCMK__XA_CIB_USER);
496 pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &call_options, cib_none);
497
498 input = get_op_input(req);
499 enable_acl = cib_acl_enabled(*cib, user);
500
501 pcmk__trace("Processing %s for section '%s', user '%s'", op,
502 pcmk__s(section, "(null)"), pcmk__s(user, "(null)"));
503 pcmk__log_xml_trace(req, "request");
504
505 if (!should_copy_cib(op, section, call_options)) {
506 // Make a copy of the top-level element to store version details
507 top = pcmk__xe_create(NULL, (const char *) (*cib)->name);
508 pcmk__xe_copy_attrs(top, *cib, pcmk__xaf_none);
509 old_versions = top;
510
511 } else {
512 old_versions = *cib;
513 *cib = pcmk__xml_copy(NULL, *cib);
514 }
515
516 if (pcmk__is_set(call_options, cib_transaction)) {
517 /* This is a request within a transaction, so don't commit changes yet.
518 * On success, we'll commit when we finish performing the whole commit-
519 * transaction operation. The tracking flag is already set, and ACLs
520 * have already been unpacked and applied.
521 */
522 pcmk__assert(pcmk__xml_doc_all_flags_set((*cib)->doc,
523 pcmk__xf_tracking));
524
525 } else {
526 pcmk__xml_commit_changes((*cib)->doc);
527 pcmk__xml_doc_set_flags((*cib)->doc, pcmk__xf_tracking);
528 if (enable_acl) {
529 pcmk__enable_acls((*cib)->doc, (*cib)->doc, user);
530 }
531 }
532
533 rc = fn(req, input, cib, output);
534
535 // Tracking flag should still be set after operation
536 pcmk__assert(pcmk__xml_doc_all_flags_set((*cib)->doc, pcmk__xf_tracking));
537
538 // Allow ourselves to make any additional necessary changes
539 xml_acl_disable(*cib);
540
541 if (rc != pcmk_rc_ok) {
542 goto done;
543 }
544
545 if (*cib == NULL) {
546 rc = EINVAL;
547 goto done;
548 }
549
550 if (xml_acl_denied(*cib)) {
551 pcmk__trace("ACL rejected part or all of the proposed changes");
552 rc = EACCES;
553 goto done;
554 }
555
556 /* If the CIB is from a file, we don't need to check that the feature set is
557 * supported. All we care about in that case is the schema version, which
558 * is checked elsewhere.
559 */
560 if (variant != cib_file) {
561 rc = check_new_feature_set(*cib);
562 if (rc != pcmk_rc_ok) {
563 goto done;
564 }
565 }
566
567 rc = check_cib_versions(old_versions, *cib, req, input);
568
569 pcmk__strip_xml_text(*cib);
570
571 if (pcmk__xe_attr_is_true(req, PCMK__XA_CIB_UPDATE)) {
572 /* This is a replace operation as a reply to a sync request. Keep
573 * whatever versions are in the received CIB.
574 */
575 manage_version = false;
576 }
577
578 /* If we didn't make a copy, the diff will only be accurate for the
579 * top-level PCMK_XE_CIB element
580 */
581 *diff = xml_create_patchset(0, old_versions, *cib, config_changed,
582 manage_version);
583
584 /* pcmk__xml_commit_changes() resets document private data, so call it even
585 * if there were no changes. If this request is part of a transaction, we'll
586 * commit changes later, as part of the commit-transaction operation.
587 */
588 if (!pcmk__is_set(call_options, cib_transaction)) {
589 pcmk__xml_commit_changes((*cib)->doc);
590 }
591
592 if (*diff == NULL) {
593 goto done;
594 }
595
596 /* If this request is within a transaction, *diff is the cumulative set of
597 * changes so far, since we don't commit changes between requests.
598 */
599 pcmk__log_xml_patchset(LOG_INFO, *diff);
600
601 /* *cib must not be modified after this point, except for the attributes for
602 * which pcmk__xa_filterable() returns true
603 */
604
605 if (*config_changed && !pcmk__is_set(call_options, cib_no_mtime)) {
606 rc = set_update_origin(*cib, req);
607 if (rc != pcmk_rc_ok) {
608 goto done;
609 }
610 }
611
612 // Skip validation for status-only updates, since we allow anything there
613 if ((rc == pcmk_rc_ok)
614 && !pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)
615 && !pcmk__configured_schema_validates(*cib)) {
616
617 rc = pcmk_rc_schema_validation;
618 }
619
620 done:
621 /* @TODO This may not work correctly when !should_copy_cib(), since we don't
622 * keep the original CIB.
623 */
624 if ((rc != pcmk_rc_ok) && cib_acl_enabled(old_versions, user)) {
625 xmlNode *saved_cib = *cib;
626
627 if (xml_acl_filtered_copy(user, old_versions, *cib, cib)) {
628 if (*cib == NULL) {
629 pcmk__debug("Pre-filtered the entire cib result");
630 }
631 pcmk__xml_free(saved_cib);
632 }
633 }
634
635 pcmk__xml_free(top);
636 pcmk__trace("Done");
637 return rc;
638 }
639
640 int
641 cib__create_op(cib_t *cib, const char *op, const char *host,
642 const char *section, xmlNode *data, int call_options,
643 const char *user_name, const char *client_name,
644 xmlNode **op_msg)
645 {
646 CRM_CHECK((cib != NULL) && (op_msg != NULL), return EPROTO);
647
648 *op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
649
650 cib->call_id++;
651 if (cib->call_id < 1) {
652 cib->call_id = 1;
653 }
654
655 pcmk__xe_set(*op_msg, PCMK__XA_T, PCMK__VALUE_CIB);
656 pcmk__xe_set(*op_msg, PCMK__XA_CIB_OP, op);
657 pcmk__xe_set(*op_msg, PCMK__XA_CIB_HOST, host);
658 pcmk__xe_set(*op_msg, PCMK__XA_CIB_SECTION, section);
659 pcmk__xe_set(*op_msg, PCMK__XA_CIB_USER, user_name);
660 pcmk__xe_set(*op_msg, PCMK__XA_CIB_CLIENTNAME, client_name);
661 pcmk__xe_set_int(*op_msg, PCMK__XA_CIB_CALLID, cib->call_id);
662
663 pcmk__trace("Sending call options: %.8lx, %d", (long) call_options,
664 call_options);
665 pcmk__xe_set_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options);
666
667 if (data != NULL) {
668 xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA);
669
670 pcmk__xml_copy(wrapper, data);
671 }
672
673 return pcmk_rc_ok;
674 }
675
676 /*!
677 * \internal
678 * \brief Check whether a CIB request is supported in a transaction
679 *
680 * \param[in] request CIB request
681 *
682 * \return Standard Pacemaker return code
683 */
684 static int
685 validate_transaction_request(const xmlNode *request)
686 {
687 const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
688 const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST);
689 const cib__operation_t *operation = NULL;
690 int rc = cib__get_operation(op, &operation);
691
692 if (rc != pcmk_rc_ok) {
693 // cib__get_operation() logs error
694 return rc;
695 }
696
697 if (!pcmk__is_set(operation->flags, cib__op_attr_transaction)) {
698 pcmk__err("Operation %s is not supported in CIB transactions", op);
699 return EOPNOTSUPP;
700 }
701
702 if (host != NULL) {
703 pcmk__err("Operation targeting a specific node (%s) is not supported "
704 "in a CIB transaction",
705 host);
706 return EOPNOTSUPP;
707 }
708 return pcmk_rc_ok;
709 }
710
711 /*!
712 * \internal
713 * \brief Append a CIB request to a CIB transaction
714 *
715 * \param[in,out] cib CIB client whose transaction to extend
716 * \param[in,out] request Request to add to transaction
717 *
718 * \return Standard Pacemaker return code
719 */
720 int
721 cib__extend_transaction(cib_t *cib, xmlNode *request)
722 {
723 const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
724 const char *client_id = NULL;
725 int rc = pcmk_rc_ok;
726
727 pcmk__assert((cib != NULL) && (request != NULL));
728
729 rc = validate_transaction_request(request);
730
731 if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
732 rc = pcmk_rc_no_transaction;
733 }
734
735 if (rc == pcmk_rc_ok) {
736 pcmk__xml_copy(cib->transaction, request);
737 return pcmk_rc_ok;
738 }
739
740 cib->cmds->client_id(cib, NULL, &client_id);
741
742 pcmk__err("Failed to add '%s' operation to transaction for client %s: %s",
743 op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
744 pcmk__log_xml_info(request, "failed");
745
746 return rc;
747 }
748
749 void
750 cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
751 {
752 xmlNode *output = NULL;
753 cib_callback_client_t *blob = NULL;
754
755 if (msg != NULL) {
756 xmlNode *wrapper = NULL;
757
758 pcmk__xe_get_int(msg, PCMK__XA_CIB_RC, &rc);
759 pcmk__xe_get_int(msg, PCMK__XA_CIB_CALLID, &call_id);
760 wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL);
761 output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
762 }
763
764 blob = cib__lookup_id(call_id);
765
766 if (blob == NULL) {
767 pcmk__trace("No callback found for call %d", call_id);
768 }
769
770 if (cib == NULL) {
771 pcmk__debug("No cib object supplied");
772 }
773
774 if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
775 pcmk__trace("Invoking callback %s for call %d",
776 pcmk__s(blob->id, "without ID"), call_id);
777 blob->callback(msg, call_id, rc, output, blob->user_data);
778
779 } else if ((cib != NULL) && (rc != pcmk_ok)) {
780 pcmk__warn("CIB command failed: %s", pcmk_strerror(rc));
781 pcmk__log_xml_debug(msg, "Failed CIB Update");
782 }
783
784 /* This may free user_data, so do it after the callback */
785 if (blob) {
786 remove_cib_op_callback(call_id, FALSE);
787 }
788
789 pcmk__trace("OP callback activated for %d", call_id);
790 }
791
792 void
793 cib_native_notify(gpointer data, gpointer user_data)
794 {
795 xmlNode *msg = user_data;
796 cib_notify_client_t *entry = data;
797 const char *event = NULL;
798
799 if (msg == NULL) {
800 pcmk__warn("Skipping callback - NULL message");
801 return;
802 }
803
804 event = pcmk__xe_get(msg, PCMK__XA_SUBT);
805
806 if (entry == NULL) {
807 pcmk__warn("Skipping callback - NULL callback client");
808 return;
809
810 } else if (entry->callback == NULL) {
811 pcmk__warn("Skipping callback - NULL callback");
812 return;
813
814 } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
815 pcmk__trace("Skipping callback - event mismatch %p/%s vs. %s", entry,
816 entry->event, event);
817 return;
818 }
819
820 pcmk__trace("Invoking callback for %p/%s event...", entry, event);
821 entry->callback(event, msg);
822 pcmk__trace("Callback invoked...");
823 }
824
825 int
826 cib_internal_op(cib_t * cib, const char *op, const char *host,
827 const char *section, xmlNode * data,
828 xmlNode ** output_data, int call_options, const char *user_name)
829 {
830 /* Note: *output_data gets set only for create and query requests. There are
831 * a lot of opportunities to clean up, clarify, check/enforce things, etc.
832 */
833 int (*delegate)(cib_t *cib, const char *op, const char *host,
834 const char *section, xmlNode *data, xmlNode **output_data,
835 int call_options, const char *user_name) = NULL;
836
837 if (cib == NULL) {
838 return -EINVAL;
839 }
840
841 delegate = cib->delegate_fn;
842 if (delegate == NULL) {
843 return -EPROTONOSUPPORT;
844 }
845 if (user_name == NULL) {
846 user_name = getenv("CIB_user");
847 }
848 return delegate(cib, op, host, section, data, output_data, call_options, user_name);
849 }
850
851 /*!
852 * \brief Apply a CIB update patch to a given CIB
853 *
854 * \param[in] event CIB update patch
855 * \param[in] input CIB to patch
856 * \param[out] output Resulting CIB after patch
857 * \param[in] level Log the patch at this log level (unless LOG_CRIT)
858 *
859 * \return Legacy Pacemaker return code
860 * \note sbd calls this function
861 */
862 int
863 cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
864 int level)
865 {
866 int rc = pcmk_err_generic;
867
868 xmlNode *wrapper = NULL;
869 xmlNode *diff = NULL;
870
|
(1) Event path: |
Condition "event != NULL", taking true branch. |
|
(2) Event path: |
Condition "input != NULL", taking true branch. |
|
(3) Event path: |
Condition "output != NULL", taking true branch. |
871 pcmk__assert((event != NULL) && (input != NULL) && (output != NULL));
872
873 pcmk__xe_get_int(event, PCMK__XA_CIB_RC, &rc);
874 wrapper = pcmk__xe_first_child(event, PCMK__XE_CIB_UPDATE_RESULT, NULL,
875 NULL);
876 diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
877
|
(4) Event path: |
Condition "rc < 0", taking false branch. |
|
(5) Event path: |
Condition "diff == NULL", taking false branch. |
878 if ((rc < pcmk_ok) || (diff == NULL)) {
879 return rc;
880 }
881
|
(6) Event path: |
Condition "level > 2", taking true branch. |
882 if (level > LOG_CRIT) {
|
(7) Event path: |
Switch case value "255". |
|
(8) Event path: |
Breaking from switch. |
883 pcmk__log_xml_patchset(level, diff);
884 }
885
|
(9) Event path: |
Condition "input == NULL", taking false branch. |
886 if (input == NULL) {
887 return rc;
888 }
889
|
(10) Event path: |
Condition "*output != input", taking true branch. |
890 if (*output != input) {
891 pcmk__xml_free(*output);
892 *output = pcmk__xml_copy(NULL, input);
893 }
894
895 rc = cib__process_apply_patch(event, diff, output, NULL);
896 rc = pcmk_rc2legacy(rc);
|
(11) Event path: |
Condition "rc == 0", taking false branch. |
897 if (rc == pcmk_ok) {
898 return pcmk_ok;
899 }
900
|
(12) Event path: |
Switch case default. |
|
(13) Event path: |
Condition "trace_cs == NULL", taking true branch. |
|
(14) Event path: |
Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch. |
|
(15) Event path: |
Breaking from switch. |
901 pcmk__debug("Update didn't apply: %s (%d)", pcmk_strerror(rc), rc);
902
|
(16) Event path: |
Condition "rc == -205", taking false branch. |
903 if (rc == -pcmk_err_old_data) {
904 // Mask this error, since it means we already have the supplied update
905 return pcmk_ok;
906 }
907
908 // Some other error
|
CID (unavailable; MK=46f1f22811675aa3ef01982fa82bf4d1) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(17) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(18) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
909 g_clear_pointer(output, pcmk__xml_free);
910 return rc;
911 }
912
913 #define log_signon_query_err(out, fmt, args...) do { \
914 if (out != NULL) { \
915 out->err(out, fmt, ##args); \
916 } else { \
917 pcmk__err(fmt, ##args); \
918 } \
919 } while (0)
920
921 int
922 cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
923 {
924 int rc = pcmk_rc_ok;
925 cib_t *cib_conn = NULL;
926
927 pcmk__assert(cib_object != NULL);
928
929 if (cib == NULL) {
930 cib_conn = cib_new();
931 } else {
932 if (*cib == NULL) {
933 *cib = cib_new();
934 }
935 cib_conn = *cib;
936 }
937
938 if (cib_conn == NULL) {
939 return ENOMEM;
940 }
941
942 if (cib_conn->state == cib_disconnected) {
943 rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
944 rc = pcmk_legacy2rc(rc);
945 }
946
947 if (rc != pcmk_rc_ok) {
948 log_signon_query_err(out, "Could not connect to the CIB: %s",
949 pcmk_rc_str(rc));
950 goto done;
951 }
952
953 if (out != NULL) {
954 out->transient(out, "Querying CIB...");
955 }
956 rc = cib_conn->cmds->query(cib_conn, NULL, cib_object, cib_sync_call);
957 rc = pcmk_legacy2rc(rc);
958
959 if (rc != pcmk_rc_ok) {
960 log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
961 }
962
963 done:
964 if (cib == NULL) {
965 cib__clean_up_connection(&cib_conn);
966 }
967
968 if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
969 return pcmk_rc_no_input;
970 }
971 return rc;
972 }
973
974 /*!
975 * \internal
976 * \brief Create a new CIB connection object and connect to the CIB API
977 *
978 * This function attempts to connect up to 5 times.
979 *
980 * \param[out] cib Where to store CIB connection object
981 *
982 * \return Standard Pacemaker return code
983 *
984 * \note The caller is responsible for signing off and freeing the newly
985 * allocated CIB connection object using the \c signoff() method and
986 * \c cib_delete().
987 */
988 int
989 cib__create_signon(cib_t **cib)
990 {
991 static const int attempts = 5;
992 int rc = pcmk_rc_ok;
993
994 pcmk__assert((cib != NULL) && (*cib == NULL));
995
996 *cib = cib_new();
997 if (*cib == NULL) {
998 return ENOMEM;
999 }
1000
1001 pcmk__trace("Attempting connection to CIB API (up to %d time%s)", attempts,
1002 pcmk__plural_s(attempts));
1003
1004 for (int remaining = attempts - 1; remaining >= 0; --remaining) {
1005 rc = (*cib)->cmds->signon(*cib, crm_system_name, cib_command);
1006
1007 if ((rc == pcmk_ok)
1008 || (remaining == 0)
1009 || ((errno != EAGAIN) && (errno != EALREADY))) {
1010 break;
1011 }
1012
1013 // Retry after soft error (interrupted by signal, etc.)
1014 pcmk__sleep_ms((attempts - remaining) * 500);
1015 pcmk__debug("Re-attempting connection to CIB manager (%d attempt%s "
1016 "remaining)",
1017 remaining, pcmk__plural_s(remaining));
1018 }
1019
1020 rc = pcmk_legacy2rc(rc);
1021 if (rc != pcmk_rc_ok) {
1022 cib__clean_up_connection(cib);
1023 }
1024
1025 return rc;
1026 }
1027
1028 int
1029 cib__clean_up_connection(cib_t **cib)
1030 {
1031 int rc;
1032
1033 if (*cib == NULL) {
1034 return pcmk_rc_ok;
1035 }
1036
1037 rc = (*cib)->cmds->signoff(*cib);
1038 cib_delete(*cib);
1039 *cib = NULL;
1040 return pcmk_legacy2rc(rc);
1041 }
1042