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