19 #include <libxml/relaxng.h>
22 # include <libxslt/xslt.h>
23 # include <libxslt/transform.h>
24 # include <libxslt/xsltutils.h>
35 #define SCHEMA_ZERO { .v = { 0, 0 } }
37 #define schema_scanf(s, prefix, version, suffix) \
38 sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
40 #define schema_strdup_printf(prefix, version, suffix) \
41 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
45 xmlRelaxNGValidCtxtPtr valid;
46 xmlRelaxNGParserCtxtPtr parser;
47 } 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 get_schema_root(
void)
123 static const char *base = NULL;
126 base = getenv(
"PCMK_schema_directory");
128 if (base == NULL || strlen(base) == 0) {
135 get_schema_path(
const char *name,
const char *file)
137 const char *base = get_schema_root();
151 version_from_filename(
const char *filename, schema_version_t *
version)
153 int rc =
schema_scanf(filename,
"pacemaker-", *version,
".rng");
159 schema_filter(
const struct dirent *a)
164 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
170 }
else if (!version_from_filename(a->d_name, &version)) {
182 schema_sort(
const struct dirent **a,
const struct dirent **b)
187 if (!version_from_filename(a[0]->d_name, &a_version)
188 || !version_from_filename(b[0]->d_name, &b_version)) {
193 for (
int i = 0; i < 2; ++i) {
194 if (a_version.v[i] < b_version.v[i]) {
196 }
else if (a_version.v[i] > b_version.v[i]) {
212 const char *name,
const char *location,
const char *transform,
213 const char *transform_enter,
bool transform_onleave,
216 int last = xml_schema_max;
217 bool have_version = FALSE;
220 known_schemas = realloc_safe(known_schemas,
221 xml_schema_max *
sizeof(
struct schema_s));
223 memset(known_schemas+last, 0,
sizeof(
struct schema_s));
224 known_schemas[last].validator = validator;
225 known_schemas[last].after_transform = after_transform;
227 for (
int i = 0; i < 2; ++i) {
228 known_schemas[last].version.v[i] = version->v[i];
236 known_schemas[last].name);
240 schema_scanf(name,
"%*[^-]-", known_schemas[last].version,
"");
241 known_schemas[last].name = strdup(name);
242 known_schemas[last].location = strdup(location);
246 known_schemas[last].transform = strdup(transform);
248 if (transform_enter) {
249 known_schemas[last].transform_enter = strdup(transform_enter);
251 known_schemas[last].transform_onleave = transform_onleave;
252 if (after_transform == 0) {
253 after_transform = xml_schema_max;
255 known_schemas[last].after_transform = after_transform;
257 if (known_schemas[last].after_transform < 0) {
258 crm_debug(
"Added supported schema %d: %s (%s)",
259 last, known_schemas[last].name, known_schemas[last].location);
261 }
else if (known_schemas[last].transform) {
262 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d with %s)",
263 last, known_schemas[last].name, known_schemas[last].location,
264 known_schemas[last].after_transform,
265 known_schemas[last].transform);
268 crm_debug(
"Added supported schema %d: %s (%s upgrades to %d)",
269 last, known_schemas[last].name, known_schemas[last].location,
270 known_schemas[last].after_transform);
302 add_schema_by_version(
const schema_version_t *version,
int next,
303 bool transform_expected)
305 bool transform_onleave = FALSE;
309 *transform_upgrade = NULL,
310 *transform_enter = NULL;
313 if (transform_expected) {
316 xslt = get_schema_path(NULL, transform_upgrade);
319 if (!transform_expected) {
322 }
else if (stat(xslt, &s) == 0) {
326 xslt = get_schema_path(NULL, transform_enter);
327 if (stat(xslt, &s) != 0) {
329 crm_debug(
"Upgrade-enter transform %s not found", xslt);
331 free(transform_enter);
332 transform_enter = strdup(
"upgrade-enter.xsl");
333 xslt = get_schema_path(NULL, transform_enter);
334 if (stat(xslt, &s) != 0) {
335 crm_debug(
"Upgrade-enter transform %s not found, either", xslt);
343 memcpy(strrchr(xslt,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
344 transform_onleave = (stat(xslt, &s) == 0);
347 free(transform_enter);
348 transform_enter = NULL;
352 crm_err(
"Upgrade transform %s not found", xslt);
354 free(transform_upgrade);
355 transform_upgrade = NULL;
361 transform_upgrade, transform_enter, transform_onleave, next);
363 free(transform_upgrade);
364 free(transform_enter);
377 const char *base = get_schema_root();
378 struct dirent **namelist = NULL;
381 max = scandir(base, &namelist, schema_filter, schema_sort);
386 for (lpc = 0; lpc < max; lpc++) {
387 bool transform_expected = FALSE;
391 if (!version_from_filename(namelist[lpc]->d_name, &version)) {
393 crm_err(
"Skipping schema '%s': could not parse version",
394 namelist[lpc]->d_name);
397 if ((lpc + 1) < max) {
400 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
401 && (version.v[0] < next_version.v[0])) {
402 transform_expected = TRUE;
408 if (add_schema_by_version(&version, next, transform_expected)
414 for (lpc = 0; lpc < max; lpc++) {
421 "pacemaker-next.rng", NULL, NULL, FALSE, -1);
424 "N/A", NULL, NULL, FALSE, -1);
429 relaxng_invalid_stderr(
void *userData, xmlErrorPtr error)
449 crm_err(
"Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
454 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs,
const char *relaxng_file,
455 relaxng_ctx_cache_t **cached_ctx)
458 gboolean valid = TRUE;
459 relaxng_ctx_cache_t *ctx = NULL;
462 CRM_CHECK(relaxng_file != NULL,
return FALSE);
464 if (cached_ctx && *cached_ctx) {
468 crm_debug(
"Creating RNG parser context");
469 ctx = calloc(1,
sizeof(relaxng_ctx_cache_t));
471 xmlLoadExtDtdDefaultValue = 1;
472 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
473 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
476 xmlRelaxNGSetParserErrors(ctx->parser,
477 (xmlRelaxNGValidityErrorFunc) xml_log,
478 (xmlRelaxNGValidityWarningFunc) xml_log,
479 GUINT_TO_POINTER(LOG_ERR));
481 xmlRelaxNGSetParserErrors(ctx->parser,
482 (xmlRelaxNGValidityErrorFunc) fprintf,
483 (xmlRelaxNGValidityWarningFunc) fprintf,
487 ctx->rng = xmlRelaxNGParse(ctx->parser);
489 crm_err(
"Could not find/parse %s", relaxng_file);
492 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
493 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
496 xmlRelaxNGSetValidErrors(ctx->valid,
497 (xmlRelaxNGValidityErrorFunc) xml_log,
498 (xmlRelaxNGValidityWarningFunc) xml_log,
499 GUINT_TO_POINTER(LOG_ERR));
501 xmlRelaxNGSetValidErrors(ctx->valid,
502 (xmlRelaxNGValidityErrorFunc) fprintf,
503 (xmlRelaxNGValidityWarningFunc) fprintf,
511 xmlLineNumbersDefault(1);
512 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
517 crm_err(
"Internal libxml error during validation");
526 if (ctx->parser != NULL) {
527 xmlRelaxNGFreeParserCtxt(ctx->parser);
529 if (ctx->valid != NULL) {
530 xmlRelaxNGFreeValidCtxt(ctx->valid);
532 if (ctx->rng != NULL) {
533 xmlRelaxNGFree(ctx->rng);
549 relaxng_ctx_cache_t *ctx = NULL;
551 for (lpc = 0; lpc < xml_schema_max; lpc++) {
553 switch (known_schemas[lpc].validator) {
557 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
561 if (ctx->parser != NULL) {
562 xmlRelaxNGFreeParserCtxt(ctx->parser);
564 if (ctx->valid != NULL) {
565 xmlRelaxNGFreeValidCtxt(ctx->valid);
567 if (ctx->rng != NULL) {
568 xmlRelaxNGFree(ctx->rng);
571 known_schemas[lpc].cache = NULL;
574 free(known_schemas[lpc].name);
575 free(known_schemas[lpc].location);
576 free(known_schemas[lpc].transform);
577 free(known_schemas[lpc].transform_enter);
580 known_schemas = NULL;
582 xsltCleanupGlobals();
589 validate_with(xmlNode *xml,
int method, gboolean to_logs)
591 xmlDocPtr doc = NULL;
592 gboolean valid = FALSE;
605 file = get_schema_path(known_schemas[method].name,
606 known_schemas[method].location);
608 crm_trace(
"Validating with: %s (type=%d)",
609 crm_str(file), known_schemas[method].validator);
610 switch (known_schemas[method].validator) {
613 validate_with_relaxng(doc, to_logs, file,
614 (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
617 crm_err(
"Unknown validator type: %d",
618 known_schemas[method].validator);
627 validate_with_silent(xmlNode *xml,
int method)
629 bool rc, sl_backup = silent_logging;
630 silent_logging = TRUE;
631 rc = validate_with(xml, method, TRUE);
632 silent_logging = sl_backup;
637 dump_file(
const char *filename)
645 fp = fopen(filename,
"r");
647 crm_perror(LOG_ERR,
"Could not open %s for reading", filename);
651 fprintf(stderr,
"%4d ", ++line);
657 }
else if (ch ==
'\n') {
658 fprintf(stderr,
"\n%4d ", ++line);
674 char *filename = NULL;
678 umask(S_IWGRP | S_IWOTH | S_IROTH);
679 fd = mkstemp(filename);
684 doc = xmlParseFile(filename);
685 xml = xmlDocGetRootElement(doc);
696 validate_xml(xmlNode *xml_blob,
const char *validation, gboolean to_logs)
700 if (validation == NULL) {
704 if (validation == NULL) {
708 for (lpc = 0; lpc < xml_schema_max; lpc++) {
709 if (validate_with(xml_blob, lpc, FALSE)) {
712 known_schemas[lpc].name);
713 crm_info(
"XML validated against %s", known_schemas[lpc].name);
714 if(known_schemas[lpc].after_transform == 0) {
724 if (strcmp(validation,
"none") == 0) {
726 }
else if (version < xml_schema_max) {
727 return validate_with(xml_blob, version, to_logs);
730 crm_err(
"Unknown validator: %s", validation);
737 cib_upgrade_err(
void *ctx,
const char *fmt, ...)
764 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
770 const char *fmt_iter = fmt;
771 uint8_t msg_log_level = LOG_WARNING;
772 const unsigned * log_level = (
const unsigned *) ctx;
776 } scan_state = escan_seennothing;
781 while (!found && *fmt_iter !=
'\0') {
783 switch (*fmt_iter++) {
785 if (scan_state == escan_seennothing) {
786 scan_state = escan_seenpercent;
787 }
else if (scan_state == escan_seenpercent) {
788 scan_state = escan_seennothing;
792 if (scan_state == escan_seenpercent) {
793 scan_state = escan_seennothing;
794 arg_cur = va_arg(aq,
char *);
795 if (arg_cur != NULL) {
796 switch (arg_cur[0]) {
798 if (!strncmp(arg_cur,
"WARNING: ",
799 sizeof(
"WARNING: ") - 1)) {
800 msg_log_level = LOG_WARNING;
803 memmove(arg_cur, arg_cur +
sizeof(
"WARNING: ") - 1,
804 strlen(arg_cur +
sizeof(
"WARNING: ") - 1) + 1);
809 if (!strncmp(arg_cur,
"INFO: ",
810 sizeof(
"INFO: ") - 1)) {
811 msg_log_level = LOG_INFO;
814 memmove(arg_cur, arg_cur +
sizeof(
"INFO: ") - 1,
815 strlen(arg_cur +
sizeof(
"INFO: ") - 1) + 1);
820 if (!strncmp(arg_cur,
"DEBUG: ",
821 sizeof(
"DEBUG: ") - 1)) {
822 msg_log_level = LOG_DEBUG;
825 memmove(arg_cur, arg_cur +
sizeof(
"DEBUG: ") - 1,
826 strlen(arg_cur +
sizeof(
"DEBUG: ") - 1) + 1);
834 case '#':
case '-':
case ' ':
case '+':
case '\'':
case 'I':
case '.':
835 case '0':
case '1':
case '2':
case '3':
case '4':
836 case '5':
case '6':
case '7':
case '8':
case '9':
853 if (scan_state == escan_seenpercent) {
854 (void) va_arg(aq,
void *);
855 scan_state = escan_seennothing;
859 scan_state = escan_seennothing;
864 if (log_level != NULL) {
867 if (*log_level + 4 >= msg_log_level) {
868 vfprintf(stderr, fmt, ap);
893 #ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
894 #define PCMK_SCHEMAS_EMERGENCY_XSLT 1
898 apply_transformation(xmlNode *xml,
const char *transform, gboolean to_logs)
902 xmlDocPtr res = NULL;
903 xmlDocPtr doc = NULL;
904 xsltStylesheet *xslt = NULL;
905 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
906 xmlChar *emergency_result;
907 int emergency_txt_len;
913 xform = get_schema_path(NULL, transform);
915 xmlLoadExtDtdDefaultValue = 1;
916 xmlSubstituteEntitiesDefault(1);
920 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
925 xslt = xsltParseStylesheetFile((
pcmkXmlStr) xform);
928 res = xsltApplyStylesheet(xslt, doc, NULL);
931 xsltSetGenericErrorFunc(NULL, NULL);
934 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
935 emergency_res = xsltSaveResultToString(&emergency_result,
936 &emergency_txt_len, res, xslt);
938 CRM_CHECK(emergency_res == 0,
goto cleanup);
939 out =
string2xml((
const char *) emergency_result);
940 free(emergency_result);
942 out = xmlDocGetRootElement(res);
947 xsltFreeStylesheet(xslt);
962 apply_upgrade(xmlNode *xml,
const struct schema_s *schema, gboolean to_logs)
964 bool transform_onleave = schema->transform_onleave;
965 char *transform_leave;
966 xmlNode *upgrade = NULL,
969 if (schema->transform_enter) {
970 crm_debug(
"Upgrading %s-style configuration, pre-upgrade phase with %s",
971 schema->name, schema->transform_enter);
972 upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
973 if (upgrade == NULL) {
974 crm_warn(
"Upgrade-enter transformation %s failed",
975 schema->transform_enter);
976 transform_onleave = FALSE;
979 if (upgrade == NULL) {
983 crm_debug(
"Upgrading %s-style configuration, main phase with %s",
984 schema->name, schema->transform);
985 final = apply_transformation(upgrade, schema->transform, to_logs);
986 if (upgrade != xml) {
991 if (
final != NULL && transform_onleave) {
995 transform_leave = strdup(schema->transform_enter);
997 memcpy(strrchr(transform_leave,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
998 crm_debug(
"Upgrading %s-style configuration, post-upgrade phase with %s",
999 schema->name, transform_leave);
1000 final = apply_transformation(upgrade, transform_leave, to_logs);
1001 if (
final == NULL) {
1002 crm_warn(
"Upgrade-leave transformation %s failed", transform_leave);
1007 free(transform_leave);
1018 if (version < 0 || version >= xml_schema_max) {
1021 return known_schemas[
version].name;
1032 for (; lpc < xml_schema_max; lpc++) {
1045 xmlNode *xml = NULL;
1047 int max_stable_schemas = xml_latest_schema_index();
1048 int lpc = 0, match = -1, rc =
pcmk_ok;
1051 CRM_CHECK(best != NULL,
return -EINVAL);
1054 CRM_CHECK(xml_blob != NULL,
return -EINVAL);
1055 CRM_CHECK(*xml_blob != NULL,
return -EINVAL);
1060 if (value != NULL) {
1064 if (lpc >= 0 && transform == FALSE) {
1067 }
else if (lpc < 0) {
1073 if (match >= max_stable_schemas) {
1080 while (lpc <= max_stable_schemas) {
1081 crm_debug(
"Testing '%s' validation (%d of %d)",
1082 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>",
1083 lpc, max_stable_schemas);
1085 if (validate_with(xml, lpc, to_logs) == FALSE) {
1087 crm_info(
"Configuration not valid for schema: %s",
1088 known_schemas[lpc].name);
1092 known_schemas[lpc].name ? known_schemas[lpc].name :
"<unset>");
1102 crm_debug(
"Configuration valid for schema: %s",
1103 known_schemas[next].name);
1113 if (rc ==
pcmk_ok && transform) {
1114 xmlNode *upgrade = NULL;
1115 next = known_schemas[lpc].after_transform;
1119 crm_trace(
"Stopping at %s", known_schemas[lpc].name);
1122 }
else if (max > 0 && (lpc == max || next > max)) {
1123 crm_trace(
"Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1124 known_schemas[lpc].name, lpc, next, max);
1127 }
else if (known_schemas[lpc].transform == NULL
1133 || validate_with_silent(xml, next)) {
1134 crm_debug(
"%s-style configuration is also valid for %s",
1135 known_schemas[lpc].name, known_schemas[next].name);
1140 crm_debug(
"Upgrading %s-style configuration to %s with %s",
1141 known_schemas[lpc].name, known_schemas[next].name,
1142 known_schemas[lpc].transform);
1145 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1147 if (upgrade == NULL) {
1148 crm_err(
"Transformation %s failed",
1149 known_schemas[lpc].transform);
1152 }
else if (validate_with(upgrade, next, to_logs)) {
1153 crm_info(
"Transformation %s successful",
1154 known_schemas[lpc].transform);
1162 crm_err(
"Transformation %s did not produce a valid configuration",
1163 known_schemas[lpc].transform);
1172 if (transform == FALSE || rc !=
pcmk_ok) {
1178 if (*best > match && *best) {
1179 crm_info(
"%s the configuration from %s to %s",
1180 transform?
"Transformed":
"Upgraded",
1181 value ? value :
"<none>", known_schemas[*best].name);
1195 char *
const orig_value = strdup(value == NULL ?
"(none)" : value);
1199 int min_version = xml_minimum_schema_index();
1201 if (version < min_version) {
1202 xmlNode *converted = NULL;
1208 if (version < min_version) {
1209 if (version < orig_version || orig_version == -1) {
1212 " validate with any schema in range [%s, %s],"
1213 " cannot upgrade to %s.",
1219 fprintf(stderr,
"Your current configuration %s could not"
1220 " validate with any schema in range [%s, %s],"
1221 " cannot upgrade to %s.\n",
1227 }
else if (to_logs) {
1228 crm_config_err(
"Your current configuration could only be upgraded to %s... "
1229 "the minimum requirement is %s.",
crm_str(value),
1232 fprintf(stderr,
"Your current configuration could only be upgraded to %s... "
1233 "the minimum requirement is %s.\n",
1245 if (version < xml_latest_schema_index()) {
1247 "which is acceptable but not the most recent",
1250 }
else if (to_logs) {
1251 crm_info(
"Your configuration was internally updated to the latest version (%s)",
1259 " It is highly encouraged and prevents many common cluster issues.");
1262 fprintf(stderr,
"Configuration validation is currently disabled."
1263 " It is highly encouraged and prevents many common cluster issues.\n");
#define CRM_CHECK(expr, failure_action)
const char * crm_get_tmpdir(void)
#define pcmk_err_schema_validation
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)
#define pcmk_err_transform_failed
void crm_schema_cleanup(void)
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
int get_schema_version(const char *name)
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.
xmlNode * string2xml(const char *input)
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_warn(fmt, args...)
#define crm_debug(fmt, args...)
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
#define crm_trace(fmt, args...)
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
void free_xml(xmlNode *child)
gboolean crm_ends_with_ext(const char *s, const char *match)
const char * xml_latest_schema(void)
const xmlChar * pcmkXmlStr
#define crm_config_warn(fmt...)
unsigned int crm_log_level
#define crm_perror(level, fmt, args...)
Log a system error message.
#define crm_err(fmt, args...)
#define CRM_SCHEMA_DIRECTORY
#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.
#define safe_str_eq(a, b)
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
#define crm_info(fmt, args...)
const char * get_schema_name(int version)