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