LCOV - code coverage report
Current view: top level - common - schemas.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 203 650 31.2 %
Date: 2024-05-07 11:09:47 Functions: 16 39 41.0 %

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

Generated by: LCOV version 1.14