20#include <libxml/relaxng.h>
21#include <libxml/tree.h>
22#include <libxml/xmlstring.h>
23#include <libxslt/xslt.h>
24#include <libxslt/transform.h>
25#include <libxslt/security.h>
26#include <libxslt/xsltutils.h>
34#define SCHEMA_ZERO { .v = { 0, 0 } }
36#define schema_strdup_printf(prefix, version, suffix) \
37 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
41 xmlRelaxNGValidCtxtPtr valid;
42 xmlRelaxNGParserCtxtPtr parser;
45static GList *known_schemas = NULL;
46static bool initialized =
false;
47static bool silent_logging = FALSE;
49static void G_GNUC_PRINTF(2, 3)
50xml_log(
int priority, const
char *fmt, ...)
55 if (silent_logging == FALSE) {
63xml_latest_schema_index(
void)
72 return g_list_length(known_schemas) - 2;
82get_highest_schema(
void)
103 GList *entry = get_highest_schema();
117#if defined(PCMK__UNIT_TESTING)
122 GList *x_0_entry = NULL;
124 static GList *x_0_entry = NULL;
129 if (x_0_entry != NULL) {
132 x_0_entry = get_highest_schema();
133 highest_schema = x_0_entry->data;
135 for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
143 x_0_entry = iter->next;
150 if (iter->prev == NULL) {
151 x_0_entry = known_schemas->data;
162 return sscanf(filename,
"pacemaker-%hhu.%hhu.rng", &(
version->v[0]), &(
version->v[1])) == 2;
164 return sscanf(filename,
"pacemaker-%hhu.%hhu", &(
version->v[0]), &(
version->v[1])) == 2;
169schema_filter(
const struct dirent *a)
174 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
180 }
else if (!version_from_filename(a->d_name, &
version)) {
194 for (
int i = 0; i < 2; ++i) {
195 if (a_version.
v[i] < b_version.
v[i]) {
197 }
else if (a_version.
v[i] > b_version.
v[i]) {
205schema_cmp_directory(
const struct dirent **a,
const struct dirent **b)
210 if (!version_from_filename(a[0]->d_name, &a_version)
211 || !version_from_filename(b[0]->d_name, &b_version)) {
216 return schema_cmp(a_version, b_version);
244 known_schemas = g_list_prepend(known_schemas, schema);
248wrap_libxslt(
bool finalize)
250 static xsltSecurityPrefsPtr secprefs;
256 secprefs = xsltNewSecurityPrefs();
257 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
259 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
261 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
263 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
269 xsltFreeSecurityPrefs(secprefs);
275 xsltCleanupGlobals();
290transform_filter(
const struct dirent *entry)
292 const char *re = NULL;
293 unsigned int major = 0;
294 unsigned int minor = 0;
295 unsigned int order = 0;
300 re =
"upgrade-[[:digit:]]{1,3}\\.[[:digit:]]{1,3}-[[:digit:]]{1,3}\\.xsl";
309 if (sscanf(entry->d_name,
"upgrade-%u.%u-%u.xsl",
310 &major, &minor, &order) != 3) {
314 if ((major > UCHAR_MAX) || (minor > UCHAR_MAX) || (order > UCHAR_MAX)) {
335compare_transforms(
const struct dirent **entry1,
const struct dirent **entry2)
337 unsigned char major1 = 0;
338 unsigned char major2 = 0;
339 unsigned char minor1 = 0;
340 unsigned char minor2 = 0;
341 unsigned char order1 = 0;
342 unsigned char order2 = 0;
345 CRM_LOG_ASSERT(sscanf((*entry1)->d_name,
"upgrade-%hhu.%hhu-%hhu.xsl",
346 &major1, &minor1, &order1) == 3);
347 CRM_LOG_ASSERT(sscanf((*entry2)->d_name,
"upgrade-%hhu.%hhu-%hhu.xsl",
348 &major2, &minor2, &order2) == 3);
350 if (major1 < major2) {
352 }
else if (major1 > major2) {
356 if (minor1 < minor2) {
358 }
else if (minor1 > minor2) {
362 if (order1 < order2) {
364 }
else if (order1 > order2) {
378free_transform_list(
void *
data)
380 g_list_free_full((GList *)
data, free);
398load_transforms_from_dir(
const char *dir)
400 struct dirent **namelist = NULL;
401 GHashTable *transforms = NULL;
402 int num_matches = scandir(dir, &namelist, transform_filter,
405 if (num_matches < 0) {
414 for (
int i = 0; i < num_matches; i++) {
416 unsigned char order = 0;
418 if (sscanf(namelist[i]->d_name,
"upgrade-%hhu.%hhu-%hhu.xsl",
423 GList *list = g_hash_table_lookup(transforms, version_s);
433 list = g_list_append(list, namelist[i]);
434 g_hash_table_insert(transforms, version_s, list);
437 list = g_list_append(list, namelist[i]);
456 struct dirent **namelist = NULL;
457 GHashTable *transforms = NULL;
459 max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
468 transforms = load_transforms_from_dir(dir);
470 for (lpc = 0; lpc < max; lpc++) {
473 if (version_from_filename(namelist[lpc]->d_name, &
version)) {
476 char *orig_key = NULL;
477 GList *transform_list = NULL;
479 if (transforms != NULL) {
481 g_hash_table_lookup_extended(transforms, version_s,
482 (gpointer *) &orig_key,
483 (gpointer *) &transform_list);
484 g_hash_table_steal(transforms, version_s);
495 crm_warn(
"Skipping schema '%s': could not parse version",
496 namelist[lpc]->d_name);
500 for (lpc = 0; lpc < max; lpc++) {
506 if (transforms != NULL) {
507 g_hash_table_destroy(transforms);
512schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
539 known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
556 int schema_index = 0;
576 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
579 crm_debug(
"Loaded schema %d: %s", schema_index, schema->
name);
586validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
587 void *error_handler_context,
const char *relaxng_file,
588 relaxng_ctx_cache_t **cached_ctx)
592 relaxng_ctx_cache_t *ctx = NULL;
595 CRM_CHECK(relaxng_file != NULL,
return false);
597 if (cached_ctx && *cached_ctx) {
601 crm_debug(
"Creating RNG parser context");
604 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
605 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
608 xmlRelaxNGSetParserErrors(ctx->parser,
609 (xmlRelaxNGValidityErrorFunc) error_handler,
610 (xmlRelaxNGValidityWarningFunc) error_handler,
611 error_handler_context);
613 xmlRelaxNGSetParserErrors(ctx->parser,
614 (xmlRelaxNGValidityErrorFunc) fprintf,
615 (xmlRelaxNGValidityWarningFunc) fprintf,
619 ctx->rng = xmlRelaxNGParse(ctx->parser);
621 crm_err(
"Could not find/parse %s", relaxng_file);
624 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
625 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
628 xmlRelaxNGSetValidErrors(ctx->valid,
629 (xmlRelaxNGValidityErrorFunc) error_handler,
630 (xmlRelaxNGValidityWarningFunc) error_handler,
631 error_handler_context);
633 xmlRelaxNGSetValidErrors(ctx->valid,
634 (xmlRelaxNGValidityErrorFunc) fprintf,
635 (xmlRelaxNGValidityWarningFunc) fprintf,
640 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
645 crm_err(
"Internal libxml error during validation");
654 if (ctx->parser != NULL) {
655 xmlRelaxNGFreeParserCtxt(ctx->parser);
657 if (ctx->valid != NULL) {
658 xmlRelaxNGFreeValidCtxt(ctx->valid);
660 if (ctx->rng != NULL) {
661 xmlRelaxNGFree(ctx->rng);
670free_schema(gpointer
data)
673 relaxng_ctx_cache_t *ctx = NULL;
680 ctx = (relaxng_ctx_cache_t *) schema->
cache;
685 if (ctx->parser != NULL) {
686 xmlRelaxNGFreeParserCtxt(ctx->parser);
689 if (ctx->valid != NULL) {
690 xmlRelaxNGFreeValidCtxt(ctx->valid);
693 if (ctx->rng != NULL) {
694 xmlRelaxNGFree(ctx->rng);
698 schema->
cache = NULL;
714 if (known_schemas != NULL) {
715 g_list_free_full(known_schemas, free_schema);
716 known_schemas = NULL;
737 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
764 if (entry1 == NULL) {
765 return (entry2 == NULL)? 0 : -1;
767 }
else if (entry2 == NULL) {
780 xmlRelaxNGValidityErrorFunc error_handler,
781 void *error_handler_context)
785 relaxng_ctx_cache_t **cache = NULL;
787 if (schema == NULL) {
798 crm_trace(
"Validating with %s (type=%d)",
799 pcmk__s(file,
"missing schema"), schema->
validator);
802 cache = (relaxng_ctx_cache_t **) &(schema->
cache);
803 valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
817 bool rc, sl_backup = silent_logging;
818 silent_logging = TRUE;
819 rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
820 silent_logging = sl_backup;
826 xmlRelaxNGValidityErrorFunc error_handler,
827 void *error_handler_context)
832 CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL),
return false);
834 if (validation == NULL) {
842 " (manually edit to use a known schema)",
843 ((validation == NULL)?
"missing" :
"unknown"));
847 schema = entry->data;
848 return validate_with(xml_blob, schema, error_handler,
849 error_handler_context);
864 (xmlRelaxNGValidityErrorFunc) xml_log,
865 GUINT_TO_POINTER(LOG_ERR));
891static void G_GNUC_PRINTF(2, 3)
892cib_upgrade_err(
void *ctx, const
char *fmt, ...)
898 const char *fmt_iter = fmt;
899 uint8_t msg_log_level = LOG_WARNING;
900 const unsigned * log_level = (
const unsigned *) ctx;
904 } scan_state = escan_seennothing;
909 while (!found && *fmt_iter !=
'\0') {
911 switch (*fmt_iter++) {
913 if (scan_state == escan_seennothing) {
914 scan_state = escan_seenpercent;
915 }
else if (scan_state == escan_seenpercent) {
916 scan_state = escan_seennothing;
920 if (scan_state == escan_seenpercent) {
921 size_t prefix_len = 0;
923 scan_state = escan_seennothing;
924 arg_cur = va_arg(aq,
char *);
927 prefix_len =
sizeof(
"WARNING: ") - 1;
928 msg_log_level = LOG_WARNING;
931 prefix_len =
sizeof(
"INFO: ") - 1;
932 msg_log_level = LOG_INFO;
935 prefix_len =
sizeof(
"DEBUG: ") - 1;
936 msg_log_level = LOG_DEBUG;
944 memmove(arg_cur, arg_cur + prefix_len,
945 strlen(arg_cur + prefix_len) + 1);
949 case '#':
case '-':
case ' ':
case '+':
case '\'':
case 'I':
case '.':
950 case '0':
case '1':
case '2':
case '3':
case '4':
951 case '5':
case '6':
case '7':
case '8':
case '9':
968 if (scan_state == escan_seenpercent) {
969 (void) va_arg(aq,
void *);
970 scan_state = escan_seennothing;
974 scan_state = escan_seennothing;
979 if (log_level != NULL) {
982 if (*log_level + 4 >= msg_log_level) {
983 vfprintf(stderr, fmt, ap);
1005apply_transformation(
const xmlNode *xml,
const char *transform,
1009 xmlNode *out = NULL;
1010 xmlDocPtr res = NULL;
1011 xsltStylesheet *xslt = NULL;
1018 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
1023 xslt = xsltParseStylesheetFile((
const xmlChar *) xform);
1029 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
1032 xsltSetGenericErrorFunc(NULL, NULL);
1034 out = xmlDocGetRootElement(res);
1038 xsltFreeStylesheet(xslt);
1059apply_upgrade(
const xmlNode *input_xml,
int schema_index, gboolean to_logs)
1061 pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1065 xmlNode *old_xml = NULL;
1066 xmlNode *new_xml = NULL;
1067 xmlRelaxNGValidityErrorFunc error_handler = NULL;
1069 pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
1072 error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1075 for (GList *iter = schema->
transforms; iter != NULL; iter = iter->next) {
1076 const struct dirent *entry = iter->data;
1077 const char *transform = entry->d_name;
1079 crm_debug(
"Upgrading schema from %s to %s: applying XSL transform %s",
1080 schema->
name, upgraded_schema->
name, transform);
1082 new_xml = apply_transformation(input_xml, transform, to_logs);
1085 if (new_xml == NULL) {
1086 crm_err(
"XSL transform %s failed, aborting upgrade", transform);
1089 input_xml = new_xml;
1097 if (!validate_with(new_xml, upgraded_schema, error_handler,
1098 GUINT_TO_POINTER(LOG_ERR))) {
1099 crm_err(
"Schema upgrade from %s to %s failed: "
1100 "XSL transform pipeline produced an invalid configuration",
1101 schema->
name, upgraded_schema->
name);
1107 crm_info(
"Schema upgrade from %s to %s succeeded",
1108 schema->
name, upgraded_schema->
name);
1121get_configured_schema(
const xmlNode *xml)
1147 int max_stable_schemas = xml_latest_schema_index();
1148 int max_schema_index = 0;
1150 GList *entry = NULL;
1153 xmlRelaxNGValidityErrorFunc error_handler =
1154 to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1156 CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1159 if (max_schema_name != NULL) {
1162 if (max_entry != NULL) {
1168 if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1169 max_schema_index = max_stable_schemas;
1172 entry = get_configured_schema(*xml);
1173 if (entry == NULL) {
1176 original_schema = entry->data;
1177 if (original_schema->
schema_index >= max_schema_index) {
1181 for (; entry != NULL; entry = entry->next) {
1183 xmlNode *upgrade = NULL;
1189 if (!validate_with(*xml, current_schema, error_handler,
1190 GUINT_TO_POINTER(LOG_ERR))) {
1191 crm_debug(
"Schema %s does not validate", current_schema->
name);
1192 if (best_schema != NULL) {
1202 best_schema = current_schema;
1203 if (current_schema->
schema_index == max_schema_index) {
1208 if (!transform || (current_schema->
transforms == NULL)
1209 || validate_with_silent(*xml, entry->next->data)) {
1217 upgrade = apply_upgrade(*xml, current_schema->
schema_index, to_logs);
1218 if (upgrade == NULL) {
1225 best_schema = current_schema;
1231 if ((best_schema != NULL)
1233 crm_info(
"%s the configuration schema to %s",
1234 (transform?
"Transformed" :
"Upgraded"),
1261 GList *entry = NULL;
1267 entry = get_configured_schema(*xml);
1268 if (entry == NULL) {
1272 original_schema = entry->data;
1275 xmlNode *converted = NULL;
1276 const char *new_schema_name = NULL;
1286 schema = (entry == NULL)? NULL : entry->data;
1288 if ((schema == NULL)
1292 if ((schema == NULL)
1297 "%s schema) to at least %s because it "
1298 "does not validate with any schema from "
1300 original_schema->
name,
1301 x_0_schema->
name, original_schema->
name);
1303 fprintf(stderr,
"Cannot upgrade configuration (claiming "
1304 "%s schema) to at least %s because it "
1305 "does not validate with any schema from "
1306 "%s to the latest\n",
1307 original_schema->
name,
1308 x_0_schema->
name, original_schema->
name);
1314 "%s schema) to at least %s because it "
1315 "would not upgrade past %s",
1316 original_schema->
name, x_0_schema->
name,
1317 pcmk__s(new_schema_name,
"unspecified version"));
1319 fprintf(stderr,
"Cannot upgrade configuration (claiming "
1320 "%s schema) to at least %s because it "
1321 "would not upgrade past %s\n",
1322 original_schema->
name, x_0_schema->
name,
1323 pcmk__s(new_schema_name,
"unspecified version"));
1336 if (schema->
schema_index < xml_latest_schema_index()) {
1339 "internally upgraded to acceptable (but "
1340 "not most recent) %s",
1341 original_schema->
name, schema->
name);
1343 }
else if (to_logs) {
1344 crm_info(
"Configuration with %s schema was internally "
1345 "upgraded to latest version %s",
1346 original_schema->
name, schema->
name);
1350 }
else if (!to_logs) {
1354 pcmk__assert((entry != NULL) && (entry->data != NULL));
1356 none_schema = entry->data;
1359 fprintf(stderr,
"Schema validation of configuration is "
1362 " and will be removed in a future release)\n");
1387 if (!version_from_filename(
name, &ver)) {
1391 for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1392 iter != NULL; iter = iter->prev) {
1395 if (schema_cmp(ver, schema->
version) != -1) {
1399 for (GList *iter2 = g_list_last(schema->
transforms); iter2 != NULL;
1400 iter2 = iter2->prev) {
1402 const struct dirent *entry = iter2->data;
1414append_href(xmlNode *xml,
void *user_data)
1416 GList **list = user_data;
1422 *list = g_list_prepend(*list, href);
1426external_refs_in_schema(GList **list,
const char *contents)
1431 const char *search =
"//*[local-name()='externalRef'] | //*[local-name()='include']";
1439read_file_contents(
const char *file,
char **contents)
1457add_schema_file_to_xml(xmlNode *
parent,
const char *file, GList **already_included)
1459 char *contents = NULL;
1461 xmlNode *file_node = NULL;
1462 GList *includes = NULL;
1466 if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1479 rc = read_file_contents(
path, &contents);
1491 *already_included = g_list_prepend(*already_included,
path);
1493 xmlAddChild(file_node,
1494 xmlNewCDataBlock(
parent->doc, (
const xmlChar *) contents,
1500 external_refs_in_schema(&includes, contents);
1505 for (GList *iter = includes; iter != NULL; iter = iter->next) {
1506 add_schema_file_to_xml(
parent, iter->data, already_included);
1510 g_list_free_full(includes, free);
1533 add_schema_file_to_xml(schema_node,
name, already_included);
1535 if (schema_node->children == NULL) {
1551 if (pcmk__str_empty(dir)) {
1572 "deprecated and will be removed in a future release "
1573 "without the possibility of upgrades (manually edit "
1574 "to use a supported schema)", schema);
1588 if (best_version != NULL) {
1597 *best_version = (schema == NULL)? -1 : schema->
schema_index;
#define pcmk__assert_alloc(nmemb, size)
#define PCMK__REMOTE_SCHEMA_DIR
@ pcmk__schema_validator_rng
@ pcmk__schema_validator_none
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
int pcmk__file_contents(const char *filename, char **contents)
#define crm_info(fmt, args...)
#define crm_warn(fmt, args...)
#define crm_log_xml_debug(xml, text)
#define CRM_LOG_ASSERT(expr)
#define CRM_CHECK(expr, failure_action)
#define crm_debug(fmt, args...)
#define crm_err(fmt, args...)
unsigned int crm_log_level
#define crm_trace(fmt, args...)
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
#define PCMK__ENV_REMOTE_SCHEMA_DIRECTORY
const char * pcmk__env_option(const char *option)
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
@ pcmk_rc_transform_failed
@ pcmk_rc_schema_validation
#define pcmk__assert(expr)
#define schema_strdup_printf(prefix, version, suffix)
GList * pcmk__schema_files_later_than(const char *name)
bool pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context)
int pcmk_update_configured_schema(xmlNode **xml)
void pcmk__load_schemas_from_dir(const char *dir)
GList * pcmk__get_schema(const char *name)
void pcmk__sort_schemas(void)
const char * pcmk__highest_schema_name(void)
GList * pcmk__find_x_0_schema(void)
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
const char * pcmk__remote_schema_dir(void)
int pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
Update XML from its configured schema to the latest major series.
bool pcmk__configured_schema_validates(xmlNode *xml)
void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
void pcmk__schema_cleanup(void)
void pcmk__warn_if_schema_deprecated(const char *schema)
void pcmk__schema_init(void)
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
int pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, bool to_logs)
Update CIB XML to latest schema that validates it.
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
bool pcmk__ends_with_ext(const char *s, const char *match)
bool pcmk__starts_with(const char *str, const char *prefix)
Check whether a string starts with a certain sequence.
bool pcmk__ends_with(const char *s, const char *match)
#define pcmk__str_copy(str)
pcmk__schema_version_t version
enum pcmk__schema_validator validator
Wrappers for and extensions to libxml2.
Deprecated Pacemaker XML API.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
void pcmk__xml_free(xmlNode *xml)
@ pcmk__xml_artefact_ns_legacy_xslt
@ pcmk__xml_artefact_ns_legacy_rng
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
xmlNode * pcmk__xml_parse(const char *input)
#define PCMK_XA_VALIDATE_WITH
void pcmk__xpath_foreach_result(xmlDoc *doc, const char *path, void(*fn)(xmlNode *, void *), void *user_data)