Line data Source code
1 : /*
2 : * Original copyright 2004 International Business Machines
3 : * Later changes copyright 2008-2024 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 <stdlib.h>
13 : #include <stdio.h>
14 : #include <stdarg.h>
15 : #include <string.h>
16 : #include <sys/utsname.h>
17 :
18 : #include <glib.h>
19 :
20 : #include <crm/crm.h>
21 : #include <crm/cib/internal.h>
22 : #include <crm/common/cib_internal.h>
23 : #include <crm/common/xml.h>
24 : #include <crm/common/xml_internal.h>
25 : #include <crm/pengine/rules.h>
26 :
27 : gboolean
28 0 : cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
29 : {
30 0 : *epoch = -1;
31 0 : *updates = -1;
32 0 : *admin_epoch = -1;
33 :
34 0 : if (cib == NULL) {
35 0 : return FALSE;
36 :
37 : } else {
38 0 : crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
39 0 : crm_element_value_int(cib, PCMK_XA_NUM_UPDATES, updates);
40 0 : crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
41 : }
42 0 : return TRUE;
43 : }
44 :
45 : gboolean
46 0 : cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
47 : int *_admin_epoch, int *_epoch, int *_updates)
48 : {
49 0 : int add[] = { 0, 0, 0 };
50 0 : int del[] = { 0, 0, 0 };
51 :
52 0 : xml_patch_versions(diff, add, del);
53 :
54 0 : *admin_epoch = add[0];
55 0 : *epoch = add[1];
56 0 : *updates = add[2];
57 :
58 0 : *_admin_epoch = del[0];
59 0 : *_epoch = del[1];
60 0 : *_updates = del[2];
61 :
62 0 : return TRUE;
63 : }
64 :
65 : /*!
66 : * \internal
67 : * \brief Get the XML patchset from a CIB diff notification
68 : *
69 : * \param[in] msg CIB diff notification
70 : * \param[out] patchset Where to store XML patchset
71 : *
72 : * \return Standard Pacemaker return code
73 : */
74 : int
75 0 : cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
76 : {
77 0 : int rc = pcmk_err_generic;
78 0 : xmlNode *wrapper = NULL;
79 :
80 0 : CRM_ASSERT(patchset != NULL);
81 0 : *patchset = NULL;
82 :
83 0 : if (msg == NULL) {
84 0 : crm_err("CIB diff notification received with no XML");
85 0 : return ENOMSG;
86 : }
87 :
88 0 : if ((crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc) != 0)
89 0 : || (rc != pcmk_ok)) {
90 :
91 0 : crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
92 : pcmk_strerror(rc), rc);
93 0 : crm_log_xml_debug(msg, "failed");
94 0 : return pcmk_legacy2rc(rc);
95 : }
96 :
97 0 : wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
98 0 : *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
99 :
100 0 : if (*patchset == NULL) {
101 0 : crm_err("CIB diff notification received with no patchset");
102 0 : return ENOMSG;
103 : }
104 0 : return pcmk_rc_ok;
105 : }
106 :
107 : #define XPATH_DIFF_V1 "//" PCMK__XE_CIB_UPDATE_RESULT "//" PCMK__XE_DIFF_ADDED
108 :
109 : /*!
110 : * \internal
111 : * \brief Check whether a given CIB element was modified in a CIB patchset (v1)
112 : *
113 : * \param[in] patchset CIB XML patchset
114 : * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
115 : * to \c PCMK_XE_CIB)
116 : *
117 : * \return \c true if \p element was modified, or \c false otherwise
118 : */
119 : static bool
120 0 : element_in_patchset_v1(const xmlNode *patchset, const char *element)
121 : {
122 0 : char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
123 : pcmk__s(element, PCMK_XE_CIB));
124 0 : xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
125 :
126 0 : free(xpath);
127 :
128 0 : if (xpath_obj == NULL) {
129 0 : return false;
130 : }
131 0 : freeXpathObject(xpath_obj);
132 0 : return true;
133 : }
134 :
135 : /*!
136 : * \internal
137 : * \brief Check whether a given CIB element was modified in a CIB patchset (v2)
138 : *
139 : * \param[in] patchset CIB XML patchset
140 : * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
141 : * to \c PCMK_XE_CIB). Supported values include any CIB
142 : * element supported by \c pcmk__cib_abs_xpath_for().
143 : *
144 : * \return \c true if \p element was modified, or \c false otherwise
145 : */
146 : static bool
147 0 : element_in_patchset_v2(const xmlNode *patchset, const char *element)
148 : {
149 0 : const char *element_xpath = pcmk__cib_abs_xpath_for(element);
150 0 : const char *parent_xpath = pcmk_cib_parent_name_for(element);
151 0 : char *element_regex = NULL;
152 0 : bool rc = false;
153 :
154 0 : CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
155 :
156 : // Matches if and only if element_xpath is part of a changed path
157 0 : element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
158 :
159 0 : for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
160 : NULL, NULL);
161 0 : change != NULL; change = pcmk__xe_next_same(change)) {
162 :
163 0 : const char *op = crm_element_value(change, PCMK__XA_CIB_OP);
164 0 : const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
165 :
166 0 : if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
167 : // Change to an existing element
168 0 : rc = true;
169 0 : break;
170 : }
171 :
172 0 : if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
173 0 : && pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
174 0 : && pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
175 : element)) {
176 :
177 : // Newly added element
178 0 : rc = true;
179 0 : break;
180 : }
181 : }
182 :
183 0 : free(element_regex);
184 0 : return rc;
185 : }
186 :
187 : /*!
188 : * \internal
189 : * \brief Check whether a given CIB element was modified in a CIB patchset
190 : *
191 : * \param[in] patchset CIB XML patchset
192 : * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
193 : * to \c PCMK_XE_CIB). Supported values include any CIB
194 : * element supported by \c pcmk__cib_abs_xpath_for().
195 : *
196 : * \return \c true if \p element was modified, or \c false otherwise
197 : */
198 : bool
199 0 : cib__element_in_patchset(const xmlNode *patchset, const char *element)
200 : {
201 0 : int format = 1;
202 :
203 0 : CRM_ASSERT(patchset != NULL);
204 :
205 0 : crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
206 0 : switch (format) {
207 0 : case 1:
208 0 : return element_in_patchset_v1(patchset, element);
209 :
210 0 : case 2:
211 0 : return element_in_patchset_v2(patchset, element);
212 :
213 0 : default:
214 0 : crm_warn("Unknown patch format: %d", format);
215 0 : return false;
216 : }
217 : }
218 :
219 : /*!
220 : * \brief Create XML for a new (empty) CIB
221 : *
222 : * \param[in] cib_epoch What to use as \c PCMK_XA_EPOCH CIB attribute
223 : *
224 : * \return Newly created XML for empty CIB
225 : * \note It is the caller's responsibility to free the result with free_xml().
226 : */
227 : xmlNode *
228 0 : createEmptyCib(int cib_epoch)
229 : {
230 0 : xmlNode *cib_root = NULL, *config = NULL;
231 :
232 0 : cib_root = pcmk__xe_create(NULL, PCMK_XE_CIB);
233 0 : crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
234 0 : crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name());
235 :
236 0 : crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
237 0 : crm_xml_add_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
238 0 : crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
239 :
240 0 : config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION);
241 0 : pcmk__xe_create(cib_root, PCMK_XE_STATUS);
242 :
243 0 : pcmk__xe_create(config, PCMK_XE_CRM_CONFIG);
244 0 : pcmk__xe_create(config, PCMK_XE_NODES);
245 0 : pcmk__xe_create(config, PCMK_XE_RESOURCES);
246 0 : pcmk__xe_create(config, PCMK_XE_CONSTRAINTS);
247 :
248 : #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
249 : {
250 : xmlNode *rsc_defaults = pcmk__xe_create(config, PCMK_XE_RSC_DEFAULTS);
251 : xmlNode *meta = pcmk__xe_create(rsc_defaults, PCMK_XE_META_ATTRIBUTES);
252 : xmlNode *nvpair = pcmk__xe_create(meta, PCMK_XE_NVPAIR);
253 :
254 : crm_xml_add(meta, PCMK_XA_ID, "build-resource-defaults");
255 : crm_xml_add(nvpair, PCMK_XA_ID, "build-" PCMK_META_RESOURCE_STICKINESS);
256 : crm_xml_add(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
257 : crm_xml_add_int(nvpair, PCMK_XA_VALUE,
258 : PCMK__RESOURCE_STICKINESS_DEFAULT);
259 : }
260 : #endif
261 0 : return cib_root;
262 : }
263 :
264 : static bool
265 0 : cib_acl_enabled(xmlNode *xml, const char *user)
266 : {
267 0 : bool rc = FALSE;
268 :
269 0 : if(pcmk_acl_required(user)) {
270 0 : const char *value = NULL;
271 0 : GHashTable *options = pcmk__strkey_table(free, free);
272 :
273 0 : cib_read_config(options, xml);
274 0 : value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL);
275 0 : rc = crm_is_true(value);
276 0 : g_hash_table_destroy(options);
277 : }
278 :
279 0 : crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
280 0 : return rc;
281 : }
282 :
283 : /*!
284 : * \internal
285 : * \brief Determine whether to perform operations on a scratch copy of the CIB
286 : *
287 : * \param[in] op CIB operation
288 : * \param[in] section CIB section
289 : * \param[in] call_options CIB call options
290 : *
291 : * \return \p true if we should make a copy of the CIB, or \p false otherwise
292 : */
293 : static bool
294 0 : should_copy_cib(const char *op, const char *section, int call_options)
295 : {
296 0 : if (pcmk_is_set(call_options, cib_dryrun)) {
297 : // cib_dryrun implies a scratch copy by definition; no side effects
298 0 : return true;
299 : }
300 :
301 0 : if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
302 : /* Commit-transaction must make a copy for atomicity. We must revert to
303 : * the original CIB if the entire transaction cannot be applied
304 : * successfully.
305 : */
306 0 : return true;
307 : }
308 :
309 0 : if (pcmk_is_set(call_options, cib_transaction)) {
310 : /* If cib_transaction is set, then we're in the process of committing a
311 : * transaction. The commit-transaction request already made a scratch
312 : * copy, and we're accumulating changes in that copy.
313 : */
314 0 : return false;
315 : }
316 :
317 0 : if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
318 : /* Copying large CIBs accounts for a huge percentage of our CIB usage,
319 : * and this avoids some of it.
320 : *
321 : * @TODO: Is this safe? See discussion at
322 : * https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
323 : */
324 0 : return false;
325 : }
326 :
327 : // Default behavior is to operate on a scratch copy
328 0 : return true;
329 : }
330 :
331 : int
332 0 : cib_perform_op(cib_t *cib, const char *op, int call_options, cib__op_fn_t fn,
333 : bool is_query, const char *section, xmlNode *req, xmlNode *input,
334 : bool manage_counters, bool *config_changed, xmlNode **current_cib,
335 : xmlNode **result_cib, xmlNode **diff, xmlNode **output)
336 : {
337 0 : int rc = pcmk_ok;
338 0 : bool check_schema = true;
339 0 : bool make_copy = true;
340 0 : xmlNode *top = NULL;
341 0 : xmlNode *scratch = NULL;
342 0 : xmlNode *patchset_cib = NULL;
343 0 : xmlNode *local_diff = NULL;
344 :
345 0 : const char *user = crm_element_value(req, PCMK__XA_CIB_USER);
346 0 : bool with_digest = false;
347 :
348 0 : crm_trace("Begin %s%s%s op",
349 : (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
350 : (is_query? "read-only " : ""), op);
351 :
352 0 : CRM_CHECK(output != NULL, return -ENOMSG);
353 0 : CRM_CHECK(current_cib != NULL, return -ENOMSG);
354 0 : CRM_CHECK(result_cib != NULL, return -ENOMSG);
355 0 : CRM_CHECK(config_changed != NULL, return -ENOMSG);
356 :
357 0 : if(output) {
358 0 : *output = NULL;
359 : }
360 :
361 0 : *result_cib = NULL;
362 0 : *config_changed = false;
363 :
364 0 : if (fn == NULL) {
365 0 : return -EINVAL;
366 : }
367 :
368 0 : if (is_query) {
369 0 : xmlNode *cib_ro = *current_cib;
370 0 : xmlNode *cib_filtered = NULL;
371 :
372 0 : if (cib_acl_enabled(cib_ro, user)
373 0 : && xml_acl_filtered_copy(user, *current_cib, *current_cib,
374 : &cib_filtered)) {
375 :
376 0 : if (cib_filtered == NULL) {
377 0 : crm_debug("Pre-filtered the entire cib");
378 0 : return -EACCES;
379 : }
380 0 : cib_ro = cib_filtered;
381 0 : crm_log_xml_trace(cib_ro, "filtered");
382 : }
383 :
384 0 : rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
385 :
386 0 : if(output == NULL || *output == NULL) {
387 : /* nothing */
388 :
389 0 : } else if(cib_filtered == *output) {
390 0 : cib_filtered = NULL; /* Let them have this copy */
391 :
392 0 : } else if (*output == *current_cib) {
393 : /* They already know not to free it */
394 :
395 0 : } else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
396 : /* We're about to free the document of which *output is a part */
397 0 : *output = pcmk__xml_copy(NULL, *output);
398 :
399 0 : } else if ((*output)->doc == (*current_cib)->doc) {
400 : /* Give them a copy they can free */
401 0 : *output = pcmk__xml_copy(NULL, *output);
402 : }
403 :
404 0 : free_xml(cib_filtered);
405 0 : return rc;
406 : }
407 :
408 0 : make_copy = should_copy_cib(op, section, call_options);
409 :
410 0 : if (!make_copy) {
411 : /* Conditional on v2 patch style */
412 :
413 0 : scratch = *current_cib;
414 :
415 : // Make a copy of the top-level element to store version details
416 0 : top = pcmk__xe_create(NULL, (const char *) scratch->name);
417 0 : pcmk__xe_copy_attrs(top, scratch, pcmk__xaf_none);
418 0 : patchset_cib = top;
419 :
420 0 : xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
421 0 : rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
422 :
423 : /* If scratch points to a new object now (for example, after an erase
424 : * operation), then *current_cib should point to the same object.
425 : */
426 0 : *current_cib = scratch;
427 :
428 : } else {
429 0 : scratch = pcmk__xml_copy(NULL, *current_cib);
430 0 : patchset_cib = *current_cib;
431 :
432 0 : xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
433 0 : rc = (*fn) (op, call_options, section, req, input, *current_cib,
434 : &scratch, output);
435 :
436 0 : if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
437 0 : crm_trace("Inferring changes after %s op", op);
438 0 : xml_track_changes(scratch, user, *current_cib,
439 0 : cib_acl_enabled(*current_cib, user));
440 0 : xml_calculate_changes(*current_cib, scratch);
441 : }
442 0 : CRM_CHECK(*current_cib != scratch, return -EINVAL);
443 : }
444 :
445 0 : xml_acl_disable(scratch); /* Allow the system to make any additional changes */
446 :
447 0 : if (rc == pcmk_ok && scratch == NULL) {
448 0 : rc = -EINVAL;
449 0 : goto done;
450 :
451 0 : } else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
452 0 : crm_trace("ACL rejected part or all of the proposed changes");
453 0 : rc = -EACCES;
454 0 : goto done;
455 :
456 0 : } else if (rc != pcmk_ok) {
457 0 : goto done;
458 : }
459 :
460 : /* If the CIB is from a file, we don't need to check that the feature set is
461 : * supported. All we care about in that case is the schema version, which
462 : * is checked elsewhere.
463 : */
464 0 : if (scratch && (cib == NULL || cib->variant != cib_file)) {
465 0 : const char *new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
466 :
467 0 : rc = pcmk__check_feature_set(new_version);
468 0 : if (rc != pcmk_rc_ok) {
469 0 : pcmk__config_err("Discarding update with feature set '%s' greater than our own '%s'",
470 : new_version, CRM_FEATURE_SET);
471 0 : rc = pcmk_rc2legacy(rc);
472 0 : goto done;
473 : }
474 : }
475 :
476 0 : if (patchset_cib != NULL) {
477 0 : int old = 0;
478 0 : int new = 0;
479 :
480 0 : crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
481 0 : crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
482 :
483 0 : if (old > new) {
484 0 : crm_err("%s went backwards: %d -> %d (Opts: %#x)",
485 : PCMK_XA_ADMIN_EPOCH, old, new, call_options);
486 0 : crm_log_xml_warn(req, "Bad Op");
487 0 : crm_log_xml_warn(input, "Bad Data");
488 0 : rc = -pcmk_err_old_data;
489 :
490 0 : } else if (old == new) {
491 0 : crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
492 0 : crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
493 0 : if (old > new) {
494 0 : crm_err("%s went backwards: %d -> %d (Opts: %#x)",
495 : PCMK_XA_EPOCH, old, new, call_options);
496 0 : crm_log_xml_warn(req, "Bad Op");
497 0 : crm_log_xml_warn(input, "Bad Data");
498 0 : rc = -pcmk_err_old_data;
499 : }
500 : }
501 : }
502 :
503 0 : crm_trace("Massaging CIB contents");
504 0 : pcmk__strip_xml_text(scratch);
505 :
506 0 : if (!make_copy) {
507 : /* At this point, patchset_cib is just the PCMK_XE_CIB tag and its
508 : * properties.
509 : *
510 : * The v1 format would barf on this, but we know the v2 patch
511 : * format only needs it for the top-level version fields
512 : */
513 0 : local_diff = xml_create_patchset(2, patchset_cib, scratch,
514 : config_changed, manage_counters);
515 :
516 : } else {
517 : static time_t expires = 0;
518 0 : time_t tm_now = time(NULL);
519 :
520 0 : if (expires < tm_now) {
521 0 : expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
522 0 : with_digest = true;
523 : }
524 :
525 0 : local_diff = xml_create_patchset(0, patchset_cib, scratch,
526 : config_changed, manage_counters);
527 : }
528 :
529 0 : pcmk__log_xml_changes(LOG_TRACE, scratch);
530 0 : xml_accept_changes(scratch);
531 :
532 0 : if(local_diff) {
533 0 : patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
534 0 : pcmk__log_xml_patchset(LOG_INFO, local_diff);
535 0 : crm_log_xml_trace(local_diff, "raw patch");
536 : }
537 :
538 0 : if (make_copy && (local_diff != NULL)) {
539 : // Original to compare against doesn't exist
540 0 : pcmk__if_tracing(
541 : {
542 : // Validate the calculated patch set
543 : int test_rc = pcmk_ok;
544 : int format = 1;
545 : xmlNode *cib_copy = pcmk__xml_copy(NULL, patchset_cib);
546 :
547 : crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
548 : test_rc = xml_apply_patchset(cib_copy, local_diff,
549 : manage_counters);
550 :
551 : if (test_rc != pcmk_ok) {
552 : save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
553 : save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
554 : save_xml_to_file(scratch, "PatchApply:actual", NULL);
555 : save_xml_to_file(local_diff, "PatchApply:diff", NULL);
556 : crm_err("v%d patchset error, patch failed to apply: %s "
557 : "(%d)",
558 : format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
559 : test_rc);
560 : }
561 : free_xml(cib_copy);
562 : },
563 : {}
564 : );
565 : }
566 :
567 0 : if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
568 : /* Throttle the amount of costly validation we perform due to status updates
569 : * a) we don't really care whats in the status section
570 : * b) we don't validate any of its contents at the moment anyway
571 : */
572 0 : check_schema = false;
573 : }
574 :
575 : /* === scratch must not be modified after this point ===
576 : * Exceptions, anything in:
577 :
578 : static filter_t filter[] = {
579 : { 0, PCMK_XA_CRM_DEBUG_ORIGIN },
580 : { 0, PCMK_XA_CIB_LAST_WRITTEN },
581 : { 0, PCMK_XA_UPDATE_ORIGIN },
582 : { 0, PCMK_XA_UPDATE_CLIENT },
583 : { 0, PCMK_XA_UPDATE_USER },
584 : };
585 : */
586 :
587 0 : if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
588 0 : const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
589 :
590 0 : pcmk__xe_add_last_written(scratch);
591 0 : pcmk__warn_if_schema_deprecated(schema);
592 :
593 : /* Make values of origin, client, and user in scratch match
594 : * the ones in req (if the schema allows the attributes)
595 : */
596 0 : if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") >= 0) {
597 0 : const char *origin = crm_element_value(req, PCMK__XA_SRC);
598 0 : const char *client = crm_element_value(req,
599 : PCMK__XA_CIB_CLIENTNAME);
600 :
601 0 : if (origin != NULL) {
602 0 : crm_xml_add(scratch, PCMK_XA_UPDATE_ORIGIN, origin);
603 : } else {
604 0 : pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_ORIGIN);
605 : }
606 :
607 0 : if (client != NULL) {
608 0 : crm_xml_add(scratch, PCMK_XA_UPDATE_CLIENT, user);
609 : } else {
610 0 : pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_CLIENT);
611 : }
612 :
613 0 : if (user != NULL) {
614 0 : crm_xml_add(scratch, PCMK_XA_UPDATE_USER, user);
615 : } else {
616 0 : pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_USER);
617 : }
618 : }
619 : }
620 :
621 0 : crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
622 0 : if ((rc == pcmk_ok) && check_schema
623 0 : && !pcmk__configured_schema_validates(scratch)) {
624 0 : const char *current_schema = crm_element_value(scratch,
625 : PCMK_XA_VALIDATE_WITH);
626 :
627 0 : crm_warn("Updated CIB does not validate against %s schema",
628 : pcmk__s(current_schema, "unspecified"));
629 0 : rc = -pcmk_err_schema_validation;
630 : }
631 :
632 0 : done:
633 :
634 0 : *result_cib = scratch;
635 :
636 : /* @TODO: This may not work correctly with !make_copy, since we don't
637 : * keep the original CIB.
638 : */
639 0 : if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
640 0 : && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
641 :
642 0 : if (*result_cib == NULL) {
643 0 : crm_debug("Pre-filtered the entire cib result");
644 : }
645 0 : free_xml(scratch);
646 : }
647 :
648 0 : if(diff) {
649 0 : *diff = local_diff;
650 : } else {
651 0 : free_xml(local_diff);
652 : }
653 :
654 0 : free_xml(top);
655 0 : crm_trace("Done");
656 0 : return rc;
657 : }
658 :
659 : int
660 0 : cib__create_op(cib_t *cib, const char *op, const char *host,
661 : const char *section, xmlNode *data, int call_options,
662 : const char *user_name, const char *client_name,
663 : xmlNode **op_msg)
664 : {
665 0 : CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
666 :
667 0 : *op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
668 :
669 0 : cib->call_id++;
670 0 : if (cib->call_id < 1) {
671 0 : cib->call_id = 1;
672 : }
673 :
674 0 : crm_xml_add(*op_msg, PCMK__XA_T, PCMK__VALUE_CIB);
675 0 : crm_xml_add(*op_msg, PCMK__XA_CIB_OP, op);
676 0 : crm_xml_add(*op_msg, PCMK__XA_CIB_HOST, host);
677 0 : crm_xml_add(*op_msg, PCMK__XA_CIB_SECTION, section);
678 0 : crm_xml_add(*op_msg, PCMK__XA_CIB_USER, user_name);
679 0 : crm_xml_add(*op_msg, PCMK__XA_CIB_CLIENTNAME, client_name);
680 0 : crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLID, cib->call_id);
681 :
682 0 : crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
683 0 : crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options);
684 :
685 0 : if (data != NULL) {
686 0 : xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA);
687 :
688 0 : pcmk__xml_copy(wrapper, data);
689 : }
690 :
691 0 : if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
692 0 : CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
693 : free_xml(*op_msg); return -EPROTO);
694 : }
695 0 : return pcmk_ok;
696 : }
697 :
698 : /*!
699 : * \internal
700 : * \brief Check whether a CIB request is supported in a transaction
701 : *
702 : * \param[in] request CIB request
703 : *
704 : * \return Standard Pacemaker return code
705 : */
706 : static int
707 0 : validate_transaction_request(const xmlNode *request)
708 : {
709 0 : const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
710 0 : const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
711 0 : const cib__operation_t *operation = NULL;
712 0 : int rc = cib__get_operation(op, &operation);
713 :
714 0 : if (rc != pcmk_rc_ok) {
715 : // cib__get_operation() logs error
716 0 : return rc;
717 : }
718 :
719 0 : if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
720 0 : crm_err("Operation %s is not supported in CIB transactions", op);
721 0 : return EOPNOTSUPP;
722 : }
723 :
724 0 : if (host != NULL) {
725 0 : crm_err("Operation targeting a specific node (%s) is not supported in "
726 : "a CIB transaction",
727 : host);
728 0 : return EOPNOTSUPP;
729 : }
730 0 : return pcmk_rc_ok;
731 : }
732 :
733 : /*!
734 : * \internal
735 : * \brief Append a CIB request to a CIB transaction
736 : *
737 : * \param[in,out] cib CIB client whose transaction to extend
738 : * \param[in,out] request Request to add to transaction
739 : *
740 : * \return Legacy Pacemaker return code
741 : */
742 : int
743 0 : cib__extend_transaction(cib_t *cib, xmlNode *request)
744 : {
745 0 : int rc = pcmk_rc_ok;
746 :
747 0 : CRM_ASSERT((cib != NULL) && (request != NULL));
748 :
749 0 : rc = validate_transaction_request(request);
750 :
751 0 : if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
752 0 : rc = pcmk_rc_no_transaction;
753 : }
754 :
755 0 : if (rc == pcmk_rc_ok) {
756 0 : pcmk__xml_copy(cib->transaction, request);
757 :
758 : } else {
759 0 : const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
760 0 : const char *client_id = NULL;
761 :
762 0 : cib->cmds->client_id(cib, NULL, &client_id);
763 0 : crm_err("Failed to add '%s' operation to transaction for client %s: %s",
764 : op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
765 0 : crm_log_xml_info(request, "failed");
766 : }
767 0 : return pcmk_rc2legacy(rc);
768 : }
769 :
770 : void
771 0 : cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
772 : {
773 0 : xmlNode *output = NULL;
774 0 : cib_callback_client_t *blob = NULL;
775 :
776 0 : if (msg != NULL) {
777 0 : xmlNode *wrapper = NULL;
778 :
779 0 : crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
780 0 : crm_element_value_int(msg, PCMK__XA_CIB_CALLID, &call_id);
781 0 : wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL);
782 0 : output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
783 : }
784 :
785 0 : blob = cib__lookup_id(call_id);
786 :
787 0 : if (blob == NULL) {
788 0 : crm_trace("No callback found for call %d", call_id);
789 : }
790 :
791 0 : if (cib == NULL) {
792 0 : crm_debug("No cib object supplied");
793 : }
794 :
795 0 : if (rc == -pcmk_err_diff_resync) {
796 : /* This is an internal value that clients do not and should not care about */
797 0 : rc = pcmk_ok;
798 : }
799 :
800 0 : if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
801 0 : crm_trace("Invoking callback %s for call %d",
802 : pcmk__s(blob->id, "without ID"), call_id);
803 0 : blob->callback(msg, call_id, rc, output, blob->user_data);
804 :
805 0 : } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
806 0 : crm_warn("CIB command failed: %s", pcmk_strerror(rc));
807 0 : crm_log_xml_debug(msg, "Failed CIB Update");
808 : }
809 :
810 : /* This may free user_data, so do it after the callback */
811 0 : if (blob) {
812 0 : remove_cib_op_callback(call_id, FALSE);
813 : }
814 :
815 0 : if (cib && cib->op_callback != NULL) {
816 0 : crm_trace("Invoking global callback for call %d", call_id);
817 0 : cib->op_callback(msg, call_id, rc, output);
818 : }
819 0 : crm_trace("OP callback activated for %d", call_id);
820 0 : }
821 :
822 : void
823 0 : cib_native_notify(gpointer data, gpointer user_data)
824 : {
825 0 : xmlNode *msg = user_data;
826 0 : cib_notify_client_t *entry = data;
827 0 : const char *event = NULL;
828 :
829 0 : if (msg == NULL) {
830 0 : crm_warn("Skipping callback - NULL message");
831 0 : return;
832 : }
833 :
834 0 : event = crm_element_value(msg, PCMK__XA_SUBT);
835 :
836 0 : if (entry == NULL) {
837 0 : crm_warn("Skipping callback - NULL callback client");
838 0 : return;
839 :
840 0 : } else if (entry->callback == NULL) {
841 0 : crm_warn("Skipping callback - NULL callback");
842 0 : return;
843 :
844 0 : } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
845 0 : crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
846 0 : return;
847 : }
848 :
849 0 : crm_trace("Invoking callback for %p/%s event...", entry, event);
850 0 : entry->callback(event, msg);
851 0 : crm_trace("Callback invoked...");
852 : }
853 :
854 : gboolean
855 0 : cib_read_config(GHashTable * options, xmlNode * current_cib)
856 : {
857 0 : xmlNode *config = NULL;
858 0 : crm_time_t *now = NULL;
859 :
860 0 : if (options == NULL || current_cib == NULL) {
861 0 : return FALSE;
862 : }
863 :
864 0 : now = crm_time_new(NULL);
865 :
866 0 : g_hash_table_remove_all(options);
867 :
868 0 : config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG);
869 0 : if (config) {
870 0 : pe_unpack_nvpairs(current_cib, config, PCMK_XE_CLUSTER_PROPERTY_SET,
871 : NULL, options, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, TRUE,
872 : now, NULL);
873 : }
874 :
875 0 : pcmk__validate_cluster_options(options);
876 :
877 0 : crm_time_free(now);
878 :
879 0 : return TRUE;
880 : }
881 :
882 : int
883 0 : cib_internal_op(cib_t * cib, const char *op, const char *host,
884 : const char *section, xmlNode * data,
885 : xmlNode ** output_data, int call_options, const char *user_name)
886 : {
887 0 : int (*delegate) (cib_t * cib, const char *op, const char *host,
888 : const char *section, xmlNode * data,
889 : xmlNode ** output_data, int call_options, const char *user_name) =
890 0 : cib->delegate_fn;
891 :
892 0 : if(user_name == NULL) {
893 0 : user_name = getenv("CIB_user");
894 : }
895 :
896 0 : return delegate(cib, op, host, section, data, output_data, call_options, user_name);
897 : }
898 :
899 : /*!
900 : * \brief Apply a CIB update patch to a given CIB
901 : *
902 : * \param[in] event CIB update patch
903 : * \param[in] input CIB to patch
904 : * \param[out] output Resulting CIB after patch
905 : * \param[in] level Log the patch at this log level (unless LOG_CRIT)
906 : *
907 : * \return Legacy Pacemaker return code
908 : * \note sbd calls this function
909 : */
910 : int
911 0 : cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
912 : int level)
913 : {
914 0 : int rc = pcmk_err_generic;
915 :
916 0 : xmlNode *wrapper = NULL;
917 0 : xmlNode *diff = NULL;
918 :
919 0 : CRM_ASSERT(event);
920 0 : CRM_ASSERT(input);
921 0 : CRM_ASSERT(output);
922 :
923 0 : crm_element_value_int(event, PCMK__XA_CIB_RC, &rc);
924 0 : wrapper = pcmk__xe_first_child(event, PCMK__XE_CIB_UPDATE_RESULT, NULL,
925 : NULL);
926 0 : diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
927 :
928 0 : if (rc < pcmk_ok || diff == NULL) {
929 0 : return rc;
930 : }
931 :
932 0 : if (level > LOG_CRIT) {
933 0 : pcmk__log_xml_patchset(level, diff);
934 : }
935 :
936 0 : if (input != NULL) {
937 0 : rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
938 : NULL);
939 :
940 0 : if (rc != pcmk_ok) {
941 0 : crm_debug("Update didn't apply: %s (%d) %p",
942 : pcmk_strerror(rc), rc, *output);
943 :
944 0 : if (rc == -pcmk_err_old_data) {
945 0 : crm_trace("Masking error, we already have the supplied update");
946 0 : return pcmk_ok;
947 : }
948 0 : free_xml(*output);
949 0 : *output = NULL;
950 0 : return rc;
951 : }
952 : }
953 0 : return rc;
954 : }
955 :
956 : #define log_signon_query_err(out, fmt, args...) do { \
957 : if (out != NULL) { \
958 : out->err(out, fmt, ##args); \
959 : } else { \
960 : crm_err(fmt, ##args); \
961 : } \
962 : } while (0)
963 :
964 : int
965 0 : cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
966 : {
967 0 : int rc = pcmk_rc_ok;
968 0 : cib_t *cib_conn = NULL;
969 :
970 0 : CRM_ASSERT(cib_object != NULL);
971 :
972 0 : if (cib == NULL) {
973 0 : cib_conn = cib_new();
974 : } else {
975 0 : if (*cib == NULL) {
976 0 : *cib = cib_new();
977 : }
978 0 : cib_conn = *cib;
979 : }
980 :
981 0 : if (cib_conn == NULL) {
982 0 : return ENOMEM;
983 : }
984 :
985 0 : if (cib_conn->state == cib_disconnected) {
986 0 : rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
987 0 : rc = pcmk_legacy2rc(rc);
988 : }
989 :
990 0 : if (rc != pcmk_rc_ok) {
991 0 : log_signon_query_err(out, "Could not connect to the CIB: %s",
992 : pcmk_rc_str(rc));
993 0 : goto done;
994 : }
995 :
996 0 : if (out != NULL) {
997 0 : out->transient(out, "Querying CIB...");
998 : }
999 0 : rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
1000 : cib_scope_local|cib_sync_call);
1001 0 : rc = pcmk_legacy2rc(rc);
1002 :
1003 0 : if (rc != pcmk_rc_ok) {
1004 0 : log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
1005 : }
1006 :
1007 0 : done:
1008 0 : if (cib == NULL) {
1009 0 : cib__clean_up_connection(&cib_conn);
1010 : }
1011 :
1012 0 : if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
1013 0 : return pcmk_rc_no_input;
1014 : }
1015 0 : return rc;
1016 : }
1017 :
1018 : int
1019 0 : cib__clean_up_connection(cib_t **cib)
1020 : {
1021 : int rc;
1022 :
1023 0 : if (*cib == NULL) {
1024 0 : return pcmk_rc_ok;
1025 : }
1026 :
1027 0 : rc = (*cib)->cmds->signoff(*cib);
1028 0 : cib_delete(*cib);
1029 0 : *cib = NULL;
1030 0 : return pcmk_legacy2rc(rc);
1031 : }
1032 :
1033 : // Deprecated functions kept only for backward API compatibility
1034 : // LCOV_EXCL_START
1035 :
1036 : #include <crm/cib/util_compat.h>
1037 :
1038 : xmlNode *
1039 : cib_get_generation(cib_t * cib)
1040 : {
1041 : xmlNode *the_cib = NULL;
1042 : xmlNode *generation = pcmk__xe_create(NULL, PCMK__XE_GENERATION_TUPLE);
1043 :
1044 : cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
1045 : if (the_cib != NULL) {
1046 : pcmk__xe_copy_attrs(generation, the_cib, pcmk__xaf_none);
1047 : free_xml(the_cib);
1048 : }
1049 :
1050 : return generation;
1051 : }
1052 :
1053 : const char *
1054 : get_object_path(const char *object_type)
1055 : {
1056 : return pcmk_cib_xpath_for(object_type);
1057 : }
1058 :
1059 : const char *
1060 : get_object_parent(const char *object_type)
1061 : {
1062 : return pcmk_cib_parent_name_for(object_type);
1063 : }
1064 :
1065 : xmlNode *
1066 : get_object_root(const char *object_type, xmlNode *the_root)
1067 : {
1068 : return pcmk_find_cib_element(the_root, object_type);
1069 : }
1070 :
1071 : const char *
1072 : cib_pref(GHashTable * options, const char *name)
1073 : {
1074 : return pcmk__cluster_option(options, name);
1075 : }
1076 :
1077 : void
1078 : cib_metadata(void)
1079 : {
1080 : pcmk__output_t *out = NULL;
1081 : int rc = pcmk__output_new(&out, "text", NULL, NULL);
1082 :
1083 : if (rc != pcmk_rc_ok) {
1084 : crm_err("Unable to output metadata: %s", pcmk_rc_str(rc));
1085 : return;
1086 : }
1087 :
1088 : pcmk__daemon_metadata(out, "pacemaker-based",
1089 : "Cluster Information Base manager options",
1090 : "Cluster options used by Pacemaker's Cluster "
1091 : "Information Base manager",
1092 : pcmk__opt_based);
1093 :
1094 : out->finish(out, CRM_EX_OK, true, NULL);
1095 : pcmk__output_free(out);
1096 : }
1097 :
1098 : // LCOV_EXCL_STOP
1099 : // End deprecated API
|