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> 30 #define SCHEMA_ZERO { .v = { 0, 0 } } 32 #define schema_strdup_printf(prefix, version, suffix) \ 33 crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1]) 37 xmlRelaxNGValidCtxtPtr valid;
38 xmlRelaxNGParserCtxtPtr parser;
39 } relaxng_ctx_cache_t;
41 static GList *known_schemas = NULL;
42 static bool initialized =
false;
43 static bool silent_logging = FALSE;
45 static void G_GNUC_PRINTF(2, 3)
46 xml_log(
int priority, const
char *fmt, ...)
51 if (silent_logging == FALSE) {
59 xml_latest_schema_index(
void)
68 return g_list_length(known_schemas) - 2;
78 get_highest_schema(
void)
99 GList *entry = get_highest_schema();
113 #if defined(PCMK__UNIT_TESTING) 118 GList *x_0_entry = NULL;
120 static GList *x_0_entry = NULL;
125 if (x_0_entry != NULL) {
128 x_0_entry = get_highest_schema();
129 highest_schema = x_0_entry->data;
131 for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
139 x_0_entry = iter->next;
146 if (iter->prev == NULL) {
147 x_0_entry = known_schemas->data;
158 return sscanf(filename,
"pacemaker-%hhu.%hhu.rng", &(
version->v[0]), &(
version->v[1])) == 2;
160 return sscanf(filename,
"pacemaker-%hhu.%hhu", &(
version->v[0]), &(
version->v[1])) == 2;
165 schema_filter(
const struct dirent *a)
170 if (strstr(a->d_name,
"pacemaker-") != a->d_name) {
176 }
else if (!version_from_filename(a->d_name, &
version)) {
190 for (
int i = 0; i < 2; ++i) {
191 if (a_version.
v[i] < b_version.
v[i]) {
193 }
else if (a_version.
v[i] > b_version.
v[i]) {
201 schema_cmp_directory(
const struct dirent **a,
const struct dirent **b)
206 if (!version_from_filename(a[0]->d_name, &a_version)
207 || !version_from_filename(b[0]->d_name, &b_version)) {
212 return schema_cmp(a_version, b_version);
240 known_schemas = g_list_prepend(known_schemas, schema);
244 wrap_libxslt(
bool finalize)
246 static xsltSecurityPrefsPtr secprefs;
252 secprefs = xsltNewSecurityPrefs();
253 ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
255 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
257 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
259 | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
265 xsltFreeSecurityPrefs(secprefs);
271 xsltCleanupGlobals();
285 transform_filter(
const struct dirent *entry)
287 return pcmk__str_eq(entry->d_name,
288 "upgrade-[[:digit:]]+.[[:digit:]]+-[[:digit:]]+.xsl",
299 free_transform_list(
void *
data)
301 g_list_free_full((GList *)
data, free);
319 load_transforms_from_dir(
const char *dir)
321 struct dirent **namelist = NULL;
322 int num_matches = scandir(dir, &namelist, transform_filter, versionsort);
325 for (
int i = 0; i < num_matches; i++) {
329 if (sscanf(namelist[i]->d_name,
"upgrade-%hhu.%hhu-%d.xsl",
334 GList *list = g_hash_table_lookup(transforms, version_s);
344 list = g_list_append(list, namelist[i]);
345 g_hash_table_insert(transforms, version_s, list);
348 list = g_list_append(list, namelist[i]);
366 struct dirent **namelist = NULL;
367 GHashTable *transforms = NULL;
369 max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
371 crm_warn(
"Could not load schemas from %s: %s", dir, strerror(errno));
376 transforms = load_transforms_from_dir(dir);
378 for (lpc = 0; lpc < max; lpc++) {
381 if (version_from_filename(namelist[lpc]->d_name, &
version)) {
384 char *orig_key = NULL;
385 GList *transform_list = NULL;
388 g_hash_table_lookup_extended(transforms, version_s,
389 (gpointer *) &orig_key,
390 (gpointer *) &transform_list);
391 g_hash_table_steal(transforms, version_s);
401 crm_warn(
"Skipping schema '%s': could not parse version",
402 namelist[lpc]->d_name);
406 for (lpc = 0; lpc < max; lpc++) {
411 g_hash_table_destroy(transforms);
415 schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
442 known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
459 int schema_index = 0;
479 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
482 crm_debug(
"Loaded schema %d: %s", schema_index, schema->
name);
489 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
490 void *error_handler_context,
const char *relaxng_file,
491 relaxng_ctx_cache_t **cached_ctx)
495 relaxng_ctx_cache_t *ctx = NULL;
498 CRM_CHECK(relaxng_file != NULL,
return false);
500 if (cached_ctx && *cached_ctx) {
504 crm_debug(
"Creating RNG parser context");
507 ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
508 CRM_CHECK(ctx->parser != NULL,
goto cleanup);
511 xmlRelaxNGSetParserErrors(ctx->parser,
512 (xmlRelaxNGValidityErrorFunc) error_handler,
513 (xmlRelaxNGValidityWarningFunc) error_handler,
514 error_handler_context);
516 xmlRelaxNGSetParserErrors(ctx->parser,
517 (xmlRelaxNGValidityErrorFunc) fprintf,
518 (xmlRelaxNGValidityWarningFunc) fprintf,
522 ctx->rng = xmlRelaxNGParse(ctx->parser);
524 crm_err(
"Could not find/parse %s", relaxng_file);
527 ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
528 CRM_CHECK(ctx->valid != NULL,
goto cleanup);
531 xmlRelaxNGSetValidErrors(ctx->valid,
532 (xmlRelaxNGValidityErrorFunc) error_handler,
533 (xmlRelaxNGValidityWarningFunc) error_handler,
534 error_handler_context);
536 xmlRelaxNGSetValidErrors(ctx->valid,
537 (xmlRelaxNGValidityErrorFunc) fprintf,
538 (xmlRelaxNGValidityWarningFunc) fprintf,
543 rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
548 crm_err(
"Internal libxml error during validation");
557 if (ctx->parser != NULL) {
558 xmlRelaxNGFreeParserCtxt(ctx->parser);
560 if (ctx->valid != NULL) {
561 xmlRelaxNGFreeValidCtxt(ctx->valid);
563 if (ctx->rng != NULL) {
564 xmlRelaxNGFree(ctx->rng);
573 free_schema(gpointer
data)
576 relaxng_ctx_cache_t *ctx = NULL;
583 ctx = (relaxng_ctx_cache_t *) schema->
cache;
588 if (ctx->parser != NULL) {
589 xmlRelaxNGFreeParserCtxt(ctx->parser);
592 if (ctx->valid != NULL) {
593 xmlRelaxNGFreeValidCtxt(ctx->valid);
596 if (ctx->rng != NULL) {
597 xmlRelaxNGFree(ctx->rng);
601 schema->
cache = NULL;
617 if (known_schemas != NULL) {
618 g_list_free_full(known_schemas, free_schema);
619 known_schemas = NULL;
640 for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
667 if (entry1 == NULL) {
668 return (entry2 == NULL)? 0 : -1;
670 }
else if (entry2 == NULL) {
683 xmlRelaxNGValidityErrorFunc error_handler,
684 void *error_handler_context)
688 relaxng_ctx_cache_t **cache = NULL;
690 if (schema == NULL) {
701 crm_trace(
"Validating with %s (type=%d)",
702 pcmk__s(file,
"missing schema"), schema->
validator);
705 cache = (relaxng_ctx_cache_t **) &(schema->
cache);
706 valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
720 bool rc, sl_backup = silent_logging;
721 silent_logging = TRUE;
722 rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
723 silent_logging = sl_backup;
729 xmlRelaxNGValidityErrorFunc error_handler,
730 void *error_handler_context)
735 CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL),
return false);
737 if (validation == NULL) {
745 " (manually edit to use a known schema)",
746 ((validation == NULL)?
"missing" :
"unknown"));
750 schema = entry->data;
751 return validate_with(xml_blob, schema, error_handler,
752 error_handler_context);
767 (xmlRelaxNGValidityErrorFunc) xml_log,
768 GUINT_TO_POINTER(LOG_ERR));
794 static void G_GNUC_PRINTF(2, 3)
795 cib_upgrade_err(
void *ctx, const
char *fmt, ...)
801 const char *fmt_iter = fmt;
802 uint8_t msg_log_level = LOG_WARNING;
803 const unsigned * log_level = (
const unsigned *) ctx;
807 } scan_state = escan_seennothing;
812 while (!found && *fmt_iter !=
'\0') {
814 switch (*fmt_iter++) {
816 if (scan_state == escan_seennothing) {
817 scan_state = escan_seenpercent;
818 }
else if (scan_state == escan_seenpercent) {
819 scan_state = escan_seennothing;
823 if (scan_state == escan_seenpercent) {
824 size_t prefix_len = 0;
826 scan_state = escan_seennothing;
827 arg_cur = va_arg(aq,
char *);
830 prefix_len =
sizeof(
"WARNING: ") - 1;
831 msg_log_level = LOG_WARNING;
834 prefix_len =
sizeof(
"INFO: ") - 1;
835 msg_log_level = LOG_INFO;
838 prefix_len =
sizeof(
"DEBUG: ") - 1;
839 msg_log_level = LOG_DEBUG;
847 memmove(arg_cur, arg_cur + prefix_len,
848 strlen(arg_cur + prefix_len) + 1);
852 case '#':
case '-':
case ' ':
case '+':
case '\'':
case 'I':
case '.':
853 case '0':
case '1':
case '2':
case '3':
case '4':
854 case '5':
case '6':
case '7':
case '8':
case '9':
871 if (scan_state == escan_seenpercent) {
872 (void) va_arg(aq,
void *);
873 scan_state = escan_seennothing;
877 scan_state = escan_seennothing;
882 if (log_level != NULL) {
885 if (*log_level + 4 >= msg_log_level) {
886 vfprintf(stderr, fmt, ap);
908 apply_transformation(
const xmlNode *xml,
const char *transform,
913 xmlDocPtr res = NULL;
914 xsltStylesheet *xslt = NULL;
921 xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
926 xslt = xsltParseStylesheetFile((
pcmkXmlStr) xform);
932 res = xsltApplyStylesheet(xslt, xml->doc, NULL);
935 xsltSetGenericErrorFunc(NULL, NULL);
937 out = xmlDocGetRootElement(res);
941 xsltFreeStylesheet(xslt);
962 apply_upgrade(
const xmlNode *input_xml,
int schema_index, gboolean to_logs)
964 pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
968 xmlNode *old_xml = NULL;
969 xmlNode *new_xml = NULL;
970 xmlRelaxNGValidityErrorFunc error_handler = NULL;
972 pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
975 error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
978 for (GList *iter = schema->
transforms; iter != NULL; iter = iter->next) {
979 const struct dirent *entry = iter->data;
980 const char *transform = entry->d_name;
982 crm_debug(
"Upgrading schema from %s to %s: applying XSL transform %s",
983 schema->
name, upgraded_schema->
name, transform);
985 new_xml = apply_transformation(input_xml, transform, to_logs);
988 if (new_xml == NULL) {
989 crm_err(
"XSL transform %s failed, aborting upgrade", transform);
1000 if (!validate_with(new_xml, upgraded_schema, error_handler,
1001 GUINT_TO_POINTER(LOG_ERR))) {
1002 crm_err(
"Schema upgrade from %s to %s failed: " 1003 "XSL transform pipeline produced an invalid configuration",
1004 schema->
name, upgraded_schema->
name);
1010 crm_info(
"Schema upgrade from %s to %s succeeded",
1011 schema->
name, upgraded_schema->
name);
1024 get_configured_schema(
const xmlNode *xml)
1050 int max_stable_schemas = xml_latest_schema_index();
1051 int max_schema_index = 0;
1053 GList *entry = NULL;
1056 xmlRelaxNGValidityErrorFunc error_handler =
1057 to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1059 CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1062 if (max_schema_name != NULL) {
1065 if (max_entry != NULL) {
1071 if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1072 max_schema_index = max_stable_schemas;
1075 entry = get_configured_schema(*xml);
1076 if (entry == NULL) {
1079 original_schema = entry->data;
1080 if (original_schema->
schema_index >= max_schema_index) {
1084 for (; entry != NULL; entry = entry->next) {
1086 xmlNode *upgrade = NULL;
1092 if (!validate_with(*xml, current_schema, error_handler,
1093 GUINT_TO_POINTER(LOG_ERR))) {
1094 crm_debug(
"Schema %s does not validate", current_schema->
name);
1095 if (best_schema != NULL) {
1105 best_schema = current_schema;
1106 if (current_schema->
schema_index == max_schema_index) {
1110 if (!transform || (current_schema->
transforms == NULL)
1111 || validate_with_silent(*xml, entry->next->data)) {
1119 upgrade = apply_upgrade(*xml, current_schema->
schema_index, to_logs);
1120 if (upgrade == NULL) {
1127 best_schema = current_schema;
1133 if ((best_schema != NULL)
1135 crm_info(
"%s the configuration schema to %s",
1136 (transform?
"Transformed" :
"Upgraded"),
1163 GList *entry = NULL;
1169 entry = get_configured_schema(*xml);
1170 if (entry == NULL) {
1174 original_schema = entry->data;
1177 xmlNode *converted = NULL;
1178 const char *new_schema_name = NULL;
1188 schema = (entry == NULL)? NULL : entry->data;
1190 if ((schema == NULL)
1194 if ((schema == NULL)
1199 "%s schema) to at least %s because it " 1200 "does not validate with any schema from " 1202 original_schema->
name,
1203 x_0_schema->
name, original_schema->
name);
1205 fprintf(stderr,
"Cannot upgrade configuration (claiming " 1206 "%s schema) to at least %s because it " 1207 "does not validate with any schema from " 1208 "%s to the latest\n",
1209 original_schema->
name,
1210 x_0_schema->
name, original_schema->
name);
1216 "%s schema) to at least %s because it " 1217 "would not upgrade past %s",
1218 original_schema->
name, x_0_schema->
name,
1219 pcmk__s(new_schema_name,
"unspecified version"));
1221 fprintf(stderr,
"Cannot upgrade configuration (claiming " 1222 "%s schema) to at least %s because it " 1223 "would not upgrade past %s\n",
1224 original_schema->
name, x_0_schema->
name,
1225 pcmk__s(new_schema_name,
"unspecified version"));
1238 if (schema->
schema_index < xml_latest_schema_index()) {
1241 "internally upgraded to acceptable (but " 1242 "not most recent) %s",
1243 original_schema->
name, schema->
name);
1245 }
else if (to_logs) {
1246 crm_info(
"Configuration with %s schema was internally " 1247 "upgraded to latest version %s",
1248 original_schema->
name, schema->
name);
1252 }
else if (!to_logs) {
1256 pcmk__assert((entry != NULL) && (entry->data != NULL));
1258 none_schema = entry->data;
1261 fprintf(stderr,
"Schema validation of configuration is " 1264 " and will be removed in a future release)\n");
1289 if (!version_from_filename(
name, &ver)) {
1293 for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1294 iter != NULL; iter = iter->prev) {
1297 if (schema_cmp(ver, schema->
version) != -1) {
1301 for (GList *iter2 = g_list_last(schema->
transforms); iter2 != NULL;
1302 iter2 = iter2->prev) {
1304 const struct dirent *entry = iter2->data;
1316 append_href(xmlNode *xml,
void *user_data)
1318 GList **list = user_data;
1324 *list = g_list_prepend(*list, href);
1328 external_refs_in_schema(GList **list,
const char *contents)
1333 const char *search =
"//*[local-name()='externalRef'] | //*[local-name()='include']";
1341 read_file_contents(
const char *file,
char **contents)
1359 add_schema_file_to_xml(xmlNode *
parent,
const char *file, GList **already_included)
1361 char *contents = NULL;
1363 xmlNode *file_node = NULL;
1364 GList *includes = NULL;
1368 if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1381 rc = read_file_contents(
path, &contents);
1393 *already_included = g_list_prepend(*already_included,
path);
1395 xmlAddChild(file_node, xmlNewCDataBlock(
parent->doc, (
pcmkXmlStr) contents,
1401 external_refs_in_schema(&includes, contents);
1406 for (GList *iter = includes; iter != NULL; iter = iter->next) {
1407 add_schema_file_to_xml(
parent, iter->data, already_included);
1411 g_list_free_full(includes, free);
1434 add_schema_file_to_xml(schema_node,
name, already_included);
1436 if (schema_node->children == NULL) {
1452 if (pcmk__str_empty(dir)) {
1473 "deprecated and will be removed in a future release " 1474 "without the possibility of upgrades (manually edit " 1475 "to use a supported schema)", schema);
1489 if (best_version != NULL) {
1498 *best_version = (schema == NULL)? -1 : schema->
schema_index;
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
int pcmk_update_configured_schema(xmlNode **xml)
#define CRM_CHECK(expr, failure_action)
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
#define schema_strdup_printf(prefix, version, suffix)
bool pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context)
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
const char * pcmk__highest_schema_name(void)
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
int pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
Update XML from its configured schema to the latest major series.
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
bool pcmk__ends_with(const char *s, const char *match)
const char * pcmk__env_option(const char *option)
Deprecated Pacemaker XML API.
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
const char * pcmk__remote_schema_dir(void)
void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
void pcmk__xml_free(xmlNode *xml)
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
#define crm_warn(fmt, args...)
#define crm_debug(fmt, args...)
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap)
Base for directing lib{xml2,xslt} log into standard libqb backend.
void pcmk__schema_init(void)
GList * pcmk__get_schema(const char *name)
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...)
#define crm_log_xml_debug(xml, text)
bool pcmk__configured_schema_validates(xmlNode *xml)
Wrappers for and extensions to libxml2.
enum pcmk__schema_validator validator
#define pcmk__str_copy(str)
#define PCMK_XA_VALIDATE_WITH
xmlNode * pcmk__xml_parse(const char *input)
#define PCMK__ENV_REMOTE_SCHEMA_DIRECTORY
const xmlChar * pcmkXmlStr
#define pcmk__assert(expr)
unsigned int crm_log_level
int pcmk__file_contents(const char *filename, char **contents)
void pcmk__load_schemas_from_dir(const char *dir)
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
GList * pcmk__schema_files_later_than(const char *name)
#define crm_err(fmt, args...)
void pcmk__warn_if_schema_deprecated(const char *schema)
pcmk__schema_version_t version
void crm_foreach_xpath_result(xmlNode *xml, const char *xpath, void(*helper)(xmlNode *, void *), void *user_data)
Run a supplied function for each result of an xpath search.
bool pcmk__starts_with(const char *str, const char *prefix)
Check whether a string starts with a certain sequence.
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.
void pcmk__schema_cleanup(void)
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
GList * pcmk__find_x_0_schema(void)
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
#define pcmk__assert_alloc(nmemb, size)
void pcmk__sort_schemas(void)
#define PCMK__REMOTE_SCHEMA_DIR
#define crm_info(fmt, args...)
bool pcmk__ends_with_ext(const char *s, const char *match)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1