This source file includes following definitions.
- G_GNUC_PRINTF
- xml_latest_schema_index
- get_highest_schema
- pcmk__highest_schema_name
- pcmk__find_x_0_schema
- version_from_filename
- schema_filter
- schema_cmp
- schema_cmp_directory
- add_schema
- wrap_libxslt
- transform_filter
- free_transform_list
- load_transforms_from_dir
- pcmk__load_schemas_from_dir
- schema_sort_GCompareFunc
- pcmk__sort_schemas
- pcmk__schema_init
- validate_with_relaxng
- free_schema
- pcmk__schema_cleanup
- pcmk__get_schema
- pcmk__cmp_schemas_by_name
- validate_with
- validate_with_silent
- pcmk__validate_xml
- pcmk__configured_schema_validates
- G_GNUC_PRINTF
- apply_transformation
- apply_upgrade
- get_configured_schema
- pcmk__update_schema
- pcmk_update_configured_schema
- pcmk__update_configured_schema
- pcmk__schema_files_later_than
- append_href
- external_refs_in_schema
- read_file_contents
- add_schema_file_to_xml
- pcmk__build_schema_xml_node
- pcmk__remote_schema_dir
- pcmk__warn_if_schema_deprecated
- cli_config_update
1
2
3
4
5
6
7
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 <sys/stat.h>
17 #include <stdarg.h>
18
19 #include <libxml/relaxng.h>
20 #include <libxslt/xslt.h>
21 #include <libxslt/transform.h>
22 #include <libxslt/security.h>
23 #include <libxslt/xsltutils.h>
24
25 #include <crm/common/xml.h>
26 #include <crm/common/xml_internal.h>
27
28 #include "crmcommon_private.h"
29
30 #define SCHEMA_ZERO { .v = { 0, 0 } }
31
32 #define schema_strdup_printf(prefix, version, suffix) \
33 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
34
35 typedef struct {
36 xmlRelaxNGPtr rng;
37 xmlRelaxNGValidCtxtPtr valid;
38 xmlRelaxNGParserCtxtPtr parser;
39 } relaxng_ctx_cache_t;
40
41 static GList *known_schemas = NULL;
42 static bool initialized = false;
43 static bool silent_logging = FALSE;
44
45 static void G_GNUC_PRINTF(2, 3)
46 xml_log(int priority, const char *fmt, ...)
47 {
48 va_list ap;
49
50 va_start(ap, fmt);
51 if (silent_logging == FALSE) {
52
53 PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
54 }
55 va_end(ap);
56 }
57
58 static int
59 xml_latest_schema_index(void)
60 {
61
62
63
64
65
66
67
68 return g_list_length(known_schemas) - 2;
69 }
70
71
72
73
74
75
76
77 static GList *
78 get_highest_schema(void)
79 {
80
81
82
83
84 GList *entry = pcmk__get_schema("none");
85
86 pcmk__assert((entry != NULL) && (entry->prev != NULL));
87 return entry->prev;
88 }
89
90
91
92
93
94
95
96 const char *
97 pcmk__highest_schema_name(void)
98 {
99 GList *entry = get_highest_schema();
100
101 return ((pcmk__schema_t *)(entry->data))->name;
102 }
103
104
105
106
107
108
109
110 GList *
111 pcmk__find_x_0_schema(void)
112 {
113 #if defined(PCMK__UNIT_TESTING)
114
115
116
117
118 GList *x_0_entry = NULL;
119 #else
120 static GList *x_0_entry = NULL;
121 #endif
122
123 pcmk__schema_t *highest_schema = NULL;
124
125 if (x_0_entry != NULL) {
126 return x_0_entry;
127 }
128 x_0_entry = get_highest_schema();
129 highest_schema = x_0_entry->data;
130
131 for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
132 pcmk__schema_t *schema = iter->data;
133
134
135
136
137
138 if (schema->version.v[0] < highest_schema->version.v[0]) {
139 x_0_entry = iter->next;
140 break;
141 }
142
143
144
145
146 if (iter->prev == NULL) {
147 x_0_entry = known_schemas->data;
148 break;
149 }
150 }
151 return x_0_entry;
152 }
153
154 static inline bool
155 version_from_filename(const char *filename, pcmk__schema_version_t *version)
156 {
157 if (pcmk__ends_with(filename, ".rng")) {
158 return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2;
159 } else {
160 return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2;
161 }
162 }
163
164 static int
165 schema_filter(const struct dirent *a)
166 {
167 int rc = 0;
168 pcmk__schema_version_t version = SCHEMA_ZERO;
169
170 if (strstr(a->d_name, "pacemaker-") != a->d_name) {
171
172
173 } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
174
175
176 } else if (!version_from_filename(a->d_name, &version)) {
177
178
179 } else {
180
181 rc = 1;
182 }
183
184 return rc;
185 }
186
187 static int
188 schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version)
189 {
190 for (int i = 0; i < 2; ++i) {
191 if (a_version.v[i] < b_version.v[i]) {
192 return -1;
193 } else if (a_version.v[i] > b_version.v[i]) {
194 return 1;
195 }
196 }
197 return 0;
198 }
199
200 static int
201 schema_cmp_directory(const struct dirent **a, const struct dirent **b)
202 {
203 pcmk__schema_version_t a_version = SCHEMA_ZERO;
204 pcmk__schema_version_t b_version = SCHEMA_ZERO;
205
206 if (!version_from_filename(a[0]->d_name, &a_version)
207 || !version_from_filename(b[0]->d_name, &b_version)) {
208
209 return 0;
210 }
211
212 return schema_cmp(a_version, b_version);
213 }
214
215
216
217
218
219 static void
220 add_schema(enum pcmk__schema_validator validator,
221 const pcmk__schema_version_t *version, const char *name,
222 GList *transforms)
223 {
224 pcmk__schema_t *schema = NULL;
225
226 schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
227
228 schema->validator = validator;
229 schema->version.v[0] = version->v[0];
230 schema->version.v[1] = version->v[1];
231 schema->transforms = transforms;
232
233
234 if (version->v[0] || version->v[1]) {
235 schema->name = schema_strdup_printf("pacemaker-", *version, "");
236 } else {
237 schema->name = pcmk__str_copy(name);
238 }
239
240 known_schemas = g_list_prepend(known_schemas, schema);
241 }
242
243 static void
244 wrap_libxslt(bool finalize)
245 {
246 static xsltSecurityPrefsPtr secprefs;
247 int ret = 0;
248
249
250 if (!finalize) {
251 pcmk__assert(secprefs == NULL);
252 secprefs = xsltNewSecurityPrefs();
253 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
254 xsltSecurityForbid)
255 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
256 xsltSecurityForbid)
257 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
258 xsltSecurityForbid)
259 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
260 xsltSecurityForbid);
261 if (ret != 0) {
262 return;
263 }
264 } else {
265 xsltFreeSecurityPrefs(secprefs);
266 secprefs = NULL;
267 }
268
269
270 if (finalize) {
271 xsltCleanupGlobals();
272 }
273 }
274
275
276
277
278
279
280
281
282
283
284 static int
285 transform_filter(const struct dirent *entry)
286 {
287 return pcmk__str_eq(entry->d_name,
288 "upgrade-[[:digit:]]+.[[:digit:]]+-[[:digit:]]+.xsl",
289 pcmk__str_regex)? 1 : 0;
290 }
291
292
293
294
295
296
297
298 static void
299 free_transform_list(void *data)
300 {
301 g_list_free_full((GList *) data, free);
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 static GHashTable *
319 load_transforms_from_dir(const char *dir)
320 {
321 struct dirent **namelist = NULL;
322 int num_matches = scandir(dir, &namelist, transform_filter, versionsort);
323 GHashTable *transforms = pcmk__strkey_table(free, free_transform_list);
324
325 for (int i = 0; i < num_matches; i++) {
326 pcmk__schema_version_t version = SCHEMA_ZERO;
327 int order = 0;
328
329 if (sscanf(namelist[i]->d_name, "upgrade-%hhu.%hhu-%d.xsl",
330 &(version.v[0]), &(version.v[1]), &order) == 3) {
331
332 char *version_s = crm_strdup_printf("%hhu.%hhu",
333 version.v[0], version.v[1]);
334 GList *list = g_hash_table_lookup(transforms, version_s);
335
336 if (list == NULL) {
337
338
339
340
341
342
343
344 list = g_list_append(list, namelist[i]);
345 g_hash_table_insert(transforms, version_s, list);
346
347 } else {
348 list = g_list_append(list, namelist[i]);
349 free(version_s);
350 }
351
352 } else {
353
354 free(namelist[i]);
355 }
356 }
357
358 free(namelist);
359 return transforms;
360 }
361
362 void
363 pcmk__load_schemas_from_dir(const char *dir)
364 {
365 int lpc, max;
366 struct dirent **namelist = NULL;
367 GHashTable *transforms = NULL;
368
369 max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
370 if (max < 0) {
371 crm_warn("Could not load schemas from %s: %s", dir, strerror(errno));
372 return;
373 }
374
375
376 transforms = load_transforms_from_dir(dir);
377
378 for (lpc = 0; lpc < max; lpc++) {
379 pcmk__schema_version_t version = SCHEMA_ZERO;
380
381 if (version_from_filename(namelist[lpc]->d_name, &version)) {
382 char *version_s = crm_strdup_printf("%hhu.%hhu",
383 version.v[0], version.v[1]);
384 char *orig_key = NULL;
385 GList *transform_list = NULL;
386
387
388 g_hash_table_lookup_extended(transforms, version_s,
389 (gpointer *) &orig_key,
390 (gpointer *) &transform_list);
391 g_hash_table_steal(transforms, version_s);
392
393 add_schema(pcmk__schema_validator_rng, &version, NULL,
394 transform_list);
395
396 free(version_s);
397 free(orig_key);
398
399 } else {
400
401 crm_warn("Skipping schema '%s': could not parse version",
402 namelist[lpc]->d_name);
403 }
404 }
405
406 for (lpc = 0; lpc < max; lpc++) {
407 free(namelist[lpc]);
408 }
409
410 free(namelist);
411 g_hash_table_destroy(transforms);
412 }
413
414 static gint
415 schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
416 {
417 const pcmk__schema_t *schema_a = a;
418 const pcmk__schema_t *schema_b = b;
419
420
421 if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
422 return 1;
423 } else if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
424 return -1;
425 } else {
426 return schema_cmp(schema_a->version, schema_b->version);
427 }
428 }
429
430
431
432
433
434
435
436
437
438
439 void
440 pcmk__sort_schemas(void)
441 {
442 known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
443 }
444
445
446
447
448
449
450
451
452 void
453 pcmk__schema_init(void)
454 {
455 if (!initialized) {
456 const char *remote_schema_dir = pcmk__remote_schema_dir();
457 char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
458 const pcmk__schema_version_t zero = SCHEMA_ZERO;
459 int schema_index = 0;
460
461 initialized = true;
462
463 wrap_libxslt(false);
464
465 pcmk__load_schemas_from_dir(base);
466 pcmk__load_schemas_from_dir(remote_schema_dir);
467 free(base);
468
469
470 add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL);
471
472
473
474
475
476 pcmk__sort_schemas();
477
478
479 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
480 pcmk__schema_t *schema = iter->data;
481
482 crm_debug("Loaded schema %d: %s", schema_index, schema->name);
483 schema->schema_index = schema_index++;
484 }
485 }
486 }
487
488 static bool
489 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
490 void *error_handler_context, const char *relaxng_file,
491 relaxng_ctx_cache_t **cached_ctx)
492 {
493 int rc = 0;
494 bool valid = true;
495 relaxng_ctx_cache_t *ctx = NULL;
496
497 CRM_CHECK(doc != NULL, return false);
498 CRM_CHECK(relaxng_file != NULL, return false);
499
500 if (cached_ctx && *cached_ctx) {
501 ctx = *cached_ctx;
502
503 } else {
504 crm_debug("Creating RNG parser context");
505 ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
506
507 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
508 CRM_CHECK(ctx->parser != NULL, goto cleanup);
509
510 if (error_handler) {
511 xmlRelaxNGSetParserErrors(ctx->parser,
512 (xmlRelaxNGValidityErrorFunc) error_handler,
513 (xmlRelaxNGValidityWarningFunc) error_handler,
514 error_handler_context);
515 } else {
516 xmlRelaxNGSetParserErrors(ctx->parser,
517 (xmlRelaxNGValidityErrorFunc) fprintf,
518 (xmlRelaxNGValidityWarningFunc) fprintf,
519 stderr);
520 }
521
522 ctx->rng = xmlRelaxNGParse(ctx->parser);
523 CRM_CHECK(ctx->rng != NULL,
524 crm_err("Could not find/parse %s", relaxng_file);
525 goto cleanup);
526
527 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
528 CRM_CHECK(ctx->valid != NULL, goto cleanup);
529
530 if (error_handler) {
531 xmlRelaxNGSetValidErrors(ctx->valid,
532 (xmlRelaxNGValidityErrorFunc) error_handler,
533 (xmlRelaxNGValidityWarningFunc) error_handler,
534 error_handler_context);
535 } else {
536 xmlRelaxNGSetValidErrors(ctx->valid,
537 (xmlRelaxNGValidityErrorFunc) fprintf,
538 (xmlRelaxNGValidityWarningFunc) fprintf,
539 stderr);
540 }
541 }
542
543 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
544 if (rc > 0) {
545 valid = false;
546
547 } else if (rc < 0) {
548 crm_err("Internal libxml error during validation");
549 }
550
551 cleanup:
552
553 if (cached_ctx) {
554 *cached_ctx = ctx;
555
556 } else {
557 if (ctx->parser != NULL) {
558 xmlRelaxNGFreeParserCtxt(ctx->parser);
559 }
560 if (ctx->valid != NULL) {
561 xmlRelaxNGFreeValidCtxt(ctx->valid);
562 }
563 if (ctx->rng != NULL) {
564 xmlRelaxNGFree(ctx->rng);
565 }
566 free(ctx);
567 }
568
569 return valid;
570 }
571
572 static void
573 free_schema(gpointer data)
574 {
575 pcmk__schema_t *schema = data;
576 relaxng_ctx_cache_t *ctx = NULL;
577
578 switch (schema->validator) {
579 case pcmk__schema_validator_none:
580 break;
581
582 case pcmk__schema_validator_rng:
583 ctx = (relaxng_ctx_cache_t *) schema->cache;
584 if (ctx == NULL) {
585 break;
586 }
587
588 if (ctx->parser != NULL) {
589 xmlRelaxNGFreeParserCtxt(ctx->parser);
590 }
591
592 if (ctx->valid != NULL) {
593 xmlRelaxNGFreeValidCtxt(ctx->valid);
594 }
595
596 if (ctx->rng != NULL) {
597 xmlRelaxNGFree(ctx->rng);
598 }
599
600 free(ctx);
601 schema->cache = NULL;
602 break;
603 }
604
605 free(schema->name);
606 g_list_free_full(schema->transforms, free);
607 free(schema);
608 }
609
610
611
612
613
614 void
615 pcmk__schema_cleanup(void)
616 {
617 if (known_schemas != NULL) {
618 g_list_free_full(known_schemas, free_schema);
619 known_schemas = NULL;
620 }
621 initialized = false;
622
623 wrap_libxslt(true);
624 }
625
626
627
628
629
630
631
632
633
634 GList *
635 pcmk__get_schema(const char *name)
636 {
637 if (name == NULL) {
638 return NULL;
639 }
640 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
641 pcmk__schema_t *schema = iter->data;
642
643 if (pcmk__str_eq(name, schema->name, pcmk__str_none)) {
644 return iter;
645 }
646 }
647 return NULL;
648 }
649
650
651
652
653
654
655
656
657
658
659
660
661 int
662 pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
663 {
664 GList *entry1 = pcmk__get_schema(schema1_name);
665 GList *entry2 = pcmk__get_schema(schema2_name);
666
667 if (entry1 == NULL) {
668 return (entry2 == NULL)? 0 : -1;
669
670 } else if (entry2 == NULL) {
671 return 1;
672
673 } else {
674 pcmk__schema_t *schema1 = entry1->data;
675 pcmk__schema_t *schema2 = entry2->data;
676
677 return schema1->schema_index - schema2->schema_index;
678 }
679 }
680
681 static bool
682 validate_with(xmlNode *xml, pcmk__schema_t *schema,
683 xmlRelaxNGValidityErrorFunc error_handler,
684 void *error_handler_context)
685 {
686 bool valid = false;
687 char *file = NULL;
688 relaxng_ctx_cache_t **cache = NULL;
689
690 if (schema == NULL) {
691 return false;
692 }
693
694 if (schema->validator == pcmk__schema_validator_none) {
695 return true;
696 }
697
698 file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
699 schema->name);
700
701 crm_trace("Validating with %s (type=%d)",
702 pcmk__s(file, "missing schema"), schema->validator);
703 switch (schema->validator) {
704 case pcmk__schema_validator_rng:
705 cache = (relaxng_ctx_cache_t **) &(schema->cache);
706 valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
707 break;
708 default:
709 crm_err("Unknown validator type: %d", schema->validator);
710 break;
711 }
712
713 free(file);
714 return valid;
715 }
716
717 static bool
718 validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
719 {
720 bool rc, sl_backup = silent_logging;
721 silent_logging = TRUE;
722 rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
723 silent_logging = sl_backup;
724 return rc;
725 }
726
727 bool
728 pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
729 xmlRelaxNGValidityErrorFunc error_handler,
730 void *error_handler_context)
731 {
732 GList *entry = NULL;
733 pcmk__schema_t *schema = NULL;
734
735 CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
736
737 if (validation == NULL) {
738 validation = crm_element_value(xml_blob, PCMK_XA_VALIDATE_WITH);
739 }
740 pcmk__warn_if_schema_deprecated(validation);
741
742 entry = pcmk__get_schema(validation);
743 if (entry == NULL) {
744 pcmk__config_err("Cannot validate CIB with %s " PCMK_XA_VALIDATE_WITH
745 " (manually edit to use a known schema)",
746 ((validation == NULL)? "missing" : "unknown"));
747 return false;
748 }
749
750 schema = entry->data;
751 return validate_with(xml_blob, schema, error_handler,
752 error_handler_context);
753 }
754
755
756
757
758
759
760
761
762
763 bool
764 pcmk__configured_schema_validates(xmlNode *xml)
765 {
766 return pcmk__validate_xml(xml, NULL,
767 (xmlRelaxNGValidityErrorFunc) xml_log,
768 GUINT_TO_POINTER(LOG_ERR));
769 }
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794 static void G_GNUC_PRINTF(2, 3)
795 cib_upgrade_err(void *ctx, const char *fmt, ...)
796 {
797 va_list ap, aq;
798 char *arg_cur;
799
800 bool found = false;
801 const char *fmt_iter = fmt;
802 uint8_t msg_log_level = LOG_WARNING;
803 const unsigned * log_level = (const unsigned *) ctx;
804 enum {
805 escan_seennothing,
806 escan_seenpercent,
807 } scan_state = escan_seennothing;
808
809 va_start(ap, fmt);
810 va_copy(aq, ap);
811
812 while (!found && *fmt_iter != '\0') {
813
814 switch (*fmt_iter++) {
815 case '%':
816 if (scan_state == escan_seennothing) {
817 scan_state = escan_seenpercent;
818 } else if (scan_state == escan_seenpercent) {
819 scan_state = escan_seennothing;
820 }
821 break;
822 case 's':
823 if (scan_state == escan_seenpercent) {
824 size_t prefix_len = 0;
825
826 scan_state = escan_seennothing;
827 arg_cur = va_arg(aq, char *);
828
829 if (pcmk__starts_with(arg_cur, "WARNING: ")) {
830 prefix_len = sizeof("WARNING: ") - 1;
831 msg_log_level = LOG_WARNING;
832
833 } else if (pcmk__starts_with(arg_cur, "INFO: ")) {
834 prefix_len = sizeof("INFO: ") - 1;
835 msg_log_level = LOG_INFO;
836
837 } else if (pcmk__starts_with(arg_cur, "DEBUG: ")) {
838 prefix_len = sizeof("DEBUG: ") - 1;
839 msg_log_level = LOG_DEBUG;
840
841 } else {
842 break;
843 }
844
845 found = true;
846 if (ctx == NULL) {
847 memmove(arg_cur, arg_cur + prefix_len,
848 strlen(arg_cur + prefix_len) + 1);
849 }
850 }
851 break;
852 case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
853 case '0': case '1': case '2': case '3': case '4':
854 case '5': case '6': case '7': case '8': case '9':
855 case '*':
856 break;
857 case 'l':
858 case 'z':
859 case 't':
860 case 'j':
861 case 'd': case 'i':
862 case 'o':
863 case 'u':
864 case 'x': case 'X':
865 case 'e': case 'E':
866 case 'f': case 'F':
867 case 'g': case 'G':
868 case 'a': case 'A':
869 case 'c':
870 case 'p':
871 if (scan_state == escan_seenpercent) {
872 (void) va_arg(aq, void *);
873 scan_state = escan_seennothing;
874 }
875 break;
876 default:
877 scan_state = escan_seennothing;
878 break;
879 }
880 }
881
882 if (log_level != NULL) {
883
884
885 if (*log_level + 4 >= msg_log_level) {
886 vfprintf(stderr, fmt, ap);
887 }
888 } else {
889 PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
890 }
891
892 va_end(aq);
893 va_end(ap);
894 }
895
896
897
898
899
900
901
902
903
904
905
906
907 static xmlNode *
908 apply_transformation(const xmlNode *xml, const char *transform,
909 gboolean to_logs)
910 {
911 char *xform = NULL;
912 xmlNode *out = NULL;
913 xmlDocPtr res = NULL;
914 xsltStylesheet *xslt = NULL;
915
916 xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
917 transform);
918
919
920 if (to_logs) {
921 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
922 } else {
923 xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
924 }
925
926 xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
927 CRM_CHECK(xslt != NULL, goto cleanup);
928
929
930
931
932 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
933 CRM_CHECK(res != NULL, goto cleanup);
934
935 xsltSetGenericErrorFunc(NULL, NULL);
936
937 out = xmlDocGetRootElement(res);
938
939 cleanup:
940 if (xslt) {
941 xsltFreeStylesheet(xslt);
942 }
943
944 free(xform);
945
946 return out;
947 }
948
949
950
951
952
953
954
955
956
957
958
959
960
961 static xmlNode *
962 apply_upgrade(const xmlNode *input_xml, int schema_index, gboolean to_logs)
963 {
964 pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
965 pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
966 schema_index + 1);
967
968 xmlNode *old_xml = NULL;
969 xmlNode *new_xml = NULL;
970 xmlRelaxNGValidityErrorFunc error_handler = NULL;
971
972 pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
973
974 if (to_logs) {
975 error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
976 }
977
978 for (GList *iter = schema->transforms; iter != NULL; iter = iter->next) {
979 const struct dirent *entry = iter->data;
980 const char *transform = entry->d_name;
981
982 crm_debug("Upgrading schema from %s to %s: applying XSL transform %s",
983 schema->name, upgraded_schema->name, transform);
984
985 new_xml = apply_transformation(input_xml, transform, to_logs);
986 pcmk__xml_free(old_xml);
987
988 if (new_xml == NULL) {
989 crm_err("XSL transform %s failed, aborting upgrade", transform);
990 return NULL;
991 }
992 input_xml = new_xml;
993 old_xml = new_xml;
994 }
995
996
997 pcmk__xml_new_private_data((xmlNode *) new_xml->doc);
998
999
1000 if (!validate_with(new_xml, upgraded_schema, error_handler,
1001 GUINT_TO_POINTER(LOG_ERR))) {
1002 crm_err("Schema upgrade from %s to %s failed: "
1003 "XSL transform pipeline produced an invalid configuration",
1004 schema->name, upgraded_schema->name);
1005 crm_log_xml_debug(new_xml, "bad-transform-result");
1006 pcmk__xml_free(new_xml);
1007 return NULL;
1008 }
1009
1010 crm_info("Schema upgrade from %s to %s succeeded",
1011 schema->name, upgraded_schema->name);
1012 return new_xml;
1013 }
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023 static GList *
1024 get_configured_schema(const xmlNode *xml)
1025 {
1026 const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1027
1028 pcmk__warn_if_schema_deprecated(schema_name);
1029 return pcmk__get_schema(schema_name);
1030 }
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046 int
1047 pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
1048 bool to_logs)
1049 {
1050 int max_stable_schemas = xml_latest_schema_index();
1051 int max_schema_index = 0;
1052 int rc = pcmk_rc_ok;
1053 GList *entry = NULL;
1054 pcmk__schema_t *best_schema = NULL;
1055 pcmk__schema_t *original_schema = NULL;
1056 xmlRelaxNGValidityErrorFunc error_handler =
1057 to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1058
1059 CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1060 return EINVAL);
1061
1062 if (max_schema_name != NULL) {
1063 GList *max_entry = pcmk__get_schema(max_schema_name);
1064
1065 if (max_entry != NULL) {
1066 pcmk__schema_t *max_schema = max_entry->data;
1067
1068 max_schema_index = max_schema->schema_index;
1069 }
1070 }
1071 if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1072 max_schema_index = max_stable_schemas;
1073 }
1074
1075 entry = get_configured_schema(*xml);
1076 if (entry == NULL) {
1077 return pcmk_rc_cib_corrupt;
1078 }
1079 original_schema = entry->data;
1080 if (original_schema->schema_index >= max_schema_index) {
1081 return pcmk_rc_ok;
1082 }
1083
1084 for (; entry != NULL; entry = entry->next) {
1085 pcmk__schema_t *current_schema = entry->data;
1086 xmlNode *upgrade = NULL;
1087
1088 if (current_schema->schema_index > max_schema_index) {
1089 break;
1090 }
1091
1092 if (!validate_with(*xml, current_schema, error_handler,
1093 GUINT_TO_POINTER(LOG_ERR))) {
1094 crm_debug("Schema %s does not validate", current_schema->name);
1095 if (best_schema != NULL) {
1096
1097 break;
1098 }
1099 rc = pcmk_rc_schema_validation;
1100 continue;
1101 }
1102
1103 crm_debug("Schema %s validates", current_schema->name);
1104 rc = pcmk_rc_ok;
1105 best_schema = current_schema;
1106 if (current_schema->schema_index == max_schema_index) {
1107 break;
1108 }
1109
1110 if (!transform || (current_schema->transforms == NULL)
1111 || validate_with_silent(*xml, entry->next->data)) {
1112
1113
1114
1115
1116 continue;
1117 }
1118
1119 upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1120 if (upgrade == NULL) {
1121
1122
1123
1124
1125 rc = pcmk_rc_transform_failed;
1126 } else {
1127 best_schema = current_schema;
1128 pcmk__xml_free(*xml);
1129 *xml = upgrade;
1130 }
1131 }
1132
1133 if ((best_schema != NULL)
1134 && (best_schema->schema_index > original_schema->schema_index)) {
1135 crm_info("%s the configuration schema to %s",
1136 (transform? "Transformed" : "Upgraded"),
1137 best_schema->name);
1138 crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1139 }
1140 return rc;
1141 }
1142
1143 int
1144 pcmk_update_configured_schema(xmlNode **xml)
1145 {
1146 return pcmk__update_configured_schema(xml, true);
1147 }
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 int
1159 pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
1160 {
1161 pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1162 pcmk__schema_t *original_schema = NULL;
1163 GList *entry = NULL;
1164
1165 if (xml == NULL) {
1166 return EINVAL;
1167 }
1168
1169 entry = get_configured_schema(*xml);
1170 if (entry == NULL) {
1171 return pcmk_rc_cib_corrupt;
1172 }
1173
1174 original_schema = entry->data;
1175 if (original_schema->schema_index < x_0_schema->schema_index) {
1176
1177 xmlNode *converted = NULL;
1178 const char *new_schema_name = NULL;
1179 pcmk__schema_t *schema = NULL;
1180
1181 entry = NULL;
1182 converted = pcmk__xml_copy(NULL, *xml);
1183 if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1184 new_schema_name = crm_element_value(converted,
1185 PCMK_XA_VALIDATE_WITH);
1186 entry = pcmk__get_schema(new_schema_name);
1187 }
1188 schema = (entry == NULL)? NULL : entry->data;
1189
1190 if ((schema == NULL)
1191 || (schema->schema_index < x_0_schema->schema_index)) {
1192
1193
1194 if ((schema == NULL)
1195 || (schema->schema_index < original_schema->schema_index)) {
1196
1197 if (to_logs) {
1198 pcmk__config_err("Cannot upgrade configuration (claiming "
1199 "%s schema) to at least %s because it "
1200 "does not validate with any schema from "
1201 "%s to the latest",
1202 original_schema->name,
1203 x_0_schema->name, original_schema->name);
1204 } else {
1205 fprintf(stderr, "Cannot upgrade configuration (claiming "
1206 "%s schema) to at least %s because it "
1207 "does not validate with any schema from "
1208 "%s to the latest\n",
1209 original_schema->name,
1210 x_0_schema->name, original_schema->name);
1211 }
1212 } else {
1213
1214 if (to_logs) {
1215 pcmk__config_err("Cannot upgrade configuration (claiming "
1216 "%s schema) to at least %s because it "
1217 "would not upgrade past %s",
1218 original_schema->name, x_0_schema->name,
1219 pcmk__s(new_schema_name, "unspecified version"));
1220 } else {
1221 fprintf(stderr, "Cannot upgrade configuration (claiming "
1222 "%s schema) to at least %s because it "
1223 "would not upgrade past %s\n",
1224 original_schema->name, x_0_schema->name,
1225 pcmk__s(new_schema_name, "unspecified version"));
1226 }
1227 }
1228
1229 pcmk__xml_free(converted);
1230 converted = NULL;
1231 return pcmk_rc_transform_failed;
1232
1233 } else {
1234
1235 pcmk__xml_free(*xml);
1236 *xml = converted;
1237
1238 if (schema->schema_index < xml_latest_schema_index()) {
1239 if (to_logs) {
1240 pcmk__config_warn("Configuration with %s schema was "
1241 "internally upgraded to acceptable (but "
1242 "not most recent) %s",
1243 original_schema->name, schema->name);
1244 }
1245 } else if (to_logs) {
1246 crm_info("Configuration with %s schema was internally "
1247 "upgraded to latest version %s",
1248 original_schema->name, schema->name);
1249 }
1250 }
1251
1252 } else if (!to_logs) {
1253 pcmk__schema_t *none_schema = NULL;
1254
1255 entry = pcmk__get_schema(PCMK_VALUE_NONE);
1256 pcmk__assert((entry != NULL) && (entry->data != NULL));
1257
1258 none_schema = entry->data;
1259 if (original_schema->schema_index >= none_schema->schema_index) {
1260
1261 fprintf(stderr, "Schema validation of configuration is "
1262 "disabled (support for " PCMK_XA_VALIDATE_WITH
1263 " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1264 " and will be removed in a future release)\n");
1265 }
1266 }
1267
1268 return pcmk_rc_ok;
1269 }
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283 GList *
1284 pcmk__schema_files_later_than(const char *name)
1285 {
1286 GList *lst = NULL;
1287 pcmk__schema_version_t ver;
1288
1289 if (!version_from_filename(name, &ver)) {
1290 return lst;
1291 }
1292
1293 for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1294 iter != NULL; iter = iter->prev) {
1295 pcmk__schema_t *schema = iter->data;
1296
1297 if (schema_cmp(ver, schema->version) != -1) {
1298 continue;
1299 }
1300
1301 for (GList *iter2 = g_list_last(schema->transforms); iter2 != NULL;
1302 iter2 = iter2->prev) {
1303
1304 const struct dirent *entry = iter2->data;
1305
1306 lst = g_list_prepend(lst, pcmk__str_copy(entry->d_name));
1307 }
1308
1309 lst = g_list_prepend(lst, crm_strdup_printf("%s.rng", schema->name));
1310 }
1311
1312 return lst;
1313 }
1314
1315 static void
1316 append_href(xmlNode *xml, void *user_data)
1317 {
1318 GList **list = user_data;
1319 char *href = crm_element_value_copy(xml, "href");
1320
1321 if (href == NULL) {
1322 return;
1323 }
1324 *list = g_list_prepend(*list, href);
1325 }
1326
1327 static void
1328 external_refs_in_schema(GList **list, const char *contents)
1329 {
1330
1331
1332
1333 const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1334 xmlNode *xml = pcmk__xml_parse(contents);
1335
1336 crm_foreach_xpath_result(xml, search, append_href, list);
1337 pcmk__xml_free(xml);
1338 }
1339
1340 static int
1341 read_file_contents(const char *file, char **contents)
1342 {
1343 int rc = pcmk_rc_ok;
1344 char *path = NULL;
1345
1346 if (pcmk__ends_with(file, ".rng")) {
1347 path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1348 } else {
1349 path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1350 }
1351
1352 rc = pcmk__file_contents(path, contents);
1353
1354 free(path);
1355 return rc;
1356 }
1357
1358 static void
1359 add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
1360 {
1361 char *contents = NULL;
1362 char *path = NULL;
1363 xmlNode *file_node = NULL;
1364 GList *includes = NULL;
1365 int rc = pcmk_rc_ok;
1366
1367
1368 if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1369 return;
1370 }
1371
1372
1373
1374
1375 if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
1376 path = crm_strdup_printf("%s.rng", file);
1377 } else {
1378 path = pcmk__str_copy(file);
1379 }
1380
1381 rc = read_file_contents(path, &contents);
1382 if (rc != pcmk_rc_ok || contents == NULL) {
1383 crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1384 free(path);
1385 return;
1386 }
1387
1388
1389
1390
1391 file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
1392 crm_xml_add(file_node, PCMK_XA_PATH, path);
1393 *already_included = g_list_prepend(*already_included, path);
1394
1395 xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
1396 strlen(contents)));
1397
1398
1399
1400
1401 external_refs_in_schema(&includes, contents);
1402
1403
1404
1405
1406 for (GList *iter = includes; iter != NULL; iter = iter->next) {
1407 add_schema_file_to_xml(parent, iter->data, already_included);
1408 }
1409
1410 free(contents);
1411 g_list_free_full(includes, free);
1412 }
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428 void
1429 pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
1430 {
1431 xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1432
1433 crm_xml_add(schema_node, PCMK_XA_VERSION, name);
1434 add_schema_file_to_xml(schema_node, name, already_included);
1435
1436 if (schema_node->children == NULL) {
1437
1438 pcmk__xml_free(schema_node);
1439 }
1440 }
1441
1442
1443
1444
1445
1446
1447 const char *
1448 pcmk__remote_schema_dir(void)
1449 {
1450 const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1451
1452 if (pcmk__str_empty(dir)) {
1453 return PCMK__REMOTE_SCHEMA_DIR;
1454 }
1455
1456 return dir;
1457 }
1458
1459
1460
1461
1462
1463
1464
1465 void
1466 pcmk__warn_if_schema_deprecated(const char *schema)
1467 {
1468
1469
1470
1471 if (pcmk__str_eq(schema, PCMK_VALUE_NONE, pcmk__str_none)) {
1472 pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1473 "deprecated and will be removed in a future release "
1474 "without the possibility of upgrades (manually edit "
1475 "to use a supported schema)", schema);
1476 }
1477 }
1478
1479
1480
1481
1482 #include <crm/common/xml_compat.h>
1483
1484 gboolean
1485 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1486 {
1487 int rc = pcmk__update_configured_schema(xml, to_logs);
1488
1489 if (best_version != NULL) {
1490 const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
1491
1492 if (name == NULL) {
1493 *best_version = -1;
1494 } else {
1495 GList *entry = pcmk__get_schema(name);
1496 pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1497
1498 *best_version = (schema == NULL)? -1 : schema->schema_index;
1499 }
1500 }
1501 return (rc == pcmk_rc_ok)? TRUE: FALSE;
1502 }
1503
1504
1505