pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
xpath.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2025 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>
21#include "crmcommon_private.h"
22
64xmlNode *
65pcmk__xpath_result(xmlXPathObject *xpath_obj, int index)
66{
67 xmlNode *match = NULL;
68
69 CRM_CHECK((xpath_obj != NULL) && (index >= 0), return NULL);
70
71 match = xmlXPathNodeSetItem(xpath_obj->nodesetval, index);
72 if (match == NULL) {
73 // Previously requested or out of range
74 return NULL;
75 }
76
77 if (match->type != XML_NAMESPACE_DECL) {
78 xpath_obj->nodesetval->nodeTab[index] = NULL;
79 }
80
81 return match;
82}
83
101xmlNode *
103{
104 pcmk__assert(match != NULL);
105
106 switch (match->type) {
107 case XML_ELEMENT_NODE:
108 return match;
109
110 case XML_DOCUMENT_NODE:
111 // Happens if XPath expression is "/"; return root element instead
112 return xmlDocGetRootElement((xmlDoc *) match);
113
114 default:
115 if ((match->parent != NULL)
116 && (match->parent->type == XML_ELEMENT_NODE)) {
117
118 // Probably an attribute; return parent element instead
119 return match->parent;
120 }
121 crm_err("Cannot get element from XPath expression match of type %s",
122 pcmk__xml_element_type_text(match->type));
123 return NULL;
124 }
125}
126
136xmlXPathObject *
137pcmk__xpath_search(xmlDoc *doc, const char *path)
138{
139 const xmlChar *xpath_expr = (const xmlChar *) path;
140 xmlXPathContext *xpath_context = NULL;
141 xmlXPathObject *xpath_obj = NULL;
142
143 CRM_CHECK((doc != NULL) && !pcmk__str_empty(path), return NULL);
144
145 xpath_context = xmlXPathNewContext(doc);
146 pcmk__mem_assert(xpath_context);
147
148 xpath_obj = xmlXPathEval(xpath_expr, xpath_context);
149
150 xmlXPathFreeContext(xpath_context);
151 return xpath_obj;
152}
153
169void
170pcmk__xpath_foreach_result(xmlDoc *doc, const char *path,
171 void (*fn)(xmlNode *, void *), void *user_data)
172{
173 xmlXPathObject *xpath_obj = NULL;
174 int num_results = 0;
175
176 CRM_CHECK((doc != NULL) && !pcmk__str_empty(path) && (fn != NULL), return);
177
178 xpath_obj = pcmk__xpath_search(doc, path);
179 num_results = pcmk__xpath_num_results(xpath_obj);
180
181 for (int i = 0; i < num_results; i++) {
182 xmlNode *result = pcmk__xpath_result(xpath_obj, i);
183
184 if (result != NULL) {
185 (*fn)(result, user_data);
186 }
187 }
188 xmlXPathFreeObject(xpath_obj);
189}
190
205xmlNode *
206pcmk__xpath_find_one(xmlDoc *doc, const char *path, uint8_t level)
207{
208 int num_results = 0;
209 xmlNode *result = NULL;
210 xmlXPathObject *xpath_obj = NULL;
211 const xmlNode *root = NULL;
212 const char *root_name = "(unknown)";
213
214 CRM_CHECK((doc != NULL) && (path != NULL), goto done);
215
216 xpath_obj = pcmk__xpath_search(doc, path);
217 num_results = pcmk__xpath_num_results(xpath_obj);
218
219 if (num_results == 1) {
220 result = pcmk__xpath_result(xpath_obj, 0);
221 goto done;
222 }
223
224 if (level >= LOG_NEVER) {
225 // For no matches or multiple matches, the rest is just logging
226 goto done;
227 }
228
229 root = xmlDocGetRootElement(doc);
230 if (root != NULL) {
231 root_name = (const char *) root->name;
232 }
233
234 if (num_results < 1) {
235 do_crm_log(level, "No match for %s in <%s>", path, root_name);
236
237 if (root != NULL) {
238 crm_log_xml_explicit(root, "no-match");
239 }
240 goto done;
241 }
242
243 do_crm_log(level, "Multiple matches for %s in <%s>", path, root_name);
244
245 for (int i = 0; i < num_results; i++) {
246 xmlNode *match = pcmk__xpath_result(xpath_obj, i);
247 xmlChar *match_path = NULL;
248
249 if (match == NULL) {
250 CRM_LOG_ASSERT(match != NULL);
251 continue;
252 }
253
254 match_path = xmlGetNodePath(match);
255 do_crm_log(level, "%s[%d] = %s",
256 path, i, pcmk__s((const char *) match_path, "(unknown)"));
257 free(match_path);
258 }
259
260 if (root != NULL) {
261 crm_log_xml_explicit(root, "multiple-matches");
262 }
263
264done:
265 xmlXPathFreeObject(xpath_obj);
266 return result;
267}
268
280GString *
281pcmk__element_xpath(const xmlNode *xml)
282{
283 const xmlNode *parent = NULL;
284 GString *xpath = NULL;
285 const char *id = NULL;
286
287 if (xml == NULL) {
288 return NULL;
289 }
290
291 parent = xml->parent;
293 if (xpath == NULL) {
294 xpath = g_string_sized_new(256);
295 }
296
297 // Build xpath like "/" -> "/cib" -> "/cib/configuration"
298 if (parent == NULL) {
299 g_string_append_c(xpath, '/');
300 } else if (parent->parent == NULL) {
301 g_string_append(xpath, (const gchar *) xml->name);
302 } else {
303 pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL);
304 }
305
306 id = pcmk__xe_id(xml);
307 if (id != NULL) {
308 pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL);
309 }
310
311 return xpath;
312}
313
323char *
324pcmk__xpath_node_id(const char *xpath, const char *node)
325{
326 char *retval = NULL;
327 char *patt = NULL;
328 char *start = NULL;
329 char *end = NULL;
330
331 if (node == NULL || xpath == NULL) {
332 return retval;
333 }
334
335 patt = crm_strdup_printf("/%s[@" PCMK_XA_ID "=", node);
336 start = strstr(xpath, patt);
337
338 if (!start) {
339 free(patt);
340 return retval;
341 }
342
343 start += strlen(patt);
344 start++;
345
346 end = strstr(start, "\'");
347 pcmk__assert(end != NULL);
348 retval = strndup(start, end-start);
349
350 free(patt);
351 return retval;
352}
353
354static int
355output_attr_child(xmlNode *child, void *userdata)
356{
357 pcmk__output_t *out = userdata;
358
359 out->info(out, " Value: %s \t(id=%s)",
361 pcmk__s(pcmk__xe_id(child), "<none>"));
362 return pcmk_rc_ok;
363}
364
374void
376 const char *name)
377{
378 if (out == NULL || name == NULL || search == NULL ||
379 search->children == NULL) {
380 return;
381 }
382
383 out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name);
384 pcmk__xe_foreach_child(search, NULL, output_attr_child, out);
385}
386
387// Deprecated functions kept only for backward API compatibility
388// LCOV_EXCL_START
389
391
392xmlXPathObjectPtr
393xpath_search(const xmlNode *xml_top, const char *path)
394{
395 CRM_CHECK(xml_top != NULL, return NULL);
396
397 return pcmk__xpath_search(xml_top->doc, path);
398}
399
400xmlNode *
401getXpathResult(xmlXPathObjectPtr xpathObj, int index)
402{
403 xmlNode *match = NULL;
404 int max = pcmk__xpath_num_results(xpathObj);
405
406 CRM_CHECK(index >= 0, return NULL);
407 CRM_CHECK(xpathObj != NULL, return NULL);
408
409 if (index >= max) {
410 crm_err("Requested index %d of only %d items", index, max);
411 return NULL;
412
413 } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
414 /* Previously requested */
415 return NULL;
416 }
417
418 match = xpathObj->nodesetval->nodeTab[index];
419 CRM_CHECK(match != NULL, return NULL);
420
421 if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
422 // See the comment for pcmk__xpath_result()
423 xpathObj->nodesetval->nodeTab[index] = NULL;
424 }
425
426 switch (match->type) {
427 case XML_ELEMENT_NODE:
428 return match;
429
430 case XML_DOCUMENT_NODE: // Searched for '/'
431 return match->children;
432
433 default:
434 if ((match->parent != NULL)
435 && (match->parent->type == XML_ELEMENT_NODE)) {
436 return match->parent;
437 }
438 crm_warn("Unsupported XPath match type %d (bug?)", match->type);
439 return NULL;
440 }
441}
442
443void
444freeXpathObject(xmlXPathObjectPtr xpathObj)
445{
446 int max = pcmk__xpath_num_results(xpathObj);
447
448 if (xpathObj == NULL) {
449 return;
450 }
451
452 for (int lpc = 0; lpc < max; lpc++) {
453 if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
454 xpathObj->nodesetval->nodeTab[lpc] = NULL;
455 }
456 }
457
458 /* _Now_ it's safe to free it */
459 xmlXPathFreeObject(xpathObj);
460}
461
462void
463dedupXpathResults(xmlXPathObjectPtr xpathObj)
464{
465 int max = pcmk__xpath_num_results(xpathObj);
466
467 if (xpathObj == NULL) {
468 return;
469 }
470
471 for (int lpc = 0; lpc < max; lpc++) {
472 xmlNode *xml = NULL;
473 gboolean dedup = FALSE;
474
475 if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
476 continue;
477 }
478
479 xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
480
481 for (; xml; xml = xml->parent) {
482 int lpc2 = 0;
483
484 for (lpc2 = 0; lpc2 < max; lpc2++) {
485 if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
486 xpathObj->nodesetval->nodeTab[lpc] = NULL;
487 dedup = TRUE;
488 break;
489 }
490 }
491
492 if (dedup) {
493 break;
494 }
495 }
496 }
497}
498
499void
500crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
501 void (*helper)(xmlNode*, void*), void *user_data)
502{
503 xmlXPathObject *xpathObj = NULL;
504 int nresults = 0;
505
506 CRM_CHECK(xml != NULL, return);
507
508 xpathObj = pcmk__xpath_search(xml->doc, xpath);
509 nresults = pcmk__xpath_num_results(xpathObj);
510
511 for (int i = 0; i < nresults; i++) {
512 xmlNode *result = pcmk__xpath_result(xpathObj, i);
513
514 CRM_LOG_ASSERT(result != NULL);
515
516 if (result != NULL) {
518
519 CRM_LOG_ASSERT(result != NULL);
520
521 if (result != NULL) {
522 (*helper)(result, user_data);
523 }
524 }
525 }
526 xmlXPathFreeObject(xpathObj);
527}
528
529xmlNode *
530get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
531{
532 int max;
533 xmlNode *result = NULL;
534 xmlXPathObject *xpathObj = NULL;
535 char *nodePath = NULL;
536 char *matchNodePath = NULL;
537
538 if (xpath == NULL) {
539 return xml_obj; /* or return NULL? */
540 }
541
542 xpathObj = pcmk__xpath_search(xml_obj->doc, xpath);
543 nodePath = (char *)xmlGetNodePath(xml_obj);
544 max = pcmk__xpath_num_results(xpathObj);
545
546 if (max == 0) {
547 if (error_level < LOG_NEVER) {
548 do_crm_log(error_level, "No match for %s in %s",
549 xpath, pcmk__s(nodePath, "unknown path"));
550 crm_log_xml_explicit(xml_obj, "Unexpected Input");
551 }
552
553 } else if (max > 1) {
554 if (error_level < LOG_NEVER) {
555 int lpc = 0;
556
557 do_crm_log(error_level, "Too many matches for %s in %s",
558 xpath, pcmk__s(nodePath, "unknown path"));
559
560 for (lpc = 0; lpc < max; lpc++) {
561 xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
562
563 CRM_LOG_ASSERT(match != NULL);
564 if (match != NULL) {
565 match = pcmk__xpath_match_element(match);
566
567 CRM_LOG_ASSERT(match != NULL);
568 if (match != NULL) {
569 matchNodePath = (char *) xmlGetNodePath(match);
570 do_crm_log(error_level, "%s[%d] = %s",
571 xpath, lpc,
572 pcmk__s(matchNodePath,
573 "unrecognizable match"));
574 free(matchNodePath);
575 }
576 }
577 }
578 crm_log_xml_explicit(xml_obj, "Bad Input");
579 }
580
581 } else {
582 result = pcmk__xpath_result(xpathObj, 0);
583 if (result != NULL) {
585 }
586 }
587
588 xmlXPathFreeObject(xpathObj);
589 free(nodePath);
590
591 return result;
592}
593
594// LCOV_EXCL_STOP
595// End deprecated API
const char * parent
Definition cib.c:27
const char * path
Definition cib.c:28
const char * name
Definition cib.c:26
G_GNUC_INTERNAL const char * pcmk__xml_element_type_text(xmlElementType type)
Definition xml.c:41
#define do_crm_log(level, fmt, args...)
Log a message.
Definition logging.h:149
#define crm_warn(fmt, args...)
Definition logging.h:360
#define CRM_LOG_ASSERT(expr)
Definition logging.h:196
#define crm_log_xml_explicit(xml, text)
Definition logging.h:380
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define LOG_NEVER
Definition logging.h:48
pcmk__action_result_t result
Definition pcmk_fence.c:37
@ pcmk_rc_ok
Definition results.h:159
#define pcmk__assert(expr)
#define pcmk__mem_assert(ptr)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1299
This structure contains everything that makes up a single output formatter.
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
Wrappers for and extensions to libxml2.
Deprecated Pacemaker XML API.
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int(*handler)(xmlNode *xml, void *userdata), void *userdata)
#define PCMK_XA_ID
Definition xml_names.h:301
#define PCMK_XA_VALUE
Definition xml_names.h:442
#define PCMK_XA_NAME
Definition xml_names.h:330
void dedupXpathResults(xmlXPathObjectPtr xpathObj)
Definition xpath.c:463
void crm_foreach_xpath_result(xmlNode *xml, const char *xpath, void(*helper)(xmlNode *, void *), void *user_data)
Definition xpath.c:500
xmlXPathObject * pcmk__xpath_search(xmlDoc *doc, const char *path)
Definition xpath.c:137
xmlNode * get_xpath_object(const char *xpath, xmlNode *xml_obj, int error_level)
Definition xpath.c:530
xmlNode * getXpathResult(xmlXPathObjectPtr xpathObj, int index)
Definition xpath.c:401
xmlNode * pcmk__xpath_result(xmlXPathObject *xpath_obj, int index)
Definition xpath.c:65
void freeXpathObject(xmlXPathObjectPtr xpathObj)
Definition xpath.c:444
void pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, const char *name)
Definition xpath.c:375
GString * pcmk__element_xpath(const xmlNode *xml)
Definition xpath.c:281
xmlNode * pcmk__xpath_match_element(xmlNode *match)
Definition xpath.c:102
xmlNode * pcmk__xpath_find_one(xmlDoc *doc, const char *path, uint8_t level)
Definition xpath.c:206
xmlXPathObjectPtr xpath_search(const xmlNode *xml_top, const char *path)
Definition xpath.c:393
char * pcmk__xpath_node_id(const char *xpath, const char *node)
Definition xpath.c:324
void pcmk__xpath_foreach_result(xmlDoc *doc, const char *path, void(*fn)(xmlNode *, void *), void *user_data)
Definition xpath.c:170