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