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