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