1 /* 2 * Copyright 2017-2025 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__CRM_COMMON_XML_INTERNAL__H 11 #define PCMK__CRM_COMMON_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 21 #include <crm/crm.h> /* transitively imports qblog.h */ 22 #include <crm/common/output_internal.h> 23 #include <crm/common/xml_names.h> // PCMK_XA_ID, PCMK_XE_CLONE 24 25 // This file is a wrapper for other {xml_*,xpath}_internal.h headers 26 #include <crm/common/xml_comment_internal.h> 27 #include <crm/common/xml_element_internal.h> 28 #include <crm/common/xml_idref_internal.h> 29 #include <crm/common/xml_io_internal.h> 30 #include <crm/common/xml_names_internal.h> 31 #include <crm/common/xpath_internal.h> 32 33 #include <libxml/relaxng.h> 34 35 #ifdef __cplusplus 36 extern "C" { 37 #endif 38 39 /*! 40 * \brief Base for directing lib{xml2,xslt} log into standard libqb backend 41 * 42 * This macro implements the core of what can be needed for directing 43 * libxml2 or libxslt error messaging into standard, preconfigured 44 * libqb-backed log stream. 45 * 46 * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt) 47 * emits a single message by chunks (location is emitted separatedly from 48 * the message itself), so we have to take the effort to combine these 49 * chunks back to single message. Whether to do this or not is driven 50 * with \p dechunk toggle. 51 * 52 * The form of a macro was chosen for implicit deriving of __FILE__, etc. 53 * and also because static dechunking buffer should be differentiated per 54 * library (here we assume different functions referring to this macro 55 * will not ever be using both at once), preferably also per-library 56 * context of use to avoid clashes altogether. 57 * 58 * Note that we cannot use qb_logt, because callsite data have to be known 59 * at the moment of compilation, which it is not always the case -- xml_log 60 * (and unfortunately there's no clear explanation of the fail to compile). 61 * 62 * Also note that there's no explicit guard against said libraries producing 63 * never-newline-terminated chunks (which would just keep consuming memory), 64 * as it's quite improbable. Termination of the program in between the 65 * same-message chunks will raise a flag with valgrind and the likes, though. 66 * 67 * And lastly, regarding how dechunking combines with other non-message 68 * parameters -- for \p priority, most important running specification 69 * wins (possibly elevated to LOG_ERR in case of nonconformance with the 70 * newline-termination "protocol"), \p dechunk is expected to always be 71 * on once it was at the start, and the rest (\p postemit and \p prefix) 72 * are picked directly from the last chunk entry finalizing the message 73 * (also reasonable to always have it the same with all related entries). 74 * 75 * \param[in] priority Syslog priority for the message to be logged 76 * \param[in] dechunk Whether to dechunk new-line terminated message 77 * \param[in] postemit Code to be executed once message is sent out 78 * \param[in] prefix How to prefix the message or NULL for raw passing 79 * \param[in] fmt Format string as with printf-like functions 80 * \param[in] ap Variable argument list to supplement \p fmt format string 81 */ 82 #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \ 83 do { \ 84 if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \ 85 qb_log_from_external_source_va(__func__, __FILE__, (fmt), \ 86 (priority), __LINE__, 0, (ap)); \ 87 (void) (postemit); \ 88 } else { \ 89 int CXLB_len = 0; \ 90 char *CXLB_buf = NULL; \ 91 static int CXLB_buffer_len = 0; \ 92 static char *CXLB_buffer = NULL; \ 93 static uint8_t CXLB_priority = 0; \ 94 \ 95 CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \ 96 \ 97 if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \ 98 if (CXLB_len < 0) { \ 99 CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\ 100 CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \ 101 } else if (CXLB_len > 0 /* && (dechunk) */ \ 102 && CXLB_buf[CXLB_len - 1] == '\n') { \ 103 CXLB_buf[CXLB_len - 1] = '\0'; \ 104 } \ 105 if (CXLB_buffer) { \ 106 qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \ 107 CXLB_priority, __LINE__, 0, \ 108 (prefix) != NULL ? (prefix) : "", \ 109 CXLB_buffer, CXLB_buf); \ 110 free(CXLB_buffer); \ 111 } else { \ 112 qb_log_from_external_source(__func__, __FILE__, "%s%s", \ 113 (priority), __LINE__, 0, \ 114 (prefix) != NULL ? (prefix) : "", \ 115 CXLB_buf); \ 116 } \ 117 if (CXLB_len < 0) { \ 118 CXLB_buf = NULL; /* restore temporary override */ \ 119 } \ 120 CXLB_buffer = NULL; \ 121 CXLB_buffer_len = 0; \ 122 (void) (postemit); \ 123 \ 124 } else if (CXLB_buffer == NULL) { \ 125 CXLB_buffer_len = CXLB_len; \ 126 CXLB_buffer = CXLB_buf; \ 127 CXLB_buf = NULL; \ 128 CXLB_priority = (priority); /* remember as a running severest */ \ 129 \ 130 } else { \ 131 CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \ 132 memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \ 133 CXLB_buffer_len += CXLB_len; \ 134 CXLB_buffer[CXLB_buffer_len] = '\0'; \ 135 CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \ 136 } \ 137 free(CXLB_buf); \ 138 } \ 139 } while (0) 140 141 /*! 142 * \internal 143 * \brief Bit flags to control format in XML logs and dumps 144 */ 145 enum pcmk__xml_fmt_options { 146 //! Exclude certain XML attributes (for calculating digests) 147 pcmk__xml_fmt_filtered = (1 << 0), 148 149 //! Include indentation and newlines 150 pcmk__xml_fmt_pretty = (1 << 1), 151 152 //! Include the opening tag of an XML element, and include XML comments 153 pcmk__xml_fmt_open = (1 << 3), 154 155 //! Include the children of an XML element 156 pcmk__xml_fmt_children = (1 << 4), 157 158 //! Include the closing tag of an XML element 159 pcmk__xml_fmt_close = (1 << 5), 160 161 // @COMPAT Can we start including text nodes unconditionally? 162 //! Include XML text nodes 163 pcmk__xml_fmt_text = (1 << 6), 164 }; 165 166 int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, 167 int depth, uint32_t options); 168 int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml); 169 170 enum pcmk__xml_artefact_ns { 171 pcmk__xml_artefact_ns_legacy_rng = 1, 172 pcmk__xml_artefact_ns_legacy_xslt, 173 pcmk__xml_artefact_ns_base_rng, 174 pcmk__xml_artefact_ns_base_xslt, 175 }; 176 177 void pcmk__strip_xml_text(xmlNode *xml); 178 179 /*! 180 * \internal 181 * \brief Indicators of which XML characters to escape 182 * 183 * XML allows the escaping of special characters by replacing them with entity 184 * references (for example, <tt>"""</tt>) or character references (for 185 * example, <tt>" "</tt>). 186 * 187 * The special characters <tt>'&'</tt> (except as the beginning of an entity 188 * reference) and <tt>'<'</tt> are not allowed in their literal forms in XML 189 * character data. Character data is non-markup text (for example, the content 190 * of a text node). <tt>'>'</tt> is allowed under most circumstances; we escape 191 * it for safety and symmetry. 192 * 193 * For more details, see the "Character Data and Markup" section of the XML 194 * spec, currently section 2.4: 195 * https://www.w3.org/TR/xml/#dt-markup 196 * 197 * Attribute values are handled specially. 198 * * If an attribute value is delimited by single quotes, then single quotes 199 * must be escaped within the value. 200 * * Similarly, if an attribute value is delimited by double quotes, then double 201 * quotes must be escaped within the value. 202 * * A conformant XML processor replaces a literal whitespace character (tab, 203 * newline, carriage return, space) in an attribute value with a space 204 * (\c '#x20') character. However, a reference to a whitespace character (for 205 * example, \c "
" for \c '\n') does not get replaced. 206 * * For more details, see the "Attribute-Value Normalization" section of the 207 * XML spec, currently section 3.3.3. Note that the default attribute type 208 * is CDATA; we don't deal with NMTOKENS, etc.: 209 * https://www.w3.org/TR/xml/#AVNormalize 210 * 211 * Pacemaker always delimits attribute values with double quotes, so there's no 212 * need to escape single quotes. 213 * 214 * Newlines and tabs should be escaped in attribute values when XML is 215 * serialized to text, so that future parsing preserves them rather than 216 * normalizing them to spaces. 217 * 218 * We always escape carriage returns, so that they're not converted to spaces 219 * during attribute-value normalization and because displaying them as literals 220 * is messy. 221 */ 222 enum pcmk__xml_escape_type { 223 /*! 224 * For text nodes. 225 * * Escape \c '<', \c '>', and \c '&' using entity references. 226 * * Do not escape \c '\n' and \c '\t'. 227 * * Escape other non-printing characters using character references. 228 */ 229 pcmk__xml_escape_text, 230 231 /*! 232 * For attribute values. 233 * * Escape \c '<', \c '>', \c '&', and \c '"' using entity references. 234 * * Escape \c '\n', \c '\t', and other non-printing characters using 235 * character references. 236 */ 237 pcmk__xml_escape_attr, 238 239 /* @COMPAT Drop escaping of at least '\n' and '\t' for 240 * pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip, 241 * and openstack-virtual-ip resource agents no longer depend on it. 242 * 243 * At time of writing, openstack-info may set a multiline value for the 244 * openstack_ports node attribute. The other two agents query the value and 245 * require it to be on one line with no spaces. 246 */ 247 /*! 248 * For attribute values displayed in text output delimited by double quotes. 249 * * Escape \c '\n' as \c "\\n" 250 * * Escape \c '\r' as \c "\\r" 251 * * Escape \c '\t' as \c "\\t" 252 * * Escape \c '"' as \c "\\"" 253 */ 254 pcmk__xml_escape_attr_pretty, 255 }; 256 257 bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type); 258 char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type); 259 260 /*! 261 * \internal 262 * \brief Get the root directory to scan XML artefacts of given kind for 263 * 264 * \param[in] ns governs the hierarchy nesting against the inherent root dir 265 * 266 * \return root directory to scan XML artefacts of given kind for 267 */ 268 char * 269 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); 270 271 /*! 272 * \internal 273 * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) 274 * 275 * \param[in] ns denotes path forming details (parent dir, suffix) 276 * \param[in] filespec symbolic file specification to be combined with 277 * #artefact_ns to form the final path 278 * \return unwrapped path to particular XML artifact (RNG/XSLT) 279 */ 280 char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, 281 const char *filespec); 282 283 /*! 284 * \internal 285 * \brief Return first non-text child node of an XML node 286 * 287 * \param[in] parent XML node to check 288 * 289 * \return First non-text child node of \p parent (or NULL if none) 290 */ 291 static inline xmlNode * 292 pcmk__xml_first_child(const xmlNode *parent) /**/ 293 { 294 xmlNode *child = (parent? parent->children : NULL); 295 296 while (child && (child->type == XML_TEXT_NODE)) { 297 child = child->next; 298 } 299 return child; 300 } 301 302 /*! 303 * \internal 304 * \brief Return next non-text sibling node of an XML node 305 * 306 * \param[in] child XML node to check 307 * 308 * \return Next non-text sibling of \p child (or NULL if none) 309 */ 310 static inline xmlNode * 311 pcmk__xml_next(const xmlNode *child) /*
*/ 312 { 313 xmlNode *next = (child? child->next : NULL); 314 315 while (next && (next->type == XML_TEXT_NODE)) { 316 next = next->next; 317 } 318 return next; 319 } 320 321 void pcmk__xml_free(xmlNode *xml); 322 void pcmk__xml_free_doc(xmlDoc *doc); 323 xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src); 324 325 /*! 326 * \internal 327 * \brief Flags for operations affecting XML attributes 328 */ 329 enum pcmk__xa_flags { 330 //! Flag has no effect 331 pcmk__xaf_none = 0U, 332 333 //! Don't overwrite existing values 334 pcmk__xaf_no_overwrite = (1U << 0), 335 336 /*! 337 * Treat values as score updates where possible (see 338 * \c pcmk__xe_set_score()) 339 */ 340 pcmk__xaf_score_update = (1U << 1), 341 }; 342 343 void pcmk__xml_sanitize_id(char *id); 344 345 /* internal XML-related utilities */ 346 347 /*! 348 * \internal 349 * \brief Flags related to XML change tracking and ACLs 350 */ 351 enum pcmk__xml_flags { 352 //! This flag has no effect 353 pcmk__xf_none = UINT32_C(0), 354 355 /*! 356 * Node was created or modified, or one of its descendants was created, 357 * modified, moved, or deleted. 358 */ 359 pcmk__xf_dirty = (UINT32_C(1) << 0), 360 361 //! Node was deleted (set for attribute only) 362 pcmk__xf_deleted = (UINT32_C(1) << 1), 363 364 //! Node was created 365 pcmk__xf_created = (UINT32_C(1) << 2), 366 367 //! Node was modified 368 pcmk__xf_modified = (UINT32_C(1) << 3), 369 370 /*! 371 * \brief Tracking is enabled (set for document only) 372 * 373 * Call \c pcmk__xml_commit_changes() before setting this flag if a clean 374 * start for tracking is needed. 375 */ 376 pcmk__xf_tracking = (UINT32_C(1) << 4), 377 378 //! Skip counting this node when getting a node's position among siblings 379 pcmk__xf_skip = (UINT32_C(1) << 6), 380 381 //! Node was moved 382 pcmk__xf_moved = (UINT32_C(1) << 7), 383 384 //! ACLs are enabled (set for document only) 385 pcmk__xf_acl_enabled = (UINT32_C(1) << 8), 386 387 /* @TODO Consider splitting the ACL permission flags (pcmk__xf_acl_read, 388 * pcmk__xf_acl_write, pcmk__xf_acl_write, and pcmk__xf_acl_create) into a 389 * separate enum and reserving this enum for tracking-related flags. 390 * 391 * The ACL permission flags have various meanings in different contexts (for 392 * example, what permission an ACL grants or denies; what permissions the 393 * current ACL user has for a given XML node; and possibly others). And 394 * for xml_acl_t objects, they're used in exclusive mode (exactly one is 395 * set), rather than as flags. 396 */ 397 398 //! ACL read permission 399 pcmk__xf_acl_read = (UINT32_C(1) << 9), 400 401 //! ACL write permission (implies read permission in most or all contexts) 402 pcmk__xf_acl_write = (UINT32_C(1) << 10), 403 404 //! ACL deny permission (that is, no permission) 405 pcmk__xf_acl_deny = (UINT32_C(1) << 11), 406 407 /*! 408 * ACL create permission for attributes (if attribute exists, this is mapped 409 * to \c pcmk__xf_acl_write) 410 */ 411 pcmk__xf_acl_create = (UINT32_C(1) << 12), 412 413 //! ACLs deny the user access (set for document only) 414 pcmk__xf_acl_denied = (UINT32_C(1) << 13), 415 416 //! Ignore attribute moves within an element (set for document only) 417 pcmk__xf_ignore_attr_pos = (UINT32_C(1) << 14), 418 }; 419 420 void pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags); 421 bool pcmk__xml_doc_all_flags_set(const xmlDoc *xml, uint32_t flags); 422 423 void pcmk__xml_commit_changes(xmlDoc *doc); 424 void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml); 425 426 bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), 427 void *user_data); 428 429 static inline const char * 430 pcmk__xml_attr_value(const xmlAttr *attr) /*
*/ 431 { 432 return ((attr == NULL) || (attr->children == NULL))? NULL 433 : (const char *) attr->children->content; 434 } 435 436 /*! 437 * \internal 438 * \brief Check whether a given CIB element was modified in a CIB patchset 439 * 440 * \param[in] patchset CIB XML patchset 441 * \param[in] element XML tag of CIB element to check (\c NULL is equivalent 442 * to \c PCMK_XE_CIB). Supported values include any CIB 443 * element supported by \c pcmk__cib_abs_xpath_for(). 444 * 445 * \return \c true if \p element was modified, or \c false otherwise 446 */ 447 bool pcmk__cib_element_in_patchset(const xmlNode *patchset, 448 const char *element); 449 450 #ifdef __cplusplus 451 } 452 #endif 453 454 #endif // PCMK__CRM_COMMON_XML_INTERNAL__H