Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F1841576
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
64 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/common/schemas.c b/lib/common/schemas.c
index 7f6092e051..88a305132c 100644
--- a/lib/common/schemas.c
+++ b/lib/common/schemas.c
@@ -1,1294 +1,1303 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <libxml/relaxng.h>
#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#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;
#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])
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
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 bool silent_logging = FALSE;
static void
xml_log(int priority, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
static void
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (silent_logging == FALSE) {
/* XXX should not this enable dechunking as well? */
PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
}
va_end(ap);
}
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"
}
static int
xml_minimum_schema_index(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;
}
const char *
xml_latest_schema(void)
{
return get_schema_name(xml_latest_schema_index());
}
static inline bool
version_from_filename(const char *filename, schema_version_t *version)
{
int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
return (rc == 2);
}
static int
schema_filter(const struct dirent *a)
{
int rc = 0;
schema_version_t version = SCHEMA_ZERO;
if (strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
} else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
/* crm_trace("%s - wrong suffix", a->d_name); */
} else if (!version_from_filename(a->d_name, &version)) {
/* crm_trace("%s - wrong format", a->d_name); */
} else {
/* crm_debug("%s - candidate", a->d_name); */
rc = 1;
}
return rc;
}
static int
schema_sort(const struct dirent **a, const struct dirent **b)
{
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;
} else if (a_version.v[i] > b_version.v[i]) {
return 1;
}
}
return 0;
}
/*!
* \internal
* \brief Add given schema + auxiliary data to internal bookkeeping.
*
* \note When providing \p version, should not be called directly but
* through \c add_schema_by_version.
*/
static void
add_schema(enum schema_validator_e validator, const schema_version_t *version,
const char *name, const char *transform,
const char *transform_enter, bool transform_onleave,
int after_transform)
{
int last = xml_schema_max;
bool have_version = FALSE;
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;
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, "");
} else {
CRM_ASSERT(name);
schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
known_schemas[last].name = strdup(name);
}
if (transform) {
known_schemas[last].transform = strdup(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 */
}
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);
}
}
/*!
* \internal
* \brief Add version-specified schema + auxiliary data to internal bookkeeping.
* \return Standard Pacemaker return value (the only possible values are
* \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
*
* \note There's no reliance on the particular order of schemas entering here.
*
* \par A bit of theory
* We track 3 XSLT stylesheets that differ per usage:
* - "upgrade":
* . sparsely spread over the sequence of all available schemas,
* as they are only relevant when major version of the schema
* is getting bumped -- in that case, it MUST be set
* . name convention: upgrade-X.Y.xsl
* - "upgrade-enter":
* . may only accompany "upgrade" occurrence, but doesn't need to
* be present anytime such one is, i.e., it MAY not be set when
* "upgrade" is
* . name convention: upgrade-X.Y-enter.xsl,
* when not present: upgrade-enter.xsl
* - "upgrade-leave":
* . like "upgrade-enter", but SHOULD be present whenever
* "upgrade-enter" is (and vice versa, but that's only
* to prevent confusion based on observing the files,
* it would get ignored regardless)
* . name convention: (see "upgrade-enter")
*/
static int
add_schema_by_version(const schema_version_t *version, int next,
bool transform_expected)
{
bool transform_onleave = FALSE;
int rc = pcmk_rc_ok;
struct stat s;
char *xslt = NULL,
*transform_upgrade = NULL,
*transform_enter = NULL;
/* prologue for further transform_expected handling */
if (transform_expected) {
/* check if there's suitable "upgrade" stylesheet */
transform_upgrade = schema_strdup_printf("upgrade-", *version, );
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_upgrade);
}
if (!transform_expected) {
/* jump directly to the end */
} else if (stat(xslt, &s) == 0) {
/* perhaps there's also a targeted "upgrade-enter" stylesheet */
transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
free(xslt);
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
/* or initially, at least a generic one */
crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
free(xslt);
free(transform_enter);
transform_enter = strdup("upgrade-enter");
xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform_enter);
if (stat(xslt, &s) != 0) {
crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
free(xslt);
xslt = NULL;
}
}
/* xslt contains full path to "upgrade-enter" stylesheet */
if (xslt != NULL) {
/* then there should be "upgrade-leave" counterpart (enter->leave) */
memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
transform_onleave = (stat(xslt, &s) == 0);
free(xslt);
} else {
free(transform_enter);
transform_enter = NULL;
}
} else {
crm_err("Upgrade transform %s not found", xslt);
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);
free(transform_upgrade);
free(transform_enter);
return rc;
}
static void
wrap_libxslt(bool finalize)
{
static xsltSecurityPrefsPtr secprefs;
int ret = 0;
/* security framework preferences */
if (!finalize) {
CRM_ASSERT(secprefs == NULL);
secprefs = xsltNewSecurityPrefs();
ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
xsltSecurityForbid)
| xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
xsltSecurityForbid);
if (ret != 0) {
return;
}
} else {
xsltFreeSecurityPrefs(secprefs);
secprefs = NULL;
}
/* cleanup only */
if (finalize) {
xsltCleanupGlobals();
}
}
/*!
* \internal
* \brief Load pacemaker schemas into cache
*
* \note This currently also serves as an entry point for the
* generic initialization of the libxslt library.
*/
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;
wrap_libxslt(false);
max = scandir(base, &namelist, schema_filter, schema_sort);
if (max < 0) {
crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
free(base);
} else {
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;
}
} else {
next = -1;
}
if (add_schema_by_version(&version, next, transform_expected)
== ENOENT) {
break;
}
}
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);
}
#if 0
static void
relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
{
/*
Structure xmlError
struct _xmlError {
int domain : What part of the library raised this er
int code : The error code, e.g. an xmlParserError
char * message : human-readable informative error messag
xmlErrorLevel level : how consequent is the error
char * file : the filename
int line : the line number if available
char * str1 : extra string information
char * str2 : extra string information
char * str3 : extra string information
int int1 : extra number information
int int2 : column number of the error or 0 if N/A
void * ctxt : the parser context if available
void * node : the node in the tree
}
*/
crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
}
#endif
static gboolean
validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
relaxng_ctx_cache_t **cached_ctx)
{
int rc = 0;
gboolean valid = TRUE;
relaxng_ctx_cache_t *ctx = NULL;
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));
xmlLoadExtDtdDefaultValue = 1;
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL,
crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf,
stderr);
}
}
/* xmlRelaxNGSetValidStructuredErrors( */
/* valid, relaxng_invalid_stderr, valid); */
xmlLineNumbersDefault(1);
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
valid = FALSE;
} else if (rc < 0) {
crm_err("Internal libxml error during validation");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
/*!
* \internal
* \brief Clean up global memory associated with XML schemas
*/
void
crm_schema_cleanup(void)
{
int lpc;
relaxng_ctx_cache_t *ctx = NULL;
for (lpc = 0; lpc < xml_schema_max; lpc++) {
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;
}
free(known_schemas[lpc].name);
free(known_schemas[lpc].transform);
free(known_schemas[lpc].transform_enter);
}
free(known_schemas);
known_schemas = NULL;
wrap_libxslt(true);
}
static gboolean
validate_with(xmlNode *xml, int method, gboolean to_logs)
{
xmlDocPtr doc = NULL;
gboolean valid = FALSE;
char *file = NULL;
if (method < 0) {
return FALSE;
}
if (known_schemas[method].validator == schema_validator_none) {
return TRUE;
}
CRM_CHECK(xml != NULL, return FALSE);
+
+ if (pcmk__str_eq(known_schemas[method].name, "pacemaker-next",
+ pcmk__str_none)) {
+ crm_warn("The pacemaker-next schema is deprecated and will be removed "
+ "in a future release.");
+ }
+
doc = getDocPtr(xml);
file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
known_schemas[method].name);
crm_trace("Validating with %s (type=%d)",
pcmk__s(file, "missing schema"), known_schemas[method].validator);
switch (known_schemas[method].validator) {
case schema_validator_rng:
valid =
validate_with_relaxng(doc, to_logs, file,
(relaxng_ctx_cache_t **) & (known_schemas[method].cache));
break;
default:
crm_err("Unknown validator type: %d",
known_schemas[method].validator);
break;
}
free(file);
return valid;
}
static bool
validate_with_silent(xmlNode *xml, int method)
{
bool rc, sl_backup = silent_logging;
silent_logging = TRUE;
rc = validate_with(xml, method, TRUE);
silent_logging = sl_backup;
return rc;
}
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(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 = xmlParseFile(filename);
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)
{
int version = 0;
if (validation == NULL) {
validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
}
if (validation == NULL) {
int lpc = 0;
bool valid = FALSE;
for (lpc = 0; lpc < xml_schema_max; lpc++) {
if (validate_with(xml_blob, lpc, FALSE)) {
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;
}
}
}
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, to_logs);
}
crm_err("Unknown validator: %s", validation);
return FALSE;
}
static void
cib_upgrade_err(void *ctx, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
/* With this arrangement, an attempt to identify the message severity
as explicitly signalled directly from XSLT is performed in rather
a smart way (no reliance on formatting string + arguments being
always specified as ["%s", purposeful_string], as it can also be
["%s: %s", some_prefix, purposeful_string] etc. so every argument
pertaining %s specifier is investigated), and if such a mark found,
the respective level is determined and, when the messages are to go
to the native logs, the mark itself gets dropped
(by the means of string shift).
NOTE: whether the native logging is the right sink is decided per
the ctx parameter -- NULL denotes this case, otherwise it
carries a pointer to the numeric expression of the desired
target logging level (messages with higher level will be
suppressed)
NOTE: on some architectures, this string shift may not have any
effect, but that's an acceptable tradeoff
The logging level for not explicitly designated messages
(suspicious, likely internal errors or some runaways) is
LOG_WARNING.
*/
static void
cib_upgrade_err(void *ctx, const char *fmt, ...)
{
va_list ap, aq;
char *arg_cur;
bool found = FALSE;
const char *fmt_iter = fmt;
uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
const unsigned * log_level = (const unsigned *) ctx;
enum {
escan_seennothing,
escan_seenpercent,
} scan_state = escan_seennothing;
va_start(ap, fmt);
va_copy(aq, ap);
while (!found && *fmt_iter != '\0') {
/* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
switch (*fmt_iter++) {
case '%':
if (scan_state == escan_seennothing) {
scan_state = escan_seenpercent;
} else if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
}
break;
case 's':
if (scan_state == escan_seenpercent) {
scan_state = escan_seennothing;
arg_cur = va_arg(aq, char *);
if (arg_cur != NULL) {
switch (arg_cur[0]) {
case 'W':
if (!strncmp(arg_cur, "WARNING: ",
sizeof("WARNING: ") - 1)) {
msg_log_level = LOG_WARNING;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
}
found = TRUE;
break;
case 'I':
if (!strncmp(arg_cur, "INFO: ",
sizeof("INFO: ") - 1)) {
msg_log_level = LOG_INFO;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
}
found = TRUE;
break;
case 'D':
if (!strncmp(arg_cur, "DEBUG: ",
sizeof("DEBUG: ") - 1)) {
msg_log_level = LOG_DEBUG;
}
if (ctx == NULL) {
memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
}
found = TRUE;
break;
}
}
}
break;
case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '*':
break;
case 'l':
case 'z':
case 't':
case 'j':
case 'd': case 'i':
case 'o':
case 'u':
case 'x': case 'X':
case 'e': case 'E':
case 'f': case 'F':
case 'g': case 'G':
case 'a': case 'A':
case 'c':
case 'p':
if (scan_state == escan_seenpercent) {
(void) va_arg(aq, void *); /* skip forward */
scan_state = escan_seennothing;
}
break;
default:
scan_state = escan_seennothing;
break;
}
}
if (log_level != NULL) {
/* intention of the following offset is:
cibadmin -V -> start showing INFO labelled messages */
if (*log_level + 4 >= msg_log_level) {
vfprintf(stderr, fmt, ap);
}
} else {
PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
}
va_end(aq);
va_end(ap);
}
/* Denotes temporary emergency fix for "xmldiff'ing not text-node-ready";
proper fix is most likely to teach __xml_diff_object and friends to
deal with XML_TEXT_NODE (and more?), i.e., those nodes currently
missing "_private" field (implicitly as NULL) which clashes with
unchecked accesses (e.g. in __xml_offset) -- the outcome may be that
those unexpected XML nodes will simply be ignored for the purpose of
diff'ing, or it may be made more robust, or per the user's preference
(which then may be exposed as crm_diff switch).
Said XML_TEXT_NODE may appear unexpectedly due to how upgrade-2.10.xsl
is arranged.
The emergency fix is simple: reparse XSLT output with blank-ignoring
parser. */
#ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
#define PCMK_SCHEMAS_EMERGENCY_XSLT 1
#endif
static xmlNode *
apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xmlDocPtr doc = NULL;
xsltStylesheet *xslt = NULL;
#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
xmlChar *emergency_result;
int emergency_txt_len;
int emergency_res;
#endif
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
transform);
xmlLoadExtDtdDefaultValue = 1;
xmlSubstituteEntitiesDefault(1);
/* for capturing, e.g., what's emitted via <xsl:message> */
if (to_logs) {
xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
} else {
xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
}
xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
emergency_res = xsltSaveResultToString(&emergency_result,
&emergency_txt_len, res, xslt);
xmlFreeDoc(res);
CRM_CHECK(emergency_res == 0, goto cleanup);
out = string2xml((const char *) emergency_result);
free(emergency_result);
#else
out = xmlDocGetRootElement(res);
#endif
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
/*!
* \internal
* \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
*
* \note Only emits warnings about enter/leave phases in case of issues.
*/
static xmlNode *
apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
{
bool transform_onleave = schema->transform_onleave;
char *transform_leave;
xmlNode *upgrade = NULL,
*final = NULL;
if (schema->transform_enter) {
crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
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",
schema->transform_enter);
transform_onleave = FALSE;
}
}
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);
if (upgrade != xml) {
free_xml(upgrade);
upgrade = NULL;
}
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);
final = apply_transformation(upgrade, transform_leave, to_logs);
if (final == NULL) {
crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
final = upgrade;
} else {
free_xml(upgrade);
}
free(transform_leave);
}
return final;
}
const char *
get_schema_name(int version)
{
if (version < 0 || version >= xml_schema_max) {
return "unknown";
}
return known_schemas[version].name;
}
int
get_schema_version(const char *name)
{
int lpc = 0;
if (name == NULL) {
name = PCMK__VALUE_NONE;
}
for (; lpc < xml_schema_max; lpc++) {
if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
return lpc;
}
}
return -1;
}
/* set which validation to use */
int
update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
gboolean 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 */
CRM_CHECK(best != NULL, return -EINVAL);
*best = 0;
CRM_CHECK(xml_blob != NULL, return -EINVAL);
CRM_CHECK(*xml_blob != NULL, return -EINVAL);
xml = *xml_blob;
value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
if (value != NULL) {
match = get_schema_version(value);
lpc = match;
if (lpc >= 0 && transform == FALSE) {
*best = lpc++;
} else if (lpc < 0) {
crm_debug("Unknown validation schema");
lpc = 0;
}
}
if (match >= max_stable_schemas) {
/* nothing to do */
free(value);
*best = match;
return pcmk_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);
if (validate_with(xml, lpc, to_logs) == 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) {
/* 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;
}
if (rc == pcmk_ok) {
*best = lpc;
}
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, to_logs)) {
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 == FALSE || rc != pcmk_ok) {
/* we need some progress! */
lpc++;
}
}
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);
}
*xml_blob = xml;
free(value);
return rc;
}
gboolean
cli_config_update(xmlNode **xml, int *best_version, gboolean 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 version = get_schema_version(value);
int orig_version = version;
int min_version = xml_minimum_schema_index();
if (version < min_version) {
// Current configuration schema is not acceptable, try to update
xmlNode *converted = NULL;
converted = copy_xml(*xml);
update_validation(&converted, &version, 0, TRUE, to_logs);
value = crm_element_value(converted, XML_ATTR_VALIDATION);
if (version < min_version) {
// Updated configuration schema is still not acceptable
if (version < orig_version || orig_version == -1) {
// 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 "
"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());
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"schema %s) 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());
}
} 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 "
"would not upgrade past %s",
orig_value,
get_schema_name(min_version),
pcmk__s(value, "unspecified version"));
} else {
fprintf(stderr, "Cannot upgrade configuration (claiming "
"schema %s) to at least %s because it "
"would not upgrade past %s\n",
orig_value,
get_schema_name(min_version),
pcmk__s(value, "unspecified version"));
}
}
free_xml(converted);
converted = NULL;
rc = FALSE;
} else {
// Updated configuration schema is acceptable
free_xml(*xml);
*xml = converted;
if (version < xml_latest_schema_index()) {
if (to_logs) {
pcmk__config_warn("Configuration with schema %s 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));
}
}
}
} 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 {
fprintf(stderr, "Schema validation of configuration is disabled "
"(enabling is encouraged and prevents common "
"misconfigurations)\n");
}
}
if (best_version) {
*best_version = version;
}
free(orig_value);
return rc;
}
diff --git a/xml/Makefile.am b/xml/Makefile.am
index 67fff27707..999d5d9a51 100644
--- a/xml/Makefile.am
+++ b/xml/Makefile.am
@@ -1,313 +1,315 @@
#
# Copyright 2004-2022 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
include $(top_srcdir)/mk/common.mk
noarch_pkgconfig_DATA = $(builddir)/pacemaker-schemas.pc
# Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line
# tool XML output), and a legacy schema for crm_mon --as-xml.
#
# See README.md for details on updating CIB schema files (API is similar)
# The CIB and crm_mon schemas are installed directly in CRM_SCHEMA_DIRECTORY
# for historical reasons, while the API schema is installed in a subdirectory.
APIdir = $(CRM_SCHEMA_DIRECTORY)/api
CIBdir = $(CRM_SCHEMA_DIRECTORY)
MONdir = $(CRM_SCHEMA_DIRECTORY)
basexsltdir = $(CRM_SCHEMA_DIRECTORY)/base
dist_basexslt_DATA = $(srcdir)/base/access-render-2.xsl
# Extract a sorted list of available numeric schema versions
# from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng
numeric_versions = $(shell ls -1 $(1) \
| sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \
| sort -u -t. -k 1,1n -k 2,2n -k 3,3n)
+# @COMPAT: pacemaker-next is deprecated since 2.1.5
version_pairs = $(join \
$(1),$(addprefix \
-,$(wordlist \
2,$(words $(1)),$(1) \
) next \
) \
)
version_pairs_last = $(wordlist \
$(words \
$(wordlist \
2,$(1),$(2) \
) \
),$(1),$(2) \
)
# NOTE: All files in API_request_base, CIB_cfg_base, API_base, and CIB_base
# need to start with a unique prefix. These variables all get iterated over
# and globbed, and two files starting with the same prefix will cause
# problems.
# Names of API schemas that form the choices for pacemaker-result content
API_request_base = command-output \
crm_attribute \
crm_mon \
crm_resource \
crm_rule \
crm_simulate \
crmadmin \
digests \
pacemakerd \
stonith_admin \
version
# Names of CIB schemas that form the choices for cib/configuration content
CIB_cfg_base = options nodes resources constraints fencing acls tags alerts
# Names of all schemas (including top level and those included by others)
API_base = $(API_request_base) \
failure \
fence-event \
generic-list \
item \
node-attrs \
node-history \
nodes \
resources \
status \
subprocess-output
CIB_base = cib $(CIB_cfg_base) status score rule nvset
# Static schema files and transforms (only CIB has transforms)
#
# This is more complicated than it should be due to the need to support
# VPATH builds and "make distcheck". We need the absolute paths for reliable
# substitution back and forth, and relative paths for distributed files.
API_abs_files = $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)-*.rng))
CIB_abs_files = $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng))
CIB_abs_xsl = $(abs_srcdir)/upgrade-1.3.xsl \
$(abs_srcdir)/upgrade-2.10.xsl \
$(wildcard $(abs_srcdir)/upgrade-*enter.xsl) \
$(wildcard $(abs_srcdir)/upgrade-*leave.xsl)
MON_abs_files = $(abs_srcdir)/crm_mon.rng
API_files = $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)-*.rng))
CIB_files = $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng))
CIB_xsl = $(srcdir)/upgrade-1.3.xsl \
$(srcdir)/upgrade-2.10.xsl \
$(wildcard $(srcdir)/upgrade-*enter.xsl) \
$(wildcard $(srcdir)/upgrade-*leave.xsl)
MON_files = $(srcdir)/crm_mon.rng
# Sorted lists of all numeric schema versions
API_numeric_versions = $(call numeric_versions,${API_files})
CIB_numeric_versions = $(call numeric_versions,${CIB_files})
MON_numeric_versions = $(call numeric_versions,$(wildcard $(srcdir)/api/crm_mon*.rng))
# The highest numeric schema version
API_max ?= $(lastword $(API_numeric_versions))
CIB_max ?= $(lastword $(CIB_numeric_versions))
MON_max ?= $(lastword $(MON_numeric_versions))
# Sorted lists of all schema versions (including "next")
+# @COMPAT: pacemaker-next is deprecated since 2.1.5
API_versions = next $(API_numeric_versions)
CIB_versions = next $(CIB_numeric_versions)
# Build tree locations of static schema files and transforms (for VPATH builds)
API_build_copies = $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
CIB_build_copies = $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
MON_build_copies = $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f)))
# Dynamically generated schema files
API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng)
CIB_generated = pacemaker.rng $(foreach base,$(CIB_versions),pacemaker-$(base).rng) versions.rng
MON_generated = crm_mon.rng
CIB_version_pairs = $(call version_pairs,${CIB_numeric_versions})
CIB_version_pairs_cnt = $(words ${CIB_version_pairs})
CIB_version_pairs_last = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs})
dist_API_DATA = $(API_files)
dist_CIB_DATA = $(CIB_files) $(CIB_xsl)
nodist_API_DATA = $(API_generated)
nodist_CIB_DATA = $(CIB_generated)
nodist_MON_DATA = $(MON_generated)
EXTRA_DIST = README.md \
best-match.sh \
cibtr-2.rng \
context-of.xsl \
ocf-meta2man.xsl \
regression.sh \
upgrade-2.10-roundtrip.xsl \
upgrade-detail.xsl \
xslt_cibtr-2.rng \
assets \
test-2 \
test-2-enter \
test-2-leave \
test-2-roundtrip
cib-versions:
@echo "Max: $(CIB_max)"
@echo "Available: $(CIB_versions)"
api-versions:
@echo "Max: $(API_max)"
@echo "Available: $(API_versions)"
# Dynamically generated top-level API schema
api/api-result.rng: api/api-result-$(API_max).rng
$(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build
$(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@
api/api-result-%.rng: $(API_build_copies) best-match.sh Makefile.am
$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
$(AM_V_at)echo ' <start>' >> $@
$(AM_V_at)echo ' <element name="pacemaker-result">' >> $@
$(AM_V_at)echo ' <attribute name="api-version"> <text /> </attribute>' >> $@
$(AM_V_at)echo ' <attribute name="request"> <text /> </attribute>' >> $@
$(AM_V_at)echo ' <optional>' >> $@
$(AM_V_at)echo ' <choice>' >> $@
$(AM_V_at)for rng in $(API_request_base); do $(srcdir)/best-match.sh api/$$rng $(*) $(@) " " || :; done
$(AM_V_at)echo ' </choice>' >> $@
$(AM_V_at)echo ' </optional>' >> $@
$(AM_V_at)$(srcdir)/best-match.sh api/status $(*) $(@) " " || :
$(AM_V_at)echo ' </element>' >> $@
$(AM_V_at)echo ' </start>' >> $@
$(AM_V_SCHEMA)echo '</grammar>' >> $@
crm_mon.rng: api/crm_mon-$(MON_max).rng
$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0"' >> $@
$(AM_V_at)echo ' datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
$(AM_V_at)echo ' <start>' >> $@
$(AM_V_at)echo ' <ref name="element-crm_mon-old"/>' >> $@
$(AM_V_at)echo ' </start>' >> $@
$(AM_V_at)echo ' <define name="element-crm_mon-old">' >> $@
$(AM_V_at)echo ' <element name="crm_mon">' >> $@
$(AM_V_at)echo ' <attribute name="version"> <text/> </attribute>' >> $@
$(AM_V_at)echo ' <externalRef href="$(<)" />' >> $@
$(AM_V_at)echo ' </element>' >> $@
$(AM_V_at)echo ' </define>' >> $@
$(AM_V_SCHEMA)echo '</grammar>' >> $@
# Dynamically generated top-level CIB schema
pacemaker.rng: pacemaker-$(CIB_max).rng
$(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@
pacemaker-%.rng: $(CIB_build_copies) best-match.sh Makefile.am
$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
$(AM_V_at)echo ' <start>' >> $@
$(AM_V_at)echo ' <element name="cib">' >> $@
$(AM_V_at)$(srcdir)/best-match.sh cib $(*) $(@) " "
$(AM_V_at)echo ' <element name="configuration">' >> $@
$(AM_V_at)echo ' <interleave>' >> $@
$(AM_V_at)for rng in $(CIB_cfg_base); do $(srcdir)/best-match.sh $$rng $(*) $(@) " " || :; done
$(AM_V_at)echo ' </interleave>' >> $@
$(AM_V_at)echo ' </element>' >> $@
$(AM_V_at)echo ' <optional>' >> $@
$(AM_V_at)echo ' <element name="status">' >> $@
$(AM_V_at)$(srcdir)/best-match.sh status $(*) $(@) " "
$(AM_V_at)echo ' </element>' >> $@
$(AM_V_at)echo ' </optional>' >> $@
$(AM_V_at)echo ' </element>' >> $@
$(AM_V_at)echo ' </start>' >> $@
$(AM_V_SCHEMA)echo '</grammar>' >> $@
# Dynamically generated CIB schema listing all pacemaker versions
versions.rng: pacemaker-$(CIB_max).rng Makefile.am
$(AM_V_at)echo '<?xml version="1.0" encoding="UTF-8"?>' > $@
$(AM_V_at)echo '<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">' >> $@
$(AM_V_at)echo ' <start>' >> $@
$(AM_V_at)echo ' <interleave>' >> $@
$(AM_V_at)echo ' <optional>' >> $@
$(AM_V_at)echo ' <attribute name="validate-with">' >> $@
$(AM_V_at)echo ' <choice>' >> $@
$(AM_V_at)echo ' <value>none</value>' >> $@
$(AM_V_at)echo ' <value>pacemaker-0.6</value>' >> $@
$(AM_V_at)echo ' <value>transitional-0.6</value>' >> $@
$(AM_V_at)echo ' <value>pacemaker-0.7</value>' >> $@
$(AM_V_at)echo ' <value>pacemaker-1.1</value>' >> $@
$(AM_V_at)for rng in $(CIB_versions); do echo " <value>pacemaker-$$rng</value>" >> $@; done
$(AM_V_at)echo ' </choice>' >> $@
$(AM_V_at)echo ' </attribute>' >> $@
$(AM_V_at)echo ' </optional>' >> $@
$(AM_V_at)echo ' <attribute name="admin_epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
$(AM_V_at)echo ' <attribute name="epoch"><data type="nonNegativeInteger"/></attribute>' >> $@
$(AM_V_at)echo ' <attribute name="num_updates"><data type="nonNegativeInteger"/></attribute>' >> $@
$(AM_V_at)echo ' </interleave>' >> $@
$(AM_V_at)echo ' </start>' >> $@
$(AM_V_SCHEMA)echo '</grammar>' >> $@
# diff fails with ec=2 if no predecessor is found;
# this uses '=' GNU extension to sed, if that's not available,
# one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`;
# XXX: use line information from hunk to avoid "not detected" for ambiguity
version_diff = \
@for p in $(1); do \
set `echo "$${p}" | tr '-' ' '`; \
echo "\#\#\# *-$$2.rng vs. predecessor"; \
for v in *-$$2.rng; do \
echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \
old=`./best-match.sh $${b} $$1`; \
p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \
case $$? in \
1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \
-e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \
| while read hline; do \
read h && read i || break; \
iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \
ctxt="(not detected)"; \
if test `echo "$${iline}" | wc -l` -eq 1; then \
ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \
echo "<inject id=\"GOAL\"/>$${i}"; \
sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \
} | $(XSLTPROC) --param skip 1 context-of.xsl -`; \
fi; \
echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \
-e '/^\(+++\|---\)/p'; \
echo "$${h} context: $${ctxt}"; \
echo "$${p}" | sed -n -e "1,$${hline}d" \
-e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \
done; \
;; \
2) echo "\#\#\#\#\# $${v} has no predecessor";; \
esac; \
done; \
done
diff: best-match.sh
@echo "# Comparing changes in + since $(CIB_max)"
$(call version_diff,${CIB_version_pairs_last})
fulldiff: best-match.sh
@echo "# Comparing all changes across all the subsequent increments"
$(call version_diff,${CIB_version_pairs})
CLEANFILES = $(API_generated) $(CIB_generated) $(MON_generated)
# Remove pacemaker schema files generated by *any* source version. This allows
# "make -C xml clean" to have the desired effect when checking out an earlier
# revision in a source tree.
clean-local:
if [ "x$(srcdir)" != "x$(builddir)" ]; then \
rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies); \
fi
rm -f $(builddir)/pacemaker-[0-9]*.[0-9]*.rng
# Enable ability to use $@ in prerequisite
.SECONDEXPANSION:
# For VPATH builds, copy the static schema files into the build tree
$(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@))
$(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then \
$(MKDIR_P) "$(dir $(@))"; \
cp "$(<)" "$(@)"; \
fi
diff --git a/xml/constraints-next.rng b/xml/constraints-next.rng
index e67c3206e7..58fd27fb7f 100644
--- a/xml/constraints-next.rng
+++ b/xml/constraints-next.rng
@@ -1,285 +1,289 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ @COMPAT: The constraints-next schema is deprecated since 2.1.5 and will be
+ removed in a future release.
+-->
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<ref name="element-constraints"/>
</start>
<define name="element-constraints">
<element name="constraints">
<zeroOrMore>
<choice>
<ref name="element-location"/>
<ref name="element-colocation"/>
<ref name="element-order"/>
<ref name="element-rsc_ticket"/>
</choice>
</zeroOrMore>
</element>
</define>
<define name="element-location">
<element name="rsc_location">
<attribute name="id"><data type="ID"/></attribute>
<choice>
<group>
<choice>
<attribute name="rsc"><data type="IDREF"/></attribute>
<attribute name="rsc-pattern"><text/></attribute>
</choice>
<optional>
<attribute name="role">
<ref name="attribute-roles"/>
</attribute>
</optional>
</group>
<oneOrMore>
<ref name="element-resource-set"/>
</oneOrMore>
</choice>
<choice>
<group>
<externalRef href="score.rng"/>
<attribute name="node"><text/></attribute>
</group>
<oneOrMore>
<grammar>
<include href="rule-3.9.rng">
<start>
<ref name="element-rule-location"/>
</start>
</include>
</grammar>
</oneOrMore>
</choice>
<optional>
<ref name="element-lifetime"/>
</optional>
<optional>
<attribute name="resource-discovery">
<ref name="attribute-discovery"/>
</attribute>
</optional>
</element>
</define>
<define name="element-resource-set">
<element name="resource_set">
<choice>
<attribute name="id-ref"><data type="IDREF"/></attribute>
<group>
<attribute name="id"><data type="ID"/></attribute>
<optional>
<attribute name="sequential"><data type="boolean"/></attribute>
</optional>
<optional>
<attribute name="require-all"><data type="boolean"/></attribute>
</optional>
<optional>
<attribute name="ordering">
<choice>
<value>group</value>
<value>listed</value>
</choice>
</attribute>
</optional>
<optional>
<attribute name="action">
<ref name="attribute-actions"/>
</attribute>
</optional>
<optional>
<attribute name="role">
<ref name="attribute-roles"/>
</attribute>
</optional>
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="kind">
<ref name="order-types"/>
</attribute>
</choice>
</optional>
<oneOrMore>
<element name="resource_ref">
<attribute name="id"><data type="IDREF"/></attribute>
</element>
</oneOrMore>
</group>
</choice>
</element>
</define>
<define name="element-colocation">
<element name="rsc_colocation">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<externalRef href="score.rng"/>
</optional>
<optional>
<attribute name="influence"><text/></attribute>
</optional>
<optional>
<ref name="element-lifetime"/>
</optional>
<choice>
<oneOrMore>
<ref name="element-resource-set"/>
</oneOrMore>
<group>
<attribute name="rsc"><data type="IDREF"/></attribute>
<attribute name="with-rsc"><data type="IDREF"/></attribute>
<optional>
<attribute name="node-attribute"><text/></attribute>
</optional>
<optional>
<attribute name="rsc-role">
<ref name="attribute-roles"/>
</attribute>
</optional>
<optional>
<attribute name="with-rsc-role">
<ref name="attribute-roles"/>
</attribute>
</optional>
<optional>
<attribute name="rsc-instance"><data type="integer"/></attribute>
</optional>
<optional>
<attribute name="with-rsc-instance"><data type="integer"/></attribute>
</optional>
</group>
</choice>
</element>
</define>
<define name="element-order">
<element name="rsc_order">
<attribute name="id"><data type="ID"/></attribute>
<optional>
<ref name="element-lifetime"/>
</optional>
<optional>
<attribute name="symmetrical"><data type="boolean"/></attribute>
</optional>
<optional>
<attribute name="require-all"><data type="boolean"/></attribute>
</optional>
<optional>
<choice>
<externalRef href="score.rng"/>
<attribute name="kind">
<ref name="order-types"/>
</attribute>
</choice>
</optional>
<choice>
<oneOrMore>
<ref name="element-resource-set"/>
</oneOrMore>
<group>
<attribute name="first"><data type="IDREF"/></attribute>
<attribute name="then"><data type="IDREF"/></attribute>
<optional>
<attribute name="first-action">
<ref name="attribute-actions"/>
</attribute>
</optional>
<optional>
<attribute name="then-action">
<ref name="attribute-actions"/>
</attribute>
</optional>
<optional>
<attribute name="first-instance"><data type="integer"/></attribute>
</optional>
<optional>
<attribute name="then-instance"><data type="integer"/></attribute>
</optional>
</group>
</choice>
</element>
</define>
<define name="element-rsc_ticket">
<element name="rsc_ticket">
<attribute name="id"><data type="ID"/></attribute>
<choice>
<oneOrMore>
<ref name="element-resource-set"/>
</oneOrMore>
<group>
<attribute name="rsc"><data type="IDREF"/></attribute>
<optional>
<attribute name="rsc-role">
<ref name="attribute-roles"/>
</attribute>
</optional>
</group>
</choice>
<attribute name="ticket"><text/></attribute>
<optional>
<attribute name="loss-policy">
<choice>
<value>stop</value>
<value>demote</value>
<value>fence</value>
<value>freeze</value>
</choice>
</attribute>
</optional>
</element>
</define>
<define name="attribute-discovery">
<choice>
<value>always</value>
<value>never</value>
<value>exclusive</value>
</choice>
</define>
<define name="attribute-actions">
<choice>
<value>start</value>
<value>promote</value>
<value>demote</value>
<value>stop</value>
</choice>
</define>
<define name="attribute-roles">
<choice>
<value>Stopped</value>
<value>Started</value>
<value>Promoted</value>
<value>Unpromoted</value>
<value>Master</value>
<value>Slave</value>
</choice>
</define>
<define name="order-types">
<choice>
<value>Optional</value>
<value>Mandatory</value>
<value>Serialize</value>
</choice>
</define>
<define name="element-lifetime">
<element name="lifetime">
<oneOrMore>
<grammar>
<include href="rule-3.9.rng">
<start>
<ref name="element-rule-location"/>
</start>
</include>
</grammar>
</oneOrMore>
</element>
</define>
</grammar>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 6:06 AM (10 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1018281
Default Alt Text
(64 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment