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