pacemaker  1.1.24-3850484742
Scalable High-Availability cluster resource manager
schemas.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2004-2016 Andrew Beekhof <andrew@beekhof.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include <crm_internal.h>
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <sys/stat.h>
26 #include <stdarg.h>
27 
28 #if HAVE_LIBXML2
29 # include <libxml/relaxng.h>
30 #endif
31 
32 #if HAVE_LIBXSLT
33 # include <libxslt/xslt.h>
34 # include <libxslt/transform.h>
35 # include <libxslt/xsltutils.h>
36 #endif
37 
38 #include <crm/msg_xml.h>
39 #include <crm/common/xml.h>
40 #include <crm/common/xml_internal.h> /* CRM_XML_LOG_BASE */
41 
42 typedef struct {
43  unsigned char v[2];
44 } schema_version_t;
45 
46 #define SCHEMA_ZERO { .v = { 0, 0 } }
47 
48 #define schema_scanf(s, prefix, version, suffix) \
49  sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1]))
50 
51 #define schema_strdup_printf(prefix, version, suffix) \
52  crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1])
53 
54 typedef struct {
55  xmlRelaxNGPtr rng;
56  xmlRelaxNGValidCtxtPtr valid;
57  xmlRelaxNGParserCtxtPtr parser;
58 } relaxng_ctx_cache_t;
59 
64 };
65 
66 struct schema_s {
67  char *name;
68  char *location;
69  char *transform;
70  void *cache;
71  enum schema_validator_e validator;
72  int after_transform;
73  schema_version_t version;
74 };
75 
76 static struct schema_s *known_schemas = NULL;
77 static int xml_schema_max = 0;
78 
79 static void
80 xml_log(int priority, const char *fmt, ...)
81 G_GNUC_PRINTF(2, 3);
82 
83 static void
84 xml_log(int priority, const char *fmt, ...)
85 {
86  va_list ap;
87 
88  va_start(ap, fmt);
89  /* XXX should not this enable dechunking as well? */
90  CRM_XML_LOG_BASE(priority, FALSE, 0, NULL, fmt, ap);
91  va_end(ap);
92 }
93 
94 static int
95 xml_latest_schema_index(void)
96 {
97  return xml_schema_max - 4;
98 }
99 
100 static int
101 xml_minimum_schema_index(void)
102 {
103  static int best = 0;
104  if (best == 0) {
105  int lpc = 0;
106 
107  best = xml_latest_schema_index();
108  for (lpc = best; lpc > 0; lpc--) {
109  if (known_schemas[lpc].version.v[0]
110  < known_schemas[best].version.v[0]) {
111  return best;
112  } else {
113  best = lpc;
114  }
115  }
116  best = xml_latest_schema_index();
117  }
118  return best;
119 }
120 
121 const char *
123 {
124  return get_schema_name(xml_latest_schema_index());
125 }
126 
127 static const char *
128 get_schema_root(void)
129 {
130  static const char *base = NULL;
131 
132  if (base == NULL) {
133  base = getenv("PCMK_schema_directory");
134  }
135  if (base == NULL || strlen(base) == 0) {
136  base = CRM_DTD_DIRECTORY;
137  }
138  return base;
139 }
140 
141 static char *
142 get_schema_path(const char *name, const char *file)
143 {
144  const char *base = get_schema_root();
145 
146  if (file) {
147  return crm_strdup_printf("%s/%s", base, file);
148  }
149  return crm_strdup_printf("%s/%s.rng", base, name);
150 }
151 
152 static inline bool
153 version_from_filename(const char *filename, schema_version_t *version)
154 {
155  int rc = schema_scanf(filename, "pacemaker-", *version, ".rng");
156 
157  return (rc == 2);
158 }
159 
160 static int
161 schema_filter(const struct dirent *a)
162 {
163  int rc = 0;
164  schema_version_t version = SCHEMA_ZERO;
165 
166  if (strstr(a->d_name, "pacemaker-") != a->d_name) {
167  /* crm_trace("%s - wrong prefix", a->d_name); */
168 
169  } else if (!crm_ends_with_ext(a->d_name, ".rng")) {
170  /* crm_trace("%s - wrong suffix", a->d_name); */
171 
172  } else if (!version_from_filename(a->d_name, &version)) {
173  /* crm_trace("%s - wrong format", a->d_name); */
174 
175  } else if (strcmp(a->d_name, "pacemaker-1.1.rng") == 0) {
176  /* "-1.1" was used for what later became "-next" */
177  /* crm_trace("%s - hack", a->d_name); */
178 
179  } else {
180  /* crm_debug("%s - candidate", a->d_name); */
181  rc = 1;
182  }
183 
184  return rc;
185 }
186 
187 static int
188 schema_sort(const struct dirent **a, const struct dirent **b)
189 {
190  schema_version_t a_version = SCHEMA_ZERO;
191  schema_version_t b_version = SCHEMA_ZERO;
192 
193  if (!version_from_filename(a[0]->d_name, &a_version)
194  || !version_from_filename(b[0]->d_name, &b_version)) {
195  // Shouldn't be possible, but makes static analysis happy
196  return 0;
197  }
198 
199  for (int i = 0; i < 2; ++i) {
200  if (a_version.v[i] < b_version.v[i]) {
201  return -1;
202  } else if (a_version.v[i] > b_version.v[i]) {
203  return 1;
204  }
205  }
206  return 0;
207 }
208 
209 static void
210 add_schema(enum schema_validator_e validator, const schema_version_t *version,
211  const char *name, const char *location, const char *transform,
212  int after_transform)
213 {
214  int last = xml_schema_max;
215  bool have_version = FALSE;
216 
217  xml_schema_max++;
218  known_schemas = realloc_safe(known_schemas,
219  xml_schema_max * sizeof(struct schema_s));
220  CRM_ASSERT(known_schemas != NULL);
221  memset(known_schemas+last, 0, sizeof(struct schema_s));
222  known_schemas[last].validator = validator;
223  known_schemas[last].after_transform = after_transform;
224 
225  for (int i = 0; i < 2; ++i) {
226  known_schemas[last].version.v[i] = version->v[i];
227  if (version->v[i]) {
228  have_version = TRUE;
229  }
230  }
231  if (have_version) {
232  known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, "");
233  known_schemas[last].location = crm_strdup_printf("%s.rng",
234  known_schemas[last].name);
235  } else {
236  CRM_ASSERT(name);
237  CRM_ASSERT(location);
238  schema_scanf(name, "%*[^-]-", known_schemas[last].version, "");
239  known_schemas[last].name = strdup(name);
240  known_schemas[last].location = strdup(location);
241  }
242 
243  if (transform) {
244  known_schemas[last].transform = strdup(transform);
245  }
246  if (after_transform == 0) {
247  after_transform = xml_schema_max; /* upgrade is a one-way */
248  }
249  known_schemas[last].after_transform = after_transform;
250 
251  if (known_schemas[last].after_transform < 0) {
252  crm_debug("Added supported schema %d: %s (%s)",
253  last, known_schemas[last].name, known_schemas[last].location);
254 
255  } else if (known_schemas[last].transform) {
256  crm_debug("Added supported schema %d: %s (%s upgrades to %d with %s)",
257  last, known_schemas[last].name, known_schemas[last].location,
258  known_schemas[last].after_transform,
259  known_schemas[last].transform);
260 
261  } else {
262  crm_debug("Added supported schema %d: %s (%s upgrades to %d)",
263  last, known_schemas[last].name, known_schemas[last].location,
264  known_schemas[last].after_transform);
265  }
266 }
267 
272 void
274 {
275  int lpc, max;
276  const char *base = get_schema_root();
277  struct dirent **namelist = NULL;
278  const schema_version_t zero = SCHEMA_ZERO;
279 
280  max = scandir(base, &namelist, schema_filter, schema_sort);
281 
282  add_schema(schema_validator_dtd, &zero, "pacemaker-0.6",
283  "crm.dtd", "upgrade06.xsl", 3);
284 
285  add_schema(schema_validator_dtd, &zero, "transitional-0.6",
286  "crm-transitional.dtd", "upgrade06.xsl", 3);
287 
288  add_schema(schema_validator_rng, &zero, "pacemaker-0.7",
289  "pacemaker-1.0.rng", NULL, 0);
290 
291  if (max < 0) {
292  crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
293 
294  } else {
295  for (lpc = 0; lpc < max; lpc++) {
296  int next = 0;
297  schema_version_t version = SCHEMA_ZERO;
298  char *transform = NULL;
299 
300  if (!version_from_filename(namelist[lpc]->d_name, &version)) {
301  // Shouldn't be possible, but makes static analysis happy
302  crm_err("Skipping schema '%s': could not parse version",
303  namelist[lpc]->d_name);
304  free(namelist[lpc]);
305  continue;
306  }
307  if ((lpc + 1) < max) {
308  schema_version_t next_version = SCHEMA_ZERO;
309 
310  if (version_from_filename(namelist[lpc+1]->d_name, &next_version)
311  && (version.v[0] < next_version.v[0])) {
312 
313  struct stat s;
314  char *xslt = NULL;
315 
316  transform = schema_strdup_printf("upgrade-", version, ".xsl");
317  xslt = get_schema_path(NULL, transform);
318  if (stat(xslt, &s) != 0) {
319  crm_err("Transform %s not found", xslt);
320  free(xslt);
321  add_schema(schema_validator_rng, &version, NULL, NULL,
322  NULL, -1);
323  break;
324  } else {
325  free(xslt);
326  }
327  }
328 
329  } else {
330  next = -1;
331  }
332  add_schema(schema_validator_rng, &version, NULL, NULL, transform,
333  next);
334  free(namelist[lpc]);
335  free(transform);
336  }
337  }
338 
339  /* 1.1 was the old name for -next */
340  add_schema(schema_validator_rng, &zero, "pacemaker-1.1",
341  "pacemaker-next.rng", NULL, 0);
342 
343  add_schema(schema_validator_rng, &zero, "pacemaker-next",
344  "pacemaker-next.rng", NULL, -1);
345 
346  add_schema(schema_validator_none, &zero, "none",
347  "N/A", NULL, -1);
348  free(namelist);
349 }
350 
351 static gboolean
352 validate_with_dtd(xmlDocPtr doc, gboolean to_logs, const char *dtd_file)
353 {
354  gboolean valid = TRUE;
355 
356  xmlDtdPtr dtd = NULL;
357  xmlValidCtxtPtr cvp = NULL;
358 
359  CRM_CHECK(doc != NULL, return FALSE);
360  CRM_CHECK(dtd_file != NULL, return FALSE);
361 
362  dtd = xmlParseDTD(NULL, (const xmlChar *)dtd_file);
363  if (dtd == NULL) {
364  crm_err("Could not locate/parse DTD: %s", dtd_file);
365  return TRUE;
366  }
367 
368  cvp = xmlNewValidCtxt();
369  if (cvp) {
370  if (to_logs) {
371  cvp->userData = (void *)LOG_ERR;
372  cvp->error = (xmlValidityErrorFunc) xml_log;
373  cvp->warning = (xmlValidityWarningFunc) xml_log;
374  } else {
375  cvp->userData = (void *)stderr;
376  cvp->error = (xmlValidityErrorFunc) fprintf;
377  cvp->warning = (xmlValidityWarningFunc) fprintf;
378  }
379 
380  if (!xmlValidateDtd(cvp, doc, dtd)) {
381  valid = FALSE;
382  }
383  xmlFreeValidCtxt(cvp);
384 
385  } else {
386  crm_err("Internal error: No valid context");
387  }
388 
389  xmlFreeDtd(dtd);
390  return valid;
391 }
392 
393 #if 0
394 static void
395 relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
396 {
397  /*
398  Structure xmlError
399  struct _xmlError {
400  int domain : What part of the library raised this er
401  int code : The error code, e.g. an xmlParserError
402  char * message : human-readable informative error messag
403  xmlErrorLevel level : how consequent is the error
404  char * file : the filename
405  int line : the line number if available
406  char * str1 : extra string information
407  char * str2 : extra string information
408  char * str3 : extra string information
409  int int1 : extra number information
410  int int2 : column number of the error or 0 if N/A
411  void * ctxt : the parser context if available
412  void * node : the node in the tree
413  }
414  */
415  crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
416 }
417 #endif
418 
419 static gboolean
420 validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
421  relaxng_ctx_cache_t **cached_ctx)
422 {
423  int rc = 0;
424  gboolean valid = TRUE;
425  relaxng_ctx_cache_t *ctx = NULL;
426 
427  CRM_CHECK(doc != NULL, return FALSE);
428  CRM_CHECK(relaxng_file != NULL, return FALSE);
429 
430  if (cached_ctx && *cached_ctx) {
431  ctx = *cached_ctx;
432 
433  } else {
434  crm_info("Creating RNG parser context");
435  ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
436 
437  xmlLoadExtDtdDefaultValue = 1;
438  ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
439  CRM_CHECK(ctx->parser != NULL, goto cleanup);
440 
441  if (to_logs) {
442  xmlRelaxNGSetParserErrors(ctx->parser,
443  (xmlRelaxNGValidityErrorFunc) xml_log,
444  (xmlRelaxNGValidityWarningFunc) xml_log,
445  GUINT_TO_POINTER(LOG_ERR));
446  } else {
447  xmlRelaxNGSetParserErrors(ctx->parser,
448  (xmlRelaxNGValidityErrorFunc) fprintf,
449  (xmlRelaxNGValidityWarningFunc) fprintf,
450  stderr);
451  }
452 
453  ctx->rng = xmlRelaxNGParse(ctx->parser);
454  CRM_CHECK(ctx->rng != NULL,
455  crm_err("Could not find/parse %s", relaxng_file);
456  goto cleanup);
457 
458  ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
459  CRM_CHECK(ctx->valid != NULL, goto cleanup);
460 
461  if (to_logs) {
462  xmlRelaxNGSetValidErrors(ctx->valid,
463  (xmlRelaxNGValidityErrorFunc) xml_log,
464  (xmlRelaxNGValidityWarningFunc) xml_log,
465  GUINT_TO_POINTER(LOG_ERR));
466  } else {
467  xmlRelaxNGSetValidErrors(ctx->valid,
468  (xmlRelaxNGValidityErrorFunc) fprintf,
469  (xmlRelaxNGValidityWarningFunc) fprintf,
470  stderr);
471  }
472  }
473 
474  /* xmlRelaxNGSetValidStructuredErrors( */
475  /* valid, relaxng_invalid_stderr, valid); */
476 
477  xmlLineNumbersDefault(1);
478  rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
479  if (rc > 0) {
480  valid = FALSE;
481 
482  } else if (rc < 0) {
483  crm_err("Internal libxml error during validation");
484  }
485 
486  cleanup:
487 
488  if (cached_ctx) {
489  *cached_ctx = ctx;
490 
491  } else {
492  if (ctx->parser != NULL) {
493  xmlRelaxNGFreeParserCtxt(ctx->parser);
494  }
495  if (ctx->valid != NULL) {
496  xmlRelaxNGFreeValidCtxt(ctx->valid);
497  }
498  if (ctx->rng != NULL) {
499  xmlRelaxNGFree(ctx->rng);
500  }
501  free(ctx);
502  }
503 
504  return valid;
505 }
506 
511 void
513 {
514  int lpc;
515  relaxng_ctx_cache_t *ctx = NULL;
516 
517  for (lpc = 0; lpc < xml_schema_max; lpc++) {
518 
519  switch (known_schemas[lpc].validator) {
521  case schema_validator_dtd: // not cached
522  break;
523  case schema_validator_rng: // cached
524  ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
525  if (ctx == NULL) {
526  break;
527  }
528  if (ctx->parser != NULL) {
529  xmlRelaxNGFreeParserCtxt(ctx->parser);
530  }
531  if (ctx->valid != NULL) {
532  xmlRelaxNGFreeValidCtxt(ctx->valid);
533  }
534  if (ctx->rng != NULL) {
535  xmlRelaxNGFree(ctx->rng);
536  }
537  free(ctx);
538  known_schemas[lpc].cache = NULL;
539  break;
540  }
541  free(known_schemas[lpc].name);
542  free(known_schemas[lpc].location);
543  free(known_schemas[lpc].transform);
544  }
545  free(known_schemas);
546  known_schemas = NULL;
547 }
548 
549 static gboolean
550 validate_with(xmlNode *xml, int method, gboolean to_logs)
551 {
552  xmlDocPtr doc = NULL;
553  gboolean valid = FALSE;
554  char *file = NULL;
555 
556  if (method < 0) {
557  return FALSE;
558  }
559 
560  if (known_schemas[method].validator == schema_validator_none) {
561  return TRUE;
562  }
563 
564  CRM_CHECK(xml != NULL, return FALSE);
565  doc = getDocPtr(xml);
566  file = get_schema_path(known_schemas[method].name,
567  known_schemas[method].location);
568 
569  crm_trace("Validating with: %s (type=%d)",
570  crm_str(file), known_schemas[method].validator);
571  switch (known_schemas[method].validator) {
573  valid = validate_with_dtd(doc, to_logs, file);
574  break;
576  valid =
577  validate_with_relaxng(doc, to_logs, file,
578  (relaxng_ctx_cache_t **) & (known_schemas[method].cache));
579  break;
580  default:
581  crm_err("Unknown validator type: %d",
582  known_schemas[method].validator);
583  break;
584  }
585 
586  free(file);
587  return valid;
588 }
589 
590 static void
591 dump_file(const char *filename)
592 {
593 
594  FILE *fp = NULL;
595  int ch, line = 0;
596 
597  CRM_CHECK(filename != NULL, return);
598 
599  fp = fopen(filename, "r");
600  if (fp == NULL) {
601  crm_perror(LOG_ERR, "Could not open %s for reading", filename);
602  return;
603  }
604 
605  fprintf(stderr, "%4d ", ++line);
606  do {
607  ch = getc(fp);
608  if (ch == EOF) {
609  putc('\n', stderr);
610  break;
611  } else if (ch == '\n') {
612  fprintf(stderr, "\n%4d ", ++line);
613  } else {
614  putc(ch, stderr);
615  }
616  } while (1);
617 
618  fclose(fp);
619 }
620 
621 gboolean
622 validate_xml_verbose(xmlNode *xml_blob)
623 {
624  int fd = 0;
625  xmlDoc *doc = NULL;
626  xmlNode *xml = NULL;
627  gboolean rc = FALSE;
628  const char *tmpdir = getenv("TMPDIR");
629  char *filename = NULL;
630 
631  if ((tmpdir == NULL) || (*tmpdir != '/')) {
632  tmpdir = "/tmp";
633  }
634  filename = crm_strdup_printf("%s/cib-invalid.XXXXXX", tmpdir);
635 
636  umask(S_IWGRP | S_IWOTH | S_IROTH);
637  fd = mkstemp(filename);
638  write_xml_fd(xml_blob, filename, fd, FALSE);
639 
640  dump_file(filename);
641 
642  doc = xmlParseFile(filename);
643  xml = xmlDocGetRootElement(doc);
644  rc = validate_xml(xml, NULL, FALSE);
645  free_xml(xml);
646 
647  unlink(filename);
648  free(filename);
649 
650  return rc;
651 }
652 
653 gboolean
654 validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
655 {
656  int version = 0;
657 
658  if (validation == NULL) {
659  validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
660  }
661 
662  if (validation == NULL) {
663  int lpc = 0;
664  bool valid = FALSE;
665 
666  /* @COMPAT pre-1.0 configs */
667  validation = crm_element_value(xml_blob, "ignore_dtd");
668  if (crm_is_true(validation)) {
669  crm_xml_add(xml_blob, XML_ATTR_VALIDATION, "none");
670  return TRUE;
671  }
672 
673  /* Work it out */
674  for (lpc = 0; lpc < xml_schema_max; lpc++) {
675  if (validate_with(xml_blob, lpc, FALSE)) {
676  valid = TRUE;
678  known_schemas[lpc].name);
679  crm_info("XML validated against %s", known_schemas[lpc].name);
680  if(known_schemas[lpc].after_transform == 0) {
681  break;
682  }
683  }
684  }
685 
686  return valid;
687  }
688 
689  version = get_schema_version(validation);
690  if (strcmp(validation, "none") == 0) {
691  return TRUE;
692  } else if (version < xml_schema_max) {
693  return validate_with(xml_blob, version, to_logs);
694  }
695 
696  crm_err("Unknown validator: %s", validation);
697  return FALSE;
698 }
699 
700 #if HAVE_LIBXSLT
701 
702 static void
703 cib_upgrade_err(void *ctx, const char *fmt, ...)
704 G_GNUC_PRINTF(2, 3);
705 
706 static void
707 cib_upgrade_err(void *ctx, const char *fmt, ...)
708 {
709  va_list ap;
710 
711  va_start(ap, fmt);
712  CRM_XML_LOG_BASE(LOG_WARNING, TRUE, 0, "CIB upgrade: ", fmt, ap);
713  va_end(ap);
714 }
715 
716 static xmlNode *
717 apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs)
718 {
719  char *xform = NULL;
720  xmlNode *out = NULL;
721  xmlDocPtr res = NULL;
722  xmlDocPtr doc = NULL;
723  xsltStylesheet *xslt = NULL;
724 
725  CRM_CHECK(xml != NULL, return FALSE);
726  doc = getDocPtr(xml);
727  xform = get_schema_path(NULL, transform);
728 
729  xmlLoadExtDtdDefaultValue = 1;
730  xmlSubstituteEntitiesDefault(1);
731 
732  /* for capturing, e.g., what's emitted via <xsl:message> */
733  if (to_logs) {
734  xsltSetGenericErrorFunc(NULL, cib_upgrade_err);
735  } else {
736  xsltSetGenericErrorFunc((void *) stderr, (xmlGenericErrorFunc) fprintf);
737  }
738 
739  xslt = xsltParseStylesheetFile((const xmlChar *)xform);
740  CRM_CHECK(xslt != NULL, goto cleanup);
741 
742  res = xsltApplyStylesheet(xslt, doc, NULL);
743  CRM_CHECK(res != NULL, goto cleanup);
744 
745  xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */
746 
747  out = xmlDocGetRootElement(res);
748 
749  cleanup:
750  if (xslt) {
751  xsltFreeStylesheet(xslt);
752  }
753 
754  free(xform);
755 
756  return out;
757 }
758 #endif
759 
760 const char *
762 {
763  if (version < 0 || version >= xml_schema_max) {
764  return "unknown";
765  }
766  return known_schemas[version].name;
767 }
768 
769 int
770 get_schema_version(const char *name)
771 {
772  int lpc = 0;
773 
774  if (name == NULL) {
775  name = "none";
776  }
777  for (; lpc < xml_schema_max; lpc++) {
778  if (safe_str_eq(name, known_schemas[lpc].name)) {
779  return lpc;
780  }
781  }
782  return -1;
783 }
784 
785 /* set which validation to use */
786 int
787 update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform,
788  gboolean to_logs)
789 {
790  xmlNode *xml = NULL;
791  char *value = NULL;
792  int max_stable_schemas = xml_latest_schema_index();
793  int lpc = 0, match = -1, rc = pcmk_ok;
794  int next = -1; /* -1 denotes "inactive" value */
795 
796  CRM_CHECK(best != NULL, return -EINVAL);
797  *best = 0;
798 
799  CRM_CHECK(xml_blob != NULL, return -EINVAL);
800  CRM_CHECK(*xml_blob != NULL, return -EINVAL);
801 
802  xml = *xml_blob;
804 
805  if (value != NULL) {
806  match = get_schema_version(value);
807 
808  lpc = match;
809  if (lpc >= 0 && transform == FALSE) {
810  *best = lpc++;
811 
812  } else if (lpc < 0) {
813  crm_debug("Unknown validation schema");
814  lpc = 0;
815  }
816  }
817 
818  if (match >= max_stable_schemas) {
819  /* nothing to do */
820  free(value);
821  *best = match;
822  return pcmk_ok;
823  }
824 
825  while (lpc <= max_stable_schemas) {
826  crm_debug("Testing '%s' validation (%d of %d)",
827  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
828  lpc, max_stable_schemas);
829 
830  if (validate_with(xml, lpc, to_logs) == FALSE) {
831  if (next != -1) {
832  crm_info("Configuration not valid for schema: %s",
833  known_schemas[lpc].name);
834  next = -1;
835  } else {
836  crm_trace("%s validation failed",
837  known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
838  }
839  if (*best) {
840  /* we've satisfied the validation, no need to check further */
841  break;
842  }
844 
845  } else {
846  if (next != -1) {
847  crm_debug("Configuration valid for schema: %s",
848  known_schemas[next].name);
849  next = -1;
850  }
851  rc = pcmk_ok;
852  }
853 
854  if (rc == pcmk_ok) {
855  *best = lpc;
856  }
857 
858  if (rc == pcmk_ok && transform) {
859  xmlNode *upgrade = NULL;
860  next = known_schemas[lpc].after_transform;
861 
862  if (next <= lpc) {
863  /* There is no next version, or next would regress */
864  crm_trace("Stopping at %s", known_schemas[lpc].name);
865  break;
866 
867  } else if (max > 0 && (lpc == max || next > max)) {
868  crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
869  known_schemas[lpc].name, lpc, next, max);
870  break;
871 
872  } else if (known_schemas[lpc].transform == NULL) {
873  crm_debug("%s-style configuration is also valid for %s",
874  known_schemas[lpc].name, known_schemas[next].name);
875 
876  lpc = next;
877 
878  } else {
879  crm_debug("Upgrading %s-style configuration to %s with %s",
880  known_schemas[lpc].name, known_schemas[next].name,
881  known_schemas[lpc].transform ? known_schemas[lpc].transform : "no-op");
882 
883 #if HAVE_LIBXSLT
884  upgrade = apply_transformation(xml, known_schemas[lpc].transform, to_logs);
885 #endif
886  if (upgrade == NULL) {
887  crm_err("Transformation %s failed",
888  known_schemas[lpc].transform);
890 
891  } else if (validate_with(upgrade, next, to_logs)) {
892  crm_info("Transformation %s successful",
893  known_schemas[lpc].transform);
894  lpc = next;
895  *best = next;
896  free_xml(xml);
897  xml = upgrade;
898  rc = pcmk_ok;
899 
900  } else {
901  crm_err("Transformation %s did not produce a valid configuration",
902  known_schemas[lpc].transform);
903  crm_log_xml_info(upgrade, "transform:bad");
904  free_xml(upgrade);
906  }
907  next = -1;
908  }
909  }
910 
911  if (transform == FALSE || rc != pcmk_ok) {
912  /* we need some progress! */
913  lpc++;
914  }
915  }
916 
917  if (*best > match && *best) {
918  crm_info("%s the configuration from %s to %s",
919  transform?"Transformed":"Upgraded",
920  value ? value : "<none>", known_schemas[*best].name);
921  crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
922  }
923 
924  *xml_blob = xml;
925  free(value);
926  return rc;
927 }
928 
929 gboolean
930 cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
931 {
932  gboolean rc = TRUE;
933  const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
934  char *const orig_value = strdup(value == NULL ? "(none)" : value);
935 
936  int version = get_schema_version(value);
937  int orig_version = version;
938  int min_version = xml_minimum_schema_index();
939 
940  if (version < min_version) {
941  xmlNode *converted = NULL;
942 
943  converted = copy_xml(*xml);
944  update_validation(&converted, &version, 0, TRUE, to_logs);
945 
946  value = crm_element_value(converted, XML_ATTR_VALIDATION);
947  if (version < min_version) {
948  if (version < orig_version || orig_version == -1) {
949  if (to_logs) {
950  crm_config_err("Your current configuration %s could not"
951  " validate with any schema in range [%s, %s],"
952  " cannot upgrade to %s.",
953  orig_value,
954  get_schema_name(orig_version),
956  get_schema_name(min_version));
957  } else {
958  fprintf(stderr, "Your current configuration %s could not"
959  " validate with any schema in range [%s, %s],"
960  " cannot upgrade to %s.\n",
961  orig_value,
962  get_schema_name(orig_version),
964  get_schema_name(min_version));
965  }
966  } else if (to_logs) {
967  crm_config_err("Your current configuration could only be upgraded to %s... "
968  "the minimum requirement is %s.", crm_str(value),
969  get_schema_name(min_version));
970  } else {
971  fprintf(stderr, "Your current configuration could only be upgraded to %s... "
972  "the minimum requirement is %s.\n",
973  crm_str(value), get_schema_name(min_version));
974  }
975 
976  free_xml(converted);
977  converted = NULL;
978  rc = FALSE;
979 
980  } else {
981  free_xml(*xml);
982  *xml = converted;
983 
984  if (version < xml_latest_schema_index()) {
985  crm_config_warn("Your configuration was internally updated to %s... "
986  "which is acceptable but not the most recent",
987  get_schema_name(version));
988 
989  } else if (to_logs) {
990  crm_info("Your configuration was internally updated to the latest version (%s)",
991  get_schema_name(version));
992  }
993  }
994 
995  } else if (version >= get_schema_version("none")) {
996  if (to_logs) {
997  crm_config_warn("Configuration validation is currently disabled."
998  " It is highly encouraged and prevents many common cluster issues.");
999 
1000  } else {
1001  fprintf(stderr, "Configuration validation is currently disabled."
1002  " It is highly encouraged and prevents many common cluster issues.\n");
1003  }
1004  }
1005 
1006  if (best_version) {
1007  *best_version = version;
1008  }
1009 
1010  free(orig_value);
1011  return rc;
1012 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:190
void crm_schema_init(void)
Definition: schemas.c:273
#define crm_notice(fmt, args...)
Definition: logging.h:276
#define schema_strdup_printf(prefix, version, suffix)
Definition: schemas.c:51
#define crm_config_err(fmt...)
Definition: crm_internal.h:225
#define schema_scanf(s, prefix, version, suffix)
Definition: schemas.c:48
#define pcmk_ok
Definition: error.h:45
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:216
#define CRM_DTD_DIRECTORY
Definition: config.h:50
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:67
char version[256]
Definition: plugin.c:84
xmlDoc * getDocPtr(xmlNode *node)
Definition: xml.c:1939
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:2114
int get_schema_version(const char *name)
Definition: schemas.c:770
#define crm_debug(fmt, args...)
Definition: logging.h:279
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: nvpair.c:570
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:393
#define pcmk_err_schema_validation
Definition: error.h:50
void crm_schema_cleanup(void)
Definition: schemas.c:512
#define crm_trace(fmt, args...)
Definition: logging.h:280
#define SCHEMA_ZERO
Definition: schemas.c:46
const char * get_schema_name(int version)
Definition: schemas.c:761
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:86
void free_xml(xmlNode *child)
Definition: xml.c:2108
gboolean crm_ends_with_ext(const char *s, const char *match)
Definition: strings.c:363
gboolean validate_xml_verbose(xmlNode *xml_blob)
Definition: schemas.c:622
#define pcmk_err_transform_failed
Definition: error.h:51
#define crm_config_warn(fmt...)
Definition: crm_internal.h:226
gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs)
Definition: schemas.c:654
#define crm_perror(level, fmt, args...)
Log a system error message.
Definition: logging.h:252
#define crm_err(fmt, args...)
Definition: logging.h:274
#define crm_log_xml_info(xml, text)
Definition: logging.h:286
#define CRM_ASSERT(expr)
Definition: error.h:20
#define crm_str(x)
Definition: logging.h:300
gboolean crm_is_true(const char *s)
Definition: strings.c:197
gboolean cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs)
Definition: schemas.c:930
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:787
#define safe_str_eq(a, b)
Definition: util.h:74
int write_xml_fd(xmlNode *xml_node, const char *filename, int fd, gboolean compress)
Definition: xml.c:2538
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
#define crm_info(fmt, args...)
Definition: logging.h:277
const char * xml_latest_schema(void)
Definition: schemas.c:122
schema_validator_e
Definition: schemas.c:60