/src/pacemaker/lib/common/xpath.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-2026 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 | | #include <crm_internal.h> |
11 | | #include <stdint.h> // uint8_t |
12 | | #include <stdio.h> |
13 | | #include <string.h> |
14 | | |
15 | | #include <libxml/tree.h> // xmlNode |
16 | | #include <libxml/xmlstring.h> // xmlChar |
17 | | #include <libxml/xpath.h> // xmlXPathObject, etc. |
18 | | |
19 | | #include <crm/common/xml.h> |
20 | | #include "crmcommon_private.h" |
21 | | |
22 | | /*! |
23 | | * \internal |
24 | | * \brief Get a node from the result set of evaluating an XPath expression |
25 | | * |
26 | | * Evaluating an XPath expression stores the list of matching nodes in an |
27 | | * \c xmlXPathObject. This function gets the node at a particular index within |
28 | | * that list. |
29 | | * |
30 | | * \param[in,out] xpath_obj XPath object containing result nodes |
31 | | * \param[in] index Index of result node to get |
32 | | * |
33 | | * \return Result node at the given index if possible, or \c NULL otherwise |
34 | | * |
35 | | * \note This has a side effect: it sets the result node at \p index to NULL |
36 | | * within \p xpath_obj, so the result at a given index can be retrieved |
37 | | * only once. This is a workaround to prevent a use-after-free error. |
38 | | * |
39 | | * All elements returned by an XPath query are pointers to elements from |
40 | | * the tree, except namespace nodes (which are allocated separately for |
41 | | * the XPath object's node set). Accordingly, only namespace nodes and the |
42 | | * node set itself are freed when libxml2 frees a node set. |
43 | | * |
44 | | * This logic requires checking the type of every node in the node set. |
45 | | * However, a node may have been freed already while processing an XPath |
46 | | * object -- either directly (for example, with \c xmlFreeNode()) or |
47 | | * indirectly (for example, with \c xmlNodeSetContent()). In that case, |
48 | | * checking the freed node's type while freeing the XPath object is a |
49 | | * use-after-free error. |
50 | | * |
51 | | * To reduce the likelihood of this, when we access a node in the XPath |
52 | | * object, we remove it from the XPath object's node set by setting it to |
53 | | * \c NULL. This approach is adapted from \c xpath2.c in libxml2's |
54 | | * examples. That file also describes a way to reproduce the |
55 | | * use-after-free error. |
56 | | * |
57 | | * However, there are still ways that a use-after-free can occur. For |
58 | | * example, freeing the entire XML tree before freeing an XPath object |
59 | | * that contains pointers to it would be an error. It's dangerous to mix |
60 | | * processing XPath search results with modifications to a tree, and it |
61 | | * must be done with care. |
62 | | */ |
63 | | xmlNode * |
64 | | pcmk__xpath_result(xmlXPathObject *xpath_obj, int index) |
65 | 0 | { |
66 | 0 | xmlNode *match = NULL; |
67 | |
|
68 | 0 | CRM_CHECK((xpath_obj != NULL) && (index >= 0), return NULL); |
69 | | |
70 | 0 | match = xmlXPathNodeSetItem(xpath_obj->nodesetval, index); |
71 | 0 | if (match == NULL) { |
72 | | // Previously requested or out of range |
73 | 0 | return NULL; |
74 | 0 | } |
75 | | |
76 | 0 | if (match->type != XML_NAMESPACE_DECL) { |
77 | 0 | xpath_obj->nodesetval->nodeTab[index] = NULL; |
78 | 0 | } |
79 | |
|
80 | 0 | return match; |
81 | 0 | } |
82 | | |
83 | | /*! |
84 | | * \internal |
85 | | * \brief Get an element node corresponding to an XPath match node |
86 | | * |
87 | | * Each node in an XPath object's result node set may be of an arbitrary type. |
88 | | * This function is guaranteed to return an element node (or \c NULL). |
89 | | * |
90 | | * \param[in] match XML node that matched some XPath expression |
91 | | * |
92 | | * \retval \p match if \p match is an element |
93 | | * \retval Root element of \p match if \p match is a document |
94 | | * \retval <tt>match->parent</tt> if \p match is not an element but its parent |
95 | | * is an element |
96 | | * \retval \c NULL otherwise |
97 | | * |
98 | | * \todo Phase this out. Code that relies on this behavior is likely buggy. |
99 | | */ |
100 | | xmlNode * |
101 | | pcmk__xpath_match_element(xmlNode *match) |
102 | 0 | { |
103 | 0 | pcmk__assert(match != NULL); |
104 | |
|
105 | 0 | switch (match->type) { |
106 | 0 | case XML_ELEMENT_NODE: |
107 | 0 | return match; |
108 | | |
109 | 0 | case XML_DOCUMENT_NODE: |
110 | | // Happens if XPath expression is "/"; return root element instead |
111 | 0 | return xmlDocGetRootElement((xmlDoc *) match); |
112 | | |
113 | 0 | default: |
114 | 0 | if ((match->parent != NULL) |
115 | 0 | && (match->parent->type == XML_ELEMENT_NODE)) { |
116 | | |
117 | | // Probably an attribute; return parent element instead |
118 | 0 | return match->parent; |
119 | 0 | } |
120 | 0 | pcmk__err("Cannot get element from XPath expression match of type " |
121 | 0 | "%s", |
122 | 0 | pcmk__xml_element_type_text(match->type)); |
123 | 0 | return NULL; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | /*! |
128 | | * \internal |
129 | | * \brief Search an XML document using an XPath expression |
130 | | * |
131 | | * \param[in] doc XML document to search |
132 | | * \param[in] path XPath expression to evaluate in the context of \p doc |
133 | | * |
134 | | * \return XPath object containing result of evaluating \p path against \p doc |
135 | | */ |
136 | | xmlXPathObject * |
137 | | pcmk__xpath_search(xmlDoc *doc, const char *path) |
138 | 0 | { |
139 | 0 | const xmlChar *xpath_expr = (const xmlChar *) path; |
140 | 0 | xmlXPathContext *xpath_context = NULL; |
141 | 0 | xmlXPathObject *xpath_obj = NULL; |
142 | |
|
143 | 0 | CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return NULL); |
144 | | |
145 | 0 | xpath_context = xmlXPathNewContext(doc); |
146 | 0 | pcmk__mem_assert(xpath_context); |
147 | |
|
148 | 0 | xpath_obj = xmlXPathEval(xpath_expr, xpath_context); |
149 | |
|
150 | 0 | xmlXPathFreeContext(xpath_context); |
151 | 0 | return xpath_obj; |
152 | 0 | } |
153 | | |
154 | | /*! |
155 | | * \internal |
156 | | * \brief Run a supplied function for each result of an XPath search |
157 | | * |
158 | | * \param[in,out] doc XML document to search |
159 | | * \param[in] path XPath expression to evaluate in the context of |
160 | | * \p doc |
161 | | * \param[in] fn Function to call for each result XML element |
162 | | * \param[in,out] user_data Data to pass to \p fn |
163 | | * |
164 | | * \note This function processes the result node set in forward order. If \p fn |
165 | | * may free any part of any result node, then it is safer to process the |
166 | | * result node set in reverse order. (The node set is in document order.) |
167 | | * See comments in libxml's <tt>examples/xpath2.c</tt> file. |
168 | | */ |
169 | | void |
170 | | pcmk__xpath_foreach_result(xmlDoc *doc, const char *path, |
171 | | void (*fn)(xmlNode *, void *), void *user_data) |
172 | 0 | { |
173 | 0 | xmlXPathObject *xpath_obj = NULL; |
174 | 0 | int num_results = 0; |
175 | |
|
176 | 0 | pcmk__assert(fn != NULL); |
177 | 0 | CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return); |
178 | | |
179 | 0 | xpath_obj = pcmk__xpath_search(doc, path); |
180 | 0 | num_results = pcmk__xpath_num_results(xpath_obj); |
181 | |
|
182 | 0 | for (int i = 0; i < num_results; i++) { |
183 | 0 | xmlNode *result = pcmk__xpath_result(xpath_obj, i); |
184 | |
|
185 | 0 | if (result != NULL) { |
186 | 0 | fn(result, user_data); |
187 | 0 | } |
188 | 0 | } |
189 | 0 | xmlXPathFreeObject(xpath_obj); |
190 | 0 | } |
191 | | |
192 | | /*! |
193 | | * \internal |
194 | | * \brief Search an XML document using an XPath expression and get result node |
195 | | * |
196 | | * This function requires a unique result node from evaluating the XPath |
197 | | * expression. If there are multiple result nodes or no result nodes, it returns |
198 | | * \c NULL. |
199 | | * |
200 | | * \param[in] doc XML document to search |
201 | | * \param[in] path XPath expression to evaluate in the context of \p doc |
202 | | * \param[in] level Log level for errors |
203 | | * |
204 | | * \return Result node from evaluating \p path if unique, or \c NULL otherwise |
205 | | */ |
206 | | xmlNode * |
207 | | pcmk__xpath_find_one(xmlDoc *doc, const char *path, uint8_t level) |
208 | 0 | { |
209 | 0 | int num_results = 0; |
210 | 0 | xmlNode *result = NULL; |
211 | 0 | xmlXPathObject *xpath_obj = NULL; |
212 | 0 | const xmlNode *root = NULL; |
213 | 0 | const char *root_name = "(unknown)"; |
214 | |
|
215 | 0 | CRM_CHECK((doc != NULL) && (path != NULL), goto done); |
216 | | |
217 | 0 | xpath_obj = pcmk__xpath_search(doc, path); |
218 | 0 | num_results = pcmk__xpath_num_results(xpath_obj); |
219 | |
|
220 | 0 | if (num_results == 1) { |
221 | 0 | result = pcmk__xpath_result(xpath_obj, 0); |
222 | 0 | goto done; |
223 | 0 | } |
224 | | |
225 | 0 | if (level >= PCMK__LOG_NEVER) { |
226 | | // For no matches or multiple matches, the rest is just logging |
227 | 0 | goto done; |
228 | 0 | } |
229 | | |
230 | 0 | root = xmlDocGetRootElement(doc); |
231 | 0 | if (root != NULL) { |
232 | 0 | root_name = (const char *) root->name; |
233 | 0 | } |
234 | |
|
235 | 0 | if (num_results < 1) { |
236 | 0 | do_crm_log(level, "No match for %s in <%s>", path, root_name); |
237 | | |
238 | 0 | if (root != NULL) { |
239 | 0 | crm_log_xml_explicit(root, "no-match"); |
240 | 0 | } |
241 | 0 | goto done; |
242 | 0 | } |
243 | | |
244 | 0 | do_crm_log(level, "Multiple matches for %s in <%s>", path, root_name); |
245 | | |
246 | 0 | for (int i = 0; i < num_results; i++) { |
247 | 0 | xmlNode *match = pcmk__xpath_result(xpath_obj, i); |
248 | 0 | xmlChar *match_path = NULL; |
249 | |
|
250 | 0 | if (match == NULL) { |
251 | 0 | CRM_LOG_ASSERT(match != NULL); |
252 | 0 | continue; |
253 | 0 | } |
254 | | |
255 | 0 | match_path = xmlGetNodePath(match); |
256 | 0 | do_crm_log(level, "%s[%d] = %s", |
257 | 0 | path, i, pcmk__s((const char *) match_path, "(unknown)")); |
258 | 0 | free(match_path); |
259 | 0 | } |
260 | | |
261 | 0 | if (root != NULL) { |
262 | 0 | crm_log_xml_explicit(root, "multiple-matches"); |
263 | 0 | } |
264 | | |
265 | 0 | done: |
266 | 0 | xmlXPathFreeObject(xpath_obj); |
267 | 0 | return result; |
268 | 0 | } |
269 | | |
270 | | /*! |
271 | | * \internal |
272 | | * \brief Get an XPath string that matches an XML element as closely as possible |
273 | | * |
274 | | * \param[in] xml The XML element for which to build an XPath string |
275 | | * |
276 | | * \return \c GString that matches \p xml, or \c NULL if \p xml is \c NULL |
277 | | * (guaranteed not to be \c NULL if \p xml is not \c NULL) |
278 | | * |
279 | | * \note The caller is responsible for freeing the string using |
280 | | * \c g_string_free(). |
281 | | */ |
282 | | GString * |
283 | | pcmk__element_xpath(const xmlNode *xml) |
284 | 0 | { |
285 | 0 | const xmlNode *parent = NULL; |
286 | 0 | GString *xpath = NULL; |
287 | 0 | const char *id = NULL; |
288 | |
|
289 | 0 | if (xml == NULL) { |
290 | 0 | return NULL; |
291 | 0 | } |
292 | | |
293 | 0 | parent = xml->parent; |
294 | 0 | xpath = pcmk__element_xpath(parent); |
295 | 0 | if (xpath == NULL) { |
296 | 0 | xpath = g_string_sized_new(256); |
297 | 0 | } |
298 | | |
299 | | // Build xpath like "/" -> "/cib" -> "/cib/configuration" |
300 | 0 | if (parent == NULL) { |
301 | 0 | g_string_append_c(xpath, '/'); |
302 | 0 | } else if (parent->parent == NULL) { |
303 | 0 | g_string_append(xpath, (const char *) xml->name); |
304 | 0 | } else { |
305 | 0 | pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL); |
306 | 0 | } |
307 | |
|
308 | 0 | id = pcmk__xe_id(xml); |
309 | 0 | if (id != NULL) { |
310 | 0 | pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL); |
311 | 0 | } |
312 | |
|
313 | 0 | return xpath; |
314 | 0 | } |
315 | | |
316 | | /*! |
317 | | * \internal |
318 | | * \brief Extract the ID attribute from an XML element |
319 | | * |
320 | | * \param[in] xpath String to search |
321 | | * \param[in] node Node to get the ID for |
322 | | * |
323 | | * \return ID attribute of \p node in xpath string \p xpath |
324 | | */ |
325 | | char * |
326 | | pcmk__xpath_node_id(const char *xpath, const char *node) |
327 | 0 | { |
328 | 0 | char *retval = NULL; |
329 | 0 | char *patt = NULL; |
330 | 0 | const char *start = NULL; |
331 | 0 | const char *end = NULL; |
332 | |
|
333 | 0 | if (node == NULL || xpath == NULL) { |
334 | 0 | return retval; |
335 | 0 | } |
336 | | |
337 | 0 | patt = pcmk__assert_asprintf("/%s[@" PCMK_XA_ID "=", node); |
338 | |
|
339 | 0 | start = strstr(xpath, patt); |
340 | 0 | if (start == NULL) { |
341 | 0 | goto done; |
342 | 0 | } |
343 | | |
344 | 0 | start += strlen(patt); |
345 | 0 | start++; |
346 | |
|
347 | 0 | end = strchr(start, '\''); |
348 | 0 | pcmk__assert(end != NULL); |
349 | 0 | retval = strndup(start, end-start); |
350 | |
|
351 | 0 | done: |
352 | 0 | free(patt); |
353 | 0 | return retval; |
354 | 0 | } |
355 | | |
356 | | static int |
357 | | output_attr_child(xmlNode *child, void *userdata) |
358 | 0 | { |
359 | 0 | pcmk__output_t *out = userdata; |
360 | |
|
361 | 0 | out->info(out, " Value: %s \t(id=%s)", |
362 | 0 | pcmk__xe_get(child, PCMK_XA_VALUE), |
363 | 0 | pcmk__s(pcmk__xe_id(child), "<none>")); |
364 | 0 | return pcmk_rc_ok; |
365 | 0 | } |
366 | | |
367 | | /*! |
368 | | * \internal |
369 | | * \brief Warn if an XPath query returned multiple nodes with the same ID |
370 | | * |
371 | | * \param[in,out] out Output object |
372 | | * \param[in] search XPath search result, most typically the result of |
373 | | * calling <tt>cib->cmds->query()</tt>. |
374 | | * \param[in] name Name searched for |
375 | | */ |
376 | | void |
377 | | pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, |
378 | | const char *name) |
379 | 0 | { |
380 | 0 | if (out == NULL || name == NULL || search == NULL || |
381 | 0 | search->children == NULL) { |
382 | 0 | return; |
383 | 0 | } |
384 | | |
385 | 0 | out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name); |
386 | 0 | pcmk__xe_foreach_child(search, NULL, output_attr_child, out); |
387 | 0 | } |
388 | | |
389 | | // Deprecated functions kept only for backward API compatibility |
390 | | // LCOV_EXCL_START |
391 | | |
392 | | #include <crm/common/xml_compat.h> |
393 | | |
394 | | xmlXPathObjectPtr |
395 | | xpath_search(const xmlNode *xml_top, const char *path) |
396 | 0 | { |
397 | 0 | CRM_CHECK(xml_top != NULL, return NULL); |
398 | | |
399 | 0 | return pcmk__xpath_search(xml_top->doc, path); |
400 | 0 | } |
401 | | |
402 | | xmlNode * |
403 | | getXpathResult(xmlXPathObjectPtr xpathObj, int index) |
404 | 0 | { |
405 | 0 | xmlNode *match = NULL; |
406 | 0 | int max = pcmk__xpath_num_results(xpathObj); |
407 | |
|
408 | 0 | CRM_CHECK(index >= 0, return NULL); |
409 | 0 | CRM_CHECK(xpathObj != NULL, return NULL); |
410 | | |
411 | 0 | if (index >= max) { |
412 | 0 | pcmk__err("Requested index %d of only %d items", index, max); |
413 | 0 | return NULL; |
414 | |
|
415 | 0 | } else if(xpathObj->nodesetval->nodeTab[index] == NULL) { |
416 | | /* Previously requested */ |
417 | 0 | return NULL; |
418 | 0 | } |
419 | | |
420 | 0 | match = xpathObj->nodesetval->nodeTab[index]; |
421 | 0 | CRM_CHECK(match != NULL, return NULL); |
422 | | |
423 | 0 | if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) { |
424 | | // See the comment for pcmk__xpath_result() |
425 | 0 | xpathObj->nodesetval->nodeTab[index] = NULL; |
426 | 0 | } |
427 | |
|
428 | 0 | switch (match->type) { |
429 | 0 | case XML_ELEMENT_NODE: |
430 | 0 | return match; |
431 | | |
432 | 0 | case XML_DOCUMENT_NODE: // Searched for '/' |
433 | 0 | return match->children; |
434 | | |
435 | 0 | default: |
436 | 0 | if ((match->parent != NULL) |
437 | 0 | && (match->parent->type == XML_ELEMENT_NODE)) { |
438 | 0 | return match->parent; |
439 | 0 | } |
440 | 0 | pcmk__warn("Unsupported XPath match type %d (bug?)", match->type); |
441 | 0 | return NULL; |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | void |
446 | | freeXpathObject(xmlXPathObjectPtr xpathObj) |
447 | 0 | { |
448 | 0 | int max = pcmk__xpath_num_results(xpathObj); |
449 | |
|
450 | 0 | if (xpathObj == NULL) { |
451 | 0 | return; |
452 | 0 | } |
453 | | |
454 | 0 | for (int lpc = 0; lpc < max; lpc++) { |
455 | 0 | if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) { |
456 | 0 | xpathObj->nodesetval->nodeTab[lpc] = NULL; |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | /* _Now_ it's safe to free it */ |
461 | 0 | xmlXPathFreeObject(xpathObj); |
462 | 0 | } |
463 | | |
464 | | void |
465 | | dedupXpathResults(xmlXPathObjectPtr xpathObj) |
466 | 0 | { |
467 | 0 | int max = pcmk__xpath_num_results(xpathObj); |
468 | |
|
469 | 0 | if (xpathObj == NULL) { |
470 | 0 | return; |
471 | 0 | } |
472 | | |
473 | 0 | for (int lpc = 0; lpc < max; lpc++) { |
474 | 0 | xmlNode *xml = NULL; |
475 | 0 | gboolean dedup = FALSE; |
476 | |
|
477 | 0 | if (xpathObj->nodesetval->nodeTab[lpc] == NULL) { |
478 | 0 | continue; |
479 | 0 | } |
480 | | |
481 | 0 | xml = xpathObj->nodesetval->nodeTab[lpc]->parent; |
482 | |
|
483 | 0 | for (; xml; xml = xml->parent) { |
484 | 0 | int lpc2 = 0; |
485 | |
|
486 | 0 | for (lpc2 = 0; lpc2 < max; lpc2++) { |
487 | 0 | if (xpathObj->nodesetval->nodeTab[lpc2] == xml) { |
488 | 0 | xpathObj->nodesetval->nodeTab[lpc] = NULL; |
489 | 0 | dedup = TRUE; |
490 | 0 | break; |
491 | 0 | } |
492 | 0 | } |
493 | |
|
494 | 0 | if (dedup) { |
495 | 0 | break; |
496 | 0 | } |
497 | 0 | } |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | void |
502 | | crm_foreach_xpath_result(xmlNode *xml, const char *xpath, |
503 | | void (*helper)(xmlNode*, void*), void *user_data) |
504 | 0 | { |
505 | 0 | xmlXPathObject *xpathObj = NULL; |
506 | 0 | int nresults = 0; |
507 | |
|
508 | 0 | CRM_CHECK(xml != NULL, return); |
509 | | |
510 | 0 | xpathObj = pcmk__xpath_search(xml->doc, xpath); |
511 | 0 | nresults = pcmk__xpath_num_results(xpathObj); |
512 | |
|
513 | 0 | for (int i = 0; i < nresults; i++) { |
514 | 0 | xmlNode *result = pcmk__xpath_result(xpathObj, i); |
515 | |
|
516 | 0 | CRM_LOG_ASSERT(result != NULL); |
517 | |
|
518 | 0 | if (result != NULL) { |
519 | 0 | result = pcmk__xpath_match_element(result); |
520 | |
|
521 | 0 | CRM_LOG_ASSERT(result != NULL); |
522 | |
|
523 | 0 | if (result != NULL) { |
524 | 0 | helper(result, user_data); |
525 | 0 | } |
526 | 0 | } |
527 | 0 | } |
528 | 0 | xmlXPathFreeObject(xpathObj); |
529 | 0 | } |
530 | | |
531 | | xmlNode * |
532 | | get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level) |
533 | 0 | { |
534 | 0 | int max; |
535 | 0 | xmlNode *result = NULL; |
536 | 0 | xmlXPathObject *xpathObj = NULL; |
537 | 0 | char *nodePath = NULL; |
538 | 0 | char *matchNodePath = NULL; |
539 | |
|
540 | 0 | if (xpath == NULL) { |
541 | 0 | return xml_obj; /* or return NULL? */ |
542 | 0 | } |
543 | | |
544 | 0 | xpathObj = pcmk__xpath_search(xml_obj->doc, xpath); |
545 | 0 | nodePath = (char *)xmlGetNodePath(xml_obj); |
546 | 0 | max = pcmk__xpath_num_results(xpathObj); |
547 | |
|
548 | 0 | if (max == 0) { |
549 | 0 | if (error_level < PCMK__LOG_NEVER) { |
550 | 0 | do_crm_log(error_level, "No match for %s in %s", |
551 | 0 | xpath, pcmk__s(nodePath, "unknown path")); |
552 | 0 | crm_log_xml_explicit(xml_obj, "Unexpected Input"); |
553 | 0 | } |
554 | |
|
555 | 0 | } else if (max > 1) { |
556 | 0 | if (error_level < PCMK__LOG_NEVER) { |
557 | 0 | int lpc = 0; |
558 | |
|
559 | 0 | do_crm_log(error_level, "Too many matches for %s in %s", |
560 | 0 | xpath, pcmk__s(nodePath, "unknown path")); |
561 | | |
562 | 0 | for (lpc = 0; lpc < max; lpc++) { |
563 | 0 | xmlNode *match = pcmk__xpath_result(xpathObj, lpc); |
564 | |
|
565 | 0 | CRM_LOG_ASSERT(match != NULL); |
566 | 0 | if (match != NULL) { |
567 | 0 | match = pcmk__xpath_match_element(match); |
568 | |
|
569 | 0 | CRM_LOG_ASSERT(match != NULL); |
570 | 0 | if (match != NULL) { |
571 | 0 | matchNodePath = (char *) xmlGetNodePath(match); |
572 | 0 | do_crm_log(error_level, "%s[%d] = %s", |
573 | 0 | xpath, lpc, |
574 | 0 | pcmk__s(matchNodePath, |
575 | 0 | "unrecognizable match")); |
576 | 0 | free(matchNodePath); |
577 | 0 | } |
578 | 0 | } |
579 | 0 | } |
580 | 0 | crm_log_xml_explicit(xml_obj, "Bad Input"); |
581 | 0 | } |
582 | |
|
583 | 0 | } else { |
584 | 0 | result = pcmk__xpath_result(xpathObj, 0); |
585 | 0 | if (result != NULL) { |
586 | 0 | result = pcmk__xpath_match_element(result); |
587 | 0 | } |
588 | 0 | } |
589 | | |
590 | 0 | xmlXPathFreeObject(xpathObj); |
591 | 0 | free(nodePath); |
592 | |
|
593 | 0 | return result; |
594 | 0 | } |
595 | | |
596 | | // LCOV_EXCL_STOP |
597 | | // End deprecated API |