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

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