19 #include <libxml/relaxng.h>
22 # include <libxslt/xslt.h>
23 # include <libxslt/transform.h>
24 # include <libxslt/security.h>
25 # include <libxslt/xsltutils.h>
36 #define SCHEMA_ZERO { .v = { 0, 0 } }
38 #define schema_scanf(s, prefix, version, suffix) \
39 sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
41 #define schema_strdup_printf(prefix, version, suffix) \
42 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
46 xmlRelaxNGValidCtxtPtr valid;
47 xmlRelaxNGParserCtxtPtr parser;
48 } relaxng_ctx_cache_t;
62 char *transform_enter;
63 bool transform_onleave;
66 static struct schema_s *known_schemas = NULL;
67 static int xml_schema_max = 0;
68 static bool silent_logging = FALSE;
71 xml_log(
int priority,
const char *fmt, ...)
75 xml_log(
int priority, const
char *fmt, ...)
80 if (silent_logging == FALSE) {
88 xml_latest_schema_index(
void)
90 return xml_schema_max - 3;
94 xml_minimum_schema_index(
void)
100 best = xml_latest_schema_index();
101 for (lpc = best; lpc > 0; lpc--) {
102 if (known_schemas[lpc].
version.v[0]
103 < known_schemas[best].version.v[0]) {
109 best = xml_latest_schema_index();
121 version_from_filename(
const char *filename, schema_version_t *
version)
129 schema_filter(
const struct dirent *a)
134 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
140 }
else if (!version_from_filename(a->d_name, &
version)) {
152 schema_sort(
const struct dirent **a,
const struct dirent **b)
157 if (!version_from_filename(a[0]->d_name, &a_version)
158 || !version_from_filename(b[0]->d_name, &b_version)) {
163 for (
int i = 0; i < 2; ++i) {
164 if (a_version.v[i] < b_version.v[i]) {
166 }
else if (a_version.v[i] > b_version.v[i]) {
182 const char *name,
const char *transform,
183 const char *transform_enter,
bool transform_onleave,
186 int last = xml_schema_max;
187 bool have_version = FALSE;
190 known_schemas = realloc_safe(known_schemas,
191 xml_schema_max *
sizeof(
struct schema_s));
193 memset(known_schemas+last, 0,
sizeof(
struct schema_s));
194 known_schemas[last].validator = validator;
195 known_schemas[last].after_transform = after_transform;
197 for (
int i = 0; i < 2; ++i) {
198 known_schemas[last].version.v[i] =
version->v[i];
208 known_schemas[last].name = strdup(name);
212 known_schemas[last].transform = strdup(transform);
214 if (transform_enter) {
215 known_schemas[last].transform_enter = strdup(transform_enter);
217 known_schemas[last].transform_onleave = transform_onleave;
218 if (after_transform == 0) {
219 after_transform = xml_schema_max;
221 known_schemas[last].after_transform = after_transform;
223 if (known_schemas[last].after_transform < 0) {
224 crm_debug(
"Added supported schema %d: %s",
225 last, known_schemas[last].name);
227 }
else if (known_schemas[last].transform) {
228 crm_debug(
"Added supported schema %d: %s (upgrades to %d with %s.xsl)",
229 last, known_schemas[last].name,
230 known_schemas[last].after_transform,
231 known_schemas[last].transform);
234 crm_debug(
"Added supported schema %d: %s (upgrades to %d)",
235 last, known_schemas[last].name,
236 known_schemas[last].after_transform);
268 add_schema_by_version(
const schema_version_t *
version,
int next,
269 bool transform_expected)
271 bool transform_onleave = FALSE;
275 *transform_upgrade = NULL,
276 *transform_enter = NULL;
279 if (transform_expected) {
286 if (!transform_expected) {
289 }
else if (stat(xslt, &s) == 0) {
295 if (stat(xslt, &s) != 0) {
297 crm_debug(
"Upgrade-enter transform %s.xsl not found", xslt);
299 free(transform_enter);
300 transform_enter = strdup(
"upgrade-enter");
303 if (stat(xslt, &s) != 0) {
304 crm_debug(
"Upgrade-enter transform %s.xsl not found, either", xslt);
312 memcpy(strrchr(xslt,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
313 transform_onleave = (stat(xslt, &s) == 0);
316 free(transform_enter);
317 transform_enter = NULL;
321 crm_err(
"Upgrade transform %s not found", xslt);
323 free(transform_upgrade);
324 transform_upgrade = NULL;
330 transform_upgrade, transform_enter, transform_onleave, next);
332 free(transform_upgrade);
333 free(transform_enter);
339 wrap_libxslt(
bool finalize)
341 static xsltSecurityPrefsPtr secprefs;
347 secprefs = xsltNewSecurityPrefs();
348 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
350 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
352 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
354 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
360 xsltFreeSecurityPrefs(secprefs);
366 xsltCleanupGlobals();
384 struct dirent **namelist = NULL;
389 max = scandir(base, &namelist, schema_filter, schema_sort);
396 for (lpc = 0; lpc < max; lpc++) {
397 bool transform_expected = FALSE;
401 if (!version_from_filename(namelist[lpc]->d_name, &
version)) {
403 crm_err(
"Skipping schema '%s': could not parse version",
404 namelist[lpc]->d_name);
407 if ((lpc + 1) < max) {
410 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
411 && (
version.v[0] < next_version.v[0])) {
412 transform_expected = TRUE;
418 if (add_schema_by_version(&
version, next, transform_expected)
424 for (lpc = 0; lpc < max; lpc++) {
431 NULL, NULL, FALSE, -1);
438 relaxng_invalid_stderr(
void *userData, xmlErrorPtr error)
458 crm_err(
"Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
463 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs,
const char *relaxng_file,
464 relaxng_ctx_cache_t **cached_ctx)
467 gboolean valid = TRUE;
468 relaxng_ctx_cache_t *ctx = NULL;
471 CRM_CHECK(relaxng_file != NULL,
return FALSE);
473 if (cached_ctx && *cached_ctx) {
477 crm_debug(
"Creating RNG parser context");
478 ctx = calloc(1,
sizeof(relaxng_ctx_cache_t));
480 xmlLoadExtDtdDefaultValue = 1;
481 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
482 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
485 xmlRelaxNGSetParserErrors(ctx->parser,
486 (xmlRelaxNGValidityErrorFunc) xml_log,
487 (xmlRelaxNGValidityWarningFunc) xml_log,
488 GUINT_TO_POINTER(LOG_ERR));
490 xmlRelaxNGSetParserErrors(ctx->parser,
491 (xmlRelaxNGValidityErrorFunc) fprintf,
492 (xmlRelaxNGValidityWarningFunc) fprintf,
496 ctx->rng = xmlRelaxNGParse(ctx->parser);
498 crm_err(
"Could not find/parse %s", relaxng_file);
501 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
502 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
505 xmlRelaxNGSetValidErrors(ctx->valid,
506 (xmlRelaxNGValidityErrorFunc) xml_log,
507 (xmlRelaxNGValidityWarningFunc) xml_log,
508 GUINT_TO_POINTER(LOG_ERR));
510 xmlRelaxNGSetValidErrors(ctx->valid,
511 (xmlRelaxNGValidityErrorFunc) fprintf,
512 (xmlRelaxNGValidityWarningFunc) fprintf,
520 xmlLineNumbersDefault(1);
521 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
526 crm_err(
"Internal libxml error during validation");
535 if (ctx->parser != NULL) {
536 xmlRelaxNGFreeParserCtxt(ctx->parser);
538 if (ctx->valid != NULL) {
539 xmlRelaxNGFreeValidCtxt(ctx->valid);
541 if (ctx->rng != NULL) {
542 xmlRelaxNGFree(ctx->rng);
558 relaxng_ctx_cache_t *ctx = NULL;
560 for (lpc = 0; lpc < xml_schema_max; lpc++) {
562 switch (known_schemas[lpc].validator) {
566 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
570 if (ctx->parser != NULL) {
571 xmlRelaxNGFreeParserCtxt(ctx->parser);
573 if (ctx->valid != NULL) {
574 xmlRelaxNGFreeValidCtxt(ctx->valid);
576 if (ctx->rng != NULL) {
577 xmlRelaxNGFree(ctx->rng);
580 known_schemas[lpc].cache = NULL;
583 free(known_schemas[lpc].name);
584 free(known_schemas[lpc].transform);
585 free(known_schemas[lpc].transform_enter);
588 known_schemas = NULL;
594 validate_with(xmlNode *xml,
int method, gboolean to_logs)
596 xmlDocPtr doc = NULL;
597 gboolean valid = FALSE;
611 known_schemas[method].name);
613 crm_trace(
"Validating with: %s (type=%d)",
614 crm_str(file), known_schemas[method].validator);
615 switch (known_schemas[method].validator) {
618 validate_with_relaxng(doc, to_logs, file,
619 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
622 crm_err(
"Unknown validator type: %d",
623 known_schemas[method].validator);
632 validate_with_silent(xmlNode *xml,
int method)
634 bool rc, sl_backup = silent_logging;
635 silent_logging = TRUE;
636 rc = validate_with(xml, method, TRUE);
637 silent_logging = sl_backup;
642 dump_file(
const char *filename)
650 fp = fopen(filename,
"r");
652 crm_perror(LOG_ERR,
"Could not open %s for reading", filename);
656 fprintf(stderr,
"%4d ", ++line);
662 }
else if (ch ==
'\n') {
663 fprintf(stderr,
"\n%4d ", ++line);
679 char *filename = NULL;
683 umask(S_IWGRP | S_IWOTH | S_IROTH);
684 fd = mkstemp(filename);
689 doc = xmlParseFile(filename);
690 xml = xmlDocGetRootElement(doc);
701 validate_xml(xmlNode *xml_blob,
const char *validation, gboolean to_logs)
705 if (validation == NULL) {
709 if (validation == NULL) {
713 for (lpc = 0; lpc < xml_schema_max; lpc++) {
714 if (validate_with(xml_blob, lpc, FALSE)) {
717 known_schemas[lpc].name);
718 crm_info(
"XML validated against %s", known_schemas[lpc].name);
719 if(known_schemas[lpc].after_transform == 0) {
729 if (strcmp(validation,
"none") == 0) {
731 }
else if (
version < xml_schema_max) {
732 return validate_with(xml_blob,
version, to_logs);
735 crm_err(
"Unknown validator: %s", validation);
742 cib_upgrade_err(
void *ctx,
const char *fmt, ...)
769 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
775 const char *fmt_iter = fmt;
776 uint8_t msg_log_level = LOG_WARNING;
777 const unsigned * log_level = (
const unsigned *) ctx;
781 } scan_state = escan_seennothing;
786 while (!found && *fmt_iter !=
'\0') {
788 switch (*fmt_iter++) {
790 if (scan_state == escan_seennothing) {
791 scan_state = escan_seenpercent;
792 }
else if (scan_state == escan_seenpercent) {
793 scan_state = escan_seennothing;
797 if (scan_state == escan_seenpercent) {
798 scan_state = escan_seennothing;
799 arg_cur = va_arg(aq,
char *);
800 if (arg_cur != NULL) {
801 switch (arg_cur[0]) {
803 if (!strncmp(arg_cur,
"WARNING: ",
804 sizeof(
"WARNING: ") - 1)) {
805 msg_log_level = LOG_WARNING;
808 memmove(arg_cur, arg_cur +
sizeof(
"WARNING: ") - 1,
809 strlen(arg_cur +
sizeof(
"WARNING: ") - 1) + 1);
814 if (!strncmp(arg_cur,
"INFO: ",
815 sizeof(
"INFO: ") - 1)) {
816 msg_log_level = LOG_INFO;
819 memmove(arg_cur, arg_cur +
sizeof(
"INFO: ") - 1,
820 strlen(arg_cur +
sizeof(
"INFO: ") - 1) + 1);
825 if (!strncmp(arg_cur,
"DEBUG: ",
826 sizeof(
"DEBUG: ") - 1)) {
827 msg_log_level = LOG_DEBUG;
830 memmove(arg_cur, arg_cur +
sizeof(
"DEBUG: ") - 1,
831 strlen(arg_cur +
sizeof(
"DEBUG: ") - 1) + 1);
839 case '#':
case '-':
case ' ':
case '+':
case '\'':
case 'I':
case '.':
840 case '0':
case '1':
case '2':
case '3':
case '4':
841 case '5':
case '6':
case '7':
case '8':
case '9':
858 if (scan_state == escan_seenpercent) {
859 (void) va_arg(aq,
void *);
860 scan_state = escan_seennothing;
864 scan_state = escan_seennothing;
869 if (log_level != NULL) {
872 if (*log_level + 4 >= msg_log_level) {
873 vfprintf(stderr, fmt, ap);
898 #ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
899 #define PCMK_SCHEMAS_EMERGENCY_XSLT 1
903 apply_transformation(xmlNode *xml,
const char *transform, gboolean to_logs)
907 xmlDocPtr res = NULL;
908 xmlDocPtr doc = NULL;
909 xsltStylesheet *xslt = NULL;
910 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
911 xmlChar *emergency_result;
912 int emergency_txt_len;
921 xmlLoadExtDtdDefaultValue = 1;
922 xmlSubstituteEntitiesDefault(1);
926 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
931 xslt = xsltParseStylesheetFile((
pcmkXmlStr) xform);
934 res = xsltApplyStylesheet(xslt, doc, NULL);
937 xsltSetGenericErrorFunc(NULL, NULL);
940 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
941 emergency_res = xsltSaveResultToString(&emergency_result,
942 &emergency_txt_len, res, xslt);
944 CRM_CHECK(emergency_res == 0,
goto cleanup);
945 out =
string2xml((
const char *) emergency_result);
946 free(emergency_result);
948 out = xmlDocGetRootElement(res);
953 xsltFreeStylesheet(xslt);
968 apply_upgrade(xmlNode *xml,
const struct schema_s *schema, gboolean to_logs)
970 bool transform_onleave = schema->transform_onleave;
971 char *transform_leave;
972 xmlNode *upgrade = NULL,
975 if (schema->transform_enter) {
976 crm_debug(
"Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
977 schema->name, schema->transform_enter);
978 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
979 if (upgrade == NULL) {
980 crm_warn(
"Upgrade-enter transformation %s.xsl failed",
981 schema->transform_enter);
982 transform_onleave = FALSE;
985 if (upgrade == NULL) {
989 crm_debug(
"Upgrading %s-style configuration, main phase with %s.xsl",
990 schema->name, schema->transform);
991 final = apply_transformation(upgrade, schema->transform, to_logs);
992 if (upgrade != xml) {
997 if (
final != NULL && transform_onleave) {
1001 transform_leave = strdup(schema->transform_enter);
1003 memcpy(strrchr(transform_leave,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
1004 crm_debug(
"Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
1005 schema->name, transform_leave);
1006 final = apply_transformation(upgrade, transform_leave, to_logs);
1007 if (
final == NULL) {
1008 crm_warn(
"Upgrade-leave transformation %s.xsl failed", transform_leave);
1013 free(transform_leave);
1024 if (version < 0 || version >= xml_schema_max) {
1027 return known_schemas[
version].name;
1038 for (; lpc < xml_schema_max; lpc++) {
1051 xmlNode *xml = NULL;
1053 int max_stable_schemas = xml_latest_schema_index();
1054 int lpc = 0, match = -1, rc =
pcmk_ok;
1057 CRM_CHECK(best != NULL,
return -EINVAL);
1060 CRM_CHECK(xml_blob != NULL,
return -EINVAL);
1061 CRM_CHECK(*xml_blob != NULL,
return -EINVAL);
1066 if (value != NULL) {
1070 if (lpc >= 0 && transform == FALSE) {
1073 }
else if (lpc < 0) {
1079 if (match >= max_stable_schemas) {
1086 while (lpc <= max_stable_schemas) {
1087 crm_debug(
"Testing '%s' validation (%d of %d)",
1088 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>",
1089 lpc, max_stable_schemas);
1091 if (validate_with(xml, lpc, to_logs) == FALSE) {
1093 crm_info(
"Configuration not valid for schema: %s",
1094 known_schemas[lpc].name);
1098 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>");
1108 crm_debug(
"Configuration valid for schema: %s",
1109 known_schemas[next].name);
1119 if (rc ==
pcmk_ok && transform) {
1120 xmlNode *upgrade = NULL;
1121 next = known_schemas[lpc].after_transform;
1125 crm_trace(
"Stopping at %s", known_schemas[lpc].name);
1128 }
else if (max > 0 && (lpc == max || next > max)) {
1129 crm_trace(
"Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1130 known_schemas[lpc].name, lpc, next, max);
1133 }
else if (known_schemas[lpc].transform == NULL
1139 || validate_with_silent(xml, next)) {
1140 crm_debug(
"%s-style configuration is also valid for %s",
1141 known_schemas[lpc].name, known_schemas[next].name);
1146 crm_debug(
"Upgrading %s-style configuration to %s with %s.xsl",
1147 known_schemas[lpc].name, known_schemas[next].name,
1148 known_schemas[lpc].transform);
1151 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);
1158 }
else if (validate_with(upgrade, next, to_logs)) {
1159 crm_info(
"Transformation %s.xsl successful",
1160 known_schemas[lpc].transform);
1168 crm_err(
"Transformation %s.xsl did not produce a valid configuration",
1169 known_schemas[lpc].transform);
1178 if (transform == FALSE || rc !=
pcmk_ok) {
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);
1201 char *
const orig_value = strdup(value == NULL ?
"(none)" : value);
1205 int min_version = xml_minimum_schema_index();
1208 xmlNode *converted = NULL;
1215 if (
version < orig_version || orig_version == -1) {
1218 " validate with any schema in range [%s, %s],"
1219 " cannot upgrade to %s.",
1225 fprintf(stderr,
"Your current configuration %s could not"
1226 " validate with any schema in range [%s, %s],"
1227 " cannot upgrade to %s.\n",
1233 }
else if (to_logs) {
1234 crm_config_err(
"Your current configuration could only be upgraded to %s... "
1235 "the minimum requirement is %s.",
crm_str(value),
1238 fprintf(stderr,
"Your current configuration could only be upgraded to %s... "
1239 "the minimum requirement is %s.\n",
1251 if (
version < xml_latest_schema_index()) {
1253 "which is acceptable but not the most recent",
1256 }
else if (to_logs) {
1257 crm_info(
"Your configuration was internally updated to the latest version (%s)",
1265 " It is highly encouraged and prevents many common cluster issues.");
1268 fprintf(stderr,
"Configuration validation is currently disabled."
1269 " It is highly encouraged and prevents many common cluster issues.\n");