pacemaker 2.1.1-77db578727
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
schemas.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2020 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 <sys/stat.h>
17#include <stdarg.h>
18
19#include <libxml/relaxng.h>
20
21#if HAVE_LIBXSLT
22# include <libxslt/xslt.h>
23# include <libxslt/transform.h>
24# include <libxslt/security.h>
25# include <libxslt/xsltutils.h>
26#endif
27
28#include <crm/msg_xml.h>
29#include <crm/common/xml.h>
30#include <crm/common/xml_internal.h> /* PCMK__XML_LOG_BASE */
31
32typedef struct {
33 unsigned char v[2];
34} schema_version_t;
35
36#define SCHEMA_ZERO { .v = { 0, 0 } }
37
38#define schema_scanf(s, prefix, version, suffix) \
39 sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
40
41#define schema_strdup_printf(prefix, version, suffix) \
42 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
43
44typedef struct {
45 xmlRelaxNGPtr rng;
46 xmlRelaxNGValidCtxtPtr valid;
47 xmlRelaxNGParserCtxtPtr parser;
48} relaxng_ctx_cache_t;
49
54
55struct schema_s {
56 char *name;
57 char *transform;
58 void *cache;
59 enum schema_validator_e validator;
60 int after_transform;
61 schema_version_t version;
62 char *transform_enter;
63 bool transform_onleave;
64};
65
66static struct schema_s *known_schemas = NULL;
67static int xml_schema_max = 0;
68static bool silent_logging = FALSE;
69
70static void
71xml_log(int priority, const char *fmt, ...)
72G_GNUC_PRINTF(2, 3);
73
74static void
75xml_log(int priority, const char *fmt, ...)
76{
77 va_list ap;
78
79 va_start(ap, fmt);
80 if (silent_logging == FALSE) {
81 /* XXX should not this enable dechunking as well? */
82 PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
83 }
84 va_end(ap);
85}
86
87static int
88xml_latest_schema_index(void)
89{
90 return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
91}
92
93static int
94xml_minimum_schema_index(void)
95{
96 static int best = 0;
97 if (best == 0) {
98 int lpc = 0;
99
100 best = xml_latest_schema_index();
101 for (lpc = best; lpc > 0; lpc--) {
102 if (known_schemas[lpc].version.v[0]
103 < known_schemas[best].version.v[0]) {
104 return best;
105 } else {
106 best = lpc;
107 }
108 }
109 best = xml_latest_schema_index();
110 }
111 return best;
112}
113
114const char *
116{
117 return get_schema_name(xml_latest_schema_index());
118}
119
120static inline bool
121version_from_filename(const char *filename, schema_version_t *version)
122{
123 int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
124
125 return (rc == 2);
126}
127
128static int
129schema_filter(const struct dirent *a)
130{
131 int rc = 0;
132 schema_version_t version = SCHEMA_ZERO;
133
134 if (strstr(a->d_name, "pacemaker-") != a->d_name) {
135 /* crm_trace("%s - wrong prefix", a->d_name); */
136
137 } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
138 /* crm_trace("%s - wrong suffix", a->d_name); */
139
140 } else if (!version_from_filename(a->d_name, &version)) {
141 /* crm_trace("%s - wrong format", a->d_name); */
142
143 } else {
144 /* crm_debug("%s - candidate", a->d_name); */
145 rc = 1;
146 }
147
148 return rc;
149}
150
151static int
152schema_sort(const struct dirent **a, const struct dirent **b)
153{
154 schema_version_t a_version = SCHEMA_ZERO;
155 schema_version_t b_version = SCHEMA_ZERO;
156
157 if (!version_from_filename(a[0]->d_name, &a_version)
158 || !version_from_filename(b[0]->d_name, &b_version)) {
159 // Shouldn't be possible, but makes static analysis happy
160 return 0;
161 }
162
163 for (int i = 0; i < 2; ++i) {
164 if (a_version.v[i] < b_version.v[i]) {
165 return -1;
166 } else if (a_version.v[i] > b_version.v[i]) {
167 return 1;
168 }
169 }
170 return 0;
171}
172
180static void
181add_schema(enum schema_validator_e validator, const schema_version_t *version,
182 const char *name, const char *transform,
183 const char *transform_enter, bool transform_onleave,
184 int after_transform)
185{
186 int last = xml_schema_max;
187 bool have_version = FALSE;
188
189 xml_schema_max++;
190 known_schemas = pcmk__realloc(known_schemas,
191 xml_schema_max * sizeof(struct schema_s));
192 CRM_ASSERT(known_schemas != NULL);
193 memset(known_schemas+last, 0, sizeof(struct schema_s));
194 known_schemas[last].validator = validator;
195 known_schemas[last].after_transform = after_transform;
196
197 for (int i = 0; i < 2; ++i) {
198 known_schemas[last].version.v[i] = version->v[i];
199 if (version->v[i]) {
200 have_version = TRUE;
201 }
202 }
203 if (have_version) {
204 known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
205 } else {
207 schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
208 known_schemas[last].name = strdup(name);
209 }
210
211 if (transform) {
212 known_schemas[last].transform = strdup(transform);
213 }
214 if (transform_enter) {
215 known_schemas[last].transform_enter = strdup(transform_enter);
216 }
217 known_schemas[last].transform_onleave = transform_onleave;
218 if (after_transform == 0) {
219 after_transform = xml_schema_max; /* upgrade is a one-way */
220 }
221 known_schemas[last].after_transform = after_transform;
222
223 if (known_schemas[last].after_transform < 0) {
224 crm_debug("Added supported schema %d: %s",
225 last, known_schemas[last].name);
226
227 } else if (known_schemas[last].transform) {
228 crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
229 last, known_schemas[last].name,
230 known_schemas[last].after_transform,
231 known_schemas[last].transform);
232
233 } else {
234 crm_debug("Added supported schema %d: %s (upgrades to %d)",
235 last, known_schemas[last].name,
236 known_schemas[last].after_transform);
237 }
238}
239
268static int
269add_schema_by_version(const schema_version_t *version, int next,
270 bool transform_expected)
271{
272 bool transform_onleave = FALSE;
273 int rc = pcmk_rc_ok;
274 struct stat s;
275 char *xslt = NULL,
276 *transform_upgrade = NULL,
277 *transform_enter = NULL;
278
279 /* prologue for further transform_expected handling */
280 if (transform_expected) {
281 /* check if there's suitable "upgrade" stylesheet */
282 transform_upgrade = schema_strdup_printf("upgrade-", *version, );
284 transform_upgrade);
285 }
286
287 if (!transform_expected) {
288 /* jump directly to the end */
289
290 } else if (stat(xslt, &s) == 0) {
291 /* perhaps there's also a targeted "upgrade-enter" stylesheet */
292 transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
293 free(xslt);
295 transform_enter);
296 if (stat(xslt, &s) != 0) {
297 /* or initially, at least a generic one */
298 crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
299 free(xslt);
300 free(transform_enter);
301 transform_enter = strdup("upgrade-enter");
303 transform_enter);
304 if (stat(xslt, &s) != 0) {
305 crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
306 free(xslt);
307 xslt = NULL;
308 }
309 }
310 /* xslt contains full path to "upgrade-enter" stylesheet */
311 if (xslt != NULL) {
312 /* then there should be "upgrade-leave" counterpart (enter->leave) */
313 memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
314 transform_onleave = (stat(xslt, &s) == 0);
315 free(xslt);
316 } else {
317 free(transform_enter);
318 transform_enter = NULL;
319 }
320
321 } else {
322 crm_err("Upgrade transform %s not found", xslt);
323 free(xslt);
324 free(transform_upgrade);
325 transform_upgrade = NULL;
326 next = -1;
327 rc = ENOENT;
328 }
329
330 add_schema(schema_validator_rng, version, NULL,
331 transform_upgrade, transform_enter, transform_onleave, next);
332
333 free(transform_upgrade);
334 free(transform_enter);
335
336 return rc;
337}
338
339static void
340wrap_libxslt(bool finalize)
341{
342 static xsltSecurityPrefsPtr secprefs;
343 int ret = 0;
344
345 /* security framework preferences */
346 if (!finalize) {
347 CRM_ASSERT(secprefs == NULL);
348 secprefs = xsltNewSecurityPrefs();
349 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
350 xsltSecurityForbid)
351 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
352 xsltSecurityForbid)
353 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
354 xsltSecurityForbid)
355 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
356 xsltSecurityForbid);
357 if (ret != 0) {
358 return;
359 }
360 } else {
361 xsltFreeSecurityPrefs(secprefs);
362 secprefs = NULL;
363 }
364
365 /* cleanup only */
366 if (finalize) {
367 xsltCleanupGlobals();
368 }
369}
370
378void
380{
381 int lpc, max;
383 struct dirent **namelist = NULL;
384 const schema_version_t zero = SCHEMA_ZERO;
385
386 wrap_libxslt(false);
387
388 max = scandir(base, &namelist, schema_filter, schema_sort);
389 if (max < 0) {
390 crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
391 free(base);
392
393 } else {
394 free(base);
395 for (lpc = 0; lpc < max; lpc++) {
396 bool transform_expected = FALSE;
397 int next = 0;
398 schema_version_t version = SCHEMA_ZERO;
399
400 if (!version_from_filename(namelist[lpc]->d_name, &version)) {
401 // Shouldn't be possible, but makes static analysis happy
402 crm_err("Skipping schema '%s': could not parse version",
403 namelist[lpc]->d_name);
404 continue;
405 }
406 if ((lpc + 1) < max) {
407 schema_version_t next_version = SCHEMA_ZERO;
408
409 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
410 && (version.v[0] < next_version.v[0])) {
411 transform_expected = TRUE;
412 }
413
414 } else {
415 next = -1;
416 }
417 if (add_schema_by_version(&version, next, transform_expected)
418 == ENOENT) {
419 break;
420 }
421 }
422
423 for (lpc = 0; lpc < max; lpc++) {
424 free(namelist[lpc]);
425 }
426 free(namelist);
427 }
428
429 add_schema(schema_validator_rng, &zero, "pacemaker-next",
430 NULL, NULL, FALSE, -1);
431
432 add_schema(schema_validator_none, &zero, "none", NULL, NULL, FALSE, -1);
433}
434
435#if 0
436static void
437relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
438{
439 /*
440 Structure xmlError
441 struct _xmlError {
442 int domain : What part of the library raised this er
443 int code : The error code, e.g. an xmlParserError
444 char * message : human-readable informative error messag
445 xmlErrorLevel level : how consequent is the error
446 char * file : the filename
447 int line : the line number if available
448 char * str1 : extra string information
449 char * str2 : extra string information
450 char * str3 : extra string information
451 int int1 : extra number information
452 int int2 : column number of the error or 0 if N/A
453 void * ctxt : the parser context if available
454 void * node : the node in the tree
455 }
456 */
457 crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
458}
459#endif
460
461static gboolean
462validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
463 relaxng_ctx_cache_t **cached_ctx)
464{
465 int rc = 0;
466 gboolean valid = TRUE;
467 relaxng_ctx_cache_t *ctx = NULL;
468
469 CRM_CHECK(doc != NULL, return FALSE);
470 CRM_CHECK(relaxng_file != NULL, return FALSE);
471
472 if (cached_ctx && *cached_ctx) {
473 ctx = *cached_ctx;
474
475 } else {
476 crm_debug("Creating RNG parser context");
477 ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
478
479 xmlLoadExtDtdDefaultValue = 1;
480 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
481 CRM_CHECK(ctx->parser != NULL, goto cleanup);
482
483 if (to_logs) {
484 xmlRelaxNGSetParserErrors(ctx->parser,
485 (xmlRelaxNGValidityErrorFunc) xml_log,
486 (xmlRelaxNGValidityWarningFunc) xml_log,
487 GUINT_TO_POINTER(LOG_ERR));
488 } else {
489 xmlRelaxNGSetParserErrors(ctx->parser,
490 (xmlRelaxNGValidityErrorFunc) fprintf,
491 (xmlRelaxNGValidityWarningFunc) fprintf,
492 stderr);
493 }
494
495 ctx->rng = xmlRelaxNGParse(ctx->parser);
496 CRM_CHECK(ctx->rng != NULL,
497 crm_err("Could not find/parse %s", relaxng_file);
498 goto cleanup);
499
500 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
501 CRM_CHECK(ctx->valid != NULL, goto cleanup);
502
503 if (to_logs) {
504 xmlRelaxNGSetValidErrors(ctx->valid,
505 (xmlRelaxNGValidityErrorFunc) xml_log,
506 (xmlRelaxNGValidityWarningFunc) xml_log,
507 GUINT_TO_POINTER(LOG_ERR));
508 } else {
509 xmlRelaxNGSetValidErrors(ctx->valid,
510 (xmlRelaxNGValidityErrorFunc) fprintf,
511 (xmlRelaxNGValidityWarningFunc) fprintf,
512 stderr);
513 }
514 }
515
516 /* xmlRelaxNGSetValidStructuredErrors( */
517 /* valid, relaxng_invalid_stderr, valid); */
518
519 xmlLineNumbersDefault(1);
520 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
521 if (rc > 0) {
522 valid = FALSE;
523
524 } else if (rc < 0) {
525 crm_err("Internal libxml error during validation");
526 }
527
528 cleanup:
529
530 if (cached_ctx) {
531 *cached_ctx = ctx;
532
533 } else {
534 if (ctx->parser != NULL) {
535 xmlRelaxNGFreeParserCtxt(ctx->parser);
536 }
537 if (ctx->valid != NULL) {
538 xmlRelaxNGFreeValidCtxt(ctx->valid);
539 }
540 if (ctx->rng != NULL) {
541 xmlRelaxNGFree(ctx->rng);
542 }
543 free(ctx);
544 }
545
546 return valid;
547}
548
553void
555{
556 int lpc;
557 relaxng_ctx_cache_t *ctx = NULL;
558
559 for (lpc = 0; lpc < xml_schema_max; lpc++) {
560
561 switch (known_schemas[lpc].validator) {
562 case schema_validator_none: // not cached
563 break;
564 case schema_validator_rng: // cached
565 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
566 if (ctx == NULL) {
567 break;
568 }
569 if (ctx->parser != NULL) {
570 xmlRelaxNGFreeParserCtxt(ctx->parser);
571 }
572 if (ctx->valid != NULL) {
573 xmlRelaxNGFreeValidCtxt(ctx->valid);
574 }
575 if (ctx->rng != NULL) {
576 xmlRelaxNGFree(ctx->rng);
577 }
578 free(ctx);
579 known_schemas[lpc].cache = NULL;
580 break;
581 }
582 free(known_schemas[lpc].name);
583 free(known_schemas[lpc].transform);
584 free(known_schemas[lpc].transform_enter);
585 }
586 free(known_schemas);
587 known_schemas = NULL;
588
589 wrap_libxslt(true);
590}
591
592static gboolean
593validate_with(xmlNode *xml, int method, gboolean to_logs)
594{
595 xmlDocPtr doc = NULL;
596 gboolean valid = FALSE;
597 char *file = NULL;
598
599 if (method < 0) {
600 return FALSE;
601 }
602
603 if (known_schemas[method].validator == schema_validator_none) {
604 return TRUE;
605 }
606
607 CRM_CHECK(xml != NULL, return FALSE);
608 doc = getDocPtr(xml);
610 known_schemas[method].name);
611
612 crm_trace("Validating with: %s (type=%d)",
613 crm_str(file), known_schemas[method].validator);
614 switch (known_schemas[method].validator) {
616 valid =
617 validate_with_relaxng(doc, to_logs, file,
618 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
619 break;
620 default:
621 crm_err("Unknown validator type: %d",
622 known_schemas[method].validator);
623 break;
624 }
625
626 free(file);
627 return valid;
628}
629
630static bool
631validate_with_silent(xmlNode *xml, int method)
632{
633 bool rc, sl_backup = silent_logging;
634 silent_logging = TRUE;
635 rc = validate_with(xml, method, TRUE);
636 silent_logging = sl_backup;
637 return rc;
638}
639
640static void
641dump_file(const char *filename)
642{
643
644 FILE *fp = NULL;
645 int ch, line = 0;
646
647 CRM_CHECK(filename != NULL, return);
648
649 fp = fopen(filename, "r");
650 if (fp == NULL) {
651 crm_perror(LOG_ERR, "Could not open %s for reading", filename);
652 return;
653 }
654
655 fprintf(stderr, "%4d ", ++line);
656 do {
657 ch = getc(fp);
658 if (ch == EOF) {
659 putc('\n', stderr);
660 break;
661 } else if (ch == '\n') {
662 fprintf(stderr, "\n%4d ", ++line);
663 } else {
664 putc(ch, stderr);
665 }
666 } while (1);
667
668 fclose(fp);
669}
670
671gboolean
672validate_xml_verbose(xmlNode *xml_blob)
673{
674 int fd = 0;
675 xmlDoc *doc = NULL;
676 xmlNode *xml = NULL;
677 gboolean rc = FALSE;
678 char *filename = NULL;
679
680 filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
681
682 umask(S_IWGRP | S_IWOTH | S_IROTH);
683 fd = mkstemp(filename);
684 write_xml_fd(xml_blob, filename, fd, FALSE);
685
686 dump_file(filename);
687
688 doc = xmlParseFile(filename);
689 xml = xmlDocGetRootElement(doc);
690 rc = validate_xml(xml, NULL, FALSE);
691 free_xml(xml);
692
693 unlink(filename);
694 free(filename);
695
696 return rc;
697}
698
699gboolean
700validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
701{
702 int version = 0;
703
704 if (validation == NULL) {
705 validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
706 }
707
708 if (validation == NULL) {
709 int lpc = 0;
710 bool valid = FALSE;
711
712 for (lpc = 0; lpc < xml_schema_max; lpc++) {
713 if (validate_with(xml_blob, lpc, FALSE)) {
714 valid = TRUE;
716 known_schemas[lpc].name);
717 crm_info("XML validated against %s", known_schemas[lpc].name);
718 if(known_schemas[lpc].after_transform == 0) {
719 break;
720 }
721 }
722 }
723
724 return valid;
725 }
726
727 version = get_schema_version(validation);
728 if (strcmp(validation, "none") == 0) {
729 return TRUE;
730 } else if (version < xml_schema_max) {
731 return validate_with(xml_blob, version, to_logs);
732 }
733
734 crm_err("Unknown validator: %s", validation);
735 return FALSE;
736}
737
738#if HAVE_LIBXSLT
739
740static void
741cib_upgrade_err(void *ctx, const char *fmt, ...)
742G_GNUC_PRINTF(2, 3);
743
744/* With this arrangement, an attempt to identify the message severity
745 as explicitly signalled directly from XSLT is performed in rather
746 a smart way (no reliance on formatting string + arguments being
747 always specified as ["%s", purposeful_string], as it can also be
748 ["%s: %s", some_prefix, purposeful_string] etc. so every argument
749 pertaining %s specifier is investigated), and if such a mark found,
750 the respective level is determined and, when the messages are to go
751 to the native logs, the mark itself gets dropped
752 (by the means of string shift).
753
754 NOTE: whether the native logging is the right sink is decided per
755 the ctx parameter -- NULL denotes this case, otherwise it
756 carries a pointer to the numeric expression of the desired
757 target logging level (messages with higher level will be
758 suppressed)
759
760 NOTE: on some architectures, this string shift may not have any
761 effect, but that's an acceptable tradeoff
762
763 The logging level for not explicitly designated messages
764 (suspicious, likely internal errors or some runaways) is
765 LOG_WARNING.
766 */
767static void
768cib_upgrade_err(void *ctx, const char *fmt, ...)
769{
770 va_list ap, aq;
771 char *arg_cur;
772
773 bool found = FALSE;
774 const char *fmt_iter = fmt;
775 uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
776 const unsigned * log_level = (const unsigned *) ctx;
777 enum {
778 escan_seennothing,
779 escan_seenpercent,
780 } scan_state = escan_seennothing;
781
782 va_start(ap, fmt);
783 va_copy(aq, ap);
784
785 while (!found && *fmt_iter != '\0') {
786 /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
787 switch (*fmt_iter++) {
788 case '%':
789 if (scan_state == escan_seennothing) {
790 scan_state = escan_seenpercent;
791 } else if (scan_state == escan_seenpercent) {
792 scan_state = escan_seennothing;
793 }
794 break;
795 case 's':
796 if (scan_state == escan_seenpercent) {
797 scan_state = escan_seennothing;
798 arg_cur = va_arg(aq, char *);
799 if (arg_cur != NULL) {
800 switch (arg_cur[0]) {
801 case 'W':
802 if (!strncmp(arg_cur, "WARNING: ",
803 sizeof("WARNING: ") - 1)) {
804 msg_log_level = LOG_WARNING;
805 }
806 if (ctx == NULL) {
807 memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
808 strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
809 }
810 found = TRUE;
811 break;
812 case 'I':
813 if (!strncmp(arg_cur, "INFO: ",
814 sizeof("INFO: ") - 1)) {
815 msg_log_level = LOG_INFO;
816 }
817 if (ctx == NULL) {
818 memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
819 strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
820 }
821 found = TRUE;
822 break;
823 case 'D':
824 if (!strncmp(arg_cur, "DEBUG: ",
825 sizeof("DEBUG: ") - 1)) {
826 msg_log_level = LOG_DEBUG;
827 }
828 if (ctx == NULL) {
829 memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
830 strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
831 }
832 found = TRUE;
833 break;
834 }
835 }
836 }
837 break;
838 case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
839 case '0': case '1': case '2': case '3': case '4':
840 case '5': case '6': case '7': case '8': case '9':
841 case '*':
842 break;
843 case 'l':
844 case 'z':
845 case 't':
846 case 'j':
847 case 'd': case 'i':
848 case 'o':
849 case 'u':
850 case 'x': case 'X':
851 case 'e': case 'E':
852 case 'f': case 'F':
853 case 'g': case 'G':
854 case 'a': case 'A':
855 case 'c':
856 case 'p':
857 if (scan_state == escan_seenpercent) {
858 (void) va_arg(aq, void *); /* skip forward */
859 scan_state = escan_seennothing;
860 }
861 break;
862 default:
863 scan_state = escan_seennothing;
864 break;
865 }
866 }
867
868 if (log_level != NULL) {
869 /* intention of the following offset is:
870 cibadmin -V -> start showing INFO labelled messages */
871 if (*log_level + 4 >= msg_log_level) {
872 vfprintf(stderr, fmt, ap);
873 }
874 } else {
875 PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
876 }
877
878 va_end(aq);
879 va_end(ap);
880}
881
882
883/* Denotes temporary emergency fix for "xmldiff'ing not text-node-ready";
884 proper fix is most likely to teach __xml_diff_object and friends to
885 deal with XML_TEXT_NODE (and more?), i.e., those nodes currently
886 missing "_private" field (implicitly as NULL) which clashes with
887 unchecked accesses (e.g. in __xml_offset) -- the outcome may be that
888 those unexpected XML nodes will simply be ignored for the purpose of
889 diff'ing, or it may be made more robust, or per the user's preference
890 (which then may be exposed as crm_diff switch).
891
892 Said XML_TEXT_NODE may appear unexpectedly due to how upgrade-2.10.xsl
893 is arranged.
894
895 The emergency fix is simple: reparse XSLT output with blank-ignoring
896 parser. */
897#ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
898#define PCMK_SCHEMAS_EMERGENCY_XSLT 1
899#endif
900
901static xmlNode *
902apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
903{
904 char *xform = NULL;
905 xmlNode *out = NULL;
906 xmlDocPtr res = NULL;
907 xmlDocPtr doc = NULL;
908 xsltStylesheet *xslt = NULL;
909#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
910 xmlChar *emergency_result;
911 int emergency_txt_len;
912 int emergency_res;
913#endif
914
915 CRM_CHECK(xml != NULL, return FALSE);
916 doc = getDocPtr(xml);
918 transform);
919
920 xmlLoadExtDtdDefaultValue = 1;
921 xmlSubstituteEntitiesDefault(1);
922
923 /* for capturing, e.g., what's emitted via <xsl:message> */
924 if (to_logs) {
925 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
926 } else {
927 xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
928 }
929
930 xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
931 CRM_CHECK(xslt != NULL, goto cleanup);
932
933 res = xsltApplyStylesheet(xslt, doc, NULL);
934 CRM_CHECK(res != NULL, goto cleanup);
935
936 xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
937
938
939#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
940 emergency_res = xsltSaveResultToString(&emergency_result,
941 &emergency_txt_len, res, xslt);
942 xmlFreeDoc(res);
943 CRM_CHECK(emergency_res == 0, goto cleanup);
944 out = string2xml((const char *) emergency_result);
945 free(emergency_result);
946#else
947 out = xmlDocGetRootElement(res);
948#endif
949
950 cleanup:
951 if (xslt) {
952 xsltFreeStylesheet(xslt);
953 }
954
955 free(xform);
956
957 return out;
958}
959
966static xmlNode *
967apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
968{
969 bool transform_onleave = schema->transform_onleave;
970 char *transform_leave;
971 xmlNode *upgrade = NULL,
972 *final = NULL;
973
974 if (schema->transform_enter) {
975 crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
976 schema->name, schema->transform_enter);
977 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
978 if (upgrade == NULL) {
979 crm_warn("Upgrade-enter transformation %s.xsl failed",
980 schema->transform_enter);
981 transform_onleave = FALSE;
982 }
983 }
984 if (upgrade == NULL) {
985 upgrade = xml;
986 }
987
988 crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
989 schema->name, schema->transform);
990 final = apply_transformation(upgrade, schema->transform, to_logs);
991 if (upgrade != xml) {
992 free_xml(upgrade);
993 upgrade = NULL;
994 }
995
996 if (final != NULL && transform_onleave) {
997 upgrade = final;
998 /* following condition ensured in add_schema_by_version */
999 CRM_ASSERT(schema->transform_enter != NULL);
1000 transform_leave = strdup(schema->transform_enter);
1001 /* enter -> leave */
1002 memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1003 crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
1004 schema->name, transform_leave);
1005 final = apply_transformation(upgrade, transform_leave, to_logs);
1006 if (final == NULL) {
1007 crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
1008 final = upgrade;
1009 } else {
1010 free_xml(upgrade);
1011 }
1012 free(transform_leave);
1013 }
1014
1015 return final;
1016}
1017
1018#endif /* HAVE_LIBXSLT */
1019
1020const char *
1022{
1023 if (version < 0 || version >= xml_schema_max) {
1024 return "unknown";
1025 }
1026 return known_schemas[version].name;
1027}
1028
1029int
1031{
1032 int lpc = 0;
1033
1034 if (name == NULL) {
1035 name = "none";
1036 }
1037 for (; lpc < xml_schema_max; lpc++) {
1038 if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
1039 return lpc;
1040 }
1041 }
1042 return -1;
1043}
1044
1045/* set which validation to use */
1046int
1047update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
1048 gboolean to_logs)
1049{
1050 xmlNode *xml = NULL;
1051 char *value = NULL;
1052 int max_stable_schemas = xml_latest_schema_index();
1053 int lpc = 0, match = -1, rc = pcmk_ok;
1054 int next = -1; /* -1 denotes "inactive" value */
1055
1056 CRM_CHECK(best != NULL, return -EINVAL);
1057 *best = 0;
1058
1059 CRM_CHECK(xml_blob != NULL, return -EINVAL);
1060 CRM_CHECK(*xml_blob != NULL, return -EINVAL);
1061
1062 xml = *xml_blob;
1064
1065 if (value != NULL) {
1066 match = get_schema_version(value);
1067
1068 lpc = match;
1069 if (lpc >= 0 && transform == FALSE) {
1070 *best = lpc++;
1071
1072 } else if (lpc < 0) {
1073 crm_debug("Unknown validation schema");
1074 lpc = 0;
1075 }
1076 }
1077
1078 if (match >= max_stable_schemas) {
1079 /* nothing to do */
1080 free(value);
1081 *best = match;
1082 return pcmk_ok;
1083 }
1084
1085 while (lpc <= max_stable_schemas) {
1086 crm_debug("Testing '%s' validation (%d of %d)",
1087 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1088 lpc, max_stable_schemas);
1089
1090 if (validate_with(xml, lpc, to_logs) == FALSE) {
1091 if (next != -1) {
1092 crm_info("Configuration not valid for schema: %s",
1093 known_schemas[lpc].name);
1094 next = -1;
1095 } else {
1096 crm_trace("%s validation failed",
1097 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1098 }
1099 if (*best) {
1100 /* we've satisfied the validation, no need to check further */
1101 break;
1102 }
1104
1105 } else {
1106 if (next != -1) {
1107 crm_debug("Configuration valid for schema: %s",
1108 known_schemas[next].name);
1109 next = -1;
1110 }
1111 rc = pcmk_ok;
1112 }
1113
1114 if (rc == pcmk_ok) {
1115 *best = lpc;
1116 }
1117
1118 if (rc == pcmk_ok && transform) {
1119 xmlNode *upgrade = NULL;
1120 next = known_schemas[lpc].after_transform;
1121
1122 if (next <= lpc) {
1123 /* There is no next version, or next would regress */
1124 crm_trace("Stopping at %s", known_schemas[lpc].name);
1125 break;
1126
1127 } else if (max > 0 && (lpc == max || next > max)) {
1128 crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1129 known_schemas[lpc].name, lpc, next, max);
1130 break;
1131
1132 } else if (known_schemas[lpc].transform == NULL
1133 /* possibly avoid transforming when readily valid
1134 (in general more restricted when crossing the major
1135 version boundary, as X.0 "transitional" version is
1136 expected to be more strict than it's successors that
1137 may re-allow constructs from previous major line) */
1138 || validate_with_silent(xml, next)) {
1139 crm_debug("%s-style configuration is also valid for %s",
1140 known_schemas[lpc].name, known_schemas[next].name);
1141
1142 lpc = next;
1143
1144 } else {
1145 crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1146 known_schemas[lpc].name, known_schemas[next].name,
1147 known_schemas[lpc].transform);
1148
1149#if HAVE_LIBXSLT
1150 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1151#endif
1152 if (upgrade == NULL) {
1153 crm_err("Transformation %s.xsl failed",
1154 known_schemas[lpc].transform);
1156
1157 } else if (validate_with(upgrade, next, to_logs)) {
1158 crm_info("Transformation %s.xsl successful",
1159 known_schemas[lpc].transform);
1160 lpc = next;
1161 *best = next;
1162 free_xml(xml);
1163 xml = upgrade;
1164 rc = pcmk_ok;
1165
1166 } else {
1167 crm_err("Transformation %s.xsl did not produce a valid configuration",
1168 known_schemas[lpc].transform);
1169 crm_log_xml_info(upgrade, "transform:bad");
1170 free_xml(upgrade);
1172 }
1173 next = -1;
1174 }
1175 }
1176
1177 if (transform == FALSE || rc != pcmk_ok) {
1178 /* we need some progress! */
1179 lpc++;
1180 }
1181 }
1182
1183 if (*best > match && *best) {
1184 crm_info("%s the configuration from %s to %s",
1185 transform?"Transformed":"Upgraded",
1186 value ? value : "<none>", known_schemas[*best].name);
1187 crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1188 }
1189
1190 *xml_blob = xml;
1191 free(value);
1192 return rc;
1193}
1194
1195gboolean
1196cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1197{
1198 gboolean rc = TRUE;
1199 const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1200 char *const orig_value = strdup(value == NULL ? "(none)" : value);
1201
1202 int version = get_schema_version(value);
1203 int orig_version = version;
1204 int min_version = xml_minimum_schema_index();
1205
1206 if (version < min_version) {
1207 // Current configuration schema is not acceptable, try to update
1208 xmlNode *converted = NULL;
1209
1210 converted = copy_xml(*xml);
1211 update_validation(&converted, &version, 0, TRUE, to_logs);
1212
1213 value = crm_element_value(converted, XML_ATTR_VALIDATION);
1214 if (version < min_version) {
1215 // Updated configuration schema is still not acceptable
1216
1217 if (version < orig_version || orig_version == -1) {
1218 // We couldn't validate any schema at all
1219 if (to_logs) {
1220 pcmk__config_err("Cannot upgrade configuration (claiming "
1221 "schema %s) to at least %s because it "
1222 "does not validate with any schema from "
1223 "%s to %s",
1224 orig_value,
1225 get_schema_name(min_version),
1226 get_schema_name(orig_version),
1228 } else {
1229 fprintf(stderr, "Cannot upgrade configuration (claiming "
1230 "schema %s) to at least %s because it "
1231 "does not validate with any schema from "
1232 "%s to %s\n",
1233 orig_value,
1234 get_schema_name(min_version),
1235 get_schema_name(orig_version),
1237 }
1238 } else {
1239 // We updated configuration successfully, but still too low
1240 if (to_logs) {
1241 pcmk__config_err("Cannot upgrade configuration (claiming "
1242 "schema %s) to at least %s because it "
1243 "would not upgrade past %s",
1244 orig_value,
1245 get_schema_name(min_version),
1246 crm_str(value));
1247 } else {
1248 fprintf(stderr, "Cannot upgrade configuration (claiming "
1249 "schema %s) to at least %s because it "
1250 "would not upgrade past %s\n",
1251 orig_value,
1252 get_schema_name(min_version),
1253 crm_str(value));
1254 }
1255 }
1256
1257 free_xml(converted);
1258 converted = NULL;
1259 rc = FALSE;
1260
1261 } else {
1262 // Updated configuration schema is acceptable
1263 free_xml(*xml);
1264 *xml = converted;
1265
1266 if (version < xml_latest_schema_index()) {
1267 if (to_logs) {
1268 pcmk__config_warn("Configuration with schema %s was "
1269 "internally upgraded to acceptable (but "
1270 "not most recent) %s",
1271 orig_value, get_schema_name(version));
1272 }
1273 } else {
1274 if (to_logs) {
1275 crm_info("Configuration with schema %s was internally "
1276 "upgraded to latest version %s",
1277 orig_value, get_schema_name(version));
1278 }
1279 }
1280 }
1281
1282 } else if (version >= get_schema_version("none")) {
1283 // Schema validation is disabled
1284 if (to_logs) {
1285 pcmk__config_warn("Schema validation of configuration is disabled "
1286 "(enabling is encouraged and prevents common "
1287 "misconfigurations)");
1288
1289 } else {
1290 fprintf(stderr, "Schema validation of configuration is disabled "
1291 "(enabling is encouraged and prevents common "
1292 "misconfigurations)\n");
1293 }
1294 }
1295
1296 if (best_version) {
1297 *best_version = version;
1298 }
1299
1300 free(orig_value);
1301 return rc;
1302}
const char * pcmk__get_tmpdir(void)
Definition io.c:540
uint32_t version
Definition remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define crm_log_xml_info(xml, text)
Definition logging.h:362
#define crm_str(x)
Definition logging.h:376
#define crm_info(fmt, args...)
Definition logging.h:353
#define crm_warn(fmt, args...)
Definition logging.h:351
#define crm_notice(fmt, args...)
Definition logging.h:352
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:301
#define CRM_CHECK(expr, failure_action)
Definition logging.h:218
#define crm_debug(fmt, args...)
Definition logging.h:355
#define crm_err(fmt, args...)
Definition logging.h:350
unsigned int crm_log_level
Definition logging.c:45
#define crm_trace(fmt, args...)
Definition logging.h:356
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
#define XML_ATTR_VALIDATION
Definition msg_xml.h:114
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:530
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition nvpair.c:727
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition nvpair.c:324
char * name
Definition pcmk_fence.c:31
int rc
Definition pcmk_fence.c:35
char * strerror(int errnum)
#define CRM_ASSERT(expr)
Definition results.h:42
#define pcmk_err_schema_validation
Definition results.h:72
@ pcmk_rc_ok
Definition results.h:142
#define pcmk_ok
Definition results.h:67
#define pcmk_err_transform_failed
Definition results.h:73
const char * get_schema_name(int version)
Definition schemas.c:1021
int get_schema_version(const char *name)
Definition schemas.c:1030
#define SCHEMA_ZERO
Definition schemas.c:36
int update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, gboolean to_logs)
Update CIB XML to most recent schema version.
Definition schemas.c:1047
#define schema_strdup_printf(prefix, version, suffix)
Definition schemas.c:41
gboolean validate_xml_verbose(xmlNode *xml_blob)
Definition schemas.c:672
schema_validator_e
Definition schemas.c:50
@ schema_validator_none
Definition schemas.c:51
@ schema_validator_rng
Definition schemas.c:52
const char * xml_latest_schema(void)
Definition schemas.c:115
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition schemas.c:1196
#define schema_scanf(s, prefix, version, suffix)
Definition schemas.c:38
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
Definition schemas.c:700
void crm_schema_cleanup(void)
Definition schemas.c:554
void crm_schema_init(void)
Definition schemas.c:379
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition strings.c:562
@ pcmk__str_casei
Wrappers for and extensions to libxml2.
const xmlChar * pcmkXmlStr
Definition xml.h:51
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition xml.c:1270
xmlNode * string2xml(const char *input)
Definition xml.c:868
xmlDoc * getDocPtr(xmlNode *node)
Definition xml.c:658
void free_xml(xmlNode *child)
Definition xml.c:823
xmlNode * copy_xml(xmlNode *src_node)
Definition xml.c:829
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition xml.c:2902
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
@ 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:2930