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