root/lib/common/schemas.c

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

DEFINITIONS

This source file includes following definitions.
  1. xml_log
  2. xml_latest_schema_index
  3. xml_minimum_schema_index
  4. xml_latest_schema
  5. version_from_filename
  6. schema_filter
  7. schema_sort
  8. add_schema
  9. add_schema_by_version
  10. wrap_libxslt
  11. crm_schema_init
  12. validate_with_relaxng
  13. crm_schema_cleanup
  14. validate_with
  15. validate_with_silent
  16. dump_file
  17. validate_xml_verbose
  18. validate_xml
  19. pcmk__validate_xml
  20. cib_upgrade_err
  21. apply_transformation
  22. apply_upgrade
  23. get_schema_name
  24. get_schema_version
  25. update_validation
  26. cli_config_update

   1 /*
   2  * Copyright 2004-2023 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/msg_xml.h>
  26 #include <crm/common/xml.h>
  27 #include <crm/common/xml_internal.h>  /* PCMK__XML_LOG_BASE */
  28 
  29 typedef struct {
  30     unsigned char v[2];
  31 } schema_version_t;
  32 
  33 #define SCHEMA_ZERO { .v = { 0, 0 } }
  34 
  35 #define schema_scanf(s, prefix, version, suffix) \
  36     sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
  37 
  38 #define schema_strdup_printf(prefix, version, suffix) \
  39     crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
  40 
  41 typedef struct {
  42     xmlRelaxNGPtr rng;
  43     xmlRelaxNGValidCtxtPtr valid;
  44     xmlRelaxNGParserCtxtPtr parser;
  45 } relaxng_ctx_cache_t;
  46 
  47 enum schema_validator_e {
  48     schema_validator_none,
  49     schema_validator_rng
  50 };
  51 
  52 struct schema_s {
  53     char *name;
  54     char *transform;
  55     void *cache;
  56     enum schema_validator_e validator;
  57     int after_transform;
  58     schema_version_t version;
  59     char *transform_enter;
  60     bool transform_onleave;
  61 };
  62 
  63 static struct schema_s *known_schemas = NULL;
  64 static int xml_schema_max = 0;
  65 static bool silent_logging = FALSE;
  66 
  67 static void
  68 xml_log(int priority, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
  69 G_GNUC_PRINTF(2, 3);
  70 
  71 static void
  72 xml_log(int priority, const char *fmt, ...)
  73 {
  74     va_list ap;
  75 
  76     va_start(ap, fmt);
  77     if (silent_logging == FALSE) {
  78         /* XXX should not this enable dechunking as well? */
  79         PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
  80     }
  81     va_end(ap);
  82 }
  83 
  84 static int
  85 xml_latest_schema_index(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  86 {
  87     // @COMPAT: pacemaker-next is deprecated since 2.1.5
  88     return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
  89 }
  90 
  91 static int
  92 xml_minimum_schema_index(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     static int best = 0;
  95     if (best == 0) {
  96         int lpc = 0;
  97 
  98         best = xml_latest_schema_index();
  99         for (lpc = best; lpc > 0; lpc--) {
 100             if (known_schemas[lpc].version.v[0]
 101                 < known_schemas[best].version.v[0]) {
 102                 return best;
 103             } else {
 104                 best = lpc;
 105             }
 106         }
 107         best = xml_latest_schema_index();
 108     }
 109     return best;
 110 }
 111 
 112 const char *
 113 xml_latest_schema(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 114 {
 115     return get_schema_name(xml_latest_schema_index());
 116 }
 117 
 118 static inline bool
 119 version_from_filename(const char *filename, schema_version_t *version)
     /* [previous][next][first][last][top][bottom][index][help] */
 120 {
 121     int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
 122 
 123     return (rc == 2);
 124 }
 125 
 126 static int
 127 schema_filter(const struct dirent *a)
     /* [previous][next][first][last][top][bottom][index][help] */
 128 {
 129     int rc = 0;
 130     schema_version_t version = SCHEMA_ZERO;
 131 
 132     if (strstr(a->d_name, "pacemaker-") != a->d_name) {
 133         /* crm_trace("%s - wrong prefix", a->d_name); */
 134 
 135     } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
 136         /* crm_trace("%s - wrong suffix", a->d_name); */
 137 
 138     } else if (!version_from_filename(a->d_name, &version)) {
 139         /* crm_trace("%s - wrong format", a->d_name); */
 140 
 141     } else {
 142         /* crm_debug("%s - candidate", a->d_name); */
 143         rc = 1;
 144     }
 145 
 146     return rc;
 147 }
 148 
 149 static int
 150 schema_sort(const struct dirent **a, const struct dirent **b)
     /* [previous][next][first][last][top][bottom][index][help] */
 151 {
 152     schema_version_t a_version = SCHEMA_ZERO;
 153     schema_version_t b_version = SCHEMA_ZERO;
 154 
 155     if (!version_from_filename(a[0]->d_name, &a_version)
 156         || !version_from_filename(b[0]->d_name, &b_version)) {
 157         // Shouldn't be possible, but makes static analysis happy
 158         return 0;
 159     }
 160 
 161     for (int i = 0; i < 2; ++i) {
 162         if (a_version.v[i] < b_version.v[i]) {
 163             return -1;
 164         } else if (a_version.v[i] > b_version.v[i]) {
 165             return 1;
 166         }
 167     }
 168     return 0;
 169 }
 170 
 171 /*!
 172  * \internal
 173  * \brief Add given schema + auxiliary data to internal bookkeeping.
 174  *
 175  * \note When providing \p version, should not be called directly but
 176  *       through \c add_schema_by_version.
 177  */
 178 static void
 179 add_schema(enum schema_validator_e validator, const schema_version_t *version,
     /* [previous][next][first][last][top][bottom][index][help] */
 180            const char *name, const char *transform,
 181            const char *transform_enter, bool transform_onleave,
 182            int after_transform)
 183 {
 184     int last = xml_schema_max;
 185     bool have_version = FALSE;
 186 
 187     xml_schema_max++;
 188     known_schemas = pcmk__realloc(known_schemas,
 189                                   xml_schema_max * sizeof(struct schema_s));
 190     CRM_ASSERT(known_schemas != NULL);
 191     memset(known_schemas+last, 0, sizeof(struct schema_s));
 192     known_schemas[last].validator = validator;
 193     known_schemas[last].after_transform = after_transform;
 194 
 195     for (int i = 0; i < 2; ++i) {
 196         known_schemas[last].version.v[i] = version->v[i];
 197         if (version->v[i]) {
 198             have_version = TRUE;
 199         }
 200     }
 201     if (have_version) {
 202         known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
 203     } else {
 204         CRM_ASSERT(name);
 205         schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
 206         known_schemas[last].name = strdup(name);
 207     }
 208 
 209     if (transform) {
 210         known_schemas[last].transform = strdup(transform);
 211     }
 212     if (transform_enter) {
 213         known_schemas[last].transform_enter = strdup(transform_enter);
 214     }
 215     known_schemas[last].transform_onleave = transform_onleave;
 216     if (after_transform == 0) {
 217         after_transform = xml_schema_max;  /* upgrade is a one-way */
 218     }
 219     known_schemas[last].after_transform = after_transform;
 220 
 221     if (known_schemas[last].after_transform < 0) {
 222         crm_debug("Added supported schema %d: %s",
 223                   last, known_schemas[last].name);
 224 
 225     } else if (known_schemas[last].transform) {
 226         crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
 227                   last, known_schemas[last].name,
 228                   known_schemas[last].after_transform,
 229                   known_schemas[last].transform);
 230 
 231     } else {
 232         crm_debug("Added supported schema %d: %s (upgrades to %d)",
 233                   last, known_schemas[last].name,
 234                   known_schemas[last].after_transform);
 235     }
 236 }
 237 
 238 /*!
 239  * \internal
 240  * \brief Add version-specified schema + auxiliary data to internal bookkeeping.
 241  * \return Standard Pacemaker return value (the only possible values are
 242  * \c ENOENT when no upgrade schema is associated, or \c pcmk_rc_ok otherwise.
 243  *
 244  * \note There's no reliance on the particular order of schemas entering here.
 245  *
 246  * \par A bit of theory
 247  * We track 3 XSLT stylesheets that differ per usage:
 248  * - "upgrade":
 249  *   . sparsely spread over the sequence of all available schemas,
 250  *     as they are only relevant when major version of the schema
 251  *     is getting bumped -- in that case, it MUST be set
 252  *   . name convention:  upgrade-X.Y.xsl
 253  * - "upgrade-enter":
 254  *   . may only accompany "upgrade" occurrence, but doesn't need to
 255  *     be present anytime such one is, i.e., it MAY not be set when
 256  *     "upgrade" is
 257  *   . name convention:  upgrade-X.Y-enter.xsl,
 258  *     when not present: upgrade-enter.xsl
 259  * - "upgrade-leave":
 260  *   . like "upgrade-enter", but SHOULD be present whenever
 261  *     "upgrade-enter" is (and vice versa, but that's only
 262  *     to prevent confusion based on observing the files,
 263  *     it would get ignored regardless)
 264  *   . name convention:  (see "upgrade-enter")
 265  */
 266 static int
 267 add_schema_by_version(const schema_version_t *version, int next,
     /* [previous][next][first][last][top][bottom][index][help] */
 268                       bool transform_expected)
 269 {
 270     bool transform_onleave = FALSE;
 271     int rc = pcmk_rc_ok;
 272     struct stat s;
 273     char *xslt = NULL,
 274          *transform_upgrade = NULL,
 275          *transform_enter = NULL;
 276 
 277     /* prologue for further transform_expected handling */
 278     if (transform_expected) {
 279         /* check if there's suitable "upgrade" stylesheet */
 280         transform_upgrade = schema_strdup_printf("upgrade-", *version, );
 281         xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 282                                        transform_upgrade);
 283     }
 284 
 285     if (!transform_expected) {
 286         /* jump directly to the end */
 287 
 288     } else if (stat(xslt, &s) == 0) {
 289         /* perhaps there's also a targeted "upgrade-enter" stylesheet */
 290         transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
 291         free(xslt);
 292         xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 293                                        transform_enter);
 294         if (stat(xslt, &s) != 0) {
 295             /* or initially, at least a generic one */
 296             crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
 297             free(xslt);
 298             free(transform_enter);
 299             transform_enter = strdup("upgrade-enter");
 300             xslt = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 301                                            transform_enter);
 302             if (stat(xslt, &s) != 0) {
 303                 crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
 304                 free(xslt);
 305                 xslt = NULL;
 306             }
 307         }
 308         /* xslt contains full path to "upgrade-enter" stylesheet */
 309         if (xslt != NULL) {
 310             /* then there should be "upgrade-leave" counterpart (enter->leave) */
 311             memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
 312             transform_onleave = (stat(xslt, &s) == 0);
 313             free(xslt);
 314         } else {
 315             free(transform_enter);
 316             transform_enter = NULL;
 317         }
 318 
 319     } else {
 320         crm_err("Upgrade transform %s not found", xslt);
 321         free(xslt);
 322         free(transform_upgrade);
 323         transform_upgrade = NULL;
 324         next = -1;
 325         rc = ENOENT;
 326     }
 327 
 328     add_schema(schema_validator_rng, version, NULL,
 329                transform_upgrade, transform_enter, transform_onleave, next);
 330 
 331     free(transform_upgrade);
 332     free(transform_enter);
 333 
 334     return rc;
 335 }
 336 
 337 static void
 338 wrap_libxslt(bool finalize)
     /* [previous][next][first][last][top][bottom][index][help] */
 339 {
 340     static xsltSecurityPrefsPtr secprefs;
 341     int ret = 0;
 342 
 343     /* security framework preferences */
 344     if (!finalize) {
 345         CRM_ASSERT(secprefs == NULL);
 346         secprefs = xsltNewSecurityPrefs();
 347         ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
 348                                    xsltSecurityForbid)
 349               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
 350                                      xsltSecurityForbid)
 351               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
 352                                      xsltSecurityForbid)
 353               | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
 354                                      xsltSecurityForbid);
 355         if (ret != 0) {
 356             return;
 357         }
 358     } else {
 359         xsltFreeSecurityPrefs(secprefs);
 360         secprefs = NULL;
 361     }
 362 
 363     /* cleanup only */
 364     if (finalize) {
 365         xsltCleanupGlobals();
 366     }
 367 }
 368 
 369 /*!
 370  * \internal
 371  * \brief Load pacemaker schemas into cache
 372  *
 373  * \note This currently also serves as an entry point for the
 374  *       generic initialization of the libxslt library.
 375  */
 376 void
 377 crm_schema_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 378 {
 379     int lpc, max;
 380     char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng);
 381     struct dirent **namelist = NULL;
 382     const schema_version_t zero = SCHEMA_ZERO;
 383 
 384     wrap_libxslt(false);
 385 
 386     max = scandir(base, &namelist, schema_filter, schema_sort);
 387     if (max < 0) {
 388         crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
 389         free(base);
 390 
 391     } else {
 392         free(base);
 393         for (lpc = 0; lpc < max; lpc++) {
 394             bool transform_expected = FALSE;
 395             int next = 0;
 396             schema_version_t version = SCHEMA_ZERO;
 397 
 398             if (!version_from_filename(namelist[lpc]->d_name, &version)) {
 399                 // Shouldn't be possible, but makes static analysis happy
 400                 crm_err("Skipping schema '%s': could not parse version",
 401                         namelist[lpc]->d_name);
 402                 continue;
 403             }
 404             if ((lpc + 1) < max) {
 405                 schema_version_t next_version = SCHEMA_ZERO;
 406 
 407                 if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
 408                         && (version.v[0] < next_version.v[0])) {
 409                     transform_expected = TRUE;
 410                 }
 411 
 412             } else {
 413                 next = -1;
 414             }
 415             if (add_schema_by_version(&version, next, transform_expected)
 416                     == ENOENT) {
 417                 break;
 418             }
 419         }
 420 
 421         for (lpc = 0; lpc < max; lpc++) {
 422             free(namelist[lpc]);
 423         }
 424         free(namelist);
 425     }
 426 
 427     // @COMPAT: Deprecated since 2.1.5
 428     add_schema(schema_validator_rng, &zero, "pacemaker-next",
 429                NULL, NULL, FALSE, -1);
 430 
 431     add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
 432                NULL, NULL, FALSE, -1);
 433 }
 434 
 435 static gboolean
 436 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
     /* [previous][next][first][last][top][bottom][index][help] */
 437                       relaxng_ctx_cache_t **cached_ctx)
 438 {
 439     int rc = 0;
 440     gboolean valid = TRUE;
 441     relaxng_ctx_cache_t *ctx = NULL;
 442 
 443     CRM_CHECK(doc != NULL, return FALSE);
 444     CRM_CHECK(relaxng_file != NULL, return FALSE);
 445 
 446     if (cached_ctx && *cached_ctx) {
 447         ctx = *cached_ctx;
 448 
 449     } else {
 450         crm_debug("Creating RNG parser context");
 451         ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
 452 
 453         ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
 454         CRM_CHECK(ctx->parser != NULL, goto cleanup);
 455 
 456         if (error_handler) {
 457             xmlRelaxNGSetParserErrors(ctx->parser,
 458                                       (xmlRelaxNGValidityErrorFunc) error_handler,
 459                                       (xmlRelaxNGValidityWarningFunc) error_handler,
 460                                       error_handler_context);
 461         } else {
 462             xmlRelaxNGSetParserErrors(ctx->parser,
 463                                       (xmlRelaxNGValidityErrorFunc) fprintf,
 464                                       (xmlRelaxNGValidityWarningFunc) fprintf,
 465                                       stderr);
 466         }
 467 
 468         ctx->rng = xmlRelaxNGParse(ctx->parser);
 469         CRM_CHECK(ctx->rng != NULL,
 470                   crm_err("Could not find/parse %s", relaxng_file);
 471                   goto cleanup);
 472 
 473         ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
 474         CRM_CHECK(ctx->valid != NULL, goto cleanup);
 475 
 476         if (error_handler) {
 477             xmlRelaxNGSetValidErrors(ctx->valid,
 478                                      (xmlRelaxNGValidityErrorFunc) error_handler,
 479                                      (xmlRelaxNGValidityWarningFunc) error_handler,
 480                                      error_handler_context);
 481         } else {
 482             xmlRelaxNGSetValidErrors(ctx->valid,
 483                                      (xmlRelaxNGValidityErrorFunc) fprintf,
 484                                      (xmlRelaxNGValidityWarningFunc) fprintf,
 485                                      stderr);
 486         }
 487     }
 488 
 489     rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
 490     if (rc > 0) {
 491         valid = FALSE;
 492 
 493     } else if (rc < 0) {
 494         crm_err("Internal libxml error during validation");
 495     }
 496 
 497   cleanup:
 498 
 499     if (cached_ctx) {
 500         *cached_ctx = ctx;
 501 
 502     } else {
 503         if (ctx->parser != NULL) {
 504             xmlRelaxNGFreeParserCtxt(ctx->parser);
 505         }
 506         if (ctx->valid != NULL) {
 507             xmlRelaxNGFreeValidCtxt(ctx->valid);
 508         }
 509         if (ctx->rng != NULL) {
 510             xmlRelaxNGFree(ctx->rng);
 511         }
 512         free(ctx);
 513     }
 514 
 515     return valid;
 516 }
 517 
 518 /*!
 519  * \internal
 520  * \brief Clean up global memory associated with XML schemas
 521  */
 522 void
 523 crm_schema_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 524 {
 525     int lpc;
 526     relaxng_ctx_cache_t *ctx = NULL;
 527 
 528     for (lpc = 0; lpc < xml_schema_max; lpc++) {
 529 
 530         switch (known_schemas[lpc].validator) {
 531             case schema_validator_none: // not cached
 532                 break;
 533             case schema_validator_rng: // cached
 534                 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
 535                 if (ctx == NULL) {
 536                     break;
 537                 }
 538                 if (ctx->parser != NULL) {
 539                     xmlRelaxNGFreeParserCtxt(ctx->parser);
 540                 }
 541                 if (ctx->valid != NULL) {
 542                     xmlRelaxNGFreeValidCtxt(ctx->valid);
 543                 }
 544                 if (ctx->rng != NULL) {
 545                     xmlRelaxNGFree(ctx->rng);
 546                 }
 547                 free(ctx);
 548                 known_schemas[lpc].cache = NULL;
 549                 break;
 550         }
 551         free(known_schemas[lpc].name);
 552         free(known_schemas[lpc].transform);
 553         free(known_schemas[lpc].transform_enter);
 554     }
 555     free(known_schemas);
 556     known_schemas = NULL;
 557 
 558     wrap_libxslt(true);
 559 }
 560 
 561 static gboolean
 562 validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
     /* [previous][next][first][last][top][bottom][index][help] */
 563 {
 564     gboolean valid = FALSE;
 565     char *file = NULL;
 566     struct schema_s *schema = NULL;
 567     relaxng_ctx_cache_t **cache = NULL;
 568 
 569     if (method < 0) {
 570         return FALSE;
 571     }
 572 
 573     schema = &(known_schemas[method]);
 574     if (schema->validator == schema_validator_none) {
 575         return TRUE;
 576     }
 577 
 578     if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
 579         crm_warn("The pacemaker-next schema is deprecated and will be removed "
 580                  "in a future release.");
 581     }
 582 
 583     file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
 584                                    schema->name);
 585 
 586     crm_trace("Validating with %s (type=%d)",
 587               pcmk__s(file, "missing schema"), schema->validator);
 588     switch (schema->validator) {
 589         case schema_validator_rng:
 590             cache = (relaxng_ctx_cache_t **) &(schema->cache);
 591             valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
 592             break;
 593         default:
 594             crm_err("Unknown validator type: %d",
 595                     known_schemas[method].validator);
 596             break;
 597     }
 598 
 599     free(file);
 600     return valid;
 601 }
 602 
 603 static bool
 604 validate_with_silent(xmlNode *xml, int method)
     /* [previous][next][first][last][top][bottom][index][help] */
 605 {
 606     bool rc, sl_backup = silent_logging;
 607     silent_logging = TRUE;
 608     rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
 609     silent_logging = sl_backup;
 610     return rc;
 611 }
 612 
 613 static void
 614 dump_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 615 {
 616 
 617     FILE *fp = NULL;
 618     int ch, line = 0;
 619 
 620     CRM_CHECK(filename != NULL, return);
 621 
 622     fp = fopen(filename, "r");
 623     if (fp == NULL) {
 624         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
 625         return;
 626     }
 627 
 628     fprintf(stderr, "%4d ", ++line);
 629     do {
 630         ch = getc(fp);
 631         if (ch == EOF) {
 632             putc('\n', stderr);
 633             break;
 634         } else if (ch == '\n') {
 635             fprintf(stderr, "\n%4d ", ++line);
 636         } else {
 637             putc(ch, stderr);
 638         }
 639     } while (1);
 640 
 641     fclose(fp);
 642 }
 643 
 644 gboolean
 645 validate_xml_verbose(const xmlNode *xml_blob)
     /* [previous][next][first][last][top][bottom][index][help] */
 646 {
 647     int fd = 0;
 648     xmlDoc *doc = NULL;
 649     xmlNode *xml = NULL;
 650     gboolean rc = FALSE;
 651     char *filename = NULL;
 652 
 653     filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
 654 
 655     umask(S_IWGRP | S_IWOTH | S_IROTH);
 656     fd = mkstemp(filename);
 657     write_xml_fd(xml_blob, filename, fd, FALSE);
 658 
 659     dump_file(filename);
 660 
 661     doc = xmlReadFile(filename, NULL, 0);
 662     xml = xmlDocGetRootElement(doc);
 663     rc = validate_xml(xml, NULL, FALSE);
 664     free_xml(xml);
 665 
 666     unlink(filename);
 667     free(filename);
 668 
 669     return rc;
 670 }
 671 
 672 gboolean
 673 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 674 {
 675     return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
 676 }
 677 
 678 gboolean
 679 pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
     /* [previous][next][first][last][top][bottom][index][help] */
 680 {
 681     int version = 0;
 682 
 683     CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
 684 
 685     if (validation == NULL) {
 686         validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
 687     }
 688 
 689     if (validation == NULL) {
 690         int lpc = 0;
 691         bool valid = FALSE;
 692 
 693         for (lpc = 0; lpc < xml_schema_max; lpc++) {
 694             if (validate_with(xml_blob, lpc, NULL, NULL)) {
 695                 valid = TRUE;
 696                 crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
 697                             known_schemas[lpc].name);
 698                 crm_info("XML validated against %s", known_schemas[lpc].name);
 699                 if(known_schemas[lpc].after_transform == 0) {
 700                     break;
 701                 }
 702             }
 703         }
 704 
 705         return valid;
 706     }
 707 
 708     version = get_schema_version(validation);
 709     if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
 710         return TRUE;
 711     } else if (version < xml_schema_max) {
 712         return validate_with(xml_blob, version, error_handler, error_handler_context);
 713     }
 714 
 715     crm_err("Unknown validator: %s", validation);
 716     return FALSE;
 717 }
 718 
 719 static void
 720 cib_upgrade_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
 721 G_GNUC_PRINTF(2, 3);
 722 
 723 /* With this arrangement, an attempt to identify the message severity
 724    as explicitly signalled directly from XSLT is performed in rather
 725    a smart way (no reliance on formatting string + arguments being
 726    always specified as ["%s", purposeful_string], as it can also be
 727    ["%s: %s", some_prefix, purposeful_string] etc. so every argument
 728    pertaining %s specifier is investigated), and if such a mark found,
 729    the respective level is determined and, when the messages are to go
 730    to the native logs, the mark itself gets dropped
 731    (by the means of string shift).
 732 
 733    NOTE: whether the native logging is the right sink is decided per
 734          the ctx parameter -- NULL denotes this case, otherwise it
 735          carries a pointer to the numeric expression of the desired
 736          target logging level (messages with higher level will be
 737          suppressed)
 738 
 739    NOTE: on some architectures, this string shift may not have any
 740          effect, but that's an acceptable tradeoff
 741 
 742    The logging level for not explicitly designated messages
 743    (suspicious, likely internal errors or some runaways) is
 744    LOG_WARNING.
 745  */
 746 static void
 747 cib_upgrade_err(void *ctx, const char *fmt, ...)
 748 {
 749     va_list ap, aq;
 750     char *arg_cur;
 751 
 752     bool found = FALSE;
 753     const char *fmt_iter = fmt;
 754     uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
 755     const unsigned * log_level = (const unsigned *) ctx;
 756     enum {
 757         escan_seennothing,
 758         escan_seenpercent,
 759     } scan_state = escan_seennothing;
 760 
 761     va_start(ap, fmt);
 762     va_copy(aq, ap);
 763 
 764     while (!found && *fmt_iter != '\0') {
 765         /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
 766         switch (*fmt_iter++) {
 767         case '%':
 768             if (scan_state == escan_seennothing) {
 769                 scan_state = escan_seenpercent;
 770             } else if (scan_state == escan_seenpercent) {
 771                 scan_state = escan_seennothing;
 772             }
 773             break;
 774         case 's':
 775             if (scan_state == escan_seenpercent) {
 776                 scan_state = escan_seennothing;
 777                 arg_cur = va_arg(aq, char *);
 778                 if (arg_cur != NULL) {
 779                     switch (arg_cur[0]) {
 780                     case 'W':
 781                         if (!strncmp(arg_cur, "WARNING: ",
 782                                      sizeof("WARNING: ") - 1)) {
 783                             msg_log_level = LOG_WARNING;
 784                         }
 785                         if (ctx == NULL) {
 786                             memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
 787                                     strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
 788                         }
 789                         found = TRUE;
 790                         break;
 791                     case 'I':
 792                         if (!strncmp(arg_cur, "INFO: ",
 793                                      sizeof("INFO: ") - 1)) {
 794                             msg_log_level = LOG_INFO;
 795                         }
 796                         if (ctx == NULL) {
 797                             memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
 798                                     strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
 799                         }
 800                         found = TRUE;
 801                         break;
 802                     case 'D':
 803                         if (!strncmp(arg_cur, "DEBUG: ",
 804                                      sizeof("DEBUG: ") - 1)) {
 805                             msg_log_level = LOG_DEBUG;
 806                         }
 807                         if (ctx == NULL) {
 808                             memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
 809                                     strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
 810                         }
 811                         found = TRUE;
 812                         break;
 813                     }
 814                 }
 815             }
 816             break;
 817         case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
 818         case '0': case '1': case '2': case '3': case '4':
 819         case '5': case '6': case '7': case '8': case '9':
 820         case '*':
 821             break;
 822         case 'l':
 823         case 'z':
 824         case 't':
 825         case 'j':
 826         case 'd': case 'i':
 827         case 'o':
 828         case 'u':
 829         case 'x': case 'X':
 830         case 'e': case 'E':
 831         case 'f': case 'F':
 832         case 'g': case 'G':
 833         case 'a': case 'A':
 834         case 'c':
 835         case 'p':
 836             if (scan_state == escan_seenpercent) {
 837                 (void) va_arg(aq, void *);  /* skip forward */
 838                 scan_state = escan_seennothing;
 839             }
 840             break;
 841         default:
 842             scan_state = escan_seennothing;
 843             break;
 844         }
 845     }
 846 
 847     if (log_level != NULL) {
 848         /* intention of the following offset is:
 849            cibadmin -V -> start showing INFO labelled messages */
 850         if (*log_level + 4 >= msg_log_level) {
 851             vfprintf(stderr, fmt, ap);
 852         }
 853     } else {
 854         PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
 855     }
 856 
 857     va_end(aq);
 858     va_end(ap);
 859 }
 860 
 861 static xmlNode *
 862 apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 863 {
 864     char *xform = NULL;
 865     xmlNode *out = NULL;
 866     xmlDocPtr res = NULL;
 867     xsltStylesheet *xslt = NULL;
 868 
 869     xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 870                                     transform);
 871 
 872     /* for capturing, e.g., what's emitted via <xsl:message> */
 873     if (to_logs) {
 874         xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
 875     } else {
 876         xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
 877     }
 878 
 879     xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
 880     CRM_CHECK(xslt != NULL, goto cleanup);
 881 
 882     res = xsltApplyStylesheet(xslt, xml->doc, NULL);
 883     CRM_CHECK(res != NULL, goto cleanup);
 884 
 885     xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
 886 
 887     out = xmlDocGetRootElement(res);
 888 
 889   cleanup:
 890     if (xslt) {
 891         xsltFreeStylesheet(xslt);
 892     }
 893 
 894     free(xform);
 895 
 896     return out;
 897 }
 898 
 899 /*!
 900  * \internal
 901  * \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
 902  *
 903  * \note Only emits warnings about enter/leave phases in case of issues.
 904  */
 905 static xmlNode *
 906 apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 907 {
 908     bool transform_onleave = schema->transform_onleave;
 909     char *transform_leave;
 910     xmlNode *upgrade = NULL,
 911             *final = NULL;
 912 
 913     if (schema->transform_enter) {
 914         crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
 915                   schema->name, schema->transform_enter);
 916         upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
 917         if (upgrade == NULL) {
 918             crm_warn("Upgrade-enter transformation %s.xsl failed",
 919                      schema->transform_enter);
 920             transform_onleave = FALSE;
 921         }
 922     }
 923     if (upgrade == NULL) {
 924         upgrade = xml;
 925     }
 926 
 927     crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
 928               schema->name, schema->transform);
 929     final = apply_transformation(upgrade, schema->transform, to_logs);
 930     if (upgrade != xml) {
 931         free_xml(upgrade);
 932         upgrade = NULL;
 933     }
 934 
 935     if (final != NULL && transform_onleave) {
 936         upgrade = final;
 937         /* following condition ensured in add_schema_by_version */
 938         CRM_ASSERT(schema->transform_enter != NULL);
 939         transform_leave = strdup(schema->transform_enter);
 940         /* enter -> leave */
 941         memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
 942         crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
 943                   schema->name, transform_leave);
 944         final = apply_transformation(upgrade, transform_leave, to_logs);
 945         if (final == NULL) {
 946             crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
 947             final = upgrade;
 948         } else {
 949             free_xml(upgrade);
 950         }
 951         free(transform_leave);
 952     }
 953 
 954     return final;
 955 }
 956 
 957 const char *
 958 get_schema_name(int version)
     /* [previous][next][first][last][top][bottom][index][help] */
 959 {
 960     if (version < 0 || version >= xml_schema_max) {
 961         return "unknown";
 962     }
 963     return known_schemas[version].name;
 964 }
 965 
 966 int
 967 get_schema_version(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 968 {
 969     int lpc = 0;
 970 
 971     if (name == NULL) {
 972         name = PCMK__VALUE_NONE;
 973     }
 974     for (; lpc < xml_schema_max; lpc++) {
 975         if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
 976             return lpc;
 977         }
 978     }
 979     return -1;
 980 }
 981 
 982 /* set which validation to use */
 983 int
 984 update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
     /* [previous][next][first][last][top][bottom][index][help] */
 985                   gboolean to_logs)
 986 {
 987     xmlNode *xml = NULL;
 988     char *value = NULL;
 989     int max_stable_schemas = xml_latest_schema_index();
 990     int lpc = 0, match = -1, rc = pcmk_ok;
 991     int next = -1;  /* -1 denotes "inactive" value */
 992     xmlRelaxNGValidityErrorFunc error_handler = 
 993         to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
 994 
 995     CRM_CHECK(best != NULL, return -EINVAL);
 996     *best = 0;
 997 
 998     CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
 999               && ((*xml_blob)->doc != NULL),
1000               return -EINVAL);
1001 
1002     xml = *xml_blob;
1003     value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
1004 
1005     if (value != NULL) {
1006         match = get_schema_version(value);
1007 
1008         lpc = match;
1009         if (lpc >= 0 && transform == FALSE) {
1010             *best = lpc++;
1011 
1012         } else if (lpc < 0) {
1013             crm_debug("Unknown validation schema");
1014             lpc = 0;
1015         }
1016     }
1017 
1018     if (match >= max_stable_schemas) {
1019         /* nothing to do */
1020         free(value);
1021         *best = match;
1022         return pcmk_ok;
1023     }
1024 
1025     while (lpc <= max_stable_schemas) {
1026         crm_debug("Testing '%s' validation (%d of %d)",
1027                   known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1028                   lpc, max_stable_schemas);
1029 
1030         if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
1031             if (next != -1) {
1032                 crm_info("Configuration not valid for schema: %s",
1033                          known_schemas[lpc].name);
1034                 next = -1;
1035             } else {
1036                 crm_trace("%s validation failed",
1037                           known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1038             }
1039             if (*best) {
1040                 /* we've satisfied the validation, no need to check further */
1041                 break;
1042             }
1043             rc = -pcmk_err_schema_validation;
1044 
1045         } else {
1046             if (next != -1) {
1047                 crm_debug("Configuration valid for schema: %s",
1048                           known_schemas[next].name);
1049                 next = -1;
1050             }
1051             rc = pcmk_ok;
1052         }
1053 
1054         if (rc == pcmk_ok) {
1055             *best = lpc;
1056         }
1057 
1058         if (rc == pcmk_ok && transform) {
1059             xmlNode *upgrade = NULL;
1060             next = known_schemas[lpc].after_transform;
1061 
1062             if (next <= lpc) {
1063                 /* There is no next version, or next would regress */
1064                 crm_trace("Stopping at %s", known_schemas[lpc].name);
1065                 break;
1066 
1067             } else if (max > 0 && (lpc == max || next > max)) {
1068                 crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1069                           known_schemas[lpc].name, lpc, next, max);
1070                 break;
1071 
1072             } else if (known_schemas[lpc].transform == NULL
1073                        /* possibly avoid transforming when readily valid
1074                           (in general more restricted when crossing the major
1075                           version boundary, as X.0 "transitional" version is
1076                           expected to be more strict than it's successors that
1077                           may re-allow constructs from previous major line) */
1078                        || validate_with_silent(xml, next)) {
1079                 crm_debug("%s-style configuration is also valid for %s",
1080                            known_schemas[lpc].name, known_schemas[next].name);
1081 
1082                 lpc = next;
1083 
1084             } else {
1085                 crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1086                            known_schemas[lpc].name, known_schemas[next].name,
1087                            known_schemas[lpc].transform);
1088 
1089                 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1090                 if (upgrade == NULL) {
1091                     crm_err("Transformation %s.xsl failed",
1092                             known_schemas[lpc].transform);
1093                     rc = -pcmk_err_transform_failed;
1094 
1095                 } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) {
1096                     crm_info("Transformation %s.xsl successful",
1097                              known_schemas[lpc].transform);
1098                     lpc = next;
1099                     *best = next;
1100                     free_xml(xml);
1101                     xml = upgrade;
1102                     rc = pcmk_ok;
1103 
1104                 } else {
1105                     crm_err("Transformation %s.xsl did not produce a valid configuration",
1106                             known_schemas[lpc].transform);
1107                     crm_log_xml_info(upgrade, "transform:bad");
1108                     free_xml(upgrade);
1109                     rc = -pcmk_err_schema_validation;
1110                 }
1111                 next = -1;
1112             }
1113         }
1114 
1115         if (transform == FALSE || rc != pcmk_ok) {
1116             /* we need some progress! */
1117             lpc++;
1118         }
1119     }
1120 
1121     if (*best > match && *best) {
1122         crm_info("%s the configuration from %s to %s",
1123                    transform?"Transformed":"Upgraded",
1124                    value ? value : "<none>", known_schemas[*best].name);
1125         crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1126     }
1127 
1128     *xml_blob = xml;
1129     free(value);
1130     return rc;
1131 }
1132 
1133 gboolean
1134 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1135 {
1136     gboolean rc = TRUE;
1137     const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1138     char *const orig_value = strdup(value == NULL ? "(none)" : value);
1139 
1140     int version = get_schema_version(value);
1141     int orig_version = version;
1142     int min_version = xml_minimum_schema_index();
1143 
1144     if (version < min_version) {
1145         // Current configuration schema is not acceptable, try to update
1146         xmlNode *converted = NULL;
1147 
1148         converted = copy_xml(*xml);
1149         update_validation(&converted, &version, 0, TRUE, to_logs);
1150 
1151         value = crm_element_value(converted, XML_ATTR_VALIDATION);
1152         if (version < min_version) {
1153             // Updated configuration schema is still not acceptable
1154 
1155             if (version < orig_version || orig_version == -1) {
1156                 // We couldn't validate any schema at all
1157                 if (to_logs) {
1158                     pcmk__config_err("Cannot upgrade configuration (claiming "
1159                                      "schema %s) to at least %s because it "
1160                                      "does not validate with any schema from "
1161                                      "%s to %s",
1162                                      orig_value,
1163                                      get_schema_name(min_version),
1164                                      get_schema_name(orig_version),
1165                                      xml_latest_schema());
1166                 } else {
1167                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1168                                     "schema %s) to at least %s because it "
1169                                     "does not validate with any schema from "
1170                                     "%s to %s\n",
1171                                     orig_value,
1172                                     get_schema_name(min_version),
1173                                     get_schema_name(orig_version),
1174                                     xml_latest_schema());
1175                 }
1176             } else {
1177                 // We updated configuration successfully, but still too low
1178                 if (to_logs) {
1179                     pcmk__config_err("Cannot upgrade configuration (claiming "
1180                                      "schema %s) to at least %s because it "
1181                                      "would not upgrade past %s",
1182                                      orig_value,
1183                                      get_schema_name(min_version),
1184                                      pcmk__s(value, "unspecified version"));
1185                 } else {
1186                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1187                                     "schema %s) to at least %s because it "
1188                                     "would not upgrade past %s\n",
1189                                     orig_value,
1190                                     get_schema_name(min_version),
1191                                     pcmk__s(value, "unspecified version"));
1192                 }
1193             }
1194 
1195             free_xml(converted);
1196             converted = NULL;
1197             rc = FALSE;
1198 
1199         } else {
1200             // Updated configuration schema is acceptable
1201             free_xml(*xml);
1202             *xml = converted;
1203 
1204             if (version < xml_latest_schema_index()) {
1205                 if (to_logs) {
1206                     pcmk__config_warn("Configuration with schema %s was "
1207                                       "internally upgraded to acceptable (but "
1208                                       "not most recent) %s",
1209                                       orig_value, get_schema_name(version));
1210                 }
1211             } else {
1212                 if (to_logs) {
1213                     crm_info("Configuration with schema %s was internally "
1214                              "upgraded to latest version %s",
1215                              orig_value, get_schema_name(version));
1216                 }
1217             }
1218         }
1219 
1220     } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
1221         // Schema validation is disabled
1222         if (to_logs) {
1223             pcmk__config_warn("Schema validation of configuration is disabled "
1224                               "(enabling is encouraged and prevents common "
1225                               "misconfigurations)");
1226 
1227         } else {
1228             fprintf(stderr, "Schema validation of configuration is disabled "
1229                             "(enabling is encouraged and prevents common "
1230                             "misconfigurations)\n");
1231         }
1232     }
1233 
1234     if (best_version) {
1235         *best_version = version;
1236     }
1237 
1238     free(orig_value);
1239     return rc;
1240 }

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