pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
schemas.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2023 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <stdio.h>
13 #include <string.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <sys/stat.h>
17 #include <stdarg.h>
18 
19 #include <libxml/relaxng.h>
20 #include <libxslt/xslt.h>
21 #include <libxslt/transform.h>
22 #include <libxslt/security.h>
23 #include <libxslt/xsltutils.h>
24 
25 #include <crm/msg_xml.h>
26 #include <crm/common/xml.h>
27 #include <crm/common/xml_internal.h> /* PCMK__XML_LOG_BASE */
28 
29 typedef struct {
30  unsigned char v[2];
31 } schema_version_t;
32 
33 #define SCHEMA_ZERO { .v = { 0, 0 } }
34 
35 #define schema_scanf(s, prefix, version, suffix) \
36  sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
37 
38 #define schema_strdup_printf(prefix, version, suffix) \
39  crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
40 
41 typedef struct {
42  xmlRelaxNGPtr rng;
43  xmlRelaxNGValidCtxtPtr valid;
44  xmlRelaxNGParserCtxtPtr parser;
45 } relaxng_ctx_cache_t;
46 
50 };
51 
52 struct schema_s {
53  char *name;
54  char *transform;
55  void *cache;
56  enum schema_validator_e validator;
57  int after_transform;
58  schema_version_t version;
59  char *transform_enter;
60  bool transform_onleave;
61 };
62 
63 static struct schema_s *known_schemas = NULL;
64 static int xml_schema_max = 0;
65 static bool silent_logging = FALSE;
66 
67 static void
68 xml_log(int priority, const char *fmt, ...)
69 G_GNUC_PRINTF(2, 3);
70 
71 static void
72 xml_log(int priority, const char *fmt, ...)
73 {
74  va_list ap;
75 
76  va_start(ap, fmt);
77  if (silent_logging == FALSE) {
78  /* XXX should not this enable dechunking as well? */
79  PCMK__XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
80  }
81  va_end(ap);
82 }
83 
84 static int
85 xml_latest_schema_index(void)
86 {
87  // @COMPAT: pacemaker-next is deprecated since 2.1.5
88  return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
89 }
90 
91 static int
92 xml_minimum_schema_index(void)
93 {
94  static int best = 0;
95  if (best == 0) {
96  int lpc = 0;
97 
98  best = xml_latest_schema_index();
99  for (lpc = best; lpc > 0; lpc--) {
100  if (known_schemas[lpc].version.v[0]
101  < known_schemas[best].version.v[0]) {
102  return best;
103  } else {
104  best = lpc;
105  }
106  }
107  best = xml_latest_schema_index();
108  }
109  return best;
110 }
111 
112 const char *
114 {
115  return get_schema_name(xml_latest_schema_index());
116 }
117 
118 static inline bool
119 version_from_filename(const char *filename, schema_version_t *version)
120 {
121  int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
122 
123  return (rc == 2);
124 }
125 
126 static int
127 schema_filter(const struct dirent *a)
128 {
129  int rc = 0;
130  schema_version_t version = SCHEMA_ZERO;
131 
132  if (strstr(a->d_name, "pacemaker-") != a->d_name) {
133  /* crm_trace("%s - wrong prefix", a->d_name); */
134 
135  } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
136  /* crm_trace("%s - wrong suffix", a->d_name); */
137 
138  } else if (!version_from_filename(a->d_name, &version)) {
139  /* crm_trace("%s - wrong format", a->d_name); */
140 
141  } else {
142  /* crm_debug("%s - candidate", a->d_name); */
143  rc = 1;
144  }
145 
146  return rc;
147 }
148 
149 static int
150 schema_sort(const struct dirent **a, const struct dirent **b)
151 {
152  schema_version_t a_version = SCHEMA_ZERO;
153  schema_version_t b_version = SCHEMA_ZERO;
154 
155  if (!version_from_filename(a[0]->d_name, &a_version)
156  || !version_from_filename(b[0]->d_name, &b_version)) {
157  // Shouldn't be possible, but makes static analysis happy
158  return 0;
159  }
160 
161  for (int i = 0; i < 2; ++i) {
162  if (a_version.v[i] < b_version.v[i]) {
163  return -1;
164  } else if (a_version.v[i] > b_version.v[i]) {
165  return 1;
166  }
167  }
168  return 0;
169 }
170 
178 static void
179 add_schema(enum schema_validator_e validator, const schema_version_t *version,
180  const char *name, const char *transform,
181  const char *transform_enter, bool transform_onleave,
182  int after_transform)
183 {
184  int last = xml_schema_max;
185  bool have_version = FALSE;
186 
187  xml_schema_max++;
188  known_schemas = pcmk__realloc(known_schemas,
189  xml_schema_max * sizeof(struct schema_s));
190  CRM_ASSERT(known_schemas != NULL);
191  memset(known_schemas+last, 0, sizeof(struct schema_s));
192  known_schemas[last].validator = validator;
193  known_schemas[last].after_transform = after_transform;
194 
195  for (int i = 0; i < 2; ++i) {
196  known_schemas[last].version.v[i] = version->v[i];
197  if (version->v[i]) {
198  have_version = TRUE;
199  }
200  }
201  if (have_version) {
202  known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
203  } else {
204  CRM_ASSERT(name);
205  schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
206  known_schemas[last].name = strdup(name);
207  }
208 
209  if (transform) {
210  known_schemas[last].transform = strdup(transform);
211  }
212  if (transform_enter) {
213  known_schemas[last].transform_enter = strdup(transform_enter);
214  }
215  known_schemas[last].transform_onleave = transform_onleave;
216  if (after_transform == 0) {
217  after_transform = xml_schema_max; /* upgrade is a one-way */
218  }
219  known_schemas[last].after_transform = after_transform;
220 
221  if (known_schemas[last].after_transform < 0) {
222  crm_debug("Added supported schema %d: %s",
223  last, known_schemas[last].name);
224 
225  } else if (known_schemas[last].transform) {
226  crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
227  last, known_schemas[last].name,
228  known_schemas[last].after_transform,
229  known_schemas[last].transform);
230 
231  } else {
232  crm_debug("Added supported schema %d: %s (upgrades to %d)",
233  last, known_schemas[last].name,
234  known_schemas[last].after_transform);
235  }
236 }
237 
266 static int
267 add_schema_by_version(const schema_version_t *version, int next,
268  bool transform_expected)
269 {
270  bool transform_onleave = FALSE;
271  int rc = pcmk_rc_ok;
272  struct stat s;
273  char *xslt = NULL,
274  *transform_upgrade = NULL,
275  *transform_enter = NULL;
276 
277  /* prologue for further transform_expected handling */
278  if (transform_expected) {
279  /* check if there's suitable "upgrade" stylesheet */
280  transform_upgrade = schema_strdup_printf("upgrade-", *version, );
282  transform_upgrade);
283  }
284 
285  if (!transform_expected) {
286  /* jump directly to the end */
287 
288  } else if (stat(xslt, &s) == 0) {
289  /* perhaps there's also a targeted "upgrade-enter" stylesheet */
290  transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
291  free(xslt);
293  transform_enter);
294  if (stat(xslt, &s) != 0) {
295  /* or initially, at least a generic one */
296  crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
297  free(xslt);
298  free(transform_enter);
299  transform_enter = strdup("upgrade-enter");
301  transform_enter);
302  if (stat(xslt, &s) != 0) {
303  crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
304  free(xslt);
305  xslt = NULL;
306  }
307  }
308  /* xslt contains full path to "upgrade-enter" stylesheet */
309  if (xslt != NULL) {
310  /* then there should be "upgrade-leave" counterpart (enter->leave) */
311  memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
312  transform_onleave = (stat(xslt, &s) == 0);
313  free(xslt);
314  } else {
315  free(transform_enter);
316  transform_enter = NULL;
317  }
318 
319  } else {
320  crm_err("Upgrade transform %s not found", xslt);
321  free(xslt);
322  free(transform_upgrade);
323  transform_upgrade = NULL;
324  next = -1;
325  rc = ENOENT;
326  }
327 
328  add_schema(schema_validator_rng, version, NULL,
329  transform_upgrade, transform_enter, transform_onleave, next);
330 
331  free(transform_upgrade);
332  free(transform_enter);
333 
334  return rc;
335 }
336 
337 static void
338 wrap_libxslt(bool finalize)
339 {
340  static xsltSecurityPrefsPtr secprefs;
341  int ret = 0;
342 
343  /* security framework preferences */
344  if (!finalize) {
345  CRM_ASSERT(secprefs == NULL);
346  secprefs = xsltNewSecurityPrefs();
347  ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
348  xsltSecurityForbid)
349  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
350  xsltSecurityForbid)
351  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
352  xsltSecurityForbid)
353  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
354  xsltSecurityForbid);
355  if (ret != 0) {
356  return;
357  }
358  } else {
359  xsltFreeSecurityPrefs(secprefs);
360  secprefs = NULL;
361  }
362 
363  /* cleanup only */
364  if (finalize) {
365  xsltCleanupGlobals();
366  }
367 }
368 
376 void
378 {
379  int lpc, max;
381  struct dirent **namelist = NULL;
382  const schema_version_t zero = SCHEMA_ZERO;
383 
384  wrap_libxslt(false);
385 
386  max = scandir(base, &namelist, schema_filter, schema_sort);
387  if (max < 0) {
388  crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
389  free(base);
390 
391  } else {
392  free(base);
393  for (lpc = 0; lpc < max; lpc++) {
394  bool transform_expected = FALSE;
395  int next = 0;
396  schema_version_t version = SCHEMA_ZERO;
397 
398  if (!version_from_filename(namelist[lpc]->d_name, &version)) {
399  // Shouldn't be possible, but makes static analysis happy
400  crm_err("Skipping schema '%s': could not parse version",
401  namelist[lpc]->d_name);
402  continue;
403  }
404  if ((lpc + 1) < max) {
405  schema_version_t next_version = SCHEMA_ZERO;
406 
407  if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
408  && (version.v[0] < next_version.v[0])) {
409  transform_expected = TRUE;
410  }
411 
412  } else {
413  next = -1;
414  }
415  if (add_schema_by_version(&version, next, transform_expected)
416  == ENOENT) {
417  break;
418  }
419  }
420 
421  for (lpc = 0; lpc < max; lpc++) {
422  free(namelist[lpc]);
423  }
424  free(namelist);
425  }
426 
427  // @COMPAT: Deprecated since 2.1.5
428  add_schema(schema_validator_rng, &zero, "pacemaker-next",
429  NULL, NULL, FALSE, -1);
430 
431  add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
432  NULL, NULL, FALSE, -1);
433 }
434 
435 static gboolean
436 validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file,
437  relaxng_ctx_cache_t **cached_ctx)
438 {
439  int rc = 0;
440  gboolean valid = TRUE;
441  relaxng_ctx_cache_t *ctx = NULL;
442 
443  CRM_CHECK(doc != NULL, return FALSE);
444  CRM_CHECK(relaxng_file != NULL, return FALSE);
445 
446  if (cached_ctx && *cached_ctx) {
447  ctx = *cached_ctx;
448 
449  } else {
450  crm_debug("Creating RNG parser context");
451  ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
452 
453  ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
454  CRM_CHECK(ctx->parser != NULL, goto cleanup);
455 
456  if (error_handler) {
457  xmlRelaxNGSetParserErrors(ctx->parser,
458  (xmlRelaxNGValidityErrorFunc) error_handler,
459  (xmlRelaxNGValidityWarningFunc) error_handler,
460  error_handler_context);
461  } else {
462  xmlRelaxNGSetParserErrors(ctx->parser,
463  (xmlRelaxNGValidityErrorFunc) fprintf,
464  (xmlRelaxNGValidityWarningFunc) fprintf,
465  stderr);
466  }
467 
468  ctx->rng = xmlRelaxNGParse(ctx->parser);
469  CRM_CHECK(ctx->rng != NULL,
470  crm_err("Could not find/parse %s", relaxng_file);
471  goto cleanup);
472 
473  ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
474  CRM_CHECK(ctx->valid != NULL, goto cleanup);
475 
476  if (error_handler) {
477  xmlRelaxNGSetValidErrors(ctx->valid,
478  (xmlRelaxNGValidityErrorFunc) error_handler,
479  (xmlRelaxNGValidityWarningFunc) error_handler,
480  error_handler_context);
481  } else {
482  xmlRelaxNGSetValidErrors(ctx->valid,
483  (xmlRelaxNGValidityErrorFunc) fprintf,
484  (xmlRelaxNGValidityWarningFunc) fprintf,
485  stderr);
486  }
487  }
488 
489  rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
490  if (rc > 0) {
491  valid = FALSE;
492 
493  } else if (rc < 0) {
494  crm_err("Internal libxml error during validation");
495  }
496 
497  cleanup:
498 
499  if (cached_ctx) {
500  *cached_ctx = ctx;
501 
502  } else {
503  if (ctx->parser != NULL) {
504  xmlRelaxNGFreeParserCtxt(ctx->parser);
505  }
506  if (ctx->valid != NULL) {
507  xmlRelaxNGFreeValidCtxt(ctx->valid);
508  }
509  if (ctx->rng != NULL) {
510  xmlRelaxNGFree(ctx->rng);
511  }
512  free(ctx);
513  }
514 
515  return valid;
516 }
517 
522 void
524 {
525  int lpc;
526  relaxng_ctx_cache_t *ctx = NULL;
527 
528  for (lpc = 0; lpc < xml_schema_max; lpc++) {
529 
530  switch (known_schemas[lpc].validator) {
531  case schema_validator_none: // not cached
532  break;
533  case schema_validator_rng: // cached
534  ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
535  if (ctx == NULL) {
536  break;
537  }
538  if (ctx->parser != NULL) {
539  xmlRelaxNGFreeParserCtxt(ctx->parser);
540  }
541  if (ctx->valid != NULL) {
542  xmlRelaxNGFreeValidCtxt(ctx->valid);
543  }
544  if (ctx->rng != NULL) {
545  xmlRelaxNGFree(ctx->rng);
546  }
547  free(ctx);
548  known_schemas[lpc].cache = NULL;
549  break;
550  }
551  free(known_schemas[lpc].name);
552  free(known_schemas[lpc].transform);
553  free(known_schemas[lpc].transform_enter);
554  }
555  free(known_schemas);
556  known_schemas = NULL;
557 
558  wrap_libxslt(true);
559 }
560 
561 static gboolean
562 validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
563 {
564  gboolean valid = FALSE;
565  char *file = NULL;
566  struct schema_s *schema = NULL;
567  relaxng_ctx_cache_t **cache = NULL;
568 
569  if (method < 0) {
570  return FALSE;
571  }
572 
573  schema = &(known_schemas[method]);
574  if (schema->validator == schema_validator_none) {
575  return TRUE;
576  }
577 
578  if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) {
579  crm_warn("The pacemaker-next schema is deprecated and will be removed "
580  "in a future release.");
581  }
582 
584  schema->name);
585 
586  crm_trace("Validating with %s (type=%d)",
587  pcmk__s(file, "missing schema"), schema->validator);
588  switch (schema->validator) {
590  cache = (relaxng_ctx_cache_t **) &(schema->cache);
591  valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache);
592  break;
593  default:
594  crm_err("Unknown validator type: %d",
595  known_schemas[method].validator);
596  break;
597  }
598 
599  free(file);
600  return valid;
601 }
602 
603 static bool
604 validate_with_silent(xmlNode *xml, int method)
605 {
606  bool rc, sl_backup = silent_logging;
607  silent_logging = TRUE;
608  rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR));
609  silent_logging = sl_backup;
610  return rc;
611 }
612 
613 static void
614 dump_file(const char *filename)
615 {
616 
617  FILE *fp = NULL;
618  int ch, line = 0;
619 
620  CRM_CHECK(filename != NULL, return);
621 
622  fp = fopen(filename, "r");
623  if (fp == NULL) {
624  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
625  return;
626  }
627 
628  fprintf(stderr, "%4d ", ++line);
629  do {
630  ch = getc(fp);
631  if (ch == EOF) {
632  putc('\n', stderr);
633  break;
634  } else if (ch == '\n') {
635  fprintf(stderr, "\n%4d ", ++line);
636  } else {
637  putc(ch, stderr);
638  }
639  } while (1);
640 
641  fclose(fp);
642 }
643 
644 gboolean
645 validate_xml_verbose(const xmlNode *xml_blob)
646 {
647  int fd = 0;
648  xmlDoc *doc = NULL;
649  xmlNode *xml = NULL;
650  gboolean rc = FALSE;
651  char *filename = NULL;
652 
653  filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
654 
655  umask(S_IWGRP | S_IWOTH | S_IROTH);
656  fd = mkstemp(filename);
657  write_xml_fd(xml_blob, filename, fd, FALSE);
658 
659  dump_file(filename);
660 
661  doc = xmlReadFile(filename, NULL, 0);
662  xml = xmlDocGetRootElement(doc);
663  rc = validate_xml(xml, NULL, FALSE);
664  free_xml(xml);
665 
666  unlink(filename);
667  free(filename);
668 
669  return rc;
670 }
671 
672 gboolean
673 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
674 {
675  return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR));
676 }
677 
678 gboolean
679 pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context)
680 {
681  int version = 0;
682 
683  CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE);
684 
685  if (validation == NULL) {
686  validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
687  }
688 
689  if (validation == NULL) {
690  int lpc = 0;
691  bool valid = FALSE;
692 
693  for (lpc = 0; lpc < xml_schema_max; lpc++) {
694  if (validate_with(xml_blob, lpc, NULL, NULL)) {
695  valid = TRUE;
697  known_schemas[lpc].name);
698  crm_info("XML validated against %s", known_schemas[lpc].name);
699  if(known_schemas[lpc].after_transform == 0) {
700  break;
701  }
702  }
703  }
704 
705  return valid;
706  }
707 
708  version = get_schema_version(validation);
709  if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
710  return TRUE;
711  } else if (version < xml_schema_max) {
712  return validate_with(xml_blob, version, error_handler, error_handler_context);
713  }
714 
715  crm_err("Unknown validator: %s", validation);
716  return FALSE;
717 }
718 
719 static void
720 cib_upgrade_err(void *ctx, const char *fmt, ...)
721 G_GNUC_PRINTF(2, 3);
722 
723 /* With this arrangement, an attempt to identify the message severity
724  as explicitly signalled directly from XSLT is performed in rather
725  a smart way (no reliance on formatting string + arguments being
726  always specified as ["%s", purposeful_string], as it can also be
727  ["%s: %s", some_prefix, purposeful_string] etc. so every argument
728  pertaining %s specifier is investigated), and if such a mark found,
729  the respective level is determined and, when the messages are to go
730  to the native logs, the mark itself gets dropped
731  (by the means of string shift).
732 
733  NOTE: whether the native logging is the right sink is decided per
734  the ctx parameter -- NULL denotes this case, otherwise it
735  carries a pointer to the numeric expression of the desired
736  target logging level (messages with higher level will be
737  suppressed)
738 
739  NOTE: on some architectures, this string shift may not have any
740  effect, but that's an acceptable tradeoff
741 
742  The logging level for not explicitly designated messages
743  (suspicious, likely internal errors or some runaways) is
744  LOG_WARNING.
745  */
746 static void
747 cib_upgrade_err(void *ctx, const char *fmt, ...)
748 {
749  va_list ap, aq;
750  char *arg_cur;
751 
752  bool found = FALSE;
753  const char *fmt_iter = fmt;
754  uint8_t msg_log_level = LOG_WARNING; /* default for runaway messages */
755  const unsigned * log_level = (const unsigned *) ctx;
756  enum {
757  escan_seennothing,
758  escan_seenpercent,
759  } scan_state = escan_seennothing;
760 
761  va_start(ap, fmt);
762  va_copy(aq, ap);
763 
764  while (!found && *fmt_iter != '\0') {
765  /* while casing schema borrowed from libqb:qb_vsnprintf_serialize */
766  switch (*fmt_iter++) {
767  case '%':
768  if (scan_state == escan_seennothing) {
769  scan_state = escan_seenpercent;
770  } else if (scan_state == escan_seenpercent) {
771  scan_state = escan_seennothing;
772  }
773  break;
774  case 's':
775  if (scan_state == escan_seenpercent) {
776  scan_state = escan_seennothing;
777  arg_cur = va_arg(aq, char *);
778  if (arg_cur != NULL) {
779  switch (arg_cur[0]) {
780  case 'W':
781  if (!strncmp(arg_cur, "WARNING: ",
782  sizeof("WARNING: ") - 1)) {
783  msg_log_level = LOG_WARNING;
784  }
785  if (ctx == NULL) {
786  memmove(arg_cur, arg_cur + sizeof("WARNING: ") - 1,
787  strlen(arg_cur + sizeof("WARNING: ") - 1) + 1);
788  }
789  found = TRUE;
790  break;
791  case 'I':
792  if (!strncmp(arg_cur, "INFO: ",
793  sizeof("INFO: ") - 1)) {
794  msg_log_level = LOG_INFO;
795  }
796  if (ctx == NULL) {
797  memmove(arg_cur, arg_cur + sizeof("INFO: ") - 1,
798  strlen(arg_cur + sizeof("INFO: ") - 1) + 1);
799  }
800  found = TRUE;
801  break;
802  case 'D':
803  if (!strncmp(arg_cur, "DEBUG: ",
804  sizeof("DEBUG: ") - 1)) {
805  msg_log_level = LOG_DEBUG;
806  }
807  if (ctx == NULL) {
808  memmove(arg_cur, arg_cur + sizeof("DEBUG: ") - 1,
809  strlen(arg_cur + sizeof("DEBUG: ") - 1) + 1);
810  }
811  found = TRUE;
812  break;
813  }
814  }
815  }
816  break;
817  case '#': case '-': case ' ': case '+': case '\'': case 'I': case '.':
818  case '0': case '1': case '2': case '3': case '4':
819  case '5': case '6': case '7': case '8': case '9':
820  case '*':
821  break;
822  case 'l':
823  case 'z':
824  case 't':
825  case 'j':
826  case 'd': case 'i':
827  case 'o':
828  case 'u':
829  case 'x': case 'X':
830  case 'e': case 'E':
831  case 'f': case 'F':
832  case 'g': case 'G':
833  case 'a': case 'A':
834  case 'c':
835  case 'p':
836  if (scan_state == escan_seenpercent) {
837  (void) va_arg(aq, void *); /* skip forward */
838  scan_state = escan_seennothing;
839  }
840  break;
841  default:
842  scan_state = escan_seennothing;
843  break;
844  }
845  }
846 
847  if (log_level != NULL) {
848  /* intention of the following offset is:
849  cibadmin -V -> start showing INFO labelled messages */
850  if (*log_level + 4 >= msg_log_level) {
851  vfprintf(stderr, fmt, ap);
852  }
853  } else {
854  PCMK__XML_LOG_BASE(msg_log_level, TRUE, 0, "CIB upgrade: ", fmt, ap);
855  }
856 
857  va_end(aq);
858  va_end(ap);
859 }
860 
861 static xmlNode *
862 apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
863 {
864  char *xform = NULL;
865  xmlNode *out = NULL;
866  xmlDocPtr res = NULL;
867  xsltStylesheet *xslt = NULL;
868 
870  transform);
871 
872  /* for capturing, e.g., what's emitted via <xsl:message> */
873  if (to_logs) {
874  xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
875  } else {
876  xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
877  }
878 
879  xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
880  CRM_CHECK(xslt != NULL, goto cleanup);
881 
882  res = xsltApplyStylesheet(xslt, xml->doc, NULL);
883  CRM_CHECK(res != NULL, goto cleanup);
884 
885  xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
886 
887  out = xmlDocGetRootElement(res);
888 
889  cleanup:
890  if (xslt) {
891  xsltFreeStylesheet(xslt);
892  }
893 
894  free(xform);
895 
896  return out;
897 }
898 
905 static xmlNode *
906 apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
907 {
908  bool transform_onleave = schema->transform_onleave;
909  char *transform_leave;
910  xmlNode *upgrade = NULL,
911  *final = NULL;
912 
913  if (schema->transform_enter) {
914  crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
915  schema->name, schema->transform_enter);
916  upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
917  if (upgrade == NULL) {
918  crm_warn("Upgrade-enter transformation %s.xsl failed",
919  schema->transform_enter);
920  transform_onleave = FALSE;
921  }
922  }
923  if (upgrade == NULL) {
924  upgrade = xml;
925  }
926 
927  crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
928  schema->name, schema->transform);
929  final = apply_transformation(upgrade, schema->transform, to_logs);
930  if (upgrade != xml) {
931  free_xml(upgrade);
932  upgrade = NULL;
933  }
934 
935  if (final != NULL && transform_onleave) {
936  upgrade = final;
937  /* following condition ensured in add_schema_by_version */
938  CRM_ASSERT(schema->transform_enter != NULL);
939  transform_leave = strdup(schema->transform_enter);
940  /* enter -> leave */
941  memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
942  crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
943  schema->name, transform_leave);
944  final = apply_transformation(upgrade, transform_leave, to_logs);
945  if (final == NULL) {
946  crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
947  final = upgrade;
948  } else {
949  free_xml(upgrade);
950  }
951  free(transform_leave);
952  }
953 
954  return final;
955 }
956 
957 const char *
959 {
960  if (version < 0 || version >= xml_schema_max) {
961  return "unknown";
962  }
963  return known_schemas[version].name;
964 }
965 
966 int
968 {
969  int lpc = 0;
970 
971  if (name == NULL) {
973  }
974  for (; lpc < xml_schema_max; lpc++) {
975  if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
976  return lpc;
977  }
978  }
979  return -1;
980 }
981 
982 /* set which validation to use */
983 int
984 update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
985  gboolean to_logs)
986 {
987  xmlNode *xml = NULL;
988  char *value = NULL;
989  int max_stable_schemas = xml_latest_schema_index();
990  int lpc = 0, match = -1, rc = pcmk_ok;
991  int next = -1; /* -1 denotes "inactive" value */
992  xmlRelaxNGValidityErrorFunc error_handler =
993  to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL;
994 
995  CRM_CHECK(best != NULL, return -EINVAL);
996  *best = 0;
997 
998  CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL)
999  && ((*xml_blob)->doc != NULL),
1000  return -EINVAL);
1001 
1002  xml = *xml_blob;
1004 
1005  if (value != NULL) {
1006  match = get_schema_version(value);
1007 
1008  lpc = match;
1009  if (lpc >= 0 && transform == FALSE) {
1010  *best = lpc++;
1011 
1012  } else if (lpc < 0) {
1013  crm_debug("Unknown validation schema");
1014  lpc = 0;
1015  }
1016  }
1017 
1018  if (match >= max_stable_schemas) {
1019  /* nothing to do */
1020  free(value);
1021  *best = match;
1022  return pcmk_ok;
1023  }
1024 
1025  while (lpc <= max_stable_schemas) {
1026  crm_debug("Testing '%s' validation (%d of %d)",
1027  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1028  lpc, max_stable_schemas);
1029 
1030  if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) {
1031  if (next != -1) {
1032  crm_info("Configuration not valid for schema: %s",
1033  known_schemas[lpc].name);
1034  next = -1;
1035  } else {
1036  crm_trace("%s validation failed",
1037  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1038  }
1039  if (*best) {
1040  /* we've satisfied the validation, no need to check further */
1041  break;
1042  }
1044 
1045  } else {
1046  if (next != -1) {
1047  crm_debug("Configuration valid for schema: %s",
1048  known_schemas[next].name);
1049  next = -1;
1050  }
1051  rc = pcmk_ok;
1052  }
1053 
1054  if (rc == pcmk_ok) {
1055  *best = lpc;
1056  }
1057 
1058  if (rc == pcmk_ok && transform) {
1059  xmlNode *upgrade = NULL;
1060  next = known_schemas[lpc].after_transform;
1061 
1062  if (next <= lpc) {
1063  /* There is no next version, or next would regress */
1064  crm_trace("Stopping at %s", known_schemas[lpc].name);
1065  break;
1066 
1067  } else if (max > 0 && (lpc == max || next > max)) {
1068  crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1069  known_schemas[lpc].name, lpc, next, max);
1070  break;
1071 
1072  } else if (known_schemas[lpc].transform == NULL
1073  /* possibly avoid transforming when readily valid
1074  (in general more restricted when crossing the major
1075  version boundary, as X.0 "transitional" version is
1076  expected to be more strict than it's successors that
1077  may re-allow constructs from previous major line) */
1078  || validate_with_silent(xml, next)) {
1079  crm_debug("%s-style configuration is also valid for %s",
1080  known_schemas[lpc].name, known_schemas[next].name);
1081 
1082  lpc = next;
1083 
1084  } else {
1085  crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1086  known_schemas[lpc].name, known_schemas[next].name,
1087  known_schemas[lpc].transform);
1088 
1089  upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1090  if (upgrade == NULL) {
1091  crm_err("Transformation %s.xsl failed",
1092  known_schemas[lpc].transform);
1094 
1095  } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) {
1096  crm_info("Transformation %s.xsl successful",
1097  known_schemas[lpc].transform);
1098  lpc = next;
1099  *best = next;
1100  free_xml(xml);
1101  xml = upgrade;
1102  rc = pcmk_ok;
1103 
1104  } else {
1105  crm_err("Transformation %s.xsl did not produce a valid configuration",
1106  known_schemas[lpc].transform);
1107  crm_log_xml_info(upgrade, "transform:bad");
1108  free_xml(upgrade);
1110  }
1111  next = -1;
1112  }
1113  }
1114 
1115  if (transform == FALSE || rc != pcmk_ok) {
1116  /* we need some progress! */
1117  lpc++;
1118  }
1119  }
1120 
1121  if (*best > match && *best) {
1122  crm_info("%s the configuration from %s to %s",
1123  transform?"Transformed":"Upgraded",
1124  value ? value : "<none>", known_schemas[*best].name);
1125  crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1126  }
1127 
1128  *xml_blob = xml;
1129  free(value);
1130  return rc;
1131 }
1132 
1133 gboolean
1134 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1135 {
1136  gboolean rc = TRUE;
1137  const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1138  char *const orig_value = strdup(value == NULL ? "(none)" : value);
1139 
1140  int version = get_schema_version(value);
1141  int orig_version = version;
1142  int min_version = xml_minimum_schema_index();
1143 
1144  if (version < min_version) {
1145  // Current configuration schema is not acceptable, try to update
1146  xmlNode *converted = NULL;
1147 
1148  converted = copy_xml(*xml);
1149  update_validation(&converted, &version, 0, TRUE, to_logs);
1150 
1151  value = crm_element_value(converted, XML_ATTR_VALIDATION);
1152  if (version < min_version) {
1153  // Updated configuration schema is still not acceptable
1154 
1155  if (version < orig_version || orig_version == -1) {
1156  // We couldn't validate any schema at all
1157  if (to_logs) {
1158  pcmk__config_err("Cannot upgrade configuration (claiming "
1159  "schema %s) to at least %s because it "
1160  "does not validate with any schema from "
1161  "%s to %s",
1162  orig_value,
1163  get_schema_name(min_version),
1164  get_schema_name(orig_version),
1165  xml_latest_schema());
1166  } else {
1167  fprintf(stderr, "Cannot upgrade configuration (claiming "
1168  "schema %s) to at least %s because it "
1169  "does not validate with any schema from "
1170  "%s to %s\n",
1171  orig_value,
1172  get_schema_name(min_version),
1173  get_schema_name(orig_version),
1174  xml_latest_schema());
1175  }
1176  } else {
1177  // We updated configuration successfully, but still too low
1178  if (to_logs) {
1179  pcmk__config_err("Cannot upgrade configuration (claiming "
1180  "schema %s) to at least %s because it "
1181  "would not upgrade past %s",
1182  orig_value,
1183  get_schema_name(min_version),
1184  pcmk__s(value, "unspecified version"));
1185  } else {
1186  fprintf(stderr, "Cannot upgrade configuration (claiming "
1187  "schema %s) to at least %s because it "
1188  "would not upgrade past %s\n",
1189  orig_value,
1190  get_schema_name(min_version),
1191  pcmk__s(value, "unspecified version"));
1192  }
1193  }
1194 
1195  free_xml(converted);
1196  converted = NULL;
1197  rc = FALSE;
1198 
1199  } else {
1200  // Updated configuration schema is acceptable
1201  free_xml(*xml);
1202  *xml = converted;
1203 
1204  if (version < xml_latest_schema_index()) {
1205  if (to_logs) {
1206  pcmk__config_warn("Configuration with schema %s was "
1207  "internally upgraded to acceptable (but "
1208  "not most recent) %s",
1209  orig_value, get_schema_name(version));
1210  }
1211  } else {
1212  if (to_logs) {
1213  crm_info("Configuration with schema %s was internally "
1214  "upgraded to latest version %s",
1215  orig_value, get_schema_name(version));
1216  }
1217  }
1218  }
1219 
1220  } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
1221  // Schema validation is disabled
1222  if (to_logs) {
1223  pcmk__config_warn("Schema validation of configuration is disabled "
1224  "(enabling is encouraged and prevents common "
1225  "misconfigurations)");
1226 
1227  } else {
1228  fprintf(stderr, "Schema validation of configuration is disabled "
1229  "(enabling is encouraged and prevents common "
1230  "misconfigurations)\n");
1231  }
1232  }
1233 
1234  if (best_version) {
1235  *best_version = version;
1236  }
1237 
1238  free(orig_value);
1239  return rc;
1240 }
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:2586
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:238
#define pcmk_err_schema_validation
Definition: results.h:73
void crm_schema_init(void)
Definition: schemas.c:377
#define crm_notice(fmt, args...)
Definition: logging.h:383
#define schema_strdup_printf(prefix, version, suffix)
Definition: schemas.c:38
const char * name
Definition: cib.c:26
#define pcmk__config_warn(fmt...)
#define schema_scanf(s, prefix, version, suffix)
Definition: schemas.c:35
#define pcmk__config_err(fmt...)
#define pcmk_err_transform_failed
Definition: results.h:74
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:302
#define PCMK__VALUE_NONE
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:789
#define crm_warn(fmt, args...)
Definition: logging.h:382
int get_schema_version(const char *name)
Definition: schemas.c:967
#define crm_debug(fmt, args...)
Definition: logging.h:386
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:644
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:447
#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:69
gboolean validate_xml_verbose(const xmlNode *xml_blob)
Definition: schemas.c:645
void crm_schema_cleanup(void)
Definition: schemas.c:523
#define crm_trace(fmt, args...)
Definition: logging.h:387
#define SCHEMA_ZERO
Definition: schemas.c:33
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
const char * get_schema_name(int version)
Definition: schemas.c:958
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:142
void free_xml(xmlNode *child)
Definition: xml.c:783
const xmlChar * pcmkXmlStr
Definition: xml.h:50
const char * pcmk__get_tmpdir(void)
Definition: io.c:547
int write_xml_fd(const xmlNode *xml, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition: xml.c:1255
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
Definition: schemas.c:673
unsigned int crm_log_level
Definition: logging.c:45
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:323
#define crm_err(fmt, args...)
Definition: logging.h:381
#define CRM_ASSERT(expr)
Definition: results.h:42
#define crm_log_xml_info(xml, text)
Definition: logging.h:393
#define pcmk_ok
Definition: results.h:68
gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context)
Definition: schemas.c:679
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:1134
int update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, gboolean to_logs)
Update CIB XML to most recent schema version.
Definition: schemas.c:984
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2614
#define crm_info(fmt, args...)
Definition: logging.h:384
uint32_t version
Definition: remote.c:213
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:560
const char * xml_latest_schema(void)
Definition: schemas.c:113
schema_validator_e
Definition: schemas.c:47