pacemaker 2.1.5-a3f44794f94
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 // @COMPAT: pacemaker-next is deprecated since 2.1.5
88 return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
89}
90
91static int
92xml_minimum_schema_index(void)
93{
94 static int best = 0;
95 if (best == 0) {
96 int lpc = 0;
97
98 best = xml_latest_schema_index();
99 for (lpc = best; lpc > 0; lpc--) {
100 if (known_schemas[lpc].version.v[0]
101 < known_schemas[best].version.v[0]) {
102 return best;
103 } else {
104 best = lpc;
105 }
106 }
107 best = xml_latest_schema_index();
108 }
109 return best;
110}
111
112const char *
114{
115 return get_schema_name(xml_latest_schema_index());
116}
117
118static inline bool
119version_from_filename(const char *filename, schema_version_t *version)
120{
121 int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
122
123 return (rc == 2);
124}
125
126static int
127schema_filter(const struct dirent *a)
128{
129 int rc = 0;
130 schema_version_t version = SCHEMA_ZERO;
131
132 if (strstr(a->d_name, "pacemaker-") != a->d_name) {
133 /* crm_trace("%s - wrong prefix", a->d_name); */
134
135 } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
136 /* crm_trace("%s - wrong suffix", a->d_name); */
137
138 } else if (!version_from_filename(a->d_name, &version)) {
139 /* crm_trace("%s - wrong format", a->d_name); */
140
141 } else {
142 /* crm_debug("%s - candidate", a->d_name); */
143 rc = 1;
144 }
145
146 return rc;
147}
148
149static int
150schema_sort(const struct dirent **a, const struct dirent **b)
151{
152 schema_version_t a_version = SCHEMA_ZERO;
153 schema_version_t b_version = SCHEMA_ZERO;
154
155 if (!version_from_filename(a[0]->d_name, &a_version)
156 || !version_from_filename(b[0]->d_name, &b_version)) {
157 // Shouldn't be possible, but makes static analysis happy
158 return 0;
159 }
160
161 for (int i = 0; i < 2; ++i) {
162 if (a_version.v[i] < b_version.v[i]) {
163 return -1;
164 } else if (a_version.v[i] > b_version.v[i]) {
165 return 1;
166 }
167 }
168 return 0;
169}
170
178static void
179add_schema(enum schema_validator_e validator, const schema_version_t *version,
180 const char *name, const char *transform,
181 const char *transform_enter, bool transform_onleave,
182 int after_transform)
183{
184 int last = xml_schema_max;
185 bool have_version = FALSE;
186
187 xml_schema_max++;
188 known_schemas = pcmk__realloc(known_schemas,
189 xml_schema_max * sizeof(struct schema_s));
190 CRM_ASSERT(known_schemas != NULL);
191 memset(known_schemas+last, 0, sizeof(struct schema_s));
192 known_schemas[last].validator = validator;
193 known_schemas[last].after_transform = after_transform;
194
195 for (int i = 0; i < 2; ++i) {
196 known_schemas[last].version.v[i] = version->v[i];
197 if (version->v[i]) {
198 have_version = TRUE;
199 }
200 }
201 if (have_version) {
202 known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
203 } else {
205 schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
206 known_schemas[last].name = strdup(name);
207 }
208
209 if (transform) {
210 known_schemas[last].transform = strdup(transform);
211 }
212 if (transform_enter) {
213 known_schemas[last].transform_enter = strdup(transform_enter);
214 }
215 known_schemas[last].transform_onleave = transform_onleave;
216 if (after_transform == 0) {
217 after_transform = xml_schema_max; /* upgrade is a one-way */
218 }
219 known_schemas[last].after_transform = after_transform;
220
221 if (known_schemas[last].after_transform < 0) {
222 crm_debug("Added supported schema %d: %s",
223 last, known_schemas[last].name);
224
225 } else if (known_schemas[last].transform) {
226 crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
227 last, known_schemas[last].name,
228 known_schemas[last].after_transform,
229 known_schemas[last].transform);
230
231 } else {
232 crm_debug("Added supported schema %d: %s (upgrades to %d)",
233 last, known_schemas[last].name,
234 known_schemas[last].after_transform);
235 }
236}
237
266static int
267add_schema_by_version(const schema_version_t *version, int next,
268 bool transform_expected)
269{
270 bool transform_onleave = FALSE;
271 int rc = pcmk_rc_ok;
272 struct stat s;
273 char *xslt = NULL,
274 *transform_upgrade = NULL,
275 *transform_enter = NULL;
276
277 /* prologue for further transform_expected handling */
278 if (transform_expected) {
279 /* check if there's suitable "upgrade" stylesheet */
280 transform_upgrade = schema_strdup_printf("upgrade-", *version, );
282 transform_upgrade);
283 }
284
285 if (!transform_expected) {
286 /* jump directly to the end */
287
288 } else if (stat(xslt, &s) == 0) {
289 /* perhaps there's also a targeted "upgrade-enter" stylesheet */
290 transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
291 free(xslt);
293 transform_enter);
294 if (stat(xslt, &s) != 0) {
295 /* or initially, at least a generic one */
296 crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
297 free(xslt);
298 free(transform_enter);
299 transform_enter = strdup("upgrade-enter");
301 transform_enter);
302 if (stat(xslt, &s) != 0) {
303 crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
304 free(xslt);
305 xslt = NULL;
306 }
307 }
308 /* xslt contains full path to "upgrade-enter" stylesheet */
309 if (xslt != NULL) {
310 /* then there should be "upgrade-leave" counterpart (enter->leave) */
311 memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
312 transform_onleave = (stat(xslt, &s) == 0);
313 free(xslt);
314 } else {
315 free(transform_enter);
316 transform_enter = NULL;
317 }
318
319 } else {
320 crm_err("Upgrade transform %s not found", xslt);
321 free(xslt);
322 free(transform_upgrade);
323 transform_upgrade = NULL;
324 next = -1;
325 rc = ENOENT;
326 }
327
328 add_schema(schema_validator_rng, version, NULL,
329 transform_upgrade, transform_enter, transform_onleave, next);
330
331 free(transform_upgrade);
332 free(transform_enter);
333
334 return rc;
335}
336
337static void
338wrap_libxslt(bool finalize)
339{
340 static xsltSecurityPrefsPtr secprefs;
341 int ret = 0;
342
343 /* security framework preferences */
344 if (!finalize) {
345 CRM_ASSERT(secprefs == NULL);
346 secprefs = xsltNewSecurityPrefs();
347 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
348 xsltSecurityForbid)
349 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
350 xsltSecurityForbid)
351 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
352 xsltSecurityForbid)
353 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
354 xsltSecurityForbid);
355 if (ret != 0) {
356 return;
357 }
358 } else {
359 xsltFreeSecurityPrefs(secprefs);
360 secprefs = NULL;
361 }
362
363 /* cleanup only */
364 if (finalize) {
365 xsltCleanupGlobals();
366 }
367}
368
376void
378{
379 int lpc, max;
381 struct dirent **namelist = NULL;
382 const schema_version_t zero = SCHEMA_ZERO;
383
384 wrap_libxslt(false);
385
386 max = scandir(base, &namelist, schema_filter, schema_sort);
387 if (max < 0) {
388 crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
389 free(base);
390
391 } else {
392 free(base);
393 for (lpc = 0; lpc < max; lpc++) {
394 bool transform_expected = FALSE;
395 int next = 0;
396 schema_version_t version = SCHEMA_ZERO;
397
398 if (!version_from_filename(namelist[lpc]->d_name, &version)) {
399 // Shouldn't be possible, but makes static analysis happy
400 crm_err("Skipping schema '%s': could not parse version",
401 namelist[lpc]->d_name);
402 continue;
403 }
404 if ((lpc + 1) < max) {
405 schema_version_t next_version = SCHEMA_ZERO;
406
407 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
408 && (version.v[0] < next_version.v[0])) {
409 transform_expected = TRUE;
410 }
411
412 } else {
413 next = -1;
414 }
415 if (add_schema_by_version(&version, next, transform_expected)
416 == ENOENT) {
417 break;
418 }
419 }
420
421 for (lpc = 0; lpc < max; lpc++) {
422 free(namelist[lpc]);
423 }
424 free(namelist);
425 }
426
427 // @COMPAT: Deprecated since 2.1.5
428 add_schema(schema_validator_rng, &zero, "pacemaker-next",
429 NULL, NULL, FALSE, -1);
430
431 add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
432 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
609 if (pcmk__str_eq(known_schemas[method].name, "pacemaker-next",
611 crm_warn("The pacemaker-next schema is deprecated and will be removed "
612 "in a future release.");
613 }
614
615 doc = getDocPtr(xml);
617 known_schemas[method].name);
618
619 crm_trace("Validating with %s (type=%d)",
620 pcmk__s(file, "missing schema"), known_schemas[method].validator);
621 switch (known_schemas[method].validator) {
623 valid =
624 validate_with_relaxng(doc, to_logs, file,
625 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
626 break;
627 default:
628 crm_err("Unknown validator type: %d",
629 known_schemas[method].validator);
630 break;
631 }
632
633 free(file);
634 return valid;
635}
636
637static bool
638validate_with_silent(xmlNode *xml, int method)
639{
640 bool rc, sl_backup = silent_logging;
641 silent_logging = TRUE;
642 rc = validate_with(xml, method, TRUE);
643 silent_logging = sl_backup;
644 return rc;
645}
646
647static void
648dump_file(const char *filename)
649{
650
651 FILE *fp = NULL;
652 int ch, line = 0;
653
654 CRM_CHECK(filename != NULL, return);
655
656 fp = fopen(filename, "r");
657 if (fp == NULL) {
658 crm_perror(LOG_ERR, "Could not open %s for reading", filename);
659 return;
660 }
661
662 fprintf(stderr, "%4d ", ++line);
663 do {
664 ch = getc(fp);
665 if (ch == EOF) {
666 putc('\n', stderr);
667 break;
668 } else if (ch == '\n') {
669 fprintf(stderr, "\n%4d ", ++line);
670 } else {
671 putc(ch, stderr);
672 }
673 } while (1);
674
675 fclose(fp);
676}
677
678gboolean
679validate_xml_verbose(xmlNode *xml_blob)
680{
681 int fd = 0;
682 xmlDoc *doc = NULL;
683 xmlNode *xml = NULL;
684 gboolean rc = FALSE;
685 char *filename = NULL;
686
687 filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
688
689 umask(S_IWGRP | S_IWOTH | S_IROTH);
690 fd = mkstemp(filename);
691 write_xml_fd(xml_blob, filename, fd, FALSE);
692
693 dump_file(filename);
694
695 doc = xmlParseFile(filename);
696 xml = xmlDocGetRootElement(doc);
697 rc = validate_xml(xml, NULL, FALSE);
698 free_xml(xml);
699
700 unlink(filename);
701 free(filename);
702
703 return rc;
704}
705
706gboolean
707validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
708{
709 int version = 0;
710
711 if (validation == NULL) {
712 validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
713 }
714
715 if (validation == NULL) {
716 int lpc = 0;
717 bool valid = FALSE;
718
719 for (lpc = 0; lpc < xml_schema_max; lpc++) {
720 if (validate_with(xml_blob, lpc, FALSE)) {
721 valid = TRUE;
723 known_schemas[lpc].name);
724 crm_info("XML validated against %s", known_schemas[lpc].name);
725 if(known_schemas[lpc].after_transform == 0) {
726 break;
727 }
728 }
729 }
730
731 return valid;
732 }
733
734 version = get_schema_version(validation);
735 if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
736 return TRUE;
737 } else if (version < xml_schema_max) {
738 return validate_with(xml_blob, version, to_logs);
739 }
740
741 crm_err("Unknown validator: %s", validation);
742 return FALSE;
743}
744
745static void
746cib_upgrade_err(void *ctx, const char *fmt, ...)
747G_GNUC_PRINTF(2, 3);
748
749/* With this arrangement, an attempt to identify the message severity
750 as explicitly signalled directly from XSLT is performed in rather
751 a smart way (no reliance on formatting string + arguments being
752 always specified as ["%s", purposeful_string], as it can also be
753 ["%s: %s", some_prefix, purposeful_string] etc. so every argument
754 pertaining %s specifier is investigated), and if such a mark found,
755 the respective level is determined and, when the messages are to go
756 to the native logs, the mark itself gets dropped
757 (by the means of string shift).
758
759 NOTE: whether the native logging is the right sink is decided per
760 the ctx parameter -- NULL denotes this case, otherwise it
761 carries a pointer to the numeric expression of the desired
762 target logging level (messages with higher level will be
763 suppressed)
764
765 NOTE: on some architectures, this string shift may not have any
766 effect, but that's an acceptable tradeoff
767
768 The logging level for not explicitly designated messages
769 (suspicious, likely internal errors or some runaways) is
770 LOG_WARNING.
771 */
772static void
773cib_upgrade_err(void *ctx, const char *fmt, ...)
774{
775 va_list ap, aq;
776 char *arg_cur;
777
778 bool found = FALSE;
779 const char *fmt_iter = fmt;
780 uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
781 const unsigned * log_level = (const unsigned *) ctx;
782 enum {
783 escan_seennothing,
784 escan_seenpercent,
785 } scan_state = escan_seennothing;
786
787 va_start(ap, fmt);
788 va_copy(aq, ap);
789
790 while (!found && *fmt_iter != '\0') {
791 /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
792 switch (*fmt_iter++) {
793 case '%':
794 if (scan_state == escan_seennothing) {
795 scan_state = escan_seenpercent;
796 } else if (scan_state == escan_seenpercent) {
797 scan_state = escan_seennothing;
798 }
799 break;
800 case 's':
801 if (scan_state == escan_seenpercent) {
802 scan_state = escan_seennothing;
803 arg_cur = va_arg(aq, char *);
804 if (arg_cur != NULL) {
805 switch (arg_cur[0]) {
806 case 'W':
807 if (!strncmp(arg_cur, "WARNING: ",
808 sizeof("WARNING: ") - 1)) {
809 msg_log_level = LOG_WARNING;
810 }
811 if (ctx == NULL) {
812 memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
813 strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
814 }
815 found = TRUE;
816 break;
817 case 'I':
818 if (!strncmp(arg_cur, "INFO: ",
819 sizeof("INFO: ") - 1)) {
820 msg_log_level = LOG_INFO;
821 }
822 if (ctx == NULL) {
823 memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
824 strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
825 }
826 found = TRUE;
827 break;
828 case 'D':
829 if (!strncmp(arg_cur, "DEBUG: ",
830 sizeof("DEBUG: ") - 1)) {
831 msg_log_level = LOG_DEBUG;
832 }
833 if (ctx == NULL) {
834 memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
835 strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
836 }
837 found = TRUE;
838 break;
839 }
840 }
841 }
842 break;
843 case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
844 case '0': case '1': case '2': case '3': case '4':
845 case '5': case '6': case '7': case '8': case '9':
846 case '*':
847 break;
848 case 'l':
849 case 'z':
850 case 't':
851 case 'j':
852 case 'd': case 'i':
853 case 'o':
854 case 'u':
855 case 'x': case 'X':
856 case 'e': case 'E':
857 case 'f': case 'F':
858 case 'g': case 'G':
859 case 'a': case 'A':
860 case 'c':
861 case 'p':
862 if (scan_state == escan_seenpercent) {
863 (void) va_arg(aq, void *); /* skip forward */
864 scan_state = escan_seennothing;
865 }
866 break;
867 default:
868 scan_state = escan_seennothing;
869 break;
870 }
871 }
872
873 if (log_level != NULL) {
874 /* intention of the following offset is:
875 cibadmin -V -> start showing INFO labelled messages */
876 if (*log_level + 4 >= msg_log_level) {
877 vfprintf(stderr, fmt, ap);
878 }
879 } else {
880 PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
881 }
882
883 va_end(aq);
884 va_end(ap);
885}
886
887
888/* Denotes temporary emergency fix for "xmldiff'ing not text-node-ready";
889 proper fix is most likely to teach __xml_diff_object and friends to
890 deal with XML_TEXT_NODE (and more?), i.e., those nodes currently
891 missing "_private" field (implicitly as NULL) which clashes with
892 unchecked accesses (e.g. in __xml_offset) -- the outcome may be that
893 those unexpected XML nodes will simply be ignored for the purpose of
894 diff'ing, or it may be made more robust, or per the user's preference
895 (which then may be exposed as crm_diff switch).
896
897 Said XML_TEXT_NODE may appear unexpectedly due to how upgrade-2.10.xsl
898 is arranged.
899
900 The emergency fix is simple: reparse XSLT output with blank-ignoring
901 parser. */
902#ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
903#define PCMK_SCHEMAS_EMERGENCY_XSLT 1
904#endif
905
906static xmlNode *
907apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
908{
909 char *xform = NULL;
910 xmlNode *out = NULL;
911 xmlDocPtr res = NULL;
912 xmlDocPtr doc = NULL;
913 xsltStylesheet *xslt = NULL;
914#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
915 xmlChar *emergency_result;
916 int emergency_txt_len;
917 int emergency_res;
918#endif
919
920 CRM_CHECK(xml != NULL, return FALSE);
921 doc = getDocPtr(xml);
923 transform);
924
925 xmlLoadExtDtdDefaultValue = 1;
926 xmlSubstituteEntitiesDefault(1);
927
928 /* for capturing, e.g., what's emitted via <xsl:message> */
929 if (to_logs) {
930 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
931 } else {
932 xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
933 }
934
935 xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
936 CRM_CHECK(xslt != NULL, goto cleanup);
937
938 res = xsltApplyStylesheet(xslt, doc, NULL);
939 CRM_CHECK(res != NULL, goto cleanup);
940
941 xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
942
943
944#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
945 emergency_res = xsltSaveResultToString(&emergency_result,
946 &emergency_txt_len, res, xslt);
947 xmlFreeDoc(res);
948 CRM_CHECK(emergency_res == 0, goto cleanup);
949 out = string2xml((const char *) emergency_result);
950 free(emergency_result);
951#else
952 out = xmlDocGetRootElement(res);
953#endif
954
955 cleanup:
956 if (xslt) {
957 xsltFreeStylesheet(xslt);
958 }
959
960 free(xform);
961
962 return out;
963}
964
971static xmlNode *
972apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
973{
974 bool transform_onleave = schema->transform_onleave;
975 char *transform_leave;
976 xmlNode *upgrade = NULL,
977 *final = NULL;
978
979 if (schema->transform_enter) {
980 crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
981 schema->name, schema->transform_enter);
982 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
983 if (upgrade == NULL) {
984 crm_warn("Upgrade-enter transformation %s.xsl failed",
985 schema->transform_enter);
986 transform_onleave = FALSE;
987 }
988 }
989 if (upgrade == NULL) {
990 upgrade = xml;
991 }
992
993 crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
994 schema->name, schema->transform);
995 final = apply_transformation(upgrade, schema->transform, to_logs);
996 if (upgrade != xml) {
997 free_xml(upgrade);
998 upgrade = NULL;
999 }
1000
1001 if (final != NULL && transform_onleave) {
1002 upgrade = final;
1003 /* following condition ensured in add_schema_by_version */
1004 CRM_ASSERT(schema->transform_enter != NULL);
1005 transform_leave = strdup(schema->transform_enter);
1006 /* enter -> leave */
1007 memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1008 crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
1009 schema->name, transform_leave);
1010 final = apply_transformation(upgrade, transform_leave, to_logs);
1011 if (final == NULL) {
1012 crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
1013 final = upgrade;
1014 } else {
1015 free_xml(upgrade);
1016 }
1017 free(transform_leave);
1018 }
1019
1020 return final;
1021}
1022
1023const char *
1025{
1026 if (version < 0 || version >= xml_schema_max) {
1027 return "unknown";
1028 }
1029 return known_schemas[version].name;
1030}
1031
1032int
1034{
1035 int lpc = 0;
1036
1037 if (name == NULL) {
1039 }
1040 for (; lpc < xml_schema_max; lpc++) {
1041 if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
1042 return lpc;
1043 }
1044 }
1045 return -1;
1046}
1047
1048/* set which validation to use */
1049int
1050update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
1051 gboolean to_logs)
1052{
1053 xmlNode *xml = NULL;
1054 char *value = NULL;
1055 int max_stable_schemas = xml_latest_schema_index();
1056 int lpc = 0, match = -1, rc = pcmk_ok;
1057 int next = -1; /* -1 denotes "inactive" value */
1058
1059 CRM_CHECK(best != NULL, return -EINVAL);
1060 *best = 0;
1061
1062 CRM_CHECK(xml_blob != NULL, return -EINVAL);
1063 CRM_CHECK(*xml_blob != NULL, return -EINVAL);
1064
1065 xml = *xml_blob;
1067
1068 if (value != NULL) {
1069 match = get_schema_version(value);
1070
1071 lpc = match;
1072 if (lpc >= 0 && transform == FALSE) {
1073 *best = lpc++;
1074
1075 } else if (lpc < 0) {
1076 crm_debug("Unknown validation schema");
1077 lpc = 0;
1078 }
1079 }
1080
1081 if (match >= max_stable_schemas) {
1082 /* nothing to do */
1083 free(value);
1084 *best = match;
1085 return pcmk_ok;
1086 }
1087
1088 while (lpc <= max_stable_schemas) {
1089 crm_debug("Testing '%s' validation (%d of %d)",
1090 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1091 lpc, max_stable_schemas);
1092
1093 if (validate_with(xml, lpc, to_logs) == FALSE) {
1094 if (next != -1) {
1095 crm_info("Configuration not valid for schema: %s",
1096 known_schemas[lpc].name);
1097 next = -1;
1098 } else {
1099 crm_trace("%s validation failed",
1100 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1101 }
1102 if (*best) {
1103 /* we've satisfied the validation, no need to check further */
1104 break;
1105 }
1107
1108 } else {
1109 if (next != -1) {
1110 crm_debug("Configuration valid for schema: %s",
1111 known_schemas[next].name);
1112 next = -1;
1113 }
1114 rc = pcmk_ok;
1115 }
1116
1117 if (rc == pcmk_ok) {
1118 *best = lpc;
1119 }
1120
1121 if (rc == pcmk_ok && transform) {
1122 xmlNode *upgrade = NULL;
1123 next = known_schemas[lpc].after_transform;
1124
1125 if (next <= lpc) {
1126 /* There is no next version, or next would regress */
1127 crm_trace("Stopping at %s", known_schemas[lpc].name);
1128 break;
1129
1130 } else if (max > 0 && (lpc == max || next > max)) {
1131 crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1132 known_schemas[lpc].name, lpc, next, max);
1133 break;
1134
1135 } else if (known_schemas[lpc].transform == NULL
1136 /* possibly avoid transforming when readily valid
1137 (in general more restricted when crossing the major
1138 version boundary, as X.0 "transitional" version is
1139 expected to be more strict than it's successors that
1140 may re-allow constructs from previous major line) */
1141 || validate_with_silent(xml, next)) {
1142 crm_debug("%s-style configuration is also valid for %s",
1143 known_schemas[lpc].name, known_schemas[next].name);
1144
1145 lpc = next;
1146
1147 } else {
1148 crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1149 known_schemas[lpc].name, known_schemas[next].name,
1150 known_schemas[lpc].transform);
1151
1152 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1153 if (upgrade == NULL) {
1154 crm_err("Transformation %s.xsl failed",
1155 known_schemas[lpc].transform);
1157
1158 } else if (validate_with(upgrade, next, to_logs)) {
1159 crm_info("Transformation %s.xsl successful",
1160 known_schemas[lpc].transform);
1161 lpc = next;
1162 *best = next;
1163 free_xml(xml);
1164 xml = upgrade;
1165 rc = pcmk_ok;
1166
1167 } else {
1168 crm_err("Transformation %s.xsl did not produce a valid configuration",
1169 known_schemas[lpc].transform);
1170 crm_log_xml_info(upgrade, "transform:bad");
1171 free_xml(upgrade);
1173 }
1174 next = -1;
1175 }
1176 }
1177
1178 if (transform == FALSE || rc != pcmk_ok) {
1179 /* we need some progress! */
1180 lpc++;
1181 }
1182 }
1183
1184 if (*best > match && *best) {
1185 crm_info("%s the configuration from %s to %s",
1186 transform?"Transformed":"Upgraded",
1187 value ? value : "<none>", known_schemas[*best].name);
1188 crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1189 }
1190
1191 *xml_blob = xml;
1192 free(value);
1193 return rc;
1194}
1195
1196gboolean
1197cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1198{
1199 gboolean rc = TRUE;
1200 const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1201 char *const orig_value = strdup(value == NULL ? "(none)" : value);
1202
1203 int version = get_schema_version(value);
1204 int orig_version = version;
1205 int min_version = xml_minimum_schema_index();
1206
1207 if (version < min_version) {
1208 // Current configuration schema is not acceptable, try to update
1209 xmlNode *converted = NULL;
1210
1211 converted = copy_xml(*xml);
1212 update_validation(&converted, &version, 0, TRUE, to_logs);
1213
1214 value = crm_element_value(converted, XML_ATTR_VALIDATION);
1215 if (version < min_version) {
1216 // Updated configuration schema is still not acceptable
1217
1218 if (version < orig_version || orig_version == -1) {
1219 // We couldn't validate any schema at all
1220 if (to_logs) {
1221 pcmk__config_err("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",
1225 orig_value,
1226 get_schema_name(min_version),
1227 get_schema_name(orig_version),
1229 } else {
1230 fprintf(stderr, "Cannot upgrade configuration (claiming "
1231 "schema %s) to at least %s because it "
1232 "does not validate with any schema from "
1233 "%s to %s\n",
1234 orig_value,
1235 get_schema_name(min_version),
1236 get_schema_name(orig_version),
1238 }
1239 } else {
1240 // We updated configuration successfully, but still too low
1241 if (to_logs) {
1242 pcmk__config_err("Cannot upgrade configuration (claiming "
1243 "schema %s) to at least %s because it "
1244 "would not upgrade past %s",
1245 orig_value,
1246 get_schema_name(min_version),
1247 pcmk__s(value, "unspecified version"));
1248 } else {
1249 fprintf(stderr, "Cannot upgrade configuration (claiming "
1250 "schema %s) to at least %s because it "
1251 "would not upgrade past %s\n",
1252 orig_value,
1253 get_schema_name(min_version),
1254 pcmk__s(value, "unspecified version"));
1255 }
1256 }
1257
1258 free_xml(converted);
1259 converted = NULL;
1260 rc = FALSE;
1261
1262 } else {
1263 // Updated configuration schema is acceptable
1264 free_xml(*xml);
1265 *xml = converted;
1266
1267 if (version < xml_latest_schema_index()) {
1268 if (to_logs) {
1269 pcmk__config_warn("Configuration with schema %s was "
1270 "internally upgraded to acceptable (but "
1271 "not most recent) %s",
1272 orig_value, get_schema_name(version));
1273 }
1274 } else {
1275 if (to_logs) {
1276 crm_info("Configuration with schema %s was internally "
1277 "upgraded to latest version %s",
1278 orig_value, get_schema_name(version));
1279 }
1280 }
1281 }
1282
1284 // Schema validation is disabled
1285 if (to_logs) {
1286 pcmk__config_warn("Schema validation of configuration is disabled "
1287 "(enabling is encouraged and prevents common "
1288 "misconfigurations)");
1289
1290 } else {
1291 fprintf(stderr, "Schema validation of configuration is disabled "
1292 "(enabling is encouraged and prevents common "
1293 "misconfigurations)\n");
1294 }
1295 }
1296
1297 if (best_version) {
1298 *best_version = version;
1299 }
1300
1301 free(orig_value);
1302 return rc;
1303}
const char * name
Definition: cib.c:24
uint32_t version
Definition: remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
const char * pcmk__get_tmpdir(void)
Definition: io.c:541
#define crm_log_xml_info(xml, text)
Definition: logging.h:371
#define crm_info(fmt, args...)
Definition: logging.h:362
#define crm_warn(fmt, args...)
Definition: logging.h:360
#define crm_notice(fmt, args...)
Definition: logging.h:361
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:310
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
#define crm_debug(fmt, args...)
Definition: logging.h:364
#define crm_err(fmt, args...)
Definition: logging.h:359
unsigned int crm_log_level
Definition: logging.c:45
#define crm_trace(fmt, args...)
Definition: logging.h:365
#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:517
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:714
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:148
#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:1024
int get_schema_version(const char *name)
Definition: schemas.c:1033
#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:1050
#define schema_strdup_printf(prefix, version, suffix)
Definition: schemas.c:38
gboolean validate_xml_verbose(xmlNode *xml_blob)
Definition: schemas.c:679
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:113
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:1197
#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:707
void crm_schema_cleanup(void)
Definition: schemas.c:554
void crm_schema_init(void)
Definition: schemas.c:377
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:563
@ pcmk__str_none
@ pcmk__str_casei
Wrappers for and extensions to libxml2.
const xmlChar * pcmkXmlStr
Definition: xml.h:50
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition: xml.c:1333
xmlNode * string2xml(const char *input)
Definition: xml.c:930
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:711
void free_xml(xmlNode *child)
Definition: xml.c:885
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:891
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:3035
#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:3063