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(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
|
(1) Event path: |
Condition "xml == NULL", taking false branch. |
1238 if (xml == NULL) {
1239 return EINVAL;
1240 }
1241
1242 entry = get_configured_schema(*xml);
|
(2) Event path: |
Condition "entry == NULL", taking false branch. |
1243 if (entry == NULL) {
1244 return pcmk_rc_cib_corrupt;
1245 }
1246
1247 original_schema = entry->data;
|
(3) Event path: |
Condition "original_schema->schema_index < x_0_schema->schema_index", taking true branch. |
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);
|
(4) Event path: |
Condition "pcmk__update_schema(&converted, NULL, true /* 1 */, to_logs) == pcmk_rc_ok", taking false branch. |
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 }
|
(5) Event path: |
Condition "entry == NULL", taking true branch. |
1260 schema = (entry == NULL)? NULL : entry->data;
1261
|
(6) Event path: |
Condition "schema == NULL", taking true branch. |
1262 if ((schema == NULL)
1263 || (schema->schema_index < x_0_schema->schema_index)) {
1264 // Updated configuration schema is still not acceptable
1265
|
(7) Event path: |
Condition "schema == NULL", taking true branch. |
1266 if ((schema == NULL)
1267 || (schema->schema_index < original_schema->schema_index)) {
1268 // We couldn't validate any schema at all
|
(8) Event path: |
Condition "to_logs", taking true branch. |
1269 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. |
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);
|
(11) Event path: |
Falling through to end of if statement. |
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 }
|
(12) Event path: |
Falling through to end of if statement. |
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
|
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". |
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