Line data Source code
1 : /*
2 : * Copyright 2004-2024 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 :
12 : #include <libxml/tree.h>
13 :
14 : #include <crm/crm.h>
15 : #include <crm/common/xml.h>
16 : #include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc.
17 : #include "crmcommon_private.h"
18 :
19 : static int show_xml_node(pcmk__output_t *out, GString *buffer,
20 : const char *prefix, const xmlNode *data, int depth,
21 : uint32_t options);
22 :
23 : // Log an XML library error
24 : void
25 0 : pcmk__log_xmllib_err(void *ctx, const char *fmt, ...)
26 : {
27 : va_list ap;
28 :
29 0 : va_start(ap, fmt);
30 0 : pcmk__if_tracing(
31 : {
32 : PCMK__XML_LOG_BASE(LOG_ERR, TRUE,
33 : crm_abort(__FILE__, __PRETTY_FUNCTION__,
34 : __LINE__, "xml library error", TRUE,
35 : TRUE),
36 : "XML Error: ", fmt, ap);
37 : },
38 : {
39 : PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap);
40 : }
41 : );
42 0 : va_end(ap);
43 0 : }
44 :
45 : /*!
46 : * \internal
47 : * \brief Output an XML comment with depth-based indentation
48 : *
49 : * \param[in,out] out Output object
50 : * \param[in] data XML node to output
51 : * \param[in] depth Current indentation level
52 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
53 : *
54 : * \return Standard Pacemaker return code
55 : *
56 : * \note This currently produces output only for text-like output objects.
57 : */
58 : static int
59 0 : show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth,
60 : uint32_t options)
61 : {
62 0 : if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
63 0 : int width = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
64 :
65 0 : return out->info(out, "%*s<!--%s-->",
66 0 : width, "", (const char *) data->content);
67 : }
68 0 : return pcmk_rc_no_output;
69 : }
70 :
71 : /*!
72 : * \internal
73 : * \brief Output an XML element in a formatted way
74 : *
75 : * \param[in,out] out Output object
76 : * \param[in,out] buffer Where to build output strings
77 : * \param[in] prefix String to prepend to every line of output
78 : * \param[in] data XML node to output
79 : * \param[in] depth Current indentation level
80 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
81 : *
82 : * \return Standard Pacemaker return code
83 : *
84 : * \note This is a recursive helper function for \p show_xml_node().
85 : * \note This currently produces output only for text-like output objects.
86 : * \note \p buffer may be overwritten many times. The caller is responsible for
87 : * freeing it using \p g_string_free() but should not rely on its
88 : * contents.
89 : */
90 : static int
91 0 : show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix,
92 : const xmlNode *data, int depth, uint32_t options)
93 : {
94 0 : int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0;
95 0 : int rc = pcmk_rc_no_output;
96 :
97 0 : if (pcmk_is_set(options, pcmk__xml_fmt_open)) {
98 0 : const char *hidden = crm_element_value(data, PCMK__XA_HIDDEN);
99 :
100 : g_string_truncate(buffer, 0);
101 :
102 0 : for (int lpc = 0; lpc < spaces; lpc++) {
103 : g_string_append_c(buffer, ' ');
104 : }
105 0 : pcmk__g_strcat(buffer, "<", data->name, NULL);
106 :
107 0 : for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
108 0 : attr = attr->next) {
109 0 : xml_node_private_t *nodepriv = attr->_private;
110 0 : const char *p_name = (const char *) attr->name;
111 0 : const char *p_value = pcmk__xml_attr_value(attr);
112 0 : gchar *p_copy = NULL;
113 :
114 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
115 0 : continue;
116 : }
117 :
118 : // @COMPAT Remove when v1 patchsets are removed
119 0 : if (pcmk_any_flags_set(options,
120 : pcmk__xml_fmt_diff_plus
121 : |pcmk__xml_fmt_diff_minus)
122 0 : && (strcmp(PCMK__XA_CRM_DIFF_MARKER, p_name) == 0)) {
123 0 : continue;
124 : }
125 :
126 0 : if ((hidden != NULL) && (p_name[0] != '\0')
127 0 : && (strstr(hidden, p_name) != NULL)) {
128 :
129 0 : p_value = "*****";
130 :
131 : } else {
132 0 : p_copy = pcmk__xml_escape(p_value, true);
133 0 : p_value = p_copy;
134 : }
135 :
136 0 : pcmk__g_strcat(buffer, " ", p_name, "=\"",
137 : pcmk__s(p_value, "<null>"), "\"", NULL);
138 0 : g_free(p_copy);
139 : }
140 :
141 0 : if ((data->children != NULL)
142 0 : && pcmk_is_set(options, pcmk__xml_fmt_children)) {
143 0 : g_string_append_c(buffer, '>');
144 :
145 : } else {
146 0 : g_string_append(buffer, "/>");
147 : }
148 :
149 0 : rc = out->info(out, "%s%s%s",
150 0 : pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ",
151 : buffer->str);
152 : }
153 :
154 0 : if (data->children == NULL) {
155 0 : return rc;
156 : }
157 :
158 0 : if (pcmk_is_set(options, pcmk__xml_fmt_children)) {
159 0 : for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
160 0 : child = pcmk__xml_next(child)) {
161 :
162 0 : int temp_rc = show_xml_node(out, buffer, prefix, child, depth + 1,
163 : options
164 : |pcmk__xml_fmt_open
165 : |pcmk__xml_fmt_close);
166 0 : rc = pcmk__output_select_rc(rc, temp_rc);
167 : }
168 : }
169 :
170 0 : if (pcmk_is_set(options, pcmk__xml_fmt_close)) {
171 0 : int temp_rc = out->info(out, "%s%s%*s</%s>",
172 : pcmk__s(prefix, ""),
173 0 : pcmk__str_empty(prefix)? "" : " ",
174 0 : spaces, "", data->name);
175 0 : rc = pcmk__output_select_rc(rc, temp_rc);
176 : }
177 :
178 0 : return rc;
179 : }
180 :
181 : /*!
182 : * \internal
183 : * \brief Output an XML element or comment in a formatted way
184 : *
185 : * \param[in,out] out Output object
186 : * \param[in,out] buffer Where to build output strings
187 : * \param[in] prefix String to prepend to every line of output
188 : * \param[in] data XML node to log
189 : * \param[in] depth Current indentation level
190 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
191 : *
192 : * \return Standard Pacemaker return code
193 : *
194 : * \note This is a recursive helper function for \p pcmk__xml_show().
195 : * \note This currently produces output only for text-like output objects.
196 : * \note \p buffer may be overwritten many times. The caller is responsible for
197 : * freeing it using \p g_string_free() but should not rely on its
198 : * contents.
199 : */
200 : static int
201 0 : show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix,
202 : const xmlNode *data, int depth, uint32_t options)
203 : {
204 0 : switch (data->type) {
205 0 : case XML_COMMENT_NODE:
206 0 : return show_xml_comment(out, data, depth, options);
207 0 : case XML_ELEMENT_NODE:
208 0 : return show_xml_element(out, buffer, prefix, data, depth, options);
209 0 : default:
210 0 : return pcmk_rc_no_output;
211 : }
212 : }
213 :
214 : /*!
215 : * \internal
216 : * \brief Output an XML element or comment in a formatted way
217 : *
218 : * \param[in,out] out Output object
219 : * \param[in] prefix String to prepend to every line of output
220 : * \param[in] data XML node to output
221 : * \param[in] depth Current nesting level
222 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
223 : *
224 : * \return Standard Pacemaker return code
225 : *
226 : * \note This currently produces output only for text-like output objects.
227 : */
228 : int
229 0 : pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
230 : int depth, uint32_t options)
231 : {
232 0 : int rc = pcmk_rc_no_output;
233 0 : GString *buffer = NULL;
234 :
235 0 : CRM_ASSERT(out != NULL);
236 0 : CRM_CHECK(depth >= 0, depth = 0);
237 :
238 0 : if (data == NULL) {
239 0 : return rc;
240 : }
241 :
242 : /* Allocate a buffer once, for show_xml_node() to truncate and reuse in
243 : * recursive calls
244 : */
245 0 : buffer = g_string_sized_new(1024);
246 0 : rc = show_xml_node(out, buffer, prefix, data, depth, options);
247 0 : g_string_free(buffer, TRUE);
248 :
249 0 : return rc;
250 : }
251 :
252 : /*!
253 : * \internal
254 : * \brief Output XML portions that have been marked as changed
255 : *
256 : * \param[in,out] out Output object
257 : * \param[in] data XML node to output
258 : * \param[in] depth Current indentation level
259 : * \param[in] options Group of \p pcmk__xml_fmt_options flags
260 : *
261 : * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing
262 : * changes to \p data and its children.
263 : * \note This currently produces output only for text-like output objects.
264 : */
265 : static int
266 0 : show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth,
267 : uint32_t options)
268 : {
269 : /* @COMPAT: When log_data_element() is removed, we can remove the options
270 : * argument here and instead hard-code pcmk__xml_log_pretty.
271 : */
272 0 : xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private;
273 0 : int rc = pcmk_rc_no_output;
274 0 : int temp_rc = pcmk_rc_no_output;
275 :
276 0 : if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) {
277 : // Newly created
278 0 : return pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth,
279 : options
280 : |pcmk__xml_fmt_open
281 : |pcmk__xml_fmt_children
282 : |pcmk__xml_fmt_close);
283 : }
284 :
285 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
286 : // Modified or moved
287 0 : bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
288 0 : int spaces = pretty? (2 * depth) : 0;
289 0 : const char *prefix = PCMK__XML_PREFIX_MODIFIED;
290 :
291 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
292 0 : prefix = PCMK__XML_PREFIX_MOVED;
293 : }
294 :
295 : // Log opening tag
296 0 : rc = pcmk__xml_show(out, prefix, data, depth,
297 : options|pcmk__xml_fmt_open);
298 :
299 : // Log changes to attributes
300 0 : for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
301 0 : attr = attr->next) {
302 0 : const char *name = (const char *) attr->name;
303 :
304 0 : nodepriv = attr->_private;
305 :
306 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
307 0 : const char *value = pcmk__xml_attr_value(attr);
308 :
309 0 : temp_rc = out->info(out, "%s %*s @%s=%s",
310 : PCMK__XML_PREFIX_DELETED, spaces, "", name,
311 : value);
312 :
313 0 : } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
314 0 : const char *value = pcmk__xml_attr_value(attr);
315 :
316 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
317 0 : prefix = PCMK__XML_PREFIX_CREATED;
318 :
319 0 : } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) {
320 0 : prefix = PCMK__XML_PREFIX_MODIFIED;
321 :
322 0 : } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
323 0 : prefix = PCMK__XML_PREFIX_MOVED;
324 :
325 : } else {
326 0 : prefix = PCMK__XML_PREFIX_MODIFIED;
327 : }
328 :
329 0 : temp_rc = out->info(out, "%s %*s @%s=%s",
330 : prefix, spaces, "", name, value);
331 : }
332 0 : rc = pcmk__output_select_rc(rc, temp_rc);
333 : }
334 :
335 : // Log changes to children
336 0 : for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
337 0 : child = pcmk__xml_next(child)) {
338 0 : temp_rc = show_xml_changes_recursive(out, child, depth + 1,
339 : options);
340 0 : rc = pcmk__output_select_rc(rc, temp_rc);
341 : }
342 :
343 : // Log closing tag
344 0 : temp_rc = pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth,
345 : options|pcmk__xml_fmt_close);
346 0 : return pcmk__output_select_rc(rc, temp_rc);
347 : }
348 :
349 : // This node hasn't changed, but check its children
350 0 : for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
351 0 : child = pcmk__xml_next(child)) {
352 0 : temp_rc = show_xml_changes_recursive(out, child, depth + 1, options);
353 0 : rc = pcmk__output_select_rc(rc, temp_rc);
354 : }
355 0 : return rc;
356 : }
357 :
358 : /*!
359 : * \internal
360 : * \brief Output changes to an XML node and any children
361 : *
362 : * \param[in,out] out Output object
363 : * \param[in] xml XML node to output
364 : *
365 : * \return Standard Pacemaker return code
366 : *
367 : * \note This currently produces output only for text-like output objects.
368 : */
369 : int
370 0 : pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml)
371 : {
372 0 : xml_doc_private_t *docpriv = NULL;
373 0 : int rc = pcmk_rc_no_output;
374 0 : int temp_rc = pcmk_rc_no_output;
375 :
376 0 : CRM_ASSERT(out != NULL);
377 0 : CRM_ASSERT(xml != NULL);
378 0 : CRM_ASSERT(xml->doc != NULL);
379 :
380 0 : docpriv = xml->doc->_private;
381 0 : if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
382 0 : return rc;
383 : }
384 :
385 0 : for (const GList *iter = docpriv->deleted_objs; iter != NULL;
386 0 : iter = iter->next) {
387 0 : const pcmk__deleted_xml_t *deleted_obj = iter->data;
388 :
389 0 : if (deleted_obj->position >= 0) {
390 0 : temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)",
391 0 : deleted_obj->path, deleted_obj->position);
392 : } else {
393 0 : temp_rc = out->info(out, PCMK__XML_PREFIX_DELETED " %s",
394 0 : deleted_obj->path);
395 : }
396 0 : rc = pcmk__output_select_rc(rc, temp_rc);
397 : }
398 :
399 0 : temp_rc = show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty);
400 0 : return pcmk__output_select_rc(rc, temp_rc);
401 : }
402 :
403 : // Deprecated functions kept only for backward API compatibility
404 : // LCOV_EXCL_START
405 :
406 : #include <crm/common/logging_compat.h>
407 : #include <crm/common/xml_compat.h>
408 :
409 : void
410 : log_data_element(int log_level, const char *file, const char *function,
411 : int line, const char *prefix, const xmlNode *data, int depth,
412 : int legacy_options)
413 : {
414 : uint32_t options = 0;
415 : pcmk__output_t *out = NULL;
416 :
417 : // Confine log_level to uint8_t range
418 : log_level = pcmk__clip_log_level(log_level);
419 :
420 : if (data == NULL) {
421 : do_crm_log(log_level, "%s%sNo data to dump as XML",
422 : pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ");
423 : return;
424 : }
425 :
426 : switch (log_level) {
427 : case LOG_NEVER:
428 : return;
429 : case LOG_STDOUT:
430 : CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
431 : break;
432 : default:
433 : CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
434 : pcmk__output_set_log_level(out, log_level);
435 : break;
436 : }
437 :
438 : /* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and
439 : * start using the pcmk__xml_fmt_options in all the internal functions.
440 : *
441 : * xml_log_option_dirty_add and xml_log_option_diff_all are ignored by
442 : * internal code and only used here, so they don't need to be addressed.
443 : */
444 : if (pcmk_is_set(legacy_options, xml_log_option_filtered)) {
445 : options |= pcmk__xml_fmt_filtered;
446 : }
447 : if (pcmk_is_set(legacy_options, xml_log_option_formatted)) {
448 : options |= pcmk__xml_fmt_pretty;
449 : }
450 : if (pcmk_is_set(legacy_options, xml_log_option_open)) {
451 : options |= pcmk__xml_fmt_open;
452 : }
453 : if (pcmk_is_set(legacy_options, xml_log_option_children)) {
454 : options |= pcmk__xml_fmt_children;
455 : }
456 : if (pcmk_is_set(legacy_options, xml_log_option_close)) {
457 : options |= pcmk__xml_fmt_close;
458 : }
459 : if (pcmk_is_set(legacy_options, xml_log_option_text)) {
460 : options |= pcmk__xml_fmt_text;
461 : }
462 : if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) {
463 : options |= pcmk__xml_fmt_diff_plus;
464 : }
465 : if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) {
466 : options |= pcmk__xml_fmt_diff_minus;
467 : }
468 : if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) {
469 : options |= pcmk__xml_fmt_diff_short;
470 : }
471 :
472 : // Log element based on options
473 : if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) {
474 : CRM_CHECK(depth >= 0, depth = 0);
475 : show_xml_changes_recursive(out, data, depth, options);
476 : goto done;
477 : }
478 :
479 : if (pcmk_is_set(options, pcmk__xml_fmt_pretty)
480 : && ((data->children == NULL)
481 : || (crm_element_value(data, PCMK__XA_CRM_DIFF_MARKER) != NULL))) {
482 :
483 : if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
484 : legacy_options |= xml_log_option_diff_all;
485 : prefix = PCMK__XML_PREFIX_CREATED;
486 :
487 : } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) {
488 : legacy_options |= xml_log_option_diff_all;
489 : prefix = PCMK__XML_PREFIX_DELETED;
490 : }
491 : }
492 :
493 : if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)
494 : && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) {
495 :
496 : if (!pcmk_any_flags_set(options,
497 : pcmk__xml_fmt_diff_plus
498 : |pcmk__xml_fmt_diff_minus)) {
499 : // Nothing will ever be logged
500 : goto done;
501 : }
502 :
503 : // Keep looking for the actual change
504 : for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
505 : child = pcmk__xml_next(child)) {
506 : log_data_element(log_level, file, function, line, prefix, child,
507 : depth + 1, options);
508 : }
509 :
510 : } else {
511 : pcmk__xml_show(out, prefix, data, depth,
512 : options
513 : |pcmk__xml_fmt_open
514 : |pcmk__xml_fmt_children
515 : |pcmk__xml_fmt_close);
516 : }
517 :
518 : done:
519 : out->finish(out, CRM_EX_OK, true, NULL);
520 : pcmk__output_free(out);
521 : }
522 :
523 : void
524 : xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml)
525 : {
526 : pcmk__output_t *out = NULL;
527 : int rc = pcmk_rc_ok;
528 :
529 : switch (log_level) {
530 : case LOG_NEVER:
531 : return;
532 : case LOG_STDOUT:
533 : CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
534 : break;
535 : default:
536 : CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
537 : pcmk__output_set_log_level(out, log_level);
538 : break;
539 : }
540 : rc = pcmk__xml_show_changes(out, xml);
541 : out->finish(out, pcmk_rc2exitc(rc), true, NULL);
542 : pcmk__output_free(out);
543 : }
544 :
545 : // LCOV_EXCL_STOP
546 : // End deprecated API
|