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