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