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(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 /*!
167  * \internal
168  * \brief Get the root directory to scan XML artefacts of given kind for
169  *
170  * \param[in] ns governs the hierarchy nesting against the inherent root dir
171  *
172  * \return root directory to scan XML artefacts of given kind for
173  */
174 char *
175 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
176 
177 /*!
178  * \internal
179  * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
180  *
181  * \param[in] ns       denotes path forming details (parent dir, suffix)
182  * \param[in] filespec symbolic file specification to be combined with
183  *                     #artefact_ns to form the final path
184  * \return unwrapped path to particular XML artifact (RNG/XSLT)
185  */
186 char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
187                               const char *filespec);
188 
189 /*!
190  * \internal
191  * \brief Return first non-text child node of an XML node
192  *
193  * \param[in] parent  XML node to check
194  *
195  * \return First non-text child node of \p parent (or NULL if none)
196  */
197 static inline xmlNode *
pcmk__xml_first_child(const xmlNode * parent)198 pcmk__xml_first_child(const xmlNode *parent)
199 {
200     xmlNode *child = (parent? parent->children : NULL);
201 
202     while (child && (child->type == XML_TEXT_NODE)) {
203         child = child->next;
204     }
205     return child;
206 }
207 
208 /*!
209  * \internal
210  * \brief Return next non-text sibling node of an XML node
211  *
212  * \param[in] child  XML node to check
213  *
214  * \return Next non-text sibling of \p child (or NULL if none)
215  */
216 static inline xmlNode *
pcmk__xml_next(const xmlNode * child)217 pcmk__xml_next(const xmlNode *child)
218 {
219     xmlNode *next = (child? child->next : NULL);
220 
221     while (next && (next->type == XML_TEXT_NODE)) {
222         next = next->next;
223     }
224     return next;
225 }
226 
227 /*!
228  * \internal
229  * \brief Return first non-text child element of an XML node
230  *
231  * \param[in] parent  XML node to check
232  *
233  * \return First child element of \p parent (or NULL if none)
234  */
235 static inline xmlNode *
pcmk__xe_first_child(const xmlNode * parent)236 pcmk__xe_first_child(const xmlNode *parent)
237 {
238     xmlNode *child = (parent? parent->children : NULL);
239 
240     while (child && (child->type != XML_ELEMENT_NODE)) {
241         child = child->next;
242     }
243     return child;
244 }
245 
246 /*!
247  * \internal
248  * \brief Return next non-text sibling element of an XML element
249  *
250  * \param[in] child  XML element to check
251  *
252  * \return Next sibling element of \p child (or NULL if none)
253  */
254 static inline xmlNode *
pcmk__xe_next(const xmlNode * child)255 pcmk__xe_next(const xmlNode *child)
256 {
257     xmlNode *next = child? child->next : NULL;
258 
259     while (next && (next->type != XML_ELEMENT_NODE)) {
260         next = next->next;
261     }
262     return next;
263 }
264 
265 /*!
266  * \internal
267  * \brief Like pcmk__xe_set_props, but takes a va_list instead of
268  *        arguments directly.
269  */
270 void
271 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
272 
273 /*!
274  * \internal
275  * \brief Add a NULL-terminated list of name/value pairs to the given
276  *        XML node as properties.
277  *
278  * \param[in,out] node XML node to add properties to
279  * \param[in]     ...  NULL-terminated list of name/value pairs
280  */
281 void
282 pcmk__xe_set_props(xmlNodePtr node, ...)
283 G_GNUC_NULL_TERMINATED;
284 
285 /*!
286  * \internal
287  * \brief Get first attribute of an XML element
288  *
289  * \param[in] xe  XML element to check
290  *
291  * \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
292  */
293 static inline xmlAttr *
pcmk__xe_first_attr(const xmlNode * xe)294 pcmk__xe_first_attr(const xmlNode *xe)
295 {
296     return (xe == NULL)? NULL : xe->properties;
297 }
298 
299 /*!
300  * \internal
301  * \brief Extract the ID attribute from an XML element
302  *
303  * \param[in] xpath String to search
304  * \param[in] node  Node to get the ID for
305  *
306  * \return ID attribute of \p node in xpath string \p xpath
307  */
308 char *
309 pcmk__xpath_node_id(const char *xpath, const char *node);
310 
311 #endif // PCMK__XML_INTERNAL__H
312