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. wrap_libxslt
  12. transform_filter
  13. compare_transforms
  14. free_transform_list
  15. load_transforms_from_dir
  16. pcmk__load_schemas_from_dir
  17. schema_sort_GCompareFunc
  18. pcmk__sort_schemas
  19. pcmk__schema_init
  20. validate_with_relaxng
  21. free_schema
  22. pcmk__schema_cleanup
  23. pcmk__get_schema
  24. pcmk__cmp_schemas_by_name
  25. validate_with
  26. validate_with_silent
  27. pcmk__validate_xml
  28. pcmk__configured_schema_validates
  29. G_GNUC_PRINTF
  30. apply_transformation
  31. apply_upgrade
  32. get_configured_schema
  33. pcmk__update_schema
  34. pcmk_update_configured_schema
  35. pcmk__update_configured_schema
  36. pcmk__schema_files_later_than
  37. append_href
  38. external_refs_in_schema
  39. read_file_contents
  40. add_schema_file_to_xml
  41. pcmk__build_schema_xml_node
  42. pcmk__remote_schema_dir
  43. pcmk__warn_if_schema_deprecated
  44. cli_config_update

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

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