root/include/crm/common/xml_internal.h

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

INCLUDED FROM


DEFINITIONS

This source file includes following definitions.
  1. pcmk__xml_first_child
  2. pcmk__xml_next
  3. pcmk__xml_attr_value

   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>"&quot;"</tt>) or character references (for
 185  * example, <tt>"&#13;"</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 "&#x0A;" 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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

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