root/lib/common/xml.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. TRACKING_CHANGES
  2. insert_prefix
  3. set_parent_flag
  4. set_doc_flag
  5. __xml_node_dirty
  6. __xml_node_clean
  7. crm_node_created
  8. crm_attr_dirty
  9. crm_attr_value
  10. crm_first_attr
  11. __xml_acl_free
  12. __xml_deleted_obj_free
  13. __xml_private_clean
  14. __xml_private_free
  15. pcmkDeregisterNode
  16. pcmkRegisterNode
  17. __xml_acl_create
  18. __xml_acl_parse_entry
  19. __xml_acl_to_text
  20. __xml_acl_apply
  21. __xml_acl_unpack
  22. __xml_acl_mode_test
  23. __xml_purge_attributes
  24. xml_acl_filtered_copy
  25. __xml_acl_post_process
  26. xml_acl_denied
  27. xml_acl_disable
  28. xml_acl_enabled
  29. xml_track_changes
  30. xml_tracking_changes
  31. xml_document_dirty
  32. __xml_offset
  33. __xml_offset_no_deletions
  34. __xml_build_changes
  35. __xml_accept_changes
  36. is_config_change
  37. xml_repair_v1_diff
  38. xml_create_patchset_v1
  39. xml_create_patchset_v2
  40. patch_legacy_mode
  41. xml_create_patchset
  42. patchset_process_digest
  43. xml_log_patchset
  44. xml_log_changes
  45. xml_accept_changes
  46. find_element
  47. __subtract_xml_object
  48. __add_xml_object
  49. find_patch_xml_node
  50. xml_patch_versions
  51. xml_patch_version_check
  52. xml_apply_patchset_v1
  53. __first_xml_child_match
  54. __xml_find_path
  55. xml_apply_patchset_v2
  56. xml_apply_patchset
  57. find_xml_node
  58. find_entity
  59. copy_in_properties
  60. fix_plus_plus_recursive
  61. expand_plus_plus
  62. getDocPtr
  63. add_node_copy
  64. add_node_nocopy
  65. __xml_acl_check
  66. crm_xml_add
  67. crm_xml_replace
  68. crm_xml_add_int
  69. create_xml_node
  70. __get_prefix
  71. xml_get_path
  72. free_xml_with_position
  73. free_xml
  74. copy_xml
  75. crm_xml_err
  76. string2xml
  77. stdin2xml
  78. decompress_file
  79. strip_text_nodes
  80. filename2xml
  81. crm_xml_add_last_written
  82. crm_xml_sanitize_id
  83. crm_xml_set_id
  84. write_xml_stream
  85. write_xml_fd
  86. write_xml_file
  87. get_message_xml
  88. add_message_xml
  89. crm_xml_escape_shuffle
  90. crm_xml_escape
  91. dump_xml_attr
  92. __xml_log_element
  93. __xml_log_change_element
  94. log_data_element
  95. dump_filtered_xml
  96. dump_xml_element
  97. dump_xml_text
  98. dump_xml_comment
  99. crm_xml_dump
  100. crm_buffer_add_char
  101. dump_xml_formatted_with_text
  102. dump_xml_formatted
  103. dump_xml_unformatted
  104. xml_has_children
  105. crm_element_value_int
  106. crm_element_value_const_int
  107. crm_element_value_const
  108. crm_element_value_copy
  109. xml_remove_prop
  110. purge_diff_markers
  111. save_xml_to_file
  112. apply_xml_diff
  113. __xml_diff_object
  114. xml_calculate_changes
  115. diff_xml_object
  116. can_prune_leaf
  117. diff_filter_context
  118. in_upper_context
  119. find_xml_comment
  120. subtract_xml_comment
  121. subtract_xml_object
  122. add_xml_comment
  123. add_xml_object
  124. update_xml_child
  125. find_xml_children
  126. replace_xml_child
  127. crm_create_nvpair_xml
  128. hash2nvpair
  129. hash2smartfield
  130. hash2field
  131. hash2metafield
  132. xml2list
  133. sort_pairs
  134. dump_pair
  135. sorted_xml
  136. first_named_child
  137. crm_next_same_xml
  138. crm_xml_init
  139. crm_xml_cleanup
  140. expand_idref
  141. crm_element_value
  142. crm_destroy_xml

   1 /*
   2  * Copyright (C) 2004 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 <sys/param.h>
  22 #include <stdio.h>
  23 #include <sys/types.h>
  24 #include <unistd.h>
  25 #include <time.h>
  26 #include <string.h>
  27 #include <stdlib.h>
  28 #include <ctype.h>
  29 #include <stdarg.h>
  30 
  31 #include <crm/crm.h>
  32 #include <crm/msg_xml.h>
  33 #include <crm/common/xml.h>
  34 #include <crm/common/xml_internal.h>  /* CRM_XML_LOG_BASE */
  35 
  36 #if HAVE_BZLIB_H
  37 #  include <bzlib.h>
  38 #endif
  39 
  40 #if HAVE_LIBXML2
  41 #  include <libxml/parser.h>
  42 #  include <libxml/tree.h>
  43 #endif
  44 
  45 #define XML_BUFFER_SIZE 4096
  46 #define XML_PARSER_DEBUG 0
  47 
  48 static inline int
  49 __get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset);
  50 
  51 typedef struct {
  52     int found;
  53     const char *string;
  54 } filter_t;
  55 
  56 enum xml_private_flags {
  57      xpf_none        = 0x0000,
  58      xpf_dirty       = 0x0001,
  59      xpf_deleted     = 0x0002,
  60      xpf_created     = 0x0004,
  61      xpf_modified    = 0x0008,
  62 
  63      xpf_tracking    = 0x0010,
  64      xpf_processed   = 0x0020,
  65      xpf_skip        = 0x0040,
  66      xpf_moved       = 0x0080,
  67 
  68      xpf_acl_enabled = 0x0100,
  69      xpf_acl_read    = 0x0200,
  70      xpf_acl_write   = 0x0400,
  71      xpf_acl_deny    = 0x0800,
  72 
  73      xpf_acl_create  = 0x1000,
  74      xpf_acl_denied  = 0x2000,
  75 };
  76 
  77 typedef struct xml_private_s 
  78 {
  79         long check;
  80         uint32_t flags;
  81         char *user;
  82         GListPtr acls;
  83         GListPtr deleted_objs;
  84 } xml_private_t;
  85 
  86 typedef struct xml_acl_s {
  87         enum xml_private_flags mode;
  88         char *xpath;
  89 } xml_acl_t;
  90 
  91 typedef struct xml_deleted_obj_s {
  92         char *path;
  93         int position;
  94 } xml_deleted_obj_t;
  95 
  96 /* *INDENT-OFF* */
  97 
  98 static filter_t filter[] = {
  99     { 0, XML_ATTR_ORIGIN },
 100     { 0, XML_CIB_ATTR_WRITTEN },
 101     { 0, XML_ATTR_UPDATE_ORIG },
 102     { 0, XML_ATTR_UPDATE_CLIENT },
 103     { 0, XML_ATTR_UPDATE_USER },
 104 };
 105 /* *INDENT-ON* */
 106 
 107 static xmlNode *subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right, gboolean * changed);
 108 static xmlNode *find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact);
 109 static int add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update);
 110 static bool __xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode);
 111 const char *__xml_acl_to_text(enum xml_private_flags flags);
 112 
 113 #define CHUNK_SIZE 1024
 114 static inline bool TRACKING_CHANGES(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 115 {
 116     if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
 117         return FALSE;
 118     } else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
 119         return TRUE;
 120     }
 121     return FALSE;
 122 }
 123 
 124 #define buffer_print(buffer, max, offset, fmt, args...) do {            \
 125         int rc = (max);                                                 \
 126         if(buffer) {                                                    \
 127             rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
 128         }                                                               \
 129         if(buffer && rc < 0) {                                          \
 130             crm_perror(LOG_ERR, "snprintf failed at offset %d", offset); \
 131             (buffer)[(offset)] = 0;                                     \
 132             break;                                                      \
 133         } else if(rc >= ((max) - (offset))) {                           \
 134             char *tmp = NULL;                                           \
 135             (max) = QB_MAX(CHUNK_SIZE, (max) * 2);                      \
 136             tmp = realloc_safe((buffer), (max));                        \
 137             CRM_ASSERT(tmp);                                            \
 138             (buffer) = tmp;                                             \
 139         } else {                                                        \
 140             offset += rc;                                               \
 141             break;                                                      \
 142         }                                                               \
 143     } while(1);
 144 
 145 static void
 146 insert_prefix(int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
 147 {
 148     if (options & xml_log_option_formatted) {
 149         size_t spaces = 2 * depth;
 150 
 151         if ((*buffer) == NULL || spaces >= ((*max) - (*offset))) {
 152             (*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
 153             (*buffer) = realloc_safe((*buffer), (*max));
 154         }
 155         memset((*buffer) + (*offset), ' ', spaces);
 156         (*offset) += spaces;
 157     }
 158 }
 159 
 160 static void
 161 set_parent_flag(xmlNode *xml, long flag) 
     /* [previous][next][first][last][top][bottom][index][help] */
 162 {
 163 
 164     for(; xml; xml = xml->parent) {
 165         xml_private_t *p = xml->_private;
 166 
 167         if(p == NULL) {
 168             /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
 169         } else {
 170             p->flags |= flag;
 171             /* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
 172         }
 173     }
 174 }
 175 
 176 static void
 177 set_doc_flag(xmlNode *xml, long flag) 
     /* [previous][next][first][last][top][bottom][index][help] */
 178 {
 179 
 180     if(xml && xml->doc && xml->doc->_private){
 181         /* During calls to xmlDocCopyNode(), xml->doc may be unset */
 182         xml_private_t *p = xml->doc->_private;
 183 
 184         p->flags |= flag;
 185         /* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
 186     }
 187 }
 188 
 189 static void
 190 __xml_node_dirty(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 191 {
 192     set_doc_flag(xml, xpf_dirty);
 193     set_parent_flag(xml, xpf_dirty);
 194 }
 195 
 196 static void
 197 __xml_node_clean(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 198 {
 199     xmlNode *cIter = NULL;
 200     xml_private_t *p = xml->_private;
 201 
 202     if(p) {
 203         p->flags = 0;
 204     }
 205 
 206     for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
 207         __xml_node_clean(cIter);
 208     }
 209 }
 210 
 211 static void
 212 crm_node_created(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 213 {
 214     xmlNode *cIter = NULL;
 215     xml_private_t *p = xml->_private;
 216 
 217     if(p && TRACKING_CHANGES(xml)) {
 218         if(is_not_set(p->flags, xpf_created)) {
 219             p->flags |= xpf_created;
 220             __xml_node_dirty(xml);
 221         }
 222 
 223         for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
 224            crm_node_created(cIter);
 225         }
 226     }
 227 }
 228 
 229 static void
 230 crm_attr_dirty(xmlAttr *a) 
     /* [previous][next][first][last][top][bottom][index][help] */
 231 {
 232     xmlNode *parent = a->parent;
 233     xml_private_t *p = NULL;
 234 
 235     p = a->_private;
 236     p->flags |= (xpf_dirty|xpf_modified);
 237     p->flags = (p->flags & ~xpf_deleted);
 238     /* crm_trace("Setting flag %x due to %s[@id=%s, @%s=%s]", */
 239     /*           xpf_dirty, parent?parent->name:NULL, ID(parent), a->name, a->children->content); */
 240 
 241     __xml_node_dirty(parent);
 242 }
 243 
 244 int get_tag_name(const char *input, size_t offset, size_t max);
 245 int get_attr_name(const char *input, size_t offset, size_t max);
 246 int get_attr_value(const char *input, size_t offset, size_t max);
 247 gboolean can_prune_leaf(xmlNode * xml_node);
 248 
 249 void diff_filter_context(int context, int upper_bound, int lower_bound,
 250                          xmlNode * xml_node, xmlNode * parent);
 251 int in_upper_context(int depth, int context, xmlNode * xml_node);
 252 int add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff);
 253 
 254 static inline const char *
 255 crm_attr_value(xmlAttr * attr)
     /* [previous][next][first][last][top][bottom][index][help] */
 256 {
 257     if (attr == NULL || attr->children == NULL) {
 258         return NULL;
 259     }
 260     return (const char *)attr->children->content;
 261 }
 262 
 263 static inline xmlAttr *
 264 crm_first_attr(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 265 {
 266     if (xml == NULL) {
 267         return NULL;
 268     }
 269     return xml->properties;
 270 }
 271 
 272 #define XML_PRIVATE_MAGIC (long) 0x81726354
 273 
 274 static void
 275 __xml_acl_free(void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 276 {
 277     if(data) {
 278         xml_acl_t *acl = data;
 279 
 280         free(acl->xpath);
 281         free(acl);
 282     }
 283 }
 284 
 285 static void
 286 __xml_deleted_obj_free(void *data)
     /* [previous][next][first][last][top][bottom][index][help] */
 287 {
 288     if(data) {
 289         xml_deleted_obj_t *deleted_obj = data;
 290 
 291         free(deleted_obj->path);
 292         free(deleted_obj);
 293     }
 294 }
 295 
 296 static void
 297 __xml_private_clean(xml_private_t *p)
     /* [previous][next][first][last][top][bottom][index][help] */
 298 {
 299     if(p) {
 300         CRM_ASSERT(p->check == XML_PRIVATE_MAGIC);
 301 
 302         free(p->user);
 303         p->user = NULL;
 304 
 305         if(p->acls) {
 306             g_list_free_full(p->acls, __xml_acl_free);
 307             p->acls = NULL;
 308         }
 309 
 310         if(p->deleted_objs) {
 311             g_list_free_full(p->deleted_objs, __xml_deleted_obj_free);
 312             p->deleted_objs = NULL;
 313         }
 314     }
 315 }
 316 
 317 
 318 static void
 319 __xml_private_free(xml_private_t *p)
     /* [previous][next][first][last][top][bottom][index][help] */
 320 {
 321     __xml_private_clean(p);
 322     free(p);
 323 }
 324 
 325 static void
 326 pcmkDeregisterNode(xmlNodePtr node)
     /* [previous][next][first][last][top][bottom][index][help] */
 327 {
 328     __xml_private_free(node->_private);
 329 }
 330 
 331 static void
 332 pcmkRegisterNode(xmlNodePtr node)
     /* [previous][next][first][last][top][bottom][index][help] */
 333 {
 334     xml_private_t *p = NULL;
 335 
 336     switch(node->type) {
 337         case XML_ELEMENT_NODE:
 338         case XML_DOCUMENT_NODE:
 339         case XML_ATTRIBUTE_NODE:
 340         case XML_COMMENT_NODE:
 341             p = calloc(1, sizeof(xml_private_t));
 342             p->check = XML_PRIVATE_MAGIC;
 343             /* Flags will be reset if necessary when tracking is enabled */
 344             p->flags |= (xpf_dirty|xpf_created);
 345             node->_private = p;
 346             break;
 347         case XML_TEXT_NODE:
 348         case XML_DTD_NODE:
 349         case XML_CDATA_SECTION_NODE:
 350             break;
 351         default:
 352             /* Ignore */
 353             crm_trace("Ignoring %p %d", node, node->type);
 354             CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
 355             break;
 356     }
 357 
 358     if(p && TRACKING_CHANGES(node)) {
 359         /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
 360          * not hooked up at the point we are called
 361          */
 362         set_doc_flag(node, xpf_dirty);
 363         __xml_node_dirty(node);
 364     }
 365 }
 366 
 367 static xml_acl_t *
 368 __xml_acl_create(xmlNode * xml, xmlNode *target, enum xml_private_flags mode)
     /* [previous][next][first][last][top][bottom][index][help] */
 369 {
 370     xml_acl_t *acl = NULL;
 371 
 372     xml_private_t *p = NULL;
 373     const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
 374     const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
 375     const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
 376 
 377     if(tag == NULL) {
 378         /* Compatibility handling for pacemaker < 1.1.12 */
 379         tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
 380     }
 381     if(ref == NULL) {
 382         /* Compatibility handling for pacemaker < 1.1.12 */
 383         ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
 384     }
 385 
 386     if(target == NULL || target->doc == NULL || target->doc->_private == NULL){
 387         CRM_ASSERT(target);
 388         CRM_ASSERT(target->doc);
 389         CRM_ASSERT(target->doc->_private);
 390         return NULL;
 391 
 392     } else if (tag == NULL && ref == NULL && xpath == NULL) {
 393         crm_trace("No criteria %p", xml);
 394         return NULL;
 395     }
 396 
 397     p = target->doc->_private;
 398     acl = calloc(1, sizeof(xml_acl_t));
 399     if (acl) {
 400         const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
 401 
 402         acl->mode = mode;
 403         if(xpath) {
 404             acl->xpath = strdup(xpath);
 405             crm_trace("Using xpath: %s", acl->xpath);
 406 
 407         } else {
 408             int offset = 0;
 409             char buffer[XML_BUFFER_SIZE];
 410 
 411             if(tag) {
 412                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//%s", tag);
 413             } else {
 414                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//*");
 415             }
 416 
 417             if(ref || attr) {
 418                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[");
 419             }
 420 
 421             if(ref) {
 422                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@id='%s'", ref);
 423             }
 424 
 425             if(ref && attr) {
 426                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, " and ");
 427             }
 428 
 429             if(attr) {
 430                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@%s", attr);
 431             }
 432 
 433             if(ref || attr) {
 434                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "]");
 435             }
 436 
 437             CRM_LOG_ASSERT(offset > 0);
 438             acl->xpath = strdup(buffer);
 439             crm_trace("Built xpath: %s", acl->xpath);
 440         }
 441 
 442         p->acls = g_list_append(p->acls, acl);
 443     }
 444     return acl;
 445 }
 446 
 447 static gboolean
 448 __xml_acl_parse_entry(xmlNode * acl_top, xmlNode * acl_entry, xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
 449 {
 450     xmlNode *child = NULL;
 451 
 452     for (child = __xml_first_child(acl_entry); child; child = __xml_next(child)) {
 453         const char *tag = crm_element_name(child);
 454         const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
 455 
 456         if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
 457             tag = kind;
 458         }
 459 
 460         crm_trace("Processing %s %p", tag, child);
 461         if(tag == NULL) {
 462             CRM_ASSERT(tag != NULL);
 463 
 464         } else if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
 465                    || strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
 466             const char *ref_role = crm_element_value(child, XML_ATTR_ID);
 467 
 468             if (ref_role) {
 469                 xmlNode *role = NULL;
 470 
 471                 for (role = __xml_first_child(acl_top); role; role = __xml_next(role)) {
 472                     if (strcmp(XML_ACL_TAG_ROLE, (const char *)role->name) == 0) {
 473                         const char *role_id = crm_element_value(role, XML_ATTR_ID);
 474 
 475                         if (role_id && strcmp(ref_role, role_id) == 0) {
 476                             crm_debug("Unpacking referenced role: %s", role_id);
 477                             __xml_acl_parse_entry(acl_top, role, target);
 478                             break;
 479                         }
 480                     }
 481                 }
 482             }
 483 
 484         } else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
 485             __xml_acl_create(child, target, xpf_acl_read);
 486 
 487         } else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
 488             __xml_acl_create(child, target, xpf_acl_write);
 489 
 490         } else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
 491             __xml_acl_create(child, target, xpf_acl_deny);
 492 
 493         } else {
 494             crm_warn("Unknown ACL entry: %s/%s", tag, kind);
 495         }
 496     }
 497 
 498     return TRUE;
 499 }
 500 
 501 /*
 502     <acls>
 503       <acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
 504       <acl_role id="auto-l33t-haxor">
 505         <acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
 506       </acl_role>
 507       <acl_target id="niceguy">
 508         <role id="observer"/>
 509       </acl_target>
 510       <acl_role id="observer">
 511         <acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
 512         <acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
 513         <acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
 514       </acl_role>
 515       <acl_target id="badidea"><role id="auto-badidea"/></acl_target>
 516       <acl_role id="auto-badidea">
 517         <acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
 518         <acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
 519       </acl_role>
 520     </acls>
 521 */
 522 
 523 const char *
 524 __xml_acl_to_text(enum xml_private_flags flags)
     /* [previous][next][first][last][top][bottom][index][help] */
 525 {
 526     if(is_set(flags, xpf_acl_deny)) {
 527         return "deny";
 528     }
 529     if(is_set(flags, xpf_acl_write)) {
 530         return "read/write";
 531     }
 532     if(is_set(flags, xpf_acl_read)) {
 533         return "read";
 534     }
 535 
 536     return "none";
 537 }
 538 
 539 static void
 540 __xml_acl_apply(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 541 {
 542     GListPtr aIter = NULL;
 543     xml_private_t *p = NULL;
 544     xmlXPathObjectPtr xpathObj = NULL;
 545 
 546     if(xml_acl_enabled(xml) == FALSE) {
 547         p = xml->doc->_private;
 548         crm_trace("Not applying ACLs for %s", p->user);
 549         return;
 550     }
 551 
 552     p = xml->doc->_private;
 553     for(aIter = p->acls; aIter != NULL; aIter = aIter->next) {
 554         int max = 0, lpc = 0;
 555         xml_acl_t *acl = aIter->data;
 556 
 557         xpathObj = xpath_search(xml, acl->xpath);
 558         max = numXpathResults(xpathObj);
 559 
 560         for(lpc = 0; lpc < max; lpc++) {
 561             xmlNode *match = getXpathResult(xpathObj, lpc);
 562             char *path = xml_get_path(match);
 563 
 564             p = match->_private;
 565             crm_trace("Applying %x to %s for %s", acl->mode, path, acl->xpath);
 566 
 567 #ifdef SUSE_ACL_COMPAT
 568             if(is_not_set(p->flags, acl->mode)) {
 569                 if(is_set(p->flags, xpf_acl_read)
 570                    || is_set(p->flags, xpf_acl_write)
 571                    || is_set(p->flags, xpf_acl_deny)) {
 572                     crm_config_warn("Configuration element %s is matched by multiple ACL rules, only the first applies ('%s' wins over '%s')",
 573                                     path, __xml_acl_to_text(p->flags), __xml_acl_to_text(acl->mode));
 574                     free(path);
 575                     continue;
 576                 }
 577             }
 578 #endif
 579 
 580             p->flags |= acl->mode;
 581             free(path);
 582         }
 583         crm_trace("Now enforcing ACL: %s (%d matches)", acl->xpath, max);
 584         freeXpathObject(xpathObj);
 585     }
 586 
 587     p = xml->_private;
 588     if(is_not_set(p->flags, xpf_acl_read) && is_not_set(p->flags, xpf_acl_write)) {
 589         p->flags |= xpf_acl_deny;
 590         p = xml->doc->_private;
 591         crm_info("Enforcing default ACL for %s to %s", p->user, crm_element_name(xml));
 592     }
 593 
 594 }
 595 
 596 static void
 597 __xml_acl_unpack(xmlNode *source, xmlNode *target, const char *user)
     /* [previous][next][first][last][top][bottom][index][help] */
 598 {
 599 #if ENABLE_ACL
 600     xml_private_t *p = NULL;
 601 
 602     if(target == NULL || target->doc == NULL || target->doc->_private == NULL) {
 603         return;
 604     }
 605 
 606     p = target->doc->_private;
 607     if(pcmk_acl_required(user) == FALSE) {
 608         crm_trace("no acls needed for '%s'", user);
 609 
 610     } else if(p->acls == NULL) {
 611         xmlNode *acls = get_xpath_object("//"XML_CIB_TAG_ACLS, source, LOG_TRACE);
 612 
 613         free(p->user);
 614         p->user = strdup(user);
 615 
 616         if(acls) {
 617             xmlNode *child = NULL;
 618 
 619             for (child = __xml_first_child(acls); child; child = __xml_next(child)) {
 620                 const char *tag = crm_element_name(child);
 621 
 622                 if (strcmp(tag, XML_ACL_TAG_USER) == 0 || strcmp(tag, XML_ACL_TAG_USERv1) == 0) {
 623                     const char *id = crm_element_value(child, XML_ATTR_ID);
 624 
 625                     if(id && strcmp(id, user) == 0) {
 626                         crm_debug("Unpacking ACLs for %s", id);
 627                         __xml_acl_parse_entry(acls, child, target);
 628                     }
 629                 }
 630             }
 631         }
 632     }
 633 #endif
 634 }
 635 
 636 static inline bool
 637 __xml_acl_mode_test(enum xml_private_flags allowed, enum xml_private_flags requested)
     /* [previous][next][first][last][top][bottom][index][help] */
 638 {
 639     if(is_set(allowed, xpf_acl_deny)) {
 640         return FALSE;
 641 
 642     } else if(is_set(allowed, requested)) {
 643         return TRUE;
 644 
 645     } else if(is_set(requested, xpf_acl_read) && is_set(allowed, xpf_acl_write)) {
 646         return TRUE;
 647 
 648     } else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_acl_write)) {
 649         return TRUE;
 650 
 651     } else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_created)) {
 652         return TRUE;
 653     }
 654     return FALSE;
 655 }
 656 
 657 /* rc = TRUE if orig_cib has been filtered
 658  * That means '*result' rather than 'xml' should be exploited afterwards
 659  */
 660 static bool
 661 __xml_purge_attributes(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 662 {
 663     xmlNode *child = NULL;
 664     xmlAttr *xIter = NULL;
 665     bool readable_children = FALSE;
 666     xml_private_t *p = xml->_private;
 667 
 668     if(__xml_acl_mode_test(p->flags, xpf_acl_read)) {
 669         crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
 670         return TRUE;
 671     }
 672 
 673     xIter = crm_first_attr(xml);
 674     while(xIter != NULL) {
 675         xmlAttr *tmp = xIter;
 676         const char *prop_name = (const char *)xIter->name;
 677 
 678         xIter = xIter->next;
 679         if (strcmp(prop_name, XML_ATTR_ID) == 0) {
 680             continue;
 681         }
 682 
 683         xmlUnsetProp(xml, tmp->name);
 684     }
 685 
 686     child = __xml_first_child(xml);
 687     while ( child != NULL ) {
 688         xmlNode *tmp = child;
 689 
 690         child = __xml_next(child);
 691         readable_children |= __xml_purge_attributes(tmp);
 692     }
 693 
 694     if(readable_children == FALSE) {
 695         free_xml(xml); /* Nothing readable under here, purge completely */
 696     }
 697     return readable_children;
 698 }
 699 
 700 bool
 701 xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml, xmlNode ** result)
     /* [previous][next][first][last][top][bottom][index][help] */
 702 {
 703     GListPtr aIter = NULL;
 704     xmlNode *target = NULL;
 705     xml_private_t *p = NULL;
 706     xml_private_t *doc = NULL;
 707 
 708     *result = NULL;
 709     if(xml == NULL || pcmk_acl_required(user) == FALSE) {
 710         crm_trace("no acls needed for '%s'", user);
 711         return FALSE;
 712     }
 713 
 714     crm_trace("filtering copy of %p for '%s'", xml, user);
 715     target = copy_xml(xml);
 716     if(target == NULL) {
 717         return TRUE;
 718     }
 719 
 720     __xml_acl_unpack(acl_source, target, user);
 721     set_doc_flag(target, xpf_acl_enabled);
 722     __xml_acl_apply(target);
 723 
 724     doc = target->doc->_private;
 725     for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
 726         int max = 0;
 727         xml_acl_t *acl = aIter->data;
 728 
 729         if(acl->mode != xpf_acl_deny) {
 730             /* Nothing to do */
 731 
 732         } else if(acl->xpath) {
 733             int lpc = 0;
 734             xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
 735 
 736             max = numXpathResults(xpathObj);
 737             for(lpc = 0; lpc < max; lpc++) {
 738                 xmlNode *match = getXpathResult(xpathObj, lpc);
 739 
 740                 crm_trace("Purging attributes from %s", acl->xpath);
 741                 if(__xml_purge_attributes(match) == FALSE && match == target) {
 742                     crm_trace("No access to the entire document for %s", user);
 743                     freeXpathObject(xpathObj);
 744                     return TRUE;
 745                 }
 746             }
 747             crm_trace("Enforced ACL %s (%d matches)", acl->xpath, max);
 748             freeXpathObject(xpathObj);
 749         }
 750     }
 751 
 752     p = target->_private;
 753     if(is_set(p->flags, xpf_acl_deny) && __xml_purge_attributes(target) == FALSE) {
 754         crm_trace("No access to the entire document for %s", user);
 755         return TRUE;
 756     }
 757 
 758     if(doc->acls) {
 759         g_list_free_full(doc->acls, __xml_acl_free);
 760         doc->acls = NULL;
 761 
 762     } else {
 763         crm_trace("Ordinary user '%s' cannot access the CIB without any defined ACLs", doc->user);
 764         free_xml(target);
 765         target = NULL;
 766     }
 767 
 768     if(target) {
 769         *result = target;
 770     }
 771 
 772     return TRUE;
 773 }
 774 
 775 static void
 776 __xml_acl_post_process(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 777 {
 778     xmlNode *cIter = __xml_first_child(xml);
 779     xml_private_t *p = xml->_private;
 780 
 781     if(is_set(p->flags, xpf_created)) {
 782         xmlAttr *xIter = NULL;
 783         char *path = xml_get_path(xml);
 784 
 785         /* Always allow new scaffolding (e.g. node with no attributes or only an
 786          * 'id'), except in the ACLs section
 787          */
 788 
 789         for (xIter = crm_first_attr(xml); xIter != NULL; xIter = xIter->next) {
 790             const char *prop_name = (const char *)xIter->name;
 791 
 792             if (strcmp(prop_name, XML_ATTR_ID) == 0 && strstr(path, "/"XML_CIB_TAG_ACLS"/") == NULL) {
 793                 /* Delay the acl check */
 794                 continue;
 795 
 796             } else if(__xml_acl_check(xml, NULL, xpf_acl_write)) {
 797                 crm_trace("Creation of %s=%s is allowed", crm_element_name(xml), ID(xml));
 798                 break;
 799 
 800             } else {
 801                 crm_trace("Cannot add new node %s at %s", crm_element_name(xml), path);
 802 
 803                 if(xml != xmlDocGetRootElement(xml->doc)) {
 804                     xmlUnlinkNode(xml);
 805                     xmlFreeNode(xml);
 806                 }
 807                 free(path);
 808                 return;
 809             }
 810         }
 811         free(path);
 812     }
 813 
 814     while (cIter != NULL) {
 815         xmlNode *child = cIter;
 816         cIter = __xml_next(cIter); /* In case it is free'd */
 817         __xml_acl_post_process(child);
 818     }
 819 }
 820 
 821 bool
 822 xml_acl_denied(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 823 {
 824     if(xml && xml->doc && xml->doc->_private){
 825         xml_private_t *p = xml->doc->_private;
 826 
 827         return is_set(p->flags, xpf_acl_denied);
 828     }
 829     return FALSE;
 830 }
 831 
 832 void
 833 xml_acl_disable(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 834 {
 835     if(xml_acl_enabled(xml)) {
 836         xml_private_t *p = xml->doc->_private;
 837 
 838         /* Catch anything that was created but shouldn't have been */
 839         __xml_acl_apply(xml);
 840         __xml_acl_post_process(xml);
 841         clear_bit(p->flags, xpf_acl_enabled);
 842     }
 843 }
 844 
 845 bool
 846 xml_acl_enabled(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 847 {
 848     if(xml && xml->doc && xml->doc->_private){
 849         xml_private_t *p = xml->doc->_private;
 850 
 851         return is_set(p->flags, xpf_acl_enabled);
 852     }
 853     return FALSE;
 854 }
 855 
 856 void
 857 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) 
     /* [previous][next][first][last][top][bottom][index][help] */
 858 {
 859     xml_accept_changes(xml);
 860     crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
 861     set_doc_flag(xml, xpf_tracking);
 862     if(enforce_acls) {
 863         if(acl_source == NULL) {
 864             acl_source = xml;
 865         }
 866         set_doc_flag(xml, xpf_acl_enabled);
 867         __xml_acl_unpack(acl_source, xml, user);
 868         __xml_acl_apply(xml);
 869     }
 870 }
 871 
 872 bool xml_tracking_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 873 {
 874     if(xml == NULL) {
 875         return FALSE;
 876 
 877     } else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
 878         return TRUE;
 879     }
 880     return FALSE;
 881 }
 882 
 883 bool xml_document_dirty(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 884 {
 885     if(xml != NULL && xml->doc && xml->doc->_private) {
 886         xml_private_t *doc = xml->doc->_private;
 887 
 888         return is_set(doc->flags, xpf_dirty);
 889     }
 890     return FALSE;
 891 }
 892 
 893 /*
 894 <diff format="2.0">
 895   <version>
 896     <source admin_epoch="1" epoch="2" num_updates="3"/>
 897     <target admin_epoch="1" epoch="3" num_updates="0"/>
 898   </version>
 899   <change operation="add" xpath="/cib/configuration/nodes">
 900     <node id="node2" uname="node2" description="foo"/>
 901   </change>
 902   <change operation="add" xpath="/cib/configuration/nodes/node[node2]">
 903     <instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
 904       <nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
 905     </instance_attributes>
 906   </change>
 907   <change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
 908     <change-list>
 909       <change-attr operation="set" name="type" value="member"/>
 910       <change-attr operation="unset" name="description"/>
 911     </change-list>
 912     <change-result>
 913       <node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
 914     </change-result>
 915   </change>
 916   <change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
 917   <change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
 918     <change-list>
 919       <change-attr operation="set" name="description" value="some grabage here"/>
 920     </change-list>
 921     <change-result>
 922       <group id="g1" description="some grabage here"/><!-- NOTE: not recursive -->
 923     </change-result>
 924   </change>
 925   <change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
 926     <change-list>
 927       <change-attr operation="set" name="oper" value="member"/>
 928       <change-attr operation="set" name="operation_key" value="Fence_start_0"/>
 929       <change-attr operation="set" name="operation" value="start"/>
 930       <change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
 931       <change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
 932       <change-attr operation="set" name="call-id" value="2"/>
 933       <change-attr operation="set" name="rc-code" value="0"/>
 934     </change-list>
 935     <change-result>
 936       <lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate"  transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
 937     </change-result>
 938   </change>
 939 </diff>
 940  */
 941 static int __xml_offset(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 942 {
 943     int position = 0;
 944     xmlNode *cIter = NULL;
 945 
 946     for(cIter = xml; cIter->prev; cIter = cIter->prev) {
 947         xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
 948 
 949         if(is_not_set(p->flags, xpf_skip)) {
 950             position++;
 951         }
 952     }
 953 
 954     return position;
 955 }
 956 
 957 static int __xml_offset_no_deletions(xmlNode *xml) 
     /* [previous][next][first][last][top][bottom][index][help] */
 958 {
 959     int position = 0;
 960     xmlNode *cIter = NULL;
 961 
 962     for(cIter = xml; cIter->prev; cIter = cIter->prev) {
 963         xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
 964 
 965         if(is_not_set(p->flags, xpf_deleted)) {
 966             position++;
 967         }
 968     }
 969 
 970     return position;
 971 }
 972 
 973 static void
 974 __xml_build_changes(xmlNode * xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
 975 {
 976     xmlNode *cIter = NULL;
 977     xmlAttr *pIter = NULL;
 978     xmlNode *change = NULL;
 979     xml_private_t *p = xml->_private;
 980 
 981     if(patchset && is_set(p->flags, xpf_created)) {
 982         int offset = 0;
 983         char buffer[XML_BUFFER_SIZE];
 984 
 985         if(__get_prefix(NULL, xml->parent, buffer, offset) > 0) {
 986             int position = __xml_offset_no_deletions(xml);
 987 
 988             change = create_xml_node(patchset, XML_DIFF_CHANGE);
 989 
 990             crm_xml_add(change, XML_DIFF_OP, "create");
 991             crm_xml_add(change, XML_DIFF_PATH, buffer);
 992             crm_xml_add_int(change, XML_DIFF_POSITION, position);
 993             add_node_copy(change, xml);
 994         }
 995 
 996         return;
 997     }
 998 
 999     for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
1000         xmlNode *attr = NULL;
1001 
1002         p = pIter->_private;
1003         if(is_not_set(p->flags, xpf_deleted) && is_not_set(p->flags, xpf_dirty)) {
1004             continue;
1005         }
1006 
1007         if(change == NULL) {
1008             int offset = 0;
1009             char buffer[XML_BUFFER_SIZE];
1010 
1011             if(__get_prefix(NULL, xml, buffer, offset) > 0) {
1012                 change = create_xml_node(patchset, XML_DIFF_CHANGE);
1013 
1014                 crm_xml_add(change, XML_DIFF_OP, "modify");
1015                 crm_xml_add(change, XML_DIFF_PATH, buffer);
1016 
1017                 change = create_xml_node(change, XML_DIFF_LIST);
1018             }
1019         }
1020 
1021         attr = create_xml_node(change, XML_DIFF_ATTR);
1022 
1023         crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
1024         if(p->flags & xpf_deleted) {
1025             crm_xml_add(attr, XML_DIFF_OP, "unset");
1026 
1027         } else {
1028             const char *value = crm_element_value(xml, (const char *)pIter->name);
1029 
1030             crm_xml_add(attr, XML_DIFF_OP, "set");
1031             crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
1032         }
1033     }
1034 
1035     if(change) {
1036         xmlNode *result = NULL;
1037 
1038         change = create_xml_node(change->parent, XML_DIFF_RESULT);
1039         result = create_xml_node(change, (const char *)xml->name);
1040 
1041         for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
1042             const char *value = crm_element_value(xml, (const char *)pIter->name);
1043 
1044             p = pIter->_private;
1045             if (is_not_set(p->flags, xpf_deleted)) {
1046                 crm_xml_add(result, (const char *)pIter->name, value);
1047             }
1048         }
1049     }
1050 
1051     for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
1052         __xml_build_changes(cIter, patchset);
1053     }
1054 
1055     p = xml->_private;
1056     if(patchset && is_set(p->flags, xpf_moved)) {
1057         int offset = 0;
1058         char buffer[XML_BUFFER_SIZE];
1059 
1060         crm_trace("%s.%s moved to position %d", xml->name, ID(xml), __xml_offset(xml));
1061         if(__get_prefix(NULL, xml, buffer, offset) > 0) {
1062             change = create_xml_node(patchset, XML_DIFF_CHANGE);
1063 
1064             crm_xml_add(change, XML_DIFF_OP, "move");
1065             crm_xml_add(change, XML_DIFF_PATH, buffer);
1066             crm_xml_add_int(change, XML_DIFF_POSITION, __xml_offset_no_deletions(xml));
1067         }
1068     }
1069 }
1070 
1071 static void
1072 __xml_accept_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1073 {
1074     xmlNode *cIter = NULL;
1075     xmlAttr *pIter = NULL;
1076     xml_private_t *p = xml->_private;
1077 
1078     p->flags = xpf_none;
1079     pIter = crm_first_attr(xml);
1080 
1081     while (pIter != NULL) {
1082         const xmlChar *name = pIter->name;
1083 
1084         p = pIter->_private;
1085         pIter = pIter->next;
1086 
1087         if(p->flags & xpf_deleted) {
1088             xml_remove_prop(xml, (const char *)name);
1089 
1090         } else {
1091             p->flags = xpf_none;
1092         }
1093     }
1094 
1095     for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
1096         __xml_accept_changes(cIter);
1097     }
1098 }
1099 
1100 static bool
1101 is_config_change(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1102 {
1103     GListPtr gIter = NULL;
1104     xml_private_t *p = NULL;
1105     xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
1106 
1107     if(config) {
1108         p = config->_private;
1109     }
1110     if(p && is_set(p->flags, xpf_dirty)) {
1111         return TRUE;
1112     }
1113 
1114     if(xml->doc && xml->doc->_private) {
1115         p = xml->doc->_private;
1116         for(gIter = p->deleted_objs; gIter; gIter = gIter->next) {
1117             xml_deleted_obj_t *deleted_obj = gIter->data;
1118 
1119             if(strstr(deleted_obj->path, "/"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION) != NULL) {
1120                 return TRUE;
1121             }
1122         }
1123     }
1124 
1125     return FALSE;
1126 }
1127 
1128 static void
1129 xml_repair_v1_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed)
     /* [previous][next][first][last][top][bottom][index][help] */
1130 {
1131     int lpc = 0;
1132     xmlNode *cib = NULL;
1133     xmlNode *diff_child = NULL;
1134 
1135     const char *tag = NULL;
1136 
1137     const char *vfields[] = {
1138         XML_ATTR_GENERATION_ADMIN,
1139         XML_ATTR_GENERATION,
1140         XML_ATTR_NUMUPDATES,
1141     };
1142 
1143     if (local_diff == NULL) {
1144         crm_trace("Nothing to do");
1145         return;
1146     }
1147 
1148     tag = "diff-removed";
1149     diff_child = find_xml_node(local_diff, tag, FALSE);
1150     if (diff_child == NULL) {
1151         diff_child = create_xml_node(local_diff, tag);
1152     }
1153 
1154     tag = XML_TAG_CIB;
1155     cib = find_xml_node(diff_child, tag, FALSE);
1156     if (cib == NULL) {
1157         cib = create_xml_node(diff_child, tag);
1158     }
1159 
1160     for(lpc = 0; last && lpc < DIMOF(vfields); lpc++){
1161         const char *value = crm_element_value(last, vfields[lpc]);
1162 
1163         crm_xml_add(diff_child, vfields[lpc], value);
1164         if(changed || lpc == 2) {
1165             crm_xml_add(cib, vfields[lpc], value);
1166         }
1167     }
1168 
1169     tag = "diff-added";
1170     diff_child = find_xml_node(local_diff, tag, FALSE);
1171     if (diff_child == NULL) {
1172         diff_child = create_xml_node(local_diff, tag);
1173     }
1174 
1175     tag = XML_TAG_CIB;
1176     cib = find_xml_node(diff_child, tag, FALSE);
1177     if (cib == NULL) {
1178         cib = create_xml_node(diff_child, tag);
1179     }
1180 
1181     for(lpc = 0; next && lpc < DIMOF(vfields); lpc++){
1182         const char *value = crm_element_value(next, vfields[lpc]);
1183 
1184         crm_xml_add(diff_child, vfields[lpc], value);
1185     }
1186 
1187     if (next) {
1188         xmlAttrPtr xIter = NULL;
1189 
1190         for (xIter = next->properties; xIter; xIter = xIter->next) {
1191             const char *p_name = (const char *)xIter->name;
1192             const char *p_value = crm_element_value(next, p_name);
1193 
1194             xmlSetProp(cib, (const xmlChar *)p_name, (const xmlChar *)p_value);
1195         }
1196     }
1197 
1198     crm_log_xml_explicit(local_diff, "Repaired-diff");
1199 }
1200 
1201 static xmlNode *
1202 xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress)
     /* [previous][next][first][last][top][bottom][index][help] */
1203 {
1204     xmlNode *patchset = diff_xml_object(source, target, suppress);
1205 
1206     if(patchset) {
1207         CRM_LOG_ASSERT(xml_document_dirty(target));
1208         xml_repair_v1_diff(source, target, patchset, config);
1209         crm_xml_add(patchset, "format", "1");
1210     }
1211     return patchset;
1212 }
1213 
1214 static xmlNode *
1215 xml_create_patchset_v2(xmlNode *source, xmlNode *target)
     /* [previous][next][first][last][top][bottom][index][help] */
1216 {
1217     int lpc = 0;
1218     GListPtr gIter = NULL;
1219     xml_private_t *doc = NULL;
1220 
1221     xmlNode *v = NULL;
1222     xmlNode *version = NULL;
1223     xmlNode *patchset = NULL;
1224     const char *vfields[] = {
1225         XML_ATTR_GENERATION_ADMIN,
1226         XML_ATTR_GENERATION,
1227         XML_ATTR_NUMUPDATES,
1228     };
1229 
1230     CRM_ASSERT(target);
1231     if(xml_document_dirty(target) == FALSE) {
1232         return NULL;
1233     }
1234 
1235     CRM_ASSERT(target->doc);
1236     doc = target->doc->_private;
1237 
1238     patchset = create_xml_node(NULL, XML_TAG_DIFF);
1239     crm_xml_add_int(patchset, "format", 2);
1240 
1241     version = create_xml_node(patchset, XML_DIFF_VERSION);
1242 
1243     v = create_xml_node(version, XML_DIFF_VSOURCE);
1244     for(lpc = 0; lpc < DIMOF(vfields); lpc++){
1245         const char *value = crm_element_value(source, vfields[lpc]);
1246 
1247         if(value == NULL) {
1248             value = "1";
1249         }
1250         crm_xml_add(v, vfields[lpc], value);
1251     }
1252 
1253     v = create_xml_node(version, XML_DIFF_VTARGET);
1254     for(lpc = 0; lpc < DIMOF(vfields); lpc++){
1255         const char *value = crm_element_value(target, vfields[lpc]);
1256 
1257         if(value == NULL) {
1258             value = "1";
1259         }
1260         crm_xml_add(v, vfields[lpc], value);
1261     }
1262 
1263     for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
1264         xml_deleted_obj_t *deleted_obj = gIter->data;
1265         xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
1266 
1267         crm_xml_add(change, XML_DIFF_OP, "delete");
1268         crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
1269         if (deleted_obj->position >= 0) {
1270             crm_xml_add_int(change, XML_DIFF_POSITION, deleted_obj->position);
1271         }
1272     }
1273 
1274     __xml_build_changes(target, patchset);
1275     return patchset;
1276 }
1277 
1278 static gboolean patch_legacy_mode(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1279 {
1280     static gboolean init = TRUE;
1281     static gboolean legacy = FALSE;
1282 
1283     if(init) {
1284         init = FALSE;
1285         legacy = daemon_option_enabled("cib", "legacy");
1286         if(legacy) {
1287             crm_notice("Enabled legacy mode");
1288         }
1289     }
1290     return legacy;
1291 }
1292 
1293 xmlNode *
1294 xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
     /* [previous][next][first][last][top][bottom][index][help] */
1295 {
1296     int counter = 0;
1297     bool config = FALSE;
1298     xmlNode *patch = NULL;
1299     const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
1300 
1301     xml_acl_disable(target);
1302     if(xml_document_dirty(target) == FALSE) {
1303         crm_trace("No change %d", format);
1304         return NULL; /* No change */
1305     }
1306 
1307     config = is_config_change(target);
1308     if(config_changed) {
1309         *config_changed = config;
1310     }
1311 
1312     if(manage_version && config) {
1313         crm_trace("Config changed %d", format);
1314         crm_xml_add(target, XML_ATTR_NUMUPDATES, "0");
1315 
1316         crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
1317         crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
1318 
1319     } else if(manage_version) {
1320         crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
1321         crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, XML_ATTR_NUMUPDATES));
1322         crm_xml_add_int(target, XML_ATTR_NUMUPDATES, counter+1);
1323     }
1324 
1325     if(format == 0) {
1326         if(patch_legacy_mode()) {
1327             format = 1;
1328 
1329         } else if(compare_version("3.0.8", version) < 0) {
1330             format = 2;
1331 
1332         } else {
1333             format = 1;
1334         }
1335         crm_trace("Using patch format %d for version: %s", format, version);
1336     }
1337 
1338     switch(format) {
1339         case 1:
1340             patch = xml_create_patchset_v1(source, target, config, FALSE);
1341             break;
1342         case 2:
1343             patch = xml_create_patchset_v2(source, target);
1344             break;
1345         default:
1346             crm_err("Unknown patch format: %d", format);
1347             return NULL;
1348     }
1349 
1350     return patch;
1351 }
1352 
1353 void
1354 patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
     /* [previous][next][first][last][top][bottom][index][help] */
1355 {
1356     int format = 1;
1357     const char *version = NULL;
1358     char *digest = NULL;
1359 
1360     if (patch == NULL || source == NULL || target == NULL) {
1361         return;
1362     }
1363 
1364     /* NOTE: We should always call xml_accept_changes() before calculating digest. */
1365     /* Otherwise, with an on-tracking dirty target, we could get a wrong digest. */
1366     CRM_LOG_ASSERT(xml_document_dirty(target) == FALSE);
1367 
1368     crm_element_value_int(patch, "format", &format);
1369     if (format > 1 && with_digest == FALSE) {
1370         return;
1371     }
1372 
1373     version = crm_element_value(source, XML_ATTR_CRM_VERSION);
1374     digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
1375 
1376     crm_xml_add(patch, XML_ATTR_DIGEST, digest);
1377     free(digest);
1378 
1379     return;
1380 }
1381 
1382 static void
1383 __xml_log_element(int log_level, const char *file, const char *function, int line,
1384                   const char *prefix, xmlNode * data, int depth, int options);
1385 
1386 void
1387 xml_log_patchset(uint8_t log_level, const char *function, xmlNode * patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
1388 {
1389     int format = 1;
1390     xmlNode *child = NULL;
1391     xmlNode *added = NULL;
1392     xmlNode *removed = NULL;
1393     gboolean is_first = TRUE;
1394 
1395     int add[] = { 0, 0, 0 };
1396     int del[] = { 0, 0, 0 };
1397 
1398     const char *fmt = NULL;
1399     const char *digest = NULL;
1400     int options = xml_log_option_formatted;
1401 
1402     static struct qb_log_callsite *patchset_cs = NULL;
1403 
1404     if (patchset_cs == NULL) {
1405         patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset", log_level, __LINE__, 0);
1406     }
1407 
1408     if (patchset == NULL) {
1409         crm_trace("Empty patch");
1410         return;
1411 
1412     } else if (log_level == 0) {
1413         /* Log to stdout */
1414     } else if (crm_is_callsite_active(patchset_cs, log_level, 0) == FALSE) {
1415         return;
1416     }
1417 
1418     xml_patch_versions(patchset, add, del);
1419     fmt = crm_element_value(patchset, "format");
1420     digest = crm_element_value(patchset, XML_ATTR_DIGEST);
1421 
1422     if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
1423         do_crm_log_alias(log_level, __FILE__, function, __LINE__,
1424                          "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
1425         do_crm_log_alias(log_level, __FILE__, function, __LINE__,
1426                          "Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
1427 
1428     } else if (patchset != NULL && (add[0] || add[1] || add[2])) {
1429         do_crm_log_alias(log_level, __FILE__, function, __LINE__, 
1430                          "%s: Local-only Change: %d.%d.%d", function ? function : "",
1431                          add[0], add[1], add[2]);
1432     }
1433 
1434     crm_element_value_int(patchset, "format", &format);
1435     if(format == 2) {
1436         xmlNode *change = NULL;
1437 
1438         for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
1439             const char *op = crm_element_value(change, XML_DIFF_OP);
1440             const char *xpath = crm_element_value(change, XML_DIFF_PATH);
1441 
1442             if(op == NULL) {
1443             } else if(strcmp(op, "create") == 0) {
1444                 int lpc = 0, max = 0;
1445                 char *prefix = crm_strdup_printf("++ %s: ", xpath);
1446 
1447                 max = strlen(prefix);
1448                 __xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
1449                                   0, xml_log_option_formatted|xml_log_option_open);
1450 
1451                 for(lpc = 2; lpc < max; lpc++) {
1452                     prefix[lpc] = ' ';
1453                 }
1454 
1455                 __xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
1456                                   0, xml_log_option_formatted|xml_log_option_close|xml_log_option_children);
1457                 free(prefix);
1458 
1459             } else if(strcmp(op, "move") == 0) {
1460                 do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+~ %s moved to offset %s", xpath, crm_element_value(change, XML_DIFF_POSITION));
1461 
1462             } else if(strcmp(op, "modify") == 0) {
1463                 xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
1464                 char buffer_set[XML_BUFFER_SIZE];
1465                 char buffer_unset[XML_BUFFER_SIZE];
1466                 int o_set = 0;
1467                 int o_unset = 0;
1468 
1469                 buffer_set[0] = 0;
1470                 buffer_unset[0] = 0;
1471                 for (child = __xml_first_child(clist); child != NULL; child = __xml_next(child)) {
1472                     const char *name = crm_element_value(child, "name");
1473 
1474                     op = crm_element_value(child, XML_DIFF_OP);
1475                     if(op == NULL) {
1476                     } else if(strcmp(op, "set") == 0) {
1477                         const char *value = crm_element_value(child, "value");
1478 
1479                         if(o_set > 0) {
1480                             o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, ", ");
1481                         }
1482                         o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, "@%s=%s", name, value);
1483 
1484                     } else if(strcmp(op, "unset") == 0) {
1485                         if(o_unset > 0) {
1486                             o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, ", ");
1487                         }
1488                         o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, "@%s", name);
1489                     }
1490                 }
1491                 if(o_set) {
1492                     do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+  %s:  %s", xpath, buffer_set);
1493                 }
1494                 if(o_unset) {
1495                     do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s:  %s", xpath, buffer_unset);
1496                 }
1497 
1498             } else if(strcmp(op, "delete") == 0) {
1499                 int position = -1;
1500 
1501                 crm_element_value_int(change, XML_DIFF_POSITION, &position);
1502                 if (position >= 0) {
1503                     do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)", xpath, position);
1504 
1505                 } else {
1506                     do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", xpath);
1507                 }
1508             }
1509         }
1510         return;
1511     }
1512 
1513     if (log_level < LOG_DEBUG || function == NULL) {
1514         options |= xml_log_option_diff_short;
1515     }
1516 
1517     removed = find_xml_node(patchset, "diff-removed", FALSE);
1518     for (child = __xml_first_child(removed); child != NULL; child = __xml_next(child)) {
1519         log_data_element(log_level, __FILE__, function, __LINE__, "- ", child, 0,
1520                          options | xml_log_option_diff_minus);
1521         if (is_first) {
1522             is_first = FALSE;
1523         } else {
1524             do_crm_log_alias(log_level, __FILE__, function, __LINE__, " --- ");
1525         }
1526     }
1527 
1528     is_first = TRUE;
1529     added = find_xml_node(patchset, "diff-added", FALSE);
1530     for (child = __xml_first_child(added); child != NULL; child = __xml_next(child)) {
1531         log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child, 0,
1532                          options | xml_log_option_diff_plus);
1533         if (is_first) {
1534             is_first = FALSE;
1535         } else {
1536             do_crm_log_alias(log_level, __FILE__, function, __LINE__, " +++ ");
1537         }
1538     }
1539 }
1540 
1541 void
1542 xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1543 {
1544     GListPtr gIter = NULL;
1545     xml_private_t *doc = NULL;
1546 
1547     CRM_ASSERT(xml);
1548     CRM_ASSERT(xml->doc);
1549 
1550     doc = xml->doc->_private;
1551     if(is_not_set(doc->flags, xpf_dirty)) {
1552         return;
1553     }
1554 
1555     for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) {
1556         xml_deleted_obj_t *deleted_obj = gIter->data;
1557 
1558         if (deleted_obj->position >= 0) {
1559             do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)",
1560                              deleted_obj->path, deleted_obj->position);
1561 
1562         } else {
1563             do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s",
1564                              deleted_obj->path);
1565         }
1566     }
1567 
1568     log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
1569                      xml_log_option_formatted|xml_log_option_dirty_add);
1570 }
1571 
1572 void
1573 xml_accept_changes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
1574 {
1575     xmlNode *top = NULL;
1576     xml_private_t *doc = NULL;
1577 
1578     if(xml == NULL) {
1579         return;
1580     }
1581 
1582     crm_trace("Accepting changes to %p", xml);
1583     doc = xml->doc->_private;
1584     top = xmlDocGetRootElement(xml->doc);
1585 
1586     __xml_private_clean(xml->doc->_private);
1587 
1588     if(is_not_set(doc->flags, xpf_dirty)) {
1589         doc->flags = xpf_none;
1590         return;
1591     }
1592 
1593     doc->flags = xpf_none;
1594     __xml_accept_changes(top);
1595 }
1596 
1597 static xmlNode *
1598 find_element(xmlNode *haystack, xmlNode *needle, gboolean exact)
     /* [previous][next][first][last][top][bottom][index][help] */
1599 {
1600     CRM_CHECK(needle != NULL, return NULL);
1601     return (needle->type == XML_COMMENT_NODE)?
1602            find_xml_comment(haystack, needle, exact)
1603            : find_entity(haystack, crm_element_name(needle), ID(needle));
1604 }
1605 
1606 /* Simplified version for applying v1-style XML patches */
1607 static void
1608 __subtract_xml_object(xmlNode * target, xmlNode * patch)
     /* [previous][next][first][last][top][bottom][index][help] */
1609 {
1610     xmlNode *patch_child = NULL;
1611     xmlNode *cIter = NULL;
1612     xmlAttrPtr xIter = NULL;
1613 
1614     char *id = NULL;
1615     const char *name = NULL;
1616     const char *value = NULL;
1617 
1618     if (target == NULL || patch == NULL) {
1619         return;
1620     }
1621 
1622     if (target->type == XML_COMMENT_NODE) {
1623         gboolean dummy;
1624 
1625         subtract_xml_comment(target->parent, target, patch, &dummy);
1626     }
1627 
1628     name = crm_element_name(target);
1629     CRM_CHECK(name != NULL, return);
1630     CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
1631     CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
1632 
1633     /* check for XML_DIFF_MARKER in a child */
1634     id = crm_element_value_copy(target, XML_ATTR_ID);
1635     value = crm_element_value(patch, XML_DIFF_MARKER);
1636     if (value != NULL && strcmp(value, "removed:top") == 0) {
1637         crm_trace("We are the root of the deletion: %s.id=%s", name, id);
1638         free_xml(target);
1639         free(id);
1640         return;
1641     }
1642 
1643     for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
1644         const char *p_name = (const char *)xIter->name;
1645 
1646         /* Removing and then restoring the id field would change the ordering of properties */
1647         if (safe_str_neq(p_name, XML_ATTR_ID)) {
1648             xml_remove_prop(target, p_name);
1649         }
1650     }
1651 
1652     /* changes to child objects */
1653     cIter = __xml_first_child(target);
1654     while (cIter) {
1655         xmlNode *target_child = cIter;
1656 
1657         cIter = __xml_next(cIter);
1658         patch_child = find_element(patch, target_child, FALSE);
1659         __subtract_xml_object(target_child, patch_child);
1660     }
1661     free(id);
1662 }
1663 
1664 static void
1665 __add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * patch)
     /* [previous][next][first][last][top][bottom][index][help] */
1666 {
1667     xmlNode *patch_child = NULL;
1668     xmlNode *target_child = NULL;
1669     xmlAttrPtr xIter = NULL;
1670 
1671     const char *id = NULL;
1672     const char *name = NULL;
1673     const char *value = NULL;
1674 
1675     if (patch == NULL) {
1676         return;
1677     } else if (parent == NULL && target == NULL) {
1678         return;
1679     }
1680 
1681     /* check for XML_DIFF_MARKER in a child */
1682     value = crm_element_value(patch, XML_DIFF_MARKER);
1683     if (target == NULL
1684         && value != NULL
1685         && strcmp(value, "added:top") == 0) {
1686         id = ID(patch);
1687         name = crm_element_name(patch);
1688         crm_trace("We are the root of the addition: %s.id=%s", name, id);
1689         add_node_copy(parent, patch);
1690         return;
1691 
1692     } else if(target == NULL) {
1693         id = ID(patch);
1694         name = crm_element_name(patch);
1695         crm_err("Could not locate: %s.id=%s", name, id);
1696         return;
1697     }
1698 
1699     if (target->type == XML_COMMENT_NODE) {
1700         add_xml_comment(parent, target, patch);
1701     }
1702 
1703     name = crm_element_name(target);
1704     CRM_CHECK(name != NULL, return);
1705     CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
1706     CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
1707 
1708     for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
1709         const char *p_name = (const char *)xIter->name;
1710         const char *p_value = crm_element_value(patch, p_name);
1711 
1712         xml_remove_prop(target, p_name); /* Preserve the patch order */
1713         crm_xml_add(target, p_name, p_value);
1714     }
1715 
1716     /* changes to child objects */
1717     for (patch_child = __xml_first_child(patch); patch_child != NULL;
1718          patch_child = __xml_next(patch_child)) {
1719 
1720         target_child = find_element(target, patch_child, FALSE);
1721         __add_xml_object(target, target_child, patch_child);
1722     }
1723 }
1724 
1725 /*!
1726  * \internal
1727  * \brief Find additions or removals in a patch set
1728  *
1729  * \param[in]     patchset   XML of patch
1730  * \param[in]     format     Patch version
1731  * \param[in]     added      TRUE if looking for additions, FALSE if removals
1732  * \param[in,out] patch_node Will be set to node if found
1733  *
1734  * \return TRUE if format is valid, FALSE if invalid
1735  */
1736 static bool
1737 find_patch_xml_node(xmlNode *patchset, int format, bool added,
     /* [previous][next][first][last][top][bottom][index][help] */
1738                     xmlNode **patch_node)
1739 {
1740     xmlNode *cib_node;
1741     const char *label;
1742 
1743     switch(format) {
1744         case 1:
1745             label = added? "diff-added" : "diff-removed";
1746             *patch_node = find_xml_node(patchset, label, FALSE);
1747             cib_node = find_xml_node(*patch_node, "cib", FALSE);
1748             if (cib_node != NULL) {
1749                 *patch_node = cib_node;
1750             }
1751             break;
1752         case 2:
1753             label = added? "target" : "source";
1754             *patch_node = find_xml_node(patchset, "version", FALSE);
1755             *patch_node = find_xml_node(*patch_node, label, FALSE);
1756             break;
1757         default:
1758             crm_warn("Unknown patch format: %d", format);
1759             *patch_node = NULL;
1760             return FALSE;
1761     }
1762     return TRUE;
1763 }
1764 
1765 bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
     /* [previous][next][first][last][top][bottom][index][help] */
1766 {
1767     int lpc = 0;
1768     int format = 1;
1769     xmlNode *tmp = NULL;
1770 
1771     const char *vfields[] = {
1772         XML_ATTR_GENERATION_ADMIN,
1773         XML_ATTR_GENERATION,
1774         XML_ATTR_NUMUPDATES,
1775     };
1776 
1777 
1778     crm_element_value_int(patchset, "format", &format);
1779 
1780     /* Process removals */
1781     if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
1782         return -EINVAL;
1783     }
1784     if (tmp) {
1785         for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1786             crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
1787             crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
1788         }
1789     }
1790 
1791     /* Process additions */
1792     if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
1793         return -EINVAL;
1794     }
1795     if (tmp) {
1796         for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1797             crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
1798             crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
1799         }
1800     }
1801 
1802     return pcmk_ok;
1803 }
1804 
1805 static int
1806 xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format) 
     /* [previous][next][first][last][top][bottom][index][help] */
1807 {
1808     int lpc = 0;
1809     bool changed = FALSE;
1810 
1811     int this[] = { 0, 0, 0 };
1812     int add[] = { 0, 0, 0 };
1813     int del[] = { 0, 0, 0 };
1814 
1815     const char *vfields[] = {
1816         XML_ATTR_GENERATION_ADMIN,
1817         XML_ATTR_GENERATION,
1818         XML_ATTR_NUMUPDATES,
1819     };
1820 
1821     for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1822         crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
1823         crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
1824         if (this[lpc] < 0) {
1825             this[lpc] = 0;
1826         }
1827     }
1828 
1829     /* Set some defaults in case nothing is present */
1830     add[0] = this[0];
1831     add[1] = this[1];
1832     add[2] = this[2] + 1;
1833     for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1834         del[lpc] = this[lpc];
1835     }
1836 
1837     xml_patch_versions(patchset, add, del);
1838 
1839     for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1840         if(this[lpc] < del[lpc]) {
1841             crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc],
1842                       this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]);
1843             return -pcmk_err_diff_resync;
1844 
1845         } else if(this[lpc] > del[lpc]) {
1846             crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc],
1847                      this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset);
1848             crm_log_xml_info(patchset, "OldPatch");
1849             return -pcmk_err_old_data;
1850         }
1851     }
1852 
1853     for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
1854         if(add[lpc] > del[lpc]) {
1855             changed = TRUE;
1856         }
1857     }
1858 
1859     if(changed == FALSE) {
1860         crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]);
1861         return -pcmk_err_old_data;
1862     }
1863 
1864     crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
1865              add[0], add[1], add[2], this[0], this[1], this[2]);
1866     return pcmk_ok;
1867 }
1868 
1869 static int
1870 xml_apply_patchset_v1(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
1871 {
1872     int rc = pcmk_ok;
1873     int root_nodes_seen = 0;
1874 
1875     xmlNode *child_diff = NULL;
1876     xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
1877     xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
1878     xmlNode *old = copy_xml(xml);
1879 
1880     crm_trace("Subtraction Phase");
1881     for (child_diff = __xml_first_child(removed); child_diff != NULL;
1882          child_diff = __xml_next(child_diff)) {
1883         CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
1884         if (root_nodes_seen == 0) {
1885             __subtract_xml_object(xml, child_diff);
1886         }
1887         root_nodes_seen++;
1888     }
1889 
1890     if (root_nodes_seen > 1) {
1891         crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
1892         rc = -ENOTUNIQ;
1893     }
1894 
1895     root_nodes_seen = 0;
1896     crm_trace("Addition Phase");
1897     if (rc == pcmk_ok) {
1898         xmlNode *child_diff = NULL;
1899 
1900         for (child_diff = __xml_first_child(added); child_diff != NULL;
1901              child_diff = __xml_next(child_diff)) {
1902             CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
1903             if (root_nodes_seen == 0) {
1904                 __add_xml_object(NULL, xml, child_diff);
1905             }
1906             root_nodes_seen++;
1907         }
1908     }
1909 
1910     if (root_nodes_seen > 1) {
1911         crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
1912         rc = -ENOTUNIQ;
1913     }
1914 
1915     purge_diff_markers(xml);       /* Purge prior to checking the digest */
1916 
1917     free_xml(old);
1918     return rc;
1919 }
1920 
1921 static xmlNode *
1922 __first_xml_child_match(xmlNode *parent, const char *name, const char *id, int position)
     /* [previous][next][first][last][top][bottom][index][help] */
1923 {
1924     xmlNode *cIter = NULL;
1925 
1926     for (cIter = __xml_first_child(parent); cIter != NULL; cIter = __xml_next(cIter)) {
1927         if(strcmp((const char *)cIter->name, name) != 0) {
1928             continue;
1929         } else if(id) {
1930             const char *cid = ID(cIter);
1931             if(cid == NULL || strcmp(cid, id) != 0) {
1932                 continue;
1933             }
1934         }
1935 
1936         /* The "position" makes sense only for XML comments for now */
1937         if (cIter->type == XML_COMMENT_NODE
1938             && position >= 0
1939             && __xml_offset(cIter) != position) {
1940             continue;
1941         }
1942 
1943         return cIter;
1944     }
1945     return NULL;
1946 }
1947 
1948 static xmlNode *
1949 __xml_find_path(xmlNode *top, const char *key, int target_position)
     /* [previous][next][first][last][top][bottom][index][help] */
1950 {
1951     xmlNode *target = (xmlNode*)top->doc;
1952     char *id = malloc(XML_BUFFER_SIZE);
1953     char *tag = malloc(XML_BUFFER_SIZE);
1954     char *section = malloc(XML_BUFFER_SIZE);
1955     char *current = strdup(key);
1956     char *remainder = malloc(XML_BUFFER_SIZE);
1957     int rc = 0;
1958 
1959     while(current) {
1960         rc = sscanf (current, "/%[^/]%s", section, remainder);
1961         if(rc <= 0) {
1962             crm_trace("Done");
1963             break;
1964 
1965         } else if(rc > 2) {
1966             crm_trace("Aborting on %s", current);
1967             target = NULL;
1968             break;
1969 
1970         } else if(tag && section) {
1971             int f = sscanf (section, "%[^[][@id='%[^']", tag, id);
1972             int current_position = -1;
1973 
1974             /* The "target_position" is for the target tag */
1975             if (rc == 1 && target_position >= 0) {
1976                 current_position = target_position;
1977             }
1978 
1979             switch(f) {
1980                 case 1:
1981                     target = __first_xml_child_match(target, tag, NULL, current_position);
1982                     break;
1983                 case 2:
1984                     target = __first_xml_child_match(target, tag, id, current_position);
1985                     break;
1986                 default:
1987                     crm_trace("Aborting on %s", section);
1988                     target = NULL;
1989                     break;
1990             }
1991 
1992             if(rc == 1 || target == NULL) {
1993                 crm_trace("Done");
1994                 break;
1995 
1996             } else {
1997                 char *tmp = current;
1998                 current = remainder;
1999                 remainder = tmp;
2000             }
2001         }
2002     }
2003 
2004     if(target) {
2005         char *path = (char *)xmlGetNodePath(target);
2006 
2007         crm_trace("Found %s for %s", path, key);
2008         free(path);
2009     } else {
2010         crm_debug("No match for %s", key);
2011     }
2012 
2013     free(remainder);
2014     free(current);
2015     free(section);
2016     free(tag);
2017     free(id);
2018     return target;
2019 }
2020 
2021 static int
2022 xml_apply_patchset_v2(xmlNode *xml, xmlNode *patchset)
     /* [previous][next][first][last][top][bottom][index][help] */
2023 {
2024     int rc = pcmk_ok;
2025     xmlNode *change = NULL;
2026     for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
2027         xmlNode *match = NULL;
2028         const char *op = crm_element_value(change, XML_DIFF_OP);
2029         const char *xpath = crm_element_value(change, XML_DIFF_PATH);
2030         int position = -1;
2031 
2032         crm_trace("Processing %s %s", change->name, op);
2033         if(op == NULL) {
2034             continue;
2035         }
2036 
2037         if(strcmp(op, "delete") == 0) {
2038             crm_element_value_int(change, XML_DIFF_POSITION, &position);
2039         }
2040 #if 0
2041         match = get_xpath_object(xpath, xml, LOG_TRACE);
2042 #else
2043         match = __xml_find_path(xml, xpath, position);
2044 #endif
2045         crm_trace("Performing %s on %s with %p", op, xpath, match);
2046 
2047         if(match == NULL && strcmp(op, "delete") == 0) {
2048             crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
2049             continue;
2050 
2051         } else if(match == NULL) {
2052             crm_err("No %s match for %s in %p", op, xpath, xml->doc);
2053             rc = -pcmk_err_diff_failed;
2054             continue;
2055 
2056         } else if(strcmp(op, "create") == 0) {
2057             int position = 0;
2058             xmlNode *child = NULL;
2059             xmlNode *match_child = NULL;
2060 
2061             match_child = match->children;
2062             crm_element_value_int(change, XML_DIFF_POSITION, &position);
2063 
2064             while(match_child && position != __xml_offset(match_child)) {
2065                 match_child = match_child->next;
2066             }
2067 
2068             child = xmlDocCopyNode(change->children, match->doc, 1);
2069             if(match_child) {
2070                 crm_trace("Adding %s at position %d", child->name, position);
2071                 xmlAddPrevSibling(match_child, child);
2072 
2073             } else if(match->last) { /* Add to the end */
2074                 crm_trace("Adding %s at position %d (end)", child->name, position);
2075                 xmlAddNextSibling(match->last, child);
2076 
2077             } else {
2078                 crm_trace("Adding %s at position %d (first)", child->name, position);
2079                 CRM_LOG_ASSERT(position == 0);
2080                 xmlAddChild(match, child);
2081             }
2082             crm_node_created(child);
2083 
2084         } else if(strcmp(op, "move") == 0) {
2085             int position = 0;
2086 
2087             crm_element_value_int(change, XML_DIFF_POSITION, &position);
2088             if(position != __xml_offset(match)) {
2089                 xmlNode *match_child = NULL;
2090                 int p = position;
2091 
2092                 if(p > __xml_offset(match)) {
2093                     p++; /* Skip ourselves */
2094                 }
2095 
2096                 CRM_ASSERT(match->parent != NULL);
2097                 match_child = match->parent->children;
2098 
2099                 while(match_child && p != __xml_offset(match_child)) {
2100                     match_child = match_child->next;
2101                 }
2102 
2103                 crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
2104                          match->name, position, __xml_offset(match), match->prev,
2105                          match_child?"next":"last", match_child?match_child:match->parent->last);
2106 
2107                 if(match_child) {
2108                     xmlAddPrevSibling(match_child, match);
2109 
2110                 } else {
2111                     CRM_ASSERT(match->parent->last != NULL);
2112                     xmlAddNextSibling(match->parent->last, match);
2113                 }
2114 
2115             } else {
2116                 crm_trace("%s is already in position %d", match->name, position);
2117             }
2118 
2119             if(position != __xml_offset(match)) {
2120                 crm_err("Moved %s.%d to position %d instead of %d (%p)",
2121                         match->name, ID(match), __xml_offset(match), position, match->prev);
2122                 rc = -pcmk_err_diff_failed;
2123             }
2124 
2125         } else if(strcmp(op, "delete") == 0) {
2126             free_xml(match);
2127 
2128         } else if(strcmp(op, "modify") == 0) {
2129             xmlAttr *pIter = crm_first_attr(match);
2130             xmlNode *attrs = __xml_first_child(first_named_child(change, XML_DIFF_RESULT));
2131 
2132             if(attrs == NULL) {
2133                 rc = -ENOMSG;
2134                 continue;
2135             }
2136             while(pIter != NULL) {
2137                 const char *name = (const char *)pIter->name;
2138 
2139                 pIter = pIter->next;
2140                 xml_remove_prop(match, name);
2141             }
2142 
2143             for (pIter = crm_first_attr(attrs); pIter != NULL; pIter = pIter->next) {
2144                 const char *name = (const char *)pIter->name;
2145                 const char *value = crm_element_value(attrs, name);
2146 
2147                 crm_xml_add(match, name, value);
2148             }
2149 
2150         } else {
2151             crm_err("Unknown operation: %s", op);
2152         }
2153     }
2154     return rc;
2155 }
2156 
2157 int
2158 xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version) 
     /* [previous][next][first][last][top][bottom][index][help] */
2159 {
2160     int format = 1;
2161     int rc = pcmk_ok;
2162     xmlNode *old = NULL;
2163     const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
2164 
2165     if(patchset == NULL) {
2166         return rc;
2167     }
2168 
2169     xml_log_patchset(LOG_TRACE, __FUNCTION__, patchset);
2170 
2171     crm_element_value_int(patchset, "format", &format);
2172     if(check_version) {
2173         rc = xml_patch_version_check(xml, patchset, format);
2174         if(rc != pcmk_ok) {
2175             return rc;
2176         }
2177     }
2178 
2179     if(digest) {
2180         /* Make it available for logging if the result doesn't have the expected digest */
2181         old = copy_xml(xml);
2182     }
2183 
2184     if(rc == pcmk_ok) {
2185         switch(format) {
2186             case 1:
2187                 rc = xml_apply_patchset_v1(xml, patchset);
2188                 break;
2189             case 2:
2190                 rc = xml_apply_patchset_v2(xml, patchset);
2191                 break;
2192             default:
2193                 crm_err("Unknown patch format: %d", format);
2194                 rc = -EINVAL;
2195         }
2196     }
2197 
2198     if(rc == pcmk_ok && digest) {
2199         static struct qb_log_callsite *digest_cs = NULL;
2200 
2201         char *new_digest = NULL;
2202         char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
2203 
2204         if (digest_cs == NULL) {
2205             digest_cs =
2206                 qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
2207                                     crm_trace_nonlog);
2208         }
2209 
2210         new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
2211         if (safe_str_neq(new_digest, digest)) {
2212             crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest);
2213             rc = -pcmk_err_diff_failed;
2214 
2215             if (digest_cs && digest_cs->targets) {
2216                 save_xml_to_file(old,     "PatchDigest:input", NULL);
2217                 save_xml_to_file(xml,     "PatchDigest:result", NULL);
2218                 save_xml_to_file(patchset,"PatchDigest:diff", NULL);
2219 
2220             } else {
2221                 crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
2222             }
2223 
2224         } else {
2225             crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest);
2226         }
2227         free(new_digest);
2228         free(version);
2229     }
2230     free_xml(old);
2231     return rc;
2232 }
2233 
2234 xmlNode *
2235 find_xml_node(xmlNode * root, const char *search_path, gboolean must_find)
     /* [previous][next][first][last][top][bottom][index][help] */
2236 {
2237     xmlNode *a_child = NULL;
2238     const char *name = "NULL";
2239 
2240     if (root != NULL) {
2241         name = crm_element_name(root);
2242     }
2243 
2244     if (search_path == NULL) {
2245         crm_warn("Will never find <NULL>");
2246         return NULL;
2247     }
2248 
2249     for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
2250         if (strcmp((const char *)a_child->name, search_path) == 0) {
2251 /*              crm_trace("returning node (%s).", crm_element_name(a_child)); */
2252             return a_child;
2253         }
2254     }
2255 
2256     if (must_find) {
2257         crm_warn("Could not find %s in %s.", search_path, name);
2258     } else if (root != NULL) {
2259         crm_trace("Could not find %s in %s.", search_path, name);
2260     } else {
2261         crm_trace("Could not find %s in <NULL>.", search_path);
2262     }
2263 
2264     return NULL;
2265 }
2266 
2267 xmlNode *
2268 find_entity(xmlNode * parent, const char *node_name, const char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
2269 {
2270     xmlNode *a_child = NULL;
2271 
2272     for (a_child = __xml_first_child(parent); a_child != NULL; a_child = __xml_next(a_child)) {
2273         /* Uncertain if node_name == NULL check is strictly necessary here */
2274         if (node_name == NULL || strcmp((const char *)a_child->name, node_name) == 0) {
2275             const char *cid = ID(a_child);
2276             if (id == NULL || (cid != NULL && strcmp(id, cid) == 0)) {
2277                 return a_child;
2278             }
2279         }
2280     }
2281 
2282     crm_trace("node <%s id=%s> not found in %s.", node_name, id, crm_element_name(parent));
2283     return NULL;
2284 }
2285 
2286 void
2287 copy_in_properties(xmlNode * target, xmlNode * src)
     /* [previous][next][first][last][top][bottom][index][help] */
2288 {
2289     if (src == NULL) {
2290         crm_warn("No node to copy properties from");
2291 
2292     } else if (target == NULL) {
2293         crm_err("No node to copy properties into");
2294 
2295     } else {
2296         xmlAttrPtr pIter = NULL;
2297 
2298         for (pIter = crm_first_attr(src); pIter != NULL; pIter = pIter->next) {
2299             const char *p_name = (const char *)pIter->name;
2300             const char *p_value = crm_attr_value(pIter);
2301 
2302             expand_plus_plus(target, p_name, p_value);
2303         }
2304     }
2305 
2306     return;
2307 }
2308 
2309 void
2310 fix_plus_plus_recursive(xmlNode * target)
     /* [previous][next][first][last][top][bottom][index][help] */
2311 {
2312     /* TODO: Remove recursion and use xpath searches for value++ */
2313     xmlNode *child = NULL;
2314     xmlAttrPtr pIter = NULL;
2315 
2316     for (pIter = crm_first_attr(target); pIter != NULL; pIter = pIter->next) {
2317         const char *p_name = (const char *)pIter->name;
2318         const char *p_value = crm_attr_value(pIter);
2319 
2320         expand_plus_plus(target, p_name, p_value);
2321     }
2322     for (child = __xml_first_child(target); child != NULL; child = __xml_next(child)) {
2323         fix_plus_plus_recursive(child);
2324     }
2325 }
2326 
2327 void
2328 expand_plus_plus(xmlNode * target, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
2329 {
2330     int offset = 1;
2331     int name_len = 0;
2332     int int_value = 0;
2333     int value_len = 0;
2334 
2335     const char *old_value = NULL;
2336 
2337     if (value == NULL || name == NULL) {
2338         return;
2339     }
2340 
2341     old_value = crm_element_value(target, name);
2342 
2343     if (old_value == NULL) {
2344         /* if no previous value, set unexpanded */
2345         goto set_unexpanded;
2346 
2347     } else if (strstr(value, name) != value) {
2348         goto set_unexpanded;
2349     }
2350 
2351     name_len = strlen(name);
2352     value_len = strlen(value);
2353     if (value_len < (name_len + 2)
2354         || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
2355         goto set_unexpanded;
2356     }
2357 
2358     /* if we are expanding ourselves,
2359      * then no previous value was set and leave int_value as 0
2360      */
2361     if (old_value != value) {
2362         int_value = char2score(old_value);
2363     }
2364 
2365     if (value[name_len + 1] != '+') {
2366         const char *offset_s = value + (name_len + 2);
2367 
2368         offset = char2score(offset_s);
2369     }
2370     int_value += offset;
2371 
2372     if (int_value > INFINITY) {
2373         int_value = (int)INFINITY;
2374     }
2375 
2376     crm_xml_add_int(target, name, int_value);
2377     return;
2378 
2379   set_unexpanded:
2380     if (old_value == value) {
2381         /* the old value is already set, nothing to do */
2382         return;
2383     }
2384     crm_xml_add(target, name, value);
2385     return;
2386 }
2387 
2388 xmlDoc *
2389 getDocPtr(xmlNode * node)
     /* [previous][next][first][last][top][bottom][index][help] */
2390 {
2391     xmlDoc *doc = NULL;
2392 
2393     CRM_CHECK(node != NULL, return NULL);
2394 
2395     doc = node->doc;
2396     if (doc == NULL) {
2397         doc = xmlNewDoc((const xmlChar *)"1.0");
2398         xmlDocSetRootElement(doc, node);
2399         xmlSetTreeDoc(node, doc);
2400     }
2401     return doc;
2402 }
2403 
2404 xmlNode *
2405 add_node_copy(xmlNode * parent, xmlNode * src_node)
     /* [previous][next][first][last][top][bottom][index][help] */
2406 {
2407     xmlNode *child = NULL;
2408     xmlDoc *doc = getDocPtr(parent);
2409 
2410     CRM_CHECK(src_node != NULL, return NULL);
2411 
2412     child = xmlDocCopyNode(src_node, doc, 1);
2413     xmlAddChild(parent, child);
2414     crm_node_created(child);
2415     return child;
2416 }
2417 
2418 int
2419 add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
     /* [previous][next][first][last][top][bottom][index][help] */
2420 {
2421     add_node_copy(parent, child);
2422     free_xml(child);
2423     return 1;
2424 }
2425 
2426 static bool
2427 __xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode)
     /* [previous][next][first][last][top][bottom][index][help] */
2428 {
2429     CRM_ASSERT(xml);
2430     CRM_ASSERT(xml->doc);
2431     CRM_ASSERT(xml->doc->_private);
2432 
2433 #if ENABLE_ACL
2434     {
2435         if(TRACKING_CHANGES(xml) && xml_acl_enabled(xml)) {
2436             int offset = 0;
2437             xmlNode *parent = xml;
2438             char buffer[XML_BUFFER_SIZE];
2439             xml_private_t *docp = xml->doc->_private;
2440 
2441             if(docp->acls == NULL) {
2442                 crm_trace("Ordinary user %s cannot access the CIB without any defined ACLs", docp->user);
2443                 set_doc_flag(xml, xpf_acl_denied);
2444                 return FALSE;
2445             }
2446 
2447             offset = __get_prefix(NULL, xml, buffer, offset);
2448             if(name) {
2449                 offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[@%s]", name);
2450             }
2451             CRM_LOG_ASSERT(offset > 0);
2452 
2453             /* Walk the tree upwards looking for xml_acl_* flags
2454              * - Creating an attribute requires write permissions for the node
2455              * - Creating a child requires write permissions for the parent
2456              */
2457 
2458             if(name) {
2459                 xmlAttr *attr = xmlHasProp(xml, (const xmlChar *)name);
2460 
2461                 if(attr && mode == xpf_acl_create) {
2462                     mode = xpf_acl_write;
2463                 }
2464             }
2465 
2466             while(parent && parent->_private) {
2467                 xml_private_t *p = parent->_private;
2468                 if(__xml_acl_mode_test(p->flags, mode)) {
2469                     return TRUE;
2470 
2471                 } else if(is_set(p->flags, xpf_acl_deny)) {
2472                     crm_trace("%x access denied to %s: parent", mode, buffer);
2473                     set_doc_flag(xml, xpf_acl_denied);
2474                     return FALSE;
2475                 }
2476                 parent = parent->parent;
2477             }
2478 
2479             crm_trace("%x access denied to %s: default", mode, buffer);
2480             set_doc_flag(xml, xpf_acl_denied);
2481             return FALSE;
2482         }
2483     }
2484 #endif
2485 
2486     return TRUE;
2487 }
2488 
2489 const char *
2490 crm_xml_add(xmlNode * node, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
2491 {
2492     bool dirty = FALSE;
2493     xmlAttr *attr = NULL;
2494 
2495     CRM_CHECK(node != NULL, return NULL);
2496     CRM_CHECK(name != NULL, return NULL);
2497 
2498     if (value == NULL) {
2499         return NULL;
2500     }
2501 #if XML_PARANOIA_CHECKS
2502     {
2503         const char *old_value = NULL;
2504 
2505         old_value = crm_element_value(node, name);
2506 
2507         /* Could be re-setting the same value */
2508         CRM_CHECK(old_value != value, crm_err("Cannot reset %s with crm_xml_add(%s)", name, value);
2509                   return value);
2510     }
2511 #endif
2512 
2513     if(TRACKING_CHANGES(node)) {
2514         const char *old = crm_element_value(node, name);
2515 
2516         if(old == NULL || value == NULL || strcmp(old, value) != 0) {
2517             dirty = TRUE;
2518         }
2519     }
2520 
2521     if(dirty && __xml_acl_check(node, name, xpf_acl_create) == FALSE) {
2522         crm_trace("Cannot add %s=%s to %s", name, value, node->name);
2523         return NULL;
2524     }
2525 
2526     attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
2527     if(dirty) {
2528         crm_attr_dirty(attr);
2529     }
2530 
2531     CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
2532     return (char *)attr->children->content;
2533 }
2534 
2535 const char *
2536 crm_xml_replace(xmlNode * node, const char *name, const char *value)
     /* [previous][next][first][last][top][bottom][index][help] */
2537 {
2538     bool dirty = FALSE;
2539     xmlAttr *attr = NULL;
2540     const char *old_value = NULL;
2541 
2542     CRM_CHECK(node != NULL, return NULL);
2543     CRM_CHECK(name != NULL && name[0] != 0, return NULL);
2544 
2545     old_value = crm_element_value(node, name);
2546 
2547     /* Could be re-setting the same value */
2548     CRM_CHECK(old_value != value, return value);
2549 
2550     if(__xml_acl_check(node, name, xpf_acl_write) == FALSE) {
2551         /* Create a fake object linked to doc->_private instead? */
2552         crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
2553         return NULL;
2554 
2555     } else if (old_value != NULL && value == NULL) {
2556         xml_remove_prop(node, name);
2557         return NULL;
2558 
2559     } else if (value == NULL) {
2560         return NULL;
2561     }
2562 
2563     if(TRACKING_CHANGES(node)) {
2564         if(old_value == NULL || value == NULL || strcmp(old_value, value) != 0) {
2565             dirty = TRUE;
2566         }
2567     }
2568 
2569     attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
2570     if(dirty) {
2571         crm_attr_dirty(attr);
2572     }
2573     CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
2574     return (char *)attr->children->content;
2575 }
2576 
2577 const char *
2578 crm_xml_add_int(xmlNode * node, const char *name, int value)
     /* [previous][next][first][last][top][bottom][index][help] */
2579 {
2580     char *number = crm_itoa(value);
2581     const char *added = crm_xml_add(node, name, number);
2582 
2583     free(number);
2584     return added;
2585 }
2586 
2587 xmlNode *
2588 create_xml_node(xmlNode * parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
2589 {
2590     xmlDoc *doc = NULL;
2591     xmlNode *node = NULL;
2592 
2593     if (name == NULL || name[0] == 0) {
2594         CRM_CHECK(name != NULL && name[0] == 0, return NULL);
2595         return NULL;
2596     }
2597 
2598     if (parent == NULL) {
2599         doc = xmlNewDoc((const xmlChar *)"1.0");
2600         node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
2601         xmlDocSetRootElement(doc, node);
2602 
2603     } else {
2604         doc = getDocPtr(parent);
2605         node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
2606         xmlAddChild(parent, node);
2607     }
2608     crm_node_created(node);
2609     return node;
2610 }
2611 
2612 static inline int
2613 __get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset)
     /* [previous][next][first][last][top][bottom][index][help] */
2614 {
2615     const char *id = ID(xml);
2616 
2617     if(offset == 0 && prefix == NULL && xml->parent) {
2618         offset = __get_prefix(NULL, xml->parent, buffer, offset);
2619     }
2620 
2621     if(id) {
2622         offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s[@id='%s']", (const char *)xml->name, id);
2623     } else if(xml->name) {
2624         offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s", (const char *)xml->name);
2625     }
2626 
2627     return offset;
2628 }
2629 
2630 char *
2631 xml_get_path(xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2632 {
2633     int offset = 0;
2634     char buffer[XML_BUFFER_SIZE];
2635 
2636     if(__get_prefix(NULL, xml, buffer, offset) > 0) {
2637         return strdup(buffer);
2638     }
2639     return NULL;
2640 }
2641 
2642 static void
2643 free_xml_with_position(xmlNode * child, int position)
     /* [previous][next][first][last][top][bottom][index][help] */
2644 {
2645     if (child != NULL) {
2646         xmlNode *top = NULL;
2647         xmlDoc *doc = child->doc;
2648         xml_private_t *p = child->_private;
2649 
2650         if (doc != NULL) {
2651             top = xmlDocGetRootElement(doc);
2652         }
2653 
2654         if (doc != NULL && top == child) {
2655             /* Free everything */
2656             xmlFreeDoc(doc);
2657 
2658         } else if(__xml_acl_check(child, NULL, xpf_acl_write) == FALSE) {
2659             int offset = 0;
2660             char buffer[XML_BUFFER_SIZE];
2661 
2662             __get_prefix(NULL, child, buffer, offset);
2663             crm_trace("Cannot remove %s %x", buffer, p->flags);
2664             return;
2665 
2666         } else {
2667             if(doc && TRACKING_CHANGES(child) && is_not_set(p->flags, xpf_created)) {
2668                 int offset = 0;
2669                 char buffer[XML_BUFFER_SIZE];
2670 
2671                 if(__get_prefix(NULL, child, buffer, offset) > 0) {
2672                     xml_deleted_obj_t *deleted_obj = calloc(1, sizeof(xml_deleted_obj_t));
2673 
2674                     crm_trace("Deleting %s %p from %p", buffer, child, doc);
2675 
2676                     deleted_obj->path = strdup(buffer);
2677 
2678                     deleted_obj->position = -1;
2679                     /* Record the "position" only for XML comments for now */
2680                     if (child->type == XML_COMMENT_NODE) {
2681                         if (position >= 0) {
2682                             deleted_obj->position = position;
2683 
2684                         } else {
2685                             deleted_obj->position = __xml_offset(child);
2686                         }
2687                     }
2688 
2689                     p = doc->_private;
2690                     p->deleted_objs = g_list_append(p->deleted_objs, deleted_obj);
2691                     set_doc_flag(child, xpf_dirty);
2692                 }
2693             }
2694 
2695             /* Free this particular subtree
2696              * Make sure to unlink it from the parent first
2697              */
2698             xmlUnlinkNode(child);
2699             xmlFreeNode(child);
2700         }
2701     }
2702 }
2703 
2704 
2705 void
2706 free_xml(xmlNode * child)
     /* [previous][next][first][last][top][bottom][index][help] */
2707 {
2708     free_xml_with_position(child, -1);
2709 }
2710 
2711 xmlNode *
2712 copy_xml(xmlNode * src)
     /* [previous][next][first][last][top][bottom][index][help] */
2713 {
2714     xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
2715     xmlNode *copy = xmlDocCopyNode(src, doc, 1);
2716 
2717     xmlDocSetRootElement(doc, copy);
2718     xmlSetTreeDoc(copy, doc);
2719     return copy;
2720 }
2721 
2722 static void
2723 crm_xml_err(void *ctx, const char *fmt, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
2724 G_GNUC_PRINTF(2, 3);
2725 
2726 static void
2727 crm_xml_err(void *ctx, const char *fmt, ...)
2728 {
2729     va_list ap;
2730     static struct qb_log_callsite *xml_error_cs = NULL;
2731 
2732     if (xml_error_cs == NULL) {
2733         xml_error_cs = qb_log_callsite_get(
2734             __func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
2735     }
2736 
2737     va_start(ap, fmt);
2738     if (xml_error_cs && xml_error_cs->targets) {
2739         CRM_XML_LOG_BASE(LOG_ERR, TRUE,
2740                          crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error",
2741                                    TRUE, TRUE),
2742                          "XML Error: ", fmt, ap);
2743     } else {
2744         CRM_XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
2745     }
2746     va_end(ap);
2747 }
2748 
2749 xmlNode *
2750 string2xml(const char *input)
     /* [previous][next][first][last][top][bottom][index][help] */
2751 {
2752     xmlNode *xml = NULL;
2753     xmlDocPtr output = NULL;
2754     xmlParserCtxtPtr ctxt = NULL;
2755     xmlErrorPtr last_error = NULL;
2756 
2757     if (input == NULL) {
2758         crm_err("Can't parse NULL input");
2759         return NULL;
2760     }
2761 
2762     /* create a parser context */
2763     ctxt = xmlNewParserCtxt();
2764     CRM_CHECK(ctxt != NULL, return NULL);
2765 
2766     /* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
2767 
2768     xmlCtxtResetLastError(ctxt);
2769     xmlSetGenericErrorFunc(ctxt, crm_xml_err);
2770     /* initGenericErrorDefaultFunc(crm_xml_err); */
2771     output =
2772         xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL,
2773                        XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
2774     if (output) {
2775         xml = xmlDocGetRootElement(output);
2776     }
2777     last_error = xmlCtxtGetLastError(ctxt);
2778     if (last_error && last_error->code != XML_ERR_OK) {
2779         /* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
2780         /*
2781          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
2782          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
2783          */
2784         crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
2785                  last_error->domain, last_error->level, last_error->code, last_error->message);
2786 
2787         if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
2788             CRM_LOG_ASSERT("Cannot parse an empty string");
2789 
2790         } else if (last_error->code != XML_ERR_DOCUMENT_END) {
2791             crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
2792                     input);
2793             if (xml != NULL) {
2794                 crm_log_xml_err(xml, "Partial");
2795             }
2796 
2797         } else {
2798             int len = strlen(input);
2799             int lpc = 0;
2800 
2801             while(lpc < len) {
2802                 crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
2803                 lpc += 80;
2804             }
2805 
2806             CRM_LOG_ASSERT("String parsing error");
2807         }
2808     }
2809 
2810     xmlFreeParserCtxt(ctxt);
2811     return xml;
2812 }
2813 
2814 xmlNode *
2815 stdin2xml(void)
     /* [previous][next][first][last][top][bottom][index][help] */
2816 {
2817     size_t data_length = 0;
2818     size_t read_chars = 0;
2819 
2820     char *xml_buffer = NULL;
2821     xmlNode *xml_obj = NULL;
2822 
2823     do {
2824         size_t next = XML_BUFFER_SIZE + data_length + 1;
2825 
2826         if(next <= 0) {
2827             crm_err("Buffer size exceeded at: %l + %d", data_length, XML_BUFFER_SIZE);
2828             break;
2829         }
2830 
2831         xml_buffer = realloc_safe(xml_buffer, next);
2832         read_chars = fread(xml_buffer + data_length, 1, XML_BUFFER_SIZE, stdin);
2833         data_length += read_chars;
2834     } while (read_chars > 0);
2835 
2836     if (data_length == 0) {
2837         crm_warn("No XML supplied on stdin");
2838         free(xml_buffer);
2839         return NULL;
2840     }
2841 
2842     xml_buffer[data_length] = '\0';
2843 
2844     xml_obj = string2xml(xml_buffer);
2845     free(xml_buffer);
2846 
2847     crm_log_xml_trace(xml_obj, "Created fragment");
2848     return xml_obj;
2849 }
2850 
2851 static char *
2852 decompress_file(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
2853 {
2854     char *buffer = NULL;
2855 
2856 #if HAVE_BZLIB_H
2857     int rc = 0;
2858     size_t length = 0, read_len = 0;
2859 
2860     BZFILE *bz_file = NULL;
2861     FILE *input = fopen(filename, "r");
2862 
2863     if (input == NULL) {
2864         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
2865         return NULL;
2866     }
2867 
2868     bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
2869 
2870     if (rc != BZ_OK) {
2871         BZ2_bzReadClose(&rc, bz_file);
2872         return NULL;
2873     }
2874 
2875     rc = BZ_OK;
2876     while (rc == BZ_OK) {
2877         buffer = realloc_safe(buffer, XML_BUFFER_SIZE + length + 1);
2878         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, XML_BUFFER_SIZE);
2879 
2880         crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
2881 
2882         if (rc == BZ_OK || rc == BZ_STREAM_END) {
2883             length += read_len;
2884         }
2885     }
2886 
2887     buffer[length] = '\0';
2888 
2889     if (rc != BZ_STREAM_END) {
2890         crm_err("Couldn't read compressed xml from file");
2891         free(buffer);
2892         buffer = NULL;
2893     }
2894 
2895     BZ2_bzReadClose(&rc, bz_file);
2896     fclose(input);
2897 
2898 #else
2899     crm_err("Cannot read compressed files:" " bzlib was not available at compile time");
2900 #endif
2901     return buffer;
2902 }
2903 
2904 void
2905 strip_text_nodes(xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
2906 {
2907     xmlNode *iter = xml->children;
2908 
2909     while (iter) {
2910         xmlNode *next = iter->next;
2911 
2912         switch (iter->type) {
2913             case XML_TEXT_NODE:
2914                 /* Remove it */
2915                 xmlUnlinkNode(iter);
2916                 xmlFreeNode(iter);
2917                 break;
2918 
2919             case XML_ELEMENT_NODE:
2920                 /* Search it */
2921                 strip_text_nodes(iter);
2922                 break;
2923 
2924             default:
2925                 /* Leave it */
2926                 break;
2927         }
2928 
2929         iter = next;
2930     }
2931 }
2932 
2933 xmlNode *
2934 filename2xml(const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
2935 {
2936     xmlNode *xml = NULL;
2937     xmlDocPtr output = NULL;
2938     gboolean uncompressed = TRUE;
2939     xmlParserCtxtPtr ctxt = NULL;
2940     xmlErrorPtr last_error = NULL;
2941     static int xml_options = XML_PARSE_NOBLANKS | XML_PARSE_RECOVER;
2942 
2943     /* create a parser context */
2944     ctxt = xmlNewParserCtxt();
2945     CRM_CHECK(ctxt != NULL, return NULL);
2946 
2947     /* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
2948 
2949     xmlCtxtResetLastError(ctxt);
2950     xmlSetGenericErrorFunc(ctxt, crm_xml_err);
2951     /* initGenericErrorDefaultFunc(crm_xml_err); */
2952 
2953     if (filename) {
2954         uncompressed = !crm_ends_with_ext(filename, ".bz2");
2955     }
2956 
2957     if (filename == NULL) {
2958         /* STDIN_FILENO == fileno(stdin) */
2959         output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, xml_options);
2960 
2961     } else if (uncompressed) {
2962         output = xmlCtxtReadFile(ctxt, filename, NULL, xml_options);
2963 
2964     } else {
2965         char *input = decompress_file(filename);
2966 
2967         output = xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL, xml_options);
2968         free(input);
2969     }
2970 
2971     if (output && (xml = xmlDocGetRootElement(output))) {
2972         strip_text_nodes(xml);
2973     }
2974 
2975     last_error = xmlCtxtGetLastError(ctxt);
2976     if (last_error && last_error->code != XML_ERR_OK) {
2977         /* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
2978         /*
2979          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
2980          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
2981          */
2982         crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
2983                 last_error->domain, last_error->level, last_error->code, last_error->message);
2984 
2985         if (last_error && last_error->code != XML_ERR_OK) {
2986             crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
2987             if (xml != NULL) {
2988                 crm_log_xml_err(xml, "Partial");
2989             }
2990         }
2991     }
2992 
2993     xmlFreeParserCtxt(ctxt);
2994     return xml;
2995 }
2996 
2997 /*!
2998  * \internal
2999  * \brief Add a "last written" attribute to an XML node, set to current time
3000  *
3001  * \param[in] xml_node XML node to get attribute
3002  *
3003  * \return Value that was set, or NULL on error
3004  */
3005 const char *
3006 crm_xml_add_last_written(xmlNode *xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
3007 {
3008     time_t now = time(NULL);
3009     char *now_str = ctime(&now);
3010 
3011     now_str[24] = EOS; /* replace the newline */
3012     return crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str);
3013 }
3014 
3015 /*!
3016  * \brief Sanitize a string so it is usable as an XML ID
3017  *
3018  * \param[in,out] id  String to sanitize
3019  */
3020 void
3021 crm_xml_sanitize_id(char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
3022 {
3023     char *c;
3024 
3025     for (c = id; *c; ++c) {
3026         /* @TODO Sanitize more comprehensively */
3027         switch (*c) {
3028             case ':':
3029             case '#':
3030                 *c = '.';
3031         }
3032     }
3033 }
3034 
3035 /*!
3036  * \brief Set the ID of an XML element using a format
3037  *
3038  * \param[in,out] xml  XML element
3039  * \param[in]     fmt  printf-style format
3040  * \param[in]     ...  any arguments required by format
3041  */
3042 void
3043 crm_xml_set_id(xmlNode *xml, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help] */
3044 {
3045     va_list ap;
3046     int len = 0;
3047     char *id = NULL;
3048 
3049     /* equivalent to crm_strdup_printf() */
3050     va_start(ap, format);
3051     len = vasprintf(&id, format, ap);
3052     va_end(ap);
3053     CRM_ASSERT(len > 0);
3054 
3055     crm_xml_sanitize_id(id);
3056     crm_xml_add(xml, XML_ATTR_ID, id);
3057     free(id);
3058 }
3059 
3060 static int
3061 write_xml_stream(xmlNode * xml_node, const char *filename, FILE * stream, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
3062 {
3063     int res = 0;
3064     char *buffer = NULL;
3065     unsigned int out = 0;
3066 
3067     CRM_CHECK(stream != NULL, return -1);
3068 
3069     crm_trace("Writing XML out to %s", filename);
3070     if (xml_node == NULL) {
3071         crm_err("Cannot write NULL to %s", filename);
3072         fclose(stream);
3073         return -1;
3074     }
3075 
3076 
3077     crm_log_xml_trace(xml_node, "Writing out");
3078 
3079     buffer = dump_xml_formatted(xml_node);
3080     CRM_CHECK(buffer != NULL && strlen(buffer) > 0, crm_log_xml_warn(xml_node, "dump:failed");
3081               goto bail);
3082 
3083     if (compress) {
3084 #if HAVE_BZLIB_H
3085         int rc = BZ_OK;
3086         unsigned int in = 0;
3087         BZFILE *bz_file = NULL;
3088 
3089         bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
3090         if (rc != BZ_OK) {
3091             crm_err("bzWriteOpen failed: %d", rc);
3092         } else {
3093             BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
3094             if (rc != BZ_OK) {
3095                 crm_err("bzWrite() failed: %d", rc);
3096             }
3097         }
3098 
3099         if (rc == BZ_OK) {
3100             BZ2_bzWriteClose(&rc, bz_file, 0, &in, &out);
3101             if (rc != BZ_OK) {
3102                 crm_err("bzWriteClose() failed: %d", rc);
3103                 out = -1;
3104             } else {
3105                 crm_trace("%s: In: %d, out: %d", filename, in, out);
3106             }
3107         }
3108 #else
3109         crm_err("Cannot write compressed files:" " bzlib was not available at compile time");
3110 #endif
3111     }
3112 
3113     if (out <= 0) {
3114         res = fprintf(stream, "%s", buffer);
3115         if (res < 0) {
3116             crm_perror(LOG_ERR, "Cannot write output to %s", filename);
3117             goto bail;
3118         }
3119     }
3120 
3121   bail:
3122 
3123     if (fflush(stream) != 0) {
3124         crm_perror(LOG_ERR, "fflush for %s failed", filename);
3125         res = -1;
3126     }
3127 
3128     /* Don't report error if the file does not support synchronization */
3129     if (fsync(fileno(stream)) < 0 && errno != EROFS  && errno != EINVAL) {
3130         crm_perror(LOG_ERR, "fsync for %s failed", filename);
3131         res = -1;
3132     }
3133 
3134     fclose(stream);
3135 
3136     crm_trace("Saved %d bytes to the Cib as XML", res);
3137     free(buffer);
3138 
3139     return res;
3140 }
3141 
3142 int
3143 write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
3144 {
3145     FILE *stream = NULL;
3146 
3147     CRM_CHECK(fd > 0, return -1);
3148     stream = fdopen(fd, "w");
3149     return write_xml_stream(xml_node, filename, stream, compress);
3150 }
3151 
3152 int
3153 write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
     /* [previous][next][first][last][top][bottom][index][help] */
3154 {
3155     FILE *stream = NULL;
3156 
3157     stream = fopen(filename, "w");
3158 
3159     return write_xml_stream(xml_node, filename, stream, compress);
3160 }
3161 
3162 xmlNode *
3163 get_message_xml(xmlNode * msg, const char *field)
     /* [previous][next][first][last][top][bottom][index][help] */
3164 {
3165     xmlNode *tmp = first_named_child(msg, field);
3166 
3167     return __xml_first_child(tmp);
3168 }
3169 
3170 gboolean
3171 add_message_xml(xmlNode * msg, const char *field, xmlNode * xml)
     /* [previous][next][first][last][top][bottom][index][help] */
3172 {
3173     xmlNode *holder = create_xml_node(msg, field);
3174 
3175     add_node_copy(holder, xml);
3176     return TRUE;
3177 }
3178 
3179 static char *
3180 crm_xml_escape_shuffle(char *text, int start, int *length, const char *replace)
     /* [previous][next][first][last][top][bottom][index][help] */
3181 {
3182     int lpc;
3183     int offset = strlen(replace) - 1;   /* We have space for 1 char already */
3184 
3185     *length += offset;
3186     text = realloc_safe(text, *length);
3187 
3188     for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
3189         text[lpc] = text[lpc - offset];
3190     }
3191 
3192     memcpy(text + start, replace, offset + 1);
3193     return text;
3194 }
3195 
3196 char *
3197 crm_xml_escape(const char *text)
     /* [previous][next][first][last][top][bottom][index][help] */
3198 {
3199     int index;
3200     int changes = 0;
3201     int length = 1 + strlen(text);
3202     char *copy = strdup(text);
3203 
3204     /*
3205      * When xmlCtxtReadDoc() parses &lt; and friends in a
3206      * value, it converts them to their human readable
3207      * form.
3208      *
3209      * If one uses xmlNodeDump() to convert it back to a
3210      * string, all is well, because special characters are
3211      * converted back to their escape sequences.
3212      *
3213      * However xmlNodeDump() is randomly dog slow, even with the same
3214      * input. So we need to replicate the escaping in our custom
3215      * version so that the result can be re-parsed by xmlCtxtReadDoc()
3216      * when necessary.
3217      */
3218 
3219     for (index = 0; index < length; index++) {
3220         switch (copy[index]) {
3221             case 0:
3222                 break;
3223             case '<':
3224                 copy = crm_xml_escape_shuffle(copy, index, &length, "&lt;");
3225                 changes++;
3226                 break;
3227             case '>':
3228                 copy = crm_xml_escape_shuffle(copy, index, &length, "&gt;");
3229                 changes++;
3230                 break;
3231             case '"':
3232                 copy = crm_xml_escape_shuffle(copy, index, &length, "&quot;");
3233                 changes++;
3234                 break;
3235             case '\'':
3236                 copy = crm_xml_escape_shuffle(copy, index, &length, "&apos;");
3237                 changes++;
3238                 break;
3239             case '&':
3240                 copy = crm_xml_escape_shuffle(copy, index, &length, "&amp;");
3241                 changes++;
3242                 break;
3243             case '\t':
3244                 /* Might as well just expand to a few spaces... */
3245                 copy = crm_xml_escape_shuffle(copy, index, &length, "    ");
3246                 changes++;
3247                 break;
3248             case '\n':
3249                 /* crm_trace("Convert: \\%.3o", copy[index]); */
3250                 copy = crm_xml_escape_shuffle(copy, index, &length, "\\n");
3251                 changes++;
3252                 break;
3253             case '\r':
3254                 copy = crm_xml_escape_shuffle(copy, index, &length, "\\r");
3255                 changes++;
3256                 break;
3257                 /* For debugging...
3258             case '\\':
3259                 crm_trace("Passthrough: \\%c", copy[index+1]);
3260                 break;
3261                 */
3262             default:
3263                 /* Check for and replace non-printing characters with their octal equivalent */
3264                 if(copy[index] < ' ' || copy[index] > '~') {
3265                     char *replace = crm_strdup_printf("\\%.3o", copy[index]);
3266 
3267                     /* crm_trace("Convert to octal: \\%.3o", copy[index]); */
3268                     copy = crm_xml_escape_shuffle(copy, index, &length, replace);
3269                     free(replace);
3270                     changes++;
3271                 }
3272         }
3273     }
3274 
3275     if (changes) {
3276         crm_trace("Dumped '%s'", copy);
3277     }
3278     return copy;
3279 }
3280 
3281 static inline void
3282 dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
     /* [previous][next][first][last][top][bottom][index][help] */
3283 {
3284     char *p_value = NULL;
3285     const char *p_name = NULL;
3286     xml_private_t *p = NULL;
3287 
3288     CRM_ASSERT(buffer != NULL);
3289     if (attr == NULL || attr->children == NULL) {
3290         return;
3291     }
3292 
3293     p = attr->_private;
3294     if (p && is_set(p->flags, xpf_deleted)) {
3295         return;
3296     }
3297 
3298     p_name = (const char *)attr->name;
3299     p_value = crm_xml_escape((const char *)attr->children->content);
3300     buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
3301     free(p_value);
3302 }
3303 
3304 static void
3305 __xml_log_element(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
3306                   const char *prefix, xmlNode * data, int depth, int options)
3307 {
3308     int max = 0;
3309     int offset = 0;
3310     const char *name = NULL;
3311     const char *hidden = NULL;
3312 
3313     xmlNode *child = NULL;
3314     xmlAttrPtr pIter = NULL;
3315 
3316     if(data == NULL) {
3317         return;
3318     }
3319 
3320     name = crm_element_name(data);
3321 
3322     if(is_set(options, xml_log_option_open)) {
3323         char *buffer = NULL;
3324 
3325         insert_prefix(options, &buffer, &offset, &max, depth);
3326 
3327         if (data->type == XML_COMMENT_NODE) {
3328             buffer_print(buffer, max, offset, "<!--%s-->", data->content);
3329 
3330         } else {
3331             buffer_print(buffer, max, offset, "<%s", name);
3332 
3333             hidden = crm_element_value(data, "hidden");
3334             for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
3335                 xml_private_t *p = pIter->_private;
3336                 const char *p_name = (const char *)pIter->name;
3337                 const char *p_value = crm_attr_value(pIter);
3338                 char *p_copy = NULL;
3339 
3340                 if(is_set(p->flags, xpf_deleted)) {
3341                     continue;
3342                 } else if ((is_set(options, xml_log_option_diff_plus)
3343                      || is_set(options, xml_log_option_diff_minus))
3344                     && strcmp(XML_DIFF_MARKER, p_name) == 0) {
3345                     continue;
3346 
3347                 } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
3348                     p_copy = strdup("*****");
3349 
3350                 } else {
3351                     p_copy = crm_xml_escape(p_value);
3352                 }
3353 
3354                 buffer_print(buffer, max, offset, " %s=\"%s\"", p_name, p_copy);
3355                 free(p_copy);
3356             }
3357 
3358             if(xml_has_children(data) == FALSE) {
3359                 buffer_print(buffer, max, offset, "/>");
3360 
3361             } else if(is_set(options, xml_log_option_children)) {
3362                 buffer_print(buffer, max, offset, ">");
3363 
3364             } else {
3365                 buffer_print(buffer, max, offset, "/>");
3366             }
3367         }
3368 
3369         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
3370         free(buffer);
3371     }
3372 
3373     if(data->type == XML_COMMENT_NODE) {
3374         return;
3375 
3376     } else if(xml_has_children(data) == FALSE) {
3377         return;
3378 
3379     } else if(is_set(options, xml_log_option_children)) {
3380         offset = 0;
3381         max = 0;
3382 
3383         for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
3384             __xml_log_element(log_level, file, function, line, prefix, child, depth + 1, options|xml_log_option_open|xml_log_option_close);
3385         }
3386     }
3387 
3388     if(is_set(options, xml_log_option_close)) {
3389         char *buffer = NULL;
3390 
3391         insert_prefix(options, &buffer, &offset, &max, depth);
3392         buffer_print(buffer, max, offset, "</%s>", name);
3393 
3394         do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
3395         free(buffer);
3396     }
3397 }
3398 
3399 static void
3400 __xml_log_change_element(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
3401                          const char *prefix, xmlNode * data, int depth, int options)
3402 {
3403     xml_private_t *p;
3404     char *prefix_m = NULL;
3405     xmlNode *child = NULL;
3406     xmlAttrPtr pIter = NULL;
3407 
3408     if(data == NULL) {
3409         return;
3410     }
3411 
3412     p = data->_private;
3413 
3414     prefix_m = strdup(prefix);
3415     prefix_m[1] = '+';
3416 
3417     if(is_set(p->flags, xpf_dirty) && is_set(p->flags, xpf_created)) {
3418         /* Continue and log full subtree */
3419         __xml_log_element(log_level, file, function, line,
3420                           prefix_m, data, depth, options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
3421 
3422     } else if(is_set(p->flags, xpf_dirty)) {
3423         char *spaces = calloc(80, 1);
3424         int s_count = 0, s_max = 80;
3425         char *prefix_del = NULL;
3426         char *prefix_moved = NULL;
3427         const char *flags = prefix;
3428 
3429         insert_prefix(options, &spaces, &s_count, &s_max, depth);
3430         prefix_del = strdup(prefix);
3431         prefix_del[0] = '-';
3432         prefix_del[1] = '-';
3433         prefix_moved = strdup(prefix);
3434         prefix_moved[1] = '~';
3435 
3436         if(is_set(p->flags, xpf_moved)) {
3437             flags = prefix_moved;
3438         } else {
3439             flags = prefix;
3440         }
3441 
3442         __xml_log_element(log_level, file, function, line,
3443                           flags, data, depth, options|xml_log_option_open);
3444 
3445         for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
3446             const char *aname = (const char*)pIter->name;
3447 
3448             p = pIter->_private;
3449             if(is_set(p->flags, xpf_deleted)) {
3450                 const char *value = crm_element_value(data, aname);
3451                 flags = prefix_del;
3452                 do_crm_log_alias(log_level, file, function, line,
3453                                  "%s %s @%s=%s", flags, spaces, aname, value);
3454 
3455             } else if(is_set(p->flags, xpf_dirty)) {
3456                 const char *value = crm_element_value(data, aname);
3457 
3458                 if(is_set(p->flags, xpf_created)) {
3459                     flags = prefix_m;
3460 
3461                 } else if(is_set(p->flags, xpf_modified)) {
3462                     flags = prefix;
3463 
3464                 } else if(is_set(p->flags, xpf_moved)) {
3465                     flags = prefix_moved;
3466 
3467                 } else {
3468                     flags = prefix;
3469                 }
3470                 do_crm_log_alias(log_level, file, function, line,
3471                                  "%s %s @%s=%s", flags, spaces, aname, value);
3472             }
3473         }
3474         free(prefix_moved);
3475         free(prefix_del);
3476         free(spaces);
3477 
3478         for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
3479             __xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
3480         }
3481 
3482         __xml_log_element(log_level, file, function, line,
3483                           prefix, data, depth, options|xml_log_option_close);
3484 
3485     } else {
3486         for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
3487             __xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
3488         }
3489     }
3490 
3491     free(prefix_m);
3492 
3493 }
3494 
3495 void
3496 log_data_element(int log_level, const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
3497                  const char *prefix, xmlNode * data, int depth, int options)
3498 {
3499     xmlNode *a_child = NULL;
3500 
3501     char *prefix_m = NULL;
3502 
3503     if (prefix == NULL) {
3504         prefix = "";
3505     }
3506 
3507     /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
3508     if (data == NULL) {
3509         do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
3510                          "No data to dump as XML");
3511         return;
3512     }
3513 
3514     if(is_set(options, xml_log_option_dirty_add) || is_set(options, xml_log_option_dirty_add)) {
3515         __xml_log_change_element(log_level, file, function, line, prefix, data, depth, options);
3516         return;
3517     }
3518 
3519     if (is_set(options, xml_log_option_formatted)) {
3520         if (is_set(options, xml_log_option_diff_plus)
3521             && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
3522             options |= xml_log_option_diff_all;
3523             prefix_m = strdup(prefix);
3524             prefix_m[1] = '+';
3525             prefix = prefix_m;
3526 
3527         } else if (is_set(options, xml_log_option_diff_minus)
3528                    && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
3529             options |= xml_log_option_diff_all;
3530             prefix_m = strdup(prefix);
3531             prefix_m[1] = '-';
3532             prefix = prefix_m;
3533         }
3534     }
3535 
3536     if (is_set(options, xml_log_option_diff_short)
3537                && is_not_set(options, xml_log_option_diff_all)) {
3538         /* Still searching for the actual change */
3539         for (a_child = __xml_first_child(data); a_child != NULL; a_child = __xml_next(a_child)) {
3540             log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
3541         }
3542     } else {
3543         __xml_log_element(log_level, file, function, line, prefix, data, depth,
3544                           options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
3545     }
3546     free(prefix_m);
3547 }
3548 
3549 static void
3550 dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
     /* [previous][next][first][last][top][bottom][index][help] */
3551 {
3552     int lpc;
3553     xmlAttrPtr xIter = NULL;
3554     static int filter_len = DIMOF(filter);
3555 
3556     for (lpc = 0; options && lpc < filter_len; lpc++) {
3557         filter[lpc].found = FALSE;
3558     }
3559 
3560     for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
3561         bool skip = FALSE;
3562         const char *p_name = (const char *)xIter->name;
3563 
3564         for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
3565             if (filter[lpc].found == FALSE && strcmp(p_name, filter[lpc].string) == 0) {
3566                 filter[lpc].found = TRUE;
3567                 skip = TRUE;
3568                 break;
3569             }
3570         }
3571 
3572         if (skip == FALSE) {
3573             dump_xml_attr(xIter, options, buffer, offset, max);
3574         }
3575     }
3576 }
3577 
3578 static void
3579 dump_xml_element(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
3580 {
3581     const char *name = NULL;
3582 
3583     CRM_ASSERT(max != NULL);
3584     CRM_ASSERT(offset != NULL);
3585     CRM_ASSERT(buffer != NULL);
3586 
3587     if (data == NULL) {
3588         crm_trace("Nothing to dump");
3589         return;
3590     }
3591 
3592     if (*buffer == NULL) {
3593         *offset = 0;
3594         *max = 0;
3595     }
3596 
3597     name = crm_element_name(data);
3598     CRM_ASSERT(name != NULL);
3599 
3600     insert_prefix(options, buffer, offset, max, depth);
3601     buffer_print(*buffer, *max, *offset, "<%s", name);
3602 
3603     if (options & xml_log_option_filtered) {
3604         dump_filtered_xml(data, options, buffer, offset, max);
3605 
3606     } else {
3607         xmlAttrPtr xIter = NULL;
3608 
3609         for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
3610             dump_xml_attr(xIter, options, buffer, offset, max);
3611         }
3612     }
3613 
3614     if (data->children == NULL) {
3615         buffer_print(*buffer, *max, *offset, "/>");
3616 
3617     } else {
3618         buffer_print(*buffer, *max, *offset, ">");
3619     }
3620 
3621     if (options & xml_log_option_formatted) {
3622         buffer_print(*buffer, *max, *offset, "\n");
3623     }
3624 
3625     if (data->children) {
3626         xmlNode *xChild = NULL;
3627         for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
3628             crm_xml_dump(xChild, options, buffer, offset, max, depth + 1);
3629         }
3630 
3631         insert_prefix(options, buffer, offset, max, depth);
3632         buffer_print(*buffer, *max, *offset, "</%s>", name);
3633 
3634         if (options & xml_log_option_formatted) {
3635             buffer_print(*buffer, *max, *offset, "\n");
3636         }
3637     }
3638 }
3639 
3640 static void
3641 dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
3642 {
3643     CRM_ASSERT(max != NULL);
3644     CRM_ASSERT(offset != NULL);
3645     CRM_ASSERT(buffer != NULL);
3646 
3647     if (data == NULL) {
3648         crm_trace("Nothing to dump");
3649         return;
3650     }
3651 
3652     if (*buffer == NULL) {
3653         *offset = 0;
3654         *max = 0;
3655     }
3656 
3657     insert_prefix(options, buffer, offset, max, depth);
3658 
3659     buffer_print(*buffer, *max, *offset, "%s", data->content);
3660 
3661     if (options & xml_log_option_formatted) {
3662         buffer_print(*buffer, *max, *offset, "\n");
3663     }
3664 }
3665 
3666 
3667 static void
3668 dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
3669 {
3670     CRM_ASSERT(max != NULL);
3671     CRM_ASSERT(offset != NULL);
3672     CRM_ASSERT(buffer != NULL);
3673 
3674     if (data == NULL) {
3675         crm_trace("Nothing to dump");
3676         return;
3677     }
3678 
3679     if (*buffer == NULL) {
3680         *offset = 0;
3681         *max = 0;
3682     }
3683 
3684     insert_prefix(options, buffer, offset, max, depth);
3685 
3686     buffer_print(*buffer, *max, *offset, "<!--");
3687     buffer_print(*buffer, *max, *offset, "%s", data->content);
3688     buffer_print(*buffer, *max, *offset, "-->");
3689 
3690     if (options & xml_log_option_formatted) {
3691         buffer_print(*buffer, *max, *offset, "\n");
3692     }
3693 }
3694 
3695 void
3696 crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
     /* [previous][next][first][last][top][bottom][index][help] */
3697 {
3698     if(data == NULL) {
3699         *offset = 0;
3700         *max = 0;
3701         return;
3702     }
3703 #if 0
3704     if (is_not_set(options, xml_log_option_filtered)) {
3705         /* Turning this code on also changes the PE tests for some reason
3706          * (not just newlines).  Figure out why before considering to
3707          * enable this permanently.
3708          *
3709          * It exists to help debug slowness in xmlNodeDump() and
3710          * potentially if we ever want to go back to it.
3711          *
3712          * In theory it's a good idea (reuse) but our custom version does
3713          * better for the filtered case and avoids the final strdup() for
3714          * everything
3715          */
3716 
3717         time_t now, next;
3718         xmlDoc *doc = NULL;
3719         xmlBuffer *xml_buffer = NULL;
3720 
3721         *buffer = NULL;
3722         doc = getDocPtr(data);
3723         /* doc will only be NULL if data is */
3724         CRM_CHECK(doc != NULL, return);
3725 
3726         now = time(NULL);
3727         xml_buffer = xmlBufferCreate();
3728         CRM_ASSERT(xml_buffer != NULL);
3729 
3730         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
3731          * realloc()s and it can take upwards of 18 seconds (yes, seconds)
3732          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
3733          * less than 1 second.
3734          *
3735          * We could also use xmlBufferCreateSize() to start with a
3736          * sane-ish initial size and avoid the first few doubles.
3737          */
3738         xmlBufferSetAllocationScheme(xml_buffer, XML_BUFFER_ALLOC_DOUBLEIT);
3739 
3740         *max = xmlNodeDump(xml_buffer, doc, data, 0, (options & xml_log_option_formatted));
3741         if (*max > 0) {
3742             *buffer = strdup((char *)xml_buffer->content);
3743         }
3744 
3745         next = time(NULL);
3746         if ((now + 1) < next) {
3747             crm_log_xml_trace(data, "Long time");
3748             crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
3749         }
3750 
3751         xmlBufferFree(xml_buffer);
3752         return;
3753     }
3754 #endif
3755 
3756     switch(data->type) {
3757         case XML_ELEMENT_NODE:
3758             /* Handle below */
3759             dump_xml_element(data, options, buffer, offset, max, depth);
3760             break;
3761         case XML_TEXT_NODE:
3762             /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
3763             if (options & xml_log_option_text) {
3764                 dump_xml_text(data, options, buffer, offset, max, depth);
3765             }
3766             return;
3767         case XML_COMMENT_NODE:
3768             dump_xml_comment(data, options, buffer, offset, max, depth);
3769             break;
3770         default:
3771             crm_warn("Unhandled type: %d", data->type);
3772             return;
3773 
3774             /*
3775             XML_ATTRIBUTE_NODE = 2
3776             XML_CDATA_SECTION_NODE = 4
3777             XML_ENTITY_REF_NODE = 5
3778             XML_ENTITY_NODE = 6
3779             XML_PI_NODE = 7
3780             XML_DOCUMENT_NODE = 9
3781             XML_DOCUMENT_TYPE_NODE = 10
3782             XML_DOCUMENT_FRAG_NODE = 11
3783             XML_NOTATION_NODE = 12
3784             XML_HTML_DOCUMENT_NODE = 13
3785             XML_DTD_NODE = 14
3786             XML_ELEMENT_DECL = 15
3787             XML_ATTRIBUTE_DECL = 16
3788             XML_ENTITY_DECL = 17
3789             XML_NAMESPACE_DECL = 18
3790             XML_XINCLUDE_START = 19
3791             XML_XINCLUDE_END = 20
3792             XML_DOCB_DOCUMENT_NODE = 21
3793             */
3794     }
3795 
3796 }
3797 
3798 void
3799 crm_buffer_add_char(char **buffer, int *offset, int *max, char c)
     /* [previous][next][first][last][top][bottom][index][help] */
3800 {
3801     buffer_print(*buffer, *max, *offset, "%c", c);
3802 }
3803 
3804 char *
3805 dump_xml_formatted_with_text(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
3806 {
3807     char *buffer = NULL;
3808     int offset = 0, max = 0;
3809 
3810     crm_xml_dump(an_xml_node, xml_log_option_formatted|xml_log_option_text, &buffer, &offset, &max, 0);
3811     return buffer;
3812 }
3813 
3814 char *
3815 dump_xml_formatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
3816 {
3817     char *buffer = NULL;
3818     int offset = 0, max = 0;
3819 
3820     crm_xml_dump(an_xml_node, xml_log_option_formatted, &buffer, &offset, &max, 0);
3821     return buffer;
3822 }
3823 
3824 char *
3825 dump_xml_unformatted(xmlNode * an_xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
3826 {
3827     char *buffer = NULL;
3828     int offset = 0, max = 0;
3829 
3830     crm_xml_dump(an_xml_node, 0, &buffer, &offset, &max, 0);
3831     return buffer;
3832 }
3833 
3834 gboolean
3835 xml_has_children(const xmlNode * xml_root)
     /* [previous][next][first][last][top][bottom][index][help] */
3836 {
3837     if (xml_root != NULL && xml_root->children != NULL) {
3838         return TRUE;
3839     }
3840     return FALSE;
3841 }
3842 
3843 int
3844 crm_element_value_int(xmlNode * data, const char *name, int *dest)
     /* [previous][next][first][last][top][bottom][index][help] */
3845 {
3846     const char *value = crm_element_value(data, name);
3847 
3848     CRM_CHECK(dest != NULL, return -1);
3849     if (value) {
3850         *dest = crm_int_helper(value, NULL);
3851         return 0;
3852     }
3853     return -1;
3854 }
3855 
3856 int
3857 crm_element_value_const_int(const xmlNode * data, const char *name, int *dest)
     /* [previous][next][first][last][top][bottom][index][help] */
3858 {
3859     return crm_element_value_int((xmlNode *) data, name, dest);
3860 }
3861 
3862 const char *
3863 crm_element_value_const(const xmlNode * data, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
3864 {
3865     return crm_element_value((xmlNode *) data, name);
3866 }
3867 
3868 char *
3869 crm_element_value_copy(xmlNode * data, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
3870 {
3871     char *value_copy = NULL;
3872     const char *value = crm_element_value(data, name);
3873 
3874     if (value != NULL) {
3875         value_copy = strdup(value);
3876     }
3877     return value_copy;
3878 }
3879 
3880 void
3881 xml_remove_prop(xmlNode * obj, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
3882 {
3883     if(__xml_acl_check(obj, NULL, xpf_acl_write) == FALSE) {
3884         crm_trace("Cannot remove %s from %s", name, obj->name);
3885 
3886     } else if(TRACKING_CHANGES(obj)) {
3887         /* Leave in place (marked for removal) until after the diff is calculated */
3888         xml_private_t *p = NULL;
3889         xmlAttr *attr = xmlHasProp(obj, (const xmlChar *)name);
3890 
3891         p = attr->_private;
3892         set_parent_flag(obj, xpf_dirty);
3893         p->flags |= xpf_deleted;
3894         /* crm_trace("Setting flag %x due to %s[@id=%s].%s", xpf_dirty, obj->name, ID(obj), name); */
3895 
3896     } else {
3897         xmlUnsetProp(obj, (const xmlChar *)name);
3898     }
3899 }
3900 
3901 void
3902 purge_diff_markers(xmlNode * a_node)
     /* [previous][next][first][last][top][bottom][index][help] */
3903 {
3904     xmlNode *child = NULL;
3905 
3906     CRM_CHECK(a_node != NULL, return);
3907 
3908     xml_remove_prop(a_node, XML_DIFF_MARKER);
3909     for (child = __xml_first_child(a_node); child != NULL; child = __xml_next(child)) {
3910         purge_diff_markers(child);
3911     }
3912 }
3913 
3914 void
3915 save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
     /* [previous][next][first][last][top][bottom][index][help] */
3916 {
3917     char *f = NULL;
3918 
3919     if (filename == NULL) {
3920         char *uuid = crm_generate_uuid();
3921 
3922         f = crm_strdup_printf("/tmp/%s", uuid);
3923         filename = f;
3924         free(uuid);
3925     }
3926 
3927     crm_info("Saving %s to %s", desc, filename);
3928     write_xml_file(xml, filename, FALSE);
3929     free(f);
3930 }
3931 
3932 gboolean
3933 apply_xml_diff(xmlNode * old, xmlNode * diff, xmlNode ** new)
     /* [previous][next][first][last][top][bottom][index][help] */
3934 {
3935     gboolean result = TRUE;
3936     int root_nodes_seen = 0;
3937     static struct qb_log_callsite *digest_cs = NULL;
3938     const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
3939     const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
3940 
3941     xmlNode *child_diff = NULL;
3942     xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
3943     xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
3944 
3945     CRM_CHECK(new != NULL, return FALSE);
3946     if (digest_cs == NULL) {
3947         digest_cs =
3948             qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
3949                                 crm_trace_nonlog);
3950     }
3951 
3952     crm_trace("Subtraction Phase");
3953     for (child_diff = __xml_first_child(removed); child_diff != NULL;
3954          child_diff = __xml_next(child_diff)) {
3955         CRM_CHECK(root_nodes_seen == 0, result = FALSE);
3956         if (root_nodes_seen == 0) {
3957             *new = subtract_xml_object(NULL, old, child_diff, FALSE, NULL, NULL);
3958         }
3959         root_nodes_seen++;
3960     }
3961 
3962     if (root_nodes_seen == 0) {
3963         *new = copy_xml(old);
3964 
3965     } else if (root_nodes_seen > 1) {
3966         crm_err("(-) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
3967         result = FALSE;
3968     }
3969 
3970     root_nodes_seen = 0;
3971     crm_trace("Addition Phase");
3972     if (result) {
3973         xmlNode *child_diff = NULL;
3974 
3975         for (child_diff = __xml_first_child(added); child_diff != NULL;
3976              child_diff = __xml_next(child_diff)) {
3977             CRM_CHECK(root_nodes_seen == 0, result = FALSE);
3978             if (root_nodes_seen == 0) {
3979                 add_xml_object(NULL, *new, child_diff, TRUE);
3980             }
3981             root_nodes_seen++;
3982         }
3983     }
3984 
3985     if (root_nodes_seen > 1) {
3986         crm_err("(+) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
3987         result = FALSE;
3988 
3989     } else if (result && digest) {
3990         char *new_digest = NULL;
3991 
3992         purge_diff_markers(*new);       /* Purge now so the diff is ok */
3993         new_digest = calculate_xml_versioned_digest(*new, FALSE, TRUE, version);
3994         if (safe_str_neq(new_digest, digest)) {
3995             crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest);
3996             result = FALSE;
3997 
3998             crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
3999             if (digest_cs && digest_cs->targets) {
4000                 save_xml_to_file(old, "diff:original", NULL);
4001                 save_xml_to_file(diff, "diff:input", NULL);
4002                 save_xml_to_file(*new, "diff:new", NULL);
4003             }
4004 
4005         } else {
4006             crm_trace("Digest matched: expected %s, calculated %s", digest, new_digest);
4007         }
4008         free(new_digest);
4009 
4010     } else if (result) {
4011         purge_diff_markers(*new);       /* Purge now so the diff is ok */
4012     }
4013 
4014     return result;
4015 }
4016 
4017 static void
4018 __xml_diff_object(xmlNode * old, xmlNode * new)
     /* [previous][next][first][last][top][bottom][index][help] */
4019 {
4020     xmlNode *cIter = NULL;
4021     xmlAttr *pIter = NULL;
4022 
4023     CRM_CHECK(new != NULL, return);
4024     if(old == NULL) {
4025         crm_node_created(new);
4026         __xml_acl_post_process(new); /* Check creation is allowed */
4027         return;
4028 
4029     } else {
4030         xml_private_t *p = new->_private;
4031 
4032         if(p->flags & xpf_processed) {
4033             /* Avoid re-comparing nodes */
4034             return;
4035         }
4036         p->flags |= xpf_processed;
4037     }
4038 
4039     for (pIter = crm_first_attr(new); pIter != NULL; pIter = pIter->next) {
4040         xml_private_t *p = pIter->_private;
4041 
4042         /* Assume everything was just created and take it from there */
4043         p->flags |= xpf_created;
4044     }
4045 
4046     for (pIter = crm_first_attr(old); pIter != NULL; ) {
4047         xmlAttr *prop = pIter;
4048         xml_private_t *p = NULL;
4049         const char *name = (const char *)pIter->name;
4050         const char *old_value = crm_element_value(old, name);
4051         xmlAttr *exists = xmlHasProp(new, pIter->name);
4052 
4053         pIter = pIter->next;
4054         if(exists == NULL) {
4055             p = new->doc->_private;
4056 
4057             /* Prevent the dirty flag being set recursively upwards */
4058             clear_bit(p->flags, xpf_tracking);
4059             exists = xmlSetProp(new, (const xmlChar *)name, (const xmlChar *)old_value);
4060             set_bit(p->flags, xpf_tracking);
4061 
4062             p = exists->_private;
4063             p->flags = 0;
4064 
4065             crm_trace("Lost %s@%s=%s", old->name, name, old_value);
4066             xml_remove_prop(new, name);
4067 
4068         } else {
4069             int p_new = __xml_offset((xmlNode*)exists);
4070             int p_old = __xml_offset((xmlNode*)prop);
4071             const char *value = crm_element_value(new, name);
4072 
4073             p = exists->_private;
4074             p->flags = (p->flags & ~xpf_created);
4075 
4076             if(strcmp(value, old_value) != 0) {
4077                 /* Restore the original value, so we can call crm_xml_add(),
4078                  * which checks ACLs
4079                  */
4080                 char *vcopy = crm_element_value_copy(new, name);
4081 
4082                 crm_trace("Modified %s@%s %s->%s", old->name, name, old_value, vcopy);
4083                 xmlSetProp(new, prop->name, (const xmlChar *)old_value);
4084                 crm_xml_add(new, name, vcopy);
4085                 free(vcopy);
4086 
4087             } else if(p_old != p_new) {
4088                 crm_info("Moved %s@%s (%d -> %d)", old->name, name, p_old, p_new);
4089                 __xml_node_dirty(new);
4090                 p->flags |= xpf_dirty|xpf_moved;
4091 
4092                 if(p_old > p_new) {
4093                     p = prop->_private;
4094                     p->flags |= xpf_skip;
4095 
4096                 } else {
4097                     p = exists->_private;
4098                     p->flags |= xpf_skip;
4099                 }
4100             }
4101         }
4102     }
4103 
4104     for (pIter = crm_first_attr(new); pIter != NULL; ) {
4105         xmlAttr *prop = pIter;
4106         xml_private_t *p = pIter->_private;
4107 
4108         pIter = pIter->next;
4109         if(is_set(p->flags, xpf_created)) {
4110             char *name = strdup((const char *)prop->name);
4111             char *value = crm_element_value_copy(new, name);
4112 
4113             crm_trace("Created %s@%s=%s", new->name, name, value);
4114             /* Remove plus create won't work as it will modify the relative attribute ordering */
4115             if(__xml_acl_check(new, name, xpf_acl_write)) {
4116                 crm_attr_dirty(prop);
4117             } else {
4118                 xmlUnsetProp(new, prop->name); /* Remove - change not allowed */
4119             }
4120 
4121             free(value);
4122             free(name);
4123         }
4124     }
4125 
4126     for (cIter = __xml_first_child(old); cIter != NULL; ) {
4127         xmlNode *old_child = cIter;
4128         xmlNode *new_child = find_element(new, cIter, TRUE);
4129 
4130         cIter = __xml_next(cIter);
4131         if(new_child) {
4132             __xml_diff_object(old_child, new_child);
4133 
4134         } else {
4135             /* Create then free (which will check the acls if necessary) */
4136             xmlNode *candidate = add_node_copy(new, old_child);
4137             xmlNode *top = xmlDocGetRootElement(candidate->doc);
4138 
4139             __xml_node_clean(candidate);
4140             __xml_acl_apply(top); /* Make sure any ACLs are applied to 'candidate' */
4141             /* Record the old position */
4142             free_xml_with_position(candidate, __xml_offset(old_child));
4143 
4144             if (find_element(new, old_child, TRUE) == NULL) {
4145                 xml_private_t *p = old_child->_private;
4146 
4147                 p->flags |= xpf_skip;
4148             }
4149         }
4150     }
4151 
4152     for (cIter = __xml_first_child(new); cIter != NULL; ) {
4153         xmlNode *new_child = cIter;
4154         xmlNode *old_child = find_element(old, cIter, TRUE);
4155 
4156         cIter = __xml_next(cIter);
4157         if(old_child == NULL) {
4158             xml_private_t *p = new_child->_private;
4159             p->flags |= xpf_skip;
4160             __xml_diff_object(old_child, new_child);
4161 
4162         } else {
4163             /* Check for movement, we already checked for differences */
4164             int p_new = __xml_offset(new_child);
4165             int p_old = __xml_offset(old_child);
4166 
4167             if(p_old != p_new) {
4168                 xml_private_t *p = new_child->_private;
4169 
4170                 crm_info("%s.%s moved from %d to %d",
4171                          new_child->name, ID(new_child), p_old, p_new);
4172                 __xml_node_dirty(new);
4173                 p->flags |= xpf_moved;
4174 
4175                 if(p_old > p_new) {
4176                     p = old_child->_private;
4177                 } else {
4178                     p = new_child->_private;
4179                 }
4180                 p->flags |= xpf_skip;
4181             }
4182         }
4183     }
4184 }
4185 
4186 void
4187 xml_calculate_changes(xmlNode * old, xmlNode * new)
     /* [previous][next][first][last][top][bottom][index][help] */
4188 {
4189     CRM_CHECK(safe_str_eq(crm_element_name(old), crm_element_name(new)), return);
4190     CRM_CHECK(safe_str_eq(ID(old), ID(new)), return);
4191 
4192     if(xml_tracking_changes(new) == FALSE) {
4193         xml_track_changes(new, NULL, NULL, FALSE);
4194     }
4195 
4196     __xml_diff_object(old, new);
4197 }
4198 
4199 xmlNode *
4200 diff_xml_object(xmlNode * old, xmlNode * new, gboolean suppress)
     /* [previous][next][first][last][top][bottom][index][help] */
4201 {
4202     xmlNode *tmp1 = NULL;
4203     xmlNode *diff = create_xml_node(NULL, "diff");
4204     xmlNode *removed = create_xml_node(diff, "diff-removed");
4205     xmlNode *added = create_xml_node(diff, "diff-added");
4206 
4207     crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
4208 
4209     tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
4210     if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
4211         free_xml(tmp1);
4212     }
4213 
4214     tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
4215     if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
4216         free_xml(tmp1);
4217     }
4218 
4219     if (added->children == NULL && removed->children == NULL) {
4220         free_xml(diff);
4221         diff = NULL;
4222     }
4223 
4224     return diff;
4225 }
4226 
4227 gboolean
4228 can_prune_leaf(xmlNode * xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
4229 {
4230     xmlNode *cIter = NULL;
4231     xmlAttrPtr pIter = NULL;
4232     gboolean can_prune = TRUE;
4233     const char *name = crm_element_name(xml_node);
4234 
4235     if (safe_str_eq(name, XML_TAG_RESOURCE_REF)
4236         || safe_str_eq(name, XML_CIB_TAG_OBJ_REF)
4237         || safe_str_eq(name, XML_ACL_TAG_ROLE_REF)
4238         || safe_str_eq(name, XML_ACL_TAG_ROLE_REFv1)) {
4239         return FALSE;
4240     }
4241 
4242     for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
4243         const char *p_name = (const char *)pIter->name;
4244 
4245         if (strcmp(p_name, XML_ATTR_ID) == 0) {
4246             continue;
4247         }
4248         can_prune = FALSE;
4249     }
4250 
4251     cIter = __xml_first_child(xml_node);
4252     while (cIter) {
4253         xmlNode *child = cIter;
4254 
4255         cIter = __xml_next(cIter);
4256         if (can_prune_leaf(child)) {
4257             free_xml(child);
4258         } else {
4259             can_prune = FALSE;
4260         }
4261     }
4262     return can_prune;
4263 }
4264 
4265 void
4266 diff_filter_context(int context, int upper_bound, int lower_bound,
     /* [previous][next][first][last][top][bottom][index][help] */
4267                     xmlNode * xml_node, xmlNode * parent)
4268 {
4269     xmlNode *us = NULL;
4270     xmlNode *child = NULL;
4271     xmlAttrPtr pIter = NULL;
4272     xmlNode *new_parent = parent;
4273     const char *name = crm_element_name(xml_node);
4274 
4275     CRM_CHECK(xml_node != NULL && name != NULL, return);
4276 
4277     us = create_xml_node(parent, name);
4278     for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
4279         const char *p_name = (const char *)pIter->name;
4280         const char *p_value = crm_attr_value(pIter);
4281 
4282         lower_bound = context;
4283         crm_xml_add(us, p_name, p_value);
4284     }
4285 
4286     if (lower_bound >= 0 || upper_bound >= 0) {
4287         crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
4288         new_parent = us;
4289 
4290     } else {
4291         upper_bound = in_upper_context(0, context, xml_node);
4292         if (upper_bound >= 0) {
4293             crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
4294             new_parent = us;
4295         } else {
4296             free_xml(us);
4297             us = NULL;
4298         }
4299     }
4300 
4301     for (child = __xml_first_child(us); child != NULL; child = __xml_next(child)) {
4302         diff_filter_context(context, upper_bound - 1, lower_bound - 1, child, new_parent);
4303     }
4304 }
4305 
4306 int
4307 in_upper_context(int depth, int context, xmlNode * xml_node)
     /* [previous][next][first][last][top][bottom][index][help] */
4308 {
4309     if (context == 0) {
4310         return 0;
4311     }
4312 
4313     if (xml_node->properties) {
4314         return depth;
4315 
4316     } else if (depth < context) {
4317         xmlNode *child = NULL;
4318 
4319         for (child = __xml_first_child(xml_node); child != NULL; child = __xml_next(child)) {
4320             if (in_upper_context(depth + 1, context, child)) {
4321                 return depth;
4322             }
4323         }
4324     }
4325     return 0;
4326 }
4327 
4328 static xmlNode *
4329 find_xml_comment(xmlNode * root, xmlNode * search_comment, gboolean exact)
     /* [previous][next][first][last][top][bottom][index][help] */
4330 {
4331     xmlNode *a_child = NULL;
4332     int search_offset = __xml_offset(search_comment);
4333 
4334     CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
4335 
4336     for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
4337         if (exact) {
4338             int offset = __xml_offset(a_child);
4339             xml_private_t *p = a_child->_private;
4340 
4341             if (offset < search_offset) {
4342                 continue;
4343 
4344             } else if (offset > search_offset) {
4345                 return NULL;
4346             }
4347 
4348             if (is_set(p->flags, xpf_skip)) {
4349                 continue;
4350             }
4351         }
4352 
4353         if (a_child->type == XML_COMMENT_NODE
4354             && safe_str_eq((const char *)a_child->content, (const char *)search_comment->content)) {
4355             return a_child;
4356 
4357         } else if (exact) {
4358             return NULL;
4359         }
4360     }
4361 
4362     return NULL;
4363 }
4364 
4365 static xmlNode *
4366 subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right,
     /* [previous][next][first][last][top][bottom][index][help] */
4367                      gboolean * changed)
4368 {
4369     CRM_CHECK(left != NULL, return NULL);
4370     CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
4371 
4372     if (right == NULL
4373         || safe_str_neq((const char *)left->content, (const char *)right->content)) {
4374         xmlNode *deleted = NULL;
4375 
4376         deleted = add_node_copy(parent, left);
4377         *changed = TRUE;
4378 
4379         return deleted;
4380     }
4381 
4382     return NULL;
4383 }
4384 
4385 xmlNode *
4386 subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
     /* [previous][next][first][last][top][bottom][index][help] */
4387                     gboolean full, gboolean * changed, const char *marker)
4388 {
4389     gboolean dummy = FALSE;
4390     gboolean skip = FALSE;
4391     xmlNode *diff = NULL;
4392     xmlNode *right_child = NULL;
4393     xmlNode *left_child = NULL;
4394     xmlAttrPtr xIter = NULL;
4395 
4396     const char *id = NULL;
4397     const char *name = NULL;
4398     const char *value = NULL;
4399     const char *right_val = NULL;
4400 
4401     int lpc = 0;
4402     static int filter_len = DIMOF(filter);
4403 
4404     if (changed == NULL) {
4405         changed = &dummy;
4406     }
4407 
4408     if (left == NULL) {
4409         return NULL;
4410     }
4411 
4412     if (left->type == XML_COMMENT_NODE) {
4413         return subtract_xml_comment(parent, left, right, changed);
4414     }
4415 
4416     id = ID(left);
4417     if (right == NULL) {
4418         xmlNode *deleted = NULL;
4419 
4420         crm_trace("Processing <%s id=%s> (complete copy)", crm_element_name(left), id);
4421         deleted = add_node_copy(parent, left);
4422         crm_xml_add(deleted, XML_DIFF_MARKER, marker);
4423 
4424         *changed = TRUE;
4425         return deleted;
4426     }
4427 
4428     name = crm_element_name(left);
4429     CRM_CHECK(name != NULL, return NULL);
4430     CRM_CHECK(safe_str_eq(crm_element_name(left), crm_element_name(right)), return NULL);
4431 
4432     /* check for XML_DIFF_MARKER in a child */
4433     value = crm_element_value(right, XML_DIFF_MARKER);
4434     if (value != NULL && strcmp(value, "removed:top") == 0) {
4435         crm_trace("We are the root of the deletion: %s.id=%s", name, id);
4436         *changed = TRUE;
4437         return NULL;
4438     }
4439 
4440     /* Avoiding creating the full heirarchy would save even more work here */
4441     diff = create_xml_node(parent, name);
4442 
4443     /* Reset filter */
4444     for (lpc = 0; lpc < filter_len; lpc++) {
4445         filter[lpc].found = FALSE;
4446     }
4447 
4448     /* changes to child objects */
4449     for (left_child = __xml_first_child(left); left_child != NULL;
4450          left_child = __xml_next(left_child)) {
4451         gboolean child_changed = FALSE;
4452 
4453         right_child = find_element(right, left_child, FALSE);
4454         subtract_xml_object(diff, left_child, right_child, full, &child_changed, marker);
4455         if (child_changed) {
4456             *changed = TRUE;
4457         }
4458     }
4459 
4460     if (*changed == FALSE) {
4461         /* Nothing to do */
4462 
4463     } else if (full) {
4464         xmlAttrPtr pIter = NULL;
4465 
4466         for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
4467             const char *p_name = (const char *)pIter->name;
4468             const char *p_value = crm_attr_value(pIter);
4469 
4470             xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
4471         }
4472 
4473         /* We already have everything we need... */
4474         goto done;
4475 
4476     } else if (id) {
4477         xmlSetProp(diff, (const xmlChar *)XML_ATTR_ID, (const xmlChar *)id);
4478     }
4479 
4480     /* changes to name/value pairs */
4481     for (xIter = crm_first_attr(left); xIter != NULL; xIter = xIter->next) {
4482         const char *prop_name = (const char *)xIter->name;
4483         xmlAttrPtr right_attr = NULL;
4484         xml_private_t *p = NULL;
4485 
4486         if (strcmp(prop_name, XML_ATTR_ID) == 0) {
4487             continue;
4488         }
4489 
4490         skip = FALSE;
4491         for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
4492             if (filter[lpc].found == FALSE && strcmp(prop_name, filter[lpc].string) == 0) {
4493                 filter[lpc].found = TRUE;
4494                 skip = TRUE;
4495                 break;
4496             }
4497         }
4498 
4499         if (skip) {
4500             continue;
4501         }
4502 
4503         right_attr = xmlHasProp(right, (const xmlChar *)prop_name);
4504         if (right_attr) {
4505             p = right_attr->_private;
4506         }
4507 
4508         right_val = crm_element_value(right, prop_name);
4509         if (right_val == NULL || (p && is_set(p->flags, xpf_deleted))) {
4510             /* new */
4511             *changed = TRUE;
4512             if (full) {
4513                 xmlAttrPtr pIter = NULL;
4514 
4515                 for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
4516                     const char *p_name = (const char *)pIter->name;
4517                     const char *p_value = crm_attr_value(pIter);
4518 
4519                     xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
4520                 }
4521                 break;
4522 
4523             } else {
4524                 const char *left_value = crm_element_value(left, prop_name);
4525 
4526                 xmlSetProp(diff, (const xmlChar *)prop_name, (const xmlChar *)value);
4527                 crm_xml_add(diff, prop_name, left_value);
4528             }
4529 
4530         } else {
4531             /* Only now do we need the left value */
4532             const char *left_value = crm_element_value(left, prop_name);
4533 
4534             if (strcmp(left_value, right_val) == 0) {
4535                 /* unchanged */
4536 
4537             } else {
4538                 *changed = TRUE;
4539                 if (full) {
4540                     xmlAttrPtr pIter = NULL;
4541 
4542                     crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
4543                               crm_element_name(left), id);
4544                     for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
4545                         const char *p_name = (const char *)pIter->name;
4546                         const char *p_value = crm_attr_value(pIter);
4547 
4548                         xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
4549                     }
4550                     break;
4551 
4552                 } else {
4553                     crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
4554                               prop_name, left_value, right_val, crm_element_name(left), id);
4555                     crm_xml_add(diff, prop_name, left_value);
4556                 }
4557             }
4558         }
4559     }
4560 
4561     if (*changed == FALSE) {
4562         free_xml(diff);
4563         return NULL;
4564 
4565     } else if (full == FALSE && id) {
4566         crm_xml_add(diff, XML_ATTR_ID, id);
4567     }
4568   done:
4569     return diff;
4570 }
4571 
4572 static int
4573 add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update)
     /* [previous][next][first][last][top][bottom][index][help] */
4574 {
4575     CRM_CHECK(update != NULL, return 0);
4576     CRM_CHECK(update->type == XML_COMMENT_NODE, return 0);
4577 
4578     if (target == NULL) {
4579         target = find_xml_comment(parent, update, FALSE);
4580     }
4581 
4582     if (target == NULL) {
4583         add_node_copy(parent, update);
4584 
4585     /* We won't reach here currently */
4586     } else if (safe_str_neq((const char *)target->content, (const char *)update->content)) {
4587         xmlFree(target->content);
4588         target->content = xmlStrdup(update->content);
4589     }
4590 
4591     return 0;
4592 }
4593 
4594 int
4595 add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff)
     /* [previous][next][first][last][top][bottom][index][help] */
4596 {
4597     xmlNode *a_child = NULL;
4598     const char *object_id = NULL;
4599     const char *object_name = NULL;
4600 
4601 #if XML_PARSE_DEBUG
4602     crm_log_xml_trace("update:", update);
4603     crm_log_xml_trace("target:", target);
4604 #endif
4605 
4606     CRM_CHECK(update != NULL, return 0);
4607 
4608     if (update->type == XML_COMMENT_NODE) {
4609         return add_xml_comment(parent, target, update);
4610     }
4611 
4612     object_name = crm_element_name(update);
4613     object_id = ID(update);
4614 
4615     CRM_CHECK(object_name != NULL, return 0);
4616 
4617     if (target == NULL && object_id == NULL) {
4618         /*  placeholder object */
4619         target = find_xml_node(parent, object_name, FALSE);
4620 
4621     } else if (target == NULL) {
4622         target = find_entity(parent, object_name, object_id);
4623     }
4624 
4625     if (target == NULL) {
4626         target = create_xml_node(parent, object_name);
4627         CRM_CHECK(target != NULL, return 0);
4628 #if XML_PARSER_DEBUG
4629         crm_trace("Added  <%s%s%s/>", crm_str(object_name),
4630                   object_id ? " id=" : "", object_id ? object_id : "");
4631 
4632     } else {
4633         crm_trace("Found node <%s%s%s/> to update",
4634                   crm_str(object_name), object_id ? " id=" : "", object_id ? object_id : "");
4635 #endif
4636     }
4637 
4638     CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(update)), return 0);
4639 
4640     if (as_diff == FALSE) {
4641         /* So that expand_plus_plus() gets called */
4642         copy_in_properties(target, update);
4643 
4644     } else {
4645         /* No need for expand_plus_plus(), just raw speed */
4646         xmlAttrPtr pIter = NULL;
4647 
4648         for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
4649             const char *p_name = (const char *)pIter->name;
4650             const char *p_value = crm_attr_value(pIter);
4651 
4652             /* Remove it first so the ordering of the update is preserved */
4653             xmlUnsetProp(target, (const xmlChar *)p_name);
4654             xmlSetProp(target, (const xmlChar *)p_name, (const xmlChar *)p_value);
4655         }
4656     }
4657 
4658     for (a_child = __xml_first_child(update); a_child != NULL; a_child = __xml_next(a_child)) {
4659 #if XML_PARSER_DEBUG
4660         crm_trace("Updating child <%s id=%s>", crm_element_name(a_child), ID(a_child));
4661 #endif
4662         add_xml_object(target, NULL, a_child, as_diff);
4663     }
4664 
4665 #if XML_PARSER_DEBUG
4666     crm_trace("Finished with <%s id=%s>", crm_str(object_name), crm_str(object_id));
4667 #endif
4668     return 0;
4669 }
4670 
4671 gboolean
4672 update_xml_child(xmlNode * child, xmlNode * to_update)
     /* [previous][next][first][last][top][bottom][index][help] */
4673 {
4674     gboolean can_update = TRUE;
4675     xmlNode *child_of_child = NULL;
4676 
4677     CRM_CHECK(child != NULL, return FALSE);
4678     CRM_CHECK(to_update != NULL, return FALSE);
4679 
4680     if (safe_str_neq(crm_element_name(to_update), crm_element_name(child))) {
4681         can_update = FALSE;
4682 
4683     } else if (safe_str_neq(ID(to_update), ID(child))) {
4684         can_update = FALSE;
4685 
4686     } else if (can_update) {
4687 #if XML_PARSER_DEBUG
4688         crm_log_xml_trace(child, "Update match found...");
4689 #endif
4690         add_xml_object(NULL, child, to_update, FALSE);
4691     }
4692 
4693     for (child_of_child = __xml_first_child(child); child_of_child != NULL;
4694          child_of_child = __xml_next(child_of_child)) {
4695         /* only update the first one */
4696         if (can_update) {
4697             break;
4698         }
4699         can_update = update_xml_child(child_of_child, to_update);
4700     }
4701 
4702     return can_update;
4703 }
4704 
4705 int
4706 find_xml_children(xmlNode ** children, xmlNode * root,
     /* [previous][next][first][last][top][bottom][index][help] */
4707                   const char *tag, const char *field, const char *value, gboolean search_matches)
4708 {
4709     int match_found = 0;
4710 
4711     CRM_CHECK(root != NULL, return FALSE);
4712     CRM_CHECK(children != NULL, return FALSE);
4713 
4714     if (tag != NULL && safe_str_neq(tag, crm_element_name(root))) {
4715 
4716     } else if (value != NULL && safe_str_neq(value, crm_element_value(root, field))) {
4717 
4718     } else {
4719         if (*children == NULL) {
4720             *children = create_xml_node(NULL, __FUNCTION__);
4721         }
4722         add_node_copy(*children, root);
4723         match_found = 1;
4724     }
4725 
4726     if (search_matches || match_found == 0) {
4727         xmlNode *child = NULL;
4728 
4729         for (child = __xml_first_child(root); child != NULL; child = __xml_next(child)) {
4730             match_found += find_xml_children(children, child, tag, field, value, search_matches);
4731         }
4732     }
4733 
4734     return match_found;
4735 }
4736 
4737 gboolean
4738 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
     /* [previous][next][first][last][top][bottom][index][help] */
4739 {
4740     gboolean can_delete = FALSE;
4741     xmlNode *child_of_child = NULL;
4742 
4743     const char *up_id = NULL;
4744     const char *child_id = NULL;
4745     const char *right_val = NULL;
4746 
4747     CRM_CHECK(child != NULL, return FALSE);
4748     CRM_CHECK(update != NULL, return FALSE);
4749 
4750     up_id = ID(update);
4751     child_id = ID(child);
4752 
4753     if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
4754         can_delete = TRUE;
4755     }
4756     if (safe_str_neq(crm_element_name(update), crm_element_name(child))) {
4757         can_delete = FALSE;
4758     }
4759     if (can_delete && delete_only) {
4760         xmlAttrPtr pIter = NULL;
4761 
4762         for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
4763             const char *p_name = (const char *)pIter->name;
4764             const char *p_value = crm_attr_value(pIter);
4765 
4766             right_val = crm_element_value(child, p_name);
4767             if (safe_str_neq(p_value, right_val)) {
4768                 can_delete = FALSE;
4769             }
4770         }
4771     }
4772 
4773     if (can_delete && parent != NULL) {
4774         crm_log_xml_trace(child, "Delete match found...");
4775         if (delete_only || update == NULL) {
4776             free_xml(child);
4777 
4778         } else {
4779             xmlNode *tmp = copy_xml(update);
4780             xmlDoc *doc = tmp->doc;
4781             xmlNode *old = NULL;
4782 
4783             xml_accept_changes(tmp);
4784             old = xmlReplaceNode(child, tmp);
4785 
4786             if(xml_tracking_changes(tmp)) {
4787                 /* Replaced sections may have included relevant ACLs */
4788                 __xml_acl_apply(tmp);
4789             }
4790 
4791             xml_calculate_changes(old, tmp);
4792             xmlDocSetRootElement(doc, old);
4793             free_xml(old);
4794         }
4795         child = NULL;
4796         return TRUE;
4797 
4798     } else if (can_delete) {
4799         crm_log_xml_debug(child, "Cannot delete the search root");
4800         can_delete = FALSE;
4801     }
4802 
4803     child_of_child = __xml_first_child(child);
4804     while (child_of_child) {
4805         xmlNode *next = __xml_next(child_of_child);
4806 
4807         can_delete = replace_xml_child(child, child_of_child, update, delete_only);
4808 
4809         /* only delete the first one */
4810         if (can_delete) {
4811             child_of_child = NULL;
4812         } else {
4813             child_of_child = next;
4814         }
4815     }
4816 
4817     return can_delete;
4818 }
4819 
4820 /*!
4821  * \brief Create an XML name/value pair
4822  *
4823  * \param[in] parent  If not NULL, make new XML node a child of this one
4824  * \param[in] id      If not NULL, use this as ID (otherwise auto-generate)
4825  * \param[in] name    Name to use
4826  * \param[in] value   Value to use
4827  *
4828  * \return New XML object on success, NULL otherwise
4829  */
4830 xmlNode *
4831 crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
4832                       const char *value)
4833 {
4834     xmlNode *nvp;
4835 
4836     /* id can be NULL so we auto-generate one, and name can be NULL if this
4837      * will be used to delete a name/value pair by ID, but both can't be NULL
4838      */
4839     CRM_CHECK(id || name, return NULL);
4840 
4841     nvp = create_xml_node(parent, XML_CIB_TAG_NVPAIR);
4842     CRM_CHECK(nvp, return NULL);
4843 
4844     if (id) {
4845         crm_xml_add(nvp, XML_ATTR_ID, id);
4846     } else {
4847         const char *parent_id = ID(parent);
4848 
4849         crm_xml_set_id(nvp, "%s-%s",
4850                        (parent_id? parent_id : XML_CIB_TAG_NVPAIR), name);
4851     }
4852     crm_xml_add(nvp, XML_NVPAIR_ATTR_NAME, name);
4853     crm_xml_add(nvp, XML_NVPAIR_ATTR_VALUE, value);
4854     return nvp;
4855 }
4856 
4857 void
4858 hash2nvpair(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
4859 {
4860     const char *name = key;
4861     const char *s_value = value;
4862     xmlNode *xml_node = user_data;
4863 
4864     crm_create_nvpair_xml(xml_node, name, name, s_value);
4865     crm_trace("dumped: name=%s value=%s", name, s_value);
4866 }
4867 
4868 void
4869 hash2smartfield(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
4870 {
4871     const char *name = key;
4872     const char *s_value = value;
4873 
4874     xmlNode *xml_node = user_data;
4875 
4876     if (isdigit(name[0])) {
4877         xmlNode *tmp = create_xml_node(xml_node, XML_TAG_PARAM);
4878 
4879         crm_xml_add(tmp, XML_NVPAIR_ATTR_NAME, name);
4880         crm_xml_add(tmp, XML_NVPAIR_ATTR_VALUE, s_value);
4881 
4882     } else if (crm_element_value(xml_node, name) == NULL) {
4883         crm_xml_add(xml_node, name, s_value);
4884         crm_trace("dumped: %s=%s", name, s_value);
4885 
4886     } else {
4887         crm_trace("duplicate: %s=%s", name, s_value);
4888     }
4889 }
4890 
4891 void
4892 hash2field(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
4893 {
4894     const char *name = key;
4895     const char *s_value = value;
4896 
4897     xmlNode *xml_node = user_data;
4898 
4899     if (crm_element_value(xml_node, name) == NULL) {
4900         crm_xml_add(xml_node, name, s_value);
4901 
4902     } else {
4903         crm_trace("duplicate: %s=%s", name, s_value);
4904     }
4905 }
4906 
4907 void
4908 hash2metafield(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
4909 {
4910     char *crm_name = NULL;
4911 
4912     if (key == NULL || value == NULL) {
4913         return;
4914     }
4915 
4916     /* Filter out cluster-generated attributes that contain a '#' or ':'
4917      * (like fail-count and last-failure).
4918      */
4919     for (crm_name = key; *crm_name; ++crm_name) {
4920         if ((*crm_name == '#') || (*crm_name == ':')) {
4921             return;
4922         }
4923     }
4924 
4925     crm_name = crm_meta_name(key);
4926     hash2field(crm_name, value, user_data);
4927     free(crm_name);
4928 }
4929 
4930 GHashTable *
4931 xml2list(xmlNode * parent)
     /* [previous][next][first][last][top][bottom][index][help] */
4932 {
4933     xmlNode *child = NULL;
4934     xmlAttrPtr pIter = NULL;
4935     xmlNode *nvpair_list = NULL;
4936     GHashTable *nvpair_hash = crm_str_table_new();
4937 
4938     CRM_CHECK(parent != NULL, return nvpair_hash);
4939 
4940     nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE);
4941     if (nvpair_list == NULL) {
4942         crm_trace("No attributes in %s", crm_element_name(parent));
4943         crm_log_xml_trace(parent, "No attributes for resource op");
4944     }
4945 
4946     crm_log_xml_trace(nvpair_list, "Unpacking");
4947 
4948     for (pIter = crm_first_attr(nvpair_list); pIter != NULL; pIter = pIter->next) {
4949         const char *p_name = (const char *)pIter->name;
4950         const char *p_value = crm_attr_value(pIter);
4951 
4952         crm_trace("Added %s=%s", p_name, p_value);
4953 
4954         g_hash_table_insert(nvpair_hash, strdup(p_name), strdup(p_value));
4955     }
4956 
4957     for (child = __xml_first_child(nvpair_list); child != NULL; child = __xml_next(child)) {
4958         if (strcmp((const char *)child->name, XML_TAG_PARAM) == 0) {
4959             const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
4960             const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
4961 
4962             crm_trace("Added %s=%s", key, value);
4963             if (key != NULL && value != NULL) {
4964                 g_hash_table_insert(nvpair_hash, strdup(key), strdup(value));
4965             }
4966         }
4967     }
4968 
4969     return nvpair_hash;
4970 }
4971 
4972 typedef struct name_value_s {
4973     const char *name;
4974     const void *value;
4975 } name_value_t;
4976 
4977 static gint
4978 sort_pairs(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
4979 {
4980     int rc = 0;
4981     const name_value_t *pair_a = a;
4982     const name_value_t *pair_b = b;
4983 
4984     CRM_ASSERT(a != NULL);
4985     CRM_ASSERT(pair_a->name != NULL);
4986 
4987     CRM_ASSERT(b != NULL);
4988     CRM_ASSERT(pair_b->name != NULL);
4989 
4990     rc = strcmp(pair_a->name, pair_b->name);
4991     if (rc < 0) {
4992         return -1;
4993     } else if (rc > 0) {
4994         return 1;
4995     }
4996     return 0;
4997 }
4998 
4999 static void
5000 dump_pair(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
5001 {
5002     name_value_t *pair = data;
5003     xmlNode *parent = user_data;
5004 
5005     crm_xml_add(parent, pair->name, pair->value);
5006 }
5007 
5008 xmlNode *
5009 sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive)
     /* [previous][next][first][last][top][bottom][index][help] */
5010 {
5011     xmlNode *child = NULL;
5012     GListPtr sorted = NULL;
5013     GListPtr unsorted = NULL;
5014     name_value_t *pair = NULL;
5015     xmlNode *result = NULL;
5016     const char *name = NULL;
5017     xmlAttrPtr pIter = NULL;
5018 
5019     CRM_CHECK(input != NULL, return NULL);
5020 
5021     name = crm_element_name(input);
5022     CRM_CHECK(name != NULL, return NULL);
5023 
5024     result = create_xml_node(parent, name);
5025 
5026     for (pIter = crm_first_attr(input); pIter != NULL; pIter = pIter->next) {
5027         const char *p_name = (const char *)pIter->name;
5028         const char *p_value = crm_attr_value(pIter);
5029 
5030         pair = calloc(1, sizeof(name_value_t));
5031         pair->name = p_name;
5032         pair->value = p_value;
5033         unsorted = g_list_prepend(unsorted, pair);
5034         pair = NULL;
5035     }
5036 
5037     sorted = g_list_sort(unsorted, sort_pairs);
5038     g_list_foreach(sorted, dump_pair, result);
5039     g_list_free_full(sorted, free);
5040 
5041     for (child = __xml_first_child(input); child != NULL; child = __xml_next(child)) {
5042         if (recursive) {
5043             sorted_xml(child, result, recursive);
5044         } else {
5045             add_node_copy(result, child);
5046         }
5047     }
5048 
5049     return result;
5050 }
5051 
5052 xmlNode *
5053 first_named_child(xmlNode * parent, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
5054 {
5055     xmlNode *match = NULL;
5056 
5057     for (match = __xml_first_child(parent); match != NULL; match = __xml_next(match)) {
5058         /*
5059          * name == NULL gives first child regardless of name; this is
5060          * semantically incorrect in this function, but may be necessary
5061          * due to prior use of xml_child_iter_filter
5062          */
5063         if (name == NULL || strcmp((const char *)match->name, name) == 0) {
5064             return match;
5065         }
5066     }
5067     return NULL;
5068 }
5069 
5070 /*!
5071  * \brief Get next instance of same XML tag
5072  *
5073  * \param[in] sibling  XML tag to start from
5074  *
5075  * \return Next sibling XML tag with same name
5076  */
5077 xmlNode *
5078 crm_next_same_xml(xmlNode *sibling)
     /* [previous][next][first][last][top][bottom][index][help] */
5079 {
5080     xmlNode *match = __xml_next(sibling);
5081     const char *name = crm_element_name(sibling);
5082 
5083     while (match != NULL) {
5084         if (!strcmp(crm_element_name(match), name)) {
5085             return match;
5086         }
5087         match = __xml_next(match);
5088     }
5089     return NULL;
5090 }
5091 
5092 void
5093 crm_xml_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
5094 {
5095     static bool init = TRUE;
5096 
5097     if(init) {
5098         init = FALSE;
5099         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
5100          * realloc_safe()s and it can take upwards of 18 seconds (yes, seconds)
5101          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
5102          * less than 1 second.
5103          */
5104         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
5105 
5106         /* Populate and free the _private field when nodes are created and destroyed */
5107         xmlDeregisterNodeDefault(pcmkDeregisterNode);
5108         xmlRegisterNodeDefault(pcmkRegisterNode);
5109 
5110         crm_schema_init();
5111     }
5112 }
5113 
5114 void
5115 crm_xml_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
5116 {
5117     crm_info("Cleaning up memory from libxml2");
5118     crm_schema_cleanup();
5119     xmlCleanupParser();
5120 }
5121 
5122 #define XPATH_MAX 512
5123 
5124 xmlNode *
5125 expand_idref(xmlNode * input, xmlNode * top)
     /* [previous][next][first][last][top][bottom][index][help] */
5126 {
5127     const char *tag = NULL;
5128     const char *ref = NULL;
5129     xmlNode *result = input;
5130     char *xpath_string = NULL;
5131 
5132     if (result == NULL) {
5133         return NULL;
5134 
5135     } else if (top == NULL) {
5136         top = input;
5137     }
5138 
5139     tag = crm_element_name(result);
5140     ref = crm_element_value(result, XML_ATTR_IDREF);
5141 
5142     if (ref != NULL) {
5143         int offset = 0;
5144 
5145         xpath_string = calloc(1, XPATH_MAX);
5146 
5147         offset += snprintf(xpath_string + offset, XPATH_MAX - offset, "//%s[@id='%s']", tag, ref);
5148         CRM_LOG_ASSERT(offset > 0);
5149 
5150         result = get_xpath_object(xpath_string, top, LOG_ERR);
5151         if (result == NULL) {
5152             char *nodePath = (char *)xmlGetNodePath(top);
5153 
5154             crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
5155                     crm_str(nodePath));
5156             free(nodePath);
5157         }
5158     }
5159 
5160     free(xpath_string);
5161     return result;
5162 }
5163 
5164 const char *
5165 crm_element_value(xmlNode * data, const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
5166 {
5167     xmlAttr *attr = NULL;
5168 
5169     if (data == NULL) {
5170         crm_err("Couldn't find %s in NULL", name ? name : "<null>");
5171         CRM_LOG_ASSERT(data != NULL);
5172         return NULL;
5173 
5174     } else if (name == NULL) {
5175         crm_err("Couldn't find NULL in %s", crm_element_name(data));
5176         return NULL;
5177     }
5178 
5179     attr = xmlHasProp(data, (const xmlChar *)name);
5180     if (attr == NULL || attr->children == NULL) {
5181         return NULL;
5182     }
5183     return (const char *)attr->children->content;
5184 }
5185 
5186 void
5187 crm_destroy_xml(gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
5188 {
5189     free_xml(data);
5190 }

/* [previous][next][first][last][top][bottom][index][help] */