17 #include <libxml/relaxng.h>
20 # include <libxslt/xslt.h>
21 # include <libxslt/transform.h>
22 # include <libxslt/xsltutils.h>
33 #define SCHEMA_ZERO { .v = { 0, 0 } }
35 #define schema_scanf(s, prefix, version, suffix) \
36 sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
38 #define schema_strdup_printf(prefix, version, suffix) \
39 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
43 xmlRelaxNGValidCtxtPtr valid;
44 xmlRelaxNGParserCtxtPtr parser;
45 } relaxng_ctx_cache_t;
60 char *transform_enter;
61 bool transform_onleave;
64 static struct schema_s *known_schemas = NULL;
65 static int xml_schema_max = 0;
66 static bool silent_logging = FALSE;
69 xml_log(
int priority,
const char *fmt, ...)
73 xml_log(
int priority, const
char *fmt, ...)
78 if (silent_logging == FALSE) {
86 xml_latest_schema_index(
void)
88 return xml_schema_max - 3;
92 xml_minimum_schema_index(
void)
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]) {
107 best = xml_latest_schema_index();
119 get_schema_root(
void)
121 static const char *base = NULL;
124 base = getenv(
"PCMK_schema_directory");
126 if (base == NULL || strlen(base) == 0) {
133 get_schema_path(
const char *name,
const char *file)
135 const char *base = get_schema_root();
149 version_from_filename(
const char *filename, schema_version_t *
version)
157 schema_filter(
const struct dirent *a)
162 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
168 }
else if (!version_from_filename(a->d_name, &
version)) {
180 schema_sort(
const struct dirent **a,
const struct dirent **b)
185 if (!version_from_filename(a[0]->d_name, &a_version)
186 || !version_from_filename(b[0]->d_name, &b_version)) {
191 for (
int i = 0; i < 2; ++i) {
192 if (a_version.v[i] < b_version.v[i]) {
194 }
else if (a_version.v[i] > b_version.v[i]) {
210 const char *name,
const char *location,
const char *transform,
211 const char *transform_enter,
bool transform_onleave,
214 int last = xml_schema_max;
215 bool have_version = FALSE;
218 known_schemas = realloc_safe(known_schemas,
219 xml_schema_max *
sizeof(
struct schema_s));
221 memset(known_schemas+last, 0,
sizeof(
struct schema_s));
222 known_schemas[last].validator = validator;
223 known_schemas[last].after_transform = after_transform;
225 for (
int i = 0; i < 2; ++i) {
226 known_schemas[last].version.v[i] =
version->v[i];
234 known_schemas[last].name);
239 known_schemas[last].name = strdup(name);
240 known_schemas[last].location = strdup(location);
244 known_schemas[last].transform = strdup(transform);
246 if (transform_enter) {
247 known_schemas[last].transform_enter = strdup(transform_enter);
249 known_schemas[last].transform_onleave = transform_onleave;
250 if (after_transform == 0) {
251 after_transform = xml_schema_max;
253 known_schemas[last].after_transform = after_transform;
255 if (known_schemas[last].after_transform < 0) {
256 crm_debug(
"Added supported schema %d: %s (%s)",
257 last, known_schemas[last].name, known_schemas[last].location);
259 }
else if (known_schemas[last].transform) {
260 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d with %s)",
261 last, known_schemas[last].name, known_schemas[last].location,
262 known_schemas[last].after_transform,
263 known_schemas[last].transform);
266 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d)",
267 last, known_schemas[last].name, known_schemas[last].location,
268 known_schemas[last].after_transform);
300 add_schema_by_version(
const schema_version_t *
version,
int next,
301 bool transform_expected)
303 bool transform_onleave = FALSE;
307 *transform_upgrade = NULL,
308 *transform_enter = NULL;
311 if (transform_expected) {
314 xslt = get_schema_path(NULL, transform_upgrade);
317 if (!transform_expected) {
320 }
else if (stat(xslt, &s) == 0) {
324 xslt = get_schema_path(NULL, transform_enter);
325 if (stat(xslt, &s) != 0) {
327 crm_debug(
"Upgrade-enter transform %s not found", xslt);
329 free(transform_enter);
330 transform_enter = strdup(
"upgrade-enter.xsl");
331 xslt = get_schema_path(NULL, transform_enter);
332 if (stat(xslt, &s) != 0) {
333 crm_debug(
"Upgrade-enter transform %s not found, either", xslt);
341 memcpy(strrchr(xslt,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
342 transform_onleave = (stat(xslt, &s) == 0);
345 free(transform_enter);
346 transform_enter = NULL;
350 crm_err(
"Upgrade transform %s not found", xslt);
352 free(transform_upgrade);
353 transform_upgrade = NULL;
359 transform_upgrade, transform_enter, transform_onleave, next);
361 free(transform_upgrade);
362 free(transform_enter);
375 const char *base = get_schema_root();
376 struct dirent **namelist = NULL;
379 max = scandir(base, &namelist, schema_filter, schema_sort);
384 for (lpc = 0; lpc < max; lpc++) {
385 bool transform_expected = FALSE;
389 if (!version_from_filename(namelist[lpc]->d_name, &
version)) {
391 crm_err(
"Skipping schema '%s': could not parse version",
392 namelist[lpc]->d_name);
395 if ((lpc + 1) < max) {
398 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
399 && (
version.v[0] < next_version.v[0])) {
400 transform_expected = TRUE;
406 if (add_schema_by_version(&
version, next, transform_expected)
412 for (lpc = 0; lpc < max; lpc++) {
419 "pacemaker-next.rng", NULL, NULL, FALSE, -1);
422 "N/A", NULL, NULL, FALSE, -1);
427 relaxng_invalid_stderr(
void *userData, xmlErrorPtr error)
447 crm_err(
"Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
452 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs,
const char *relaxng_file,
453 relaxng_ctx_cache_t **cached_ctx)
456 gboolean valid = TRUE;
457 relaxng_ctx_cache_t *ctx = NULL;
460 CRM_CHECK(relaxng_file != NULL,
return FALSE);
462 if (cached_ctx && *cached_ctx) {
466 crm_info(
"Creating RNG parser context");
467 ctx = calloc(1,
sizeof(relaxng_ctx_cache_t));
469 xmlLoadExtDtdDefaultValue = 1;
470 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
471 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
474 xmlRelaxNGSetParserErrors(ctx->parser,
475 (xmlRelaxNGValidityErrorFunc) xml_log,
476 (xmlRelaxNGValidityWarningFunc) xml_log,
477 GUINT_TO_POINTER(LOG_ERR));
479 xmlRelaxNGSetParserErrors(ctx->parser,
480 (xmlRelaxNGValidityErrorFunc) fprintf,
481 (xmlRelaxNGValidityWarningFunc) fprintf,
485 ctx->rng = xmlRelaxNGParse(ctx->parser);
487 crm_err(
"Could not find/parse %s", relaxng_file);
490 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
491 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
494 xmlRelaxNGSetValidErrors(ctx->valid,
495 (xmlRelaxNGValidityErrorFunc) xml_log,
496 (xmlRelaxNGValidityWarningFunc) xml_log,
497 GUINT_TO_POINTER(LOG_ERR));
499 xmlRelaxNGSetValidErrors(ctx->valid,
500 (xmlRelaxNGValidityErrorFunc) fprintf,
501 (xmlRelaxNGValidityWarningFunc) fprintf,
509 xmlLineNumbersDefault(1);
510 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
515 crm_err(
"Internal libxml error during validation");
524 if (ctx->parser != NULL) {
525 xmlRelaxNGFreeParserCtxt(ctx->parser);
527 if (ctx->valid != NULL) {
528 xmlRelaxNGFreeValidCtxt(ctx->valid);
530 if (ctx->rng != NULL) {
531 xmlRelaxNGFree(ctx->rng);
547 relaxng_ctx_cache_t *ctx = NULL;
549 for (lpc = 0; lpc < xml_schema_max; lpc++) {
551 switch (known_schemas[lpc].validator) {
555 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
559 if (ctx->parser != NULL) {
560 xmlRelaxNGFreeParserCtxt(ctx->parser);
562 if (ctx->valid != NULL) {
563 xmlRelaxNGFreeValidCtxt(ctx->valid);
565 if (ctx->rng != NULL) {
566 xmlRelaxNGFree(ctx->rng);
569 known_schemas[lpc].cache = NULL;
572 free(known_schemas[lpc].name);
573 free(known_schemas[lpc].location);
574 free(known_schemas[lpc].transform);
575 free(known_schemas[lpc].transform_enter);
578 known_schemas = NULL;
580 xsltCleanupGlobals();
587 validate_with(xmlNode *xml,
int method, gboolean to_logs)
589 xmlDocPtr doc = NULL;
590 gboolean valid = FALSE;
603 file = get_schema_path(known_schemas[method].name,
604 known_schemas[method].location);
606 crm_trace(
"Validating with: %s (type=%d)",
607 crm_str(file), known_schemas[method].validator);
608 switch (known_schemas[method].validator) {
611 validate_with_relaxng(doc, to_logs, file,
612 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
615 crm_err(
"Unknown validator type: %d",
616 known_schemas[method].validator);
625 validate_with_silent(xmlNode *xml,
int method)
627 bool rc, sl_backup = silent_logging;
628 silent_logging = TRUE;
629 rc = validate_with(xml, method, TRUE);
630 silent_logging = sl_backup;
635 dump_file(
const char *filename)
643 fp = fopen(filename,
"r");
645 crm_perror(LOG_ERR,
"Could not open %s for reading", filename);
649 fprintf(stderr,
"%4d ", ++line);
655 }
else if (ch ==
'\n') {
656 fprintf(stderr,
"\n%4d ", ++line);
672 char *filename = NULL;
676 umask(S_IWGRP | S_IWOTH | S_IROTH);
677 fd = mkstemp(filename);
682 doc = xmlParseFile(filename);
683 xml = xmlDocGetRootElement(doc);
694 validate_xml(xmlNode *xml_blob,
const char *validation, gboolean to_logs)
698 if (validation == NULL) {
702 if (validation == NULL) {
706 for (lpc = 0; lpc < xml_schema_max; lpc++) {
707 if (validate_with(xml_blob, lpc, FALSE)) {
710 known_schemas[lpc].name);
711 crm_info(
"XML validated against %s", known_schemas[lpc].name);
712 if(known_schemas[lpc].after_transform == 0) {
722 if (strcmp(validation,
"none") == 0) {
724 }
else if (
version < xml_schema_max) {
725 return validate_with(xml_blob,
version, to_logs);
728 crm_err(
"Unknown validator: %s", validation);
735 cib_upgrade_err(
void *ctx,
const char *fmt, ...)
762 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
768 const char *fmt_iter = fmt;
769 uint8_t msg_log_level = LOG_WARNING;
770 const unsigned * log_level = (
const unsigned *) ctx;
774 } scan_state = escan_seennothing;
779 while (!found && *fmt_iter !=
'\0') {
781 switch (*fmt_iter++) {
783 if (scan_state == escan_seennothing) {
784 scan_state = escan_seenpercent;
785 }
else if (scan_state == escan_seenpercent) {
786 scan_state = escan_seennothing;
790 if (scan_state == escan_seenpercent) {
791 scan_state = escan_seennothing;
792 arg_cur = va_arg(aq,
char *);
793 if (arg_cur != NULL) {
794 switch (arg_cur[0]) {
796 if (!strncmp(arg_cur,
"WARNING: ",
797 sizeof(
"WARNING: ") - 1)) {
798 msg_log_level = LOG_WARNING;
801 memmove(arg_cur, arg_cur +
sizeof(
"WARNING: ") - 1,
802 strlen(arg_cur +
sizeof(
"WARNING: ") - 1) + 1);
807 if (!strncmp(arg_cur,
"INFO: ",
808 sizeof(
"INFO: ") - 1)) {
809 msg_log_level = LOG_INFO;
812 memmove(arg_cur, arg_cur +
sizeof(
"INFO: ") - 1,
813 strlen(arg_cur +
sizeof(
"INFO: ") - 1) + 1);
818 if (!strncmp(arg_cur,
"DEBUG: ",
819 sizeof(
"DEBUG: ") - 1)) {
820 msg_log_level = LOG_DEBUG;
823 memmove(arg_cur, arg_cur +
sizeof(
"DEBUG: ") - 1,
824 strlen(arg_cur +
sizeof(
"DEBUG: ") - 1) + 1);
832 case '#':
case '-':
case ' ':
case '+':
case '\'':
case 'I':
case '.':
833 case '0':
case '1':
case '2':
case '3':
case '4':
834 case '5':
case '6':
case '7':
case '8':
case '9':
851 if (scan_state == escan_seenpercent) {
852 (void) va_arg(aq,
void *);
853 scan_state = escan_seennothing;
857 scan_state = escan_seennothing;
862 if (log_level != NULL) {
865 if (*log_level + 4 >= msg_log_level) {
866 vfprintf(stderr, fmt, ap);
891 #ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
892 #define PCMK_SCHEMAS_EMERGENCY_XSLT 1
896 apply_transformation(xmlNode *xml,
const char *transform, gboolean to_logs)
900 xmlDocPtr res = NULL;
901 xmlDocPtr doc = NULL;
902 xsltStylesheet *xslt = NULL;
903 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
904 xmlChar *emergency_result;
905 int emergency_txt_len;
911 xform = get_schema_path(NULL, transform);
913 xmlLoadExtDtdDefaultValue = 1;
914 xmlSubstituteEntitiesDefault(1);
918 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
923 xslt = xsltParseStylesheetFile((
const xmlChar *)xform);
926 res = xsltApplyStylesheet(xslt, doc, NULL);
929 xsltSetGenericErrorFunc(NULL, NULL);
932 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
933 emergency_res = xsltSaveResultToString(&emergency_result,
934 &emergency_txt_len, res, xslt);
936 CRM_CHECK(emergency_res == 0,
goto cleanup);
937 out =
string2xml((
const char *) emergency_result);
938 free(emergency_result);
940 out = xmlDocGetRootElement(res);
945 xsltFreeStylesheet(xslt);
960 apply_upgrade(xmlNode *xml,
const struct schema_s *schema, gboolean to_logs)
962 bool transform_onleave = schema->transform_onleave;
963 char *transform_leave;
964 xmlNode *upgrade = NULL,
967 if (schema->transform_enter) {
968 crm_debug(
"Upgrading %s-style configuration, pre-upgrade phase with %s",
969 schema->name, schema->transform_enter);
970 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
971 if (upgrade == NULL) {
972 crm_warn(
"Upgrade-enter transformation %s failed",
973 schema->transform_enter);
974 transform_onleave = FALSE;
977 if (upgrade == NULL) {
981 crm_debug(
"Upgrading %s-style configuration, main phase with %s",
982 schema->name, schema->transform);
983 final = apply_transformation(upgrade, schema->transform, to_logs);
984 if (upgrade != xml) {
989 if (
final != NULL && transform_onleave) {
993 transform_leave = strdup(schema->transform_enter);
995 memcpy(strrchr(transform_leave,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
996 crm_debug(
"Upgrading %s-style configuration, post-upgrade phase with %s",
997 schema->name, transform_leave);
998 final = apply_transformation(upgrade, transform_leave, to_logs);
1000 crm_warn(
"Upgrade-leave transformation %s failed", transform_leave);
1005 free(transform_leave);
1016 if (version < 0 || version >= xml_schema_max) {
1019 return known_schemas[
version].name;
1030 for (; lpc < xml_schema_max; lpc++) {
1043 xmlNode *xml = NULL;
1045 int max_stable_schemas = xml_latest_schema_index();
1046 int lpc = 0, match = -1, rc =
pcmk_ok;
1049 CRM_CHECK(best != NULL,
return -EINVAL);
1052 CRM_CHECK(xml_blob != NULL,
return -EINVAL);
1053 CRM_CHECK(*xml_blob != NULL,
return -EINVAL);
1058 if (value != NULL) {
1062 if (lpc >= 0 && transform == FALSE) {
1065 }
else if (lpc < 0) {
1071 if (match >= max_stable_schemas) {
1078 while (lpc <= max_stable_schemas) {
1079 crm_debug(
"Testing '%s' validation (%d of %d)",
1080 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>",
1081 lpc, max_stable_schemas);
1083 if (validate_with(xml, lpc, to_logs) == FALSE) {
1085 crm_info(
"Configuration not valid for schema: %s",
1086 known_schemas[lpc].name);
1090 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>");
1100 crm_debug(
"Configuration valid for schema: %s",
1101 known_schemas[next].name);
1111 if (rc ==
pcmk_ok && transform) {
1112 xmlNode *upgrade = NULL;
1113 next = known_schemas[lpc].after_transform;
1117 crm_trace(
"Stopping at %s", known_schemas[lpc].name);
1120 }
else if (max > 0 && (lpc == max || next > max)) {
1121 crm_trace(
"Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1122 known_schemas[lpc].name, lpc, next, max);
1125 }
else if (known_schemas[lpc].transform == NULL
1131 || validate_with_silent(xml, next)) {
1132 crm_debug(
"%s-style configuration is also valid for %s",
1133 known_schemas[lpc].name, known_schemas[next].name);
1138 crm_debug(
"Upgrading %s-style configuration to %s with %s",
1139 known_schemas[lpc].name, known_schemas[next].name,
1140 known_schemas[lpc].transform);
1143 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1145 if (upgrade == NULL) {
1146 crm_err(
"Transformation %s failed",
1147 known_schemas[lpc].transform);
1150 }
else if (validate_with(upgrade, next, to_logs)) {
1151 crm_info(
"Transformation %s successful",
1152 known_schemas[lpc].transform);
1160 crm_err(
"Transformation %s did not produce a valid configuration",
1161 known_schemas[lpc].transform);
1170 if (transform == FALSE || rc !=
pcmk_ok) {
1176 if (*best > match && *best) {
1177 crm_info(
"%s the configuration from %s to %s",
1178 transform?
"Transformed":
"Upgraded",
1179 value ? value :
"<none>", known_schemas[*best].name);
1193 char *
const orig_value = strdup(value == NULL ?
"(none)" : value);
1197 int min_version = xml_minimum_schema_index();
1200 xmlNode *converted = NULL;
1207 if (
version < orig_version || orig_version == -1) {
1210 " validate with any schema in range [%s, %s],"
1211 " cannot upgrade to %s.",
1217 fprintf(stderr,
"Your current configuration %s could not"
1218 " validate with any schema in range [%s, %s],"
1219 " cannot upgrade to %s.\n",
1225 }
else if (to_logs) {
1226 crm_config_err(
"Your current configuration could only be upgraded to %s... "
1227 "the minimum requirement is %s.",
crm_str(value),
1230 fprintf(stderr,
"Your current configuration could only be upgraded to %s... "
1231 "the minimum requirement is %s.\n",
1243 if (
version < xml_latest_schema_index()) {
1245 "which is acceptable but not the most recent",
1248 }
else if (to_logs) {
1249 crm_info(
"Your configuration was internally updated to the latest version (%s)",
1257 " It is highly encouraged and prevents many common cluster issues.");
1260 fprintf(stderr,
"Configuration validation is currently disabled."
1261 " It is highly encouraged and prevents many common cluster issues.\n");