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);
(1) Event path: Condition "max < 0", taking true branch.
436  	    if (max < 0) {
437  	        int rc = errno;
438  	
439  	        pcmk__warn("Could not load schemas from %s: %s", dir, pcmk_rc_str(rc));
(2) Event path: Jumping to label "done".
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);
CID (unavailable; MK=2e8b088bb387b098e9b16989efcd3636) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(3) Event assign_union_field: The union field "in" of "_pp" is written.
(4) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
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(xmlNode *xml, 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(xml->doc, error_handler, error_handler_context, file, cache);
776  	            break;
777  	        default:
778  	            pcmk__err("Unknown validator type: %d", schema->validator);
779  	            break;
780  	    }
781  	
782  	    free(file);
783  	    return valid;
784  	}
785  	
786  	static bool
787  	validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
788  	{
789  	    bool rc, sl_backup = silent_logging;
790  	    silent_logging = TRUE;
791  	    rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
792  	    silent_logging = sl_backup;
793  	    return rc;
794  	}
795  	
796  	bool
797  	pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
798  	                   xmlRelaxNGValidityErrorFunc error_handler,
799  	                   void *error_handler_context)
800  	{
801  	    GList *entry = NULL;
802  	    pcmk__schema_t *schema = NULL;
803  	
804  	    CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
805  	
806  	    if (validation == NULL) {
807  	        validation = pcmk__xe_get(xml_blob, PCMK_XA_VALIDATE_WITH);
808  	    }
809  	    pcmk__warn_if_schema_deprecated(validation);
810  	
811  	    entry = pcmk__get_schema(validation);
812  	    if (entry == NULL) {
813  	        pcmk__config_err("Cannot validate CIB with %s " PCMK_XA_VALIDATE_WITH
814  	                         " (manually edit to use a known schema)",
815  	                         ((validation == NULL)? "missing" : "unknown"));
816  	        return false;
817  	    }
818  	
819  	    schema = entry->data;
820  	    return validate_with(xml_blob, schema, error_handler,
821  	                         error_handler_context);
822  	}
823  	
824  	/*!
825  	 * \internal
826  	 * \brief Validate XML using its configured schema (and send errors to logs)
827  	 *
828  	 * \param[in] xml  XML to validate
829  	 *
830  	 * \return true if XML validates, otherwise false
831  	 */
832  	bool
833  	pcmk__configured_schema_validates(xmlNode *xml)
834  	{
835  	    return pcmk__validate_xml(xml, NULL,
836  	                              (xmlRelaxNGValidityErrorFunc) xml_log,
837  	                              GUINT_TO_POINTER(LOG_ERR));
838  	}
839  	
840  	/* With this arrangement, an attempt to identify the message severity
841  	   as explicitly signalled directly from XSLT is performed in rather
842  	   a smart way (no reliance on formatting string + arguments being
843  	   always specified as ["%s", purposeful_string], as it can also be
844  	   ["%s: %s", some_prefix, purposeful_string] etc. so every argument
845  	   pertaining %s specifier is investigated), and if such a mark found,
846  	   the respective level is determined and, when the messages are to go
847  	   to the native logs, the mark itself gets dropped
848  	   (by the means of string shift).
849  	
850  	   NOTE: whether the native logging is the right sink is decided per
851  	         the ctx parameter -- NULL denotes this case, otherwise it
852  	         carries a pointer to the numeric expression of the desired
853  	         target logging level (messages with higher level will be
854  	         suppressed)
855  	
856  	   NOTE: on some architectures, this string shift may not have any
857  	         effect, but that's an acceptable tradeoff
858  	
859  	   The logging level for not explicitly designated messages
860  	   (suspicious, likely internal errors or some runaways) is
861  	   LOG_WARNING.
862  	 */
863  	static void G_GNUC_PRINTF(2, 3)
864  	cib_upgrade_err(void *ctx, const char *fmt, ...)
865  	{
866  	    va_list ap, aq;
867  	    char *arg_cur;
868  	
869  	    bool found = false;
870  	    const char *fmt_iter = fmt;
871  	    uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
872  	    const unsigned * log_level = (const unsigned *) ctx;
873  	    enum {
874  	        escan_seennothing,
875  	        escan_seenpercent,
876  	    } scan_state = escan_seennothing;
877  	
878  	    va_start(ap, fmt);
879  	    va_copy(aq, ap);
880  	
881  	    while (!found && *fmt_iter != '\0') {
882  	        /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
883  	        switch (*fmt_iter++) {
884  	        case '%':
885  	            if (scan_state == escan_seennothing) {
886  	                scan_state = escan_seenpercent;
887  	            } else if (scan_state == escan_seenpercent) {
888  	                scan_state = escan_seennothing;
889  	            }
890  	            break;
891  	        case 's':
892  	            if (scan_state == escan_seenpercent) {
893  	                size_t prefix_len = 0;
894  	
895  	                scan_state = escan_seennothing;
896  	                arg_cur = va_arg(aq, char *);
897  	
898  	                if (arg_cur == NULL) {
899  	                    break;
900  	
901  	                } else if (g_str_has_prefix(arg_cur, "WARNING: ")) {
902  	                    prefix_len = sizeof("WARNING: ") - 1;
903  	                    msg_log_level = LOG_WARNING;
904  	
905  	                } else if (g_str_has_prefix(arg_cur, "INFO: ")) {
906  	                    prefix_len = sizeof("INFO: ") - 1;
907  	                    msg_log_level = LOG_INFO;
908  	
909  	                } else if (g_str_has_prefix(arg_cur, "DEBUG: ")) {
910  	                    prefix_len = sizeof("DEBUG: ") - 1;
911  	                    msg_log_level = LOG_DEBUG;
912  	
913  	                } else {
914  	                    break;
915  	                }
916  	
917  	                found = true;
918  	                if (ctx == NULL) {
919  	                    memmove(arg_cur, arg_cur + prefix_len,
920  	                            strlen(arg_cur + prefix_len) + 1);
921  	                }
922  	            }
923  	            break;
924  	        case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
925  	        case '0': case '1': case '2': case '3': case '4':
926  	        case '5': case '6': case '7': case '8': case '9':
927  	        case '*':
928  	            break;
929  	        case 'l':
930  	        case 'z':
931  	        case 't':
932  	        case 'j':
933  	        case 'd': case 'i':
934  	        case 'o':
935  	        case 'u':
936  	        case 'x': case 'X':
937  	        case 'e': case 'E':
938  	        case 'f': case 'F':
939  	        case 'g': case 'G':
940  	        case 'a': case 'A':
941  	        case 'c':
942  	        case 'p':
943  	            if (scan_state == escan_seenpercent) {
944  	                (void) va_arg(aq, void *);  /* skip forward */
945  	                scan_state = escan_seennothing;
946  	            }
947  	            break;
948  	        default:
949  	            scan_state = escan_seennothing;
950  	            break;
951  	        }
952  	    }
953  	
954  	    if (log_level != NULL) {
955  	        /* intention of the following offset is:
956  	           cibadmin -V -> start showing INFO labelled messages */
957  	        if (*log_level + 4 >= msg_log_level) {
958  	            vfprintf(stderr, fmt, ap);
959  	        }
960  	    } else {
961  	        PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
962  	    }
963  	
964  	    va_end(aq);
965  	    va_end(ap);
966  	}
967  	
968  	/*!
969  	 * \internal
970  	 * \brief Apply a single XSL transformation to given XML
971  	 *
972  	 * \param[in] xml        XML to transform
973  	 * \param[in] transform  XSL name
974  	 * \param[in] to_logs    If false, certain validation errors will be sent to
975  	 *                       stderr rather than logged
976  	 *
977  	 * \return Transformed XML on success, otherwise NULL
978  	 */
979  	static xmlNode *
980  	apply_transformation(const xmlNode *xml, const char *transform,
981  	                     gboolean to_logs)
982  	{
983  	    char *xform = NULL;
984  	    xmlNode *out = NULL;
985  	    xmlDocPtr res = NULL;
986  	    xsltStylesheet *xslt = NULL;
987  	
988  	    xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
989  	                                    transform);
990  	
991  	    /* for capturing, e.g., what's emitted via <xsl:message> */
992  	    if (to_logs) {
993  	        xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
994  	    } else {
995  	        xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
996  	    }
997  	
998  	    xslt = xsltParseStylesheetFile((const xmlChar *) xform);
999  	    CRM_CHECK(xslt != NULL, goto cleanup);
1000 	
1001 	    /* Caller allocates private data for final result document. Intermediate
1002 	     * result documents are temporary and don't need private data.
1003 	     */
1004 	    res = xsltApplyStylesheet(xslt, xml->doc, NULL);
1005 	    CRM_CHECK(res != NULL, goto cleanup);
1006 	
1007 	    xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
1008 	
1009 	    out = xmlDocGetRootElement(res);
1010 	
1011 	  cleanup:
1012 	    if (xslt) {
1013 	        xsltFreeStylesheet(xslt);
1014 	    }
1015 	
1016 	    free(xform);
1017 	
1018 	    return out;
1019 	}
1020 	
1021 	/*!
1022 	 * \internal
1023 	 * \brief Perform all transformations needed to upgrade XML to next schema
1024 	 *
1025 	 * \param[in] input_xml     XML to transform
1026 	 * \param[in] schema_index  Index of schema that successfully validates
1027 	 *                          \p original_xml
1028 	 * \param[in] to_logs       If false, certain validation errors will be sent to
1029 	 *                          stderr rather than logged
1030 	 *
1031 	 * \return XML result of schema transforms if successful, otherwise NULL
1032 	 */
1033 	static xmlNode *
1034 	apply_upgrade(const xmlNode *input_xml, int schema_index, gboolean to_logs)
1035 	{
1036 	    pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1037 	    pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
1038 	                                                      schema_index + 1);
1039 	
1040 	    xmlNode *old_xml = NULL;
1041 	    xmlNode *new_xml = NULL;
1042 	    xmlRelaxNGValidityErrorFunc error_handler = NULL;
1043 	
1044 	    pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
1045 	
1046 	    if (to_logs) {
1047 	        error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1048 	    }
1049 	
1050 	    for (GList *iter = schema->transforms; iter != NULL; iter = iter->next) {
1051 	        const struct dirent *entry = iter->data;
1052 	        const char *transform = entry->d_name;
1053 	
1054 	        pcmk__debug("Upgrading schema from %s to %s: applying XSL transform %s",
1055 	                    schema->name, upgraded_schema->name, transform);
1056 	
1057 	        new_xml = apply_transformation(input_xml, transform, to_logs);
1058 	        pcmk__xml_free(old_xml);
1059 	
1060 	        if (new_xml == NULL) {
1061 	            pcmk__err("XSL transform %s failed, aborting upgrade", transform);
1062 	            return NULL;
1063 	        }
1064 	        input_xml = new_xml;
1065 	        old_xml = new_xml;
1066 	    }
1067 	
1068 	    // Final result document from upgrade pipeline needs private data
1069 	    pcmk__assert(new_xml != NULL);
1070 	    pcmk__xml_new_private_data((xmlNode *) new_xml->doc);
1071 	
1072 	    // Ensure result validates with its new schema
1073 	    if (!validate_with(new_xml, upgraded_schema, error_handler,
1074 	                       GUINT_TO_POINTER(LOG_ERR))) {
1075 	        pcmk__err("Schema upgrade from %s to %s failed: XSL transform pipeline "
1076 	                  "produced an invalid configuration",
1077 	                  schema->name, upgraded_schema->name);
1078 	        pcmk__log_xml_debug(new_xml, "bad-transform-result");
1079 	        pcmk__xml_free(new_xml);
1080 	        return NULL;
1081 	    }
1082 	
1083 	    pcmk__info("Schema upgrade from %s to %s succeeded", schema->name,
1084 	               upgraded_schema->name);
1085 	    return new_xml;
1086 	}
1087 	
1088 	/*!
1089 	 * \internal
1090 	 * \brief Get the schema list entry corresponding to XML configuration
1091 	 *
1092 	 * \param[in] xml  CIB XML to check
1093 	 *
1094 	 * \return List entry of schema configured in \p xml
1095 	 */
1096 	static GList *
1097 	get_configured_schema(const xmlNode *xml)
1098 	{
1099 	    const char *schema_name = pcmk__xe_get(xml, PCMK_XA_VALIDATE_WITH);
1100 	
1101 	    pcmk__warn_if_schema_deprecated(schema_name);
1102 	    return pcmk__get_schema(schema_name);
1103 	}
1104 	
1105 	/*!
1106 	 * \brief Update CIB XML to latest schema that validates it
1107 	 *
1108 	 * \param[in,out] xml              XML to update (may be freed and replaced
1109 	 *                                 after being transformed)
1110 	 * \param[in]     max_schema_name  If not NULL, do not update \p xml to any
1111 	 *                                 schema later than this one
1112 	 * \param[in]     transform        If false, do not update \p xml to any schema
1113 	 *                                 that requires an XSL transform
1114 	 * \param[in]     to_logs          If false, certain validation errors will be
1115 	 *                                 sent to stderr rather than logged
1116 	 *
1117 	 * \return Standard Pacemaker return code
1118 	 */
1119 	int
1120 	pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
1121 	                    bool to_logs)
1122 	{
1123 	    int max_stable_schemas = xml_latest_schema_index();
1124 	    int max_schema_index = 0;
1125 	    int rc = pcmk_rc_ok;
1126 	    GList *entry = NULL;
1127 	    pcmk__schema_t *best_schema = NULL;
1128 	    pcmk__schema_t *original_schema = NULL;
1129 	    xmlRelaxNGValidityErrorFunc error_handler = 
1130 	        to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1131 	
1132 	    CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1133 	              return EINVAL);
1134 	
1135 	    if (max_schema_name != NULL) {
1136 	        GList *max_entry = pcmk__get_schema(max_schema_name);
1137 	
1138 	        if (max_entry != NULL) {
1139 	            pcmk__schema_t *max_schema = max_entry->data;
1140 	
1141 	            max_schema_index = max_schema->schema_index;
1142 	        }
1143 	    }
1144 	    if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1145 	        max_schema_index = max_stable_schemas;
1146 	    }
1147 	
1148 	    entry = get_configured_schema(*xml);
1149 	    if (entry == NULL) {
1150 	        return pcmk_rc_cib_corrupt;
1151 	    }
1152 	    original_schema = entry->data;
1153 	    if (original_schema->schema_index >= max_schema_index) {
1154 	        return pcmk_rc_ok;
1155 	    }
1156 	
1157 	    for (; entry != NULL; entry = entry->next) {
1158 	        pcmk__schema_t *current_schema = entry->data;
1159 	        xmlNode *upgrade = NULL;
1160 	
1161 	        if (current_schema->schema_index > max_schema_index) {
1162 	            break;
1163 	        }
1164 	
1165 	        if (!validate_with(*xml, current_schema, error_handler,
1166 	                           GUINT_TO_POINTER(LOG_ERR))) {
1167 	            pcmk__debug("Schema %s does not validate", current_schema->name);
1168 	            if (best_schema != NULL) {
1169 	                /* we've satisfied the validation, no need to check further */
1170 	                break;
1171 	            }
1172 	            rc = pcmk_rc_schema_validation;
1173 	            continue; // Try again with the next higher schema
1174 	        }
1175 	
1176 	        pcmk__debug("Schema %s validates", current_schema->name);
1177 	        rc = pcmk_rc_ok;
1178 	        best_schema = current_schema;
1179 	        if (current_schema->schema_index == max_schema_index) {
1180 	            break; // No further transformations possible
1181 	        }
1182 	
1183 	        // coverity[null_field] The index check ensures entry->next is not NULL
1184 	        if (!transform || (current_schema->transforms == NULL)
1185 	            || validate_with_silent(*xml, entry->next->data)) {
1186 	            /* The next schema either doesn't require a transform or validates
1187 	             * successfully even without the transform. Skip the transform and
1188 	             * try the next schema with the same XML.
1189 	             */
1190 	            continue;
1191 	        }
1192 	
1193 	        upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1194 	        if (upgrade == NULL) {
1195 	            /* The transform failed, so this schema can't be used. Later
1196 	             * schemas are unlikely to validate, but try anyway until we
1197 	             * run out of options.
1198 	             */
1199 	            rc = pcmk_rc_transform_failed;
1200 	        } else {
1201 	            best_schema = current_schema;
1202 	            pcmk__xml_free(*xml);
1203 	            *xml = upgrade;
1204 	        }
1205 	    }
1206 	
1207 	    if ((best_schema != NULL)
1208 	        && (best_schema->schema_index > original_schema->schema_index)) {
1209 	        pcmk__info("%s the configuration schema to %s",
1210 	                   (transform? "Transformed" : "Upgraded"), best_schema->name);
1211 	        pcmk__xe_set(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1212 	    }
1213 	    return rc;
1214 	}
1215 	
1216 	int
1217 	pcmk_update_configured_schema(xmlNode **xml)
1218 	{
1219 	    return pcmk__update_configured_schema(xml, true);
1220 	}
1221 	
1222 	/*!
1223 	 * \brief Update XML from its configured schema to the latest major series
1224 	 *
1225 	 * \param[in,out] xml      XML to update
1226 	 * \param[in]     to_logs  If false, certain validation errors will be
1227 	 *                         sent to stderr rather than logged
1228 	 *
1229 	 * \return Standard Pacemaker return code
1230 	 */
1231 	int
1232 	pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
1233 	{
1234 	    pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1235 	    pcmk__schema_t *original_schema = NULL;
1236 	    GList *entry = NULL;
1237 	
1238 	    if (xml == NULL) {
1239 	        return EINVAL;
1240 	    }
1241 	
1242 	    entry = get_configured_schema(*xml);
1243 	    if (entry == NULL) {
1244 	        return pcmk_rc_cib_corrupt;
1245 	    }
1246 	
1247 	    original_schema = entry->data;
1248 	    if (original_schema->schema_index < x_0_schema->schema_index) {
1249 	        // Current configuration schema is not acceptable, try to update
1250 	        xmlNode *converted = NULL;
1251 	        const char *new_schema_name = NULL;
1252 	        pcmk__schema_t *schema = NULL;
1253 	
1254 	        entry = NULL;
1255 	        converted = pcmk__xml_copy(NULL, *xml);
1256 	        if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1257 	            new_schema_name = pcmk__xe_get(converted, PCMK_XA_VALIDATE_WITH);
1258 	            entry = pcmk__get_schema(new_schema_name);
1259 	        }
1260 	        schema = (entry == NULL)? NULL : entry->data;
1261 	
1262 	        if ((schema == NULL)
1263 	            || (schema->schema_index < x_0_schema->schema_index)) {
1264 	            // Updated configuration schema is still not acceptable
1265 	
1266 	            if ((schema == NULL)
1267 	                || (schema->schema_index < original_schema->schema_index)) {
1268 	                // We couldn't validate any schema at all
1269 	                if (to_logs) {
1270 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1271 	                                     "%s schema) to at least %s because it "
1272 	                                     "does not validate with any schema from "
1273 	                                     "%s to the latest",
1274 	                                     original_schema->name,
1275 	                                     x_0_schema->name, original_schema->name);
1276 	                } else {
1277 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1278 	                                    "%s schema) to at least %s because it "
1279 	                                    "does not validate with any schema from "
1280 	                                    "%s to the latest\n",
1281 	                                    original_schema->name,
1282 	                                    x_0_schema->name, original_schema->name);
1283 	                }
1284 	            } else {
1285 	                // We updated configuration successfully, but still too low
1286 	                if (to_logs) {
1287 	                    pcmk__config_err("Cannot upgrade configuration (claiming "
1288 	                                     "%s schema) to at least %s because it "
1289 	                                     "would not upgrade past %s",
1290 	                                     original_schema->name, x_0_schema->name,
1291 	                                     pcmk__s(new_schema_name, "unspecified version"));
1292 	                } else {
1293 	                    fprintf(stderr, "Cannot upgrade configuration (claiming "
1294 	                                    "%s schema) to at least %s because it "
1295 	                                    "would not upgrade past %s\n",
1296 	                                    original_schema->name, x_0_schema->name,
1297 	                                    pcmk__s(new_schema_name, "unspecified version"));
1298 	                }
1299 	            }
1300 	
1301 	            g_clear_pointer(&converted, pcmk__xml_free);
1302 	            return pcmk_rc_transform_failed;
1303 	
1304 	        } else {
1305 	            // Updated configuration schema is acceptable
1306 	            pcmk__xml_free(*xml);
1307 	            *xml = converted;
1308 	
1309 	            if (schema->schema_index < xml_latest_schema_index()) {
1310 	                if (to_logs) {
1311 	                    pcmk__config_warn("Configuration with %s schema was "
1312 	                                      "internally upgraded to acceptable (but "
1313 	                                      "not most recent) %s",
1314 	                                      original_schema->name, schema->name);
1315 	                }
1316 	            } else if (to_logs) {
1317 	                pcmk__info("Configuration with %s schema was internally "
1318 	                           "upgraded to latest version %s",
1319 	                           original_schema->name, schema->name);
1320 	            }
1321 	        }
1322 	
1323 	    } else if (!to_logs) {
1324 	        pcmk__schema_t *none_schema = NULL;
1325 	
1326 	        entry = pcmk__get_schema(PCMK_VALUE_NONE);
1327 	        pcmk__assert((entry != NULL) && (entry->data != NULL));
1328 	
1329 	        none_schema = entry->data;
1330 	        if (original_schema->schema_index >= none_schema->schema_index) {
1331 	            // @COMPAT the none schema is deprecated since 2.1.8
1332 	            fprintf(stderr, "Schema validation of configuration is "
1333 	                            "disabled (support for " PCMK_XA_VALIDATE_WITH
1334 	                            " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1335 	                            " and will be removed in a future release)\n");
1336 	        }
1337 	    }
1338 	
1339 	    return pcmk_rc_ok;
1340 	}
1341 	
1342 	/*!
1343 	 * \internal
1344 	 * \brief Return a list of all schema files and any associated XSLT files
1345 	 *        later than the given one
1346 	 * \brief Return a list of all schema versions later than the given one
1347 	 *
1348 	 * \param[in] schema The schema to compare against (for example,
1349 	 *                   "pacemaker-3.1.rng" or "pacemaker-3.1")
1350 	 *
1351 	 * \note The caller is responsible for freeing both the returned list and
1352 	 *       the elements of the list
1353 	 */
1354 	GList *
1355 	pcmk__schema_files_later_than(const char *name)
1356 	{
1357 	    GList *lst = NULL;
1358 	    pcmk__schema_version_t ver;
1359 	
1360 	    if (!version_from_filename(name, &ver)) {
1361 	        return lst;
1362 	    }
1363 	
1364 	    for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1365 	         iter != NULL; iter = iter->prev) {
1366 	        pcmk__schema_t *schema = iter->data;
1367 	
1368 	        if (schema_cmp(ver, schema->version) != -1) {
1369 	            continue;
1370 	        }
1371 	
1372 	        for (GList *iter2 = g_list_last(schema->transforms); iter2 != NULL;
1373 	             iter2 = iter2->prev) {
1374 	
1375 	            const struct dirent *entry = iter2->data;
1376 	
1377 	            lst = g_list_prepend(lst, pcmk__str_copy(entry->d_name));
1378 	        }
1379 	
1380 	        lst = g_list_prepend(lst,
1381 	                             pcmk__assert_asprintf("%s.rng", schema->name));
1382 	    }
1383 	
1384 	    return lst;
1385 	}
1386 	
1387 	static void
1388 	append_href(xmlNode *xml, void *user_data)
1389 	{
1390 	    GList **list = user_data;
1391 	    char *href = pcmk__xe_get_copy(xml, "href");
1392 	
1393 	    if (href == NULL) {
1394 	        return;
1395 	    }
1396 	    *list = g_list_prepend(*list, href);
1397 	}
1398 	
1399 	static void
1400 	external_refs_in_schema(GList **list, const char *contents)
1401 	{
1402 	    /* local-name()= is needed to ignore the xmlns= setting at the top of
1403 	     * the XML file.  Otherwise, the xpath query will always return nothing.
1404 	     */
1405 	    const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1406 	    xmlNode *xml = pcmk__xml_parse(contents);
1407 	
1408 	    pcmk__xpath_foreach_result(xml->doc, search, append_href, list);
1409 	    pcmk__xml_free(xml);
1410 	}
1411 	
1412 	static int
1413 	read_file_contents(const char *file, char **contents)
1414 	{
1415 	    int rc = pcmk_rc_ok;
1416 	    char *path = NULL;
1417 	
1418 	    if (g_str_has_suffix(file, ".rng")) {
1419 	        path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1420 	    } else {
1421 	        path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1422 	    }
1423 	
1424 	    rc = pcmk__file_contents(path, contents);
1425 	
1426 	    free(path);
1427 	    return rc;
1428 	}
1429 	
1430 	static void
1431 	add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
1432 	{
1433 	    char *contents = NULL;
1434 	    char *path = NULL;
1435 	    xmlNode *file_node = NULL;
1436 	    GList *includes = NULL;
1437 	    int rc = pcmk_rc_ok;
1438 	
1439 	    /* If we already included this file, don't do so again. */
1440 	    if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1441 	        return;
1442 	    }
1443 	
1444 	    /* Ensure whatever file we were given has a suffix we know about.  If not,
1445 	     * just assume it's an RNG file.
1446 	     */
1447 	    if (!g_str_has_suffix(file, ".rng") && !g_str_has_suffix(file, ".xsl")) {
1448 	        path = pcmk__assert_asprintf("%s.rng", file);
1449 	    } else {
1450 	        path = pcmk__str_copy(file);
1451 	    }
1452 	
1453 	    rc = read_file_contents(path, &contents);
1454 	    if (rc != pcmk_rc_ok || contents == NULL) {
1455 	        pcmk__warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1456 	        free(path);
1457 	        return;
1458 	    }
1459 	
1460 	    /* Create a new <file path="..."> node with the contents of the file
1461 	     * as a CDATA block underneath it.
1462 	     */
1463 	    file_node = pcmk__xe_create(parent, PCMK__XE_FILE);
1464 	    pcmk__xe_set(file_node, PCMK_XA_PATH, path);
1465 	    *already_included = g_list_prepend(*already_included, path);
1466 	
1467 	    xmlAddChild(file_node,
1468 	                xmlNewCDataBlock(parent->doc, (const xmlChar *) contents,
1469 	                                 strlen(contents)));
1470 	
1471 	    /* Scan the file for any <externalRef> or <include> nodes and build up
1472 	     * a list of the files they reference.
1473 	     */
1474 	    external_refs_in_schema(&includes, contents);
1475 	
1476 	    /* For each referenced file, recurse to add it (and potentially anything it
1477 	     * references, ...) to the XML.
1478 	     */
1479 	    for (GList *iter = includes; iter != NULL; iter = iter->next) {
1480 	        add_schema_file_to_xml(parent, iter->data, already_included);
1481 	    }
1482 	
1483 	    free(contents);
1484 	    g_list_free_full(includes, free);
1485 	}
1486 	
1487 	/*!
1488 	 * \internal
1489 	 * \brief Add an XML schema file and all the files it references as children
1490 	 *        of a given XML node
1491 	 *
1492 	 * \param[in,out] parent            The parent XML node
1493 	 * \param[in] name                  The schema version to compare against
1494 	 *                                  (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
1495 	 * \param[in,out] already_included  A list of names that have already been added
1496 	 *                                  to the parent node.
1497 	 *
1498 	 * \note The caller is responsible for freeing both the returned list and
1499 	 *       the elements of the list
1500 	 */
1501 	void
1502 	pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
1503 	{
1504 	    xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1505 	
1506 	    pcmk__xe_set(schema_node, PCMK_XA_VERSION, name);
1507 	    add_schema_file_to_xml(schema_node, name, already_included);
1508 	
1509 	    if (schema_node->children == NULL) {
1510 	        // Not needed if empty. May happen if name was invalid, for example.
1511 	        pcmk__xml_free(schema_node);
1512 	    }
1513 	}
1514 	
1515 	/*!
1516 	 * \internal
1517 	 * \brief Return the directory containing any extra schema files that a
1518 	 *        Pacemaker Remote node fetched from the cluster
1519 	 */
1520 	const char *
1521 	pcmk__remote_schema_dir(void)
1522 	{
1523 	    const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1524 	
1525 	    if (pcmk__str_empty(dir)) {
1526 	        return PCMK__REMOTE_SCHEMA_DIR;
1527 	    }
1528 	
1529 	    return dir;
1530 	}
1531 	
1532 	/*!
1533 	 * \internal
1534 	 * \brief Warn if a given validation schema is deprecated
1535 	 *
1536 	 * \param[in] Schema name to check
1537 	 */
1538 	void
1539 	pcmk__warn_if_schema_deprecated(const char *schema)
1540 	{
1541 	    /* @COMPAT Disabling validation is deprecated since 2.1.8, but
1542 	     * resource-agents' ocf-shellfuncs (at least as of 4.15.1) uses it
1543 	     */
1544 	    if (pcmk__str_eq(schema, PCMK_VALUE_NONE, pcmk__str_none)) {
1545 	        pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1546 	                          "deprecated and will be removed in a future release "
1547 	                          "without the possibility of upgrades (manually edit "
1548 	                          "to use a supported schema)", schema);
1549 	    }
1550 	}
1551 	
1552 	// Deprecated functions kept only for backward API compatibility
1553 	// LCOV_EXCL_START
1554 	
1555 	#include <crm/common/xml_compat.h>
1556 	
1557 	gboolean
1558 	cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1559 	{
1560 	    int rc = pcmk__update_configured_schema(xml, to_logs);
1561 	
1562 	    if (best_version != NULL) {
1563 	        const char *name = pcmk__xe_get(*xml, PCMK_XA_VALIDATE_WITH);
1564 	
1565 	        if (name == NULL) {
1566 	            *best_version = -1;
1567 	        } else {
1568 	            GList *entry = pcmk__get_schema(name);
1569 	            pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1570 	
1571 	            *best_version = (schema == NULL)? -1 : schema->schema_index;
1572 	        }
1573 	    }
1574 	    return (rc == pcmk_rc_ok)? TRUE: FALSE;
1575 	}
1576 	
1577 	// LCOV_EXCL_STOP
1578 	// End deprecated API
1579