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     // @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 #if 0
 436 static void
 437 relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
     /* [previous][next][first][last][top][bottom][index][help] */
 438 {
 439     /*
 440        Structure xmlError
 441        struct _xmlError {
 442        int      domain  : What part of the library raised this er
 443        int      code    : The error code, e.g. an xmlParserError
 444        char *   message : human-readable informative error messag
 445        xmlErrorLevel    level   : how consequent is the error
 446        char *   file    : the filename
 447        int      line    : the line number if available
 448        char *   str1    : extra string information
 449        char *   str2    : extra string information
 450        char *   str3    : extra string information
 451        int      int1    : extra number information
 452        int      int2    : column number of the error or 0 if N/A
 453        void *   ctxt    : the parser context if available
 454        void *   node    : the node in the tree
 455        }
 456      */
 457     crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
 458 }
 459 #endif
 460 
 461 static gboolean
 462 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
     /* [previous][next][first][last][top][bottom][index][help] */
 463                       relaxng_ctx_cache_t **cached_ctx)
 464 {
 465     int rc = 0;
 466     gboolean valid = TRUE;
 467     relaxng_ctx_cache_t *ctx = NULL;
 468 
 469     CRM_CHECK(doc != NULL, return FALSE);
 470     CRM_CHECK(relaxng_file != NULL, return FALSE);
 471 
 472     if (cached_ctx && *cached_ctx) {
 473         ctx = *cached_ctx;
 474 
 475     } else {
 476         crm_debug("Creating RNG parser context");
 477         ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
 478 
 479         xmlLoadExtDtdDefaultValue = 1;
 480         ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
 481         CRM_CHECK(ctx->parser != NULL, goto cleanup);
 482 
 483         if (to_logs) {
 484             xmlRelaxNGSetParserErrors(ctx->parser,
 485                                       (xmlRelaxNGValidityErrorFunc) xml_log,
 486                                       (xmlRelaxNGValidityWarningFunc) xml_log,
 487                                       GUINT_TO_POINTER(LOG_ERR));
 488         } else {
 489             xmlRelaxNGSetParserErrors(ctx->parser,
 490                                       (xmlRelaxNGValidityErrorFunc) fprintf,
 491                                       (xmlRelaxNGValidityWarningFunc) fprintf,
 492                                       stderr);
 493         }
 494 
 495         ctx->rng = xmlRelaxNGParse(ctx->parser);
 496         CRM_CHECK(ctx->rng != NULL,
 497                   crm_err("Could not find/parse %s", relaxng_file);
 498                   goto cleanup);
 499 
 500         ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
 501         CRM_CHECK(ctx->valid != NULL, goto cleanup);
 502 
 503         if (to_logs) {
 504             xmlRelaxNGSetValidErrors(ctx->valid,
 505                                      (xmlRelaxNGValidityErrorFunc) xml_log,
 506                                      (xmlRelaxNGValidityWarningFunc) xml_log,
 507                                      GUINT_TO_POINTER(LOG_ERR));
 508         } else {
 509             xmlRelaxNGSetValidErrors(ctx->valid,
 510                                      (xmlRelaxNGValidityErrorFunc) fprintf,
 511                                      (xmlRelaxNGValidityWarningFunc) fprintf,
 512                                      stderr);
 513         }
 514     }
 515 
 516     /* xmlRelaxNGSetValidStructuredErrors( */
 517     /*  valid, relaxng_invalid_stderr, valid); */
 518 
 519     xmlLineNumbersDefault(1);
 520     rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
 521     if (rc > 0) {
 522         valid = FALSE;
 523 
 524     } else if (rc < 0) {
 525         crm_err("Internal libxml error during validation");
 526     }
 527 
 528   cleanup:
 529 
 530     if (cached_ctx) {
 531         *cached_ctx = ctx;
 532 
 533     } else {
 534         if (ctx->parser != NULL) {
 535             xmlRelaxNGFreeParserCtxt(ctx->parser);
 536         }
 537         if (ctx->valid != NULL) {
 538             xmlRelaxNGFreeValidCtxt(ctx->valid);
 539         }
 540         if (ctx->rng != NULL) {
 541             xmlRelaxNGFree(ctx->rng);
 542         }
 543         free(ctx);
 544     }
 545 
 546     return valid;
 547 }
 548 
 549 /*!
 550  * \internal
 551  * \brief Clean up global memory associated with XML schemas
 552  */
 553 void
 554 crm_schema_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 555 {
 556     int lpc;
 557     relaxng_ctx_cache_t *ctx = NULL;
 558 
 559     for (lpc = 0; lpc < xml_schema_max; lpc++) {
 560 
 561         switch (known_schemas[lpc].validator) {
 562             case schema_validator_none: // not cached
 563                 break;
 564             case schema_validator_rng: // cached
 565                 ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
 566                 if (ctx == NULL) {
 567                     break;
 568                 }
 569                 if (ctx->parser != NULL) {
 570                     xmlRelaxNGFreeParserCtxt(ctx->parser);
 571                 }
 572                 if (ctx->valid != NULL) {
 573                     xmlRelaxNGFreeValidCtxt(ctx->valid);
 574                 }
 575                 if (ctx->rng != NULL) {
 576                     xmlRelaxNGFree(ctx->rng);
 577                 }
 578                 free(ctx);
 579                 known_schemas[lpc].cache = NULL;
 580                 break;
 581         }
 582         free(known_schemas[lpc].name);
 583         free(known_schemas[lpc].transform);
 584         free(known_schemas[lpc].transform_enter);
 585     }
 586     free(known_schemas);
 587     known_schemas = NULL;
 588 
 589     wrap_libxslt(true);
 590 }
 591 
 592 static gboolean
 593 validate_with(xmlNode *xml, int method, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 594 {
 595     xmlDocPtr doc = NULL;
 596     gboolean valid = FALSE;
 597     char *file = NULL;
 598 
 599     if (method < 0) {
 600         return FALSE;
 601     }
 602 
 603     if (known_schemas[method].validator == schema_validator_none) {
 604         return TRUE;
 605     }
 606 
 607     CRM_CHECK(xml != NULL, return FALSE);
 608 
 609     if (pcmk__str_eq(known_schemas[method].name, "pacemaker-next",
 610                      pcmk__str_none)) {
 611         crm_warn("The pacemaker-next schema is deprecated and will be removed "
 612                  "in a future release.");
 613     }
 614 
 615     doc = getDocPtr(xml);
 616     file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng,
 617                                    known_schemas[method].name);
 618 
 619     crm_trace("Validating with %s (type=%d)",
 620               pcmk__s(file, "missing schema"), known_schemas[method].validator);
 621     switch (known_schemas[method].validator) {
 622         case schema_validator_rng:
 623             valid =
 624                 validate_with_relaxng(doc, to_logs, file,
 625                                       (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
 626             break;
 627         default:
 628             crm_err("Unknown validator type: %d",
 629                     known_schemas[method].validator);
 630             break;
 631     }
 632 
 633     free(file);
 634     return valid;
 635 }
 636 
 637 static bool
 638 validate_with_silent(xmlNode *xml, int method)
     /* [previous][next][first][last][top][bottom][index][help] */
 639 {
 640     bool rc, sl_backup = silent_logging;
 641     silent_logging = TRUE;
 642     rc = validate_with(xml, method, TRUE);
 643     silent_logging = sl_backup;
 644     return rc;
 645 }
 646 
 647 static void
 648 dump_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
 649 {
 650 
 651     FILE *fp = NULL;
 652     int ch, line = 0;
 653 
 654     CRM_CHECK(filename != NULL, return);
 655 
 656     fp = fopen(filename, "r");
 657     if (fp == NULL) {
 658         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
 659         return;
 660     }
 661 
 662     fprintf(stderr, "%4d ", ++line);
 663     do {
 664         ch = getc(fp);
 665         if (ch == EOF) {
 666             putc('\n', stderr);
 667             break;
 668         } else if (ch == '\n') {
 669             fprintf(stderr, "\n%4d ", ++line);
 670         } else {
 671             putc(ch, stderr);
 672         }
 673     } while (1);
 674 
 675     fclose(fp);
 676 }
 677 
 678 gboolean
 679 validate_xml_verbose(xmlNode *xml_blob)
     /* [previous][next][first][last][top][bottom][index][help] */
 680 {
 681     int fd = 0;
 682     xmlDoc *doc = NULL;
 683     xmlNode *xml = NULL;
 684     gboolean rc = FALSE;
 685     char *filename = NULL;
 686 
 687     filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
 688 
 689     umask(S_IWGRP | S_IWOTH | S_IROTH);
 690     fd = mkstemp(filename);
 691     write_xml_fd(xml_blob, filename, fd, FALSE);
 692 
 693     dump_file(filename);
 694 
 695     doc = xmlParseFile(filename);
 696     xml = xmlDocGetRootElement(doc);
 697     rc = validate_xml(xml, NULL, FALSE);
 698     free_xml(xml);
 699 
 700     unlink(filename);
 701     free(filename);
 702 
 703     return rc;
 704 }
 705 
 706 gboolean
 707 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 708 {
 709     int version = 0;
 710 
 711     if (validation == NULL) {
 712         validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
 713     }
 714 
 715     if (validation == NULL) {
 716         int lpc = 0;
 717         bool valid = FALSE;
 718 
 719         for (lpc = 0; lpc < xml_schema_max; lpc++) {
 720             if (validate_with(xml_blob, lpc, FALSE)) {
 721                 valid = TRUE;
 722                 crm_xml_add(xml_blob, XML_ATTR_VALIDATION,
 723                             known_schemas[lpc].name);
 724                 crm_info("XML validated against %s", known_schemas[lpc].name);
 725                 if(known_schemas[lpc].after_transform == 0) {
 726                     break;
 727                 }
 728             }
 729         }
 730 
 731         return valid;
 732     }
 733 
 734     version = get_schema_version(validation);
 735     if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
 736         return TRUE;
 737     } else if (version < xml_schema_max) {
 738         return validate_with(xml_blob, version, to_logs);
 739     }
 740 
 741     crm_err("Unknown validator: %s", validation);
 742     return FALSE;
 743 }
 744 
 745 static void
 746 cib_upgrade_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
 747 G_GNUC_PRINTF(2, 3);
 748 
 749 /* With this arrangement, an attempt to identify the message severity
 750    as explicitly signalled directly from XSLT is performed in rather
 751    a smart way (no reliance on formatting string + arguments being
 752    always specified as ["%s", purposeful_string], as it can also be
 753    ["%s: %s", some_prefix, purposeful_string] etc. so every argument
 754    pertaining %s specifier is investigated), and if such a mark found,
 755    the respective level is determined and, when the messages are to go
 756    to the native logs, the mark itself gets dropped
 757    (by the means of string shift).
 758 
 759    NOTE: whether the native logging is the right sink is decided per
 760          the ctx parameter -- NULL denotes this case, otherwise it
 761          carries a pointer to the numeric expression of the desired
 762          target logging level (messages with higher level will be
 763          suppressed)
 764 
 765    NOTE: on some architectures, this string shift may not have any
 766          effect, but that's an acceptable tradeoff
 767 
 768    The logging level for not explicitly designated messages
 769    (suspicious, likely internal errors or some runaways) is
 770    LOG_WARNING.
 771  */
 772 static void
 773 cib_upgrade_err(void *ctx, const char *fmt, ...)
 774 {
 775     va_list ap, aq;
 776     char *arg_cur;
 777 
 778     bool found = FALSE;
 779     const char *fmt_iter = fmt;
 780     uint8_t msg_log_level = LOG_WARNING;  /* default for runaway messages */
 781     const unsigned * log_level = (const unsigned *) ctx;
 782     enum {
 783         escan_seennothing,
 784         escan_seenpercent,
 785     } scan_state = escan_seennothing;
 786 
 787     va_start(ap, fmt);
 788     va_copy(aq, ap);
 789 
 790     while (!found && *fmt_iter != '\0') {
 791         /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
 792         switch (*fmt_iter++) {
 793         case '%':
 794             if (scan_state == escan_seennothing) {
 795                 scan_state = escan_seenpercent;
 796             } else if (scan_state == escan_seenpercent) {
 797                 scan_state = escan_seennothing;
 798             }
 799             break;
 800         case 's':
 801             if (scan_state == escan_seenpercent) {
 802                 scan_state = escan_seennothing;
 803                 arg_cur = va_arg(aq, char *);
 804                 if (arg_cur != NULL) {
 805                     switch (arg_cur[0]) {
 806                     case 'W':
 807                         if (!strncmp(arg_cur, "WARNING: ",
 808                                      sizeof("WARNING: ") - 1)) {
 809                             msg_log_level = LOG_WARNING;
 810                         }
 811                         if (ctx == NULL) {
 812                             memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
 813                                     strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
 814                         }
 815                         found = TRUE;
 816                         break;
 817                     case 'I':
 818                         if (!strncmp(arg_cur, "INFO: ",
 819                                      sizeof("INFO: ") - 1)) {
 820                             msg_log_level = LOG_INFO;
 821                         }
 822                         if (ctx == NULL) {
 823                             memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
 824                                     strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
 825                         }
 826                         found = TRUE;
 827                         break;
 828                     case 'D':
 829                         if (!strncmp(arg_cur, "DEBUG: ",
 830                                      sizeof("DEBUG: ") - 1)) {
 831                             msg_log_level = LOG_DEBUG;
 832                         }
 833                         if (ctx == NULL) {
 834                             memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
 835                                     strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
 836                         }
 837                         found = TRUE;
 838                         break;
 839                     }
 840                 }
 841             }
 842             break;
 843         case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
 844         case '0': case '1': case '2': case '3': case '4':
 845         case '5': case '6': case '7': case '8': case '9':
 846         case '*':
 847             break;
 848         case 'l':
 849         case 'z':
 850         case 't':
 851         case 'j':
 852         case 'd': case 'i':
 853         case 'o':
 854         case 'u':
 855         case 'x': case 'X':
 856         case 'e': case 'E':
 857         case 'f': case 'F':
 858         case 'g': case 'G':
 859         case 'a': case 'A':
 860         case 'c':
 861         case 'p':
 862             if (scan_state == escan_seenpercent) {
 863                 (void) va_arg(aq, void *);  /* skip forward */
 864                 scan_state = escan_seennothing;
 865             }
 866             break;
 867         default:
 868             scan_state = escan_seennothing;
 869             break;
 870         }
 871     }
 872 
 873     if (log_level != NULL) {
 874         /* intention of the following offset is:
 875            cibadmin -V -> start showing INFO labelled messages */
 876         if (*log_level + 4 >= msg_log_level) {
 877             vfprintf(stderr, fmt, ap);
 878         }
 879     } else {
 880         PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
 881     }
 882 
 883     va_end(aq);
 884     va_end(ap);
 885 }
 886 
 887 
 888 /* Denotes temporary emergency fix for "xmldiff'ing not text-node-ready";
 889    proper fix is most likely to teach __xml_diff_object and friends to
 890    deal with XML_TEXT_NODE (and more?), i.e., those nodes currently
 891    missing "_private" field (implicitly as NULL) which clashes with
 892    unchecked accesses (e.g. in __xml_offset) -- the outcome may be that
 893    those unexpected XML nodes will simply be ignored for the purpose of
 894    diff'ing, or it may be made more robust, or per the user's preference
 895    (which then may be exposed as crm_diff switch).
 896 
 897    Said XML_TEXT_NODE may appear unexpectedly due to how upgrade-2.10.xsl
 898    is arranged.
 899 
 900    The emergency fix is simple: reparse XSLT output with blank-ignoring
 901    parser. */
 902 #ifndef PCMK_SCHEMAS_EMERGENCY_XSLT
 903 #define PCMK_SCHEMAS_EMERGENCY_XSLT 1
 904 #endif
 905 
 906 static xmlNode *
 907 apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 908 {
 909     char *xform = NULL;
 910     xmlNode *out = NULL;
 911     xmlDocPtr res = NULL;
 912     xmlDocPtr doc = NULL;
 913     xsltStylesheet *xslt = NULL;
 914 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
 915     xmlChar *emergency_result;
 916     int emergency_txt_len;
 917     int emergency_res;
 918 #endif
 919 
 920     CRM_CHECK(xml != NULL, return FALSE);
 921     doc = getDocPtr(xml);
 922     xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt,
 923                                     transform);
 924 
 925     xmlLoadExtDtdDefaultValue = 1;
 926     xmlSubstituteEntitiesDefault(1);
 927 
 928     /* for capturing, e.g., what's emitted via <xsl:message> */
 929     if (to_logs) {
 930         xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
 931     } else {
 932         xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
 933     }
 934 
 935     xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
 936     CRM_CHECK(xslt != NULL, goto cleanup);
 937 
 938     res = xsltApplyStylesheet(xslt, doc, NULL);
 939     CRM_CHECK(res != NULL, goto cleanup);
 940 
 941     xsltSetGenericErrorFunc(NULL, NULL);  /* restore default one */
 942 
 943 
 944 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
 945     emergency_res = xsltSaveResultToString(&emergency_result,
 946                                            &emergency_txt_len, res, xslt);
 947     xmlFreeDoc(res);
 948     CRM_CHECK(emergency_res == 0, goto cleanup);
 949     out = string2xml((const char *) emergency_result);
 950     free(emergency_result);
 951 #else
 952     out = xmlDocGetRootElement(res);
 953 #endif
 954 
 955   cleanup:
 956     if (xslt) {
 957         xsltFreeStylesheet(xslt);
 958     }
 959 
 960     free(xform);
 961 
 962     return out;
 963 }
 964 
 965 /*!
 966  * \internal
 967  * \brief Possibly full enter->upgrade->leave trip per internal bookkeeping.
 968  *
 969  * \note Only emits warnings about enter/leave phases in case of issues.
 970  */
 971 static xmlNode *
 972 apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
 973 {
 974     bool transform_onleave = schema->transform_onleave;
 975     char *transform_leave;
 976     xmlNode *upgrade = NULL,
 977             *final = NULL;
 978 
 979     if (schema->transform_enter) {
 980         crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
 981                   schema->name, schema->transform_enter);
 982         upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
 983         if (upgrade == NULL) {
 984             crm_warn("Upgrade-enter transformation %s.xsl failed",
 985                      schema->transform_enter);
 986             transform_onleave = FALSE;
 987         }
 988     }
 989     if (upgrade == NULL) {
 990         upgrade = xml;
 991     }
 992 
 993     crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
 994               schema->name, schema->transform);
 995     final = apply_transformation(upgrade, schema->transform, to_logs);
 996     if (upgrade != xml) {
 997         free_xml(upgrade);
 998         upgrade = NULL;
 999     }
1000 
1001     if (final != NULL && transform_onleave) {
1002         upgrade = final;
1003         /* following condition ensured in add_schema_by_version */
1004         CRM_ASSERT(schema->transform_enter != NULL);
1005         transform_leave = strdup(schema->transform_enter);
1006         /* enter -> leave */
1007         memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1008         crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
1009                   schema->name, transform_leave);
1010         final = apply_transformation(upgrade, transform_leave, to_logs);
1011         if (final == NULL) {
1012             crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
1013             final = upgrade;
1014         } else {
1015             free_xml(upgrade);
1016         }
1017         free(transform_leave);
1018     }
1019 
1020     return final;
1021 }
1022 
1023 const char *
1024 get_schema_name(int version)
     /* [previous][next][first][last][top][bottom][index][help] */
1025 {
1026     if (version < 0 || version >= xml_schema_max) {
1027         return "unknown";
1028     }
1029     return known_schemas[version].name;
1030 }
1031 
1032 int
1033 get_schema_version(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
1034 {
1035     int lpc = 0;
1036 
1037     if (name == NULL) {
1038         name = PCMK__VALUE_NONE;
1039     }
1040     for (; lpc < xml_schema_max; lpc++) {
1041         if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
1042             return lpc;
1043         }
1044     }
1045     return -1;
1046 }
1047 
1048 /* set which validation to use */
1049 int
1050 update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
     /* [previous][next][first][last][top][bottom][index][help] */
1051                   gboolean to_logs)
1052 {
1053     xmlNode *xml = NULL;
1054     char *value = NULL;
1055     int max_stable_schemas = xml_latest_schema_index();
1056     int lpc = 0, match = -1, rc = pcmk_ok;
1057     int next = -1;  /* -1 denotes "inactive" value */
1058 
1059     CRM_CHECK(best != NULL, return -EINVAL);
1060     *best = 0;
1061 
1062     CRM_CHECK(xml_blob != NULL, return -EINVAL);
1063     CRM_CHECK(*xml_blob != NULL, return -EINVAL);
1064 
1065     xml = *xml_blob;
1066     value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
1067 
1068     if (value != NULL) {
1069         match = get_schema_version(value);
1070 
1071         lpc = match;
1072         if (lpc >= 0 && transform == FALSE) {
1073             *best = lpc++;
1074 
1075         } else if (lpc < 0) {
1076             crm_debug("Unknown validation schema");
1077             lpc = 0;
1078         }
1079     }
1080 
1081     if (match >= max_stable_schemas) {
1082         /* nothing to do */
1083         free(value);
1084         *best = match;
1085         return pcmk_ok;
1086     }
1087 
1088     while (lpc <= max_stable_schemas) {
1089         crm_debug("Testing '%s' validation (%d of %d)",
1090                   known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1091                   lpc, max_stable_schemas);
1092 
1093         if (validate_with(xml, lpc, to_logs) == FALSE) {
1094             if (next != -1) {
1095                 crm_info("Configuration not valid for schema: %s",
1096                          known_schemas[lpc].name);
1097                 next = -1;
1098             } else {
1099                 crm_trace("%s validation failed",
1100                           known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1101             }
1102             if (*best) {
1103                 /* we've satisfied the validation, no need to check further */
1104                 break;
1105             }
1106             rc = -pcmk_err_schema_validation;
1107 
1108         } else {
1109             if (next != -1) {
1110                 crm_debug("Configuration valid for schema: %s",
1111                           known_schemas[next].name);
1112                 next = -1;
1113             }
1114             rc = pcmk_ok;
1115         }
1116 
1117         if (rc == pcmk_ok) {
1118             *best = lpc;
1119         }
1120 
1121         if (rc == pcmk_ok && transform) {
1122             xmlNode *upgrade = NULL;
1123             next = known_schemas[lpc].after_transform;
1124 
1125             if (next <= lpc) {
1126                 /* There is no next version, or next would regress */
1127                 crm_trace("Stopping at %s", known_schemas[lpc].name);
1128                 break;
1129 
1130             } else if (max > 0 && (lpc == max || next > max)) {
1131                 crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1132                           known_schemas[lpc].name, lpc, next, max);
1133                 break;
1134 
1135             } else if (known_schemas[lpc].transform == NULL
1136                        /* possibly avoid transforming when readily valid
1137                           (in general more restricted when crossing the major
1138                           version boundary, as X.0 "transitional" version is
1139                           expected to be more strict than it's successors that
1140                           may re-allow constructs from previous major line) */
1141                        || validate_with_silent(xml, next)) {
1142                 crm_debug("%s-style configuration is also valid for %s",
1143                            known_schemas[lpc].name, known_schemas[next].name);
1144 
1145                 lpc = next;
1146 
1147             } else {
1148                 crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1149                            known_schemas[lpc].name, known_schemas[next].name,
1150                            known_schemas[lpc].transform);
1151 
1152                 upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1153                 if (upgrade == NULL) {
1154                     crm_err("Transformation %s.xsl failed",
1155                             known_schemas[lpc].transform);
1156                     rc = -pcmk_err_transform_failed;
1157 
1158                 } else if (validate_with(upgrade, next, to_logs)) {
1159                     crm_info("Transformation %s.xsl successful",
1160                              known_schemas[lpc].transform);
1161                     lpc = next;
1162                     *best = next;
1163                     free_xml(xml);
1164                     xml = upgrade;
1165                     rc = pcmk_ok;
1166 
1167                 } else {
1168                     crm_err("Transformation %s.xsl did not produce a valid configuration",
1169                             known_schemas[lpc].transform);
1170                     crm_log_xml_info(upgrade, "transform:bad");
1171                     free_xml(upgrade);
1172                     rc = -pcmk_err_schema_validation;
1173                 }
1174                 next = -1;
1175             }
1176         }
1177 
1178         if (transform == FALSE || rc != pcmk_ok) {
1179             /* we need some progress! */
1180             lpc++;
1181         }
1182     }
1183 
1184     if (*best > match && *best) {
1185         crm_info("%s the configuration from %s to %s",
1186                    transform?"Transformed":"Upgraded",
1187                    value ? value : "<none>", known_schemas[*best].name);
1188         crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1189     }
1190 
1191     *xml_blob = xml;
1192     free(value);
1193     return rc;
1194 }
1195 
1196 gboolean
1197 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
     /* [previous][next][first][last][top][bottom][index][help] */
1198 {
1199     gboolean rc = TRUE;
1200     const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1201     char *const orig_value = strdup(value == NULL ? "(none)" : value);
1202 
1203     int version = get_schema_version(value);
1204     int orig_version = version;
1205     int min_version = xml_minimum_schema_index();
1206 
1207     if (version < min_version) {
1208         // Current configuration schema is not acceptable, try to update
1209         xmlNode *converted = NULL;
1210 
1211         converted = copy_xml(*xml);
1212         update_validation(&converted, &version, 0, TRUE, to_logs);
1213 
1214         value = crm_element_value(converted, XML_ATTR_VALIDATION);
1215         if (version < min_version) {
1216             // Updated configuration schema is still not acceptable
1217 
1218             if (version < orig_version || orig_version == -1) {
1219                 // We couldn't validate any schema at all
1220                 if (to_logs) {
1221                     pcmk__config_err("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",
1225                                      orig_value,
1226                                      get_schema_name(min_version),
1227                                      get_schema_name(orig_version),
1228                                      xml_latest_schema());
1229                 } else {
1230                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1231                                     "schema %s) to at least %s because it "
1232                                     "does not validate with any schema from "
1233                                     "%s to %s\n",
1234                                     orig_value,
1235                                     get_schema_name(min_version),
1236                                     get_schema_name(orig_version),
1237                                     xml_latest_schema());
1238                 }
1239             } else {
1240                 // We updated configuration successfully, but still too low
1241                 if (to_logs) {
1242                     pcmk__config_err("Cannot upgrade configuration (claiming "
1243                                      "schema %s) to at least %s because it "
1244                                      "would not upgrade past %s",
1245                                      orig_value,
1246                                      get_schema_name(min_version),
1247                                      pcmk__s(value, "unspecified version"));
1248                 } else {
1249                     fprintf(stderr, "Cannot upgrade configuration (claiming "
1250                                     "schema %s) to at least %s because it "
1251                                     "would not upgrade past %s\n",
1252                                     orig_value,
1253                                     get_schema_name(min_version),
1254                                     pcmk__s(value, "unspecified version"));
1255                 }
1256             }
1257 
1258             free_xml(converted);
1259             converted = NULL;
1260             rc = FALSE;
1261 
1262         } else {
1263             // Updated configuration schema is acceptable
1264             free_xml(*xml);
1265             *xml = converted;
1266 
1267             if (version < xml_latest_schema_index()) {
1268                 if (to_logs) {
1269                     pcmk__config_warn("Configuration with schema %s was "
1270                                       "internally upgraded to acceptable (but "
1271                                       "not most recent) %s",
1272                                       orig_value, get_schema_name(version));
1273                 }
1274             } else {
1275                 if (to_logs) {
1276                     crm_info("Configuration with schema %s was internally "
1277                              "upgraded to latest version %s",
1278                              orig_value, get_schema_name(version));
1279                 }
1280             }
1281         }
1282 
1283     } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
1284         // Schema validation is disabled
1285         if (to_logs) {
1286             pcmk__config_warn("Schema validation of configuration is disabled "
1287                               "(enabling is encouraged and prevents common "
1288                               "misconfigurations)");
1289 
1290         } else {
1291             fprintf(stderr, "Schema validation of configuration is disabled "
1292                             "(enabling is encouraged and prevents common "
1293                             "misconfigurations)\n");
1294         }
1295     }
1296 
1297     if (best_version) {
1298         *best_version = version;
1299     }
1300 
1301     free(orig_value);
1302     return rc;
1303 }

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