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