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)
/* ![[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)
*/
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)
/* ![[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)
*/
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)
/* ![[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)
*/
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