pacemaker  3.0.0-d8340737c4
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 pcmk__schema_init() has been called
62  * beforehand, so we have at least two schemas (one real schema and the
63  * "none" schema).
64  *
65  * @COMPAT: The "none" schema is deprecated since 2.1.8.
66  * Update this when we drop that schema.
67  */
68  return g_list_length(known_schemas) - 2;
69 }
70 
77 static GList *
78 get_highest_schema(void)
79 {
80  /* The highest numerically versioned schema is the one before none
81  *
82  * @COMPAT none is deprecated since 2.1.8
83  */
84  GList *entry = pcmk__get_schema("none");
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 
219 static void
220 add_schema(enum pcmk__schema_validator validator,
221  const pcmk__schema_version_t *version, const char *name,
222  GList *transforms)
223 {
224  pcmk__schema_t *schema = NULL;
225 
226  schema = pcmk__assert_alloc(1, sizeof(pcmk__schema_t));
227 
228  schema->validator = validator;
229  schema->version.v[0] = version->v[0];
230  schema->version.v[1] = version->v[1];
231  schema->transforms = transforms;
232  // schema->schema_index is set after all schemas are loaded and sorted
233 
234  if (version->v[0] || version->v[1]) {
235  schema->name = schema_strdup_printf("pacemaker-", *version, "");
236  } else {
237  schema->name = pcmk__str_copy(name);
238  }
239 
240  known_schemas = g_list_prepend(known_schemas, schema);
241 }
242 
243 static void
244 wrap_libxslt(bool finalize)
245 {
246  static xsltSecurityPrefsPtr secprefs;
247  int ret = 0;
248 
249  /* security framework preferences */
250  if (!finalize) {
251  pcmk__assert(secprefs == NULL);
252  secprefs = xsltNewSecurityPrefs();
253  ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
254  xsltSecurityForbid)
255  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
256  xsltSecurityForbid)
257  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
258  xsltSecurityForbid)
259  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
260  xsltSecurityForbid);
261  if (ret != 0) {
262  return;
263  }
264  } else {
265  xsltFreeSecurityPrefs(secprefs);
266  secprefs = NULL;
267  }
268 
269  /* cleanup only */
270  if (finalize) {
271  xsltCleanupGlobals();
272  }
273 }
274 
284 static int
285 transform_filter(const struct dirent *entry)
286 {
287  return pcmk__str_eq(entry->d_name,
288  "upgrade-[[:digit:]]+.[[:digit:]]+-[[:digit:]]+.xsl",
289  pcmk__str_regex)? 1 : 0;
290 }
291 
298 static void
299 free_transform_list(void *data)
300 {
301  g_list_free_full((GList *) data, free);
302 }
303 
318 static GHashTable *
319 load_transforms_from_dir(const char *dir)
320 {
321  struct dirent **namelist = NULL;
322  int num_matches = scandir(dir, &namelist, transform_filter, versionsort);
323  GHashTable *transforms = pcmk__strkey_table(free, free_transform_list);
324 
325  for (int i = 0; i < num_matches; i++) {
327  int order = 0; // Placeholder only
328 
329  if (sscanf(namelist[i]->d_name, "upgrade-%hhu.%hhu-%d.xsl",
330  &(version.v[0]), &(version.v[1]), &order) == 3) {
331 
332  char *version_s = crm_strdup_printf("%hhu.%hhu",
333  version.v[0], version.v[1]);
334  GList *list = g_hash_table_lookup(transforms, version_s);
335 
336  if (list == NULL) {
337  /* Prepend is more efficient. However, there won't be many of
338  * these, and we want them to remain sorted by version. It's not
339  * worth reversing all the lists at the end.
340  *
341  * Avoid calling g_hash_table_insert() if the list already
342  * exists. Otherwise free_transform_list() gets called on it.
343  */
344  list = g_list_append(list, namelist[i]);
345  g_hash_table_insert(transforms, version_s, list);
346 
347  } else {
348  list = g_list_append(list, namelist[i]);
349  free(version_s);
350  }
351 
352  } else {
353  // Sanity only, should never happen thanks to transform_filter()
354  free(namelist[i]);
355  }
356  }
357 
358  free(namelist);
359  return transforms;
360 }
361 
362 void
364 {
365  int lpc, max;
366  struct dirent **namelist = NULL;
367  GHashTable *transforms = NULL;
368 
369  max = scandir(dir, &namelist, schema_filter, schema_cmp_directory);
370  if (max < 0) {
371  crm_warn("Could not load schemas from %s: %s", dir, strerror(errno));
372  return;
373  }
374 
375  // Look for any upgrade transforms in the same directory
376  transforms = load_transforms_from_dir(dir);
377 
378  for (lpc = 0; lpc < max; lpc++) {
380 
381  if (version_from_filename(namelist[lpc]->d_name, &version)) {
382  char *version_s = crm_strdup_printf("%hhu.%hhu",
383  version.v[0], version.v[1]);
384  char *orig_key = NULL;
385  GList *transform_list = NULL;
386 
387  // The schema becomes the owner of transform_list
388  g_hash_table_lookup_extended(transforms, version_s,
389  (gpointer *) &orig_key,
390  (gpointer *) &transform_list);
391  g_hash_table_steal(transforms, version_s);
392 
393  add_schema(pcmk__schema_validator_rng, &version, NULL,
394  transform_list);
395 
396  free(version_s);
397  free(orig_key);
398 
399  } else {
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  }
404  }
405 
406  for (lpc = 0; lpc < max; lpc++) {
407  free(namelist[lpc]);
408  }
409 
410  free(namelist);
411  g_hash_table_destroy(transforms);
412 }
413 
414 static gint
415 schema_sort_GCompareFunc(gconstpointer a, gconstpointer b)
416 {
417  const pcmk__schema_t *schema_a = a;
418  const pcmk__schema_t *schema_b = b;
419 
420  // @COMPAT The "none" schema is deprecated since 2.1.8
421  if (pcmk__str_eq(schema_a->name, PCMK_VALUE_NONE, pcmk__str_none)) {
422  return 1;
423  } else if (pcmk__str_eq(schema_b->name, PCMK_VALUE_NONE, pcmk__str_none)) {
424  return -1;
425  } else {
426  return schema_cmp(schema_a->version, schema_b->version);
427  }
428 }
429 
439 void
441 {
442  known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc);
443 }
444 
452 void
454 {
455  if (!initialized) {
456  const char *remote_schema_dir = pcmk__remote_schema_dir();
458  const pcmk__schema_version_t zero = SCHEMA_ZERO;
459  int schema_index = 0;
460 
461  initialized = true;
462 
463  wrap_libxslt(false);
464 
466  pcmk__load_schemas_from_dir(remote_schema_dir);
467  free(base);
468 
469  // @COMPAT Deprecated since 2.1.8
470  add_schema(pcmk__schema_validator_none, &zero, PCMK_VALUE_NONE, NULL);
471 
472  /* add_schema() prepends items to the list, so in the simple case, this
473  * just reverses the list. However if there were any remote schemas,
474  * sorting is necessary.
475  */
477 
478  // Now set the schema indexes and log the final result
479  for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
480  pcmk__schema_t *schema = iter->data;
481 
482  crm_debug("Loaded schema %d: %s", schema_index, schema->name);
483  schema->schema_index = schema_index++;
484  }
485  }
486 }
487 
488 static bool
489 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler,
490  void *error_handler_context, const char *relaxng_file,
491  relaxng_ctx_cache_t **cached_ctx)
492 {
493  int rc = 0;
494  bool valid = true;
495  relaxng_ctx_cache_t *ctx = NULL;
496 
497  CRM_CHECK(doc != NULL, return false);
498  CRM_CHECK(relaxng_file != NULL, return false);
499 
500  if (cached_ctx && *cached_ctx) {
501  ctx = *cached_ctx;
502 
503  } else {
504  crm_debug("Creating RNG parser context");
505  ctx = pcmk__assert_alloc(1, sizeof(relaxng_ctx_cache_t));
506 
507  ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
508  CRM_CHECK(ctx->parser != NULL, goto cleanup);
509 
510  if (error_handler) {
511  xmlRelaxNGSetParserErrors(ctx->parser,
512  (xmlRelaxNGValidityErrorFunc) error_handler,
513  (xmlRelaxNGValidityWarningFunc) error_handler,
514  error_handler_context);
515  } else {
516  xmlRelaxNGSetParserErrors(ctx->parser,
517  (xmlRelaxNGValidityErrorFunc) fprintf,
518  (xmlRelaxNGValidityWarningFunc) fprintf,
519  stderr);
520  }
521 
522  ctx->rng = xmlRelaxNGParse(ctx->parser);
523  CRM_CHECK(ctx->rng != NULL,
524  crm_err("Could not find/parse %s", relaxng_file);
525  goto cleanup);
526 
527  ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
528  CRM_CHECK(ctx->valid != NULL, goto cleanup);
529 
530  if (error_handler) {
531  xmlRelaxNGSetValidErrors(ctx->valid,
532  (xmlRelaxNGValidityErrorFunc) error_handler,
533  (xmlRelaxNGValidityWarningFunc) error_handler,
534  error_handler_context);
535  } else {
536  xmlRelaxNGSetValidErrors(ctx->valid,
537  (xmlRelaxNGValidityErrorFunc) fprintf,
538  (xmlRelaxNGValidityWarningFunc) fprintf,
539  stderr);
540  }
541  }
542 
543  rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
544  if (rc > 0) {
545  valid = false;
546 
547  } else if (rc < 0) {
548  crm_err("Internal libxml error during validation");
549  }
550 
551  cleanup:
552 
553  if (cached_ctx) {
554  *cached_ctx = ctx;
555 
556  } else {
557  if (ctx->parser != NULL) {
558  xmlRelaxNGFreeParserCtxt(ctx->parser);
559  }
560  if (ctx->valid != NULL) {
561  xmlRelaxNGFreeValidCtxt(ctx->valid);
562  }
563  if (ctx->rng != NULL) {
564  xmlRelaxNGFree(ctx->rng);
565  }
566  free(ctx);
567  }
568 
569  return valid;
570 }
571 
572 static void
573 free_schema(gpointer data)
574 {
575  pcmk__schema_t *schema = data;
576  relaxng_ctx_cache_t *ctx = NULL;
577 
578  switch (schema->validator) {
579  case pcmk__schema_validator_none: // not cached
580  break;
581 
582  case pcmk__schema_validator_rng: // cached
583  ctx = (relaxng_ctx_cache_t *) schema->cache;
584  if (ctx == NULL) {
585  break;
586  }
587 
588  if (ctx->parser != NULL) {
589  xmlRelaxNGFreeParserCtxt(ctx->parser);
590  }
591 
592  if (ctx->valid != NULL) {
593  xmlRelaxNGFreeValidCtxt(ctx->valid);
594  }
595 
596  if (ctx->rng != NULL) {
597  xmlRelaxNGFree(ctx->rng);
598  }
599 
600  free(ctx);
601  schema->cache = NULL;
602  break;
603  }
604 
605  free(schema->name);
606  g_list_free_full(schema->transforms, free);
607  free(schema);
608 }
609 
614 void
616 {
617  if (known_schemas != NULL) {
618  g_list_free_full(known_schemas, free_schema);
619  known_schemas = NULL;
620  }
621  initialized = false;
622 
623  wrap_libxslt(true);
624 }
625 
634 GList *
635 pcmk__get_schema(const char *name)
636 {
637  if (name == NULL) {
638  return NULL;
639  }
640  for (GList *iter = known_schemas; iter != NULL; iter = iter->next) {
641  pcmk__schema_t *schema = iter->data;
642 
643  if (pcmk__str_eq(name, schema->name, pcmk__str_none)) {
644  return iter;
645  }
646  }
647  return NULL;
648 }
649 
661 int
662 pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
663 {
664  GList *entry1 = pcmk__get_schema(schema1_name);
665  GList *entry2 = pcmk__get_schema(schema2_name);
666 
667  if (entry1 == NULL) {
668  return (entry2 == NULL)? 0 : -1;
669 
670  } else if (entry2 == NULL) {
671  return 1;
672 
673  } else {
674  pcmk__schema_t *schema1 = entry1->data;
675  pcmk__schema_t *schema2 = entry2->data;
676 
677  return schema1->schema_index - schema2->schema_index;
678  }
679 }
680 
681 static bool
682 validate_with(xmlNode *xml, pcmk__schema_t *schema,
683  xmlRelaxNGValidityErrorFunc error_handler,
684  void *error_handler_context)
685 {
686  bool valid = false;
687  char *file = NULL;
688  relaxng_ctx_cache_t **cache = NULL;
689 
690  if (schema == NULL) {
691  return false;
692  }
693 
694  if (schema->validator == pcmk__schema_validator_none) {
695  return true;
696  }
697 
699  schema->name);
700 
701  crm_trace("Validating with %s (type=%d)",
702  pcmk__s(file, "missing schema"), schema->validator);
703  switch (schema->validator) {
705  cache = (relaxng_ctx_cache_t **) &(schema->cache);
706  valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
707  break;
708  default:
709  crm_err("Unknown validator type: %d", schema->validator);
710  break;
711  }
712 
713  free(file);
714  return valid;
715 }
716 
717 static bool
718 validate_with_silent(xmlNode *xml, pcmk__schema_t *schema)
719 {
720  bool rc, sl_backup = silent_logging;
721  silent_logging = TRUE;
722  rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
723  silent_logging = sl_backup;
724  return rc;
725 }
726 
727 bool
728 pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
729  xmlRelaxNGValidityErrorFunc error_handler,
730  void *error_handler_context)
731 {
732  GList *entry = NULL;
733  pcmk__schema_t *schema = NULL;
734 
735  CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false);
736 
737  if (validation == NULL) {
738  validation = crm_element_value(xml_blob, PCMK_XA_VALIDATE_WITH);
739  }
741 
742  entry = pcmk__get_schema(validation);
743  if (entry == NULL) {
744  pcmk__config_err("Cannot validate CIB with %s " PCMK_XA_VALIDATE_WITH
745  " (manually edit to use a known schema)",
746  ((validation == NULL)? "missing" : "unknown"));
747  return false;
748  }
749 
750  schema = entry->data;
751  return validate_with(xml_blob, schema, error_handler,
752  error_handler_context);
753 }
754 
763 bool
765 {
766  return pcmk__validate_xml(xml, NULL,
767  (xmlRelaxNGValidityErrorFunc) xml_log,
768  GUINT_TO_POINTER(LOG_ERR));
769 }
770 
771 /* With this arrangement, an attempt to identify the message severity
772  as explicitly signalled directly from XSLT is performed in rather
773  a smart way (no reliance on formatting string + arguments being
774  always specified as ["%s", purposeful_string], as it can also be
775  ["%s: %s", some_prefix, purposeful_string] etc. so every argument
776  pertaining %s specifier is investigated), and if such a mark found,
777  the respective level is determined and, when the messages are to go
778  to the native logs, the mark itself gets dropped
779  (by the means of string shift).
780 
781  NOTE: whether the native logging is the right sink is decided per
782  the ctx parameter -- NULL denotes this case, otherwise it
783  carries a pointer to the numeric expression of the desired
784  target logging level (messages with higher level will be
785  suppressed)
786 
787  NOTE: on some architectures, this string shift may not have any
788  effect, but that's an acceptable tradeoff
789 
790  The logging level for not explicitly designated messages
791  (suspicious, likely internal errors or some runaways) is
792  LOG_WARNING.
793  */
794 static void G_GNUC_PRINTF(2, 3)
795 cib_upgrade_err(void *ctx, const char *fmt, ...)
796 {
797  va_list ap, aq;
798  char *arg_cur;
799 
800  bool found = false;
801  const char *fmt_iter = fmt;
802  uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
803  const unsigned * log_level = (const unsigned *) ctx;
804  enum {
805  escan_seennothing,
806  escan_seenpercent,
807  } scan_state = escan_seennothing;
808 
809  va_start(ap, fmt);
810  va_copy(aq, ap);
811 
812  while (!found && *fmt_iter != '\0') {
813  /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
814  switch (*fmt_iter++) {
815  case '%':
816  if (scan_state == escan_seennothing) {
817  scan_state = escan_seenpercent;
818  } else if (scan_state == escan_seenpercent) {
819  scan_state = escan_seennothing;
820  }
821  break;
822  case 's':
823  if (scan_state == escan_seenpercent) {
824  size_t prefix_len = 0;
825 
826  scan_state = escan_seennothing;
827  arg_cur = va_arg(aq, char *);
828 
829  if (pcmk__starts_with(arg_cur, "WARNING: ")) {
830  prefix_len = sizeof("WARNING: ") - 1;
831  msg_log_level = LOG_WARNING;
832 
833  } else if (pcmk__starts_with(arg_cur, "INFO: ")) {
834  prefix_len = sizeof("INFO: ") - 1;
835  msg_log_level = LOG_INFO;
836 
837  } else if (pcmk__starts_with(arg_cur, "DEBUG: ")) {
838  prefix_len = sizeof("DEBUG: ") - 1;
839  msg_log_level = LOG_DEBUG;
840 
841  } else {
842  break;
843  }
844 
845  found = true;
846  if (ctx == NULL) {
847  memmove(arg_cur, arg_cur + prefix_len,
848  strlen(arg_cur + prefix_len) + 1);
849  }
850  }
851  break;
852  case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
853  case '0': case '1': case '2': case '3': case '4':
854  case '5': case '6': case '7': case '8': case '9':
855  case '*':
856  break;
857  case 'l':
858  case 'z':
859  case 't':
860  case 'j':
861  case 'd': case 'i':
862  case 'o':
863  case 'u':
864  case 'x': case 'X':
865  case 'e': case 'E':
866  case 'f': case 'F':
867  case 'g': case 'G':
868  case 'a': case 'A':
869  case 'c':
870  case 'p':
871  if (scan_state == escan_seenpercent) {
872  (void) va_arg(aq, void *); /* skip forward */
873  scan_state = escan_seennothing;
874  }
875  break;
876  default:
877  scan_state = escan_seennothing;
878  break;
879  }
880  }
881 
882  if (log_level != NULL) {
883  /* intention of the following offset is:
884  cibadmin -V -> start showing INFO labelled messages */
885  if (*log_level + 4 >= msg_log_level) {
886  vfprintf(stderr, fmt, ap);
887  }
888  } else {
889  PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
890  }
891 
892  va_end(aq);
893  va_end(ap);
894 }
895 
907 static xmlNode *
908 apply_transformation(const xmlNode *xml, const char *transform,
909  gboolean to_logs)
910 {
911  char *xform = NULL;
912  xmlNode *out = NULL;
913  xmlDocPtr res = NULL;
914  xsltStylesheet *xslt = NULL;
915 
917  transform);
918 
919  /* for capturing, e.g., what's emitted via <xsl:message> */
920  if (to_logs) {
921  xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
922  } else {
923  xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
924  }
925 
926  xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
927  CRM_CHECK(xslt != NULL, goto cleanup);
928 
929  /* Caller allocates private data for final result document. Intermediate
930  * result documents are temporary and don't need private data.
931  */
932  res = xsltApplyStylesheet(xslt, xml->doc, NULL);
933  CRM_CHECK(res != NULL, goto cleanup);
934 
935  xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
936 
937  out = xmlDocGetRootElement(res);
938 
939  cleanup:
940  if (xslt) {
941  xsltFreeStylesheet(xslt);
942  }
943 
944  free(xform);
945 
946  return out;
947 }
948 
961 static xmlNode *
962 apply_upgrade(const xmlNode *input_xml, int schema_index, gboolean to_logs)
963 {
964  pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index);
965  pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas,
966  schema_index + 1);
967 
968  xmlNode *old_xml = NULL;
969  xmlNode *new_xml = NULL;
970  xmlRelaxNGValidityErrorFunc error_handler = NULL;
971 
972  pcmk__assert((schema != NULL) && (upgraded_schema != NULL));
973 
974  if (to_logs) {
975  error_handler = (xmlRelaxNGValidityErrorFunc) xml_log;
976  }
977 
978  for (GList *iter = schema->transforms; iter != NULL; iter = iter->next) {
979  const struct dirent *entry = iter->data;
980  const char *transform = entry->d_name;
981 
982  crm_debug("Upgrading schema from %s to %s: applying XSL transform %s",
983  schema->name, upgraded_schema->name, transform);
984 
985  new_xml = apply_transformation(input_xml, transform, to_logs);
986  pcmk__xml_free(old_xml);
987 
988  if (new_xml == NULL) {
989  crm_err("XSL transform %s failed, aborting upgrade", transform);
990  return NULL;
991  }
992  input_xml = new_xml;
993  old_xml = new_xml;
994  }
995 
996  // Final result document from upgrade pipeline needs private data
997  pcmk__xml_new_private_data((xmlNode *) new_xml->doc);
998 
999  // Ensure result validates with its new schema
1000  if (!validate_with(new_xml, upgraded_schema, error_handler,
1001  GUINT_TO_POINTER(LOG_ERR))) {
1002  crm_err("Schema upgrade from %s to %s failed: "
1003  "XSL transform pipeline produced an invalid configuration",
1004  schema->name, upgraded_schema->name);
1005  crm_log_xml_debug(new_xml, "bad-transform-result");
1006  pcmk__xml_free(new_xml);
1007  return NULL;
1008  }
1009 
1010  crm_info("Schema upgrade from %s to %s succeeded",
1011  schema->name, upgraded_schema->name);
1012  return new_xml;
1013 }
1014 
1023 static GList *
1024 get_configured_schema(const xmlNode *xml)
1025 {
1026  const char *schema_name = crm_element_value(xml, PCMK_XA_VALIDATE_WITH);
1027 
1028  pcmk__warn_if_schema_deprecated(schema_name);
1029  return pcmk__get_schema(schema_name);
1030 }
1031 
1046 int
1047 pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform,
1048  bool to_logs)
1049 {
1050  int max_stable_schemas = xml_latest_schema_index();
1051  int max_schema_index = 0;
1052  int rc = pcmk_rc_ok;
1053  GList *entry = NULL;
1054  pcmk__schema_t *best_schema = NULL;
1055  pcmk__schema_t *original_schema = NULL;
1056  xmlRelaxNGValidityErrorFunc error_handler =
1057  to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
1058 
1059  CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL),
1060  return EINVAL);
1061 
1062  if (max_schema_name != NULL) {
1063  GList *max_entry = pcmk__get_schema(max_schema_name);
1064 
1065  if (max_entry != NULL) {
1066  pcmk__schema_t *max_schema = max_entry->data;
1067 
1068  max_schema_index = max_schema->schema_index;
1069  }
1070  }
1071  if ((max_schema_index < 1) || (max_schema_index > max_stable_schemas)) {
1072  max_schema_index = max_stable_schemas;
1073  }
1074 
1075  entry = get_configured_schema(*xml);
1076  if (entry == NULL) {
1077  return pcmk_rc_cib_corrupt;
1078  }
1079  original_schema = entry->data;
1080  if (original_schema->schema_index >= max_schema_index) {
1081  return pcmk_rc_ok;
1082  }
1083 
1084  for (; entry != NULL; entry = entry->next) {
1085  pcmk__schema_t *current_schema = entry->data;
1086  xmlNode *upgrade = NULL;
1087 
1088  if (current_schema->schema_index > max_schema_index) {
1089  break;
1090  }
1091 
1092  if (!validate_with(*xml, current_schema, error_handler,
1093  GUINT_TO_POINTER(LOG_ERR))) {
1094  crm_debug("Schema %s does not validate", current_schema->name);
1095  if (best_schema != NULL) {
1096  /* we've satisfied the validation, no need to check further */
1097  break;
1098  }
1100  continue; // Try again with the next higher schema
1101  }
1102 
1103  crm_debug("Schema %s validates", current_schema->name);
1104  rc = pcmk_rc_ok;
1105  best_schema = current_schema;
1106  if (current_schema->schema_index == max_schema_index) {
1107  break; // No further transformations possible
1108  }
1109 
1110  if (!transform || (current_schema->transforms == NULL)
1111  || validate_with_silent(*xml, entry->next->data)) {
1112  /* The next schema either doesn't require a transform or validates
1113  * successfully even without the transform. Skip the transform and
1114  * try the next schema with the same XML.
1115  */
1116  continue;
1117  }
1118 
1119  upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs);
1120  if (upgrade == NULL) {
1121  /* The transform failed, so this schema can't be used. Later
1122  * schemas are unlikely to validate, but try anyway until we
1123  * run out of options.
1124  */
1126  } else {
1127  best_schema = current_schema;
1128  pcmk__xml_free(*xml);
1129  *xml = upgrade;
1130  }
1131  }
1132 
1133  if ((best_schema != NULL)
1134  && (best_schema->schema_index > original_schema->schema_index)) {
1135  crm_info("%s the configuration schema to %s",
1136  (transform? "Transformed" : "Upgraded"),
1137  best_schema->name);
1138  crm_xml_add(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name);
1139  }
1140  return rc;
1141 }
1142 
1143 int
1145 {
1146  return pcmk__update_configured_schema(xml, true);
1147 }
1148 
1158 int
1159 pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
1160 {
1161  pcmk__schema_t *x_0_schema = pcmk__find_x_0_schema()->data;
1162  pcmk__schema_t *original_schema = NULL;
1163  GList *entry = NULL;
1164 
1165  if (xml == NULL) {
1166  return EINVAL;
1167  }
1168 
1169  entry = get_configured_schema(*xml);
1170  if (entry == NULL) {
1171  return pcmk_rc_cib_corrupt;
1172  }
1173 
1174  original_schema = entry->data;
1175  if (original_schema->schema_index < x_0_schema->schema_index) {
1176  // Current configuration schema is not acceptable, try to update
1177  xmlNode *converted = NULL;
1178  const char *new_schema_name = NULL;
1179  pcmk__schema_t *schema = NULL;
1180 
1181  entry = NULL;
1182  converted = pcmk__xml_copy(NULL, *xml);
1183  if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) {
1184  new_schema_name = crm_element_value(converted,
1186  entry = pcmk__get_schema(new_schema_name);
1187  }
1188  schema = (entry == NULL)? NULL : entry->data;
1189 
1190  if ((schema == NULL)
1191  || (schema->schema_index < x_0_schema->schema_index)) {
1192  // Updated configuration schema is still not acceptable
1193 
1194  if ((schema == NULL)
1195  || (schema->schema_index < original_schema->schema_index)) {
1196  // We couldn't validate any schema at all
1197  if (to_logs) {
1198  pcmk__config_err("Cannot upgrade configuration (claiming "
1199  "%s schema) to at least %s because it "
1200  "does not validate with any schema from "
1201  "%s to the latest",
1202  original_schema->name,
1203  x_0_schema->name, original_schema->name);
1204  } else {
1205  fprintf(stderr, "Cannot upgrade configuration (claiming "
1206  "%s schema) to at least %s because it "
1207  "does not validate with any schema from "
1208  "%s to the latest\n",
1209  original_schema->name,
1210  x_0_schema->name, original_schema->name);
1211  }
1212  } else {
1213  // We updated configuration successfully, but still too low
1214  if (to_logs) {
1215  pcmk__config_err("Cannot upgrade configuration (claiming "
1216  "%s schema) to at least %s because it "
1217  "would not upgrade past %s",
1218  original_schema->name, x_0_schema->name,
1219  pcmk__s(new_schema_name, "unspecified version"));
1220  } else {
1221  fprintf(stderr, "Cannot upgrade configuration (claiming "
1222  "%s schema) to at least %s because it "
1223  "would not upgrade past %s\n",
1224  original_schema->name, x_0_schema->name,
1225  pcmk__s(new_schema_name, "unspecified version"));
1226  }
1227  }
1228 
1229  pcmk__xml_free(converted);
1230  converted = NULL;
1231  return pcmk_rc_transform_failed;
1232 
1233  } else {
1234  // Updated configuration schema is acceptable
1235  pcmk__xml_free(*xml);
1236  *xml = converted;
1237 
1238  if (schema->schema_index < xml_latest_schema_index()) {
1239  if (to_logs) {
1240  pcmk__config_warn("Configuration with %s schema was "
1241  "internally upgraded to acceptable (but "
1242  "not most recent) %s",
1243  original_schema->name, schema->name);
1244  }
1245  } else if (to_logs) {
1246  crm_info("Configuration with %s schema was internally "
1247  "upgraded to latest version %s",
1248  original_schema->name, schema->name);
1249  }
1250  }
1251 
1252  } else if (!to_logs) {
1253  pcmk__schema_t *none_schema = NULL;
1254 
1256  pcmk__assert((entry != NULL) && (entry->data != NULL));
1257 
1258  none_schema = entry->data;
1259  if (original_schema->schema_index >= none_schema->schema_index) {
1260  // @COMPAT the none schema is deprecated since 2.1.8
1261  fprintf(stderr, "Schema validation of configuration is "
1262  "disabled (support for " PCMK_XA_VALIDATE_WITH
1263  " set to \"" PCMK_VALUE_NONE "\" is deprecated"
1264  " and will be removed in a future release)\n");
1265  }
1266  }
1267 
1268  return pcmk_rc_ok;
1269 }
1270 
1283 GList *
1285 {
1286  GList *lst = NULL;
1288 
1289  if (!version_from_filename(name, &ver)) {
1290  return lst;
1291  }
1292 
1293  for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index());
1294  iter != NULL; iter = iter->prev) {
1295  pcmk__schema_t *schema = iter->data;
1296 
1297  if (schema_cmp(ver, schema->version) != -1) {
1298  continue;
1299  }
1300 
1301  for (GList *iter2 = g_list_last(schema->transforms); iter2 != NULL;
1302  iter2 = iter2->prev) {
1303 
1304  const struct dirent *entry = iter2->data;
1305 
1306  lst = g_list_prepend(lst, pcmk__str_copy(entry->d_name));
1307  }
1308 
1309  lst = g_list_prepend(lst, crm_strdup_printf("%s.rng", schema->name));
1310  }
1311 
1312  return lst;
1313 }
1314 
1315 static void
1316 append_href(xmlNode *xml, void *user_data)
1317 {
1318  GList **list = user_data;
1319  char *href = crm_element_value_copy(xml, "href");
1320 
1321  if (href == NULL) {
1322  return;
1323  }
1324  *list = g_list_prepend(*list, href);
1325 }
1326 
1327 static void
1328 external_refs_in_schema(GList **list, const char *contents)
1329 {
1330  /* local-name()= is needed to ignore the xmlns= setting at the top of
1331  * the XML file. Otherwise, the xpath query will always return nothing.
1332  */
1333  const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']";
1334  xmlNode *xml = pcmk__xml_parse(contents);
1335 
1336  crm_foreach_xpath_result(xml, search, append_href, list);
1337  pcmk__xml_free(xml);
1338 }
1339 
1340 static int
1341 read_file_contents(const char *file, char **contents)
1342 {
1343  int rc = pcmk_rc_ok;
1344  char *path = NULL;
1345 
1346  if (pcmk__ends_with(file, ".rng")) {
1348  } else {
1350  }
1351 
1352  rc = pcmk__file_contents(path, contents);
1353 
1354  free(path);
1355  return rc;
1356 }
1357 
1358 static void
1359 add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included)
1360 {
1361  char *contents = NULL;
1362  char *path = NULL;
1363  xmlNode *file_node = NULL;
1364  GList *includes = NULL;
1365  int rc = pcmk_rc_ok;
1366 
1367  /* If we already included this file, don't do so again. */
1368  if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) {
1369  return;
1370  }
1371 
1372  /* Ensure whatever file we were given has a suffix we know about. If not,
1373  * just assume it's an RNG file.
1374  */
1375  if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) {
1376  path = crm_strdup_printf("%s.rng", file);
1377  } else {
1378  path = pcmk__str_copy(file);
1379  }
1380 
1381  rc = read_file_contents(path, &contents);
1382  if (rc != pcmk_rc_ok || contents == NULL) {
1383  crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc));
1384  free(path);
1385  return;
1386  }
1387 
1388  /* Create a new <file path="..."> node with the contents of the file
1389  * as a CDATA block underneath it.
1390  */
1391  file_node = pcmk__xe_create(parent, PCMK_XA_FILE);
1392  crm_xml_add(file_node, PCMK_XA_PATH, path);
1393  *already_included = g_list_prepend(*already_included, path);
1394 
1395  xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents,
1396  strlen(contents)));
1397 
1398  /* Scan the file for any <externalRef> or <include> nodes and build up
1399  * a list of the files they reference.
1400  */
1401  external_refs_in_schema(&includes, contents);
1402 
1403  /* For each referenced file, recurse to add it (and potentially anything it
1404  * references, ...) to the XML.
1405  */
1406  for (GList *iter = includes; iter != NULL; iter = iter->next) {
1407  add_schema_file_to_xml(parent, iter->data, already_included);
1408  }
1409 
1410  free(contents);
1411  g_list_free_full(includes, free);
1412 }
1413 
1428 void
1429 pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
1430 {
1431  xmlNode *schema_node = pcmk__xe_create(parent, PCMK__XA_SCHEMA);
1432 
1433  crm_xml_add(schema_node, PCMK_XA_VERSION, name);
1434  add_schema_file_to_xml(schema_node, name, already_included);
1435 
1436  if (schema_node->children == NULL) {
1437  // Not needed if empty. May happen if name was invalid, for example.
1438  pcmk__xml_free(schema_node);
1439  }
1440 }
1441 
1447 const char *
1449 {
1451 
1452  if (pcmk__str_empty(dir)) {
1453  return PCMK__REMOTE_SCHEMA_DIR;
1454  }
1455 
1456  return dir;
1457 }
1458 
1465 void
1467 {
1468  /* @COMPAT Disabling validation is deprecated since 2.1.8, but
1469  * resource-agents' ocf-shellfuncs (at least as of 4.15.1) uses it
1470  */
1471  if (pcmk__str_eq(schema, PCMK_VALUE_NONE, pcmk__str_none)) {
1472  pcmk__config_warn("Support for " PCMK_XA_VALIDATE_WITH "='%s' is "
1473  "deprecated and will be removed in a future release "
1474  "without the possibility of upgrades (manually edit "
1475  "to use a supported schema)", schema);
1476  }
1477 }
1478 
1479 // Deprecated functions kept only for backward API compatibility
1480 // LCOV_EXCL_START
1481 
1482 #include <crm/common/xml_compat.h>
1483 
1484 gboolean
1485 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1486 {
1487  int rc = pcmk__update_configured_schema(xml, to_logs);
1488 
1489  if (best_version != NULL) {
1490  const char *name = crm_element_value(*xml, PCMK_XA_VALIDATE_WITH);
1491 
1492  if (name == NULL) {
1493  *best_version = -1;
1494  } else {
1495  GList *entry = pcmk__get_schema(name);
1496  pcmk__schema_t *schema = (entry == NULL)? NULL : entry->data;
1497 
1498  *best_version = (schema == NULL)? -1 : schema->schema_index;
1499  }
1500  }
1501  return (rc == pcmk_rc_ok)? TRUE: FALSE;
1502 }
1503 
1504 // LCOV_EXCL_STOP
1505 // End deprecated API
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:1463
int pcmk_update_configured_schema(xmlNode **xml)
Definition: schemas.c:1144
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
#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:728
char data[0]
Definition: cpg.c:58
#define PCMK_XA_PATH
Definition: xml_names.h:355
const char * name
Definition: cib.c:26
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
Definition: schemas.c:662
const char * pcmk__highest_schema_name(void)
Definition: schemas.c:97
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
int pcmk__update_configured_schema(xmlNode **xml, bool to_logs)
Update XML from its configured schema to the latest major series.
Definition: schemas.c:1159
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:609
bool pcmk__ends_with(const char *s, const char *match)
Definition: strings.c:610
const char * pcmk__env_option(const char *option)
Definition: options.c:1075
Deprecated Pacemaker XML API.
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml_element.c:407
const char * pcmk__remote_schema_dir(void)
Definition: schemas.c:1448
void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included)
Definition: schemas.c:1429
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
#define crm_warn(fmt, args...)
Definition: logging.h:362
#define crm_debug(fmt, args...)
Definition: logging.h:370
G_GNUC_INTERNAL void pcmk__xml_new_private_data(xmlNode *xml)
Definition: xml.c:328
#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:81
void pcmk__schema_init(void)
Definition: schemas.c:453
GList * pcmk__get_schema(const char *name)
Definition: schemas.c:635
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: xml_element.c:1466
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define PCMK_VALUE_NONE
Definition: options.h:179
#define crm_trace(fmt, args...)
Definition: logging.h:372
#define SCHEMA_ZERO
Definition: schemas.c:30
#define crm_log_xml_debug(xml, text)
Definition: logging.h:379
bool pcmk__configured_schema_validates(xmlNode *xml)
Definition: schemas.c:764
Wrappers for and extensions to libxml2.
enum pcmk__schema_validator validator
#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:168
#define PCMK__ENV_REMOTE_SCHEMA_DIRECTORY
const xmlChar * pcmkXmlStr
Definition: xml.h:41
#define pcmk__assert(expr)
unsigned int crm_log_level
Definition: logging.c:45
int pcmk__file_contents(const char *filename, char **contents)
Definition: io.c:428
#define PCMK_XA_FILE
Definition: xml_names.h:287
pcmk__schema_validator
void pcmk__load_schemas_from_dir(const char *dir)
Definition: schemas.c:363
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:685
GList * pcmk__schema_files_later_than(const char *name)
Definition: schemas.c:1284
const char * path
Definition: cib.c:28
#define crm_err(fmt, args...)
Definition: logging.h:359
void pcmk__warn_if_schema_deprecated(const char *schema)
Definition: schemas.c:1466
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
bool pcmk__starts_with(const char *str, const char *prefix)
Check whether a string starts with a certain sequence.
Definition: strings.c:558
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:1047
void pcmk__schema_cleanup(void)
Definition: schemas.c:615
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:1485
const char * parent
Definition: cib.c:27
GList * pcmk__find_x_0_schema(void)
Definition: schemas.c:111
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:1520
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
void pcmk__sort_schemas(void)
Definition: schemas.c:440
#define PCMK__REMOTE_SCHEMA_DIR
Definition: config.h:517
#define crm_info(fmt, args...)
Definition: logging.h:367
uint32_t version
Definition: remote.c:209
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:637
#define PCMK_XA_VERSION
Definition: xml_names.h:444
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1