1    	/*
2    	 * Copyright 2004-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	#include <stdio.h>
14   	#include <string.h>
15   	#include <dirent.h>
16   	#include <errno.h>
17   	#include <limits.h>             // UCHAR_MAX
18   	#include <sys/stat.h>
19   	#include <stdarg.h>
20   	
21   	#include <glib.h>                       // g_str_has_prefix()
22   	#include <libxml/relaxng.h>
23   	#include <libxml/tree.h>                // xmlNode
24   	#include <libxml/xmlstring.h>           // xmlChar
25   	#include <libxslt/xslt.h>
26   	#include <libxslt/transform.h>
27   	#include <libxslt/security.h>
28   	#include <libxslt/xsltutils.h>
29   	
30   	#include <crm/common/schemas.h>
31   	#include <crm/common/xml.h>
32   	
33   	#include "crmcommon_private.h"
34   	
35   	#define SCHEMA_ZERO { .v = { 0, 0 } }
36   	
37   	#define schema_strdup_printf(prefix, version, suffix) \
38   	    pcmk__assert_asprintf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
39   	
40   	typedef struct {
41   	    xmlRelaxNGPtr rng;
42   	    xmlRelaxNGValidCtxtPtr valid;
43   	    xmlRelaxNGParserCtxtPtr parser;
44   	} relaxng_ctx_cache_t;
45   	
46   	static GList *known_schemas = NULL;
47   	static bool initialized = false;
48   	static bool silent_logging = FALSE;
49   	
50   	static void G_GNUC_PRINTF(2, 3)
51   	xml_log(int priority, const char *fmt, ...)
52   	{
53   	    va_list ap;
54   	
55   	    va_start(ap, fmt);
56   	    if (silent_logging == FALSE) {
57   	        /* XXX should not this enable dechunking as well? */
58   	        PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
59   	    }
60   	    va_end(ap);
61   	}
62   	
63   	static int
64   	xml_latest_schema_index(void)
65   	{
66   	    /* This function assumes that pcmk__schema_init() has been called
67   	     * beforehand, so we have at least two schemas (one real schema and the
68   	     * "none" schema).
69   	     *
70   	     * @COMPAT: The "none" schema is deprecated since 2.1.8.
71   	     * Update this when we drop that schema.
72   	     */
73   	    return g_list_length(known_schemas) - 2;
74   	}
75   	
76   	/*!
77   	 * \internal
78   	 * \brief Return the schema entry of the highest-versioned schema
79   	 *
80   	 * \return Schema entry of highest-versioned schema
81   	 */
82   	static GList *
83   	get_highest_schema(void)
84   	{
85   	    /* The highest numerically versioned schema is the one before none
86   	     *
87   	     * @COMPAT none is deprecated since 2.1.8
88   	     */
89   	    GList *entry = pcmk__get_schema("none");
90   	
91   	    pcmk__assert((entry != NULL) && (entry->prev != NULL));
92   	    return entry->prev;
93   	}
94   	
95   	/*!
96   	 * \internal
97   	 * \brief Return the name of the highest-versioned schema
98   	 *
99   	 * \return Name of highest-versioned schema (or NULL on error)
100  	 */
101  	const char *
102  	pcmk__highest_schema_name(void)
103  	{
104  	    GList *entry = get_highest_schema();
105  	
106  	    return ((pcmk__schema_t *)(entry->data))->name;
107  	}
108  	
109  	/*!
110  	 * \internal
111  	 * \brief Find first entry of highest major schema version series
112  	 *
113  	 * \return Schema entry of first schema with highest major version
114  	 */
115  	GList *
116  	pcmk__find_x_0_schema(void)
117  	{
118  	#if defined(PCMK__UNIT_TESTING)
119  	    /* If we're unit testing, this can't be static because it'll stick
120  	     * around from one test run to the next. It needs to be cleared out
121  	     * every time.
122  	     */
123  	    GList *x_0_entry = NULL;
124  	#else
125  	    static GList *x_0_entry = NULL;
126  	#endif
127  	
128  	    pcmk__schema_t *highest_schema = NULL;
129  	
130  	    if (x_0_entry != NULL) {
131  	        return x_0_entry;
132  	    }
133  	    x_0_entry = get_highest_schema();
134  	    highest_schema = x_0_entry->data;
135  	
136  	    for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
137  	        pcmk__schema_t *schema = iter->data;
138  	
139  	        /* We've found a schema in an older major version series.  Return
140  	         * the index of the first one in the same major version series as
141  	         * the highest schema.
142  	         */
143  	        if (schema->version.v[0] < highest_schema->version.v[0]) {
144  	            x_0_entry = iter->next;
145  	            break;
146  	        }
147  	
148  	        /* We're out of list to examine.  This probably means there was only
149  	         * one major version series, so return the first schema entry.
150  	         */
151  	        if (iter->prev == NULL) {
152  	            x_0_entry = known_schemas->data;
153  	            break;
154  	        }
155  	    }
156  	    return x_0_entry;
157  	}
158  	
159  	static inline bool
160  	version_from_filename(const char *filename, pcmk__schema_version_t *version)
161  	{
162  	    if (filename == NULL) {
163  	        return false;
164  	    }
165  	
166  	    if (g_str_has_suffix(filename, ".rng")) {
167  	        return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2;
168  	    } else {
169  	        return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2;
170  	    }
171  	}
172  	
173  	static int
174  	schema_filter(const struct dirent *a)
175  	{
176  	    pcmk__schema_version_t version = SCHEMA_ZERO;
177  	
178  	    if (g_str_has_prefix(a->d_name, "pacemaker-")
179  	        && g_str_has_suffix(a->d_name, ".rng")
180  	        && version_from_filename(a->d_name, &version)) {
181  	
182  	        return 1;
183  	    }
184  	
185  	    return 0;
186  	}
187  	
188  	static int
189  	schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version)
190  	{
191  	    for (int i = 0; i < 2; ++i) {
192  	        if (a_version.v[i] < b_version.v[i]) {
193  	            return -1;
194  	        } else if (a_version.v[i] > b_version.v[i]) {
195  	            return 1;
196  	        }
197  	    }
198  	    return 0;
199  	}
200  	
201  	static int
202  	schema_cmp_directory(const struct dirent **a, const struct dirent **b)
203  	{
204  	    pcmk__schema_version_t a_version = SCHEMA_ZERO;
205  	    pcmk__schema_version_t b_version = SCHEMA_ZERO;
206  	
207  	    if (!version_from_filename(a[0]->d_name, &a_version)
208  	        || !version_from_filename(b[0]->d_name, &b_version)) {
209  	        // Shouldn't be possible, but makes static analysis happy
210  	        return 0;
211  	    }
212  	
213  	    return schema_cmp(a_version, b_version);
214  	}
215  	
216  	/*!
217  	 * \internal
218  	 * \brief Add given schema + auxiliary data to internal bookkeeping.
219  	 */
220  	static void
221  	add_schema(enum pcmk__schema_validator validator,
222  	           const pcmk__schema_version_t *version, const char *name,
223  	           GList *transforms)
224  	{
225  	    pcmk__schema_t *schema = NULL;
226  	
227  	    schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
228  	
229  	    schema->validator = validator;
230  	    schema->version.v[0] = version->v[0];
231  	    schema->version.v[1] = version->v[1];
232  	    schema->transforms = transforms;
233  	    // schema->schema_index is set after all schemas are loaded and sorted
234  	
235  	    if (version->v[0] || version->v[1]) {
236  	        schema->name = schema_strdup_printf("pacemaker-", *version, "");
237  	    } else {
238  	        schema->name = pcmk__str_copy(name);
239  	    }
240  	
241  	    known_schemas = g_list_prepend(known_schemas, schema);
242  	}
243  	
244  	static void
245  	wrap_libxslt(bool finalize)
246  	{
247  	    static xsltSecurityPrefsPtr secprefs;
248  	    int ret = 0;
249  	
250  	    /* security framework preferences */
251  	    if (!finalize) {
252  	        pcmk__assert(secprefs == NULL);
253  	        secprefs = xsltNewSecurityPrefs();
254  	        ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
255  	                                   xsltSecurityForbid)
256  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
257  	                                     xsltSecurityForbid)
258  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
259  	                                     xsltSecurityForbid)
260  	              | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
261  	                                     xsltSecurityForbid);
262  	        if (ret != 0) {
263  	            return;
264  	        }
265  	    } else {
266  	        xsltFreeSecurityPrefs(secprefs);
267  	        secprefs = NULL;
268  	    }
269  	
270  	    /* cleanup only */
271  	    if (finalize) {
272  	        xsltCleanupGlobals();
273  	    }
274  	}
275  	
276  	/*!
277  	 * \internal
278  	 * \brief Check whether a directory entry matches the upgrade XSLT pattern
279  	 *
280  	 * \param[in] entry  Directory entry whose filename to check
281  	 *
282  	 * \return 1 if the entry's filename is of the form
283  	 *         <tt>upgrade-X.Y-ORDER.xsl</tt> with each number in the range 0 to
284  	 *         255, or 0 otherwise
285  	 */
286  	static int
287  	transform_filter(const struct dirent *entry)
288  	{
289  	    const char *re = NULL;
290  	    unsigned int major = 0;
291  	    unsigned int minor = 0;
292  	    unsigned int order = 0;
293  	
294  	    /* Each number is an unsigned char, which is 1 to 3 digits long. (Pacemaker
295  	     * requires an 8-bit char via a configure test.)
296  	     */
297  	    re = "upgrade-[[:digit:]]{1,3}\\.[[:digit:]]{1,3}-[[:digit:]]{1,3}\\.xsl";
298  	
299  	    if (!pcmk__str_eq(entry->d_name, re, pcmk__str_regex)) {
300  	        return 0;
301  	    }
302  	
303  	    /* Performance isn't critical here and this is simpler than range-checking
304  	     * within the regex
305  	     */
306  	    if (sscanf(entry->d_name, "upgrade-%u.%u-%u.xsl",
307  	               &major, &minor, &order) != 3) {
308  	        return 0;
309  	    }
310  	
311  	    if ((major > UCHAR_MAX) || (minor > UCHAR_MAX) || (order > UCHAR_MAX)) {
312  	        return 0;
313  	    }
314  	    return 1;
315  	}
316  	
317  	/*!
318  	 * \internal
319  	 * \brief Compare transform files based on the version strings in their names
320  	 *
321  	 * \retval -1 if \p entry1 sorts before \p entry2
322  	 * \retval  0 if \p entry1 sorts equal to \p entry2
323  	 * \retval  1 if \p entry1 sorts after \p entry2
324  	 *
325  	 * \note The GNU \c versionsort() function would be perfect here, but it's not
326  	 *       portable.
327  	 */
328  	static int
329  	compare_transforms(const struct dirent **entry1, const struct dirent **entry2)
330  	{
331  	    // We already validated the format of each filename in transform_filter()
332  	    static const size_t offset = sizeof("upgrade-") - 1;
333  	
334  	    gchar *ver1 = g_strdelimit(g_strdup((*entry1)->d_name + offset), "-", '.');
335  	    gchar *ver2 = g_strdelimit(g_strdup((*entry2)->d_name + offset), "-", '.');
336  	    int rc = pcmk__compare_versions(ver1, ver2);
337  	
338  	    g_free(ver1);
339  	    g_free(ver2);
340  	    return rc;
341  	}
342  	
343  	/*!
344  	 * \internal
345  	 * \brief Free a list of XSLT transform <tt>struct dirent</tt> objects
346  	 *
347  	 * \param[in,out] data  List to free
348  	 */
349  	static void
350  	free_transform_list(void *data)
351  	{
352  	    g_list_free_full((GList *) data, free);
353  	}
354  	
355  	/*!
356  	 * \internal
357  	 * \brief Load names of upgrade XSLT stylesheets from a directory into a table
358  	 *
359  	 * Stylesheets must have names of the form "upgrade-X.Y-order.xsl", where:
360  	 * * X is the schema major version
361  	 * * Y is the schema minor version
362  	 * * ORDER is the order in which the stylesheet occurs in the transform pipeline
363  	 *
364  	 * \param[in] dir  Directory containing XSLT stylesheets
365  	 *
366  	 * \return Table with schema version as key and \c GList of associated transform
367  	 *         files (as <tt>struct dirent</tt>) as value
368  	 */
369  	static GHashTable *
370  	load_transforms_from_dir(const char *dir)
371  	{
372  	    struct dirent **namelist = NULL;
373  	    GHashTable *transforms = NULL;
374  	    int num_matches = scandir(dir, &namelist, transform_filter,
375  	                              compare_transforms);
376  	
377  	    if (num_matches < 0) {
378  	        int rc = errno;
379  	
380  	        pcmk__warn("Could not load transforms from %s: %s", dir,
381  	                   pcmk_rc_str(rc));
382  	        goto done;
383  	    }
384  	
385  	    transforms = pcmk__strkey_table(free, free_transform_list);
386  	
387  	    for (int i = 0; i < num_matches; i++) {
388  	        pcmk__schema_version_t version = SCHEMA_ZERO;
389  	        unsigned char order = 0;  // Placeholder only
390  	
391  	        if (sscanf(namelist[i]->d_name, "upgrade-%hhu.%hhu-%hhu.xsl",
392  	                   &(version.v[0]), &(version.v[1]), &order) == 3) {
393  	
394  	            char *version_s = pcmk__assert_asprintf("%hhu.%hhu",
395  	                                                    version.v[0], version.v[1]);
396  	            GList *list = g_hash_table_lookup(transforms, version_s);
397  	
398  	            if (list == NULL) {
399  	                /* Prepend is more efficient. However, there won't be many of
400  	                 * these, and we want them to remain sorted by version. It's not
401  	                 * worth reversing all the lists at the end.
402  	                 *
403  	                 * Avoid calling g_hash_table_insert() if the list already
404  	                 * exists. Otherwise free_transform_list() gets called on it.
405  	                 */
406  	                list = g_list_append(list, namelist[i]);
407  	                g_hash_table_insert(transforms, version_s, list);
408  	
409  	            } else {
410  	#pragma GCC diagnostic push
411  	#pragma GCC diagnostic ignored "-Wunused-result"
412  	                g_list_append(list, namelist[i]);
413  	#pragma GCC diagnostic pop
414  	                free(version_s);
415  	            }
416  	
417  	        } else {
418  	            // Sanity only, should never happen thanks to transform_filter()
419  	            free(namelist[i]);
420  	        }
421  	    }
422  	
423  	done:
424  	    free(namelist);
425  	    return transforms;
426  	}
427  	
428  	void
429  	pcmk__load_schemas_from_dir(const char *dir)
430  	{
431  	    int lpc, max;
432  	    struct dirent **namelist = NULL;
433  	    GHashTable *transforms = NULL;
434  	
435  	    max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
436  	    if (max < 0) {
437  	        int rc = errno;
438  	
439  	        pcmk__warn("Could not load schemas from %s: %s", dir, pcmk_rc_str(rc));
440  	        goto done;
441  	    }
442  	
443  	    // Look for any upgrade transforms in the same directory
444  	    transforms = load_transforms_from_dir(dir);
445  	
446  	    for (lpc = 0; lpc < max; lpc++) {
447  	        pcmk__schema_version_t version = SCHEMA_ZERO;
448  	
449  	        if (version_from_filename(namelist[lpc]->d_name, &version)) {
450  	            char *version_s = pcmk__assert_asprintf("%hhu.%hhu",
451  	                                                    version.v[0], version.v[1]);
452  	            char *orig_key = NULL;
453  	            GList *transform_list = NULL;
454  	
455  	            if (transforms != NULL) {
456  	                // The schema becomes the owner of transform_list
457  	                g_hash_table_lookup_extended(transforms, version_s,
458  	                                             (gpointer *) &orig_key,
459  	                                             (gpointer *) &transform_list);
460  	                g_hash_table_steal(transforms, version_s);
461  	            }
462  	
463  	            add_schema(pcmk__schema_validator_rng, &version, NULL,
464  	                       transform_list);
465  	
466  	            free(version_s);
467  	            free(orig_key);
468  	
469  	        } else {
470  	            // Shouldn't be possible, but makes static analysis happy
471  	            pcmk__warn("Skipping schema '%s': could not parse version",
472  	                       namelist[lpc]->d_name);
473  	        }
474  	    }
475  	
476  	    for (lpc = 0; lpc < max; lpc++) {
477  	        free(namelist[lpc]);
478  	    }
479  	
480  	done:
481  	    free(namelist);
482  	    g_clear_pointer(&transforms, g_hash_table_destroy);
483  	}
484  	
485  	static gint
486  	schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
487  	{
488  	    const pcmk__schema_t *schema_a = a;
489  	    const pcmk__schema_t *schema_b = b;
490  	
491  	    // @COMPAT The "none" schema is deprecated since 2.1.8
492  	    if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
493  	        return 1;
494  	    } else if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
495  	        return -1;
496  	    } else {
497  	        return schema_cmp(schema_a->version, schema_b->version);
498  	    }
499  	}
500  	
501  	/*!
502  	 * \internal
503  	 * \brief Sort the list of known schemas such that all pacemaker-X.Y are in
504  	 *        version order, then "none"
505  	 *
506  	 * This function should be called whenever additional schemas are loaded using
507  	 * \c pcmk__load_schemas_from_dir(), after the initial sets in
508  	 * \c pcmk__schema_init().
509  	 */
510  	void
511  	pcmk__sort_schemas(void)
512  	{
513  	    known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
514  	}
515  	
516  	/*!
517  	 * \internal
518  	 * \brief Load pacemaker schemas into cache
519  	 *
520  	 * \note This currently also serves as an entry point for the
521  	 *       generic initialization of the libxslt library.
522  	 */
523  	void
524  	pcmk__schema_init(void)
525  	{
526  	    if (!initialized) {
527  	        const char *remote_schema_dir = pcmk__remote_schema_dir();
528  	        char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
529  	        const pcmk__schema_version_t zero = SCHEMA_ZERO;
530  	        int schema_index = 0;
531  	
532  	        initialized = true;
533  	
534  	        wrap_libxslt(false);
535  	
536  	        pcmk__load_schemas_from_dir(base);
537  	        pcmk__load_schemas_from_dir(remote_schema_dir);
538  	        free(base);
539  	
540  	        // @COMPAT Deprecated since 2.1.8
541  	        add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL);
542  	
543  	        /* add_schema() prepends items to the list, so in the simple case, this
544  	         * just reverses the list. However if there were any remote schemas,
545  	         * sorting is necessary.
546  	         */
547  	        pcmk__sort_schemas();
548  	
549  	        // Now set the schema indexes and log the final result
550  	        for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
551  	            pcmk__schema_t *schema = iter->data;
552  	
553  	            pcmk__debug("Loaded schema %d: %s", schema_index, schema->name);
554  	            schema->schema_index = schema_index++;
555  	        }
556  	    }
557  	}
558  	
559  	static bool
560  	validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
561  	                      void *error_handler_context, const char *relaxng_file,
562  	                      relaxng_ctx_cache_t **cached_ctx)
563  	{
564  	    int rc = 0;
565  	    bool valid = true;
566  	    relaxng_ctx_cache_t *ctx = NULL;
567  	
568  	    CRM_CHECK(doc != NULL, return false);
569  	    CRM_CHECK(relaxng_file != NULL, return false);
570  	
571  	    if (cached_ctx && *cached_ctx) {
572  	        ctx = *cached_ctx;
573  	
574  	    } else {
575  	        pcmk__debug("Creating RNG parser context");
576  	        ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
577  	
578  	        ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
579  	        CRM_CHECK(ctx->parser != NULL, goto cleanup);
580  	
581  	        if (error_handler) {
582  	            xmlRelaxNGSetParserErrors(ctx->parser,
583  	                                      (xmlRelaxNGValidityErrorFunc) error_handler,
584  	                                      (xmlRelaxNGValidityWarningFunc) error_handler,
585  	                                      error_handler_context);
586  	        } else {
587  	            xmlRelaxNGSetParserErrors(ctx->parser,
588  	                                      (xmlRelaxNGValidityErrorFunc) fprintf,
589  	                                      (xmlRelaxNGValidityWarningFunc) fprintf,
590  	                                      stderr);
591  	        }
592  	
593  	        ctx->rng = xmlRelaxNGParse(ctx->parser);
594  	        CRM_CHECK(ctx->rng != NULL,
595  	                  pcmk__err("Could not find/parse %s", relaxng_file);
596  	                  goto cleanup);
597  	
598  	        ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
599  	        CRM_CHECK(ctx->valid != NULL, goto cleanup);
600  	
601  	        if (error_handler) {
602  	            xmlRelaxNGSetValidErrors(ctx->valid,
603  	                                     (xmlRelaxNGValidityErrorFunc) error_handler,
604  	                                     (xmlRelaxNGValidityWarningFunc) error_handler,
605  	                                     error_handler_context);
606  	        } else {
607  	            xmlRelaxNGSetValidErrors(ctx->valid,
608  	                                     (xmlRelaxNGValidityErrorFunc) fprintf,
609  	                                     (xmlRelaxNGValidityWarningFunc) fprintf,
610  	                                     stderr);
611  	        }
612  	    }
613  	
614  	    rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
615  	    if (rc > 0) {
616  	        valid = false;
617  	
618  	    } else if (rc < 0) {
619  	        pcmk__err("Internal libxml error during validation");
620  	    }
621  	
622  	  cleanup:
623  	
624  	    if (cached_ctx) {
625  	        *cached_ctx = ctx;
626  	
627  	    } else {
628  	        if (ctx->parser != NULL) {
629  	            xmlRelaxNGFreeParserCtxt(ctx->parser);
630  	        }
631  	        if (ctx->valid != NULL) {
632  	            xmlRelaxNGFreeValidCtxt(ctx->valid);
633  	        }
634  	        if (ctx->rng != NULL) {
635  	            xmlRelaxNGFree(ctx->rng);
636  	        }
637  	        free(ctx);
638  	    }
639  	
640  	    return valid;
641  	}
642  	
643  	static void
644  	free_schema(gpointer data)
645  	{
646  	    pcmk__schema_t *schema = data;
647  	    relaxng_ctx_cache_t *ctx = NULL;
648  	
649  	    switch (schema->validator) {
650  	        case pcmk__schema_validator_none: // not cached
651  	            break;
652  	
653  	        case pcmk__schema_validator_rng: // cached
654  	            ctx = (relaxng_ctx_cache_t *) schema->cache;
655  	            if (ctx == NULL) {
656  	                break;
657  	            }
658  	
659  	            if (ctx->parser != NULL) {
660  	                xmlRelaxNGFreeParserCtxt(ctx->parser);
661  	            }
662  	
663  	            if (ctx->valid != NULL) {
664  	                xmlRelaxNGFreeValidCtxt(ctx->valid);
665  	            }
666  	
667  	            if (ctx->rng != NULL) {
668  	                xmlRelaxNGFree(ctx->rng);
669  	            }
670  	
671  	            free(ctx);
672  	            schema->cache = NULL;
673  	            break;
674  	    }
675  	
676  	    free(schema->name);
677  	    g_list_free_full(schema->transforms, free);
678  	    free(schema);
679  	}
680  	
681  	/*!
682  	 * \internal
683  	 * \brief Clean up global memory associated with XML schemas
684  	 */
685  	void
686  	pcmk__schema_cleanup(void)
687  	{
688  	    g_list_free_full(known_schemas, free_schema);
689  	    known_schemas = NULL;
690  	    initialized = false;
691  	
692  	    wrap_libxslt(true);
693  	}
694  	
695  	/*!
696  	 * \internal
697  	 * \brief Get schema list entry corresponding to a schema name
698  	 *
699  	 * \param[in] name  Name of schema to get
700  	 *
701  	 * \return Schema list entry corresponding to \p name, or NULL if unknown
702  	 */
703  	GList *
704  	pcmk__get_schema(const char *name)
705  	{
706  	    if (name == NULL) {
707  	        return NULL;
708  	    }
709  	    for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
710  	        pcmk__schema_t *schema = iter->data;
711  	
712  	        if (pcmk__str_eq(name, schema->name, pcmk__str_none)) {
713  	            return iter;
714  	        }
715  	    }
716  	    return NULL;
717  	}
718  	
719  	/*!
720  	 * \internal
721  	 * \brief Compare two schema version numbers given the schema names
722  	 *
723  	 * \param[in] schema1  Name of first schema to compare
724  	 * \param[in] schema2  Name of second schema to compare
725  	 *
726  	 * \return Standard comparison result (negative integer if \p schema1 has the
727  	 *         lower version number, positive integer if \p schema1 has the higher
728  	 *         version number, of 0 if the version numbers are equal)
729  	 */
730  	int
731  	pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
732  	{
733  	    GList *entry1 = pcmk__get_schema(schema1_name);
734  	    GList *entry2 = pcmk__get_schema(schema2_name);
735  	
736  	    if (entry1 == NULL) {
737  	        return (entry2 == NULL)? 0 : -1;
738  	
739  	    } else if (entry2 == NULL) {
740  	        return 1;
741  	
742  	    } else {
743  	        pcmk__schema_t *schema1 = entry1->data;
744  	        pcmk__schema_t *schema2 = entry2->data;
745  	
746  	        return schema1->schema_index - schema2->schema_index;
747  	    }
748  	}
749  	
750  	static bool
751  	validate_with(xmlDoc *doc, pcmk__schema_t *schema,
752  	              xmlRelaxNGValidityErrorFunc error_handler,
753  	              void *error_handler_context)
754  	{
755  	    bool valid = false;
756  	    char *file = NULL;
757  	    relaxng_ctx_cache_t **cache = NULL;
758  	
759  	    if (schema == NULL) {
760  	        return false;
761  	    }
762  	
763  	    if (schema->validator == pcmk__schema_validator_none) {
764  	        return true;
765  	    }
766  	
767  	    file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
768  	                                   schema->name);
769  	
770  	    pcmk__trace("Validating with %s (type=%d)", pcmk__s(file, "missing schema"),
771  	                schema->validator);
772  	    switch (schema->validator) {
773  	        case pcmk__schema_validator_rng:
774  	            cache = (relaxng_ctx_cache_t **) &(schema->cache);
775  	            valid = validate_with_relaxng(doc, error_handler,
776  	                                          error_handler_context, file, cache);
777  	            break;
778  	        default:
779  	            pcmk__err("Unknown validator type: %d", schema->validator);
780  	            break;
781  	    }
782  	
783  	    free(file);
784  	    return valid;
785  	}
786  	
787  	static bool
788  	validate_with_silent(xmlDoc *doc, pcmk__schema_t *schema)
789  	{
790  	    bool rc = false;
791  	    bool sl_backup = silent_logging;
792  	
793  	    silent_logging = TRUE;
794  	    rc = validate_with(doc, schema, (xmlRelaxNGValidityErrorFunc) xml_log,
795  	                       GUINT_TO_POINTER(LOG_ERR));
796  	    silent_logging = sl_backup;
797  	    return rc;
798  	}
799  	
800  	bool
801  	pcmk__validate_xml(xmlNode *xml, xmlRelaxNGValidityErrorFunc error_handler,
802  	                   void *error_handler_context)
803  	{
804  	    const char *validation = NULL;
805  	    GList *entry = NULL;
806  	    pcmk__schema_t *schema = NULL;
807  	
808  	    CRM_CHECK((xml != NULL) && (xml->doc != NULL), return false);
809  	
810  	    validation = pcmk__xe_get(xml, PCMK_XA_VALIDATE_WITH);
811  	    pcmk__warn_if_schema_deprecated(validation);
812  	
813  	    entry = pcmk__get_schema(validation);
814  	    if (entry == NULL) {
815  	        pcmk__config_err("Cannot validate CIB with %s " PCMK_XA_VALIDATE_WITH
816  	                         " (manually edit to use a known schema)",
817  	                         ((validation == NULL)? "missing" : "unknown"));
818  	        return false;
819  	    }
820  	
821  	    schema = entry->data;
822  	    return validate_with(xml->doc, schema, error_handler,
823  	                         error_handler_context);
824  	}
825  	
826  	/*!
827  	 * \internal
828  	 * \brief Validate XML using its configured schema (and send errors to logs)
829  	 *
830  	 * \param[in] xml  XML to validate
831  	 *
832  	 * \return true if XML validates, otherwise false
833  	 */
834  	bool
835  	pcmk__configured_schema_validates(xmlNode *xml)
836  	{
837  	    return pcmk__validate_xml(xml, (xmlRelaxNGValidityErrorFunc) xml_log,
838  	                              GUINT_TO_POINTER(LOG_ERR));
839  	}
840  	
841  	/* With this arrangement, an attempt to identify the message severity
842  	   as explicitly signalled directly from XSLT is performed in rather
843  	   a smart way (no reliance on formatting string + arguments being
844  	   always specified as ["%s", purposeful_string], as it can also be
845  	   ["%s: %s", some_prefix, purposeful_string] etc. so every argument
846  	   pertaining %s specifier is investigated), and if such a mark found,
847  	   the respective level is determined and, when the messages are to go
848  	   to the native logs, the mark itself gets dropped
849  	   (by the means of string shift).
850  	
851  	   NOTE: whether the native logging is the right sink is decided per
852  	         the ctx parameter -- NULL denotes this case, otherwise it
853  	         carries a pointer to the numeric expression of the desired
854  	         target logging level (messages with higher level will be
855  	         suppressed)
856  	
857  	   NOTE: on some architectures, this string shift may not have any
858  	         effect, but that's an acceptable tradeoff
859  	
860  	   The logging level for not explicitly designated messages
861  	   (suspicious, likely internal errors or some runaways) is
862  	   LOG_WARNING.
863  	 */
864  	static void G_GNUC_PRINTF(2, 3)
865  	cib_upgrade_err(void *ctx, const char *fmt, ...)
866  	{
867  	    va_list ap, aq;
868  	    char *arg_cur;
869  	
870  	    bool found = false;
871  	    const char *fmt_iter = fmt;
872  	    uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
873  	    const unsigned * log_level = (const unsigned *) ctx;
874  	    enum {
875  	        escan_seennothing,
876  	        escan_seenpercent,
877  	    } scan_state = escan_seennothing;
878  	
879  	    va_start(ap, fmt);
880  	    va_copy(aq, ap);
881  	
882  	    while (!found && *fmt_iter != '\0') {
883  	        /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
884  	        switch (*fmt_iter++) {
885  	        case '%':
886  	            if (scan_state == escan_seennothing) {
887  	                scan_state = escan_seenpercent;
888  	            } else if (scan_state == escan_seenpercent) {
889  	                scan_state = escan_seennothing;
890  	            }
891  	            break;
892  	        case 's':
893  	            if (scan_state == escan_seenpercent) {
894  	                size_t prefix_len = 0;
895  	
896  	                scan_state = escan_seennothing;
897  	                arg_cur = va_arg(aq, char *);
898  	
899  	                if (arg_cur == NULL) {
900  	                    break;
901  	
902  	                } else if (g_str_has_prefix(arg_cur, "WARNING: ")) {
903  	                    prefix_len = sizeof("WARNING: ") - 1;
904  	                    msg_log_level = LOG_WARNING;
905  	
906  	                } else if (g_str_has_prefix(arg_cur, "INFO: ")) {
907  	                    prefix_len = sizeof("INFO: ") - 1;
908  	                    msg_log_level = LOG_INFO;
909  	
910  	                } else if (g_str_has_prefix(arg_cur, "DEBUG: ")) {
911  	                    prefix_len = sizeof("DEBUG: ") - 1;
912  	                    msg_log_level = LOG_DEBUG;
913  	
914  	                } else {
915  	                    break;
916  	                }
917  	
918  	                found = true;
919  	                if (ctx == NULL) {
920  	                    memmove(arg_cur, arg_cur + prefix_len,
921  	                            strlen(arg_cur + prefix_len) + 1);
922  	                }
923  	            }
924  	            break;
925  	        case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
926  	        case '0': case '1': case '2': case '3': case '4':
927  	        case '5': case '6': case '7': case '8': case '9':
928  	        case '*':
929  	            break;
930  	        case 'l':
931  	        case 'z':
932  	        case 't':
933  	        case 'j':
934  	        case 'd': case 'i':
935  	        case 'o':
936  	        case 'u':
937  	        case 'x': case 'X':
938  	        case 'e': case 'E':
939  	        case 'f': case 'F':
940  	        case 'g': case 'G':
941  	        case 'a': case 'A':
942  	        case 'c':
943  	        case 'p':
944  	            if (scan_state == escan_seenpercent) {
945  	                (void) va_arg(aq, void *);  /* skip forward */
946  	                scan_state = escan_seennothing;
947  	            }
948  	            break;
949  	        default:
950  	            scan_state = escan_seennothing;
951  	            break;
952  	        }
953  	    }
954  	
955  	    if (log_level != NULL) {
956  	        /* intention of the following offset is:
957  	           cibadmin -V -> start showing INFO labelled messages */
958  	        if (*log_level + 4 >= msg_log_level) {
959  	            vfprintf(stderr, fmt, ap);
960  	        }
961  	    } else {
962  	        PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
963  	    }
964  	
965  	    va_end(aq);
966  	    va_end(ap);
967  	}
968  	
969  	/*!
970  	 * \internal
971  	 * \brief Apply a single XSL transformation to the given XML document
972  	 *
973  	 * \param[in] doc        XML document
974  	 * \param[in] transform  XSL name
975  	 * \param[in] to_logs    If false, certain validation errors will be sent to
976  	 *                       stderr rather than logged
977  	 *
978  	 * \return Transformed XML on success, otherwise NULL
979  	 */
980  	static xmlDoc *
981  	apply_transformation(xmlDoc *doc, const char *transform, bool to_logs)
982  	{
983  	    char *xform = NULL;
984  	    xsltStylesheet *xslt = NULL;
985  	    xmlDoc *result_doc = NULL;
986  	
987  	    xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
988  	                                    transform);
989  	
990  	    /* for capturing, e.g., what's emitted via <xsl:message> */
991  	    if (to_logs) {
992  	        xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
993  	    } else {
994  	        xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
995  	    }
996  	
997  	    xslt = xsltParseStylesheetFile((const xmlChar *) xform);
998  	    CRM_CHECK(xslt != NULL, goto cleanup);
999  	
1000 	    /* Caller allocates private data for final result document. Intermediate
1001 	     * result documents are temporary and don't need private data.
1002 	     */
1003 	    result_doc = xsltApplyStylesheet(xslt, doc, NULL);
1004 	    CRM_CHECK(result_doc != NULL, goto cleanup);
1005 	
1006 	    xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
1007 	
1008 	  cleanup:
1009 	    if (xslt) {
1010 	        xsltFreeStylesheet(xslt);
1011 	    }
1012 	
1013 	    free(xform);
1014 	
1015 	    return result_doc;
1016 	}
1017 	
1018 	/*!
1019 	 * \internal
1020 	 * \brief Perform all transformations needed to upgrade XML to next schema
1021 	 *
1022 	 * \param[in] input_doc     XML document to transform
1023 	 * \param[in] schema_index  Index of schema that successfully validates
1024 	 *                          \p original_xml
1025 	 * \param[in] to_logs       If false, certain validation errors will be sent to
1026 	 *                          stderr rather than logged
1027 	 *
1028 	 * \return XML result of schema transforms if successful, otherwise NULL
1029 	 */
1030 	static xmlDoc *
1031 	apply_upgrade(xmlDoc *input_doc, int schema_index, bool to_logs)
1032 	{
1033 	    pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1034 	    pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
1035 	                                                      schema_index + 1);
1036 	
1037 	    xmlDoc *old_doc = NULL;
1038 	    xmlDoc *new_doc = NULL;
1039 	    xmlRelaxNGValidityErrorFunc error_handler = NULL;
1040 	
1041 	    pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
1042 	
1043 	    if (to_logs) {
1044 	        error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1045 	    }
1046 	
1047 	    for (GList *iter = schema->transforms; iter != NULL; iter = iter->next) {
1048 	        const struct dirent *entry = iter->data;
1049 	        const char *transform = entry->d_name;
1050 	
1051 	        pcmk__debug("Upgrading schema from %s to %s: applying XSL transform %s",
1052 	                    schema->name, upgraded_schema->name, transform);
1053 	
1054 	        new_doc = apply_transformation(input_doc, transform, to_logs);
1055 	        pcmk__xml_free_doc(old_doc);
1056 	
1057 	        if (new_doc == NULL) {
1058 	            pcmk__err("XSL transform %s failed, aborting upgrade", transform);
1059 	            return NULL;
1060 	        }
1061 	
1062 	        input_doc = new_doc;
1063 	        old_doc = new_doc;
1064 	    }
1065 	
1066 	    // Final result document from upgrade pipeline needs private data
1067 	    pcmk__xml_new_private_data((xmlNode *) new_doc);
1068 	    pcmk__assert(new_doc != NULL);
1069 	
1070 	    // Ensure result validates with its new schema
1071 	    if (!validate_with(new_doc, upgraded_schema, error_handler,
1072 	                       GUINT_TO_POINTER(LOG_ERR))) {
1073 	        pcmk__err("Schema upgrade from %s to %s failed: XSL transform pipeline "
1074 	                  "produced an invalid configuration",
1075 	                  schema->name, upgraded_schema->name);
1076 	        pcmk__log_xml_debug(xmlDocGetRootElement(new_doc),
1077 	                            "bad-transform-result");
1078 	        pcmk__xml_free_doc(new_doc);
1079 	        return NULL;
1080 	    }
1081 	
1082 	    pcmk__info("Schema upgrade from %s to %s succeeded", schema->name,
1083 	               upgraded_schema->name);
1084 	    return new_doc;
1085 	}
1086 	
1087 	/*!
1088 	 * \internal
1089 	 * \brief Get the schema list entry corresponding to XML configuration
1090 	 *
1091 	 * \param[in] xml  CIB XML to check
1092 	 *
1093 	 * \return List entry of schema configured in \p xml
1094 	 */
1095 	static GList *
1096 	get_configured_schema(const xmlNode *xml)
1097 	{
1098 	    const char *schema_name = pcmk__xe_get(xml, PCMK_XA_VALIDATE_WITH);
1099 	
1100 	    pcmk__warn_if_schema_deprecated(schema_name);
1101 	    return pcmk__get_schema(schema_name);
1102 	}
1103 	
1104 	/*!
1105 	 * \brief Update CIB XML to latest schema that validates it
1106 	 *
1107 	 * \param[in,out] xml              XML to update (may be freed and replaced
1108 	 *                                 after being transformed)
1109 	 * \param[in]     max_schema_name  If not NULL, do not update \p xml to any
1110 	 *                                 schema later than this one
1111 	 * \param[in]     transform        If false, do not update \p xml to any schema
1112 	 *                                 that requires an XSL transform
1113 	 * \param[in]     to_logs          If false, certain validation errors will be
1114 	 *                                 sent to stderr rather than logged
1115 	 *
1116 	 * \return Standard Pacemaker return code
1117 	 */
1118 	int
1119 	pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
1120 	                    bool to_logs)
1121 	{
1122 	    int max_stable_schemas = xml_latest_schema_index();
1123 	    int max_schema_index = 0;
1124 	    int rc = pcmk_rc_ok;
1125 	    GList *entry = NULL;
1126 	    pcmk__schema_t *best_schema = NULL;
1127 	    pcmk__schema_t *original_schema = NULL;
1128 	    xmlRelaxNGValidityErrorFunc error_handler = NULL;
1129 	
1130 	    CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1131 	              return EINVAL);
1132 	
1133 	    if (to_logs) {
1134 	        error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1135 	    }
1136 	
1137 	    if (max_schema_name != NULL) {
1138 	        GList *max_entry = pcmk__get_schema(max_schema_name);
1139 	
1140 	        if (max_entry != NULL) {
1141 	            pcmk__schema_t *max_schema = max_entry->data;
1142 	
1143 	            max_schema_index = max_schema->schema_index;
1144 	        }
1145 	    }
1146 	    if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1147 	        max_schema_index = max_stable_schemas;
1148 	    }
1149 	
1150 	    entry = get_configured_schema(*xml);
1151 	    if (entry == NULL) {
1152 	        return pcmk_rc_cib_corrupt;
1153 	    }
1154 	    original_schema = entry->data;
1155 	    if (original_schema->schema_index >= max_schema_index) {
1156 	        return pcmk_rc_ok;
1157 	    }
1158 	
1159 	    for (; entry != NULL; entry = entry->next) {
1160 	        pcmk__schema_t *current_schema = entry->data;
1161 	        xmlDoc *upgrade = NULL;
1162 	
1163 	        if (current_schema->schema_index > max_schema_index) {
1164 	            break;
1165 	        }
1166 	
1167 	        if (!validate_with((*xml)->doc, current_schema, error_handler,
1168 	                           GUINT_TO_POINTER(LOG_ERR))) {
1169 	            pcmk__debug("Schema %s does not validate", current_schema->name);
1170 	            if (best_schema != NULL) {
1171 	                /* we've satisfied the validation, no need to check further */
1172 	                break;
1173 	            }
1174 	            rc = pcmk_rc_schema_validation;
1175 	            continue; // Try again with the next higher schema
1176 	        }
1177 	
1178 	        pcmk__debug("Schema %s validates", current_schema->name);
1179 	        rc = pcmk_rc_ok;
1180 	        best_schema = current_schema;
1181 	        if (current_schema->schema_index == max_schema_index) {
1182 	            break; // No further transformations possible
1183 	        }
1184 	
1185 	        // coverity[null_field] The index check ensures entry->next is not NULL
1186 	        if (!transform || (current_schema->transforms == NULL)
1187 	            || validate_with_silent((*xml)->doc, entry->next->data)) {
1188 	            /* The next schema either doesn't require a transform or validates
1189 	             * successfully even without the transform. Skip the transform and
1190 	             * try the next schema with the same XML.
1191 	             */
1192 	            continue;
1193 	        }
1194 	
1195 	        upgrade = apply_upgrade((*xml)->doc, current_schema->schema_index,
1196 	                                to_logs);
1197 	        if (upgrade == NULL) {
1198 	            /* The transform failed, so this schema can't be used. Later
1199 	             * schemas are unlikely to validate, but try anyway until we
1200 	             * run out of options.
1201 	             */
1202 	            rc = pcmk_rc_transform_failed;
1203 	
1204 	        } else {
1205 	            best_schema = current_schema;
1206 	            pcmk__xml_free(*xml);
1207 	            *xml = xmlDocGetRootElement(upgrade);
1208 	        }
1209 	    }
1210 	
1211 	    if ((best_schema != NULL)
1212 	        && (best_schema->schema_index > original_schema->schema_index)) {
1213 	        pcmk__info("%s the configuration schema to %s",
1214 	                   (transform? "Transformed" : "Upgraded"), best_schema->name);
1215 	        pcmk__xe_set(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1216 	    }
1217 	    return rc;
1218 	}
1219 	
1220 	int
1221 	pcmk_update_configured_schema(xmlNode **xml)
1222 	{
1223 	    return pcmk__update_configured_schema(xml, true);
1224 	}
1225 	
1226 	/*!
1227 	 * \brief Update XML from its configured schema to the latest major series
1228 	 *
1229 	 * \param[in,out] xml      XML to update
1230 	 * \param[in]     to_logs  If false, certain validation errors will be
1231 	 *                         sent to stderr rather than logged
1232 	 *
1233 	 * \return Standard Pacemaker return code
1234 	 */
1235 	int
1236 	pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
1237 	{
1238 	    pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1239 	    pcmk__schema_t *original_schema = NULL;
1240 	    GList *entry = NULL;
1241 	
(1) Event path: Condition "xml == NULL", taking false branch.
1242 	    if (xml == NULL) {
1243 	        return EINVAL;
1244 	    }
1245 	
1246 	    entry = get_configured_schema(*xml);
(2) Event path: Condition "entry == NULL", taking false branch.
1247 	    if (entry == NULL) {
1248 	        return pcmk_rc_cib_corrupt;
1249 	    }
1250 	
1251 	    original_schema = entry->data;
(3) Event path: Condition "original_schema->schema_index < x_0_schema->schema_index", taking true branch.
1252 	    if (original_schema->schema_index < x_0_schema->schema_index) {
1253 	        // Current configuration schema is not acceptable, try to update
1254 	        xmlNode *converted = NULL;
1255 	        const char *new_schema_name = NULL;
1256 	        pcmk__schema_t *schema = NULL;
1257 	
1258 	        entry = NULL;
1259 	        converted = pcmk__xml_copy(NULL, *xml);
(4) Event path: Condition "pcmk__update_schema(&converted, NULL, true /* 1 */, to_logs) == pcmk_rc_ok", taking false branch.
1260 	        if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1261 	            new_schema_name = pcmk__xe_get(converted, PCMK_XA_VALIDATE_WITH);
1262 	            entry = pcmk__get_schema(new_schema_name);
1263 	        }
(5) Event path: Condition "entry == NULL", taking true branch.
1264 	        schema = (entry == NULL)? NULL : entry->data;
1265 	
(6) Event path: Condition "schema == NULL", taking true branch.
1266 	        if ((schema == NULL)
1267 	            || (schema->schema_index < x_0_schema->schema_index)) {
1268 	            // Updated configuration schema is still not acceptable
1269 	
(7) Event path: Condition "schema == NULL", taking true branch.
1270 	            if ((schema == NULL)
1271 	                || (schema->schema_index < original_schema->schema_index)) {
1272 	                // We couldn't validate any schema at all
(8) Event path: Condition "to_logs", taking true branch.
1273 	                if (to_logs) {
(9) Event path: Condition "pcmk__config_error_handler == NULL", taking true branch.
(10) Event path: Falling through to end of if statement.
1274 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1275 	                                     "%s schema) to at least %s because it "
1276 	                                     "does not validate with any schema from "
1277 	                                     "%s to the latest",
1278 	                                     original_schema->name,
1279 	                                     x_0_schema->name, original_schema->name);
(11) Event path: Falling through to end of if statement.
1280 	                } else {
1281 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1282 	                                    "%s schema) to at least %s because it "
1283 	                                    "does not validate with any schema from "
1284 	                                    "%s to the latest\n",
1285 	                                    original_schema->name,
1286 	                                    x_0_schema->name, original_schema->name);
1287 	                }
(12) Event path: Falling through to end of if statement.
1288 	            } else {
1289 	                // We updated configuration successfully, but still too low
1290 	                if (to_logs) {
1291 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1292 	                                     "%s schema) to at least %s because it "
1293 	                                     "would not upgrade past %s",
1294 	                                     original_schema->name, x_0_schema->name,
1295 	                                     pcmk__s(new_schema_name, "unspecified version"));
1296 	                } else {
1297 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1298 	                                    "%s schema) to at least %s because it "
1299 	                                    "would not upgrade past %s\n",
1300 	                                    original_schema->name, x_0_schema->name,
1301 	                                    pcmk__s(new_schema_name, "unspecified version"));
1302 	                }
1303 	            }
1304 	
CID (unavailable; MK=8f2cb661e2077e5f5d755ea1834a11c9) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(13) Event assign_union_field: The union field "in" of "_pp" is written.
(14) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
1305 	            g_clear_pointer(&converted, pcmk__xml_free);
1306 	            return pcmk_rc_transform_failed;
1307 	
1308 	        } else {
1309 	            // Updated configuration schema is acceptable
1310 	            pcmk__xml_free(*xml);
1311 	            *xml = converted;
1312 	
1313 	            if (schema->schema_index < xml_latest_schema_index()) {
1314 	                if (to_logs) {
1315 	                    pcmk__config_warn("Configuration with %s schema was "
1316 	                                      "internally upgraded to acceptable (but "
1317 	                                      "not most recent) %s",
1318 	                                      original_schema->name, schema->name);
1319 	                }
1320 	            } else if (to_logs) {
1321 	                pcmk__info("Configuration with %s schema was internally "
1322 	                           "upgraded to latest version %s",
1323 	                           original_schema->name, schema->name);
1324 	            }
1325 	        }
1326 	
1327 	    } else if (!to_logs) {
1328 	        pcmk__schema_t *none_schema = NULL;
1329 	
1330 	        entry = pcmk__get_schema(PCMK_VALUE_NONE);
1331 	        pcmk__assert((entry != NULL) && (entry->data != NULL));
1332 	
1333 	        none_schema = entry->data;
1334 	        if (original_schema->schema_index >= none_schema->schema_index) {
1335 	            // @COMPAT the none schema is deprecated since 2.1.8
1336 	            fprintf(stderr, "Schema validation of configuration is "
1337 	                            "disabled (support for " PCMK_XA_VALIDATE_WITH
1338 	                            " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1339 	                            " and will be removed in a future release)\n");
1340 	        }
1341 	    }
1342 	
1343 	    return pcmk_rc_ok;
1344 	}
1345 	
1346 	/*!
1347 	 * \internal
1348 	 * \brief Return a list of all schema files and any associated XSLT files
1349 	 *        later than the given one
1350 	 * \brief Return a list of all schema versions later than the given one
1351 	 *
1352 	 * \param[in] schema The schema to compare against (for example,
1353 	 *                   "pacemaker-3.1.rng" or "pacemaker-3.1")
1354 	 *
1355 	 * \note The caller is responsible for freeing both the returned list and
1356 	 *       the elements of the list
1357 	 */
1358 	GList *
1359 	pcmk__schema_files_later_than(const char *name)
1360 	{
1361 	    GList *lst = NULL;
1362 	    pcmk__schema_version_t ver;
1363 	
1364 	    if (!version_from_filename(name, &ver)) {
1365 	        return lst;
1366 	    }
1367 	
1368 	    for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1369 	         iter != NULL; iter = iter->prev) {
1370 	        pcmk__schema_t *schema = iter->data;
1371 	
1372 	        if (schema_cmp(ver, schema->version) != -1) {
1373 	            continue;
1374 	        }
1375 	
1376 	        for (GList *iter2 = g_list_last(schema->transforms); iter2 != NULL;
1377 	             iter2 = iter2->prev) {
1378 	
1379 	            const struct dirent *entry = iter2->data;
1380 	
1381 	            lst = g_list_prepend(lst, pcmk__str_copy(entry->d_name));
1382 	        }
1383 	
1384 	        lst = g_list_prepend(lst,
1385 	                             pcmk__assert_asprintf("%s.rng", schema->name));
1386 	    }
1387 	
1388 	    return lst;
1389 	}
1390 	
1391 	static void
1392 	append_href(xmlNode *xml, void *user_data)
1393 	{
1394 	    GList **list = user_data;
1395 	    char *href = pcmk__xe_get_copy(xml, "href");
1396 	
1397 	    if (href == NULL) {
1398 	        return;
1399 	    }
1400 	    *list = g_list_prepend(*list, href);
1401 	}
1402 	
1403 	static void
1404 	external_refs_in_schema(GList **list, const char *contents)
1405 	{
1406 	    /* local-name()= is needed to ignore the xmlns= setting at the top of
1407 	     * the XML file.  Otherwise, the xpath query will always return nothing.
1408 	     */
1409 	    const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1410 	    xmlNode *xml = pcmk__xml_parse(contents);
1411 	
1412 	    pcmk__xpath_foreach_result(xml->doc, search, append_href, list);
1413 	    pcmk__xml_free(xml);
1414 	}
1415 	
1416 	static int
1417 	read_file_contents(const char *file, char **contents)
1418 	{
1419 	    int rc = pcmk_rc_ok;
1420 	    char *path = NULL;
1421 	
1422 	    if (g_str_has_suffix(file, ".rng")) {
1423 	        path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1424 	    } else {
1425 	        path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1426 	    }
1427 	
1428 	    rc = pcmk__file_contents(path, contents);
1429 	
1430 	    free(path);
1431 	    return rc;
1432 	}
1433 	
1434 	static void
1435 	add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
1436 	{
1437 	    char *contents = NULL;
1438 	    char *path = NULL;
1439 	    xmlNode *file_node = NULL;
1440 	    GList *includes = NULL;
1441 	    int rc = pcmk_rc_ok;
1442 	
1443 	    /* If we already included this file, don't do so again. */
1444 	    if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1445 	        return;
1446 	    }
1447 	
1448 	    /* Ensure whatever file we were given has a suffix we know about.  If not,
1449 	     * just assume it's an RNG file.
1450 	     */
1451 	    if (!g_str_has_suffix(file, ".rng") && !g_str_has_suffix(file, ".xsl")) {
1452 	        path = pcmk__assert_asprintf("%s.rng", file);
1453 	    } else {
1454 	        path = pcmk__str_copy(file);
1455 	    }
1456 	
1457 	    rc = read_file_contents(path, &contents);
1458 	    if (rc != pcmk_rc_ok || contents == NULL) {
1459 	        pcmk__warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1460 	        free(path);
1461 	        return;
1462 	    }
1463 	
1464 	    /* Create a new <file path="..."> node with the contents of the file
1465 	     * as a CDATA block underneath it.
1466 	     */
1467 	    file_node = pcmk__xe_create(parent, PCMK__XE_FILE);
1468 	    pcmk__xe_set(file_node, PCMK_XA_PATH, path);
1469 	    *already_included = g_list_prepend(*already_included, path);
1470 	
1471 	    xmlAddChild(file_node,
1472 	                xmlNewCDataBlock(parent->doc, (const xmlChar *) contents,
1473 	                                 strlen(contents)));
1474 	
1475 	    /* Scan the file for any <externalRef> or <include> nodes and build up
1476 	     * a list of the files they reference.
1477 	     */
1478 	    external_refs_in_schema(&includes, contents);
1479 	
1480 	    /* For each referenced file, recurse to add it (and potentially anything it
1481 	     * references, ...) to the XML.
1482 	     */
1483 	    for (GList *iter = includes; iter != NULL; iter = iter->next) {
1484 	        add_schema_file_to_xml(parent, iter->data, already_included);
1485 	    }
1486 	
1487 	    free(contents);
1488 	    g_list_free_full(includes, free);
1489 	}
1490 	
1491 	/*!
1492 	 * \internal
1493 	 * \brief Add an XML schema file and all the files it references as children
1494 	 *        of a given XML node
1495 	 *
1496 	 * \param[in,out] parent            The parent XML node
1497 	 * \param[in] name                  The schema version to compare against
1498 	 *                                  (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
1499 	 * \param[in,out] already_included  A list of names that have already been added
1500 	 *                                  to the parent node.
1501 	 *
1502 	 * \note The caller is responsible for freeing both the returned list and
1503 	 *       the elements of the list
1504 	 */
1505 	void
1506 	pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
1507 	{
1508 	    xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1509 	
1510 	    pcmk__xe_set(schema_node, PCMK_XA_VERSION, name);
1511 	    add_schema_file_to_xml(schema_node, name, already_included);
1512 	
1513 	    if (schema_node->children == NULL) {
1514 	        // Not needed if empty. May happen if name was invalid, for example.
1515 	        pcmk__xml_free(schema_node);
1516 	    }
1517 	}
1518 	
1519 	/*!
1520 	 * \internal
1521 	 * \brief Return the directory containing any extra schema files that a
1522 	 *        Pacemaker Remote node fetched from the cluster
1523 	 */
1524 	const char *
1525 	pcmk__remote_schema_dir(void)
1526 	{
1527 	    const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1528 	
1529 	    if (pcmk__str_empty(dir)) {
1530 	        return PCMK__REMOTE_SCHEMA_DIR;
1531 	    }
1532 	
1533 	    return dir;
1534 	}
1535 	
1536 	/*!
1537 	 * \internal
1538 	 * \brief Warn if a given validation schema is deprecated
1539 	 *
1540 	 * \param[in] Schema name to check
1541 	 */
1542 	void
1543 	pcmk__warn_if_schema_deprecated(const char *schema)
1544 	{
1545 	    /* @COMPAT Disabling validation is deprecated since 2.1.8, but
1546 	     * resource-agents' ocf-shellfuncs (at least as of 4.15.1) uses it
1547 	     */
1548 	    if (pcmk__str_eq(schema, PCMK_VALUE_NONE, pcmk__str_none)) {
1549 	        pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1550 	                          "deprecated and will be removed in a future release "
1551 	                          "without the possibility of upgrades (manually edit "
1552 	                          "to use a supported schema)", schema);
1553 	    }
1554 	}
1555 	
1556 	// Deprecated functions kept only for backward API compatibility
1557 	// LCOV_EXCL_START
1558 	
1559 	#include <crm/common/xml_compat.h>
1560 	
1561 	gboolean
1562 	cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1563 	{
1564 	    int rc = pcmk__update_configured_schema(xml, to_logs);
1565 	
1566 	    if (best_version != NULL) {
1567 	        const char *name = pcmk__xe_get(*xml, PCMK_XA_VALIDATE_WITH);
1568 	
1569 	        if (name == NULL) {
1570 	            *best_version = -1;
1571 	        } else {
1572 	            GList *entry = pcmk__get_schema(name);
1573 	            pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1574 	
1575 	            *best_version = (schema == NULL)? -1 : schema->schema_index;
1576 	        }
1577 	    }
1578 	    return (rc == pcmk_rc_ok)? TRUE: FALSE;
1579 	}
1580 	
1581 	// LCOV_EXCL_STOP
1582 	// End deprecated API
1583