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