/src/pacemaker/lib/common/patchset_display.c
Line | Count | Source |
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 | | |
12 | | #include <crm/common/xml.h> |
13 | | |
14 | | #include "crmcommon_private.h" |
15 | | |
16 | | /*! |
17 | | * \internal |
18 | | * \brief Output an XML patchset header |
19 | | * |
20 | | * This function parses a header from an XML patchset (a \c PCMK_XE_DIFF element |
21 | | * and its children). |
22 | | * |
23 | | * All header lines contain three integers separated by dots, of the form |
24 | | * <tt>{0}.{1}.{2}</tt>: |
25 | | * * \p {0}: \c PCMK_XA_ADMIN_EPOCH |
26 | | * * \p {1}: \c PCMK_XA_EPOCH |
27 | | * * \p {2}: \c PCMK_XA_NUM_UPDATES |
28 | | * |
29 | | * Lines containing \p "---" describe removals and end with the patch format |
30 | | * number. Lines containing \p "+++" describe additions and end with the patch |
31 | | * digest. |
32 | | * |
33 | | * \param[in,out] out Output object |
34 | | * \param[in] patchset XML patchset to output |
35 | | * |
36 | | * \return Standard Pacemaker return code |
37 | | * |
38 | | * \note This function produces output only for text-like formats. |
39 | | */ |
40 | | static int |
41 | | xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset) |
42 | 0 | { |
43 | 0 | int rc = pcmk_rc_no_output; |
44 | 0 | int add[] = { 0, 0, 0 }; |
45 | 0 | int del[] = { 0, 0, 0 }; |
46 | |
|
47 | 0 | pcmk__xml_patchset_versions(patchset, del, add); |
48 | |
|
49 | 0 | if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) { |
50 | 0 | const char *fmt = pcmk__xe_get(patchset, PCMK_XA_FORMAT); |
51 | 0 | const char *digest = pcmk__xe_get(patchset, PCMK_XA_DIGEST); |
52 | |
|
53 | 0 | out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], |
54 | 0 | pcmk__s(fmt, "(no format)")); |
55 | 0 | rc = out->info(out, "Diff: +++ %d.%d.%d %s", |
56 | 0 | add[0], add[1], add[2], pcmk__s(digest, "(no digest)")); |
57 | |
|
58 | 0 | } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) { |
59 | 0 | rc = out->info(out, "Local-only Change: %d.%d.%d", |
60 | 0 | add[0], add[1], add[2]); |
61 | 0 | } |
62 | |
|
63 | 0 | return rc; |
64 | 0 | } |
65 | | |
66 | | /*! |
67 | | * \internal |
68 | | * \brief Output a user-friendly form of an XML patchset |
69 | | * |
70 | | * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its |
71 | | * children) into a user-friendly combined diff output. |
72 | | * |
73 | | * \param[in,out] out Output object |
74 | | * \param[in] patchset XML patchset to output |
75 | | * |
76 | | * \return Standard Pacemaker return code |
77 | | * |
78 | | * \note This function produces output only for text-like formats. |
79 | | */ |
80 | | static int |
81 | | xml_show_patchset(pcmk__output_t *out, const xmlNode *patchset) |
82 | 0 | { |
83 | 0 | int rc = xml_show_patchset_header(out, patchset); |
84 | 0 | int temp_rc = pcmk_rc_no_output; |
85 | |
|
86 | 0 | for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL, |
87 | 0 | NULL); |
88 | 0 | change != NULL; change = pcmk__xe_next(change, NULL)) { |
89 | |
|
90 | 0 | const char *op = pcmk__xe_get(change, PCMK_XA_OPERATION); |
91 | 0 | const char *xpath = pcmk__xe_get(change, PCMK_XA_PATH); |
92 | |
|
93 | 0 | if (op == NULL) { |
94 | 0 | continue; |
95 | 0 | } |
96 | | |
97 | 0 | if (strcmp(op, PCMK_VALUE_CREATE) == 0) { |
98 | 0 | char *prefix = pcmk__assert_asprintf(PCMK__XML_PREFIX_CREATED |
99 | 0 | " %s: ", |
100 | 0 | xpath); |
101 | |
|
102 | 0 | temp_rc = pcmk__xml_show(out, prefix, change->children, 0, |
103 | 0 | pcmk__xml_fmt_pretty|pcmk__xml_fmt_open); |
104 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
105 | | |
106 | | // Overwrite all except the first two characters with spaces |
107 | 0 | for (char *ch = prefix + 2; *ch != '\0'; ch++) { |
108 | 0 | *ch = ' '; |
109 | 0 | } |
110 | |
|
111 | 0 | temp_rc = pcmk__xml_show(out, prefix, change->children, 0, |
112 | 0 | pcmk__xml_fmt_pretty |
113 | 0 | |pcmk__xml_fmt_children |
114 | 0 | |pcmk__xml_fmt_close); |
115 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
116 | 0 | free(prefix); |
117 | |
|
118 | 0 | } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) { |
119 | 0 | const char *position = pcmk__xe_get(change, PCMK_XE_POSITION); |
120 | |
|
121 | 0 | temp_rc = out->info(out, |
122 | 0 | PCMK__XML_PREFIX_MOVED " %s moved to offset %s", |
123 | 0 | xpath, position); |
124 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
125 | |
|
126 | 0 | } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) { |
127 | 0 | xmlNode *clist = pcmk__xe_first_child(change, PCMK_XE_CHANGE_LIST, |
128 | 0 | NULL, NULL); |
129 | 0 | GString *buffer_set = NULL; |
130 | 0 | GString *buffer_unset = NULL; |
131 | |
|
132 | 0 | for (const xmlNode *child = pcmk__xe_first_child(clist, NULL, NULL, |
133 | 0 | NULL); |
134 | 0 | child != NULL; child = pcmk__xe_next(child, NULL)) { |
135 | |
|
136 | 0 | const char *name = pcmk__xe_get(child, PCMK_XA_NAME); |
137 | |
|
138 | 0 | op = pcmk__xe_get(child, PCMK_XA_OPERATION); |
139 | 0 | if (op == NULL) { |
140 | 0 | continue; |
141 | 0 | } |
142 | | |
143 | 0 | if (strcmp(op, "set") == 0) { |
144 | 0 | const char *value = pcmk__xe_get(child, PCMK_XA_VALUE); |
145 | |
|
146 | 0 | pcmk__add_separated_word(&buffer_set, 256, "@", ", "); |
147 | 0 | pcmk__g_strcat(buffer_set, name, "=", value, NULL); |
148 | |
|
149 | 0 | } else if (strcmp(op, "unset") == 0) { |
150 | 0 | pcmk__add_separated_word(&buffer_unset, 256, "@", ", "); |
151 | 0 | g_string_append(buffer_unset, name); |
152 | 0 | } |
153 | 0 | } |
154 | |
|
155 | 0 | if (buffer_set != NULL) { |
156 | 0 | temp_rc = out->info(out, "+ %s: %s", xpath, buffer_set->str); |
157 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
158 | 0 | g_string_free(buffer_set, TRUE); |
159 | 0 | } |
160 | |
|
161 | 0 | if (buffer_unset != NULL) { |
162 | 0 | temp_rc = out->info(out, "-- %s: %s", |
163 | 0 | xpath, buffer_unset->str); |
164 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
165 | 0 | g_string_free(buffer_unset, TRUE); |
166 | 0 | } |
167 | |
|
168 | 0 | } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) { |
169 | 0 | int position = -1; |
170 | |
|
171 | 0 | pcmk__xe_get_int(change, PCMK_XE_POSITION, &position); |
172 | 0 | if (position >= 0) { |
173 | 0 | temp_rc = out->info(out, "-- %s (%d)", xpath, position); |
174 | 0 | } else { |
175 | 0 | temp_rc = out->info(out, "-- %s", xpath); |
176 | 0 | } |
177 | 0 | rc = pcmk__output_select_rc(rc, temp_rc); |
178 | 0 | } |
179 | 0 | } |
180 | |
|
181 | 0 | return rc; |
182 | 0 | } |
183 | | |
184 | | /*! |
185 | | * \internal |
186 | | * \brief Output a user-friendly form of an XML patchset |
187 | | * |
188 | | * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its |
189 | | * children) into a user-friendly combined diff output. |
190 | | * |
191 | | * \param[in,out] out Output object |
192 | | * \param[in] args Message-specific arguments |
193 | | * |
194 | | * \return Standard Pacemaker return code |
195 | | * |
196 | | * \note \p args should contain the following: |
197 | | * -# XML patchset |
198 | | */ |
199 | | PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") |
200 | | static int |
201 | | xml_patchset_default(pcmk__output_t *out, va_list args) |
202 | 0 | { |
203 | 0 | const xmlNode *patchset = va_arg(args, const xmlNode *); |
204 | |
|
205 | 0 | int format = 1; |
206 | |
|
207 | 0 | if (patchset == NULL) { |
208 | 0 | pcmk__trace("Empty patch"); |
209 | 0 | return pcmk_rc_no_output; |
210 | 0 | } |
211 | | |
212 | 0 | pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format); |
213 | 0 | if (format != 2) { |
214 | 0 | pcmk__err("Unknown patch format: %d", format); |
215 | 0 | return pcmk_rc_bad_xml_patch; |
216 | 0 | } |
217 | | |
218 | 0 | return xml_show_patchset(out, patchset); |
219 | 0 | } |
220 | | |
221 | | /*! |
222 | | * \internal |
223 | | * \brief Output a user-friendly form of an XML patchset |
224 | | * |
225 | | * This function parses an XML patchset (a \c PCMK_XE_DIFF element and its |
226 | | * children) into a user-friendly combined diff output. |
227 | | * |
228 | | * \param[in,out] out Output object |
229 | | * \param[in] args Message-specific arguments |
230 | | * |
231 | | * \return Standard Pacemaker return code |
232 | | * |
233 | | * \note \p args should contain the following: |
234 | | * -# XML patchset |
235 | | */ |
236 | | PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") |
237 | | static int |
238 | | xml_patchset_log(pcmk__output_t *out, va_list args) |
239 | 0 | { |
240 | 0 | static struct qb_log_callsite *patchset_cs = NULL; |
241 | |
|
242 | 0 | const xmlNode *patchset = va_arg(args, const xmlNode *); |
243 | |
|
244 | 0 | uint8_t log_level = pcmk__output_get_log_level(out); |
245 | 0 | int format = 1; |
246 | |
|
247 | 0 | if (log_level == PCMK__LOG_NEVER) { |
248 | 0 | return pcmk_rc_no_output; |
249 | 0 | } |
250 | | |
251 | 0 | if (patchset == NULL) { |
252 | 0 | pcmk__trace("Empty patch"); |
253 | 0 | return pcmk_rc_no_output; |
254 | 0 | } |
255 | | |
256 | 0 | if (patchset_cs == NULL) { |
257 | 0 | patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset", |
258 | 0 | log_level, __LINE__, |
259 | 0 | crm_trace_nonlog); |
260 | 0 | } |
261 | |
|
262 | 0 | if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) { |
263 | | // Nothing would be logged, so skip all the work |
264 | 0 | return pcmk_rc_no_output; |
265 | 0 | } |
266 | | |
267 | 0 | pcmk__xe_get_int(patchset, PCMK_XA_FORMAT, &format); |
268 | 0 | if (format != 2) { |
269 | 0 | pcmk__err("Unknown patch format: %d", format); |
270 | 0 | return pcmk_rc_bad_xml_patch; |
271 | 0 | } |
272 | | |
273 | 0 | return xml_show_patchset(out, patchset); |
274 | 0 | } |
275 | | |
276 | | /*! |
277 | | * \internal |
278 | | * \brief Output an XML patchset |
279 | | * |
280 | | * This function outputs an XML patchset (a \c PCMK_XE_DIFF element and its |
281 | | * children) without modification, as a CDATA block. |
282 | | * |
283 | | * \param[in,out] out Output object |
284 | | * \param[in] args Message-specific arguments |
285 | | * |
286 | | * \return Standard Pacemaker return code |
287 | | * |
288 | | * \note \p args should contain the following: |
289 | | * -# XML patchset |
290 | | */ |
291 | | PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") |
292 | | static int |
293 | | xml_patchset_xml(pcmk__output_t *out, va_list args) |
294 | 0 | { |
295 | 0 | const xmlNode *patchset = va_arg(args, const xmlNode *); |
296 | |
|
297 | 0 | if (patchset != NULL) { |
298 | 0 | GString *buf = g_string_sized_new(1024); |
299 | |
|
300 | 0 | pcmk__xml_string(patchset, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf, |
301 | 0 | 0); |
302 | |
|
303 | 0 | out->output_xml(out, PCMK_XE_XML_PATCHSET, buf->str); |
304 | 0 | g_string_free(buf, TRUE); |
305 | 0 | return pcmk_rc_ok; |
306 | 0 | } |
307 | 0 | pcmk__trace("Empty patch"); |
308 | 0 | return pcmk_rc_no_output; |
309 | 0 | } |
310 | | |
311 | | static pcmk__message_entry_t fmt_functions[] = { |
312 | | { "xml-patchset", "default", xml_patchset_default }, |
313 | | { "xml-patchset", "log", xml_patchset_log }, |
314 | | { "xml-patchset", "xml", xml_patchset_xml }, |
315 | | |
316 | | { NULL, NULL, NULL } |
317 | | }; |
318 | | |
319 | | /*! |
320 | | * \internal |
321 | | * \brief Register the formatting functions for XML patchsets |
322 | | * |
323 | | * \param[in,out] out Output object |
324 | | */ |
325 | | void |
326 | 0 | pcmk__register_patchset_messages(pcmk__output_t *out) { |
327 | 0 | pcmk__register_messages(out, fmt_functions); |
328 | 0 | } |