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> 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;
59 char *transform_enter;
60 bool transform_onleave;
63 static struct schema_s *known_schemas = NULL;
64 static int xml_schema_max = 0;
65 static bool silent_logging = FALSE;
68 xml_log(
int priority,
const char *fmt, ...)
72 xml_log(
int priority, const
char *fmt, ...)
77 if (silent_logging == FALSE) {
85 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 version_from_filename(
const char *filename, schema_version_t *
version)
127 schema_filter(
const struct dirent *a)
132 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
138 }
else if (!version_from_filename(a->d_name, &
version)) {
150 schema_sort(
const struct dirent **a,
const struct dirent **b)
155 if (!version_from_filename(a[0]->d_name, &a_version)
156 || !version_from_filename(b[0]->d_name, &b_version)) {
161 for (
int i = 0; i < 2; ++i) {
162 if (a_version.v[i] < b_version.v[i]) {
164 }
else if (a_version.v[i] > b_version.v[i]) {
180 const char *
name,
const char *transform,
181 const char *transform_enter,
bool transform_onleave,
184 int last = xml_schema_max;
185 bool have_version = FALSE;
188 known_schemas = pcmk__realloc(known_schemas,
189 xml_schema_max *
sizeof(
struct schema_s));
191 memset(known_schemas+last, 0,
sizeof(
struct schema_s));
192 known_schemas[last].validator = validator;
193 known_schemas[last].after_transform = after_transform;
195 for (
int i = 0; i < 2; ++i) {
196 known_schemas[last].version.v[i] =
version->v[i];
206 known_schemas[last].name = strdup(
name);
210 known_schemas[last].transform = strdup(transform);
212 if (transform_enter) {
213 known_schemas[last].transform_enter = strdup(transform_enter);
215 known_schemas[last].transform_onleave = transform_onleave;
216 if (after_transform == 0) {
217 after_transform = xml_schema_max;
219 known_schemas[last].after_transform = after_transform;
221 if (known_schemas[last].after_transform < 0) {
222 crm_debug(
"Added supported schema %d: %s",
223 last, known_schemas[last].
name);
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);
232 crm_debug(
"Added supported schema %d: %s (upgrades to %d)",
233 last, known_schemas[last].
name,
234 known_schemas[last].after_transform);
267 add_schema_by_version(
const schema_version_t *
version,
int next,
268 bool transform_expected)
270 bool transform_onleave = FALSE;
274 *transform_upgrade = NULL,
275 *transform_enter = NULL;
278 if (transform_expected) {
285 if (!transform_expected) {
288 }
else if (stat(xslt, &s) == 0) {
294 if (stat(xslt, &s) != 0) {
296 crm_debug(
"Upgrade-enter transform %s.xsl not found", xslt);
298 free(transform_enter);
299 transform_enter = strdup(
"upgrade-enter");
302 if (stat(xslt, &s) != 0) {
303 crm_debug(
"Upgrade-enter transform %s.xsl not found, either", xslt);
311 memcpy(strrchr(xslt,
'-') + 1,
"leave",
sizeof(
"leave") - 1);
312 transform_onleave = (stat(xslt, &s) == 0);
315 free(transform_enter);
316 transform_enter = NULL;
320 crm_err(
"Upgrade transform %s not found", xslt);
322 free(transform_upgrade);
323 transform_upgrade = NULL;
329 transform_upgrade, transform_enter, transform_onleave, next);
331 free(transform_upgrade);
332 free(transform_enter);
338 wrap_libxslt(
bool finalize)
340 static xsltSecurityPrefsPtr secprefs;
346 secprefs = xsltNewSecurityPrefs();
347 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
349 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
351 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
353 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
359 xsltFreeSecurityPrefs(secprefs);
365 xsltCleanupGlobals();
381 struct dirent **namelist = NULL;
386 max = scandir(base, &namelist, schema_filter, schema_sort);
388 crm_notice(
"scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
393 for (lpc = 0; lpc < max; lpc++) {
394 bool transform_expected = FALSE;
398 if (!version_from_filename(namelist[lpc]->d_name, &
version)) {
400 crm_err(
"Skipping schema '%s': could not parse version",
401 namelist[lpc]->d_name);
404 if ((lpc + 1) < max) {
407 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
408 && (
version.v[0] < next_version.v[0])) {
409 transform_expected = TRUE;
415 if (add_schema_by_version(&
version, next, transform_expected)
421 for (lpc = 0; lpc < max; lpc++) {
429 NULL, NULL, FALSE, -1);
432 NULL, NULL, FALSE, -1);
436 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
void *error_handler_context,
const char *relaxng_file,
437 relaxng_ctx_cache_t **cached_ctx)
440 gboolean valid = TRUE;
441 relaxng_ctx_cache_t *ctx = NULL;
444 CRM_CHECK(relaxng_file != NULL,
return FALSE);
446 if (cached_ctx && *cached_ctx) {
450 crm_debug(
"Creating RNG parser context");
451 ctx = calloc(1,
sizeof(relaxng_ctx_cache_t));
453 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
454 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
457 xmlRelaxNGSetParserErrors(ctx->parser,
458 (xmlRelaxNGValidityErrorFunc) error_handler,
459 (xmlRelaxNGValidityWarningFunc) error_handler,
460 error_handler_context);
462 xmlRelaxNGSetParserErrors(ctx->parser,
463 (xmlRelaxNGValidityErrorFunc) fprintf,
464 (xmlRelaxNGValidityWarningFunc) fprintf,
468 ctx->rng = xmlRelaxNGParse(ctx->parser);
470 crm_err(
"Could not find/parse %s", relaxng_file);
473 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
474 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
477 xmlRelaxNGSetValidErrors(ctx->valid,
478 (xmlRelaxNGValidityErrorFunc) error_handler,
479 (xmlRelaxNGValidityWarningFunc) error_handler,
480 error_handler_context);
482 xmlRelaxNGSetValidErrors(ctx->valid,
483 (xmlRelaxNGValidityErrorFunc) fprintf,
484 (xmlRelaxNGValidityWarningFunc) fprintf,
489 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
494 crm_err(
"Internal libxml error during validation");
503 if (ctx->parser != NULL) {
504 xmlRelaxNGFreeParserCtxt(ctx->parser);
506 if (ctx->valid != NULL) {
507 xmlRelaxNGFreeValidCtxt(ctx->valid);
509 if (ctx->rng != NULL) {
510 xmlRelaxNGFree(ctx->rng);
526 relaxng_ctx_cache_t *ctx = NULL;
528 for (lpc = 0; lpc < xml_schema_max; lpc++) {
530 switch (known_schemas[lpc].validator) {
534 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
538 if (ctx->parser != NULL) {
539 xmlRelaxNGFreeParserCtxt(ctx->parser);
541 if (ctx->valid != NULL) {
542 xmlRelaxNGFreeValidCtxt(ctx->valid);
544 if (ctx->rng != NULL) {
545 xmlRelaxNGFree(ctx->rng);
548 known_schemas[lpc].cache = NULL;
551 free(known_schemas[lpc].
name);
552 free(known_schemas[lpc].transform);
553 free(known_schemas[lpc].transform_enter);
556 known_schemas = NULL;
562 validate_with(xmlNode *xml,
int method, xmlRelaxNGValidityErrorFunc error_handler,
void* error_handler_context)
564 gboolean valid = FALSE;
566 struct schema_s *schema = NULL;
567 relaxng_ctx_cache_t **cache = NULL;
573 schema = &(known_schemas[method]);
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.");
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);
594 crm_err(
"Unknown validator type: %d",
595 known_schemas[method].validator);
604 validate_with_silent(xmlNode *xml,
int method)
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;
614 dump_file(
const char *filename)
622 fp = fopen(filename,
"r");
624 crm_perror(LOG_ERR,
"Could not open %s for reading", filename);
628 fprintf(stderr,
"%4d ", ++line);
634 }
else if (ch ==
'\n') {
635 fprintf(stderr,
"\n%4d ", ++line);
651 char *filename = NULL;
655 umask(S_IWGRP | S_IWOTH | S_IROTH);
656 fd = mkstemp(filename);
661 doc = xmlReadFile(filename, NULL, 0);
662 xml = xmlDocGetRootElement(doc);
673 validate_xml(xmlNode *xml_blob,
const char *validation, gboolean to_logs)
675 return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
679 pcmk__validate_xml(xmlNode *xml_blob,
const char *validation, xmlRelaxNGValidityErrorFunc error_handler,
void* error_handler_context)
683 CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL),
return FALSE);
685 if (validation == NULL) {
689 if (validation == NULL) {
693 for (lpc = 0; lpc < xml_schema_max; lpc++) {
694 if (validate_with(xml_blob, lpc, NULL, NULL)) {
697 known_schemas[lpc].
name);
698 crm_info(
"XML validated against %s", known_schemas[lpc].
name);
699 if(known_schemas[lpc].after_transform == 0) {
711 }
else if (
version < xml_schema_max) {
712 return validate_with(xml_blob,
version, error_handler, error_handler_context);
715 crm_err(
"Unknown validator: %s", validation);
720 cib_upgrade_err(
void *ctx,
const char *fmt, ...)
747 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
753 const char *fmt_iter = fmt;
754 uint8_t msg_log_level = LOG_WARNING;
755 const unsigned * log_level = (
const unsigned *) ctx;
759 } scan_state = escan_seennothing;
764 while (!found && *fmt_iter !=
'\0') {
766 switch (*fmt_iter++) {
768 if (scan_state == escan_seennothing) {
769 scan_state = escan_seenpercent;
770 }
else if (scan_state == escan_seenpercent) {
771 scan_state = escan_seennothing;
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]) {
781 if (!strncmp(arg_cur,
"WARNING: ",
782 sizeof(
"WARNING: ") - 1)) {
783 msg_log_level = LOG_WARNING;
786 memmove(arg_cur, arg_cur +
sizeof(
"WARNING: ") - 1,
787 strlen(arg_cur +
sizeof(
"WARNING: ") - 1) + 1);
792 if (!strncmp(arg_cur,
"INFO: ",
793 sizeof(
"INFO: ") - 1)) {
794 msg_log_level = LOG_INFO;
797 memmove(arg_cur, arg_cur +
sizeof(
"INFO: ") - 1,
798 strlen(arg_cur +
sizeof(
"INFO: ") - 1) + 1);
803 if (!strncmp(arg_cur,
"DEBUG: ",
804 sizeof(
"DEBUG: ") - 1)) {
805 msg_log_level = LOG_DEBUG;
808 memmove(arg_cur, arg_cur +
sizeof(
"DEBUG: ") - 1,
809 strlen(arg_cur +
sizeof(
"DEBUG: ") - 1) + 1);
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':
836 if (scan_state == escan_seenpercent) {
837 (void) va_arg(aq,
void *);
838 scan_state = escan_seennothing;
842 scan_state = escan_seennothing;
847 if (log_level != NULL) {
850 if (*log_level + 4 >= msg_log_level) {
851 vfprintf(stderr, fmt, ap);
862 apply_transformation(xmlNode *xml,
const char *transform, gboolean to_logs)
866 xmlDocPtr res = NULL;
867 xsltStylesheet *xslt = NULL;
874 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
879 xslt = xsltParseStylesheetFile((
pcmkXmlStr) xform);
882 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
885 xsltSetGenericErrorFunc(NULL, NULL);
887 out = xmlDocGetRootElement(res);
891 xsltFreeStylesheet(xslt);
906 apply_upgrade(xmlNode *xml,
const struct schema_s *schema, gboolean to_logs)
908 bool transform_onleave = schema->transform_onleave;
909 char *transform_leave;
910 xmlNode *upgrade = NULL,
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;
923 if (upgrade == NULL) {
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) {
935 if (
final != NULL && transform_onleave) {
939 transform_leave = strdup(schema->transform_enter);
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);
946 crm_warn(
"Upgrade-leave transformation %s.xsl failed", transform_leave);
951 free(transform_leave);
960 if (version < 0 || version >= xml_schema_max) {
963 return known_schemas[
version].name;
974 for (; lpc < xml_schema_max; lpc++) {
989 int max_stable_schemas = xml_latest_schema_index();
990 int lpc = 0, match = -1, rc =
pcmk_ok;
992 xmlRelaxNGValidityErrorFunc error_handler =
993 to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
998 CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
999 && ((*xml_blob)->doc != NULL),
1005 if (value != NULL) {
1009 if (lpc >= 0 && transform == FALSE) {
1012 }
else if (lpc < 0) {
1018 if (match >= max_stable_schemas) {
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);
1030 if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
1032 crm_info(
"Configuration not valid for schema: %s",
1033 known_schemas[lpc].
name);
1037 known_schemas[lpc].
name ? known_schemas[lpc].
name :
"<unset>");
1047 crm_debug(
"Configuration valid for schema: %s",
1048 known_schemas[next].
name);
1058 if (rc ==
pcmk_ok && transform) {
1059 xmlNode *upgrade = NULL;
1060 next = known_schemas[lpc].after_transform;
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);
1072 }
else if (known_schemas[lpc].transform == NULL
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);
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);
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);
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);
1105 crm_err(
"Transformation %s.xsl did not produce a valid configuration",
1106 known_schemas[lpc].transform);
1115 if (transform == FALSE || rc !=
pcmk_ok) {
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);
1138 char *
const orig_value = strdup(value == NULL ?
"(none)" : value);
1142 int min_version = xml_minimum_schema_index();
1146 xmlNode *converted = NULL;
1155 if (
version < orig_version || orig_version == -1) {
1159 "schema %s) to at least %s because it " 1160 "does not validate with any schema from " 1167 fprintf(stderr,
"Cannot upgrade configuration (claiming " 1168 "schema %s) to at least %s because it " 1169 "does not validate with any schema from " 1180 "schema %s) to at least %s because it " 1181 "would not upgrade past %s",
1184 pcmk__s(value,
"unspecified version"));
1186 fprintf(stderr,
"Cannot upgrade configuration (claiming " 1187 "schema %s) to at least %s because it " 1188 "would not upgrade past %s\n",
1191 pcmk__s(value,
"unspecified version"));
1204 if (
version < xml_latest_schema_index()) {
1207 "internally upgraded to acceptable (but " 1208 "not most recent) %s",
1213 crm_info(
"Configuration with schema %s was internally " 1214 "upgraded to latest version %s",
1224 "(enabling is encouraged and prevents common " 1225 "misconfigurations)");
1228 fprintf(stderr,
"Schema validation of configuration is disabled " 1229 "(enabling is encouraged and prevents common " 1230 "misconfigurations)\n");
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
#define CRM_CHECK(expr, failure_action)
#define pcmk_err_schema_validation
void crm_schema_init(void)
#define crm_notice(fmt, args...)
#define schema_strdup_printf(prefix, version, suffix)
#define pcmk__config_warn(fmt...)
#define schema_scanf(s, prefix, version, suffix)
#define pcmk__config_err(fmt...)
#define pcmk_err_transform_failed
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
xmlNode * copy_xml(xmlNode *src_node)
#define crm_warn(fmt, args...)
int get_schema_version(const char *name)
#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 PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
gboolean validate_xml_verbose(const xmlNode *xml_blob)
void crm_schema_cleanup(void)
#define crm_trace(fmt, args...)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
const char * get_schema_name(int version)
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
void free_xml(xmlNode *child)
const xmlChar * pcmkXmlStr
const char * pcmk__get_tmpdir(void)
int write_xml_fd(const xmlNode *xml, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
unsigned int crm_log_level
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
#define crm_err(fmt, args...)
#define crm_log_xml_info(xml, text)
gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context)
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
int update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, gboolean to_logs)
Update CIB XML to most recent schema version.
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
#define crm_info(fmt, args...)
bool pcmk__ends_with_ext(const char *s, const char *match)
const char * xml_latest_schema(void)