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