pacemaker  2.1.3-ea053b43a
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  return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none"
88 }
89 
90 static int
91 xml_minimum_schema_index(void)
92 {
93  static int best = 0;
94  if (best == 0) {
95  int lpc = 0;
96 
97  best = xml_latest_schema_index();
98  for (lpc = best; lpc > 0; lpc--) {
99  if (known_schemas[lpc].version.v[0]
100  < known_schemas[best].version.v[0]) {
101  return best;
102  } else {
103  best = lpc;
104  }
105  }
106  best = xml_latest_schema_index();
107  }
108  return best;
109 }
110 
111 const char *
113 {
114  return get_schema_name(xml_latest_schema_index());
115 }
116 
117 static inline bool
118 version_from_filename(const char *filename, schema_version_t *version)
119 {
120  int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
121 
122  return (rc == 2);
123 }
124 
125 static int
126 schema_filter(const struct dirent *a)
127 {
128  int rc = 0;
129  schema_version_t version = SCHEMA_ZERO;
130 
131  if (strstr(a->d_name, "pacemaker-") != a->d_name) {
132  /* crm_trace("%s - wrong prefix", a->d_name); */
133 
134  } else if (!pcmk__ends_with_ext(a->d_name, ".rng")) {
135  /* crm_trace("%s - wrong suffix", a->d_name); */
136 
137  } else if (!version_from_filename(a->d_name, &version)) {
138  /* crm_trace("%s - wrong format", a->d_name); */
139 
140  } else {
141  /* crm_debug("%s - candidate", a->d_name); */
142  rc = 1;
143  }
144 
145  return rc;
146 }
147 
148 static int
149 schema_sort(const struct dirent **a, const struct dirent **b)
150 {
151  schema_version_t a_version = SCHEMA_ZERO;
152  schema_version_t b_version = SCHEMA_ZERO;
153 
154  if (!version_from_filename(a[0]->d_name, &a_version)
155  || !version_from_filename(b[0]->d_name, &b_version)) {
156  // Shouldn't be possible, but makes static analysis happy
157  return 0;
158  }
159 
160  for (int i = 0; i < 2; ++i) {
161  if (a_version.v[i] < b_version.v[i]) {
162  return -1;
163  } else if (a_version.v[i] > b_version.v[i]) {
164  return 1;
165  }
166  }
167  return 0;
168 }
169 
177 static void
178 add_schema(enum schema_validator_e validator, const schema_version_t *version,
179  const char *name, const char *transform,
180  const char *transform_enter, bool transform_onleave,
181  int after_transform)
182 {
183  int last = xml_schema_max;
184  bool have_version = FALSE;
185 
186  xml_schema_max++;
187  known_schemas = pcmk__realloc(known_schemas,
188  xml_schema_max * sizeof(struct schema_s));
189  CRM_ASSERT(known_schemas != NULL);
190  memset(known_schemas+last, 0, sizeof(struct schema_s));
191  known_schemas[last].validator = validator;
192  known_schemas[last].after_transform = after_transform;
193 
194  for (int i = 0; i < 2; ++i) {
195  known_schemas[last].version.v[i] = version->v[i];
196  if (version->v[i]) {
197  have_version = TRUE;
198  }
199  }
200  if (have_version) {
201  known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
202  } else {
203  CRM_ASSERT(name);
204  schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
205  known_schemas[last].name = strdup(name);
206  }
207 
208  if (transform) {
209  known_schemas[last].transform = strdup(transform);
210  }
211  if (transform_enter) {
212  known_schemas[last].transform_enter = strdup(transform_enter);
213  }
214  known_schemas[last].transform_onleave = transform_onleave;
215  if (after_transform == 0) {
216  after_transform = xml_schema_max; /* upgrade is a one-way */
217  }
218  known_schemas[last].after_transform = after_transform;
219 
220  if (known_schemas[last].after_transform < 0) {
221  crm_debug("Added supported schema %d: %s",
222  last, known_schemas[last].name);
223 
224  } else if (known_schemas[last].transform) {
225  crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)",
226  last, known_schemas[last].name,
227  known_schemas[last].after_transform,
228  known_schemas[last].transform);
229 
230  } else {
231  crm_debug("Added supported schema %d: %s (upgrades to %d)",
232  last, known_schemas[last].name,
233  known_schemas[last].after_transform);
234  }
235 }
236 
265 static int
266 add_schema_by_version(const schema_version_t *version, int next,
267  bool transform_expected)
268 {
269  bool transform_onleave = FALSE;
270  int rc = pcmk_rc_ok;
271  struct stat s;
272  char *xslt = NULL,
273  *transform_upgrade = NULL,
274  *transform_enter = NULL;
275 
276  /* prologue for further transform_expected handling */
277  if (transform_expected) {
278  /* check if there's suitable "upgrade" stylesheet */
279  transform_upgrade = schema_strdup_printf("upgrade-", *version, );
281  transform_upgrade);
282  }
283 
284  if (!transform_expected) {
285  /* jump directly to the end */
286 
287  } else if (stat(xslt, &s) == 0) {
288  /* perhaps there's also a targeted "upgrade-enter" stylesheet */
289  transform_enter = schema_strdup_printf("upgrade-", *version, "-enter");
290  free(xslt);
292  transform_enter);
293  if (stat(xslt, &s) != 0) {
294  /* or initially, at least a generic one */
295  crm_debug("Upgrade-enter transform %s.xsl not found", xslt);
296  free(xslt);
297  free(transform_enter);
298  transform_enter = strdup("upgrade-enter");
300  transform_enter);
301  if (stat(xslt, &s) != 0) {
302  crm_debug("Upgrade-enter transform %s.xsl not found, either", xslt);
303  free(xslt);
304  xslt = NULL;
305  }
306  }
307  /* xslt contains full path to "upgrade-enter" stylesheet */
308  if (xslt != NULL) {
309  /* then there should be "upgrade-leave" counterpart (enter->leave) */
310  memcpy(strrchr(xslt, '-') + 1, "leave", sizeof("leave") - 1);
311  transform_onleave = (stat(xslt, &s) == 0);
312  free(xslt);
313  } else {
314  free(transform_enter);
315  transform_enter = NULL;
316  }
317 
318  } else {
319  crm_err("Upgrade transform %s not found", xslt);
320  free(xslt);
321  free(transform_upgrade);
322  transform_upgrade = NULL;
323  next = -1;
324  rc = ENOENT;
325  }
326 
327  add_schema(schema_validator_rng, version, NULL,
328  transform_upgrade, transform_enter, transform_onleave, next);
329 
330  free(transform_upgrade);
331  free(transform_enter);
332 
333  return rc;
334 }
335 
336 static void
337 wrap_libxslt(bool finalize)
338 {
339  static xsltSecurityPrefsPtr secprefs;
340  int ret = 0;
341 
342  /* security framework preferences */
343  if (!finalize) {
344  CRM_ASSERT(secprefs == NULL);
345  secprefs = xsltNewSecurityPrefs();
346  ret = xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_FILE,
347  xsltSecurityForbid)
348  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_CREATE_DIRECTORY,
349  xsltSecurityForbid)
350  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_READ_NETWORK,
351  xsltSecurityForbid)
352  | xsltSetSecurityPrefs(secprefs, XSLT_SECPREF_WRITE_NETWORK,
353  xsltSecurityForbid);
354  if (ret != 0) {
355  return;
356  }
357  } else {
358  xsltFreeSecurityPrefs(secprefs);
359  secprefs = NULL;
360  }
361 
362  /* cleanup only */
363  if (finalize) {
364  xsltCleanupGlobals();
365  }
366 }
367 
375 void
377 {
378  int lpc, max;
380  struct dirent **namelist = NULL;
381  const schema_version_t zero = SCHEMA_ZERO;
382 
383  wrap_libxslt(false);
384 
385  max = scandir(base, &namelist, schema_filter, schema_sort);
386  if (max < 0) {
387  crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
388  free(base);
389 
390  } else {
391  free(base);
392  for (lpc = 0; lpc < max; lpc++) {
393  bool transform_expected = FALSE;
394  int next = 0;
395  schema_version_t version = SCHEMA_ZERO;
396 
397  if (!version_from_filename(namelist[lpc]->d_name, &version)) {
398  // Shouldn't be possible, but makes static analysis happy
399  crm_err("Skipping schema '%s': could not parse version",
400  namelist[lpc]->d_name);
401  continue;
402  }
403  if ((lpc + 1) < max) {
404  schema_version_t next_version = SCHEMA_ZERO;
405 
406  if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
407  && (version.v[0] < next_version.v[0])) {
408  transform_expected = TRUE;
409  }
410 
411  } else {
412  next = -1;
413  }
414  if (add_schema_by_version(&version, next, transform_expected)
415  == ENOENT) {
416  break;
417  }
418  }
419 
420  for (lpc = 0; lpc < max; lpc++) {
421  free(namelist[lpc]);
422  }
423  free(namelist);
424  }
425 
426  add_schema(schema_validator_rng, &zero, "pacemaker-next",
427  NULL, NULL, FALSE, -1);
428 
429  add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE,
430  NULL, NULL, FALSE, -1);
431 }
432 
433 #if 0
434 static void
435 relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
436 {
437  /*
438  Structure xmlError
439  struct _xmlError {
440  int domain : What part of the library raised this er
441  int code : The error code, e.g. an xmlParserError
442  char * message : human-readable informative error messag
443  xmlErrorLevel level : how consequent is the error
444  char * file : the filename
445  int line : the line number if available
446  char * str1 : extra string information
447  char * str2 : extra string information
448  char * str3 : extra string information
449  int int1 : extra number information
450  int int2 : column number of the error or 0 if N/A
451  void * ctxt : the parser context if available
452  void * node : the node in the tree
453  }
454  */
455  crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
456 }
457 #endif
458 
459 static gboolean
460 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
461  relaxng_ctx_cache_t **cached_ctx)
462 {
463  int rc = 0;
464  gboolean valid = TRUE;
465  relaxng_ctx_cache_t *ctx = NULL;
466 
467  CRM_CHECK(doc != NULL, return FALSE);
468  CRM_CHECK(relaxng_file != NULL, return FALSE);
469 
470  if (cached_ctx && *cached_ctx) {
471  ctx = *cached_ctx;
472 
473  } else {
474  crm_debug("Creating RNG parser context");
475  ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
476 
477  xmlLoadExtDtdDefaultValue = 1;
478  ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
479  CRM_CHECK(ctx->parser != NULL, goto cleanup);
480 
481  if (to_logs) {
482  xmlRelaxNGSetParserErrors(ctx->parser,
483  (xmlRelaxNGValidityErrorFunc) xml_log,
484  (xmlRelaxNGValidityWarningFunc) xml_log,
485  GUINT_TO_POINTER(LOG_ERR));
486  } else {
487  xmlRelaxNGSetParserErrors(ctx->parser,
488  (xmlRelaxNGValidityErrorFunc) fprintf,
489  (xmlRelaxNGValidityWarningFunc) fprintf,
490  stderr);
491  }
492 
493  ctx->rng = xmlRelaxNGParse(ctx->parser);
494  CRM_CHECK(ctx->rng != NULL,
495  crm_err("Could not find/parse %s", relaxng_file);
496  goto cleanup);
497 
498  ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
499  CRM_CHECK(ctx->valid != NULL, goto cleanup);
500 
501  if (to_logs) {
502  xmlRelaxNGSetValidErrors(ctx->valid,
503  (xmlRelaxNGValidityErrorFunc) xml_log,
504  (xmlRelaxNGValidityWarningFunc) xml_log,
505  GUINT_TO_POINTER(LOG_ERR));
506  } else {
507  xmlRelaxNGSetValidErrors(ctx->valid,
508  (xmlRelaxNGValidityErrorFunc) fprintf,
509  (xmlRelaxNGValidityWarningFunc) fprintf,
510  stderr);
511  }
512  }
513 
514  /* xmlRelaxNGSetValidStructuredErrors( */
515  /* valid, relaxng_invalid_stderr, valid); */
516 
517  xmlLineNumbersDefault(1);
518  rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
519  if (rc > 0) {
520  valid = FALSE;
521 
522  } else if (rc < 0) {
523  crm_err("Internal libxml error during validation");
524  }
525 
526  cleanup:
527 
528  if (cached_ctx) {
529  *cached_ctx = ctx;
530 
531  } else {
532  if (ctx->parser != NULL) {
533  xmlRelaxNGFreeParserCtxt(ctx->parser);
534  }
535  if (ctx->valid != NULL) {
536  xmlRelaxNGFreeValidCtxt(ctx->valid);
537  }
538  if (ctx->rng != NULL) {
539  xmlRelaxNGFree(ctx->rng);
540  }
541  free(ctx);
542  }
543 
544  return valid;
545 }
546 
551 void
553 {
554  int lpc;
555  relaxng_ctx_cache_t *ctx = NULL;
556 
557  for (lpc = 0; lpc < xml_schema_max; lpc++) {
558 
559  switch (known_schemas[lpc].validator) {
560  case schema_validator_none: // not cached
561  break;
562  case schema_validator_rng: // cached
563  ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
564  if (ctx == NULL) {
565  break;
566  }
567  if (ctx->parser != NULL) {
568  xmlRelaxNGFreeParserCtxt(ctx->parser);
569  }
570  if (ctx->valid != NULL) {
571  xmlRelaxNGFreeValidCtxt(ctx->valid);
572  }
573  if (ctx->rng != NULL) {
574  xmlRelaxNGFree(ctx->rng);
575  }
576  free(ctx);
577  known_schemas[lpc].cache = NULL;
578  break;
579  }
580  free(known_schemas[lpc].name);
581  free(known_schemas[lpc].transform);
582  free(known_schemas[lpc].transform_enter);
583  }
584  free(known_schemas);
585  known_schemas = NULL;
586 
587  wrap_libxslt(true);
588 }
589 
590 static gboolean
591 validate_with(xmlNode *xml, int method, gboolean to_logs)
592 {
593  xmlDocPtr doc = NULL;
594  gboolean valid = FALSE;
595  char *file = NULL;
596 
597  if (method < 0) {
598  return FALSE;
599  }
600 
601  if (known_schemas[method].validator == schema_validator_none) {
602  return TRUE;
603  }
604 
605  CRM_CHECK(xml != NULL, return FALSE);
606  doc = getDocPtr(xml);
608  known_schemas[method].name);
609 
610  crm_trace("Validating with: %s (type=%d)",
611  crm_str(file), known_schemas[method].validator);
612  switch (known_schemas[method].validator) {
614  valid =
615  validate_with_relaxng(doc, to_logs, file,
616  (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
617  break;
618  default:
619  crm_err("Unknown validator type: %d",
620  known_schemas[method].validator);
621  break;
622  }
623 
624  free(file);
625  return valid;
626 }
627 
628 static bool
629 validate_with_silent(xmlNode *xml, int method)
630 {
631  bool rc, sl_backup = silent_logging;
632  silent_logging = TRUE;
633  rc = validate_with(xml, method, TRUE);
634  silent_logging = sl_backup;
635  return rc;
636 }
637 
638 static void
639 dump_file(const char *filename)
640 {
641 
642  FILE *fp = NULL;
643  int ch, line = 0;
644 
645  CRM_CHECK(filename != NULL, return);
646 
647  fp = fopen(filename, "r");
648  if (fp == NULL) {
649  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
650  return;
651  }
652 
653  fprintf(stderr, "%4d ", ++line);
654  do {
655  ch = getc(fp);
656  if (ch == EOF) {
657  putc('\n', stderr);
658  break;
659  } else if (ch == '\n') {
660  fprintf(stderr, "\n%4d ", ++line);
661  } else {
662  putc(ch, stderr);
663  }
664  } while (1);
665 
666  fclose(fp);
667 }
668 
669 gboolean
670 validate_xml_verbose(xmlNode *xml_blob)
671 {
672  int fd = 0;
673  xmlDoc *doc = NULL;
674  xmlNode *xml = NULL;
675  gboolean rc = FALSE;
676  char *filename = NULL;
677 
678  filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", pcmk__get_tmpdir());
679 
680  umask(S_IWGRP | S_IWOTH | S_IROTH);
681  fd = mkstemp(filename);
682  write_xml_fd(xml_blob, filename, fd, FALSE);
683 
684  dump_file(filename);
685 
686  doc = xmlParseFile(filename);
687  xml = xmlDocGetRootElement(doc);
688  rc = validate_xml(xml, NULL, FALSE);
689  free_xml(xml);
690 
691  unlink(filename);
692  free(filename);
693 
694  return rc;
695 }
696 
697 gboolean
698 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
699 {
700  int version = 0;
701 
702  if (validation == NULL) {
703  validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
704  }
705 
706  if (validation == NULL) {
707  int lpc = 0;
708  bool valid = FALSE;
709 
710  for (lpc = 0; lpc < xml_schema_max; lpc++) {
711  if (validate_with(xml_blob, lpc, FALSE)) {
712  valid = TRUE;
714  known_schemas[lpc].name);
715  crm_info("XML validated against %s", known_schemas[lpc].name);
716  if(known_schemas[lpc].after_transform == 0) {
717  break;
718  }
719  }
720  }
721 
722  return valid;
723  }
724 
725  version = get_schema_version(validation);
726  if (strcmp(validation, PCMK__VALUE_NONE) == 0) {
727  return TRUE;
728  } else if (version < xml_schema_max) {
729  return validate_with(xml_blob, version, to_logs);
730  }
731 
732  crm_err("Unknown validator: %s", validation);
733  return FALSE;
734 }
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  PCMK__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);
914  transform);
915 
916  xmlLoadExtDtdDefaultValue = 1;
917  xmlSubstituteEntitiesDefault(1);
918 
919  /* for capturing, e.g., what's emitted via <xsl:message> */
920  if (to_logs) {
921  xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
922  } else {
923  xsltSetGenericErrorFunc(&crm_log_level, cib_upgrade_err);
924  }
925 
926  xslt = xsltParseStylesheetFile((pcmkXmlStr) xform);
927  CRM_CHECK(xslt != NULL, goto cleanup);
928 
929  res = xsltApplyStylesheet(xslt, doc, NULL);
930  CRM_CHECK(res != NULL, goto cleanup);
931 
932  xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
933 
934 
935 #if PCMK_SCHEMAS_EMERGENCY_XSLT != 0
936  emergency_res = xsltSaveResultToString(&emergency_result,
937  &emergency_txt_len, res, xslt);
938  xmlFreeDoc(res);
939  CRM_CHECK(emergency_res == 0, goto cleanup);
940  out = string2xml((const char *) emergency_result);
941  free(emergency_result);
942 #else
943  out = xmlDocGetRootElement(res);
944 #endif
945 
946  cleanup:
947  if (xslt) {
948  xsltFreeStylesheet(xslt);
949  }
950 
951  free(xform);
952 
953  return out;
954 }
955 
962 static xmlNode *
963 apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs)
964 {
965  bool transform_onleave = schema->transform_onleave;
966  char *transform_leave;
967  xmlNode *upgrade = NULL,
968  *final = NULL;
969 
970  if (schema->transform_enter) {
971  crm_debug("Upgrading %s-style configuration, pre-upgrade phase with %s.xsl",
972  schema->name, schema->transform_enter);
973  upgrade = apply_transformation(xml, schema->transform_enter, to_logs);
974  if (upgrade == NULL) {
975  crm_warn("Upgrade-enter transformation %s.xsl failed",
976  schema->transform_enter);
977  transform_onleave = FALSE;
978  }
979  }
980  if (upgrade == NULL) {
981  upgrade = xml;
982  }
983 
984  crm_debug("Upgrading %s-style configuration, main phase with %s.xsl",
985  schema->name, schema->transform);
986  final = apply_transformation(upgrade, schema->transform, to_logs);
987  if (upgrade != xml) {
988  free_xml(upgrade);
989  upgrade = NULL;
990  }
991 
992  if (final != NULL && transform_onleave) {
993  upgrade = final;
994  /* following condition ensured in add_schema_by_version */
995  CRM_ASSERT(schema->transform_enter != NULL);
996  transform_leave = strdup(schema->transform_enter);
997  /* enter -> leave */
998  memcpy(strrchr(transform_leave, '-') + 1, "leave", sizeof("leave") - 1);
999  crm_debug("Upgrading %s-style configuration, post-upgrade phase with %s.xsl",
1000  schema->name, transform_leave);
1001  final = apply_transformation(upgrade, transform_leave, to_logs);
1002  if (final == NULL) {
1003  crm_warn("Upgrade-leave transformation %s.xsl failed", transform_leave);
1004  final = upgrade;
1005  } else {
1006  free_xml(upgrade);
1007  }
1008  free(transform_leave);
1009  }
1010 
1011  return final;
1012 }
1013 
1014 const char *
1016 {
1017  if (version < 0 || version >= xml_schema_max) {
1018  return "unknown";
1019  }
1020  return known_schemas[version].name;
1021 }
1022 
1023 int
1025 {
1026  int lpc = 0;
1027 
1028  if (name == NULL) {
1030  }
1031  for (; lpc < xml_schema_max; lpc++) {
1032  if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) {
1033  return lpc;
1034  }
1035  }
1036  return -1;
1037 }
1038 
1039 /* set which validation to use */
1040 int
1041 update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
1042  gboolean to_logs)
1043 {
1044  xmlNode *xml = NULL;
1045  char *value = NULL;
1046  int max_stable_schemas = xml_latest_schema_index();
1047  int lpc = 0, match = -1, rc = pcmk_ok;
1048  int next = -1; /* -1 denotes "inactive" value */
1049 
1050  CRM_CHECK(best != NULL, return -EINVAL);
1051  *best = 0;
1052 
1053  CRM_CHECK(xml_blob != NULL, return -EINVAL);
1054  CRM_CHECK(*xml_blob != NULL, return -EINVAL);
1055 
1056  xml = *xml_blob;
1058 
1059  if (value != NULL) {
1060  match = get_schema_version(value);
1061 
1062  lpc = match;
1063  if (lpc >= 0 && transform == FALSE) {
1064  *best = lpc++;
1065 
1066  } else if (lpc < 0) {
1067  crm_debug("Unknown validation schema");
1068  lpc = 0;
1069  }
1070  }
1071 
1072  if (match >= max_stable_schemas) {
1073  /* nothing to do */
1074  free(value);
1075  *best = match;
1076  return pcmk_ok;
1077  }
1078 
1079  while (lpc <= max_stable_schemas) {
1080  crm_debug("Testing '%s' validation (%d of %d)",
1081  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
1082  lpc, max_stable_schemas);
1083 
1084  if (validate_with(xml, lpc, to_logs) == FALSE) {
1085  if (next != -1) {
1086  crm_info("Configuration not valid for schema: %s",
1087  known_schemas[lpc].name);
1088  next = -1;
1089  } else {
1090  crm_trace("%s validation failed",
1091  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
1092  }
1093  if (*best) {
1094  /* we've satisfied the validation, no need to check further */
1095  break;
1096  }
1098 
1099  } else {
1100  if (next != -1) {
1101  crm_debug("Configuration valid for schema: %s",
1102  known_schemas[next].name);
1103  next = -1;
1104  }
1105  rc = pcmk_ok;
1106  }
1107 
1108  if (rc == pcmk_ok) {
1109  *best = lpc;
1110  }
1111 
1112  if (rc == pcmk_ok && transform) {
1113  xmlNode *upgrade = NULL;
1114  next = known_schemas[lpc].after_transform;
1115 
1116  if (next <= lpc) {
1117  /* There is no next version, or next would regress */
1118  crm_trace("Stopping at %s", known_schemas[lpc].name);
1119  break;
1120 
1121  } else if (max > 0 && (lpc == max || next > max)) {
1122  crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
1123  known_schemas[lpc].name, lpc, next, max);
1124  break;
1125 
1126  } else if (known_schemas[lpc].transform == NULL
1127  /* possibly avoid transforming when readily valid
1128  (in general more restricted when crossing the major
1129  version boundary, as X.0 "transitional" version is
1130  expected to be more strict than it's successors that
1131  may re-allow constructs from previous major line) */
1132  || validate_with_silent(xml, next)) {
1133  crm_debug("%s-style configuration is also valid for %s",
1134  known_schemas[lpc].name, known_schemas[next].name);
1135 
1136  lpc = next;
1137 
1138  } else {
1139  crm_debug("Upgrading %s-style configuration to %s with %s.xsl",
1140  known_schemas[lpc].name, known_schemas[next].name,
1141  known_schemas[lpc].transform);
1142 
1143  upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs);
1144  if (upgrade == NULL) {
1145  crm_err("Transformation %s.xsl failed",
1146  known_schemas[lpc].transform);
1148 
1149  } else if (validate_with(upgrade, next, to_logs)) {
1150  crm_info("Transformation %s.xsl successful",
1151  known_schemas[lpc].transform);
1152  lpc = next;
1153  *best = next;
1154  free_xml(xml);
1155  xml = upgrade;
1156  rc = pcmk_ok;
1157 
1158  } else {
1159  crm_err("Transformation %s.xsl did not produce a valid configuration",
1160  known_schemas[lpc].transform);
1161  crm_log_xml_info(upgrade, "transform:bad");
1162  free_xml(upgrade);
1164  }
1165  next = -1;
1166  }
1167  }
1168 
1169  if (transform == FALSE || rc != pcmk_ok) {
1170  /* we need some progress! */
1171  lpc++;
1172  }
1173  }
1174 
1175  if (*best > match && *best) {
1176  crm_info("%s the configuration from %s to %s",
1177  transform?"Transformed":"Upgraded",
1178  value ? value : "<none>", known_schemas[*best].name);
1179  crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
1180  }
1181 
1182  *xml_blob = xml;
1183  free(value);
1184  return rc;
1185 }
1186 
1187 gboolean
1188 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
1189 {
1190  gboolean rc = TRUE;
1191  const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
1192  char *const orig_value = strdup(value == NULL ? "(none)" : value);
1193 
1194  int version = get_schema_version(value);
1195  int orig_version = version;
1196  int min_version = xml_minimum_schema_index();
1197 
1198  if (version < min_version) {
1199  // Current configuration schema is not acceptable, try to update
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  // Updated configuration schema is still not acceptable
1208 
1209  if (version < orig_version || orig_version == -1) {
1210  // We couldn't validate any schema at all
1211  if (to_logs) {
1212  pcmk__config_err("Cannot upgrade configuration (claiming "
1213  "schema %s) to at least %s because it "
1214  "does not validate with any schema from "
1215  "%s to %s",
1216  orig_value,
1217  get_schema_name(min_version),
1218  get_schema_name(orig_version),
1219  xml_latest_schema());
1220  } else {
1221  fprintf(stderr, "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\n",
1225  orig_value,
1226  get_schema_name(min_version),
1227  get_schema_name(orig_version),
1228  xml_latest_schema());
1229  }
1230  } else {
1231  // We updated configuration successfully, but still too low
1232  if (to_logs) {
1233  pcmk__config_err("Cannot upgrade configuration (claiming "
1234  "schema %s) to at least %s because it "
1235  "would not upgrade past %s",
1236  orig_value,
1237  get_schema_name(min_version),
1238  crm_str(value));
1239  } else {
1240  fprintf(stderr, "Cannot upgrade configuration (claiming "
1241  "schema %s) to at least %s because it "
1242  "would not upgrade past %s\n",
1243  orig_value,
1244  get_schema_name(min_version),
1245  crm_str(value));
1246  }
1247  }
1248 
1249  free_xml(converted);
1250  converted = NULL;
1251  rc = FALSE;
1252 
1253  } else {
1254  // Updated configuration schema is acceptable
1255  free_xml(*xml);
1256  *xml = converted;
1257 
1258  if (version < xml_latest_schema_index()) {
1259  if (to_logs) {
1260  pcmk__config_warn("Configuration with schema %s was "
1261  "internally upgraded to acceptable (but "
1262  "not most recent) %s",
1263  orig_value, get_schema_name(version));
1264  }
1265  } else {
1266  if (to_logs) {
1267  crm_info("Configuration with schema %s was internally "
1268  "upgraded to latest version %s",
1269  orig_value, get_schema_name(version));
1270  }
1271  }
1272  }
1273 
1274  } else if (version >= get_schema_version(PCMK__VALUE_NONE)) {
1275  // Schema validation is disabled
1276  if (to_logs) {
1277  pcmk__config_warn("Schema validation of configuration is disabled "
1278  "(enabling is encouraged and prevents common "
1279  "misconfigurations)");
1280 
1281  } else {
1282  fprintf(stderr, "Schema validation of configuration is disabled "
1283  "(enabling is encouraged and prevents common "
1284  "misconfigurations)\n");
1285  }
1286  }
1287 
1288  if (best_version) {
1289  *best_version = version;
1290  }
1291 
1292  free(orig_value);
1293  return rc;
1294 }
char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
Definition: xml.c:2906
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
#define pcmk_err_schema_validation
Definition: results.h:73
void crm_schema_init(void)
Definition: schemas.c:376
#define crm_notice(fmt, args...)
Definition: logging.h:360
#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:323
#define PCMK__VALUE_NONE
char * strerror(int errnum)
xmlNode * string2xml(const char *input)
Definition: xml.c:869
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:658
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
#define crm_warn(fmt, args...)
Definition: logging.h:359
int get_schema_version(const char *name)
Definition: schemas.c:1024
#define crm_debug(fmt, args...)
Definition: logging.h:363
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:726
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:529
#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:67
void crm_schema_cleanup(void)
Definition: schemas.c:552
#define crm_trace(fmt, args...)
Definition: logging.h:364
#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:1015
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:120
void free_xml(xmlNode *child)
Definition: xml.c:824
gboolean validate_xml_verbose(xmlNode *xml_blob)
Definition: schemas.c:670
const xmlChar * pcmkXmlStr
Definition: xml.h:52
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
Definition: schemas.c:698
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:309
#define crm_err(fmt, args...)
Definition: logging.h:358
#define CRM_ASSERT(expr)
Definition: results.h:42
const char * pcmk__get_tmpdir(void)
Definition: io.c:540
#define crm_log_xml_info(xml, text)
Definition: logging.h:370
#define crm_str(x)
Definition: logging.h:384
#define pcmk_ok
Definition: results.h:68
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:1188
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:1041
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Write XML to a file descriptor.
Definition: xml.c:1272
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2934
#define crm_info(fmt, args...)
Definition: logging.h:361
uint32_t version
Definition: remote.c:147
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:563
const char * xml_latest_schema(void)
Definition: schemas.c:112
schema_validator_e
Definition: schemas.c:47