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