pacemaker 2.1.7-2.1.7
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
schemas.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2023 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
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
435static gboolean
436validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
437 relaxng_ctx_cache_t **cached_ctx)
438{
439 int rc = 0;
440 gboolean valid = TRUE;
441 relaxng_ctx_cache_t *ctx = NULL;
442
443 CRM_CHECK(doc != NULL, return FALSE);
444 CRM_CHECK(relaxng_file != NULL, return FALSE);
445
446 if (cached_ctx && *cached_ctx) {
447 ctx = *cached_ctx;
448
449 } else {
450 crm_debug("Creating RNG parser context");
451 ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
452
453 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
454 CRM_CHECK(ctx->parser != NULL, goto cleanup);
455
456 if (error_handler) {
457 xmlRelaxNGSetParserErrors(ctx->parser,
458 (xmlRelaxNGValidityErrorFunc) error_handler,
459 (xmlRelaxNGValidityWarningFunc) error_handler,
460 error_handler_context);
461 } else {
462 xmlRelaxNGSetParserErrors(ctx->parser,
463 (xmlRelaxNGValidityErrorFunc) fprintf,
464 (xmlRelaxNGValidityWarningFunc) fprintf,
465 stderr);
466 }
467
468 ctx->rng = xmlRelaxNGParse(ctx->parser);
469 CRM_CHECK(ctx->rng != NULL,
470 crm_err("Could not find/parse %s", relaxng_file);
471 goto cleanup);
472
473 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
474 CRM_CHECK(ctx->valid != NULL, goto cleanup);
475
476 if (error_handler) {
477 xmlRelaxNGSetValidErrors(ctx->valid,
478 (xmlRelaxNGValidityErrorFunc) error_handler,
479 (xmlRelaxNGValidityWarningFunc) error_handler,
480 error_handler_context);
481 } else {
482 xmlRelaxNGSetValidErrors(ctx->valid,
483 (xmlRelaxNGValidityErrorFunc) fprintf,
484 (xmlRelaxNGValidityWarningFunc) fprintf,
485 stderr);
486 }
487 }
488
489 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
490 if (rc > 0) {
491 valid = FALSE;
492
493 } else if (rc < 0) {
494 crm_err("Internal libxml error during validation");
495 }
496
497 cleanup:
498
499 if (cached_ctx) {
500 *cached_ctx = ctx;
501
502 } else {
503 if (ctx->parser != NULL) {
504 xmlRelaxNGFreeParserCtxt(ctx->parser);
505 }
506 if (ctx->valid != NULL) {
507 xmlRelaxNGFreeValidCtxt(ctx->valid);
508 }
509 if (ctx->rng != NULL) {
510 xmlRelaxNGFree(ctx->rng);
511 }
512 free(ctx);
513 }
514
515 return valid;
516}
517
522void
524{
525 int lpc;
526 relaxng_ctx_cache_t *ctx = NULL;
527
528 for (lpc = 0; lpc < xml_schema_max; lpc++) {
529
530 switch (known_schemas[lpc].validator) {
531 case schema_validator_none: // not cached
532 break;
533 case schema_validator_rng: // cached
534 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
535 if (ctx == NULL) {
536 break;
537 }
538 if (ctx->parser != NULL) {
539 xmlRelaxNGFreeParserCtxt(ctx->parser);
540 }
541 if (ctx->valid != NULL) {
542 xmlRelaxNGFreeValidCtxt(ctx->valid);
543 }
544 if (ctx->rng != NULL) {
545 xmlRelaxNGFree(ctx->rng);
546 }
547 free(ctx);
548 known_schemas[lpc].cache = NULL;
549 break;
550 }
551 free(known_schemas[lpc].name);
552 free(known_schemas[lpc].transform);
553 free(known_schemas[lpc].transform_enter);
554 }
555 free(known_schemas);
556 known_schemas = NULL;
557
558 wrap_libxslt(true);
559}
560
561static gboolean
562validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
563{
564 gboolean valid = FALSE;
565 char *file = NULL;
566 struct schema_s *schema = NULL;
567 relaxng_ctx_cache_t **cache = NULL;
568
569 if (method < 0) {
570 return FALSE;
571 }
572
573 schema = &(known_schemas[method]);
574 if (schema->validator == schema_validator_none) {
575 return TRUE;
576 }
577
578 if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
579 crm_warn("The pacemaker-next schema is deprecated and will be removed "
580 "in a future release.");
581 }
582
584 schema->name);
585
586 crm_trace("Validating with %s (type=%d)",
587 pcmk__s(file, "missing schema"), schema->validator);
588 switch (schema->validator) {
590 cache = (relaxng_ctx_cache_t **) &(schema->cache);
591 valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
592 break;
593 default:
594 crm_err("Unknown validator type: %d",
595 known_schemas[method].validator);
596 break;
597 }
598
599 free(file);
600 return valid;
601}
602
603static bool
604validate_with_silent(xmlNode *xml, int method)
605{
606 bool rc, sl_backup = silent_logging;
607 silent_logging = TRUE;
608 rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
609 silent_logging = sl_backup;
610 return rc;
611}
612
613static void
614dump_file(const char *filename)
615{
616
617 FILE *fp = NULL;
618 int ch, line = 0;
619
620 CRM_CHECK(filename != NULL, return);
621
622 fp = fopen(filename, "r");
623 if (fp == NULL) {
624 crm_perror(LOG_ERR, "Could not open %s for reading", filename);
625 return;
626 }
627
628 fprintf(stderr, "%4d ", ++line);
629 do {
630 ch = getc(fp);
631 if (ch == EOF) {
632 putc('\n', stderr);
633 break;
634 } else if (ch == '\n') {
635 fprintf(stderr, "\n%4d ", ++line);
636 } else {
637 putc(ch, stderr);
638 }
639 } while (1);
640
641 fclose(fp);
642}
643
644gboolean
645validate_xml_verbose(const xmlNode *xml_blob)
646{
647 int fd = 0;
648 xmlDoc *doc = NULL;
649 xmlNode *xml = NULL;
650 gboolean rc = FALSE;
651 char *filename = NULL;
652
653 filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
654
655 umask(S_IWGRP | S_IWOTH | S_IROTH);
656 fd = mkstemp(filename);
657 write_xml_fd(xml_blob, filename, fd, FALSE);
658
659 dump_file(filename);
660
661 doc = xmlReadFile(filename, NULL, 0);
662 xml = xmlDocGetRootElement(doc);
663 rc = validate_xml(xml, NULL, FALSE);
664 free_xml(xml);
665
666 unlink(filename);
667 free(filename);
668
669 return rc;
670}
671
672gboolean
673validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
674{
675 return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
676}
677
678gboolean
679pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
680{
681 int version = 0;
682
683 CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
684
685 if (validation == NULL) {
686 validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
687 }
688
689 if (validation == NULL) {
690 int lpc = 0;
691 bool valid = FALSE;
692
693 for (lpc = 0; lpc < xml_schema_max; lpc++) {
694 if (validate_with(xml_blob, lpc, NULL, NULL)) {
695 valid = TRUE;
697 known_schemas[lpc].name);
698 crm_info("XML validated against %s", known_schemas[lpc].name);
699 if(known_schemas[lpc].after_transform == 0) {
700 break;
701 }
702 }
703 }
704
705 return valid;
706 }
707
708 version = get_schema_version(validation);
709 if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
710 return TRUE;
711 } else if (version < xml_schema_max) {
712 return validate_with(xml_blob, version, error_handler, error_handler_context);
713 }
714
715 crm_err("Unknown validator: %s", validation);
716 return FALSE;
717}
718
719static void
720cib_upgrade_err(void *ctx, const char *fmt, ...)
721G_GNUC_PRINTF(2, 3);
722
723/* With this arrangement, an attempt to identify the message severity
724 as explicitly signalled directly from XSLT is performed in rather
725 a smart way (no reliance on formatting string + arguments being
726 always specified as ["%s", purposeful_string], as it can also be
727 ["%s: %s", some_prefix, purposeful_string] etc. so every argument
728 pertaining %s specifier is investigated), and if such a mark found,
729 the respective level is determined and, when the messages are to go
730 to the native logs, the mark itself gets dropped
731 (by the means of string shift).
732
733 NOTE: whether the native logging is the right sink is decided per
734 the ctx parameter -- NULL denotes this case, otherwise it
735 carries a pointer to the numeric expression of the desired
736 target logging level (messages with higher level will be
737 suppressed)
738
739 NOTE: on some architectures, this string shift may not have any
740 effect, but that's an acceptable tradeoff
741
742 The logging level for not explicitly designated messages
743 (suspicious, likely internal errors or some runaways) is
744 LOG_WARNING.
745 */
746static void
747cib_upgrade_err(void *ctx, const char *fmt, ...)
748{
749 va_list ap, aq;
750 char *arg_cur;
751
752 bool found = FALSE;
753 const char *fmt_iter = fmt;
754 uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
755 const unsigned * log_level = (const unsigned *) ctx;
756 enum {
757 escan_seennothing,
758 escan_seenpercent,
759 } scan_state = escan_seennothing;
760
761 va_start(ap, fmt);
762 va_copy(aq, ap);
763
764 while (!found && *fmt_iter != '\0') {
765 /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
766 switch (*fmt_iter++) {
767 case '%':
768 if (scan_state == escan_seennothing) {
769 scan_state = escan_seenpercent;
770 } else if (scan_state == escan_seenpercent) {
771 scan_state = escan_seennothing;
772 }
773 break;
774 case 's':
775 if (scan_state == escan_seenpercent) {
776 scan_state = escan_seennothing;
777 arg_cur = va_arg(aq, char *);
778 if (arg_cur != NULL) {
779 switch (arg_cur[0]) {
780 case 'W':
781 if (!strncmp(arg_cur, "WARNING: ",
782 sizeof("WARNING: ") - 1)) {
783 msg_log_level = LOG_WARNING;
784 }
785 if (ctx == NULL) {
786 memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
787 strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
788 }
789 found = TRUE;
790 break;
791 case 'I':
792 if (!strncmp(arg_cur, "INFO: ",
793 sizeof("INFO: ") - 1)) {
794 msg_log_level = LOG_INFO;
795 }
796 if (ctx == NULL) {
797 memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
798 strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
799 }
800 found = TRUE;
801 break;
802 case 'D':
803 if (!strncmp(arg_cur, "DEBUG: ",
804 sizeof("DEBUG: ") - 1)) {
805 msg_log_level = LOG_DEBUG;
806 }
807 if (ctx == NULL) {
808 memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
809 strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
810 }
811 found = TRUE;
812 break;
813 }
814 }
815 }
816 break;
817 case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
818 case '0': case '1': case '2': case '3': case '4':
819 case '5': case '6': case '7': case '8': case '9':
820 case '*':
821 break;
822 case 'l':
823 case 'z':
824 case 't':
825 case 'j':
826 case 'd': case 'i':
827 case 'o':
828 case 'u':
829 case 'x': case 'X':
830 case 'e': case 'E':
831 case 'f': case 'F':
832 case 'g': case 'G':
833 case 'a': case 'A':
834 case 'c':
835 case 'p':
836 if (scan_state == escan_seenpercent) {
837 (void) va_arg(aq, void *); /* skip forward */
838 scan_state = escan_seennothing;
839 }
840 break;
841 default:
842 scan_state = escan_seennothing;
843 break;
844 }
845 }
846
847 if (log_level != NULL) {
848 /* intention of the following offset is:
849 cibadmin -V -> start showing INFO labelled messages */
850 if (*log_level + 4 >= msg_log_level) {
851 vfprintf(stderr, fmt, ap);
852 }
853 } else {
854 PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
855 }
856
857 va_end(aq);
858 va_end(ap);
859}
860
861static xmlNode *
862apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
863{
864 char *xform = NULL;
865 xmlNode *out = NULL;
866 xmlDocPtr res = NULL;
867 xsltStylesheet *xslt = NULL;
868
870 transform);
871
872 /* for capturing, e.g., what's emitted via <xsl:message> */
873 if (to_logs) {
874 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
875 } else {
876 xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
877 }
878
879 xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
880 CRM_CHECK(xslt != NULL, goto cleanup);
881
882 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
883 CRM_CHECK(res != NULL, goto cleanup);
884
885 xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
886
887 out = xmlDocGetRootElement(res);
888
889 cleanup:
890 if (xslt) {
891 xsltFreeStylesheet(xslt);
892 }
893
894 free(xform);
895
896 return out;
897}
898
905static xmlNode *
906apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
907{
908 bool transform_onleave = schema->transform_onleave;
909 char *transform_leave;
910 xmlNode *upgrade = NULL,
911 *final = NULL;
912
913 if (schema->transform_enter) {
914 crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
915 schema->name, schema->transform_enter);
916 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
917 if (upgrade == NULL) {
918 crm_warn("Upgrade-enter transformation %s.xsl failed",
919 schema->transform_enter);
920 transform_onleave = FALSE;
921 }
922 }
923 if (upgrade == NULL) {
924 upgrade = xml;
925 }
926
927 crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
928 schema->name, schema->transform);
929 final = apply_transformation(upgrade, schema->transform, to_logs);
930 if (upgrade != xml) {
931 free_xml(upgrade);
932 upgrade = NULL;
933 }
934
935 if (final != NULL && transform_onleave) {
936 upgrade = final;
937 /* following condition ensured in add_schema_by_version */
938 CRM_ASSERT(schema->transform_enter != NULL);
939 transform_leave = strdup(schema->transform_enter);
940 /* enter -> leave */
941 memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
942 crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
943 schema->name, transform_leave);
944 final = apply_transformation(upgrade, transform_leave, to_logs);
945 if (final == NULL) {
946 crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
947 final = upgrade;
948 } else {
949 free_xml(upgrade);
950 }
951 free(transform_leave);
952 }
953
954 return final;
955}
956
957const char *
959{
960 if (version < 0 || version >= xml_schema_max) {
961 return "unknown";
962 }
963 return known_schemas[version].name;
964}
965
966int
968{
969 int lpc = 0;
970
971 if (name == NULL) {
973 }
974 for (; lpc < xml_schema_max; lpc++) {
975 if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
976 return lpc;
977 }
978 }
979 return -1;
980}
981
982/* set which validation to use */
983int
984update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
985 gboolean to_logs)
986{
987 xmlNode *xml = NULL;
988 char *value = NULL;
989 int max_stable_schemas = xml_latest_schema_index();
990 int lpc = 0, match = -1, rc = pcmk_ok;
991 int next = -1; /* -1 denotes "inactive" value */
992 xmlRelaxNGValidityErrorFunc error_handler =
993 to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
994
995 CRM_CHECK(best != NULL, return -EINVAL);
996 *best = 0;
997
998 CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
999 && ((*xml_blob)->doc != NULL),
1000 return -EINVAL);
1001
1002 xml = *xml_blob;
1004
1005 if (value != NULL) {
1006 match = get_schema_version(value);
1007
1008 lpc = match;
1009 if (lpc >= 0 && transform == FALSE) {
1010 *best = lpc++;
1011
1012 } else if (lpc < 0) {
1013 crm_debug("Unknown validation schema");
1014 lpc = 0;
1015 }
1016 }
1017
1018 if (match >= max_stable_schemas) {
1019 /* nothing to do */
1020 free(value);
1021 *best = match;
1022 return pcmk_ok;
1023 }
1024
1025 while (lpc <= max_stable_schemas) {
1026 crm_debug("Testing '%s' validation (%d of %d)",
1027 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1028 lpc, max_stable_schemas);
1029
1030 if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
1031 if (next != -1) {
1032 crm_info("Configuration not valid for schema: %s",
1033 known_schemas[lpc].name);
1034 next = -1;
1035 } else {
1036 crm_trace("%s validation failed",
1037 known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1038 }
1039 if (*best) {
1040 /* we've satisfied the validation, no need to check further */
1041 break;
1042 }
1044
1045 } else {
1046 if (next != -1) {
1047 crm_debug("Configuration valid for schema: %s",
1048 known_schemas[next].name);
1049 next = -1;
1050 }
1051 rc = pcmk_ok;
1052 }
1053
1054 if (rc == pcmk_ok) {
1055 *best = lpc;
1056 }
1057
1058 if (rc == pcmk_ok && transform) {
1059 xmlNode *upgrade = NULL;
1060 next = known_schemas[lpc].after_transform;
1061
1062 if (next <= lpc) {
1063 /* There is no next version, or next would regress */
1064 crm_trace("Stopping at %s", known_schemas[lpc].name);
1065 break;
1066
1067 } else if (max > 0 && (lpc == max || next > max)) {
1068 crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1069 known_schemas[lpc].name, lpc, next, max);
1070 break;
1071
1072 } else if (known_schemas[lpc].transform == NULL
1073 /* possibly avoid transforming when readily valid
1074 (in general more restricted when crossing the major
1075 version boundary, as X.0 "transitional" version is
1076 expected to be more strict than it's successors that
1077 may re-allow constructs from previous major line) */
1078 || validate_with_silent(xml, next)) {
1079 crm_debug("%s-style configuration is also valid for %s",
1080 known_schemas[lpc].name, known_schemas[next].name);
1081
1082 lpc = next;
1083
1084 } else {
1085 crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1086 known_schemas[lpc].name, known_schemas[next].name,
1087 known_schemas[lpc].transform);
1088
1089 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1090 if (upgrade == NULL) {
1091 crm_err("Transformation %s.xsl failed",
1092 known_schemas[lpc].transform);
1094
1095 } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) {
1096 crm_info("Transformation %s.xsl successful",
1097 known_schemas[lpc].transform);
1098 lpc = next;
1099 *best = next;
1100 free_xml(xml);
1101 xml = upgrade;
1102 rc = pcmk_ok;
1103
1104 } else {
1105 crm_err("Transformation %s.xsl did not produce a valid configuration",
1106 known_schemas[lpc].transform);
1107 crm_log_xml_info(upgrade, "transform:bad");
1108 free_xml(upgrade);
1110 }
1111 next = -1;
1112 }
1113 }
1114
1115 if (transform == FALSE || rc != pcmk_ok) {
1116 /* we need some progress! */
1117 lpc++;
1118 }
1119 }
1120
1121 if (*best > match && *best) {
1122 crm_info("%s the configuration from %s to %s",
1123 transform?"Transformed":"Upgraded",
1124 value ? value : "<none>", known_schemas[*best].name);
1125 crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1126 }
1127
1128 *xml_blob = xml;
1129 free(value);
1130 return rc;
1131}
1132
1133gboolean
1134cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1135{
1136 gboolean rc = TRUE;
1137 const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1138 char *const orig_value = strdup(value == NULL ? "(none)" : value);
1139
1140 int version = get_schema_version(value);
1141 int orig_version = version;
1142 int min_version = xml_minimum_schema_index();
1143
1144 if (version < min_version) {
1145 // Current configuration schema is not acceptable, try to update
1146 xmlNode *converted = NULL;
1147
1148 converted = copy_xml(*xml);
1149 update_validation(&converted, &version, 0, TRUE, to_logs);
1150
1151 value = crm_element_value(converted, XML_ATTR_VALIDATION);
1152 if (version < min_version) {
1153 // Updated configuration schema is still not acceptable
1154
1155 if (version < orig_version || orig_version == -1) {
1156 // We couldn't validate any schema at all
1157 if (to_logs) {
1158 pcmk__config_err("Cannot upgrade configuration (claiming "
1159 "schema %s) to at least %s because it "
1160 "does not validate with any schema from "
1161 "%s to %s",
1162 orig_value,
1163 get_schema_name(min_version),
1164 get_schema_name(orig_version),
1166 } else {
1167 fprintf(stderr, "Cannot upgrade configuration (claiming "
1168 "schema %s) to at least %s because it "
1169 "does not validate with any schema from "
1170 "%s to %s\n",
1171 orig_value,
1172 get_schema_name(min_version),
1173 get_schema_name(orig_version),
1175 }
1176 } else {
1177 // We updated configuration successfully, but still too low
1178 if (to_logs) {
1179 pcmk__config_err("Cannot upgrade configuration (claiming "
1180 "schema %s) to at least %s because it "
1181 "would not upgrade past %s",
1182 orig_value,
1183 get_schema_name(min_version),
1184 pcmk__s(value, "unspecified version"));
1185 } else {
1186 fprintf(stderr, "Cannot upgrade configuration (claiming "
1187 "schema %s) to at least %s because it "
1188 "would not upgrade past %s\n",
1189 orig_value,
1190 get_schema_name(min_version),
1191 pcmk__s(value, "unspecified version"));
1192 }
1193 }
1194
1195 free_xml(converted);
1196 converted = NULL;
1197 rc = FALSE;
1198
1199 } else {
1200 // Updated configuration schema is acceptable
1201 free_xml(*xml);
1202 *xml = converted;
1203
1204 if (version < xml_latest_schema_index()) {
1205 if (to_logs) {
1206 pcmk__config_warn("Configuration with schema %s was "
1207 "internally upgraded to acceptable (but "
1208 "not most recent) %s",
1209 orig_value, get_schema_name(version));
1210 }
1211 } else {
1212 if (to_logs) {
1213 crm_info("Configuration with schema %s was internally "
1214 "upgraded to latest version %s",
1215 orig_value, get_schema_name(version));
1216 }
1217 }
1218 }
1219
1221 // Schema validation is disabled
1222 if (to_logs) {
1223 pcmk__config_warn("Schema validation of configuration is disabled "
1224 "(enabling is encouraged and prevents common "
1225 "misconfigurations)");
1226
1227 } else {
1228 fprintf(stderr, "Schema validation of configuration is disabled "
1229 "(enabling is encouraged and prevents common "
1230 "misconfigurations)\n");
1231 }
1232 }
1233
1234 if (best_version) {
1235 *best_version = version;
1236 }
1237
1238 free(orig_value);
1239 return rc;
1240}
const char * name
Definition cib.c:26
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:547
#define crm_log_xml_info(xml, text)
Definition logging.h:391
#define crm_info(fmt, args...)
Definition logging.h:382
#define crm_warn(fmt, args...)
Definition logging.h:380
#define crm_notice(fmt, args...)
Definition logging.h:381
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition logging.h:323
#define CRM_CHECK(expr, failure_action)
Definition logging.h:238
#define crm_debug(fmt, args...)
Definition logging.h:384
#define crm_err(fmt, args...)
Definition logging.h:379
unsigned int crm_log_level
Definition logging.c:45
#define crm_trace(fmt, args...)
Definition logging.h:385
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
#define XML_ATTR_VALIDATION
Definition msg_xml.h:142
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:447
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition nvpair.c:644
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:302
#define PCMK__VALUE_NONE
#define CRM_ASSERT(expr)
Definition results.h:42
#define pcmk_err_schema_validation
Definition results.h:73
@ pcmk_rc_ok
Definition results.h:154
#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:958
int get_schema_version(const char *name)
Definition schemas.c:967
#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:984
#define schema_strdup_printf(prefix, version, suffix)
Definition schemas.c:38
gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context)
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:1134
#define schema_scanf(s, prefix, version, suffix)
Definition schemas.c:35
gboolean validate_xml_verbose(const xmlNode *xml_blob)
Definition schemas.c:645
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
Definition schemas.c:673
void crm_schema_cleanup(void)
Definition schemas.c:523
void crm_schema_init(void)
Definition schemas.c:377
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition strings.c:560
@ pcmk__str_none
@ pcmk__str_casei
Wrappers for and extensions to libxml2.
int write_xml_fd(const xmlNode *xml, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition xml.c:1255
const xmlChar * pcmkXmlStr
Definition xml.h:50
void free_xml(xmlNode *child)
Definition xml.c:783
xmlNode * copy_xml(xmlNode *src_node)
Definition xml.c:789
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition xml.c:2586
#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:2614