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     pcmk__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         pcmk__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     /* Caller allocates private data for final result document. Intermediate
 988      * result documents are temporary and don't need private data.
 989      */
 990     res = xsltApplyStylesheet(xslt, xml->doc, NULL);
 991     CRM_CHECK(res != NULL, goto cleanup);
 992 
 993     xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
 994 
 995     out = xmlDocGetRootElement(res);
 996 
 997   cleanup:
 998     if (xslt) {
 999         xsltFreeStylesheet(xslt);
1000     }
1001 
1002     free(xform);
1003 
1004     return out;
1005 }
1006 
1007 /*!
1008  * \internal
1009  * \brief Perform all transformations needed to upgrade XML to next schema
1010  *
1011  * A schema upgrade can require up to three XSL transformations: an "enter"
1012  * transform, the main upgrade transform, and a "leave" transform. Perform
1013  * all needed transforms to upgrade given XML to the next schema.
1014  *
1015  * \param[in] original_xml  XML to transform
1016  * \param[in] schema_index  Index of schema that successfully validates
1017  *                          \p original_xml
1018  * \param[in] to_logs       If false, certain validation errors will be sent to
1019  *                          stderr rather than logged
1020  *
1021  * \return XML result of schema transforms if successful, otherwise NULL
1022  */
1023 static xmlNode *
1024 apply_upgrade(const xmlNode *original_xml, int schema_index, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1025 {
1026     pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1027     pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
1028                                                       schema_index + 1);
1029     bool transform_onleave = false;
1030     char *transform_leave;
1031     const xmlNode *xml = original_xml;
1032     xmlNode *upgrade = NULL;
1033     xmlNode *final = NULL;
1034     xmlRelaxNGValidityErrorFunc error_handler = NULL;
1035 
1036     pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
1037 
1038     if (to_logs) {
1039         error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1040     }
1041 
1042     transform_onleave = schema->transform_onleave;
1043     if (schema->transform_enter != NULL) {
1044         crm_debug("Upgrading schema from %s to %s: "
1045                   "applying pre-upgrade XSL transform %s",
1046                   schema->name, upgraded_schema->name, schema->transform_enter);
1047         upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
1048         if (upgrade == NULL) {
1049             crm_warn("Pre-upgrade XSL transform %s failed, "
1050                      "will skip post-upgrade transform",
1051                      schema->transform_enter);
1052             transform_onleave = FALSE;
1053         } else {
1054             xml = upgrade;
1055         }
1056     }
1057 
1058 
1059     crm_debug("Upgrading schema from %s to %s: "
1060               "applying upgrade XSL transform %s",
1061               schema->name, upgraded_schema->name, schema->transform);
1062     final = apply_transformation(xml, schema->transform, to_logs);
1063     if (upgrade != xml) {
1064         free_xml(upgrade);
1065         /* upgrade = NULL; */ // Static analysis dislikes this, so be careful
1066     }
1067 
1068     if ((final != NULL) && transform_onleave) {
1069         upgrade = final;
1070         /* following condition ensured in add_schema_by_version */
1071         pcmk__assert(schema->transform_enter != NULL);
1072         transform_leave = strdup(schema->transform_enter);
1073         /* enter -> leave */
1074         memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1075         crm_debug("Upgrading schema from %s to %s: "
1076                   "applying post-upgrade XSL transform %s",
1077                   schema->name, upgraded_schema->name, transform_leave);
1078         final = apply_transformation(upgrade, transform_leave, to_logs);
1079         if (final == NULL) {
1080             crm_warn("Ignoring failure of post-upgrade XSL transform %s",
1081                      transform_leave);
1082             final = upgrade;
1083         } else {
1084             free_xml(upgrade);
1085         }
1086         free(transform_leave);
1087     }
1088 
1089     if (final == NULL) {
1090         return NULL;
1091     }
1092 
1093     // Final result document from upgrade pipeline needs private data
1094     pcmk__xml_new_private_data((xmlNode *) final->doc);
1095 
1096     // Ensure result validates with its new schema
1097     if (!validate_with(final, upgraded_schema, error_handler,
1098                        GUINT_TO_POINTER(LOG_ERR))) {
1099         crm_err("Schema upgrade from %s to %s failed: "
1100                 "XSL transform %s produced an invalid configuration",
1101                 schema->name, upgraded_schema->name, schema->transform);
1102         crm_log_xml_debug(final, "bad-transform-result");
1103         free_xml(final);
1104         return NULL;
1105     }
1106 
1107     crm_info("Schema upgrade from %s to %s succeeded",
1108              schema->name, upgraded_schema->name);
1109     return final;
1110 }
1111 
1112 /*!
1113  * \internal
1114  * \brief Get the schema list entry corresponding to XML configuration
1115  *
1116  * \param[in] xml  CIB XML to check
1117  *
1118  * \return List entry of schema configured in \p xml
1119  */
1120 static GList *
1121 get_configured_schema(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1122 {
1123     const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1124 
1125     pcmk__warn_if_schema_deprecated(schema_name);
1126     if (schema_name == NULL) {
1127         return NULL;
1128     }
1129     return pcmk__get_schema(schema_name);
1130 }
1131 
1132 /*!
1133  * \brief Update CIB XML to latest schema that validates it
1134  *
1135  * \param[in,out] xml              XML to update (may be freed and replaced
1136  *                                 after being transformed)
1137  * \param[in]     max_schema_name  If not NULL, do not update \p xml to any
1138  *                                 schema later than this one
1139  * \param[in]     transform        If false, do not update \p xml to any schema
1140  *                                 that requires an XSL transform
1141  * \param[in]     to_logs          If false, certain validation errors will be
1142  *                                 sent to stderr rather than logged
1143  *
1144  * \return Standard Pacemaker return code
1145  */
1146 int
1147 pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
     /* [previous][next][first][last][top][bottom][index][help] */
1148                     bool to_logs)
1149 {
1150     int max_stable_schemas = xml_latest_schema_index();
1151     int max_schema_index = 0;
1152     int rc = pcmk_rc_ok;
1153     GList *entry = NULL;
1154     pcmk__schema_t *best_schema = NULL;
1155     pcmk__schema_t *original_schema = NULL;
1156     xmlRelaxNGValidityErrorFunc error_handler = 
1157         to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1158 
1159     CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1160               return EINVAL);
1161 
1162     if (max_schema_name != NULL) {
1163         GList *max_entry = pcmk__get_schema(max_schema_name);
1164 
1165         if (max_entry != NULL) {
1166             pcmk__schema_t *max_schema = max_entry->data;
1167 
1168             max_schema_index = max_schema->schema_index;
1169         }
1170     }
1171     if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1172         max_schema_index = max_stable_schemas;
1173     }
1174 
1175     entry = get_configured_schema(*xml);
1176     if (entry == NULL) {
1177         // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1178         entry = known_schemas;
1179     } else {
1180         original_schema = entry->data;
1181         if (original_schema->schema_index >= max_schema_index) {
1182             return pcmk_rc_ok;
1183         }
1184     }
1185 
1186     for (; entry != NULL; entry = entry->next) {
1187         pcmk__schema_t *current_schema = entry->data;
1188         xmlNode *upgrade = NULL;
1189 
1190         if (current_schema->schema_index > max_schema_index) {
1191             break;
1192         }
1193 
1194         if (!validate_with(*xml, current_schema, error_handler,
1195                            GUINT_TO_POINTER(LOG_ERR))) {
1196             crm_debug("Schema %s does not validate", current_schema->name);
1197             if (best_schema != NULL) {
1198                 /* we've satisfied the validation, no need to check further */
1199                 break;
1200             }
1201             rc = pcmk_rc_schema_validation;
1202             continue; // Try again with the next higher schema
1203         }
1204 
1205         crm_debug("Schema %s validates", current_schema->name);
1206         rc = pcmk_rc_ok;
1207         best_schema = current_schema;
1208         if (current_schema->schema_index == max_schema_index) {
1209             break; // No further transformations possible
1210         }
1211 
1212         if (!transform || (current_schema->transform == NULL)
1213             || validate_with_silent(*xml, entry->next->data)) {
1214             /* The next schema either doesn't require a transform or validates
1215              * successfully even without the transform. Skip the transform and
1216              * try the next schema with the same XML.
1217              */
1218             continue;
1219         }
1220 
1221         upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1222         if (upgrade == NULL) {
1223             /* The transform failed, so this schema can't be used. Later
1224              * schemas are unlikely to validate, but try anyway until we
1225              * run out of options.
1226              */
1227             rc = pcmk_rc_transform_failed;
1228         } else {
1229             best_schema = current_schema;
1230             free_xml(*xml);
1231             *xml = upgrade;
1232         }
1233     }
1234 
1235     if (best_schema != NULL) {
1236         if ((original_schema == NULL)
1237             || (best_schema->schema_index > original_schema->schema_index)) {
1238             crm_info("%s the configuration schema to %s",
1239                      (transform? "Transformed" : "Upgraded"),
1240                      best_schema->name);
1241             crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1242         }
1243     }
1244     return rc;
1245 }
1246 
1247 int
1248 pcmk_update_configured_schema(xmlNode **xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1249 {
1250     return pcmk__update_configured_schema(xml, true);
1251 }
1252 
1253 /*!
1254  * \brief Update XML from its configured schema to the latest major series
1255  *
1256  * \param[in,out] xml      XML to update
1257  * \param[in]     to_logs  If false, certain validation errors will be
1258  *                         sent to stderr rather than logged
1259  *
1260  * \return Standard Pacemaker return code
1261  */
1262 int
1263 pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1264 {
1265     int rc = pcmk_rc_ok;
1266     char *original_schema_name = NULL;
1267 
1268     // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1269     const char *effective_original_name = "the first";
1270 
1271     int orig_version = -1;
1272     pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1273     GList *entry = NULL;
1274 
1275     CRM_CHECK(xml != NULL, return EINVAL);
1276 
1277     original_schema_name = crm_element_value_copy(*xml, PCMK_XA_VALIDATE_WITH);
1278     pcmk__warn_if_schema_deprecated(original_schema_name);
1279     entry = pcmk__get_schema(original_schema_name);
1280     if (entry != NULL) {
1281         pcmk__schema_t *original_schema = entry->data;
1282 
1283         effective_original_name = original_schema->name;
1284         orig_version = original_schema->schema_index;
1285     }
1286 
1287     if (orig_version < x_0_schema->schema_index) {
1288         // Current configuration schema is not acceptable, try to update
1289         xmlNode *converted = NULL;
1290         const char *new_schema_name = NULL;
1291         pcmk__schema_t *schema = NULL;
1292 
1293         entry = NULL;
1294         converted = pcmk__xml_copy(NULL, *xml);
1295         if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1296             new_schema_name = crm_element_value(converted,
1297                                                 PCMK_XA_VALIDATE_WITH);
1298             entry = pcmk__get_schema(new_schema_name);
1299         }
1300         schema = (entry == NULL)? NULL : entry->data;
1301 
1302         if ((schema == NULL)
1303             || (schema->schema_index < x_0_schema->schema_index)) {
1304             // Updated configuration schema is still not acceptable
1305 
1306             if ((orig_version == -1) || (schema == NULL)
1307                 || (schema->schema_index < orig_version)) {
1308                 // We couldn't validate any schema at all
1309                 if (to_logs) {
1310                     pcmk__config_err("Cannot upgrade configuration (claiming "
1311                                      "%s schema) to at least %s because it "
1312                                      "does not validate with any schema from "
1313                                      "%s to the latest",
1314                                      pcmk__s(original_schema_name, "no"),
1315                                      x_0_schema->name, effective_original_name);
1316                 } else {
1317                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1318                                     "%s schema) to at least %s because it "
1319                                     "does not validate with any schema from "
1320                                     "%s to the latest\n",
1321                                     pcmk__s(original_schema_name, "no"),
1322                                     x_0_schema->name, effective_original_name);
1323                 }
1324             } else {
1325                 // We updated configuration successfully, but still too low
1326                 if (to_logs) {
1327                     pcmk__config_err("Cannot upgrade configuration (claiming "
1328                                      "%s schema) to at least %s because it "
1329                                      "would not upgrade past %s",
1330                                      pcmk__s(original_schema_name, "no"),
1331                                      x_0_schema->name,
1332                                      pcmk__s(new_schema_name, "unspecified version"));
1333                 } else {
1334                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1335                                     "%s schema) to at least %s because it "
1336                                     "would not upgrade past %s\n",
1337                                     pcmk__s(original_schema_name, "no"),
1338                                     x_0_schema->name,
1339                                     pcmk__s(new_schema_name, "unspecified version"));
1340                 }
1341             }
1342 
1343             free_xml(converted);
1344             converted = NULL;
1345             rc = pcmk_rc_transform_failed;
1346 
1347         } else {
1348             // Updated configuration schema is acceptable
1349             free_xml(*xml);
1350             *xml = converted;
1351 
1352             if (schema->schema_index < xml_latest_schema_index()) {
1353                 if (to_logs) {
1354                     pcmk__config_warn("Configuration with %s schema was "
1355                                       "internally upgraded to acceptable (but "
1356                                       "not most recent) %s",
1357                                       pcmk__s(original_schema_name, "no"),
1358                                       schema->name);
1359                 }
1360             } else if (to_logs) {
1361                 crm_info("Configuration with %s schema was internally "
1362                          "upgraded to latest version %s",
1363                          pcmk__s(original_schema_name, "no"),
1364                          schema->name);
1365             }
1366         }
1367 
1368     } else {
1369         // @COMPAT the none schema is deprecated since 2.1.8
1370         pcmk__schema_t *none_schema = NULL;
1371 
1372         entry = pcmk__get_schema(PCMK_VALUE_NONE);
1373         pcmk__assert((entry != NULL) && (entry->data != NULL));
1374 
1375         none_schema = entry->data;
1376         if (!to_logs && (orig_version >= none_schema->schema_index)) {
1377             fprintf(stderr, "Schema validation of configuration is "
1378                             "disabled (support for " PCMK_XA_VALIDATE_WITH
1379                             " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1380                             " and will be removed in a future release)\n");
1381         }
1382     }
1383 
1384     free(original_schema_name);
1385     return rc;
1386 }
1387 
1388 /*!
1389  * \internal
1390  * \brief Return a list of all schema files and any associated XSLT files
1391  *        later than the given one
1392  * \brief Return a list of all schema versions later than the given one
1393  *
1394  * \param[in] schema The schema to compare against (for example,
1395  *                   "pacemaker-3.1.rng" or "pacemaker-3.1")
1396  *
1397  * \note The caller is responsible for freeing both the returned list and
1398  *       the elements of the list
1399  */
1400 GList *
1401 pcmk__schema_files_later_than(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1402 {
1403     GList *lst = NULL;
1404     pcmk__schema_version_t ver;
1405 
1406     if (!version_from_filename(name, &ver)) {
1407         return lst;
1408     }
1409 
1410     for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1411          iter != NULL; iter = iter->prev) {
1412         pcmk__schema_t *schema = iter->data;
1413         char *s = NULL;
1414 
1415         if (schema_cmp(ver, schema->version) != -1) {
1416             continue;
1417         }
1418 
1419         s = crm_strdup_printf("%s.rng", schema->name);
1420         lst = g_list_prepend(lst, s);
1421 
1422         if (schema->transform != NULL) {
1423             char *xform = crm_strdup_printf("%s.xsl", schema->transform);
1424             lst = g_list_prepend(lst, xform);
1425         }
1426 
1427         if (schema->transform_enter != NULL) {
1428             char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter);
1429 
1430             lst = g_list_prepend(lst, enter);
1431 
1432             if (schema->transform_onleave) {
1433                 int last_dash = strrchr(enter, '-') - enter;
1434                 char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter);
1435 
1436                 lst = g_list_prepend(lst, leave);
1437             }
1438         }
1439     }
1440 
1441     return lst;
1442 }
1443 
1444 static void
1445 append_href(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1446 {
1447     GList **list = user_data;
1448     char *href = crm_element_value_copy(xml, "href");
1449 
1450     if (href == NULL) {
1451         return;
1452     }
1453     *list = g_list_prepend(*list, href);
1454 }
1455 
1456 static void
1457 external_refs_in_schema(GList **list, const char *contents)
     /* [previous][next][first][last][top][bottom][index][help] */
1458 {
1459     /* local-name()= is needed to ignore the xmlns= setting at the top of
1460      * the XML file.  Otherwise, the xpath query will always return nothing.
1461      */
1462     const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1463     xmlNode *xml = pcmk__xml_parse(contents);
1464 
1465     crm_foreach_xpath_result(xml, search, append_href, list);
1466     free_xml(xml);
1467 }
1468 
1469 static int
1470 read_file_contents(const char *file, char **contents)
     /* [previous][next][first][last][top][bottom][index][help] */
1471 {
1472     int rc = pcmk_rc_ok;
1473     char *path = NULL;
1474 
1475     if (pcmk__ends_with(file, ".rng")) {
1476         path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file);
1477     } else {
1478         path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file);
1479     }
1480 
1481     rc = pcmk__file_contents(path, contents);
1482 
1483     free(path);
1484     return rc;
1485 }
1486 
1487 static void
1488 add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
     /* [previous][next][first][last][top][bottom][index][help] */
1489 {
1490     char *contents = NULL;
1491     char *path = NULL;
1492     xmlNode *file_node = NULL;
1493     GList *includes = NULL;
1494     int rc = pcmk_rc_ok;
1495 
1496     /* If we already included this file, don't do so again. */
1497     if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1498         return;
1499     }
1500 
1501     /* Ensure whatever file we were given has a suffix we know about.  If not,
1502      * just assume it's an RNG file.
1503      */
1504     if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
1505         path = crm_strdup_printf("%s.rng", file);
1506     } else {
1507         path = pcmk__str_copy(file);
1508     }
1509 
1510     rc = read_file_contents(path, &contents);
1511     if (rc != pcmk_rc_ok || contents == NULL) {
1512         crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1513         free(path);
1514         return;
1515     }
1516 
1517     /* Create a new <file path="..."> node with the contents of the file
1518      * as a CDATA block underneath it.
1519      */
1520     file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
1521     crm_xml_add(file_node, PCMK_XA_PATH, path);
1522     *already_included = g_list_prepend(*already_included, path);
1523 
1524     xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
1525                                             strlen(contents)));
1526 
1527     /* Scan the file for any <externalRef> or <include> nodes and build up
1528      * a list of the files they reference.
1529      */
1530     external_refs_in_schema(&includes, contents);
1531 
1532     /* For each referenced file, recurse to add it (and potentially anything it
1533      * references, ...) to the XML.
1534      */
1535     for (GList *iter = includes; iter != NULL; iter = iter->next) {
1536         add_schema_file_to_xml(parent, iter->data, already_included);
1537     }
1538 
1539     free(contents);
1540     g_list_free_full(includes, free);
1541 }
1542 
1543 /*!
1544  * \internal
1545  * \brief Add an XML schema file and all the files it references as children
1546  *        of a given XML node
1547  *
1548  * \param[in,out] parent            The parent XML node
1549  * \param[in] name                  The schema version to compare against
1550  *                                  (for example, "pacemaker-3.1" or "pacemaker-3.1.rng")
1551  * \param[in,out] already_included  A list of names that have already been added
1552  *                                  to the parent node.
1553  *
1554  * \note The caller is responsible for freeing both the returned list and
1555  *       the elements of the list
1556  */
1557 void
1558 pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
     /* [previous][next][first][last][top][bottom][index][help] */
1559 {
1560     xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1561 
1562     crm_xml_add(schema_node, PCMK_XA_VERSION, name);
1563     add_schema_file_to_xml(schema_node, name, already_included);
1564 
1565     if (schema_node->children == NULL) {
1566         // Not needed if empty. May happen if name was invalid, for example.
1567         free_xml(schema_node);
1568     }
1569 }
1570 
1571 /*!
1572  * \internal
1573  * \brief Return the directory containing any extra schema files that a
1574  *        Pacemaker Remote node fetched from the cluster
1575  */
1576 const char *
1577 pcmk__remote_schema_dir(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1578 {
1579     const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIRECTORY);
1580 
1581     if (pcmk__str_empty(dir)) {
1582         return PCMK__REMOTE_SCHEMA_DIR;
1583     }
1584 
1585     return dir;
1586 }
1587 
1588 /*!
1589  * \internal
1590  * \brief Warn if a given validation schema is deprecated
1591  *
1592  * \param[in] Schema name to check
1593  */
1594 void
1595 pcmk__warn_if_schema_deprecated(const char *schema)
     /* [previous][next][first][last][top][bottom][index][help] */
1596 {
1597     if ((schema == NULL) ||
1598         pcmk__strcase_any_of(schema, "pacemaker-next", PCMK_VALUE_NONE, NULL)) {
1599         pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1600                           "deprecated and will be removed in a future release "
1601                           "without the possibility of upgrades (manually edit "
1602                           "to use a supported schema)", pcmk__s(schema, ""));
1603     }
1604 }
1605 
1606 // Deprecated functions kept only for backward API compatibility
1607 // LCOV_EXCL_START
1608 
1609 #include <crm/common/xml_compat.h>
1610 
1611 const char *
1612 xml_latest_schema(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1613 {
1614     return pcmk__highest_schema_name();
1615 }
1616 
1617 const char *
1618 get_schema_name(int version)
     /* [previous][next][first][last][top][bottom][index][help] */
1619 {
1620     pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
1621 
1622     return (schema != NULL)? schema->name : "unknown";
1623 }
1624 
1625 int
1626 get_schema_version(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1627 {
1628     int lpc = 0;
1629 
1630     if (name == NULL) {
1631         name = PCMK_VALUE_NONE;
1632     }
1633 
1634     for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
1635         pcmk__schema_t *schema = iter->data;
1636 
1637         if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
1638             return lpc;
1639         }
1640 
1641         lpc++;
1642     }
1643 
1644     return -1;
1645 }
1646 
1647 int
1648 update_validation(xmlNode **xml, int *best, int max, gboolean transform,
     /* [previous][next][first][last][top][bottom][index][help] */
1649                   gboolean to_logs)
1650 {
1651     int rc = pcmk__update_schema(xml, get_schema_name(max), transform, to_logs);
1652 
1653     if ((best != NULL) && (xml != NULL) && (rc == pcmk_rc_ok)) {
1654         const char *schema_name = crm_element_value(*xml,
1655                                                     PCMK_XA_VALIDATE_WITH);
1656         GList *schema_entry = pcmk__get_schema(schema_name);
1657 
1658         if (schema_entry != NULL) {
1659             *best = ((pcmk__schema_t *)(schema_entry->data))->schema_index;
1660         }
1661     }
1662 
1663     return pcmk_rc2legacy(rc);
1664 }
1665 
1666 gboolean
1667 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1668 {
1669     bool rc = pcmk__validate_xml(xml_blob, validation,
1670                                  to_logs? (xmlRelaxNGValidityErrorFunc) xml_log : NULL,
1671                                  GUINT_TO_POINTER(LOG_ERR));
1672     return rc? TRUE : FALSE;
1673 }
1674 
1675 static void
1676 dump_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
1677 {
1678 
1679     FILE *fp = NULL;
1680     int ch, line = 0;
1681 
1682     CRM_CHECK(filename != NULL, return);
1683 
1684     fp = fopen(filename, "r");
1685     if (fp == NULL) {
1686         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
1687         return;
1688     }
1689 
1690     fprintf(stderr, "%4d ", ++line);
1691     do {
1692         ch = getc(fp);
1693         if (ch == EOF) {
1694             putc('\n', stderr);
1695             break;
1696         } else if (ch == '\n') {
1697             fprintf(stderr, "\n%4d ", ++line);
1698         } else {
1699             putc(ch, stderr);
1700         }
1701     } while (1);
1702 
1703     fclose(fp);
1704 }
1705 
1706 gboolean
1707 validate_xml_verbose(const xmlNode *xml_blob)
     /* [previous][next][first][last][top][bottom][index][help] */
1708 {
1709     int fd = 0;
1710     xmlDoc *doc = NULL;
1711     xmlNode *xml = NULL;
1712     gboolean rc = FALSE;
1713     char *filename = NULL;
1714 
1715     filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
1716 
1717     umask(S_IWGRP | S_IWOTH | S_IROTH);
1718     fd = mkstemp(filename);
1719     pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
1720 
1721     dump_file(filename);
1722 
1723     doc = xmlReadFile(filename, NULL, 0);
1724     xml = xmlDocGetRootElement(doc);
1725     rc = pcmk__validate_xml(xml, NULL, NULL, NULL);
1726     free_xml(xml);
1727 
1728     unlink(filename);
1729     free(filename);
1730 
1731     return rc? TRUE : FALSE;
1732 }
1733 
1734 gboolean
1735 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1736 {
1737     int rc = pcmk__update_configured_schema(xml, to_logs);
1738 
1739     if (best_version != NULL) {
1740         const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
1741 
1742         if (name == NULL) {
1743             *best_version = -1;
1744         } else {
1745             GList *entry = pcmk__get_schema(name);
1746             pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1747 
1748             *best_version = (schema == NULL)? -1 : schema->schema_index;
1749         }
1750     }
1751     return (rc == pcmk_rc_ok)? TRUE: FALSE;
1752 }
1753 
1754 // LCOV_EXCL_STOP
1755 // End deprecated API

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