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)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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