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