summaryrefslogtreecommitdiffstats
path: root/lib/common/schemas.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/schemas.c')
-rw-r--r--lib/common/schemas.c1561
1 files changed, 1032 insertions, 529 deletions
diff --git a/lib/common/schemas.c b/lib/common/schemas.c
index b3c09eb..16f2ccb 100644
--- a/lib/common/schemas.c
+++ b/lib/common/schemas.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2004-2023 the Pacemaker project contributors
+ * Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
@@ -22,19 +22,13 @@
#include <libxslt/security.h>
#include <libxslt/xsltutils.h>
-#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> /* PCMK__XML_LOG_BASE */
-typedef struct {
- unsigned char v[2];
-} schema_version_t;
+#include "crmcommon_private.h"
#define SCHEMA_ZERO { .v = { 0, 0 } }
-#define schema_scanf(s, prefix, version, suffix) \
- sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
-
#define schema_strdup_printf(prefix, version, suffix) \
crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
@@ -44,31 +38,11 @@ typedef struct {
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
-enum schema_validator_e {
- schema_validator_none,
- schema_validator_rng
-};
-
-struct schema_s {
- char *name;
- char *transform;
- void *cache;
- enum schema_validator_e validator;
- int after_transform;
- schema_version_t version;
- char *transform_enter;
- bool transform_onleave;
-};
-
-static struct schema_s *known_schemas = NULL;
-static int xml_schema_max = 0;
+static GList *known_schemas = NULL;
+static bool initialized = false;
static bool silent_logging = FALSE;
-static void
-xml_log(int priority, const char *fmt, ...)
-G_GNUC_PRINTF(2, 3);
-
-static void
+static void G_GNUC_PRINTF(2, 3)
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
@@ -84,50 +58,114 @@ xml_log(int priority, const char *fmt, ...)
static int
xml_latest_schema_index(void)
{
- // @COMPAT: pacemaker-next is deprecated since 2.1.5
- return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
+ /* This function assumes that crm_schema_init() has been called beforehand,
+ * so we have at least three schemas (one real schema, the "pacemaker-next"
+ * schema, and the "none" schema).
+ *
+ * @COMPAT: pacemaker-next is deprecated since 2.1.5 and none since 2.1.8.
+ * Update this when we drop those.
+ */
+ return g_list_length(known_schemas) - 3;
}
-static int
-xml_minimum_schema_index(void)
+/*!
+ * \internal
+ * \brief Return the schema entry of the highest-versioned schema
+ *
+ * \return Schema entry of highest-versioned schema (or NULL on error)
+ */
+static GList *
+get_highest_schema(void)
{
- static int best = 0;
- if (best == 0) {
- int lpc = 0;
-
- best = xml_latest_schema_index();
- for (lpc = best; lpc > 0; lpc--) {
- if (known_schemas[lpc].version.v[0]
- < known_schemas[best].version.v[0]) {
- return best;
- } else {
- best = lpc;
- }
- }
- best = xml_latest_schema_index();
- }
- return best;
+ /* The highest numerically versioned schema is the one before pacemaker-next
+ *
+ * @COMPAT pacemaker-next is deprecated since 2.1.5
+ */
+ GList *entry = pcmk__get_schema("pacemaker-next");
+
+ CRM_ASSERT((entry != NULL) && (entry->prev != NULL));
+ return entry->prev;
}
+/*!
+ * \internal
+ * \brief Return the name of the highest-versioned schema
+ *
+ * \return Name of highest-versioned schema (or NULL on error)
+ */
const char *
-xml_latest_schema(void)
+pcmk__highest_schema_name(void)
{
- return get_schema_name(xml_latest_schema_index());
+ GList *entry = get_highest_schema();
+
+ return ((pcmk__schema_t *)(entry->data))->name;
}
-static inline bool
-version_from_filename(const char *filename, schema_version_t *version)
+/*!
+ * \internal
+ * \brief Find first entry of highest major schema version series
+ *
+ * \return Schema entry of first schema with highest major version
+ */
+GList *
+pcmk__find_x_0_schema(void)
{
- int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
+#if defined(PCMK__UNIT_TESTING)
+ /* If we're unit testing, this can't be static because it'll stick
+ * around from one test run to the next. It needs to be cleared out
+ * every time.
+ */
+ GList *x_0_entry = NULL;
+#else
+ static GList *x_0_entry = NULL;
+#endif
+
+ pcmk__schema_t *highest_schema = NULL;
+
+ if (x_0_entry != NULL) {
+ return x_0_entry;
+ }
+ x_0_entry = get_highest_schema();
+ highest_schema = x_0_entry->data;
+
+ for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
+ pcmk__schema_t *schema = iter->data;
+
+ /* We've found a schema in an older major version series. Return
+ * the index of the first one in the same major version series as
+ * the highest schema.
+ */
+ if (schema->version.v[0] < highest_schema->version.v[0]) {
+ x_0_entry = iter->next;
+ break;
+ }
+
+ /* We're out of list to examine. This probably means there was only
+ * one major version series, so return the first schema entry.
+ */
+ if (iter->prev == NULL) {
+ x_0_entry = known_schemas->data;
+ break;
+ }
+ }
+ return x_0_entry;
+}
- return (rc == 2);
+static inline bool
+version_from_filename(const char *filename, pcmk__schema_version_t *version)
+{
+ if (pcmk__ends_with(filename, ".rng")) {
+ return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2;
+ } else {
+ return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2;
+ }
}
static int
schema_filter(const struct dirent *a)
{
int rc = 0;
- schema_version_t version = SCHEMA_ZERO;
+ pcmk__schema_version_t version = SCHEMA_ZERO;
if (strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
@@ -147,17 +185,8 @@ schema_filter(const struct dirent *a)
}
static int
-schema_sort(const struct dirent **a, const struct dirent **b)
+schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version)
{
- schema_version_t a_version = SCHEMA_ZERO;
- schema_version_t b_version = SCHEMA_ZERO;
-
- if (!version_from_filename(a[0]->d_name, &a_version)
- || !version_from_filename(b[0]->d_name, &b_version)) {
- // Shouldn't be possible, but makes static analysis happy
- return 0;
- }
-
for (int i = 0; i < 2; ++i) {
if (a_version.v[i] < b_version.v[i]) {
return -1;
@@ -168,6 +197,21 @@ schema_sort(const struct dirent **a, const struct dirent **b)
return 0;
}
+static int
+schema_cmp_directory(const struct dirent **a, const struct dirent **b)
+{
+ pcmk__schema_version_t a_version = SCHEMA_ZERO;
+ pcmk__schema_version_t b_version = SCHEMA_ZERO;
+
+ if (!version_from_filename(a[0]->d_name, &a_version)
+ || !version_from_filename(b[0]->d_name, &b_version)) {
+ // Shouldn't be possible, but makes static analysis happy
+ return 0;
+ }
+
+ return schema_cmp(a_version, b_version);
+}
+
/*!
* \internal
* \brief Add given schema + auxiliary data to internal bookkeeping.
@@ -176,63 +220,35 @@ schema_sort(const struct dirent **a, const struct dirent **b)
* through \c add_schema_by_version.
*/
static void
-add_schema(enum schema_validator_e validator, const schema_version_t *version,
+add_schema(enum pcmk__schema_validator validator, const pcmk__schema_version_t *version,
const char *name, const char *transform,
- const char *transform_enter, bool transform_onleave,
- int after_transform)
+ const char *transform_enter, bool transform_onleave)
{
- int last = xml_schema_max;
- bool have_version = FALSE;
+ pcmk__schema_t *schema = NULL;
- xml_schema_max++;
- known_schemas = pcmk__realloc(known_schemas,
- xml_schema_max * sizeof(struct schema_s));
- CRM_ASSERT(known_schemas != NULL);
- memset(known_schemas+last, 0, sizeof(struct schema_s));
- known_schemas[last].validator = validator;
- known_schemas[last].after_transform = after_transform;
+ schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
- for (int i = 0; i < 2; ++i) {
- known_schemas[last].version.v[i] = version->v[i];
- if (version->v[i]) {
- have_version = TRUE;
- }
- }
- if (have_version) {
- known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
+ schema->validator = validator;
+ schema->version.v[0] = version->v[0];
+ schema->version.v[1] = version->v[1];
+ schema->transform_onleave = transform_onleave;
+ // schema->schema_index is set after all schemas are loaded and sorted
+
+ if (version->v[0] || version->v[1]) {
+ schema->name = schema_strdup_printf("pacemaker-", *version, "");
} else {
- CRM_ASSERT(name);
- schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
- known_schemas[last].name = strdup(name);
+ schema->name = pcmk__str_copy(name);
}
if (transform) {
- known_schemas[last].transform = strdup(transform);
+ schema->transform = pcmk__str_copy(transform);
}
+
if (transform_enter) {
- known_schemas[last].transform_enter = strdup(transform_enter);
- }
- known_schemas[last].transform_onleave = transform_onleave;
- if (after_transform == 0) {
- after_transform = xml_schema_max; /* upgrade is a one-way */
+ schema->transform_enter = pcmk__str_copy(transform_enter);
}
- known_schemas[last].after_transform = after_transform;
- if (known_schemas[last].after_transform < 0) {
- crm_debug("Added supported schema %d: %s",
- last, known_schemas[last].name);
-
- } else if (known_schemas[last].transform) {
- crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
- last, known_schemas[last].name,
- known_schemas[last].after_transform,
- known_schemas[last].transform);
-
- } else {
- crm_debug("Added supported schema %d: %s (upgrades to %d)",
- last, known_schemas[last].name,
- known_schemas[last].after_transform);
- }
+ known_schemas = g_list_prepend(known_schemas, schema);
}
/*!
@@ -264,8 +280,7 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version,
* . name convention: (see "upgrade-enter")
*/
static int
-add_schema_by_version(const schema_version_t *version, int next,
- bool transform_expected)
+add_schema_by_version(const pcmk__schema_version_t *version, bool transform_expected)
{
bool transform_onleave = FALSE;
int rc = pcmk_rc_ok;
@@ -321,12 +336,11 @@ add_schema_by_version(const schema_version_t *version, int next,
free(xslt);
free(transform_upgrade);
transform_upgrade = NULL;
- next = -1;
rc = ENOENT;
}
- add_schema(schema_validator_rng, version, NULL,
- transform_upgrade, transform_enter, transform_onleave, next);
+ add_schema(pcmk__schema_validator_rng, version, NULL,
+ transform_upgrade, transform_enter, transform_onleave);
free(transform_upgrade);
free(transform_enter);
@@ -366,6 +380,85 @@ wrap_libxslt(bool finalize)
}
}
+void
+pcmk__load_schemas_from_dir(const char *dir)
+{
+ int lpc, max;
+ struct dirent **namelist = NULL;
+
+ max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
+ if (max < 0) {
+ crm_warn("Could not load schemas from %s: %s", dir, strerror(errno));
+ return;
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ bool transform_expected = false;
+ pcmk__schema_version_t version = SCHEMA_ZERO;
+
+ if (!version_from_filename(namelist[lpc]->d_name, &version)) {
+ // Shouldn't be possible, but makes static analysis happy
+ crm_warn("Skipping schema '%s': could not parse version",
+ namelist[lpc]->d_name);
+ continue;
+ }
+ if ((lpc + 1) < max) {
+ pcmk__schema_version_t next_version = SCHEMA_ZERO;
+
+ if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
+ && (version.v[0] < next_version.v[0])) {
+ transform_expected = true;
+ }
+ }
+
+ if (add_schema_by_version(&version, transform_expected) != pcmk_rc_ok) {
+ break;
+ }
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ free(namelist[lpc]);
+ }
+
+ free(namelist);
+}
+
+static gint
+schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
+{
+ const pcmk__schema_t *schema_a = a;
+ const pcmk__schema_t *schema_b = b;
+
+ // @COMPAT pacemaker-next is deprecated since 2.1.5 and none since 2.1.8
+ if (pcmk__str_eq(schema_a->name, "pacemaker-next", pcmk__str_none)) {
+ if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
+ return 1;
+ } else if (pcmk__str_eq(schema_b->name, "pacemaker-next", pcmk__str_none)) {
+ return -1;
+ } else {
+ return schema_cmp(schema_a->version, schema_b->version);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Sort the list of known schemas such that all pacemaker-X.Y are in
+ * version order, then pacemaker-next, then none
+ *
+ * This function should be called whenever additional schemas are loaded using
+ * pcmk__load_schemas_from_dir(), after the initial sets in crm_schema_init().
+ */
+void
+pcmk__sort_schemas(void)
+{
+ known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
+}
+
/*!
* \internal
* \brief Load pacemaker schemas into cache
@@ -376,79 +469,67 @@ wrap_libxslt(bool finalize)
void
crm_schema_init(void)
{
- int lpc, max;
- char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
- struct dirent **namelist = NULL;
- const schema_version_t zero = SCHEMA_ZERO;
+ if (!initialized) {
+ const char *remote_schema_dir = pcmk__remote_schema_dir();
+ char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
+ const pcmk__schema_version_t zero = SCHEMA_ZERO;
+ int schema_index = 0;
- wrap_libxslt(false);
+ initialized = true;
- max = scandir(base, &namelist, schema_filter, schema_sort);
- if (max < 0) {
- crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
- free(base);
+ wrap_libxslt(false);
- } else {
+ pcmk__load_schemas_from_dir(base);
+ pcmk__load_schemas_from_dir(remote_schema_dir);
free(base);
- for (lpc = 0; lpc < max; lpc++) {
- bool transform_expected = FALSE;
- int next = 0;
- schema_version_t version = SCHEMA_ZERO;
-
- if (!version_from_filename(namelist[lpc]->d_name, &version)) {
- // Shouldn't be possible, but makes static analysis happy
- crm_err("Skipping schema '%s': could not parse version",
- namelist[lpc]->d_name);
- continue;
- }
- if ((lpc + 1) < max) {
- schema_version_t next_version = SCHEMA_ZERO;
- if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
- && (version.v[0] < next_version.v[0])) {
- transform_expected = TRUE;
- }
+ // @COMPAT: Deprecated since 2.1.5
+ add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next", NULL,
+ NULL, FALSE);
+ // @COMPAT Deprecated since 2.1.8
+ add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL,
+ NULL, FALSE);
+
+ /* add_schema() prepends items to the list, so in the simple case, this
+ * just reverses the list. However if there were any remote schemas,
+ * sorting is necessary.
+ */
+ pcmk__sort_schemas();
+
+ // Now set the schema indexes and log the final result
+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
+ pcmk__schema_t *schema = iter->data;
+
+ if (schema->transform == NULL) {
+ crm_debug("Loaded schema %d: %s", schema_index, schema->name);
} else {
- next = -1;
- }
- if (add_schema_by_version(&version, next, transform_expected)
- == ENOENT) {
- break;
+ crm_debug("Loaded schema %d: %s (upgrades with %s.xsl)",
+ schema_index, schema->name, schema->transform);
}
+ schema->schema_index = schema_index++;
}
-
- for (lpc = 0; lpc < max; lpc++) {
- free(namelist[lpc]);
- }
- free(namelist);
}
-
- // @COMPAT: Deprecated since 2.1.5
- add_schema(schema_validator_rng, &zero, "pacemaker-next",
- NULL, NULL, FALSE, -1);
-
- add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
- NULL, NULL, FALSE, -1);
}
-static gboolean
-validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
+static bool
+validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context, const char *relaxng_file,
relaxng_ctx_cache_t **cached_ctx)
{
int rc = 0;
- gboolean valid = TRUE;
+ bool valid = true;
relaxng_ctx_cache_t *ctx = NULL;
- CRM_CHECK(doc != NULL, return FALSE);
- CRM_CHECK(relaxng_file != NULL, return FALSE);
+ CRM_CHECK(doc != NULL, return false);
+ CRM_CHECK(relaxng_file != NULL, return false);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_debug("Creating RNG parser context");
- ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
+ ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
@@ -488,7 +569,7 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
- valid = FALSE;
+ valid = false;
} else if (rc < 0) {
crm_err("Internal libxml error during validation");
@@ -515,6 +596,45 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
return valid;
}
+static void
+free_schema(gpointer data)
+{
+ pcmk__schema_t *schema = data;
+ relaxng_ctx_cache_t *ctx = NULL;
+
+ switch (schema->validator) {
+ case pcmk__schema_validator_none: // not cached
+ break;
+
+ case pcmk__schema_validator_rng: // cached
+ ctx = (relaxng_ctx_cache_t *) schema->cache;
+ if (ctx == NULL) {
+ break;
+ }
+
+ if (ctx->parser != NULL) {
+ xmlRelaxNGFreeParserCtxt(ctx->parser);
+ }
+
+ if (ctx->valid != NULL) {
+ xmlRelaxNGFreeValidCtxt(ctx->valid);
+ }
+
+ if (ctx->rng != NULL) {
+ xmlRelaxNGFree(ctx->rng);
+ }
+
+ free(ctx);
+ schema->cache = NULL;
+ break;
+ }
+
+ free(schema->name);
+ free(schema->transform);
+ free(schema->transform_enter);
+ free(schema);
+}
+
/*!
* \internal
* \brief Clean up global memory associated with XML schemas
@@ -522,62 +642,86 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
void
crm_schema_cleanup(void)
{
- int lpc;
- relaxng_ctx_cache_t *ctx = NULL;
+ if (known_schemas != NULL) {
+ g_list_free_full(known_schemas, free_schema);
+ known_schemas = NULL;
+ }
+ initialized = false;
- for (lpc = 0; lpc < xml_schema_max; lpc++) {
+ wrap_libxslt(true);
+}
- switch (known_schemas[lpc].validator) {
- case schema_validator_none: // not cached
- break;
- case schema_validator_rng: // cached
- ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
- if (ctx == NULL) {
- break;
- }
- if (ctx->parser != NULL) {
- xmlRelaxNGFreeParserCtxt(ctx->parser);
- }
- if (ctx->valid != NULL) {
- xmlRelaxNGFreeValidCtxt(ctx->valid);
- }
- if (ctx->rng != NULL) {
- xmlRelaxNGFree(ctx->rng);
- }
- free(ctx);
- known_schemas[lpc].cache = NULL;
- break;
+/*!
+ * \internal
+ * \brief Get schema list entry corresponding to a schema name
+ *
+ * \param[in] name Name of schema to get
+ *
+ * \return Schema list entry corresponding to \p name, or NULL if unknown
+ */
+GList *
+pcmk__get_schema(const char *name)
+{
+ // @COMPAT Not specifying a schema name is deprecated since 2.1.8
+ if (name == NULL) {
+ name = PCMK_VALUE_NONE;
+ }
+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
+ pcmk__schema_t *schema = iter->data;
+
+ if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
+ return iter;
}
- free(known_schemas[lpc].name);
- free(known_schemas[lpc].transform);
- free(known_schemas[lpc].transform_enter);
}
- free(known_schemas);
- known_schemas = NULL;
+ return NULL;
+}
- wrap_libxslt(true);
+/*!
+ * \internal
+ * \brief Compare two schema version numbers given the schema names
+ *
+ * \param[in] schema1 Name of first schema to compare
+ * \param[in] schema2 Name of second schema to compare
+ *
+ * \return Standard comparison result (negative integer if \p schema1 has the
+ * lower version number, positive integer if \p schema1 has the higher
+ * version number, of 0 if the version numbers are equal)
+ */
+int
+pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
+{
+ GList *entry1 = pcmk__get_schema(schema1_name);
+ GList *entry2 = pcmk__get_schema(schema2_name);
+
+ if (entry1 == NULL) {
+ return (entry2 == NULL)? 0 : -1;
+
+ } else if (entry2 == NULL) {
+ return 1;
+
+ } else {
+ pcmk__schema_t *schema1 = entry1->data;
+ pcmk__schema_t *schema2 = entry2->data;
+
+ return schema1->schema_index - schema2->schema_index;
+ }
}
-static gboolean
-validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
+static bool
+validate_with(xmlNode *xml, pcmk__schema_t *schema,
+ xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context)
{
- gboolean valid = FALSE;
+ bool valid = false;
char *file = NULL;
- struct schema_s *schema = NULL;
relaxng_ctx_cache_t **cache = NULL;
- if (method < 0) {
- return FALSE;
- }
-
- schema = &(known_schemas[method]);
- if (schema->validator == schema_validator_none) {
- return TRUE;
+ if (schema == NULL) {
+ return false;
}
- if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
- crm_warn("The pacemaker-next schema is deprecated and will be removed "
- "in a future release.");
+ if (schema->validator == pcmk__schema_validator_none) {
+ return true;
}
file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
@@ -586,13 +730,12 @@ validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handle
crm_trace("Validating with %s (type=%d)",
pcmk__s(file, "missing schema"), schema->validator);
switch (schema->validator) {
- case schema_validator_rng:
+ case pcmk__schema_validator_rng:
cache = (relaxng_ctx_cache_t **) &(schema->cache);
valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
break;
default:
- crm_err("Unknown validator type: %d",
- known_schemas[method].validator);
+ crm_err("Unknown validator type: %d", schema->validator);
break;
}
@@ -601,124 +744,74 @@ validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handle
}
static bool
-validate_with_silent(xmlNode *xml, int method)
+validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
{
bool rc, sl_backup = silent_logging;
silent_logging = TRUE;
- rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
+ rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
silent_logging = sl_backup;
return rc;
}
-static void
-dump_file(const char *filename)
+bool
+pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
+ xmlRelaxNGValidityErrorFunc error_handler,
+ void *error_handler_context)
{
+ GList *entry = NULL;
+ pcmk__schema_t *schema = NULL;
- FILE *fp = NULL;
- int ch, line = 0;
-
- CRM_CHECK(filename != NULL, return);
-
- fp = fopen(filename, "r");
- if (fp == NULL) {
- crm_perror(LOG_ERR, "Could not open %s for reading", filename);
- return;
- }
-
- fprintf(stderr, "%4d ", ++line);
- do {
- ch = getc(fp);
- if (ch == EOF) {
- putc('\n', stderr);
- break;
- } else if (ch == '\n') {
- fprintf(stderr, "\n%4d ", ++line);
- } else {
- putc(ch, stderr);
- }
- } while (1);
-
- fclose(fp);
-}
-
-gboolean
-validate_xml_verbose(const xmlNode *xml_blob)
-{
- int fd = 0;
- xmlDoc *doc = NULL;
- xmlNode *xml = NULL;
- gboolean rc = FALSE;
- char *filename = NULL;
-
- filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
-
- umask(S_IWGRP | S_IWOTH | S_IROTH);
- fd = mkstemp(filename);
- write_xml_fd(xml_blob, filename, fd, FALSE);
-
- dump_file(filename);
-
- doc = xmlReadFile(filename, NULL, 0);
- xml = xmlDocGetRootElement(doc);
- rc = validate_xml(xml, NULL, FALSE);
- free_xml(xml);
-
- unlink(filename);
- free(filename);
-
- return rc;
-}
-
-gboolean
-validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
-{
- return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
-}
-
-gboolean
-pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
-{
- int version = 0;
-
- CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
+ CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
if (validation == NULL) {
- validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
+ validation = crm_element_value(xml_blob, PCMK_XA_VALIDATE_WITH);
}
+ pcmk__warn_if_schema_deprecated(validation);
+ // @COMPAT Not specifying a schema name is deprecated since 2.1.8
if (validation == NULL) {
- int lpc = 0;
- bool valid = FALSE;
-
- for (lpc = 0; lpc < xml_schema_max; lpc++) {
- if (validate_with(xml_blob, lpc, NULL, NULL)) {
- valid = TRUE;
- crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
- known_schemas[lpc].name);
- crm_info("XML validated against %s", known_schemas[lpc].name);
- if(known_schemas[lpc].after_transform == 0) {
- break;
- }
+ bool valid = false;
+
+ for (entry = known_schemas; entry != NULL; entry = entry->next) {
+ schema = entry->data;
+ if (validate_with(xml_blob, schema, NULL, NULL)) {
+ valid = true;
+ crm_xml_add(xml_blob, PCMK_XA_VALIDATE_WITH, schema->name);
+ crm_info("XML validated against %s", schema->name);
}
}
-
return valid;
}
- version = get_schema_version(validation);
- if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
- return TRUE;
- } else if (version < xml_schema_max) {
- return validate_with(xml_blob, version, error_handler, error_handler_context);
+ entry = pcmk__get_schema(validation);
+ if (entry == NULL) {
+ pcmk__config_err("Cannot validate CIB with " PCMK_XA_VALIDATE_WITH
+ " set to an unknown schema such as '%s' (manually"
+ " edit to use a known schema)",
+ validation);
+ return false;
}
- crm_err("Unknown validator: %s", validation);
- return FALSE;
+ schema = entry->data;
+ return validate_with(xml_blob, schema, error_handler,
+ error_handler_context);
}
-static void
-cib_upgrade_err(void *ctx, const char *fmt, ...)
-G_GNUC_PRINTF(2, 3);
+/*!
+ * \internal
+ * \brief Validate XML using its configured schema (and send errors to logs)
+ *
+ * \param[in] xml XML to validate
+ *
+ * \return true if XML validates, otherwise false
+ */
+bool
+pcmk__configured_schema_validates(xmlNode *xml)
+{
+ return pcmk__validate_xml(xml, NULL,
+ (xmlRelaxNGValidityErrorFunc) xml_log,
+ GUINT_TO_POINTER(LOG_ERR));
+}
/* With this arrangement, an attempt to identify the message severity
as explicitly signalled directly from XSLT is performed in rather
@@ -743,7 +836,7 @@ G_GNUC_PRINTF(2, 3);
(suspicious, likely internal errors or some runaways) is
LOG_WARNING.
*/
-static void
+static void G_GNUC_PRINTF(2, 3)
cib_upgrade_err(void *ctx, const char *fmt, ...)
{
va_list ap, aq;
@@ -858,8 +951,20 @@ cib_upgrade_err(void *ctx, const char *fmt, ...)
va_end(ap);
}
+/*!
+ * \internal
+ * \brief Apply a single XSL transformation to given XML
+ *
+ * \param[in] xml XML to transform
+ * \param[in] transform XSL name
+ * \param[in] to_logs If false, certain validation errors will be sent to
+ * stderr rather than logged
+ *
+ * \return Transformed XML on success, otherwise NULL
+ */
static xmlNode *
-apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
+apply_transformation(const xmlNode *xml, const char *transform,
+ gboolean to_logs)
{
char *xform = NULL;
xmlNode *out = NULL;
@@ -898,52 +1003,79 @@ apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
/*!
* \internal
- * \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
+ * \brief Perform all transformations needed to upgrade XML to next schema
+ *
+ * A schema upgrade can require up to three XSL transformations: an "enter"
+ * transform, the main upgrade transform, and a "leave" transform. Perform
+ * all needed transforms to upgrade given XML to the next schema.
+ *
+ * \param[in] original_xml XML to transform
+ * \param[in] schema_index Index of schema that successfully validates
+ * \p original_xml
+ * \param[in] to_logs If false, certain validation errors will be sent to
+ * stderr rather than logged
*
- * \note Only emits warnings about enter/leave phases in case of issues.
+ * \return XML result of schema transforms if successful, otherwise NULL
*/
static xmlNode *
-apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
+apply_upgrade(const xmlNode *original_xml, int schema_index, gboolean to_logs)
{
- bool transform_onleave = schema->transform_onleave;
+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
+ pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
+ schema_index + 1);
+ bool transform_onleave = false;
char *transform_leave;
- xmlNode *upgrade = NULL,
- *final = NULL;
+ const xmlNode *xml = original_xml;
+ xmlNode *upgrade = NULL;
+ xmlNode *final = NULL;
+ xmlRelaxNGValidityErrorFunc error_handler = NULL;
- if (schema->transform_enter) {
- crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
- schema->name, schema->transform_enter);
+ CRM_ASSERT((schema != NULL) && (upgraded_schema != NULL));
+
+ if (to_logs) {
+ error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
+ }
+
+ transform_onleave = schema->transform_onleave;
+ if (schema->transform_enter != NULL) {
+ crm_debug("Upgrading schema from %s to %s: "
+ "applying pre-upgrade XSL transform %s",
+ schema->name, upgraded_schema->name, schema->transform_enter);
upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
if (upgrade == NULL) {
- crm_warn("Upgrade-enter transformation %s.xsl failed",
+ crm_warn("Pre-upgrade XSL transform %s failed, "
+ "will skip post-upgrade transform",
schema->transform_enter);
transform_onleave = FALSE;
+ } else {
+ xml = upgrade;
}
}
- if (upgrade == NULL) {
- upgrade = xml;
- }
- crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
- schema->name, schema->transform);
- final = apply_transformation(upgrade, schema->transform, to_logs);
+
+ crm_debug("Upgrading schema from %s to %s: "
+ "applying upgrade XSL transform %s",
+ schema->name, upgraded_schema->name, schema->transform);
+ final = apply_transformation(xml, schema->transform, to_logs);
if (upgrade != xml) {
free_xml(upgrade);
upgrade = NULL;
}
- if (final != NULL && transform_onleave) {
+ if ((final != NULL) && transform_onleave) {
upgrade = final;
/* following condition ensured in add_schema_by_version */
CRM_ASSERT(schema->transform_enter != NULL);
transform_leave = strdup(schema->transform_enter);
/* enter -> leave */
memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
- crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
- schema->name, transform_leave);
+ crm_debug("Upgrading schema from %s to %s: "
+ "applying post-upgrade XSL transform %s",
+ schema->name, upgraded_schema->name, transform_leave);
final = apply_transformation(upgrade, transform_leave, to_logs);
if (final == NULL) {
- crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
+ crm_warn("Ignoring failure of post-upgrade XSL transform %s",
+ transform_leave);
final = upgrade;
} else {
free_xml(upgrade);
@@ -951,290 +1083,661 @@ apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
free(transform_leave);
}
- return final;
-}
+ if (final == NULL) {
+ return NULL;
+ }
-const char *
-get_schema_name(int version)
-{
- if (version < 0 || version >= xml_schema_max) {
- return "unknown";
+ // Ensure result validates with its new schema
+ if (!validate_with(final, upgraded_schema, error_handler,
+ GUINT_TO_POINTER(LOG_ERR))) {
+ crm_err("Schema upgrade from %s to %s failed: "
+ "XSL transform %s produced an invalid configuration",
+ schema->name, upgraded_schema->name, schema->transform);
+ crm_log_xml_debug(final, "bad-transform-result");
+ free_xml(final);
+ return NULL;
}
- return known_schemas[version].name;
+
+ crm_info("Schema upgrade from %s to %s succeeded",
+ schema->name, upgraded_schema->name);
+ return final;
}
-int
-get_schema_version(const char *name)
+/*!
+ * \internal
+ * \brief Get the schema list entry corresponding to XML configuration
+ *
+ * \param[in] xml CIB XML to check
+ *
+ * \return List entry of schema configured in \p xml
+ */
+static GList *
+get_configured_schema(const xmlNode *xml)
{
- int lpc = 0;
+ const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
- if (name == NULL) {
- name = PCMK__VALUE_NONE;
+ pcmk__warn_if_schema_deprecated(schema_name);
+ if (schema_name == NULL) {
+ return NULL;
}
- for (; lpc < xml_schema_max; lpc++) {
- if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
- return lpc;
- }
- }
- return -1;
+ return pcmk__get_schema(schema_name);
}
-/* set which validation to use */
+/*!
+ * \brief Update CIB XML to latest schema that validates it
+ *
+ * \param[in,out] xml XML to update (may be freed and replaced
+ * after being transformed)
+ * \param[in] max_schema_name If not NULL, do not update \p xml to any
+ * schema later than this one
+ * \param[in] transform If false, do not update \p xml to any schema
+ * that requires an XSL transform
+ * \param[in] to_logs If false, certain validation errors will be
+ * sent to stderr rather than logged
+ *
+ * \return Standard Pacemaker return code
+ */
int
-update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
- gboolean to_logs)
+pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
+ bool to_logs)
{
- xmlNode *xml = NULL;
- char *value = NULL;
int max_stable_schemas = xml_latest_schema_index();
- int lpc = 0, match = -1, rc = pcmk_ok;
- int next = -1; /* -1 denotes "inactive" value */
+ int max_schema_index = 0;
+ int rc = pcmk_rc_ok;
+ GList *entry = NULL;
+ pcmk__schema_t *best_schema = NULL;
+ pcmk__schema_t *original_schema = NULL;
xmlRelaxNGValidityErrorFunc error_handler =
to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
- CRM_CHECK(best != NULL, return -EINVAL);
- *best = 0;
-
- CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
- && ((*xml_blob)->doc != NULL),
- return -EINVAL);
-
- xml = *xml_blob;
- value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
+ CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
+ return EINVAL);
- if (value != NULL) {
- match = get_schema_version(value);
+ if (max_schema_name != NULL) {
+ GList *max_entry = pcmk__get_schema(max_schema_name);
- lpc = match;
- if (lpc >= 0 && transform == FALSE) {
- *best = lpc++;
+ if (max_entry != NULL) {
+ pcmk__schema_t *max_schema = max_entry->data;
- } else if (lpc < 0) {
- crm_debug("Unknown validation schema");
- lpc = 0;
+ max_schema_index = max_schema->schema_index;
}
}
+ if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
+ max_schema_index = max_stable_schemas;
+ }
- if (match >= max_stable_schemas) {
- /* nothing to do */
- free(value);
- *best = match;
- return pcmk_ok;
+ entry = get_configured_schema(*xml);
+ if (entry == NULL) {
+ // @COMPAT Not specifying a schema name is deprecated since 2.1.8
+ entry = known_schemas;
+ } else {
+ original_schema = entry->data;
+ if (original_schema->schema_index >= max_schema_index) {
+ return pcmk_rc_ok;
+ }
}
- while (lpc <= max_stable_schemas) {
- crm_debug("Testing '%s' validation (%d of %d)",
- known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
- lpc, max_stable_schemas);
+ for (; entry != NULL; entry = entry->next) {
+ pcmk__schema_t *current_schema = entry->data;
+ xmlNode *upgrade = NULL;
- if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
- if (next != -1) {
- crm_info("Configuration not valid for schema: %s",
- known_schemas[lpc].name);
- next = -1;
- } else {
- crm_trace("%s validation failed",
- known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
- }
- if (*best) {
+ if (current_schema->schema_index > max_schema_index) {
+ break;
+ }
+
+ if (!validate_with(*xml, current_schema, error_handler,
+ GUINT_TO_POINTER(LOG_ERR))) {
+ crm_debug("Schema %s does not validate", current_schema->name);
+ if (best_schema != NULL) {
/* we've satisfied the validation, no need to check further */
break;
}
- rc = -pcmk_err_schema_validation;
-
- } else {
- if (next != -1) {
- crm_debug("Configuration valid for schema: %s",
- known_schemas[next].name);
- next = -1;
- }
- rc = pcmk_ok;
+ rc = pcmk_rc_schema_validation;
+ continue; // Try again with the next higher schema
}
- if (rc == pcmk_ok) {
- *best = lpc;
+ crm_debug("Schema %s validates", current_schema->name);
+ rc = pcmk_rc_ok;
+ best_schema = current_schema;
+ if (current_schema->schema_index == max_schema_index) {
+ break; // No further transformations possible
}
- if (rc == pcmk_ok && transform) {
- xmlNode *upgrade = NULL;
- next = known_schemas[lpc].after_transform;
-
- if (next <= lpc) {
- /* There is no next version, or next would regress */
- crm_trace("Stopping at %s", known_schemas[lpc].name);
- break;
-
- } else if (max > 0 && (lpc == max || next > max)) {
- crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
- known_schemas[lpc].name, lpc, next, max);
- break;
-
- } else if (known_schemas[lpc].transform == NULL
- /* possibly avoid transforming when readily valid
- (in general more restricted when crossing the major
- version boundary, as X.0 "transitional" version is
- expected to be more strict than it's successors that
- may re-allow constructs from previous major line) */
- || validate_with_silent(xml, next)) {
- crm_debug("%s-style configuration is also valid for %s",
- known_schemas[lpc].name, known_schemas[next].name);
-
- lpc = next;
-
- } else {
- crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
- known_schemas[lpc].name, known_schemas[next].name,
- known_schemas[lpc].transform);
-
- upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
- if (upgrade == NULL) {
- crm_err("Transformation %s.xsl failed",
- known_schemas[lpc].transform);
- rc = -pcmk_err_transform_failed;
-
- } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) {
- crm_info("Transformation %s.xsl successful",
- known_schemas[lpc].transform);
- lpc = next;
- *best = next;
- free_xml(xml);
- xml = upgrade;
- rc = pcmk_ok;
-
- } else {
- crm_err("Transformation %s.xsl did not produce a valid configuration",
- known_schemas[lpc].transform);
- crm_log_xml_info(upgrade, "transform:bad");
- free_xml(upgrade);
- rc = -pcmk_err_schema_validation;
- }
- next = -1;
- }
+ if (!transform || (current_schema->transform == NULL)
+ || validate_with_silent(*xml, entry->next->data)) {
+ /* The next schema either doesn't require a transform or validates
+ * successfully even without the transform. Skip the transform and
+ * try the next schema with the same XML.
+ */
+ continue;
}
- if (transform == FALSE || rc != pcmk_ok) {
- /* we need some progress! */
- lpc++;
+ upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
+ if (upgrade == NULL) {
+ /* The transform failed, so this schema can't be used. Later
+ * schemas are unlikely to validate, but try anyway until we
+ * run out of options.
+ */
+ rc = pcmk_rc_transform_failed;
+ } else {
+ best_schema = current_schema;
+ free_xml(*xml);
+ *xml = upgrade;
}
}
- if (*best > match && *best) {
- crm_info("%s the configuration from %s to %s",
- transform?"Transformed":"Upgraded",
- value ? value : "<none>", known_schemas[*best].name);
- crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
+ if (best_schema != NULL) {
+ if ((original_schema == NULL)
+ || (best_schema->schema_index > original_schema->schema_index)) {
+ crm_info("%s the configuration schema to %s",
+ (transform? "Transformed" : "Upgraded"),
+ best_schema->name);
+ crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
+ }
}
-
- *xml_blob = xml;
- free(value);
return rc;
}
-gboolean
-cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
+/*!
+ * \brief Update XML from its configured schema to the latest major series
+ *
+ * \param[in,out] xml XML to update
+ * \param[in] to_logs If false, certain validation errors will be
+ * sent to stderr rather than logged
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk_update_configured_schema(xmlNode **xml, bool to_logs)
{
- gboolean rc = TRUE;
- const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
- char *const orig_value = strdup(value == NULL ? "(none)" : value);
+ int rc = pcmk_rc_ok;
+ char *original_schema_name = NULL;
+
+ // @COMPAT Not specifying a schema name is deprecated since 2.1.8
+ const char *effective_original_name = "the first";
+
+ int orig_version = -1;
+ pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
+ GList *entry = NULL;
+
+ CRM_CHECK(xml != NULL, return EINVAL);
- int version = get_schema_version(value);
- int orig_version = version;
- int min_version = xml_minimum_schema_index();
+ original_schema_name = crm_element_value_copy(*xml, PCMK_XA_VALIDATE_WITH);
+ pcmk__warn_if_schema_deprecated(original_schema_name);
+ entry = pcmk__get_schema(original_schema_name);
+ if (entry != NULL) {
+ pcmk__schema_t *original_schema = entry->data;
- if (version < min_version) {
+ effective_original_name = original_schema->name;
+ orig_version = original_schema->schema_index;
+ }
+
+ if (orig_version < x_0_schema->schema_index) {
// Current configuration schema is not acceptable, try to update
xmlNode *converted = NULL;
+ const char *new_schema_name = NULL;
+ pcmk__schema_t *schema = NULL;
+
+ entry = NULL;
+ converted = pcmk__xml_copy(NULL, *xml);
+ if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
+ new_schema_name = crm_element_value(converted,
+ PCMK_XA_VALIDATE_WITH);
+ entry = pcmk__get_schema(new_schema_name);
+ }
+ schema = (entry == NULL)? NULL : entry->data;
- converted = copy_xml(*xml);
- update_validation(&converted, &version, 0, TRUE, to_logs);
-
- value = crm_element_value(converted, XML_ATTR_VALIDATION);
- if (version < min_version) {
+ if ((schema == NULL)
+ || (schema->schema_index < x_0_schema->schema_index)) {
// Updated configuration schema is still not acceptable
- if (version < orig_version || orig_version == -1) {
+ if ((orig_version == -1) || (schema == NULL)
+ || (schema->schema_index < orig_version)) {
// We couldn't validate any schema at all
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
- "schema %s) to at least %s because it "
+ "%s schema) to at least %s because it "
"does not validate with any schema from "
- "%s to %s",
- orig_value,
- get_schema_name(min_version),
- get_schema_name(orig_version),
- xml_latest_schema());
+ "%s to the latest",
+ pcmk__s(original_schema_name, "no"),
+ x_0_schema->name, effective_original_name);
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
- "schema %s) to at least %s because it "
+ "%s schema) to at least %s because it "
"does not validate with any schema from "
- "%s to %s\n",
- orig_value,
- get_schema_name(min_version),
- get_schema_name(orig_version),
- xml_latest_schema());
+ "%s to the latest\n",
+ pcmk__s(original_schema_name, "no"),
+ x_0_schema->name, effective_original_name);
}
} else {
// We updated configuration successfully, but still too low
if (to_logs) {
pcmk__config_err("Cannot upgrade configuration (claiming "
- "schema %s) to at least %s because it "
+ "%s schema) to at least %s because it "
"would not upgrade past %s",
- orig_value,
- get_schema_name(min_version),
- pcmk__s(value, "unspecified version"));
+ pcmk__s(original_schema_name, "no"),
+ x_0_schema->name,
+ pcmk__s(new_schema_name, "unspecified version"));
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
- "schema %s) to at least %s because it "
+ "%s schema) to at least %s because it "
"would not upgrade past %s\n",
- orig_value,
- get_schema_name(min_version),
- pcmk__s(value, "unspecified version"));
+ pcmk__s(original_schema_name, "no"),
+ x_0_schema->name,
+ pcmk__s(new_schema_name, "unspecified version"));
}
}
free_xml(converted);
converted = NULL;
- rc = FALSE;
+ rc = pcmk_rc_transform_failed;
} else {
// Updated configuration schema is acceptable
free_xml(*xml);
*xml = converted;
- if (version < xml_latest_schema_index()) {
+ if (schema->schema_index < xml_latest_schema_index()) {
if (to_logs) {
- pcmk__config_warn("Configuration with schema %s was "
+ pcmk__config_warn("Configuration with %s schema was "
"internally upgraded to acceptable (but "
"not most recent) %s",
- orig_value, get_schema_name(version));
- }
- } else {
- if (to_logs) {
- crm_info("Configuration with schema %s was internally "
- "upgraded to latest version %s",
- orig_value, get_schema_name(version));
+ pcmk__s(original_schema_name, "no"),
+ schema->name);
}
+ } else if (to_logs) {
+ crm_info("Configuration with %s schema was internally "
+ "upgraded to latest version %s",
+ pcmk__s(original_schema_name, "no"),
+ schema->name);
}
}
- } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
- // Schema validation is disabled
- if (to_logs) {
- pcmk__config_warn("Schema validation of configuration is disabled "
- "(enabling is encouraged and prevents common "
- "misconfigurations)");
+ } else {
+ // @COMPAT the none schema is deprecated since 2.1.8
+ pcmk__schema_t *none_schema = NULL;
+
+ entry = pcmk__get_schema(PCMK_VALUE_NONE);
+ CRM_ASSERT((entry != NULL) && (entry->data != NULL));
+
+ none_schema = entry->data;
+ if (!to_logs && (orig_version >= none_schema->schema_index)) {
+ fprintf(stderr, "Schema validation of configuration is "
+ "disabled (support for " PCMK_XA_VALIDATE_WITH
+ " set to \"" PCMK_VALUE_NONE "\" is deprecated"
+ " and will be removed in a future release)\n");
+ }
+ }
- } else {
- fprintf(stderr, "Schema validation of configuration is disabled "
- "(enabling is encouraged and prevents common "
- "misconfigurations)\n");
+ free(original_schema_name);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Return a list of all schema files and any associated XSLT files
+ * later than the given one
+ * \brief Return a list of all schema versions later than the given one
+ *
+ * \param[in] schema The schema to compare against (for example,
+ * "pacemaker-3.1.rng" or "pacemaker-3.1")
+ *
+ * \note The caller is responsible for freeing both the returned list and
+ * the elements of the list
+ */
+GList *
+pcmk__schema_files_later_than(const char *name)
+{
+ GList *lst = NULL;
+ pcmk__schema_version_t ver;
+
+ if (!version_from_filename(name, &ver)) {
+ return lst;
+ }
+
+ for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
+ iter != NULL; iter = iter->prev) {
+ pcmk__schema_t *schema = iter->data;
+ char *s = NULL;
+
+ if (schema_cmp(ver, schema->version) != -1) {
+ continue;
+ }
+
+ s = crm_strdup_printf("%s.rng", schema->name);
+ lst = g_list_prepend(lst, s);
+
+ if (schema->transform != NULL) {
+ char *xform = crm_strdup_printf("%s.xsl", schema->transform);
+ lst = g_list_prepend(lst, xform);
+ }
+
+ if (schema->transform_enter != NULL) {
+ char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter);
+
+ lst = g_list_prepend(lst, enter);
+
+ if (schema->transform_onleave) {
+ int last_dash = strrchr(enter, '-') - enter;
+ char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter);
+
+ lst = g_list_prepend(lst, leave);
+ }
}
}
- if (best_version) {
- *best_version = version;
+ return lst;
+}
+
+static void
+append_href(xmlNode *xml, void *user_data)
+{
+ GList **list = user_data;
+ char *href = crm_element_value_copy(xml, "href");
+
+ if (href == NULL) {
+ return;
}
+ *list = g_list_prepend(*list, href);
+}
+
+static void
+external_refs_in_schema(GList **list, const char *contents)
+{
+ /* local-name()= is needed to ignore the xmlns= setting at the top of
+ * the XML file. Otherwise, the xpath query will always return nothing.
+ */
+ const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
+ xmlNode *xml = pcmk__xml_parse(contents);
- free(orig_value);
+ crm_foreach_xpath_result(xml, search, append_href, list);
+ free_xml(xml);
+}
+
+static int
+read_file_contents(const char *file, char **contents)
+{
+ int rc = pcmk_rc_ok;
+ char *path = NULL;
+
+ if (pcmk__ends_with(file, ".rng")) {
+ path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
+ } else {
+ path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
+ }
+
+ rc = pcmk__file_contents(path, contents);
+
+ free(path);
return rc;
}
+
+static void
+add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
+{
+ char *contents = NULL;
+ char *path = NULL;
+ xmlNode *file_node = NULL;
+ GList *includes = NULL;
+ int rc = pcmk_rc_ok;
+
+ /* If we already included this file, don't do so again. */
+ if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
+ return;
+ }
+
+ /* Ensure whatever file we were given has a suffix we know about. If not,
+ * just assume it's an RNG file.
+ */
+ if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
+ path = crm_strdup_printf("%s.rng", file);
+ } else {
+ path = pcmk__str_copy(file);
+ }
+
+ rc = read_file_contents(path, &contents);
+ if (rc != pcmk_rc_ok || contents == NULL) {
+ crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
+ free(path);
+ return;
+ }
+
+ /* Create a new <file path="..."> node with the contents of the file
+ * as a CDATA block underneath it.
+ */
+ file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
+ crm_xml_add(file_node, PCMK_XA_PATH, path);
+ *already_included = g_list_prepend(*already_included, path);
+
+ xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
+ strlen(contents)));
+
+ /* Scan the file for any <externalRef> or <include> nodes and build up
+ * a list of the files they reference.
+ */
+ external_refs_in_schema(&includes, contents);
+
+ /* For each referenced file, recurse to add it (and potentially anything it
+ * references, ...) to the XML.
+ */
+ for (GList *iter = includes; iter != NULL; iter = iter->next) {
+ add_schema_file_to_xml(parent, iter->data, already_included);
+ }
+
+ free(contents);
+ g_list_free_full(includes, free);
+}
+
+/*!
+ * \internal
+ * \brief Add an XML schema file and all the files it references as children
+ * of a given XML node
+ *
+ * \param[in,out] parent The parent XML node
+ * \param[in] name The schema version to compare against
+ * (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
+ * \param[in,out] already_included A list of names that have already been added
+ * to the parent node.
+ *
+ * \note The caller is responsible for freeing both the returned list and
+ * the elements of the list
+ */
+void
+pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
+{
+ xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
+
+ crm_xml_add(schema_node, PCMK_XA_VERSION, name);
+ add_schema_file_to_xml(schema_node, name, already_included);
+
+ if (schema_node->children == NULL) {
+ // Not needed if empty. May happen if name was invalid, for example.
+ free_xml(schema_node);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Return the directory containing any extra schema files that a
+ * Pacemaker Remote node fetched from the cluster
+ */
+const char *
+pcmk__remote_schema_dir(void)
+{
+ const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
+
+ if (pcmk__str_empty(dir)) {
+ return PCMK__REMOTE_SCHEMA_DIR;
+ }
+
+ return dir;
+}
+
+/*!
+ * \internal
+ * \brief Warn if a given validation schema is deprecated
+ *
+ * \param[in] Schema name to check
+ */
+void
+pcmk__warn_if_schema_deprecated(const char *schema)
+{
+ if ((schema == NULL) ||
+ pcmk__strcase_any_of(schema, "pacemaker-next", PCMK_VALUE_NONE, NULL)) {
+ pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
+ "deprecated and will be removed in a future release "
+ "without the possibility of upgrades (manually edit "
+ "to use a supported schema)", pcmk__s(schema, ""));
+ }
+}
+
+// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
+
+#include <crm/common/xml_compat.h>
+
+const char *
+xml_latest_schema(void)
+{
+ return pcmk__highest_schema_name();
+}
+
+const char *
+get_schema_name(int version)
+{
+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
+
+ return (schema != NULL)? schema->name : "unknown";
+}
+
+int
+get_schema_version(const char *name)
+{
+ int lpc = 0;
+
+ if (name == NULL) {
+ name = PCMK_VALUE_NONE;
+ }
+
+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
+ pcmk__schema_t *schema = iter->data;
+
+ if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
+ return lpc;
+ }
+
+ lpc++;
+ }
+
+ return -1;
+}
+
+int
+update_validation(xmlNode **xml, int *best, int max, gboolean transform,
+ gboolean to_logs)
+{
+ int rc = pcmk__update_schema(xml, get_schema_name(max), transform, to_logs);
+
+ if ((best != NULL) && (xml != NULL) && (rc == pcmk_rc_ok)) {
+ const char *schema_name = crm_element_value(*xml,
+ PCMK_XA_VALIDATE_WITH);
+ GList *schema_entry = pcmk__get_schema(schema_name);
+
+ if (schema_entry != NULL) {
+ *best = ((pcmk__schema_t *)(schema_entry->data))->schema_index;
+ }
+ }
+
+ return pcmk_rc2legacy(rc);
+}
+
+gboolean
+validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
+{
+ bool rc = pcmk__validate_xml(xml_blob, validation,
+ to_logs? (xmlRelaxNGValidityErrorFunc) xml_log : NULL,
+ GUINT_TO_POINTER(LOG_ERR));
+ return rc? TRUE : FALSE;
+}
+
+static void
+dump_file(const char *filename)
+{
+
+ FILE *fp = NULL;
+ int ch, line = 0;
+
+ CRM_CHECK(filename != NULL, return);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ crm_perror(LOG_ERR, "Could not open %s for reading", filename);
+ return;
+ }
+
+ fprintf(stderr, "%4d ", ++line);
+ do {
+ ch = getc(fp);
+ if (ch == EOF) {
+ putc('\n', stderr);
+ break;
+ } else if (ch == '\n') {
+ fprintf(stderr, "\n%4d ", ++line);
+ } else {
+ putc(ch, stderr);
+ }
+ } while (1);
+
+ fclose(fp);
+}
+
+gboolean
+validate_xml_verbose(const xmlNode *xml_blob)
+{
+ int fd = 0;
+ xmlDoc *doc = NULL;
+ xmlNode *xml = NULL;
+ gboolean rc = FALSE;
+ char *filename = NULL;
+
+ filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
+
+ umask(S_IWGRP | S_IWOTH | S_IROTH);
+ fd = mkstemp(filename);
+ pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
+
+ dump_file(filename);
+
+ doc = xmlReadFile(filename, NULL, 0);
+ xml = xmlDocGetRootElement(doc);
+ rc = pcmk__validate_xml(xml, NULL, NULL, NULL);
+ free_xml(xml);
+
+ unlink(filename);
+ free(filename);
+
+ return rc? TRUE : FALSE;
+}
+
+gboolean
+cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
+{
+ int rc = pcmk_update_configured_schema(xml, to_logs);
+
+ if (best_version != NULL) {
+ const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
+
+ if (name == NULL) {
+ *best_version = -1;
+ } else {
+ GList *entry = pcmk__get_schema(name);
+ pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
+
+ *best_version = (schema == NULL)? -1 : schema->schema_index;
+ }
+ }
+ return (rc == pcmk_rc_ok)? TRUE: FALSE;
+}
+
+// LCOV_EXCL_STOP
+// End deprecated API