root/lib/common/schemas.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. G_GNUC_PRINTF
  2. xml_latest_schema_index
  3. get_highest_schema
  4. pcmk__highest_schema_name
  5. pcmk__find_x_0_schema
  6. version_from_filename
  7. schema_filter
  8. schema_cmp
  9. schema_cmp_directory
  10. add_schema
  11. add_schema_by_version
  12. wrap_libxslt
  13. pcmk__load_schemas_from_dir
  14. schema_sort_GCompareFunc
  15. pcmk__sort_schemas
  16. crm_schema_init
  17. validate_with_relaxng
  18. free_schema
  19. crm_schema_cleanup
  20. pcmk__get_schema
  21. pcmk__cmp_schemas_by_name
  22. validate_with
  23. validate_with_silent
  24. pcmk__validate_xml
  25. pcmk__configured_schema_validates
  26. G_GNUC_PRINTF
  27. apply_transformation
  28. apply_upgrade
  29. get_configured_schema
  30. pcmk__update_schema
  31. pcmk_update_configured_schema
  32. pcmk__update_configured_schema
  33. pcmk__schema_files_later_than
  34. append_href
  35. external_refs_in_schema
  36. read_file_contents
  37. add_schema_file_to_xml
  38. pcmk__build_schema_xml_node
  39. pcmk__remote_schema_dir
  40. pcmk__warn_if_schema_deprecated
  41. xml_latest_schema
  42. get_schema_name
  43. get_schema_version
  44. update_validation
  45. validate_xml
  46. dump_file
  47. validate_xml_verbose
  48. cli_config_update

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <stdio.h>
  13 #include <string.h>
  14 #include <dirent.h>
  15 #include <errno.h>
  16 #include <sys/stat.h>
  17 #include <stdarg.h>
  18 
  19 #include <libxml/relaxng.h>
  20 #include <libxslt/xslt.h>
  21 #include <libxslt/transform.h>
  22 #include <libxslt/security.h>
  23 #include <libxslt/xsltutils.h>
  24 
  25 #include <crm/common/xml.h>
  26 #include <crm/common/xml_internal.h>  /* PCMK__XML_LOG_BASE */
  27 
  28 #include "crmcommon_private.h"
  29 
  30 #define SCHEMA_ZERO { .v = { 0, 0 } }
  31 
  32 #define schema_strdup_printf(prefix, version, suffix) \
  33     crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
  34 
  35 typedef struct {
  36     xmlRelaxNGPtr rng;
  37     xmlRelaxNGValidCtxtPtr valid;
  38     xmlRelaxNGParserCtxtPtr parser;
  39 } relaxng_ctx_cache_t;
  40 
  41 static GList *known_schemas = NULL;
  42 static bool initialized = false;
  43 static bool silent_logging = FALSE;
  44 
  45 static void G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
  46 xml_log(int priority, const char *fmt, ...)
  47 {
  48     va_list ap;
  49 
  50     va_start(ap, fmt);
  51     if (silent_logging == FALSE) {
  52         /* XXX should not this enable dechunking as well? */
  53         PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
  54     }
  55     va_end(ap);
  56 }
  57 
  58 static int
  59 xml_latest_schema_index(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  60 {
  61     /* This function assumes that crm_schema_init() has been called beforehand,
  62      * so we have at least three schemas (one real schema, the "pacemaker-next"
  63      * schema, and the "none" schema).
  64      *
  65      * @COMPAT: pacemaker-next is deprecated since 2.1.5 and none since 2.1.8.
  66      * Update this when we drop those.
  67      */
  68     return g_list_length(known_schemas) - 3;
  69 }
  70 
  71 /*!
  72  * \internal
  73  * \brief Return the schema entry of the highest-versioned schema
  74  *
  75  * \return Schema entry of highest-versioned schema (or NULL on error)
  76  */
  77 static GList *
  78 get_highest_schema(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  79 {
  80     /* The highest numerically versioned schema is the one before pacemaker-next
  81      *
  82      * @COMPAT pacemaker-next is deprecated since 2.1.5
  83      */
  84     GList *entry = pcmk__get_schema("pacemaker-next");
  85 
  86     CRM_ASSERT((entry != NULL) && (entry->prev != NULL));
  87     return entry->prev;
  88 }
  89 
  90 /*!
  91  * \internal
  92  * \brief Return the name of the highest-versioned schema
  93  *
  94  * \return Name of highest-versioned schema (or NULL on error)
  95  */
  96 const char *
  97 pcmk__highest_schema_name(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  98 {
  99     GList *entry = get_highest_schema();
 100 
 101     return ((pcmk__schema_t *)(entry->data))->name;
 102 }
 103 
 104 /*!
 105  * \internal
 106  * \brief Find first entry of highest major schema version series
 107  *
 108  * \return Schema entry of first schema with highest major version
 109  */
 110 GList *
 111 pcmk__find_x_0_schema(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 112 {
 113 #if defined(PCMK__UNIT_TESTING)
 114     /* If we're unit testing, this can't be static because it'll stick
 115      * around from one test run to the next. It needs to be cleared out
 116      * every time.
 117      */
 118     GList *x_0_entry = NULL;
 119 #else
 120     static GList *x_0_entry = NULL;
 121 #endif
 122 
 123     pcmk__schema_t *highest_schema = NULL;
 124 
 125     if (x_0_entry != NULL) {
 126         return x_0_entry;
 127     }
 128     x_0_entry = get_highest_schema();
 129     highest_schema = x_0_entry->data;
 130 
 131     for (GList *iter = x_0_entry->prev; iter != NULL; iter = iter->prev) {
 132         pcmk__schema_t *schema = iter->data;
 133 
 134         /* We've found a schema in an older major version series.  Return
 135          * the index of the first one in the same major version series as
 136          * the highest schema.
 137          */
 138         if (schema->version.v[0] < highest_schema->version.v[0]) {
 139             x_0_entry = iter->next;
 140             break;
 141         }
 142 
 143         /* We're out of list to examine.  This probably means there was only
 144          * one major version series, so return the first schema entry.
 145          */
 146         if (iter->prev == NULL) {
 147             x_0_entry = known_schemas->data;
 148             break;
 149         }
 150     }
 151     return x_0_entry;
 152 }
 153 
 154 static inline bool
 155 version_from_filename(const char *filename, pcmk__schema_version_t *version)
     /* [previous][next][first][last][top][bottom][index][help] */
 156 {
 157     if (pcmk__ends_with(filename, ".rng")) {
 158         return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2;
 159     } else {
 160         return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2;
 161     }
 162 }
 163 
 164 static int
 165 schema_filter(const struct dirent *a)
     /* [previous][next][first][last][top][bottom][index][help] */
 166 {
 167     int rc = 0;
 168     pcmk__schema_version_t version = SCHEMA_ZERO;
 169 
 170     if (strstr(a->d_name, "pacemaker-") != a->d_name) {
 171         /* crm_trace("%s - wrong prefix", a->d_name); */
 172 
 173     } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
 174         /* crm_trace("%s - wrong suffix", a->d_name); */
 175 
 176     } else if (!version_from_filename(a->d_name, &version)) {
 177         /* crm_trace("%s - wrong format", a->d_name); */
 178 
 179     } else {
 180         /* crm_debug("%s - candidate", a->d_name); */
 181         rc = 1;
 182     }
 183 
 184     return rc;
 185 }
 186 
 187 static int
 188 schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version)
     /* [previous][next][first][last][top][bottom][index][help] */
 189 {
 190     for (int i = 0; i < 2; ++i) {
 191         if (a_version.v[i] < b_version.v[i]) {
 192             return -1;
 193         } else if (a_version.v[i] > b_version.v[i]) {
 194             return 1;
 195         }
 196     }
 197     return 0;
 198 }
 199 
 200 static int
 201 schema_cmp_directory(const struct dirent **a, const struct dirent **b)
     /* [previous][next][first][last][top][bottom][index][help] */
 202 {
 203     pcmk__schema_version_t a_version = SCHEMA_ZERO;
 204     pcmk__schema_version_t b_version = SCHEMA_ZERO;
 205 
 206     if (!version_from_filename(a[0]->d_name, &a_version)
 207         || !version_from_filename(b[0]->d_name, &b_version)) {
 208         // Shouldn't be possible, but makes static analysis happy
 209         return 0;
 210     }
 211 
 212     return schema_cmp(a_version, b_version);
 213 }
 214 
 215 /*!
 216  * \internal
 217  * \brief Add given schema + auxiliary data to internal bookkeeping.
 218  *
 219  * \note When providing \p version, should not be called directly but
 220  *       through \c add_schema_by_version.
 221  */
 222 static void
 223 add_schema(enum pcmk__schema_validator validator, const pcmk__schema_version_t *version,
     /* [previous][next][first][last][top][bottom][index][help] */
 224            const char *name, const char *transform,
 225            const char *transform_enter, bool transform_onleave)
 226 {
 227     pcmk__schema_t *schema = NULL;
 228 
 229     schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
 230 
 231     schema->validator = validator;
 232     schema->version.v[0] = version->v[0];
 233     schema->version.v[1] = version->v[1];
 234     schema->transform_onleave = transform_onleave;
 235     // schema->schema_index is set after all schemas are loaded and sorted
 236 
 237     if (version->v[0] || version->v[1]) {
 238         schema->name = schema_strdup_printf("pacemaker-", *version, "");
 239     } else {
 240         schema->name = pcmk__str_copy(name);
 241     }
 242 
 243     if (transform) {
 244         schema->transform = pcmk__str_copy(transform);
 245     }
 246 
 247     if (transform_enter) {
 248         schema->transform_enter = pcmk__str_copy(transform_enter);
 249     }
 250 
 251     known_schemas = g_list_prepend(known_schemas, schema);
 252 }
 253 
 254 /*!
 255  * \internal
 256  * \brief Add version-specified schema + auxiliary data to internal bookkeeping.
 257  * \return Standard Pacemaker return value (the only possible values are
 258  * \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
 259  *
 260  * \note There's no reliance on the particular order of schemas entering here.
 261  *
 262  * \par A bit of theory
 263  * We track 3 XSLT stylesheets that differ per usage:
 264  * - "upgrade":
 265  *   . sparsely spread over the sequence of all available schemas,
 266  *     as they are only relevant when major version of the schema
 267  *     is getting bumped -- in that case, it MUST be set
 268  *   . name convention:  upgrade-X.Y.xsl
 269  * - "upgrade-enter":
 270  *   . may only accompany "upgrade" occurrence, but doesn't need to
 271  *     be present anytime such one is, i.e., it MAY not be set when
 272  *     "upgrade" is
 273  *   . name convention:  upgrade-X.Y-enter.xsl,
 274  *     when not present: upgrade-enter.xsl
 275  * - "upgrade-leave":
 276  *   . like "upgrade-enter", but SHOULD be present whenever
 277  *     "upgrade-enter" is (and vice versa, but that's only
 278  *     to prevent confusion based on observing the files,
 279  *     it would get ignored regardless)
 280  *   . name convention:  (see "upgrade-enter")
 281  */
 282 static int
 283 add_schema_by_version(const pcmk__schema_version_t *version, bool transform_expected)
     /* [previous][next][first][last][top][bottom][index][help] */
 284 {
 285     bool transform_onleave = FALSE;
 286     int rc = pcmk_rc_ok;
 287     struct stat s;
 288     char *xslt = NULL,
 289          *transform_upgrade = NULL,
 290          *transform_enter = NULL;
 291 
 292     /* prologue for further transform_expected handling */
 293     if (transform_expected) {
 294         /* check if there's suitable "upgrade" stylesheet */
 295         transform_upgrade = schema_strdup_printf("upgrade-", *version, );
 296         xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 297                                        transform_upgrade);
 298     }
 299 
 300     if (!transform_expected) {
 301         /* jump directly to the end */
 302 
 303     } else if (stat(xslt, &s) == 0) {
 304         /* perhaps there's also a targeted "upgrade-enter" stylesheet */
 305         transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
 306         free(xslt);
 307         xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 308                                        transform_enter);
 309         if (stat(xslt, &s) != 0) {
 310             /* or initially, at least a generic one */
 311             crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
 312             free(xslt);
 313             free(transform_enter);
 314             transform_enter = strdup("upgrade-enter");
 315             xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 316                                            transform_enter);
 317             if (stat(xslt, &s) != 0) {
 318                 crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
 319                 free(xslt);
 320                 xslt = NULL;
 321             }
 322         }
 323         /* xslt contains full path to "upgrade-enter" stylesheet */
 324         if (xslt != NULL) {
 325             /* then there should be "upgrade-leave" counterpart (enter->leave) */
 326             memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
 327             transform_onleave = (stat(xslt, &s) == 0);
 328             free(xslt);
 329         } else {
 330             free(transform_enter);
 331             transform_enter = NULL;
 332         }
 333 
 334     } else {
 335         crm_err("Upgrade transform %s not found", xslt);
 336         free(xslt);
 337         free(transform_upgrade);
 338         transform_upgrade = NULL;
 339         rc = ENOENT;
 340     }
 341 
 342     add_schema(pcmk__schema_validator_rng, version, NULL,
 343                transform_upgrade, transform_enter, transform_onleave);
 344 
 345     free(transform_upgrade);
 346     free(transform_enter);
 347 
 348     return rc;
 349 }
 350 
 351 static void
 352 wrap_libxslt(bool finalize)
     /* [previous][next][first][last][top][bottom][index][help] */
 353 {
 354     static xsltSecurityPrefsPtr secprefs;
 355     int ret = 0;
 356 
 357     /* security framework preferences */
 358     if (!finalize) {
 359         CRM_ASSERT(secprefs == NULL);
 360         secprefs = xsltNewSecurityPrefs();
 361         ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
 362                                    xsltSecurityForbid)
 363               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
 364                                      xsltSecurityForbid)
 365               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
 366                                      xsltSecurityForbid)
 367               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
 368                                      xsltSecurityForbid);
 369         if (ret != 0) {
 370             return;
 371         }
 372     } else {
 373         xsltFreeSecurityPrefs(secprefs);
 374         secprefs = NULL;
 375     }
 376 
 377     /* cleanup only */
 378     if (finalize) {
 379         xsltCleanupGlobals();
 380     }
 381 }
 382 
 383 void
 384 pcmk__load_schemas_from_dir(const char *dir)
     /* [previous][next][first][last][top][bottom][index][help] */
 385 {
 386     int lpc, max;
 387     struct dirent **namelist = NULL;
 388 
 389     max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
 390     if (max < 0) {
 391         crm_warn("Could not load schemas from %s: %s", dir, strerror(errno));
 392         return;
 393     }
 394 
 395     for (lpc = 0; lpc < max; lpc++) {
 396         bool transform_expected = false;
 397         pcmk__schema_version_t version = SCHEMA_ZERO;
 398 
 399         if (!version_from_filename(namelist[lpc]->d_name, &version)) {
 400             // Shouldn't be possible, but makes static analysis happy
 401             crm_warn("Skipping schema '%s': could not parse version",
 402                      namelist[lpc]->d_name);
 403             continue;
 404         }
 405         if ((lpc + 1) < max) {
 406             pcmk__schema_version_t next_version = SCHEMA_ZERO;
 407 
 408             if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
 409                     && (version.v[0] < next_version.v[0])) {
 410                 transform_expected = true;
 411             }
 412         }
 413 
 414         if (add_schema_by_version(&version, transform_expected) != pcmk_rc_ok) {
 415             break;
 416         }
 417     }
 418 
 419     for (lpc = 0; lpc < max; lpc++) {
 420         free(namelist[lpc]);
 421     }
 422 
 423     free(namelist);
 424 }
 425 
 426 static gint
 427 schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 428 {
 429     const pcmk__schema_t *schema_a = a;
 430     const pcmk__schema_t *schema_b = b;
 431 
 432     // @COMPAT pacemaker-next is deprecated since 2.1.5 and none since 2.1.8
 433     if (pcmk__str_eq(schema_a->name, "pacemaker-next", pcmk__str_none)) {
 434         if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
 435             return -1;
 436         } else {
 437             return 1;
 438         }
 439     } else if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
 440         return 1;
 441     } else if (pcmk__str_eq(schema_b->name, "pacemaker-next", pcmk__str_none)) {
 442         return -1;
 443     } else {
 444         return schema_cmp(schema_a->version, schema_b->version);
 445     }
 446 }
 447 
 448 /*!
 449  * \internal
 450  * \brief Sort the list of known schemas such that all pacemaker-X.Y are in
 451  *        version order, then pacemaker-next, then none
 452  *
 453  * This function should be called whenever additional schemas are loaded using
 454  * pcmk__load_schemas_from_dir(), after the initial sets in crm_schema_init().
 455  */
 456 void
 457 pcmk__sort_schemas(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 458 {
 459     known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
 460 }
 461 
 462 /*!
 463  * \internal
 464  * \brief Load pacemaker schemas into cache
 465  *
 466  * \note This currently also serves as an entry point for the
 467  *       generic initialization of the libxslt library.
 468  */
 469 void
 470 crm_schema_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 471 {
 472     if (!initialized) {
 473         const char *remote_schema_dir = pcmk__remote_schema_dir();
 474         char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
 475         const pcmk__schema_version_t zero = SCHEMA_ZERO;
 476         int schema_index = 0;
 477 
 478         initialized = true;
 479 
 480         wrap_libxslt(false);
 481 
 482         pcmk__load_schemas_from_dir(base);
 483         pcmk__load_schemas_from_dir(remote_schema_dir);
 484         free(base);
 485 
 486         // @COMPAT: Deprecated since 2.1.5
 487         add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next", NULL,
 488                    NULL, FALSE);
 489 
 490         // @COMPAT Deprecated since 2.1.8
 491         add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL,
 492                    NULL, FALSE);
 493 
 494         /* add_schema() prepends items to the list, so in the simple case, this
 495          * just reverses the list. However if there were any remote schemas,
 496          * sorting is necessary.
 497          */
 498         pcmk__sort_schemas();
 499 
 500         // Now set the schema indexes and log the final result
 501         for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
 502             pcmk__schema_t *schema = iter->data;
 503 
 504             if (schema->transform == NULL) {
 505                 crm_debug("Loaded schema %d: %s", schema_index, schema->name);
 506             } else {
 507                 crm_debug("Loaded schema %d: %s (upgrades with %s.xsl)",
 508                           schema_index, schema->name, schema->transform);
 509             }
 510             schema->schema_index = schema_index++;
 511         }
 512     }
 513 }
 514 
 515 static bool
 516 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
     /* [previous][next][first][last][top][bottom][index][help] */
 517                       void *error_handler_context, const char *relaxng_file,
 518                       relaxng_ctx_cache_t **cached_ctx)
 519 {
 520     int rc = 0;
 521     bool valid = true;
 522     relaxng_ctx_cache_t *ctx = NULL;
 523 
 524     CRM_CHECK(doc != NULL, return false);
 525     CRM_CHECK(relaxng_file != NULL, return false);
 526 
 527     if (cached_ctx && *cached_ctx) {
 528         ctx = *cached_ctx;
 529 
 530     } else {
 531         crm_debug("Creating RNG parser context");
 532         ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
 533 
 534         ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
 535         CRM_CHECK(ctx->parser != NULL, goto cleanup);
 536 
 537         if (error_handler) {
 538             xmlRelaxNGSetParserErrors(ctx->parser,
 539                                       (xmlRelaxNGValidityErrorFunc) error_handler,
 540                                       (xmlRelaxNGValidityWarningFunc) error_handler,
 541                                       error_handler_context);
 542         } else {
 543             xmlRelaxNGSetParserErrors(ctx->parser,
 544                                       (xmlRelaxNGValidityErrorFunc) fprintf,
 545                                       (xmlRelaxNGValidityWarningFunc) fprintf,
 546                                       stderr);
 547         }
 548 
 549         ctx->rng = xmlRelaxNGParse(ctx->parser);
 550         CRM_CHECK(ctx->rng != NULL,
 551                   crm_err("Could not find/parse %s", relaxng_file);
 552                   goto cleanup);
 553 
 554         ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
 555         CRM_CHECK(ctx->valid != NULL, goto cleanup);
 556 
 557         if (error_handler) {
 558             xmlRelaxNGSetValidErrors(ctx->valid,
 559                                      (xmlRelaxNGValidityErrorFunc) error_handler,
 560                                      (xmlRelaxNGValidityWarningFunc) error_handler,
 561                                      error_handler_context);
 562         } else {
 563             xmlRelaxNGSetValidErrors(ctx->valid,
 564                                      (xmlRelaxNGValidityErrorFunc) fprintf,
 565                                      (xmlRelaxNGValidityWarningFunc) fprintf,
 566                                      stderr);
 567         }
 568     }
 569 
 570     rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
 571     if (rc > 0) {
 572         valid = false;
 573 
 574     } else if (rc < 0) {
 575         crm_err("Internal libxml error during validation");
 576     }
 577 
 578   cleanup:
 579 
 580     if (cached_ctx) {
 581         *cached_ctx = ctx;
 582 
 583     } else {
 584         if (ctx->parser != NULL) {
 585             xmlRelaxNGFreeParserCtxt(ctx->parser);
 586         }
 587         if (ctx->valid != NULL) {
 588             xmlRelaxNGFreeValidCtxt(ctx->valid);
 589         }
 590         if (ctx->rng != NULL) {
 591             xmlRelaxNGFree(ctx->rng);
 592         }
 593         free(ctx);
 594     }
 595 
 596     return valid;
 597 }
 598 
 599 static void
 600 free_schema(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 601 {
 602     pcmk__schema_t *schema = data;
 603     relaxng_ctx_cache_t *ctx = NULL;
 604 
 605     switch (schema->validator) {
 606         case pcmk__schema_validator_none: // not cached
 607             break;
 608 
 609         case pcmk__schema_validator_rng: // cached
 610             ctx = (relaxng_ctx_cache_t *) schema->cache;
 611             if (ctx == NULL) {
 612                 break;
 613             }
 614 
 615             if (ctx->parser != NULL) {
 616                 xmlRelaxNGFreeParserCtxt(ctx->parser);
 617             }
 618 
 619             if (ctx->valid != NULL) {
 620                 xmlRelaxNGFreeValidCtxt(ctx->valid);
 621             }
 622 
 623             if (ctx->rng != NULL) {
 624                 xmlRelaxNGFree(ctx->rng);
 625             }
 626 
 627             free(ctx);
 628             schema->cache = NULL;
 629             break;
 630     }
 631 
 632     free(schema->name);
 633     free(schema->transform);
 634     free(schema->transform_enter);
 635     free(schema);
 636 }
 637 
 638 /*!
 639  * \internal
 640  * \brief Clean up global memory associated with XML schemas
 641  */
 642 void
 643 crm_schema_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 644 {
 645     if (known_schemas != NULL) {
 646         g_list_free_full(known_schemas, free_schema);
 647         known_schemas = NULL;
 648     }
 649     initialized = false;
 650 
 651     wrap_libxslt(true);
 652 }
 653 
 654 /*!
 655  * \internal
 656  * \brief Get schema list entry corresponding to a schema name
 657  *
 658  * \param[in] name  Name of schema to get
 659  *
 660  * \return Schema list entry corresponding to \p name, or NULL if unknown
 661  */
 662 GList *
 663 pcmk__get_schema(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 664 {
 665     // @COMPAT Not specifying a schema name is deprecated since 2.1.8
 666     if (name == NULL) {
 667         name = PCMK_VALUE_NONE;
 668     }
 669     for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
 670         pcmk__schema_t *schema = iter->data;
 671 
 672         if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
 673             return iter;
 674         }
 675     }
 676     return NULL;
 677 }
 678 
 679 /*!
 680  * \internal
 681  * \brief Compare two schema version numbers given the schema names
 682  *
 683  * \param[in] schema1  Name of first schema to compare
 684  * \param[in] schema2  Name of second schema to compare
 685  *
 686  * \return Standard comparison result (negative integer if \p schema1 has the
 687  *         lower version number, positive integer if \p schema1 has the higher
 688  *         version number, of 0 if the version numbers are equal)
 689  */
 690 int
 691 pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
     /* [previous][next][first][last][top][bottom][index][help] */
 692 {
 693     GList *entry1 = pcmk__get_schema(schema1_name);
 694     GList *entry2 = pcmk__get_schema(schema2_name);
 695 
 696     if (entry1 == NULL) {
 697         return (entry2 == NULL)? 0 : -1;
 698 
 699     } else if (entry2 == NULL) {
 700         return 1;
 701 
 702     } else {
 703         pcmk__schema_t *schema1 = entry1->data;
 704         pcmk__schema_t *schema2 = entry2->data;
 705 
 706         return schema1->schema_index - schema2->schema_index;
 707     }
 708 }
 709 
 710 static bool
 711 validate_with(xmlNode *xml, pcmk__schema_t *schema,
     /* [previous][next][first][last][top][bottom][index][help] */
 712               xmlRelaxNGValidityErrorFunc error_handler,
 713               void *error_handler_context)
 714 {
 715     bool valid = false;
 716     char *file = NULL;
 717     relaxng_ctx_cache_t **cache = NULL;
 718 
 719     if (schema == NULL) {
 720         return false;
 721     }
 722 
 723     if (schema->validator == pcmk__schema_validator_none) {
 724         return true;
 725     }
 726 
 727     file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
 728                                    schema->name);
 729 
 730     crm_trace("Validating with %s (type=%d)",
 731               pcmk__s(file, "missing schema"), schema->validator);
 732     switch (schema->validator) {
 733         case pcmk__schema_validator_rng:
 734             cache = (relaxng_ctx_cache_t **) &(schema->cache);
 735             valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
 736             break;
 737         default:
 738             crm_err("Unknown validator type: %d", schema->validator);
 739             break;
 740     }
 741 
 742     free(file);
 743     return valid;
 744 }
 745 
 746 static bool
 747 validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
     /* [previous][next][first][last][top][bottom][index][help] */
 748 {
 749     bool rc, sl_backup = silent_logging;
 750     silent_logging = TRUE;
 751     rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
 752     silent_logging = sl_backup;
 753     return rc;
 754 }
 755 
 756 bool
 757 pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
     /* [previous][next][first][last][top][bottom][index][help] */
 758                    xmlRelaxNGValidityErrorFunc error_handler,
 759                    void *error_handler_context)
 760 {
 761     GList *entry = NULL;
 762     pcmk__schema_t *schema = NULL;
 763 
 764     CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
 765 
 766     if (validation == NULL) {
 767         validation = crm_element_value(xml_blob, PCMK_XA_VALIDATE_WITH);
 768     }
 769     pcmk__warn_if_schema_deprecated(validation);
 770 
 771     // @COMPAT Not specifying a schema name is deprecated since 2.1.8
 772     if (validation == NULL) {
 773         bool valid = false;
 774 
 775         for (entry = known_schemas; entry != NULL; entry = entry->next) {
 776             schema = entry->data;
 777             if (validate_with(xml_blob, schema, NULL, NULL)) {
 778                 valid = true;
 779                 crm_xml_add(xml_blob, PCMK_XA_VALIDATE_WITH, schema->name);
 780                 crm_info("XML validated against %s", schema->name);
 781             }
 782         }
 783         return valid;
 784     }
 785 
 786     entry = pcmk__get_schema(validation);
 787     if (entry == NULL) {
 788         pcmk__config_err("Cannot validate CIB with " PCMK_XA_VALIDATE_WITH
 789                          " set to an unknown schema such as '%s' (manually"
 790                          " edit to use a known schema)",
 791                          validation);
 792         return false;
 793     }
 794 
 795     schema = entry->data;
 796     return validate_with(xml_blob, schema, error_handler,
 797                          error_handler_context);
 798 }
 799 
 800 /*!
 801  * \internal
 802  * \brief Validate XML using its configured schema (and send errors to logs)
 803  *
 804  * \param[in] xml  XML to validate
 805  *
 806  * \return true if XML validates, otherwise false
 807  */
 808 bool
 809 pcmk__configured_schema_validates(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 810 {
 811     return pcmk__validate_xml(xml, NULL,
 812                               (xmlRelaxNGValidityErrorFunc) xml_log,
 813                               GUINT_TO_POINTER(LOG_ERR));
 814 }
 815 
 816 /* With this arrangement, an attempt to identify the message severity
 817    as explicitly signalled directly from XSLT is performed in rather
 818    a smart way (no reliance on formatting string + arguments being
 819    always specified as ["%s", purposeful_string], as it can also be
 820    ["%s: %s", some_prefix, purposeful_string] etc. so every argument
 821    pertaining %s specifier is investigated), and if such a mark found,
 822    the respective level is determined and, when the messages are to go
 823    to the native logs, the mark itself gets dropped
 824    (by the means of string shift).
 825 
 826    NOTE: whether the native logging is the right sink is decided per
 827          the ctx parameter -- NULL denotes this case, otherwise it
 828          carries a pointer to the numeric expression of the desired
 829          target logging level (messages with higher level will be
 830          suppressed)
 831 
 832    NOTE: on some architectures, this string shift may not have any
 833          effect, but that's an acceptable tradeoff
 834 
 835    The logging level for not explicitly designated messages
 836    (suspicious, likely internal errors or some runaways) is
 837    LOG_WARNING.
 838  */
 839 static void G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 840 cib_upgrade_err(void *ctx, const char *fmt, ...)
 841 {
 842     va_list ap, aq;
 843     char *arg_cur;
 844 
 845     bool found = FALSE;
 846     const char *fmt_iter = fmt;
 847     uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
 848     const unsigned * log_level = (const unsigned *) ctx;
 849     enum {
 850         escan_seennothing,
 851         escan_seenpercent,
 852     } scan_state = escan_seennothing;
 853 
 854     va_start(ap, fmt);
 855     va_copy(aq, ap);
 856 
 857     while (!found && *fmt_iter != '\0') {
 858         /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
 859         switch (*fmt_iter++) {
 860         case '%':
 861             if (scan_state == escan_seennothing) {
 862                 scan_state = escan_seenpercent;
 863             } else if (scan_state == escan_seenpercent) {
 864                 scan_state = escan_seennothing;
 865             }
 866             break;
 867         case 's':
 868             if (scan_state == escan_seenpercent) {
 869                 scan_state = escan_seennothing;
 870                 arg_cur = va_arg(aq, char *);
 871                 if (arg_cur != NULL) {
 872                     switch (arg_cur[0]) {
 873                     case 'W':
 874                         if (!strncmp(arg_cur, "WARNING: ",
 875                                      sizeof("WARNING: ") - 1)) {
 876                             msg_log_level = LOG_WARNING;
 877                         }
 878                         if (ctx == NULL) {
 879                             memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
 880                                     strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
 881                         }
 882                         found = TRUE;
 883                         break;
 884                     case 'I':
 885                         if (!strncmp(arg_cur, "INFO: ",
 886                                      sizeof("INFO: ") - 1)) {
 887                             msg_log_level = LOG_INFO;
 888                         }
 889                         if (ctx == NULL) {
 890                             memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
 891                                     strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
 892                         }
 893                         found = TRUE;
 894                         break;
 895                     case 'D':
 896                         if (!strncmp(arg_cur, "DEBUG: ",
 897                                      sizeof("DEBUG: ") - 1)) {
 898                             msg_log_level = LOG_DEBUG;
 899                         }
 900                         if (ctx == NULL) {
 901                             memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
 902                                     strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
 903                         }
 904                         found = TRUE;
 905                         break;
 906                     }
 907                 }
 908             }
 909             break;
 910         case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
 911         case '0': case '1': case '2': case '3': case '4':
 912         case '5': case '6': case '7': case '8': case '9':
 913         case '*':
 914             break;
 915         case 'l':
 916         case 'z':
 917         case 't':
 918         case 'j':
 919         case 'd': case 'i':
 920         case 'o':
 921         case 'u':
 922         case 'x': case 'X':
 923         case 'e': case 'E':
 924         case 'f': case 'F':
 925         case 'g': case 'G':
 926         case 'a': case 'A':
 927         case 'c':
 928         case 'p':
 929             if (scan_state == escan_seenpercent) {
 930                 (void) va_arg(aq, void *);  /* skip forward */
 931                 scan_state = escan_seennothing;
 932             }
 933             break;
 934         default:
 935             scan_state = escan_seennothing;
 936             break;
 937         }
 938     }
 939 
 940     if (log_level != NULL) {
 941         /* intention of the following offset is:
 942            cibadmin -V -> start showing INFO labelled messages */
 943         if (*log_level + 4 >= msg_log_level) {
 944             vfprintf(stderr, fmt, ap);
 945         }
 946     } else {
 947         PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
 948     }
 949 
 950     va_end(aq);
 951     va_end(ap);
 952 }
 953 
 954 /*!
 955  * \internal
 956  * \brief Apply a single XSL transformation to given XML
 957  *
 958  * \param[in] xml        XML to transform
 959  * \param[in] transform  XSL name
 960  * \param[in] to_logs    If false, certain validation errors will be sent to
 961  *                       stderr rather than logged
 962  *
 963  * \return Transformed XML on success, otherwise NULL
 964  */
 965 static xmlNode *
 966 apply_transformation(const xmlNode *xml, const char *transform,
     /* [previous][next][first][last][top][bottom][index][help] */
 967                      gboolean to_logs)
 968 {
 969     char *xform = NULL;
 970     xmlNode *out = NULL;
 971     xmlDocPtr res = NULL;
 972     xsltStylesheet *xslt = NULL;
 973 
 974     xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 975                                     transform);
 976 
 977     /* for capturing, e.g., what's emitted via <xsl:message> */
 978     if (to_logs) {
 979         xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
 980     } else {
 981         xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
 982     }
 983 
 984     xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
 985     CRM_CHECK(xslt != NULL, goto cleanup);
 986 
 987     res = xsltApplyStylesheet(xslt, xml->doc, NULL);
 988     CRM_CHECK(res != NULL, goto cleanup);
 989 
 990     xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
 991 
 992     out = xmlDocGetRootElement(res);
 993 
 994   cleanup:
 995     if (xslt) {
 996         xsltFreeStylesheet(xslt);
 997     }
 998 
 999     free(xform);
1000 
1001     return out;
1002 }
1003 
1004 /*!
1005  * \internal
1006  * \brief Perform all transformations needed to upgrade XML to next schema
1007  *
1008  * A schema upgrade can require up to three XSL transformations: an "enter"
1009  * transform, the main upgrade transform, and a "leave" transform. Perform
1010  * all needed transforms to upgrade given XML to the next schema.
1011  *
1012  * \param[in] original_xml  XML to transform
1013  * \param[in] schema_index  Index of schema that successfully validates
1014  *                          \p original_xml
1015  * \param[in] to_logs       If false, certain validation errors will be sent to
1016  *                          stderr rather than logged
1017  *
1018  * \return XML result of schema transforms if successful, otherwise NULL
1019  */
1020 static xmlNode *
1021 apply_upgrade(const xmlNode *original_xml, int schema_index, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1022 {
1023     pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1024     pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
1025                                                       schema_index + 1);
1026     bool transform_onleave = false;
1027     char *transform_leave;
1028     const xmlNode *xml = original_xml;
1029     xmlNode *upgrade = NULL;
1030     xmlNode *final = NULL;
1031     xmlRelaxNGValidityErrorFunc error_handler = NULL;
1032 
1033     CRM_ASSERT((schema != NULL) && (upgraded_schema != NULL));
1034 
1035     if (to_logs) {
1036         error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1037     }
1038 
1039     transform_onleave = schema->transform_onleave;
1040     if (schema->transform_enter != NULL) {
1041         crm_debug("Upgrading schema from %s to %s: "
1042                   "applying pre-upgrade XSL transform %s",
1043                   schema->name, upgraded_schema->name, schema->transform_enter);
1044         upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
1045         if (upgrade == NULL) {
1046             crm_warn("Pre-upgrade XSL transform %s failed, "
1047                      "will skip post-upgrade transform",
1048                      schema->transform_enter);
1049             transform_onleave = FALSE;
1050         } else {
1051             xml = upgrade;
1052         }
1053     }
1054 
1055 
1056     crm_debug("Upgrading schema from %s to %s: "
1057               "applying upgrade XSL transform %s",
1058               schema->name, upgraded_schema->name, schema->transform);
1059     final = apply_transformation(xml, schema->transform, to_logs);
1060     if (upgrade != xml) {
1061         free_xml(upgrade);
1062         upgrade = NULL;
1063     }
1064 
1065     if ((final != NULL) && transform_onleave) {
1066         upgrade = final;
1067         /* following condition ensured in add_schema_by_version */
1068         CRM_ASSERT(schema->transform_enter != NULL);
1069         transform_leave = strdup(schema->transform_enter);
1070         /* enter -> leave */
1071         memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1072         crm_debug("Upgrading schema from %s to %s: "
1073                   "applying post-upgrade XSL transform %s",
1074                   schema->name, upgraded_schema->name, transform_leave);
1075         final = apply_transformation(upgrade, transform_leave, to_logs);
1076         if (final == NULL) {
1077             crm_warn("Ignoring failure of post-upgrade XSL transform %s",
1078                      transform_leave);
1079             final = upgrade;
1080         } else {
1081             free_xml(upgrade);
1082         }
1083         free(transform_leave);
1084     }
1085 
1086     if (final == NULL) {
1087         return NULL;
1088     }
1089 
1090     // Ensure result validates with its new schema
1091     if (!validate_with(final, upgraded_schema, error_handler,
1092                        GUINT_TO_POINTER(LOG_ERR))) {
1093         crm_err("Schema upgrade from %s to %s failed: "
1094                 "XSL transform %s produced an invalid configuration",
1095                 schema->name, upgraded_schema->name, schema->transform);
1096         crm_log_xml_debug(final, "bad-transform-result");
1097         free_xml(final);
1098         return NULL;
1099     }
1100 
1101     crm_info("Schema upgrade from %s to %s succeeded",
1102              schema->name, upgraded_schema->name);
1103     return final;
1104 }
1105 
1106 /*!
1107  * \internal
1108  * \brief Get the schema list entry corresponding to XML configuration
1109  *
1110  * \param[in] xml  CIB XML to check
1111  *
1112  * \return List entry of schema configured in \p xml
1113  */
1114 static GList *
1115 get_configured_schema(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1116 {
1117     const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1118 
1119     pcmk__warn_if_schema_deprecated(schema_name);
1120     if (schema_name == NULL) {
1121         return NULL;
1122     }
1123     return pcmk__get_schema(schema_name);
1124 }
1125 
1126 /*!
1127  * \brief Update CIB XML to latest schema that validates it
1128  *
1129  * \param[in,out] xml              XML to update (may be freed and replaced
1130  *                                 after being transformed)
1131  * \param[in]     max_schema_name  If not NULL, do not update \p xml to any
1132  *                                 schema later than this one
1133  * \param[in]     transform        If false, do not update \p xml to any schema
1134  *                                 that requires an XSL transform
1135  * \param[in]     to_logs          If false, certain validation errors will be
1136  *                                 sent to stderr rather than logged
1137  *
1138  * \return Standard Pacemaker return code
1139  */
1140 int
1141 pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
     /* [previous][next][first][last][top][bottom][index][help] */
1142                     bool to_logs)
1143 {
1144     int max_stable_schemas = xml_latest_schema_index();
1145     int max_schema_index = 0;
1146     int rc = pcmk_rc_ok;
1147     GList *entry = NULL;
1148     pcmk__schema_t *best_schema = NULL;
1149     pcmk__schema_t *original_schema = NULL;
1150     xmlRelaxNGValidityErrorFunc error_handler = 
1151         to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1152 
1153     CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1154               return EINVAL);
1155 
1156     if (max_schema_name != NULL) {
1157         GList *max_entry = pcmk__get_schema(max_schema_name);
1158 
1159         if (max_entry != NULL) {
1160             pcmk__schema_t *max_schema = max_entry->data;
1161 
1162             max_schema_index = max_schema->schema_index;
1163         }
1164     }
1165     if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1166         max_schema_index = max_stable_schemas;
1167     }
1168 
1169     entry = get_configured_schema(*xml);
1170     if (entry == NULL) {
1171         // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1172         entry = known_schemas;
1173     } else {
1174         original_schema = entry->data;
1175         if (original_schema->schema_index >= max_schema_index) {
1176             return pcmk_rc_ok;
1177         }
1178     }
1179 
1180     for (; entry != NULL; entry = entry->next) {
1181         pcmk__schema_t *current_schema = entry->data;
1182         xmlNode *upgrade = NULL;
1183 
1184         if (current_schema->schema_index > max_schema_index) {
1185             break;
1186         }
1187 
1188         if (!validate_with(*xml, current_schema, error_handler,
1189                            GUINT_TO_POINTER(LOG_ERR))) {
1190             crm_debug("Schema %s does not validate", current_schema->name);
1191             if (best_schema != NULL) {
1192                 /* we've satisfied the validation, no need to check further */
1193                 break;
1194             }
1195             rc = pcmk_rc_schema_validation;
1196             continue; // Try again with the next higher schema
1197         }
1198 
1199         crm_debug("Schema %s validates", current_schema->name);
1200         rc = pcmk_rc_ok;
1201         best_schema = current_schema;
1202         if (current_schema->schema_index == max_schema_index) {
1203             break; // No further transformations possible
1204         }
1205 
1206         if (!transform || (current_schema->transform == NULL)
1207             || validate_with_silent(*xml, entry->next->data)) {
1208             /* The next schema either doesn't require a transform or validates
1209              * successfully even without the transform. Skip the transform and
1210              * try the next schema with the same XML.
1211              */
1212             continue;
1213         }
1214 
1215         upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1216         if (upgrade == NULL) {
1217             /* The transform failed, so this schema can't be used. Later
1218              * schemas are unlikely to validate, but try anyway until we
1219              * run out of options.
1220              */
1221             rc = pcmk_rc_transform_failed;
1222         } else {
1223             best_schema = current_schema;
1224             free_xml(*xml);
1225             *xml = upgrade;
1226         }
1227     }
1228 
1229     if (best_schema != NULL) {
1230         if ((original_schema == NULL)
1231             || (best_schema->schema_index > original_schema->schema_index)) {
1232             crm_info("%s the configuration schema to %s",
1233                      (transform? "Transformed" : "Upgraded"),
1234                      best_schema->name);
1235             crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1236         }
1237     }
1238     return rc;
1239 }
1240 
1241 int
1242 pcmk_update_configured_schema(xmlNode **xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1243 {
1244     return pcmk__update_configured_schema(xml, true);
1245 }
1246 
1247 /*!
1248  * \brief Update XML from its configured schema to the latest major series
1249  *
1250  * \param[in,out] xml      XML to update
1251  * \param[in]     to_logs  If false, certain validation errors will be
1252  *                         sent to stderr rather than logged
1253  *
1254  * \return Standard Pacemaker return code
1255  */
1256 int
1257 pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1258 {
1259     int rc = pcmk_rc_ok;
1260     char *original_schema_name = NULL;
1261 
1262     // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1263     const char *effective_original_name = "the first";
1264 
1265     int orig_version = -1;
1266     pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1267     GList *entry = NULL;
1268 
1269     CRM_CHECK(xml != NULL, return EINVAL);
1270 
1271     original_schema_name = crm_element_value_copy(*xml, PCMK_XA_VALIDATE_WITH);
1272     pcmk__warn_if_schema_deprecated(original_schema_name);
1273     entry = pcmk__get_schema(original_schema_name);
1274     if (entry != NULL) {
1275         pcmk__schema_t *original_schema = entry->data;
1276 
1277         effective_original_name = original_schema->name;
1278         orig_version = original_schema->schema_index;
1279     }
1280 
1281     if (orig_version < x_0_schema->schema_index) {
1282         // Current configuration schema is not acceptable, try to update
1283         xmlNode *converted = NULL;
1284         const char *new_schema_name = NULL;
1285         pcmk__schema_t *schema = NULL;
1286 
1287         entry = NULL;
1288         converted = pcmk__xml_copy(NULL, *xml);
1289         if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1290             new_schema_name = crm_element_value(converted,
1291                                                 PCMK_XA_VALIDATE_WITH);
1292             entry = pcmk__get_schema(new_schema_name);
1293         }
1294         schema = (entry == NULL)? NULL : entry->data;
1295 
1296         if ((schema == NULL)
1297             || (schema->schema_index < x_0_schema->schema_index)) {
1298             // Updated configuration schema is still not acceptable
1299 
1300             if ((orig_version == -1) || (schema == NULL)
1301                 || (schema->schema_index < orig_version)) {
1302                 // We couldn't validate any schema at all
1303                 if (to_logs) {
1304                     pcmk__config_err("Cannot upgrade configuration (claiming "
1305                                      "%s schema) to at least %s because it "
1306                                      "does not validate with any schema from "
1307                                      "%s to the latest",
1308                                      pcmk__s(original_schema_name, "no"),
1309                                      x_0_schema->name, effective_original_name);
1310                 } else {
1311                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1312                                     "%s schema) to at least %s because it "
1313                                     "does not validate with any schema from "
1314                                     "%s to the latest\n",
1315                                     pcmk__s(original_schema_name, "no"),
1316                                     x_0_schema->name, effective_original_name);
1317                 }
1318             } else {
1319                 // We updated configuration successfully, but still too low
1320                 if (to_logs) {
1321                     pcmk__config_err("Cannot upgrade configuration (claiming "
1322                                      "%s schema) to at least %s because it "
1323                                      "would not upgrade past %s",
1324                                      pcmk__s(original_schema_name, "no"),
1325                                      x_0_schema->name,
1326                                      pcmk__s(new_schema_name, "unspecified version"));
1327                 } else {
1328                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1329                                     "%s schema) to at least %s because it "
1330                                     "would not upgrade past %s\n",
1331                                     pcmk__s(original_schema_name, "no"),
1332                                     x_0_schema->name,
1333                                     pcmk__s(new_schema_name, "unspecified version"));
1334                 }
1335             }
1336 
1337             free_xml(converted);
1338             converted = NULL;
1339             rc = pcmk_rc_transform_failed;
1340 
1341         } else {
1342             // Updated configuration schema is acceptable
1343             free_xml(*xml);
1344             *xml = converted;
1345 
1346             if (schema->schema_index < xml_latest_schema_index()) {
1347                 if (to_logs) {
1348                     pcmk__config_warn("Configuration with %s schema was "
1349                                       "internally upgraded to acceptable (but "
1350                                       "not most recent) %s",
1351                                       pcmk__s(original_schema_name, "no"),
1352                                       schema->name);
1353                 }
1354             } else if (to_logs) {
1355                 crm_info("Configuration with %s schema was internally "
1356                          "upgraded to latest version %s",
1357                          pcmk__s(original_schema_name, "no"),
1358                          schema->name);
1359             }
1360         }
1361 
1362     } else {
1363         // @COMPAT the none schema is deprecated since 2.1.8
1364         pcmk__schema_t *none_schema = NULL;
1365 
1366         entry = pcmk__get_schema(PCMK_VALUE_NONE);
1367         CRM_ASSERT((entry != NULL) && (entry->data != NULL));
1368 
1369         none_schema = entry->data;
1370         if (!to_logs && (orig_version >= none_schema->schema_index)) {
1371             fprintf(stderr, "Schema validation of configuration is "
1372                             "disabled (support for " PCMK_XA_VALIDATE_WITH
1373                             " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1374                             " and will be removed in a future release)\n");
1375         }
1376     }
1377 
1378     free(original_schema_name);
1379     return rc;
1380 }
1381 
1382 /*!
1383  * \internal
1384  * \brief Return a list of all schema files and any associated XSLT files
1385  *        later than the given one
1386  * \brief Return a list of all schema versions later than the given one
1387  *
1388  * \param[in] schema The schema to compare against (for example,
1389  *                   "pacemaker-3.1.rng" or "pacemaker-3.1")
1390  *
1391  * \note The caller is responsible for freeing both the returned list and
1392  *       the elements of the list
1393  */
1394 GList *
1395 pcmk__schema_files_later_than(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1396 {
1397     GList *lst = NULL;
1398     pcmk__schema_version_t ver;
1399 
1400     if (!version_from_filename(name, &ver)) {
1401         return lst;
1402     }
1403 
1404     for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1405          iter != NULL; iter = iter->prev) {
1406         pcmk__schema_t *schema = iter->data;
1407         char *s = NULL;
1408 
1409         if (schema_cmp(ver, schema->version) != -1) {
1410             continue;
1411         }
1412 
1413         s = crm_strdup_printf("%s.rng", schema->name);
1414         lst = g_list_prepend(lst, s);
1415 
1416         if (schema->transform != NULL) {
1417             char *xform = crm_strdup_printf("%s.xsl", schema->transform);
1418             lst = g_list_prepend(lst, xform);
1419         }
1420 
1421         if (schema->transform_enter != NULL) {
1422             char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter);
1423 
1424             lst = g_list_prepend(lst, enter);
1425 
1426             if (schema->transform_onleave) {
1427                 int last_dash = strrchr(enter, '-') - enter;
1428                 char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter);
1429 
1430                 lst = g_list_prepend(lst, leave);
1431             }
1432         }
1433     }
1434 
1435     return lst;
1436 }
1437 
1438 static void
1439 append_href(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1440 {
1441     GList **list = user_data;
1442     char *href = crm_element_value_copy(xml, "href");
1443 
1444     if (href == NULL) {
1445         return;
1446     }
1447     *list = g_list_prepend(*list, href);
1448 }
1449 
1450 static void
1451 external_refs_in_schema(GList **list, const char *contents)
     /* [previous][next][first][last][top][bottom][index][help] */
1452 {
1453     /* local-name()= is needed to ignore the xmlns= setting at the top of
1454      * the XML file.  Otherwise, the xpath query will always return nothing.
1455      */
1456     const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1457     xmlNode *xml = pcmk__xml_parse(contents);
1458 
1459     crm_foreach_xpath_result(xml, search, append_href, list);
1460     free_xml(xml);
1461 }
1462 
1463 static int
1464 read_file_contents(const char *file, char **contents)
     /* [previous][next][first][last][top][bottom][index][help] */
1465 {
1466     int rc = pcmk_rc_ok;
1467     char *path = NULL;
1468 
1469     if (pcmk__ends_with(file, ".rng")) {
1470         path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1471     } else {
1472         path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1473     }
1474 
1475     rc = pcmk__file_contents(path, contents);
1476 
1477     free(path);
1478     return rc;
1479 }
1480 
1481 static void
1482 add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
     /* [previous][next][first][last][top][bottom][index][help] */
1483 {
1484     char *contents = NULL;
1485     char *path = NULL;
1486     xmlNode *file_node = NULL;
1487     GList *includes = NULL;
1488     int rc = pcmk_rc_ok;
1489 
1490     /* If we already included this file, don't do so again. */
1491     if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1492         return;
1493     }
1494 
1495     /* Ensure whatever file we were given has a suffix we know about.  If not,
1496      * just assume it's an RNG file.
1497      */
1498     if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
1499         path = crm_strdup_printf("%s.rng", file);
1500     } else {
1501         path = pcmk__str_copy(file);
1502     }
1503 
1504     rc = read_file_contents(path, &contents);
1505     if (rc != pcmk_rc_ok || contents == NULL) {
1506         crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1507         free(path);
1508         return;
1509     }
1510 
1511     /* Create a new <file path="..."> node with the contents of the file
1512      * as a CDATA block underneath it.
1513      */
1514     file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
1515     crm_xml_add(file_node, PCMK_XA_PATH, path);
1516     *already_included = g_list_prepend(*already_included, path);
1517 
1518     xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
1519                                             strlen(contents)));
1520 
1521     /* Scan the file for any <externalRef> or <include> nodes and build up
1522      * a list of the files they reference.
1523      */
1524     external_refs_in_schema(&includes, contents);
1525 
1526     /* For each referenced file, recurse to add it (and potentially anything it
1527      * references, ...) to the XML.
1528      */
1529     for (GList *iter = includes; iter != NULL; iter = iter->next) {
1530         add_schema_file_to_xml(parent, iter->data, already_included);
1531     }
1532 
1533     free(contents);
1534     g_list_free_full(includes, free);
1535 }
1536 
1537 /*!
1538  * \internal
1539  * \brief Add an XML schema file and all the files it references as children
1540  *        of a given XML node
1541  *
1542  * \param[in,out] parent            The parent XML node
1543  * \param[in] name                  The schema version to compare against
1544  *                                  (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
1545  * \param[in,out] already_included  A list of names that have already been added
1546  *                                  to the parent node.
1547  *
1548  * \note The caller is responsible for freeing both the returned list and
1549  *       the elements of the list
1550  */
1551 void
1552 pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
     /* [previous][next][first][last][top][bottom][index][help] */
1553 {
1554     xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1555 
1556     crm_xml_add(schema_node, PCMK_XA_VERSION, name);
1557     add_schema_file_to_xml(schema_node, name, already_included);
1558 
1559     if (schema_node->children == NULL) {
1560         // Not needed if empty. May happen if name was invalid, for example.
1561         free_xml(schema_node);
1562     }
1563 }
1564 
1565 /*!
1566  * \internal
1567  * \brief Return the directory containing any extra schema files that a
1568  *        Pacemaker Remote node fetched from the cluster
1569  */
1570 const char *
1571 pcmk__remote_schema_dir(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1572 {
1573     const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1574 
1575     if (pcmk__str_empty(dir)) {
1576         return PCMK__REMOTE_SCHEMA_DIR;
1577     }
1578 
1579     return dir;
1580 }
1581 
1582 /*!
1583  * \internal
1584  * \brief Warn if a given validation schema is deprecated
1585  *
1586  * \param[in] Schema name to check
1587  */
1588 void
1589 pcmk__warn_if_schema_deprecated(const char *schema)
     /* [previous][next][first][last][top][bottom][index][help] */
1590 {
1591     if ((schema == NULL) ||
1592         pcmk__strcase_any_of(schema, "pacemaker-next", PCMK_VALUE_NONE, NULL)) {
1593         pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1594                           "deprecated and will be removed in a future release "
1595                           "without the possibility of upgrades (manually edit "
1596                           "to use a supported schema)", pcmk__s(schema, ""));
1597     }
1598 }
1599 
1600 // Deprecated functions kept only for backward API compatibility
1601 // LCOV_EXCL_START
1602 
1603 #include <crm/common/xml_compat.h>
1604 
1605 const char *
1606 xml_latest_schema(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1607 {
1608     return pcmk__highest_schema_name();
1609 }
1610 
1611 const char *
1612 get_schema_name(int version)
     /* [previous][next][first][last][top][bottom][index][help] */
1613 {
1614     pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
1615 
1616     return (schema != NULL)? schema->name : "unknown";
1617 }
1618 
1619 int
1620 get_schema_version(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1621 {
1622     int lpc = 0;
1623 
1624     if (name == NULL) {
1625         name = PCMK_VALUE_NONE;
1626     }
1627 
1628     for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
1629         pcmk__schema_t *schema = iter->data;
1630 
1631         if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
1632             return lpc;
1633         }
1634 
1635         lpc++;
1636     }
1637 
1638     return -1;
1639 }
1640 
1641 int
1642 update_validation(xmlNode **xml, int *best, int max, gboolean transform,
     /* [previous][next][first][last][top][bottom][index][help] */
1643                   gboolean to_logs)
1644 {
1645     int rc = pcmk__update_schema(xml, get_schema_name(max), transform, to_logs);
1646 
1647     if ((best != NULL) && (xml != NULL) && (rc == pcmk_rc_ok)) {
1648         const char *schema_name = crm_element_value(*xml,
1649                                                     PCMK_XA_VALIDATE_WITH);
1650         GList *schema_entry = pcmk__get_schema(schema_name);
1651 
1652         if (schema_entry != NULL) {
1653             *best = ((pcmk__schema_t *)(schema_entry->data))->schema_index;
1654         }
1655     }
1656 
1657     return pcmk_rc2legacy(rc);
1658 }
1659 
1660 gboolean
1661 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1662 {
1663     bool rc = pcmk__validate_xml(xml_blob, validation,
1664                                  to_logs? (xmlRelaxNGValidityErrorFunc) xml_log : NULL,
1665                                  GUINT_TO_POINTER(LOG_ERR));
1666     return rc? TRUE : FALSE;
1667 }
1668 
1669 static void
1670 dump_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1671 {
1672 
1673     FILE *fp = NULL;
1674     int ch, line = 0;
1675 
1676     CRM_CHECK(filename != NULL, return);
1677 
1678     fp = fopen(filename, "r");
1679     if (fp == NULL) {
1680         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
1681         return;
1682     }
1683 
1684     fprintf(stderr, "%4d ", ++line);
1685     do {
1686         ch = getc(fp);
1687         if (ch == EOF) {
1688             putc('\n', stderr);
1689             break;
1690         } else if (ch == '\n') {
1691             fprintf(stderr, "\n%4d ", ++line);
1692         } else {
1693             putc(ch, stderr);
1694         }
1695     } while (1);
1696 
1697     fclose(fp);
1698 }
1699 
1700 gboolean
1701 validate_xml_verbose(const xmlNode *xml_blob)
     /* [previous][next][first][last][top][bottom][index][help] */
1702 {
1703     int fd = 0;
1704     xmlDoc *doc = NULL;
1705     xmlNode *xml = NULL;
1706     gboolean rc = FALSE;
1707     char *filename = NULL;
1708 
1709     filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
1710 
1711     umask(S_IWGRP | S_IWOTH | S_IROTH);
1712     fd = mkstemp(filename);
1713     pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
1714 
1715     dump_file(filename);
1716 
1717     doc = xmlReadFile(filename, NULL, 0);
1718     xml = xmlDocGetRootElement(doc);
1719     rc = pcmk__validate_xml(xml, NULL, NULL, NULL);
1720     free_xml(xml);
1721 
1722     unlink(filename);
1723     free(filename);
1724 
1725     return rc? TRUE : FALSE;
1726 }
1727 
1728 gboolean
1729 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1730 {
1731     int rc = pcmk__update_configured_schema(xml, to_logs);
1732 
1733     if (best_version != NULL) {
1734         const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
1735 
1736         if (name == NULL) {
1737             *best_version = -1;
1738         } else {
1739             GList *entry = pcmk__get_schema(name);
1740             pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1741 
1742             *best_version = (schema == NULL)? -1 : schema->schema_index;
1743         }
1744     }
1745     return (rc == pcmk_rc_ok)? TRUE: FALSE;
1746 }
1747 
1748 // LCOV_EXCL_STOP
1749 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */