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
- compare_transforms
- 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 <limits.h>
17 #include <sys/stat.h>
18 #include <stdarg.h>
19
20 #include <libxml/relaxng.h>
21 #include <libxml/tree.h>
22 #include <libxml/xmlstring.h>
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>
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
39 typedef struct {
40 xmlRelaxNGPtr rng;
41 xmlRelaxNGValidCtxtPtr valid;
42 xmlRelaxNGParserCtxtPtr parser;
43 } relaxng_ctx_cache_t;
44
45 static GList *known_schemas = NULL;
46 static bool initialized = false;
47 static bool silent_logging = FALSE;
48
49 static void G_GNUC_PRINTF(2, 3)
50 xml_log(int priority, const char *fmt, ...)
51 {
52 va_list ap;
53
54 va_start(ap, fmt);
55 if (silent_logging == FALSE) {
56
57 PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
58 }
59 va_end(ap);
60 }
61
62 static int
63 xml_latest_schema_index(void)
64 {
65
66
67
68
69
70
71
72 return g_list_length(known_schemas) - 2;
73 }
74
75
76
77
78
79
80
81 static GList *
82 get_highest_schema(void)
83 {
84
85
86
87
88 GList *entry = pcmk__get_schema("none");
89
90 pcmk__assert((entry != NULL) && (entry->prev != NULL));
91 return entry->prev;
92 }
93
94
95
96
97
98
99
100 const char *
101 pcmk__highest_schema_name(void)
102 {
103 GList *entry = get_highest_schema();
104
105 return ((pcmk__schema_t *)(entry->data))->name;
106 }
107
108
109
110
111
112
113
114 GList *
115 pcmk__find_x_0_schema(void)
116 {
117 #if defined(PCMK__UNIT_TESTING)
118
119
120
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
139
140
141
142 if (schema->version.v[0] < highest_schema->version.v[0]) {
143 x_0_entry = iter->next;
144 break;
145 }
146
147
148
149
150 if (iter->prev == NULL) {
151 x_0_entry = known_schemas->data;
152 break;
153 }
154 }
155 return x_0_entry;
156 }
157
158 static inline bool
159 version_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
168 static int
169 schema_filter(const struct dirent *a)
170 {
171 int rc = 0;
172 pcmk__schema_version_t version = SCHEMA_ZERO;
173
174 if (strstr(a->d_name, "pacemaker-") != a->d_name) {
175
176
177 } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
178
179
180 } else if (!version_from_filename(a->d_name, &version)) {
181
182
183 } else {
184
185 rc = 1;
186 }
187
188 return rc;
189 }
190
191 static int
192 schema_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
204 static int
205 schema_cmp_directory(const struct dirent **a, const struct dirent **b)
206 {
207 pcmk__schema_version_t a_version = SCHEMA_ZERO;
208 pcmk__schema_version_t b_version = SCHEMA_ZERO;
209
210 if (!version_from_filename(a[0]->d_name, &a_version)
211 || !version_from_filename(b[0]->d_name, &b_version)) {
212
213 return 0;
214 }
215
216 return schema_cmp(a_version, b_version);
217 }
218
219
220
221
222
223 static void
224 add_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
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
247 static void
248 wrap_libxslt(bool finalize)
249 {
250 static xsltSecurityPrefsPtr secprefs;
251 int ret = 0;
252
253
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
274 if (finalize) {
275 xsltCleanupGlobals();
276 }
277 }
278
279
280
281
282
283
284
285
286
287
288
289 static int
290 transform_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
298
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
307
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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334 static int
335 compare_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
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
371
372
373
374
375
376
377 static void
378 free_transform_list(void *data)
379 {
380 g_list_free_full((GList *) data, free);
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397 static GHashTable *
398 load_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++) {
415 pcmk__schema_version_t version = SCHEMA_ZERO;
416 unsigned char order = 0;
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
427
428
429
430
431
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
443 free(namelist[i]);
444 }
445 }
446
447 done:
448 free(namelist);
449 return transforms;
450 }
451
452 void
453 pcmk__load_schemas_from_dir(const char *dir)
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
468 transforms = load_transforms_from_dir(dir);
469
470 for (lpc = 0; lpc < max; lpc++) {
471 pcmk__schema_version_t version = SCHEMA_ZERO;
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
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
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
504 done:
505 free(namelist);
506 if (transforms != NULL) {
507 g_hash_table_destroy(transforms);
508 }
509 }
510
511 static gint
512 schema_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
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
527
528
529
530
531
532
533
534
535
536 void
537 pcmk__sort_schemas(void)
538 {
539 known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
540 }
541
542
543
544
545
546
547
548
549 void
550 pcmk__schema_init(void)
551 {
552 if (!initialized) {
553 const char *remote_schema_dir = pcmk__remote_schema_dir();
554 char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
555 const pcmk__schema_version_t zero = SCHEMA_ZERO;
556 int schema_index = 0;
557
558 initialized = true;
559
560 wrap_libxslt(false);
561
562 pcmk__load_schemas_from_dir(base);
563 pcmk__load_schemas_from_dir(remote_schema_dir);
564 free(base);
565
566
567 add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL);
568
569
570
571
572
573 pcmk__sort_schemas();
574
575
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
585 static bool
586 validate_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
669 static void
670 free_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:
677 break;
678
679 case pcmk__schema_validator_rng:
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
707
708
709
710
711 void
712 pcmk__schema_cleanup(void)
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
723
724
725
726
727
728
729
730
731 GList *
732 pcmk__get_schema(const char *name)
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
747
748
749
750
751
752
753
754
755
756
757
758 int
759 pcmk__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
778 static bool
779 validate_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
795 file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
796 schema->name);
797
798 crm_trace("Validating with %s (type=%d)",
799 pcmk__s(file, "missing schema"), schema->validator);
800 switch (schema->validator) {
801 case pcmk__schema_validator_rng:
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
814 static bool
815 validate_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
824 bool
825 pcmk__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 }
837 pcmk__warn_if_schema_deprecated(validation);
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
852
853
854
855
856
857
858
859
860 bool
861 pcmk__configured_schema_validates(xmlNode *xml)
862 {
863 return pcmk__validate_xml(xml, NULL,
864 (xmlRelaxNGValidityErrorFunc) xml_log,
865 GUINT_TO_POINTER(LOG_ERR));
866 }
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891 static void G_GNUC_PRINTF(2, 3)
892 cib_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;
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
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 *);
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
981
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
993
994
995
996
997
998
999
1000
1001
1002
1003
1004 static xmlNode *
1005 apply_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
1013 xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
1014 transform);
1015
1016
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
1027
1028
1029 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
1030 CRM_CHECK(res != NULL, goto cleanup);
1031
1032 xsltSetGenericErrorFunc(NULL, NULL);
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
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 static xmlNode *
1059 apply_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
1094 pcmk__xml_new_private_data((xmlNode *) new_xml->doc);
1095
1096
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
1112
1113
1114
1115
1116
1117
1118
1119
1120 static GList *
1121 get_configured_schema(const xmlNode *xml)
1122 {
1123 const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1124
1125 pcmk__warn_if_schema_deprecated(schema_name);
1126 return pcmk__get_schema(schema_name);
1127 }
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143 int
1144 pcmk__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
1194 break;
1195 }
1196 rc = pcmk_rc_schema_validation;
1197 continue;
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;
1205 }
1206
1207
1208 if (!transform || (current_schema->transforms == NULL)
1209 || validate_with_silent(*xml, entry->next->data)) {
1210
1211
1212
1213
1214 continue;
1215 }
1216
1217 upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1218 if (upgrade == NULL) {
1219
1220
1221
1222
1223 rc = pcmk_rc_transform_failed;
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
1241 int
1242 pcmk_update_configured_schema(xmlNode **xml)
1243 {
1244 return pcmk__update_configured_schema(xml, true);
1245 }
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256 int
1257 pcmk__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
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,
1283 PCMK_XA_VALIDATE_WITH);
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
1291
1292 if ((schema == NULL)
1293 || (schema->schema_index < original_schema->schema_index)) {
1294
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
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;
1329 return pcmk_rc_transform_failed;
1330
1331 } else {
1332
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
1353 entry = pcmk__get_schema(PCMK_VALUE_NONE);
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
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
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381 GList *
1382 pcmk__schema_files_later_than(const char *name)
1383 {
1384 GList *lst = NULL;
1385 pcmk__schema_version_t ver;
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
1413 static void
1414 append_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
1425 static void
1426 external_refs_in_schema(GList **list, const char *contents)
1427 {
1428
1429
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
1438 static int
1439 read_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")) {
1445 path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1446 } else {
1447 path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1448 }
1449
1450 rc = pcmk__file_contents(path, contents);
1451
1452 free(path);
1453 return rc;
1454 }
1455
1456 static void
1457 add_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
1466 if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1467 return;
1468 }
1469
1470
1471
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
1487
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
1498
1499
1500 external_refs_in_schema(&includes, contents);
1501
1502
1503
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
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527 void
1528 pcmk__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
1537 pcmk__xml_free(schema_node);
1538 }
1539 }
1540
1541
1542
1543
1544
1545
1546 const char *
1547 pcmk__remote_schema_dir(void)
1548 {
1549 const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1550
1551 if (pcmk__str_empty(dir)) {
1552 return PCMK__REMOTE_SCHEMA_DIR;
1553 }
1554
1555 return dir;
1556 }
1557
1558
1559
1560
1561
1562
1563
1564 void
1565 pcmk__warn_if_schema_deprecated(const char *schema)
1566 {
1567
1568
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
1579
1580
1581 #include <crm/common/xml_compat.h>
1582
1583 gboolean
1584 cli_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
1604