1 /* 2 * Copyright 2017-2024 the Pacemaker project contributors 3 * 4 * The version control history for this file may have further details. 5 * 6 * This source code is licensed under the GNU Lesser General Public License 7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. 8 */ 9 10 #ifndef PCMK__XML_INTERNAL__H 11 #define PCMK__XML_INTERNAL__H 12 13 /* 14 * Internal-only wrappers for and extensions to libxml2 (libxslt) 15 */ 16 17 #include <stdlib.h> 18 #include <stdint.h> // uint32_t 19 #include <stdio.h> 20 #include <string.h> 21 22 #include <crm/crm.h> /* transitively imports qblog.h */ 23 #include <crm/common/output_internal.h> 24 #include <crm/common/xml_io_internal.h> 25 #include <crm/common/xml_names_internal.h> // PCMK__XE_PROMOTABLE_LEGACY 26 27 #include <libxml/relaxng.h> 28 29 /*! 30 * \brief Base for directing lib{xml2,xslt} log into standard libqb backend 31 * 32 * This macro implements the core of what can be needed for directing 33 * libxml2 or libxslt error messaging into standard, preconfigured 34 * libqb-backed log stream. 35 * 36 * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt) 37 * emits a single message by chunks (location is emitted separatedly from 38 * the message itself), so we have to take the effort to combine these 39 * chunks back to single message. Whether to do this or not is driven 40 * with \p dechunk toggle. 41 * 42 * The form of a macro was chosen for implicit deriving of __FILE__, etc. 43 * and also because static dechunking buffer should be differentiated per 44 * library (here we assume different functions referring to this macro 45 * will not ever be using both at once), preferably also per-library 46 * context of use to avoid clashes altogether. 47 * 48 * Note that we cannot use qb_logt, because callsite data have to be known 49 * at the moment of compilation, which it is not always the case -- xml_log 50 * (and unfortunately there's no clear explanation of the fail to compile). 51 * 52 * Also note that there's no explicit guard against said libraries producing 53 * never-newline-terminated chunks (which would just keep consuming memory), 54 * as it's quite improbable. Termination of the program in between the 55 * same-message chunks will raise a flag with valgrind and the likes, though. 56 * 57 * And lastly, regarding how dechunking combines with other non-message 58 * parameters -- for \p priority, most important running specification 59 * wins (possibly elevated to LOG_ERR in case of nonconformance with the 60 * newline-termination "protocol"), \p dechunk is expected to always be 61 * on once it was at the start, and the rest (\p postemit and \p prefix) 62 * are picked directly from the last chunk entry finalizing the message 63 * (also reasonable to always have it the same with all related entries). 64 * 65 * \param[in] priority Syslog priority for the message to be logged 66 * \param[in] dechunk Whether to dechunk new-line terminated message 67 * \param[in] postemit Code to be executed once message is sent out 68 * \param[in] prefix How to prefix the message or NULL for raw passing 69 * \param[in] fmt Format string as with printf-like functions 70 * \param[in] ap Variable argument list to supplement \p fmt format string 71 */ 72 #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \ 73 do { \ 74 if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \ 75 qb_log_from_external_source_va(__func__, __FILE__, (fmt), \ 76 (priority), __LINE__, 0, (ap)); \ 77 (void) (postemit); \ 78 } else { \ 79 int CXLB_len = 0; \ 80 char *CXLB_buf = NULL; \ 81 static int CXLB_buffer_len = 0; \ 82 static char *CXLB_buffer = NULL; \ 83 static uint8_t CXLB_priority = 0; \ 84 \ 85 CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \ 86 \ 87 if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \ 88 if (CXLB_len < 0) { \ 89 CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\ 90 CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \ 91 } else if (CXLB_len > 0 /* && (dechunk) */ \ 92 && CXLB_buf[CXLB_len - 1] == '\n') { \ 93 CXLB_buf[CXLB_len - 1] = '\0'; \ 94 } \ 95 if (CXLB_buffer) { \ 96 qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \ 97 CXLB_priority, __LINE__, 0, \ 98 (prefix) != NULL ? (prefix) : "", \ 99 CXLB_buffer, CXLB_buf); \ 100 free(CXLB_buffer); \ 101 } else { \ 102 qb_log_from_external_source(__func__, __FILE__, "%s%s", \ 103 (priority), __LINE__, 0, \ 104 (prefix) != NULL ? (prefix) : "", \ 105 CXLB_buf); \ 106 } \ 107 if (CXLB_len < 0) { \ 108 CXLB_buf = NULL; /* restore temporary override */ \ 109 } \ 110 CXLB_buffer = NULL; \ 111 CXLB_buffer_len = 0; \ 112 (void) (postemit); \ 113 \ 114 } else if (CXLB_buffer == NULL) { \ 115 CXLB_buffer_len = CXLB_len; \ 116 CXLB_buffer = CXLB_buf; \ 117 CXLB_buf = NULL; \ 118 CXLB_priority = (priority); /* remember as a running severest */ \ 119 \ 120 } else { \ 121 CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \ 122 memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \ 123 CXLB_buffer_len += CXLB_len; \ 124 CXLB_buffer[CXLB_buffer_len] = '\0'; \ 125 CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \ 126 } \ 127 free(CXLB_buf); \ 128 } \ 129 } while (0) 130 131 /* 132 * \enum pcmk__xml_fmt_options 133 * \brief Bit flags to control format in XML logs and dumps 134 */ 135 enum pcmk__xml_fmt_options { 136 //! Exclude certain XML attributes (for calculating digests) 137 pcmk__xml_fmt_filtered = (1 << 0), 138 139 //! Include indentation and newlines 140 pcmk__xml_fmt_pretty = (1 << 1), 141 142 //! Include the opening tag of an XML element, and include XML comments 143 pcmk__xml_fmt_open = (1 << 3), 144 145 //! Include the children of an XML element 146 pcmk__xml_fmt_children = (1 << 4), 147 148 //! Include the closing tag of an XML element 149 pcmk__xml_fmt_close = (1 << 5), 150 151 // @COMPAT Can we start including text nodes unconditionally? 152 //! Include XML text nodes 153 pcmk__xml_fmt_text = (1 << 6), 154 155 // @COMPAT Remove when v1 patchsets are removed 156 //! Log a created XML subtree 157 pcmk__xml_fmt_diff_plus = (1 << 7), 158 159 // @COMPAT Remove when v1 patchsets are removed 160 //! Log a removed XML subtree 161 pcmk__xml_fmt_diff_minus = (1 << 8), 162 163 // @COMPAT Remove when v1 patchsets are removed 164 //! Log a minimal version of an XML diff (only showing the changes) 165 pcmk__xml_fmt_diff_short = (1 << 9), 166 }; 167 168 int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, 169 int depth, uint32_t options); 170 int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml); 171 172 /* XML search strings for guest, remote and pacemaker_remote nodes */ 173 174 /* search string to find CIB resources entries for cluster nodes */ 175 #define PCMK__XP_MEMBER_NODE_CONFIG \ 176 "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \ 177 "/" PCMK_XE_NODE \ 178 "[not(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_MEMBER "']" 179 180 /* search string to find CIB resources entries for guest nodes */ 181 #define PCMK__XP_GUEST_NODE_CONFIG \ 182 "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ 183 "//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \ 184 "[@" PCMK_XA_NAME "='" PCMK_META_REMOTE_NODE "']" 185 186 /* search string to find CIB resources entries for remote nodes */ 187 #define PCMK__XP_REMOTE_NODE_CONFIG \ 188 "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ 189 "[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \ 190 "[@" PCMK_XA_PROVIDER "='pacemaker']" 191 192 /* search string to find CIB node status entries for pacemaker_remote nodes */ 193 #define PCMK__XP_REMOTE_NODE_STATUS \ 194 "//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \ 195 "[@" PCMK_XA_REMOTE_NODE "='" PCMK_VALUE_TRUE "']" 196 /*! 197 * \internal 198 * \brief Serialize XML (using libxml) into provided descriptor 199 * 200 * \param[in] fd File descriptor to (piece-wise) write to 201 * \param[in] cur XML subtree to proceed 202 * 203 * \return a standard Pacemaker return code 204 */ 205 int pcmk__xml2fd(int fd, xmlNode *cur); 206 207 enum pcmk__xml_artefact_ns { 208 pcmk__xml_artefact_ns_legacy_rng = 1, 209 pcmk__xml_artefact_ns_legacy_xslt, 210 pcmk__xml_artefact_ns_base_rng, 211 pcmk__xml_artefact_ns_base_xslt, 212 }; 213 214 void pcmk__strip_xml_text(xmlNode *xml); 215 const char *pcmk__xe_add_last_written(xmlNode *xe); 216 217 xmlNode *pcmk__xe_first_child(const xmlNode *parent, const char *node_name, 218 const char *attr_n, const char *attr_v); 219 220 221 void pcmk__xe_remove_attr(xmlNode *element, const char *name); 222 bool pcmk__xe_remove_attr_cb(xmlNode *xml, void *user_data); 223 void pcmk__xe_remove_matching_attrs(xmlNode *element, 224 bool (*match)(xmlAttrPtr, void *), 225 void *user_data); 226 int pcmk__xe_delete_match(xmlNode *xml, xmlNode *search); 227 int pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace); 228 int pcmk__xe_update_match(xmlNode *xml, xmlNode *update, uint32_t flags); 229 230 GString *pcmk__element_xpath(const xmlNode *xml); 231 232 /*! 233 * \internal 234 * \enum pcmk__xml_escape_type 235 * \brief Indicators of which XML characters to escape 236 * 237 * XML allows the escaping of special characters by replacing them with entity 238 * references (for example, <tt>"""</tt>) or character references (for 239 * example, <tt>" "</tt>). 240 * 241 * The special characters <tt>'&'</tt> (except as the beginning of an entity 242 * reference) and <tt>'<'</tt> are not allowed in their literal forms in XML 243 * character data. Character data is non-markup text (for example, the content 244 * of a text node). <tt>'>'</tt> is allowed under most circumstances; we escape 245 * it for safety and symmetry. 246 * 247 * For more details, see the "Character Data and Markup" section of the XML 248 * spec, currently section 2.4: 249 * https://www.w3.org/TR/xml/#dt-markup 250 * 251 * Attribute values are handled specially. 252 * * If an attribute value is delimited by single quotes, then single quotes 253 * must be escaped within the value. 254 * * Similarly, if an attribute value is delimited by double quotes, then double 255 * quotes must be escaped within the value. 256 * * A conformant XML processor replaces a literal whitespace character (tab, 257 * newline, carriage return, space) in an attribute value with a space 258 * (\c '#x20') character. However, a reference to a whitespace character (for 259 * example, \c "
" for \c '\n') does not get replaced. 260 * * For more details, see the "Attribute-Value Normalization" section of the 261 * XML spec, currently section 3.3.3. Note that the default attribute type 262 * is CDATA; we don't deal with NMTOKENS, etc.: 263 * https://www.w3.org/TR/xml/#AVNormalize 264 * 265 * Pacemaker always delimits attribute values with double quotes, so there's no 266 * need to escape single quotes. 267 * 268 * Newlines and tabs should be escaped in attribute values when XML is 269 * serialized to text, so that future parsing preserves them rather than 270 * normalizing them to spaces. 271 * 272 * We always escape carriage returns, so that they're not converted to spaces 273 * during attribute-value normalization and because displaying them as literals 274 * is messy. 275 */ 276 enum pcmk__xml_escape_type { 277 /*! 278 * For text nodes. 279 * * Escape \c '<', \c '>', and \c '&' using entity references. 280 * * Do not escape \c '\n' and \c '\t'. 281 * * Escape other non-printing characters using character references. 282 */ 283 pcmk__xml_escape_text, 284 285 /*! 286 * For attribute values. 287 * * Escape \c '<', \c '>', \c '&', and \c '"' using entity references. 288 * * Escape \c '\n', \c '\t', and other non-printing characters using 289 * character references. 290 */ 291 pcmk__xml_escape_attr, 292 293 /* @COMPAT Drop escaping of at least '\n' and '\t' for 294 * pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip, 295 * and openstack-virtual-ip resource agents no longer depend on it. 296 * 297 * At time of writing, openstack-info may set a multiline value for the 298 * openstack_ports node attribute. The other two agents query the value and 299 * require it to be on one line with no spaces. 300 */ 301 /*! 302 * For attribute values displayed in text output delimited by double quotes. 303 * * Escape \c '\n' as \c "\\n" 304 * * Escape \c '\r' as \c "\\r" 305 * * Escape \c '\t' as \c "\\t" 306 * * Escape \c '"' as \c "\\"" 307 */ 308 pcmk__xml_escape_attr_pretty, 309 }; 310 311 bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type); 312 char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type); 313 314 /*! 315 * \internal 316 * \brief Get the root directory to scan XML artefacts of given kind for 317 * 318 * \param[in] ns governs the hierarchy nesting against the inherent root dir 319 * 320 * \return root directory to scan XML artefacts of given kind for 321 */ 322 char * 323 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); 324 325 /*! 326 * \internal 327 * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) 328 * 329 * \param[in] ns denotes path forming details (parent dir, suffix) 330 * \param[in] filespec symbolic file specification to be combined with 331 * #artefact_ns to form the final path 332 * \return unwrapped path to particular XML artifact (RNG/XSLT) 333 */ 334 char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, 335 const char *filespec); 336 337 /*! 338 * \internal 339 * \brief Retrieve the value of the \c PCMK_XA_ID XML attribute 340 * 341 * \param[in] xml XML element to check 342 * 343 * \return Value of the \c PCMK_XA_ID attribute (may be \c NULL) 344 */ 345 static inline const char * 346 pcmk__xe_id(const xmlNode *xml) /* */ 347 { 348 return crm_element_value(xml, PCMK_XA_ID); 349 } 350 351 /*! 352 * \internal 353 * \brief Check whether an XML element is of a particular type 354 * 355 * \param[in] xml XML element to compare 356 * \param[in] name XML element name to compare 357 * 358 * \return \c true if \p xml is of type \p name, otherwise \c false 359 */ 360 static inline bool 361 pcmk__xe_is(const xmlNode *xml, const char *name) /* */ 362 { 363 return (xml != NULL) && (xml->name != NULL) && (name != NULL) 364 && (strcmp((const char *) xml->name, name) == 0); 365 } 366 367 /*! 368 * \internal 369 * \brief Return first non-text child node of an XML node 370 * 371 * \param[in] parent XML node to check 372 * 373 * \return First non-text child node of \p parent (or NULL if none) 374 */ 375 static inline xmlNode * 376 pcmk__xml_first_child(const xmlNode *parent) /* */ 377 { 378 xmlNode *child = (parent? parent->children : NULL); 379 380 while (child && (child->type == XML_TEXT_NODE)) { 381 child = child->next; 382 } 383 return child; 384 } 385 386 /*! 387 * \internal 388 * \brief Return next non-text sibling node of an XML node 389 * 390 * \param[in] child XML node to check 391 * 392 * \return Next non-text sibling of \p child (or NULL if none) 393 */ 394 static inline xmlNode * 395 pcmk__xml_next(const xmlNode *child) /* */ 396 { 397 xmlNode *next = (child? child->next : NULL); 398 399 while (next && (next->type == XML_TEXT_NODE)) { 400 next = next->next; 401 } 402 return next; 403 } 404 405 /*! 406 * \internal 407 * \brief Return next non-text sibling element of an XML element 408 * 409 * \param[in] child XML element to check 410 * 411 * \return Next sibling element of \p child (or NULL if none) 412 */ 413 static inline xmlNode * 414 pcmk__xe_next(const xmlNode *child) /* */ 415 { 416 xmlNode *next = child? child->next : NULL; 417 418 while (next && (next->type != XML_ELEMENT_NODE)) { 419 next = next->next; 420 } 421 return next; 422 } 423 424 xmlNode *pcmk__xe_create(xmlNode *parent, const char *name); 425 xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src); 426 xmlNode *pcmk__xe_next_same(const xmlNode *node); 427 428 void pcmk__xe_set_content(xmlNode *node, const char *format, ...) /* */ 429 G_GNUC_PRINTF(2, 3); 430 431 /*! 432 * \internal 433 * \enum pcmk__xa_flags 434 * \brief Flags for operations affecting XML attributes 435 */ 436 enum pcmk__xa_flags { 437 //! Flag has no effect 438 pcmk__xaf_none = 0U, 439 440 //! Don't overwrite existing values 441 pcmk__xaf_no_overwrite = (1U << 0), 442 443 /*! 444 * Treat values as score updates where possible (see 445 * \c pcmk__xe_set_score()) 446 */ 447 pcmk__xaf_score_update = (1U << 1), 448 }; 449 450 int pcmk__xe_copy_attrs(xmlNode *target, const xmlNode *src, uint32_t flags); 451 452 /*! 453 * \internal 454 * \brief Like pcmk__xe_set_props, but takes a va_list instead of 455 * arguments directly. 456 * 457 * \param[in,out] node XML to add attributes to 458 * \param[in] pairs NULL-terminated list of name/value pairs to add 459 */ 460 void 461 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs); 462 463 /*! 464 * \internal 465 * \brief Add a NULL-terminated list of name/value pairs to the given 466 * XML node as properties. 467 * 468 * \param[in,out] node XML node to add properties to 469 * \param[in] ... NULL-terminated list of name/value pairs 470 * 471 * \note A NULL name terminates the arguments; a NULL value will be skipped. 472 */ 473 void 474 pcmk__xe_set_props(xmlNodePtr node, ...) /* */ 475 G_GNUC_NULL_TERMINATED; 476 477 /*! 478 * \internal 479 * \brief Get first attribute of an XML element 480 * 481 * \param[in] xe XML element to check 482 * 483 * \return First attribute of \p xe (or NULL if \p xe is NULL or has none) 484 */ 485 static inline xmlAttr * 486 pcmk__xe_first_attr(const xmlNode *xe) 487 { 488 return (xe == NULL)? NULL : xe->properties; 489 } 490 491 /*! 492 * \internal 493 * \brief Extract the ID attribute from an XML element 494 * 495 * \param[in] xpath String to search 496 * \param[in] node Node to get the ID for 497 * 498 * \return ID attribute of \p node in xpath string \p xpath 499 */ 500 char * 501 pcmk__xpath_node_id(const char *xpath, const char *node); 502 503 /*! 504 * \internal 505 * \brief Print an informational message if an xpath query returned multiple 506 * items with the same ID. 507 * 508 * \param[in,out] out The output object 509 * \param[in] search The xpath search result, most typically the result of 510 * calling cib->cmds->query(). 511 * \param[in] name The name searched for 512 */ 513 void 514 pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, 515 const char *name); 516 517 /* internal XML-related utilities */ 518 519 enum xml_private_flags { 520 pcmk__xf_none = 0x0000, 521 pcmk__xf_dirty = 0x0001, 522 pcmk__xf_deleted = 0x0002, 523 pcmk__xf_created = 0x0004, 524 pcmk__xf_modified = 0x0008, 525 526 pcmk__xf_tracking = 0x0010, 527 pcmk__xf_processed = 0x0020, 528 pcmk__xf_skip = 0x0040, 529 pcmk__xf_moved = 0x0080, 530 531 pcmk__xf_acl_enabled = 0x0100, 532 pcmk__xf_acl_read = 0x0200, 533 pcmk__xf_acl_write = 0x0400, 534 pcmk__xf_acl_deny = 0x0800, 535 536 pcmk__xf_acl_create = 0x1000, 537 pcmk__xf_acl_denied = 0x2000, 538 pcmk__xf_lazy = 0x4000, 539 }; 540 541 void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); 542 543 /*! 544 * \internal 545 * \brief Iterate over child elements of \p xml 546 * 547 * This function iterates over the children of \p xml, performing the 548 * callback function \p handler on each node. If the callback returns 549 * a value other than pcmk_rc_ok, the iteration stops and the value is 550 * returned. It is therefore possible that not all children will be 551 * visited. 552 * 553 * \param[in,out] xml The starting XML node. Can be NULL. 554 * \param[in] child_element_name The name that the node must match in order 555 * for \p handler to be run. If NULL, all 556 * child elements will match. 557 * \param[in] handler The callback function. 558 * \param[in,out] userdata User data to pass to the callback function. 559 * Can be NULL. 560 * 561 * \return Standard Pacemaker return code 562 */ 563 int 564 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, 565 int (*handler)(xmlNode *xml, void *userdata), 566 void *userdata); 567 568 bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), 569 void *user_data); 570 571 static inline const char * 572 pcmk__xml_attr_value(const xmlAttr *attr) /* */ 573 { 574 return ((attr == NULL) || (attr->children == NULL))? NULL 575 : (const char *) attr->children->content; 576 } 577 578 // @COMPAT Remove when v1 patchsets are removed 579 xmlNode *pcmk__diff_v1_xml_object(xmlNode *left, xmlNode *right, bool suppress); 580 581 // @COMPAT Drop when PCMK__XE_PROMOTABLE_LEGACY is removed 582 static inline const char * 583 pcmk__map_element_name(const xmlNode *xml) /* */ 584 { 585 if (xml == NULL) { 586 return NULL; 587 } else if (pcmk__xe_is(xml, PCMK__XE_PROMOTABLE_LEGACY)) { 588 return PCMK_XE_CLONE; 589 } else { 590 return (const char *) xml->name; 591 } 592 } 593 594 #endif // PCMK__XML_INTERNAL__H