pacemaker  2.1.9-49aab99839
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  pcmk__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  pcmk__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  /* Caller allocates private data for final result document. Intermediate
988  * result documents are temporary and don't need private data.
989  */
990  res = xsltApplyStylesheet(xslt, xml->doc, NULL);
991  CRM_CHECK(res != NULL, goto cleanup);
992 
993  xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
994 
995  out = xmlDocGetRootElement(res);
996 
997  cleanup:
998  if (xslt) {
999  xsltFreeStylesheet(xslt);
1000  }
1001 
1002  free(xform);
1003 
1004  return out;
1005 }
1006 
1023 static xmlNode *
1024 apply_upgrade(const xmlNode *original_xml, int schema_index, gboolean to_logs)
1025 {
1026  pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
1027  pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
1028  schema_index + 1);
1029  bool transform_onleave = false;
1030  char *transform_leave;
1031  const xmlNode *xml = original_xml;
1032  xmlNode *upgrade = NULL;
1033  xmlNode *final = NULL;
1034  xmlRelaxNGValidityErrorFunc error_handler = NULL;
1035 
1036  pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
1037 
1038  if (to_logs) {
1039  error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
1040  }
1041 
1042  transform_onleave = schema->transform_onleave;
1043  if (schema->transform_enter != NULL) {
1044  crm_debug("Upgrading schema from %s to %s: "
1045  "applying pre-upgrade XSL transform %s",
1046  schema->name, upgraded_schema->name, schema->transform_enter);
1047  upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
1048  if (upgrade == NULL) {
1049  crm_warn("Pre-upgrade XSL transform %s failed, "
1050  "will skip post-upgrade transform",
1051  schema->transform_enter);
1052  transform_onleave = FALSE;
1053  } else {
1054  xml = upgrade;
1055  }
1056  }
1057 
1058 
1059  crm_debug("Upgrading schema from %s to %s: "
1060  "applying upgrade XSL transform %s",
1061  schema->name, upgraded_schema->name, schema->transform);
1062  final = apply_transformation(xml, schema->transform, to_logs);
1063  if (upgrade != xml) {
1064  free_xml(upgrade);
1065  /* upgrade = NULL; */ // Static analysis dislikes this, so be careful
1066  }
1067 
1068  if ((final != NULL) && transform_onleave) {
1069  upgrade = final;
1070  /* following condition ensured in add_schema_by_version */
1071  pcmk__assert(schema->transform_enter != NULL);
1072  transform_leave = strdup(schema->transform_enter);
1073  /* enter -> leave */
1074  memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
1075  crm_debug("Upgrading schema from %s to %s: "
1076  "applying post-upgrade XSL transform %s",
1077  schema->name, upgraded_schema->name, transform_leave);
1078  final = apply_transformation(upgrade, transform_leave, to_logs);
1079  if (final == NULL) {
1080  crm_warn("Ignoring failure of post-upgrade XSL transform %s",
1081  transform_leave);
1082  final = upgrade;
1083  } else {
1084  free_xml(upgrade);
1085  }
1086  free(transform_leave);
1087  }
1088 
1089  if (final == NULL) {
1090  return NULL;
1091  }
1092 
1093  // Final result document from upgrade pipeline needs private data
1094  pcmk__xml_new_private_data((xmlNode *) final->doc);
1095 
1096  // Ensure result validates with its new schema
1097  if (!validate_with(final, upgraded_schema, error_handler,
1098  GUINT_TO_POINTER(LOG_ERR))) {
1099  crm_err("Schema upgrade from %s to %s failed: "
1100  "XSL transform %s produced an invalid configuration",
1101  schema->name, upgraded_schema->name, schema->transform);
1102  crm_log_xml_debug(final, "bad-transform-result");
1103  free_xml(final);
1104  return NULL;
1105  }
1106 
1107  crm_info("Schema upgrade from %s to %s succeeded",
1108  schema->name, upgraded_schema->name);
1109  return final;
1110 }
1111 
1120 static GList *
1121 get_configured_schema(const xmlNode *xml)
1122 {
1123  const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1124 
1125  pcmk__warn_if_schema_deprecated(schema_name);
1126  if (schema_name == NULL) {
1127  return NULL;
1128  }
1129  return pcmk__get_schema(schema_name);
1130 }
1131 
1146 int
1147 pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
1148  bool to_logs)
1149 {
1150  int max_stable_schemas = xml_latest_schema_index();
1151  int max_schema_index = 0;
1152  int rc = pcmk_rc_ok;
1153  GList *entry = NULL;
1154  pcmk__schema_t *best_schema = NULL;
1155  pcmk__schema_t *original_schema = NULL;
1156  xmlRelaxNGValidityErrorFunc error_handler =
1157  to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1158 
1159  CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1160  return EINVAL);
1161 
1162  if (max_schema_name != NULL) {
1163  GList *max_entry = pcmk__get_schema(max_schema_name);
1164 
1165  if (max_entry != NULL) {
1166  pcmk__schema_t *max_schema = max_entry->data;
1167 
1168  max_schema_index = max_schema->schema_index;
1169  }
1170  }
1171  if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1172  max_schema_index = max_stable_schemas;
1173  }
1174 
1175  entry = get_configured_schema(*xml);
1176  if (entry == NULL) {
1177  // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1178  entry = known_schemas;
1179  } else {
1180  original_schema = entry->data;
1181  if (original_schema->schema_index >= max_schema_index) {
1182  return pcmk_rc_ok;
1183  }
1184  }
1185 
1186  for (; entry != NULL; entry = entry->next) {
1187  pcmk__schema_t *current_schema = entry->data;
1188  xmlNode *upgrade = NULL;
1189 
1190  if (current_schema->schema_index > max_schema_index) {
1191  break;
1192  }
1193 
1194  if (!validate_with(*xml, current_schema, error_handler,
1195  GUINT_TO_POINTER(LOG_ERR))) {
1196  crm_debug("Schema %s does not validate", current_schema->name);
1197  if (best_schema != NULL) {
1198  /* we've satisfied the validation, no need to check further */
1199  break;
1200  }
1202  continue; // Try again with the next higher schema
1203  }
1204 
1205  crm_debug("Schema %s validates", current_schema->name);
1206  rc = pcmk_rc_ok;
1207  best_schema = current_schema;
1208  if (current_schema->schema_index == max_schema_index) {
1209  break; // No further transformations possible
1210  }
1211 
1212  if (!transform || (current_schema->transform == NULL)
1213  || validate_with_silent(*xml, entry->next->data)) {
1214  /* The next schema either doesn't require a transform or validates
1215  * successfully even without the transform. Skip the transform and
1216  * try the next schema with the same XML.
1217  */
1218  continue;
1219  }
1220 
1221  upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1222  if (upgrade == NULL) {
1223  /* The transform failed, so this schema can't be used. Later
1224  * schemas are unlikely to validate, but try anyway until we
1225  * run out of options.
1226  */
1228  } else {
1229  best_schema = current_schema;
1230  free_xml(*xml);
1231  *xml = upgrade;
1232  }
1233  }
1234 
1235  if (best_schema != NULL) {
1236  if ((original_schema == NULL)
1237  || (best_schema->schema_index > original_schema->schema_index)) {
1238  crm_info("%s the configuration schema to %s",
1239  (transform? "Transformed" : "Upgraded"),
1240  best_schema->name);
1241  crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1242  }
1243  }
1244  return rc;
1245 }
1246 
1247 int
1249 {
1250  return pcmk__update_configured_schema(xml, true);
1251 }
1252 
1262 int
1263 pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
1264 {
1265  int rc = pcmk_rc_ok;
1266  char *original_schema_name = NULL;
1267 
1268  // @COMPAT Not specifying a schema name is deprecated since 2.1.8
1269  const char *effective_original_name = "the first";
1270 
1271  int orig_version = -1;
1272  pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1273  GList *entry = NULL;
1274 
1275  CRM_CHECK(xml != NULL, return EINVAL);
1276 
1277  original_schema_name = crm_element_value_copy(*xml, PCMK_XA_VALIDATE_WITH);
1278  pcmk__warn_if_schema_deprecated(original_schema_name);
1279  entry = pcmk__get_schema(original_schema_name);
1280  if (entry != NULL) {
1281  pcmk__schema_t *original_schema = entry->data;
1282 
1283  effective_original_name = original_schema->name;
1284  orig_version = original_schema->schema_index;
1285  }
1286 
1287  if (orig_version < x_0_schema->schema_index) {
1288  // Current configuration schema is not acceptable, try to update
1289  xmlNode *converted = NULL;
1290  const char *new_schema_name = NULL;
1291  pcmk__schema_t *schema = NULL;
1292 
1293  entry = NULL;
1294  converted = pcmk__xml_copy(NULL, *xml);
1295  if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1296  new_schema_name = crm_element_value(converted,
1298  entry = pcmk__get_schema(new_schema_name);
1299  }
1300  schema = (entry == NULL)? NULL : entry->data;
1301 
1302  if ((schema == NULL)
1303  || (schema->schema_index < x_0_schema->schema_index)) {
1304  // Updated configuration schema is still not acceptable
1305 
1306  if ((orig_version == -1) || (schema == NULL)
1307  || (schema->schema_index < orig_version)) {
1308  // We couldn't validate any schema at all
1309  if (to_logs) {
1310  pcmk__config_err("Cannot upgrade configuration (claiming "
1311  "%s schema) to at least %s because it "
1312  "does not validate with any schema from "
1313  "%s to the latest",
1314  pcmk__s(original_schema_name, "no"),
1315  x_0_schema->name, effective_original_name);
1316  } else {
1317  fprintf(stderr, "Cannot upgrade configuration (claiming "
1318  "%s schema) to at least %s because it "
1319  "does not validate with any schema from "
1320  "%s to the latest\n",
1321  pcmk__s(original_schema_name, "no"),
1322  x_0_schema->name, effective_original_name);
1323  }
1324  } else {
1325  // We updated configuration successfully, but still too low
1326  if (to_logs) {
1327  pcmk__config_err("Cannot upgrade configuration (claiming "
1328  "%s schema) to at least %s because it "
1329  "would not upgrade past %s",
1330  pcmk__s(original_schema_name, "no"),
1331  x_0_schema->name,
1332  pcmk__s(new_schema_name, "unspecified version"));
1333  } else {
1334  fprintf(stderr, "Cannot upgrade configuration (claiming "
1335  "%s schema) to at least %s because it "
1336  "would not upgrade past %s\n",
1337  pcmk__s(original_schema_name, "no"),
1338  x_0_schema->name,
1339  pcmk__s(new_schema_name, "unspecified version"));
1340  }
1341  }
1342 
1343  free_xml(converted);
1344  converted = NULL;
1346 
1347  } else {
1348  // Updated configuration schema is acceptable
1349  free_xml(*xml);
1350  *xml = converted;
1351 
1352  if (schema->schema_index < xml_latest_schema_index()) {
1353  if (to_logs) {
1354  pcmk__config_warn("Configuration with %s schema was "
1355  "internally upgraded to acceptable (but "
1356  "not most recent) %s",
1357  pcmk__s(original_schema_name, "no"),
1358  schema->name);
1359  }
1360  } else if (to_logs) {
1361  crm_info("Configuration with %s schema was internally "
1362  "upgraded to latest version %s",
1363  pcmk__s(original_schema_name, "no"),
1364  schema->name);
1365  }
1366  }
1367 
1368  } else {
1369  // @COMPAT the none schema is deprecated since 2.1.8
1370  pcmk__schema_t *none_schema = NULL;
1371 
1373  pcmk__assert((entry != NULL) && (entry->data != NULL));
1374 
1375  none_schema = entry->data;
1376  if (!to_logs && (orig_version >= none_schema->schema_index)) {
1377  fprintf(stderr, "Schema validation of configuration is "
1378  "disabled (support for " PCMK_XA_VALIDATE_WITH
1379  " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1380  " and will be removed in a future release)\n");
1381  }
1382  }
1383 
1384  free(original_schema_name);
1385  return rc;
1386 }
1387 
1400 GList *
1402 {
1403  GList *lst = NULL;
1405 
1406  if (!version_from_filename(name, &ver)) {
1407  return lst;
1408  }
1409 
1410  for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1411  iter != NULL; iter = iter->prev) {
1412  pcmk__schema_t *schema = iter->data;
1413  char *s = NULL;
1414 
1415  if (schema_cmp(ver, schema->version) != -1) {
1416  continue;
1417  }
1418 
1419  s = crm_strdup_printf("%s.rng", schema->name);
1420  lst = g_list_prepend(lst, s);
1421 
1422  if (schema->transform != NULL) {
1423  char *xform = crm_strdup_printf("%s.xsl", schema->transform);
1424  lst = g_list_prepend(lst, xform);
1425  }
1426 
1427  if (schema->transform_enter != NULL) {
1428  char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter);
1429 
1430  lst = g_list_prepend(lst, enter);
1431 
1432  if (schema->transform_onleave) {
1433  int last_dash = strrchr(enter, '-') - enter;
1434  char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter);
1435 
1436  lst = g_list_prepend(lst, leave);
1437  }
1438  }
1439  }
1440 
1441  return lst;
1442 }
1443 
1444 static void
1445 append_href(xmlNode *xml, void *user_data)
1446 {
1447  GList **list = user_data;
1448  char *href = crm_element_value_copy(xml, "href");
1449 
1450  if (href == NULL) {
1451  return;
1452  }
1453  *list = g_list_prepend(*list, href);
1454 }
1455 
1456 static void
1457 external_refs_in_schema(GList **list, const char *contents)
1458 {
1459  /* local-name()= is needed to ignore the xmlns= setting at the top of
1460  * the XML file. Otherwise, the xpath query will always return nothing.
1461  */
1462  const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1463  xmlNode *xml = pcmk__xml_parse(contents);
1464 
1465  crm_foreach_xpath_result(xml, search, append_href, list);
1466  free_xml(xml);
1467 }
1468 
1469 static int
1470 read_file_contents(const char *file, char **contents)
1471 {
1472  int rc = pcmk_rc_ok;
1473  char *path = NULL;
1474 
1475  if (pcmk__ends_with(file, ".rng")) {
1477  } else {
1479  }
1480 
1481  rc = pcmk__file_contents(path, contents);
1482 
1483  free(path);
1484  return rc;
1485 }
1486 
1487 static void
1488 add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
1489 {
1490  char *contents = NULL;
1491  char *path = NULL;
1492  xmlNode *file_node = NULL;
1493  GList *includes = NULL;
1494  int rc = pcmk_rc_ok;
1495 
1496  /* If we already included this file, don't do so again. */
1497  if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1498  return;
1499  }
1500 
1501  /* Ensure whatever file we were given has a suffix we know about. If not,
1502  * just assume it's an RNG file.
1503  */
1504  if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
1505  path = crm_strdup_printf("%s.rng", file);
1506  } else {
1507  path = pcmk__str_copy(file);
1508  }
1509 
1510  rc = read_file_contents(path, &contents);
1511  if (rc != pcmk_rc_ok || contents == NULL) {
1512  crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1513  free(path);
1514  return;
1515  }
1516 
1517  /* Create a new <file path="..."> node with the contents of the file
1518  * as a CDATA block underneath it.
1519  */
1520  file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
1521  crm_xml_add(file_node, PCMK_XA_PATH, path);
1522  *already_included = g_list_prepend(*already_included, path);
1523 
1524  xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
1525  strlen(contents)));
1526 
1527  /* Scan the file for any <externalRef> or <include> nodes and build up
1528  * a list of the files they reference.
1529  */
1530  external_refs_in_schema(&includes, contents);
1531 
1532  /* For each referenced file, recurse to add it (and potentially anything it
1533  * references, ...) to the XML.
1534  */
1535  for (GList *iter = includes; iter != NULL; iter = iter->next) {
1536  add_schema_file_to_xml(parent, iter->data, already_included);
1537  }
1538 
1539  free(contents);
1540  g_list_free_full(includes, free);
1541 }
1542 
1557 void
1558 pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
1559 {
1560  xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1561 
1562  crm_xml_add(schema_node, PCMK_XA_VERSION, name);
1563  add_schema_file_to_xml(schema_node, name, already_included);
1564 
1565  if (schema_node->children == NULL) {
1566  // Not needed if empty. May happen if name was invalid, for example.
1567  free_xml(schema_node);
1568  }
1569 }
1570 
1576 const char *
1578 {
1580 
1581  if (pcmk__str_empty(dir)) {
1582  return PCMK__REMOTE_SCHEMA_DIR;
1583  }
1584 
1585  return dir;
1586 }
1587 
1594 void
1596 {
1597  if ((schema == NULL) ||
1598  pcmk__strcase_any_of(schema, "pacemaker-next", PCMK_VALUE_NONE, NULL)) {
1599  pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1600  "deprecated and will be removed in a future release "
1601  "without the possibility of upgrades (manually edit "
1602  "to use a supported schema)", pcmk__s(schema, ""));
1603  }
1604 }
1605 
1606 // Deprecated functions kept only for backward API compatibility
1607 // LCOV_EXCL_START
1608 
1609 #include <crm/common/xml_compat.h>
1610 
1611 const char *
1613 {
1614  return pcmk__highest_schema_name();
1615 }
1616 
1617 const char *
1619 {
1620  pcmk__schema_t *schema = g_list_nth_data(known_schemas, version);
1621 
1622  return (schema != NULL)? schema->name : "unknown";
1623 }
1624 
1625 int
1627 {
1628  int lpc = 0;
1629 
1630  if (name == NULL) {
1632  }
1633 
1634  for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
1635  pcmk__schema_t *schema = iter->data;
1636 
1637  if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) {
1638  return lpc;
1639  }
1640 
1641  lpc++;
1642  }
1643 
1644  return -1;
1645 }
1646 
1647 int
1648 update_validation(xmlNode **xml, int *best, int max, gboolean transform,
1649  gboolean to_logs)
1650 {
1651  int rc = pcmk__update_schema(xml, get_schema_name(max), transform, to_logs);
1652 
1653  if ((best != NULL) && (xml != NULL) && (rc == pcmk_rc_ok)) {
1654  const char *schema_name = crm_element_value(*xml,
1656  GList *schema_entry = pcmk__get_schema(schema_name);
1657 
1658  if (schema_entry != NULL) {
1659  *best = ((pcmk__schema_t *)(schema_entry->data))->schema_index;
1660  }
1661  }
1662 
1663  return pcmk_rc2legacy(rc);
1664 }
1665 
1666 gboolean
1667 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
1668 {
1669  bool rc = pcmk__validate_xml(xml_blob, validation,
1670  to_logs? (xmlRelaxNGValidityErrorFunc) xml_log : NULL,
1671  GUINT_TO_POINTER(LOG_ERR));
1672  return rc? TRUE : FALSE;
1673 }
1674 
1675 static void
1676 dump_file(const char *filename)
1677 {
1678 
1679  FILE *fp = NULL;
1680  int ch, line = 0;
1681 
1682  CRM_CHECK(filename != NULL, return);
1683 
1684  fp = fopen(filename, "r");
1685  if (fp == NULL) {
1686  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
1687  return;
1688  }
1689 
1690  fprintf(stderr, "%4d ", ++line);
1691  do {
1692  ch = getc(fp);
1693  if (ch == EOF) {
1694  putc('\n', stderr);
1695  break;
1696  } else if (ch == '\n') {
1697  fprintf(stderr, "\n%4d ", ++line);
1698  } else {
1699  putc(ch, stderr);
1700  }
1701  } while (1);
1702 
1703  fclose(fp);
1704 }
1705 
1706 gboolean
1707 validate_xml_verbose(const xmlNode *xml_blob)
1708 {
1709  int fd = 0;
1710  xmlDoc *doc = NULL;
1711  xmlNode *xml = NULL;
1712  gboolean rc = FALSE;
1713  char *filename = NULL;
1714 
1715  filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
1716 
1717  umask(S_IWGRP | S_IWOTH | S_IROTH);
1718  fd = mkstemp(filename);
1719  pcmk__xml_write_fd(xml_blob, filename, fd, false, NULL);
1720 
1721  dump_file(filename);
1722 
1723  doc = xmlReadFile(filename, NULL, 0);
1724  xml = xmlDocGetRootElement(doc);
1725  rc = pcmk__validate_xml(xml, NULL, NULL, NULL);
1726  free_xml(xml);
1727 
1728  unlink(filename);
1729  free(filename);
1730 
1731  return rc? TRUE : FALSE;
1732 }
1733 
1734 gboolean
1735 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1736 {
1737  int rc = pcmk__update_configured_schema(xml, to_logs);
1738 
1739  if (best_version != NULL) {
1740  const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
1741 
1742  if (name == NULL) {
1743  *best_version = -1;
1744  } else {
1745  GList *entry = pcmk__get_schema(name);
1746  pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1747 
1748  *best_version = (schema == NULL)? -1 : schema->schema_index;
1749  }
1750  }
1751  return (rc == pcmk_rc_ok)? TRUE: FALSE;
1752 }
1753 
1754 // LCOV_EXCL_STOP
1755 // End deprecated API
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:2202
int pcmk_update_configured_schema(xmlNode **xml)
Definition: schemas.c:1248
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:974
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:548
#define PCMK_XA_PATH
Definition: xml_names.h:355
const char * name
Definition: cib.c:26
bool pcmk__strcase_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1038
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:313
int pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
Update XML from its configured schema to the latest major series.
Definition: schemas.c:1263
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:503
bool pcmk__ends_with(const char *s, const char *match)
Definition: strings.c:620
const char * pcmk__env_option(const char *option)
Definition: options.c:1094
Deprecated Pacemaker XML API.
const char * pcmk__remote_schema_dir(void)
Definition: schemas.c:1577
void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
Definition: schemas.c:1558
#define crm_warn(fmt, args...)
Definition: logging.h:394
int get_schema_version(const char *name)
Definition: schemas.c:1626
#define crm_debug(fmt, args...)
Definition: logging.h:402
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
Definition: xml.c:326
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:758
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:458
#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:1707
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:1618
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:958
int update_validation(xmlNode **xml, int *best, int max, gboolean transform, gboolean to_logs)
Definition: schemas.c:1648
#define pcmk__str_copy(str)
#define PCMK_XA_VALIDATE_WITH
Definition: xml_names.h:441
xmlNode * pcmk__xml_parse(const char *input)
Definition: xml_io.c:245
#define PCMK__ENV_REMOTE_SCHEMA_DIRECTORY
const xmlChar * pcmkXmlStr
Definition: xml.h:41
#define pcmk__assert(expr)
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:1667
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:287
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:1401
#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
void pcmk__warn_if_schema_deprecated(const char *schema)
Definition: schemas.c:1595
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:1147
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:1735
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:662
GList * pcmk__find_x_0_schema(void)
Definition: schemas.c:111
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml.c:770
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2259
#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:574
#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:647
const char * xml_latest_schema(void)
Definition: schemas.c:1612
#define PCMK_XA_VERSION
Definition: xml_names.h:444