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

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