29 # include <libxml/relaxng.h>
33 # include <libxslt/xslt.h>
34 # include <libxslt/transform.h>
35 # include <libxslt/xsltutils.h>
46 #define SCHEMA_ZERO { .v = { 0, 0 } }
48 #define schema_scanf(s, prefix, version, suffix) \
49 sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
51 #define schema_strdup_printf(prefix, version, suffix) \
52 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
56 xmlRelaxNGValidCtxtPtr valid;
57 xmlRelaxNGParserCtxtPtr parser;
58 } relaxng_ctx_cache_t;
76 static struct schema_s *known_schemas = NULL;
77 static int xml_schema_max = 0;
80 xml_log(
int priority,
const char *fmt, ...)
84 xml_log(
int priority, const
char *fmt, ...)
95 xml_latest_schema_index(
void)
97 return xml_schema_max - 4;
101 xml_minimum_schema_index(
void)
107 best = xml_latest_schema_index();
108 for (lpc = best; lpc > 0; lpc--) {
109 if (known_schemas[lpc].
version.v[0]
110 < known_schemas[best].version.v[0]) {
116 best = xml_latest_schema_index();
128 get_schema_root(
void)
130 static const char *base = NULL;
133 base = getenv(
"PCMK_schema_directory");
135 if (base == NULL || strlen(base) == 0) {
142 get_schema_path(
const char *name,
const char *file)
144 const char *base = get_schema_root();
153 version_from_filename(
const char *filename, schema_version_t *
version)
155 int rc =
schema_scanf(filename,
"pacemaker-", *version,
".rng");
161 schema_filter(
const struct dirent *a)
166 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
172 }
else if (!version_from_filename(a->d_name, &version)) {
175 }
else if (strcmp(a->d_name,
"pacemaker-1.1.rng") == 0) {
188 schema_sort(
const struct dirent **a,
const struct dirent **b)
193 if (!version_from_filename(a[0]->d_name, &a_version)
194 || !version_from_filename(b[0]->d_name, &b_version)) {
199 for (
int i = 0; i < 2; ++i) {
200 if (a_version.v[i] < b_version.v[i]) {
202 }
else if (a_version.v[i] > b_version.v[i]) {
211 const char *name,
const char *location,
const char *transform,
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);
238 schema_scanf(name,
"%*[^-]-", known_schemas[last].version,
"");
239 known_schemas[last].name = strdup(name);
240 known_schemas[last].location = strdup(location);
244 known_schemas[last].transform = strdup(transform);
246 if (after_transform == 0) {
247 after_transform = xml_schema_max;
249 known_schemas[last].after_transform = after_transform;
251 if (known_schemas[last].after_transform < 0) {
252 crm_debug(
"Added supported schema %d: %s (%s)",
253 last, known_schemas[last].name, known_schemas[last].location);
255 }
else if (known_schemas[last].transform) {
256 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d with %s)",
257 last, known_schemas[last].name, known_schemas[last].location,
258 known_schemas[last].after_transform,
259 known_schemas[last].transform);
262 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d)",
263 last, known_schemas[last].name, known_schemas[last].location,
264 known_schemas[last].after_transform);
276 const char *base = get_schema_root();
277 struct dirent **namelist = NULL;
280 max = scandir(base, &namelist, schema_filter, schema_sort);
283 "crm.dtd",
"upgrade06.xsl", 3);
286 "crm-transitional.dtd",
"upgrade06.xsl", 3);
289 "pacemaker-1.0.rng", NULL, 0);
295 for (lpc = 0; lpc < max; lpc++) {
298 char *transform = NULL;
300 if (!version_from_filename(namelist[lpc]->d_name, &version)) {
302 crm_err(
"Skipping schema '%s': could not parse version",
303 namelist[lpc]->d_name);
307 if ((lpc + 1) < max) {
310 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
311 && (version.v[0] < next_version.v[0])) {
317 xslt = get_schema_path(NULL, transform);
318 if (stat(xslt, &s) != 0) {
319 crm_err(
"Transform %s not found", xslt);
341 "pacemaker-next.rng", NULL, 0);
344 "pacemaker-next.rng", NULL, -1);
352 validate_with_dtd(xmlDocPtr doc, gboolean to_logs,
const char *dtd_file)
354 gboolean valid = TRUE;
356 xmlDtdPtr dtd = NULL;
357 xmlValidCtxtPtr cvp = NULL;
360 CRM_CHECK(dtd_file != NULL,
return FALSE);
362 dtd = xmlParseDTD(NULL, (
const xmlChar *)dtd_file);
364 crm_err(
"Could not locate/parse DTD: %s", dtd_file);
368 cvp = xmlNewValidCtxt();
371 cvp->userData = (
void *)LOG_ERR;
372 cvp->error = (xmlValidityErrorFunc) xml_log;
373 cvp->warning = (xmlValidityWarningFunc) xml_log;
375 cvp->userData = (
void *)stderr;
376 cvp->error = (xmlValidityErrorFunc) fprintf;
377 cvp->warning = (xmlValidityWarningFunc) fprintf;
380 if (!xmlValidateDtd(cvp, doc, dtd)) {
383 xmlFreeValidCtxt(cvp);
386 crm_err(
"Internal error: No valid context");
395 relaxng_invalid_stderr(
void *userData, xmlErrorPtr error)
415 crm_err(
"Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
420 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs,
const char *relaxng_file,
421 relaxng_ctx_cache_t **cached_ctx)
424 gboolean valid = TRUE;
425 relaxng_ctx_cache_t *ctx = NULL;
428 CRM_CHECK(relaxng_file != NULL,
return FALSE);
430 if (cached_ctx && *cached_ctx) {
434 crm_info(
"Creating RNG parser context");
435 ctx = calloc(1,
sizeof(relaxng_ctx_cache_t));
437 xmlLoadExtDtdDefaultValue = 1;
438 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
439 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
442 xmlRelaxNGSetParserErrors(ctx->parser,
443 (xmlRelaxNGValidityErrorFunc) xml_log,
444 (xmlRelaxNGValidityWarningFunc) xml_log,
445 GUINT_TO_POINTER(LOG_ERR));
447 xmlRelaxNGSetParserErrors(ctx->parser,
448 (xmlRelaxNGValidityErrorFunc) fprintf,
449 (xmlRelaxNGValidityWarningFunc) fprintf,
453 ctx->rng = xmlRelaxNGParse(ctx->parser);
455 crm_err(
"Could not find/parse %s", relaxng_file);
458 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
459 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
462 xmlRelaxNGSetValidErrors(ctx->valid,
463 (xmlRelaxNGValidityErrorFunc) xml_log,
464 (xmlRelaxNGValidityWarningFunc) xml_log,
465 GUINT_TO_POINTER(LOG_ERR));
467 xmlRelaxNGSetValidErrors(ctx->valid,
468 (xmlRelaxNGValidityErrorFunc) fprintf,
469 (xmlRelaxNGValidityWarningFunc) fprintf,
477 xmlLineNumbersDefault(1);
478 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
483 crm_err(
"Internal libxml error during validation");
492 if (ctx->parser != NULL) {
493 xmlRelaxNGFreeParserCtxt(ctx->parser);
495 if (ctx->valid != NULL) {
496 xmlRelaxNGFreeValidCtxt(ctx->valid);
498 if (ctx->rng != NULL) {
499 xmlRelaxNGFree(ctx->rng);
515 relaxng_ctx_cache_t *ctx = NULL;
517 for (lpc = 0; lpc < xml_schema_max; lpc++) {
519 switch (known_schemas[lpc].validator) {
524 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
528 if (ctx->parser != NULL) {
529 xmlRelaxNGFreeParserCtxt(ctx->parser);
531 if (ctx->valid != NULL) {
532 xmlRelaxNGFreeValidCtxt(ctx->valid);
534 if (ctx->rng != NULL) {
535 xmlRelaxNGFree(ctx->rng);
538 known_schemas[lpc].cache = NULL;
541 free(known_schemas[lpc].name);
542 free(known_schemas[lpc].location);
543 free(known_schemas[lpc].transform);
546 known_schemas = NULL;
550 validate_with(xmlNode *xml,
int method, gboolean to_logs)
552 xmlDocPtr doc = NULL;
553 gboolean valid = FALSE;
566 file = get_schema_path(known_schemas[method].name,
567 known_schemas[method].location);
569 crm_trace(
"Validating with: %s (type=%d)",
570 crm_str(file), known_schemas[method].validator);
571 switch (known_schemas[method].validator) {
573 valid = validate_with_dtd(doc, to_logs, file);
577 validate_with_relaxng(doc, to_logs, file,
578 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
581 crm_err(
"Unknown validator type: %d",
582 known_schemas[method].validator);
591 dump_file(
const char *filename)
599 fp = fopen(filename,
"r");
601 crm_perror(LOG_ERR,
"Could not open %s for reading", filename);
605 fprintf(stderr,
"%4d ", ++line);
611 }
else if (ch ==
'\n') {
612 fprintf(stderr,
"\n%4d ", ++line);
628 char *filename = strdup(
CRM_STATE_DIR "/cib-invalid.XXXXXX");
630 CRM_CHECK(filename != NULL,
return FALSE);
632 umask(S_IWGRP | S_IWOTH | S_IROTH);
633 fd = mkstemp(filename);
638 doc = xmlParseFile(filename);
639 xml = xmlDocGetRootElement(doc);
650 validate_xml(xmlNode *xml_blob,
const char *validation, gboolean to_logs)
654 if (validation == NULL) {
658 if (validation == NULL) {
670 for (lpc = 0; lpc < xml_schema_max; lpc++) {
671 if (validate_with(xml_blob, lpc, FALSE)) {
674 known_schemas[lpc].name);
675 crm_info(
"XML validated against %s", known_schemas[lpc].name);
676 if(known_schemas[lpc].after_transform == 0) {
686 if (strcmp(validation,
"none") == 0) {
688 }
else if (version < xml_schema_max) {
689 return validate_with(xml_blob, version, to_logs);
692 crm_err(
"Unknown validator: %s", validation);
699 cib_upgrade_err(
void *ctx,
const char *fmt, ...)
703 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
713 apply_transformation(xmlNode *xml,
const char *transform, gboolean to_logs)
717 xmlDocPtr res = NULL;
718 xmlDocPtr doc = NULL;
719 xsltStylesheet *xslt = NULL;
723 xform = get_schema_path(NULL, transform);
725 xmlLoadExtDtdDefaultValue = 1;
726 xmlSubstituteEntitiesDefault(1);
730 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
732 xsltSetGenericErrorFunc((
void *) stderr, (xmlGenericErrorFunc) fprintf);
735 xslt = xsltParseStylesheetFile((
const xmlChar *)xform);
738 res = xsltApplyStylesheet(xslt, doc, NULL);
741 xsltSetGenericErrorFunc(NULL, NULL);
743 out = xmlDocGetRootElement(res);
747 xsltFreeStylesheet(xslt);
759 if (version < 0 || version >= xml_schema_max) {
762 return known_schemas[
version].name;
773 for (; lpc < xml_schema_max; lpc++) {
788 int max_stable_schemas = xml_latest_schema_index();
789 int lpc = 0, match = -1, rc =
pcmk_ok;
795 CRM_CHECK(xml_blob != NULL,
return -EINVAL);
796 CRM_CHECK(*xml_blob != NULL,
return -EINVAL);
805 if (lpc >= 0 && transform == FALSE) {
808 }
else if (lpc < 0) {
814 if (match >= max_stable_schemas) {
821 while (lpc <= max_stable_schemas) {
822 crm_debug(
"Testing '%s' validation (%d of %d)",
823 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>",
824 lpc, max_stable_schemas);
826 if (validate_with(xml, lpc, to_logs) == FALSE) {
828 crm_info(
"Configuration not valid for schema: %s",
829 known_schemas[lpc].name);
833 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>");
843 crm_debug(
"Configuration valid for schema: %s",
844 known_schemas[next].name);
854 if (rc ==
pcmk_ok && transform) {
855 xmlNode *upgrade = NULL;
856 next = known_schemas[lpc].after_transform;
860 crm_trace(
"Stopping at %s", known_schemas[lpc].name);
863 }
else if (max > 0 && (lpc == max || next > max)) {
864 crm_trace(
"Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
865 known_schemas[lpc].name, lpc, next, max);
868 }
else if (known_schemas[lpc].transform == NULL) {
869 crm_debug(
"%s-style configuration is also valid for %s",
870 known_schemas[lpc].name, known_schemas[next].name);
875 crm_debug(
"Upgrading %s-style configuration to %s with %s",
876 known_schemas[lpc].name, known_schemas[next].name,
877 known_schemas[lpc].transform ? known_schemas[lpc].transform :
"no-op");
880 upgrade = apply_transformation(xml, known_schemas[lpc].transform, to_logs);
882 if (upgrade == NULL) {
883 crm_err(
"Transformation %s failed",
884 known_schemas[lpc].transform);
887 }
else if (validate_with(upgrade, next, to_logs)) {
888 crm_info(
"Transformation %s successful",
889 known_schemas[lpc].transform);
897 crm_err(
"Transformation %s did not produce a valid configuration",
898 known_schemas[lpc].transform);
907 if (transform == FALSE || rc !=
pcmk_ok) {
913 if (*best > match && *best) {
914 crm_info(
"%s the configuration from %s to %s",
915 transform?
"Transformed":
"Upgraded",
916 value ? value :
"<none>", known_schemas[*best].name);
930 char *
const orig_value = strdup(value == NULL ?
"(none)" : value);
934 int min_version = xml_minimum_schema_index();
936 if (version < min_version) {
937 xmlNode *converted = NULL;
943 if (version < min_version) {
944 if (version < orig_version || orig_version == -1) {
947 " validate with any schema in range [%s, %s],"
948 " cannot upgrade to %s.",
954 fprintf(stderr,
"Your current configuration %s could not"
955 " validate with any schema in range [%s, %s],"
956 " cannot upgrade to %s.\n",
962 }
else if (to_logs) {
963 crm_config_err(
"Your current configuration could only be upgraded to %s... "
964 "the minimum requirement is %s.",
crm_str(value),
967 fprintf(stderr,
"Your current configuration could only be upgraded to %s... "
968 "the minimum requirement is %s.\n",
980 if (version < xml_latest_schema_index()) {
982 "which is acceptable but not the most recent",
985 }
else if (to_logs) {
986 crm_info(
"Your configuration was internally updated to the latest version (%s)",
994 " It is highly encouraged and prevents many common cluster issues.");
997 fprintf(stderr,
"Configuration validation is currently disabled."
998 " It is highly encouraged and prevents many common cluster issues.\n");
#define CRM_CHECK(expr, failure_action)
void crm_schema_init(void)
#define crm_notice(fmt, args...)
#define schema_strdup_printf(prefix, version, suffix)
#define crm_config_err(fmt...)
#define schema_scanf(s, prefix, version, suffix)
void crm_schema_cleanup(void)
char * crm_element_value_copy(xmlNode *data, const char *name)
int get_schema_version(const char *name)
#define CRM_DTD_DIRECTORY
char * strerror(int errnum)
#define CRM_XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
xmlDoc * getDocPtr(xmlNode *node)
gboolean validate_xml_verbose(xmlNode *xml_blob)
xmlNode * copy_xml(xmlNode *src_node)
#define crm_debug(fmt, args...)
#define pcmk_err_schema_validation
#define crm_trace(fmt, args...)
Wrappers for and extensions to libxml2.
const char * crm_element_value(xmlNode *data, const char *name)
#define XML_ATTR_VALIDATION
void free_xml(xmlNode *child)
gboolean crm_ends_with_ext(const char *s, const char *match)
#define pcmk_err_transform_failed
const char * xml_latest_schema(void)
#define crm_config_warn(fmt...)
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
#define crm_perror(level, fmt, args...)
Log a system error message.
#define crm_err(fmt, args...)
#define crm_log_xml_info(xml, text)
int update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, gboolean to_logs)
Update CIB XML to most recent schema version.
gboolean crm_is_true(const char *s)
#define safe_str_eq(a, b)
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
#define crm_info(fmt, args...)
const char * get_schema_name(int version)