1 /*
2 * Copyright 2017-2023 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__XML_INTERNAL__H
11 # define PCMK__XML_INTERNAL__H
12
13 /*
14 * Internal-only wrappers for and extensions to libxml2 (libxslt)
15 */
16
17 # include <stdlib.h>
18 # include <stdio.h>
19 # include <string.h>
20
21 # include <crm/crm.h> /* transitively imports qblog.h */
22 # include <crm/common/output_internal.h>
23
24 # include <libxml/relaxng.h>
25
26 /*!
27 * \brief Base for directing lib{xml2,xslt} log into standard libqb backend
28 *
29 * This macro implements the core of what can be needed for directing
30 * libxml2 or libxslt error messaging into standard, preconfigured
31 * libqb-backed log stream.
32 *
33 * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
34 * emits a single message by chunks (location is emitted separatedly from
35 * the message itself), so we have to take the effort to combine these
36 * chunks back to single message. Whether to do this or not is driven
37 * with \p dechunk toggle.
38 *
39 * The form of a macro was chosen for implicit deriving of __FILE__, etc.
40 * and also because static dechunking buffer should be differentiated per
41 * library (here we assume different functions referring to this macro
42 * will not ever be using both at once), preferably also per-library
43 * context of use to avoid clashes altogether.
44 *
45 * Note that we cannot use qb_logt, because callsite data have to be known
46 * at the moment of compilation, which it is not always the case -- xml_log
47 * (and unfortunately there's no clear explanation of the fail to compile).
48 *
49 * Also note that there's no explicit guard against said libraries producing
50 * never-newline-terminated chunks (which would just keep consuming memory),
51 * as it's quite improbable. Termination of the program in between the
52 * same-message chunks will raise a flag with valgrind and the likes, though.
53 *
54 * And lastly, regarding how dechunking combines with other non-message
55 * parameters -- for \p priority, most important running specification
56 * wins (possibly elevated to LOG_ERR in case of nonconformance with the
57 * newline-termination "protocol"), \p dechunk is expected to always be
58 * on once it was at the start, and the rest (\p postemit and \p prefix)
59 * are picked directly from the last chunk entry finalizing the message
60 * (also reasonable to always have it the same with all related entries).
61 *
62 * \param[in] priority Syslog priority for the message to be logged
63 * \param[in] dechunk Whether to dechunk new-line terminated message
64 * \param[in] postemit Code to be executed once message is sent out
65 * \param[in] prefix How to prefix the message or NULL for raw passing
66 * \param[in] fmt Format string as with printf-like functions
67 * \param[in] ap Variable argument list to supplement \p fmt format string
68 */
69 #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
70 do { \
71 if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
72 qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
73 (priority), __LINE__, 0, (ap)); \
74 (void) (postemit); \
75 } else { \
76 int CXLB_len = 0; \
77 char *CXLB_buf = NULL; \
78 static int CXLB_buffer_len = 0; \
79 static char *CXLB_buffer = NULL; \
80 static uint8_t CXLB_priority = 0; \
81 \
82 CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
83 \
84 if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
85 if (CXLB_len < 0) { \
86 CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
87 CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
88 } else if (CXLB_len > 0 /* && (dechunk) */ \
89 && CXLB_buf[CXLB_len - 1] == '\n') { \
90 CXLB_buf[CXLB_len - 1] = '\0'; \
91 } \
92 if (CXLB_buffer) { \
93 qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
94 CXLB_priority, __LINE__, 0, \
95 (prefix) != NULL ? (prefix) : "", \
96 CXLB_buffer, CXLB_buf); \
97 free(CXLB_buffer); \
98 } else { \
99 qb_log_from_external_source(__func__, __FILE__, "%s%s", \
100 (priority), __LINE__, 0, \
101 (prefix) != NULL ? (prefix) : "", \
102 CXLB_buf); \
103 } \
104 if (CXLB_len < 0) { \
105 CXLB_buf = NULL; /* restore temporary override */ \
106 } \
107 CXLB_buffer = NULL; \
108 CXLB_buffer_len = 0; \
109 (void) (postemit); \
110 \
111 } else if (CXLB_buffer == NULL) { \
112 CXLB_buffer_len = CXLB_len; \
113 CXLB_buffer = CXLB_buf; \
114 CXLB_buf = NULL; \
115 CXLB_priority = (priority); /* remember as a running severest */ \
116 \
117 } else { \
118 CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
119 memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
120 CXLB_buffer_len += CXLB_len; \
121 CXLB_buffer[CXLB_buffer_len] = '\0'; \
122 CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
123 } \
124 free(CXLB_buf); \
125 } \
126 } while (0)
127
128 /*
129 * \enum pcmk__xml_fmt_options
130 * \brief Bit flags to control format in XML logs and dumps
131 */
132 enum pcmk__xml_fmt_options {
133 //! Exclude certain XML attributes (for calculating digests)
134 pcmk__xml_fmt_filtered = (1 << 0),
135
136 //! Include indentation and newlines
137 pcmk__xml_fmt_pretty = (1 << 1),
138
139 //! Include the opening tag of an XML element, and include XML comments
140 pcmk__xml_fmt_open = (1 << 3),
141
142 //! Include the children of an XML element
143 pcmk__xml_fmt_children = (1 << 4),
144
145 //! Include the closing tag of an XML element
146 pcmk__xml_fmt_close = (1 << 5),
147
148 //! Include XML text nodes
149 pcmk__xml_fmt_text = (1 << 6),
150
151 // @COMPAT Remove when v1 patchsets are removed
152 //! Log a created XML subtree
153 pcmk__xml_fmt_diff_plus = (1 << 7),
154
155 // @COMPAT Remove when v1 patchsets are removed
156 //! Log a removed XML subtree
157 pcmk__xml_fmt_diff_minus = (1 << 8),
158
159 // @COMPAT Remove when v1 patchsets are removed
160 //! Log a minimal version of an XML diff (only showing the changes)
161 pcmk__xml_fmt_diff_short = (1 << 9),
162 };
163
164 int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
165 int depth, uint32_t options);
166 int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml);
167
168 /* XML search strings for guest, remote and pacemaker_remote nodes */
169
170 /* search string to find CIB resources entries for cluster nodes */
171 #define PCMK__XP_MEMBER_NODE_CONFIG \
172 "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \
173 "/" XML_CIB_TAG_NODE "[not(@type) or @type='member']"
174
175 /* search string to find CIB resources entries for guest nodes */
176 #define PCMK__XP_GUEST_NODE_CONFIG \
177 "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
178 "//" XML_TAG_META_SETS "//" XML_CIB_TAG_NVPAIR \
179 "[@name='" XML_RSC_ATTR_REMOTE_NODE "']"
180
181 /* search string to find CIB resources entries for remote nodes */
182 #define PCMK__XP_REMOTE_NODE_CONFIG \
183 "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
184 "[@type='remote'][@provider='pacemaker']"
185
186 /* search string to find CIB node status entries for pacemaker_remote nodes */
187 #define PCMK__XP_REMOTE_NODE_STATUS \
188 "//" XML_TAG_CIB "//" XML_CIB_TAG_STATUS "//" XML_CIB_TAG_STATE \
189 "[@" XML_NODE_IS_REMOTE "='true']"
190 /*!
191 * \internal
192 * \brief Serialize XML (using libxml) into provided descriptor
193 *
194 * \param[in] fd File descriptor to (piece-wise) write to
195 * \param[in] cur XML subtree to proceed
196 *
197 * \return a standard Pacemaker return code
198 */
199 int pcmk__xml2fd(int fd, xmlNode *cur);
200
201 enum pcmk__xml_artefact_ns {
202 pcmk__xml_artefact_ns_legacy_rng = 1,
203 pcmk__xml_artefact_ns_legacy_xslt,
204 pcmk__xml_artefact_ns_base_rng,
205 pcmk__xml_artefact_ns_base_xslt,
206 };
207
208 void pcmk__strip_xml_text(xmlNode *xml);
209 const char *pcmk__xe_add_last_written(xmlNode *xe);
210
211 xmlNode *pcmk__xe_match(const xmlNode *parent, const char *node_name,
212 const char *attr_n, const char *attr_v);
213
214 void pcmk__xe_remove_matching_attrs(xmlNode *element,
215 bool (*match)(xmlAttrPtr, void *),
216 void *user_data);
217
218 GString *pcmk__element_xpath(const xmlNode *xml);
219
220 /*!
221 * \internal
222 * \brief Get the root directory to scan XML artefacts of given kind for
223 *
224 * \param[in] ns governs the hierarchy nesting against the inherent root dir
225 *
226 * \return root directory to scan XML artefacts of given kind for
227 */
228 char *
229 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
230
231 /*!
232 * \internal
233 * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
234 *
235 * \param[in] ns denotes path forming details (parent dir, suffix)
236 * \param[in] filespec symbolic file specification to be combined with
237 * #artefact_ns to form the final path
238 * \return unwrapped path to particular XML artifact (RNG/XSLT)
239 */
240 char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
241 const char *filespec);
242
243 /*!
244 * \internal
245 * \brief Check whether an XML element is of a particular type
246 *
247 * \param[in] xml XML element to compare
248 * \param[in] name XML element name to compare
249 *
250 * \return \c true if \p xml is of type \p name, otherwise \c false
251 */
252 static inline bool
253 pcmk__xe_is(const xmlNode *xml, const char *name)
/* ![[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)
*/
254 {
255 return (xml != NULL) && (xml->name != NULL) && (name != NULL)
256 && (strcmp((const char *) xml->name, name) == 0);
257 }
258
259 /*!
260 * \internal
261 * \brief Return first non-text child node of an XML node
262 *
263 * \param[in] parent XML node to check
264 *
265 * \return First non-text child node of \p parent (or NULL if none)
266 */
267 static inline xmlNode *
268 pcmk__xml_first_child(const xmlNode *parent)
/* ![[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)
*/
269 {
270 xmlNode *child = (parent? parent->children : NULL);
271
272 while (child && (child->type == XML_TEXT_NODE)) {
273 child = child->next;
274 }
275 return child;
276 }
277
278 /*!
279 * \internal
280 * \brief Return next non-text sibling node of an XML node
281 *
282 * \param[in] child XML node to check
283 *
284 * \return Next non-text sibling of \p child (or NULL if none)
285 */
286 static inline xmlNode *
287 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)
*/
288 {
289 xmlNode *next = (child? child->next : NULL);
290
291 while (next && (next->type == XML_TEXT_NODE)) {
292 next = next->next;
293 }
294 return next;
295 }
296
297 /*!
298 * \internal
299 * \brief Return first non-text child element of an XML node
300 *
301 * \param[in] parent XML node to check
302 *
303 * \return First child element of \p parent (or NULL if none)
304 */
305 static inline xmlNode *
306 pcmk__xe_first_child(const xmlNode *parent)
/* ![[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)
*/
307 {
308 xmlNode *child = (parent? parent->children : NULL);
309
310 while (child && (child->type != XML_ELEMENT_NODE)) {
311 child = child->next;
312 }
313 return child;
314 }
315
316 /*!
317 * \internal
318 * \brief Return next non-text sibling element of an XML element
319 *
320 * \param[in] child XML element to check
321 *
322 * \return Next sibling element of \p child (or NULL if none)
323 */
324 static inline xmlNode *
325 pcmk__xe_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)
*/
326 {
327 xmlNode *next = child? child->next : NULL;
328
329 while (next && (next->type != XML_ELEMENT_NODE)) {
330 next = next->next;
331 }
332 return next;
333 }
334
335 /*!
336 * \internal
337 * \brief Like pcmk__xe_set_props, but takes a va_list instead of
338 * arguments directly.
339 *
340 * \param[in,out] node XML to add attributes to
341 * \param[in] pairs NULL-terminated list of name/value pairs to add
342 */
343 void
344 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
345
346 /*!
347 * \internal
348 * \brief Add a NULL-terminated list of name/value pairs to the given
349 * XML node as properties.
350 *
351 * \param[in,out] node XML node to add properties to
352 * \param[in] ... NULL-terminated list of name/value pairs
353 *
354 * \note A NULL name terminates the arguments; a NULL value will be skipped.
355 */
356 void
357 pcmk__xe_set_props(xmlNodePtr node, ...)
/* ![[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)
*/
358 G_GNUC_NULL_TERMINATED;
359
360 /*!
361 * \internal
362 * \brief Get first attribute of an XML element
363 *
364 * \param[in] xe XML element to check
365 *
366 * \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
367 */
368 static inline xmlAttr *
369 pcmk__xe_first_attr(const xmlNode *xe)
370 {
371 return (xe == NULL)? NULL : xe->properties;
372 }
373
374 /*!
375 * \internal
376 * \brief Extract the ID attribute from an XML element
377 *
378 * \param[in] xpath String to search
379 * \param[in] node Node to get the ID for
380 *
381 * \return ID attribute of \p node in xpath string \p xpath
382 */
383 char *
384 pcmk__xpath_node_id(const char *xpath, const char *node);
385
386 /* internal XML-related utilities */
387
388 enum xml_private_flags {
389 pcmk__xf_none = 0x0000,
390 pcmk__xf_dirty = 0x0001,
391 pcmk__xf_deleted = 0x0002,
392 pcmk__xf_created = 0x0004,
393 pcmk__xf_modified = 0x0008,
394
395 pcmk__xf_tracking = 0x0010,
396 pcmk__xf_processed = 0x0020,
397 pcmk__xf_skip = 0x0040,
398 pcmk__xf_moved = 0x0080,
399
400 pcmk__xf_acl_enabled = 0x0100,
401 pcmk__xf_acl_read = 0x0200,
402 pcmk__xf_acl_write = 0x0400,
403 pcmk__xf_acl_deny = 0x0800,
404
405 pcmk__xf_acl_create = 0x1000,
406 pcmk__xf_acl_denied = 0x2000,
407 pcmk__xf_lazy = 0x4000,
408 };
409
410 void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag);
411
412 /*!
413 * \internal
414 * \brief Iterate over child elements of \p xml
415 *
416 * This function iterates over the children of \p xml, performing the
417 * callback function \p handler on each node. If the callback returns
418 * a value other than pcmk_rc_ok, the iteration stops and the value is
419 * returned. It is therefore possible that not all children will be
420 * visited.
421 *
422 * \param[in,out] xml The starting XML node. Can be NULL.
423 * \param[in] child_element_name The name that the node must match in order
424 * for \p handler to be run. If NULL, all
425 * child elements will match.
426 * \param[in] handler The callback function.
427 * \param[in,out] userdata User data to pass to the callback function.
428 * Can be NULL.
429 *
430 * \return Standard Pacemaker return code
431 */
432 int
433 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
434 int (*handler)(xmlNode *xml, void *userdata),
435 void *userdata);
436
437 static inline const char *
438 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)
*/
439 {
440 return ((attr == NULL) || (attr->children == NULL))? NULL
441 : (const char *) attr->children->content;
442 }
443
444 gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation,
445 xmlRelaxNGValidityErrorFunc error_handler,
446 void *error_handler_context);
447
448 #endif // PCMK__XML_INTERNAL__H