This source file includes following definitions.
- new_string_cb
- original_string_cb
- patch_cb
- print_patch
- apply_patch
- log_patch_cib_versions
- strip_patch_cib_version
- generate_patch
- build_arg_context
- main
1
2
3
4
5
6
7
8
9
10 #include <crm_internal.h>
11
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <sys/param.h>
18 #include <sys/types.h>
19
20 #include <crm/crm.h>
21 #include <crm/msg_xml.h>
22 #include <crm/common/cmdline_internal.h>
23 #include <crm/common/xml.h>
24 #include <crm/common/ipc.h>
25 #include <crm/cib.h>
26
27 #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
28 "or apply such an output as a patch"
29
30 struct {
31 gboolean apply;
32 gboolean as_cib;
33 gboolean no_version;
34 gboolean raw_1;
35 gboolean raw_2;
36 gboolean use_stdin;
37 char *xml_file_1;
38 char *xml_file_2;
39 } options;
40
41 gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
42 gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
43 gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
44
45 static GOptionEntry original_xml_entries[] = {
46 { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
47 "XML is contained in the named file",
48 "FILE" },
49 { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
50 "XML is contained in the supplied string",
51 "STRING" },
52
53 { NULL }
54 };
55
56 static GOptionEntry operation_entries[] = {
57 { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
58 "Compare the original XML to the contents of the named file",
59 "FILE" },
60 { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
61 "Compare the original XML with the contents of the supplied string",
62 "STRING" },
63 { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
64 "Patch the original XML with the contents of the named file",
65 "FILE" },
66
67 { NULL }
68 };
69
70 static GOptionEntry addl_entries[] = {
71 { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
72 "Compare/patch the inputs as a CIB (includes versions details)",
73 NULL },
74 { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
75 "",
76 NULL },
77 { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
78 "Generate the difference without versions details",
79 NULL },
80
81 { NULL }
82 };
83
84 gboolean
85 new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
86 options.raw_2 = TRUE;
87
88 if (options.xml_file_2 != NULL) {
89 free(options.xml_file_2);
90 }
91
92 options.xml_file_2 = strdup(optarg);
93 return TRUE;
94 }
95
96 gboolean
97 original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
98 options.raw_1 = TRUE;
99
100 if (options.xml_file_1 != NULL) {
101 free(options.xml_file_1);
102 }
103
104 options.xml_file_1 = strdup(optarg);
105 return TRUE;
106 }
107
108 gboolean
109 patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
110 options.apply = TRUE;
111
112 if (options.xml_file_2 != NULL) {
113 free(options.xml_file_2);
114 }
115
116 options.xml_file_2 = strdup(optarg);
117 return TRUE;
118 }
119
120 static void
121 print_patch(xmlNode *patch)
122 {
123 char *buffer = dump_xml_formatted(patch);
124
125 printf("%s\n", crm_str(buffer));
126 free(buffer);
127 fflush(stdout);
128 }
129
130 static int
131 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
132 {
133 int rc;
134 xmlNode *output = copy_xml(input);
135
136 rc = xml_apply_patchset(output, patch, as_cib);
137 if (rc != pcmk_ok) {
138 fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc));
139 free_xml(output);
140 return rc;
141 }
142
143 if (output != NULL) {
144 const char *version;
145 char *buffer;
146
147 print_patch(output);
148
149 version = crm_element_value(output, XML_ATTR_CRM_VERSION);
150 buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
151 crm_trace("Digest: %s\n", crm_str(buffer));
152 free(buffer);
153 free_xml(output);
154 }
155 return pcmk_ok;
156 }
157
158 static void
159 log_patch_cib_versions(xmlNode *patch)
160 {
161 int add[] = { 0, 0, 0 };
162 int del[] = { 0, 0, 0 };
163
164 const char *fmt = NULL;
165 const char *digest = NULL;
166
167 xml_patch_versions(patch, add, del);
168 fmt = crm_element_value(patch, "format");
169 digest = crm_element_value(patch, XML_ATTR_DIGEST);
170
171 if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
172 crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
173 crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
174 }
175 }
176
177 static void
178 strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields)
179 {
180 int format = 1;
181
182 crm_element_value_int(patch, "format", &format);
183 if (format == 2) {
184 xmlNode *version_xml = find_xml_node(patch, "version", FALSE);
185
186 if (version_xml) {
187 free_xml(version_xml);
188 }
189
190 } else {
191 int i = 0;
192
193 const char *tags[] = {
194 XML_TAG_DIFF_REMOVED,
195 XML_TAG_DIFF_ADDED,
196 };
197
198 for (i = 0; i < DIMOF(tags); i++) {
199 xmlNode *tmp = NULL;
200 int lpc;
201
202 tmp = find_xml_node(patch, tags[i], FALSE);
203 if (tmp) {
204 for (lpc = 0; lpc < nvfields; lpc++) {
205 xml_remove_prop(tmp, vfields[lpc]);
206 }
207
208 tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
209 if (tmp) {
210 for (lpc = 0; lpc < nvfields; lpc++) {
211 xml_remove_prop(tmp, vfields[lpc]);
212 }
213 }
214 }
215 }
216 }
217 }
218
219 static int
220 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
221 gboolean as_cib, gboolean no_version)
222 {
223 xmlNode *output = NULL;
224
225 const char *vfields[] = {
226 XML_ATTR_GENERATION_ADMIN,
227 XML_ATTR_GENERATION,
228 XML_ATTR_NUMUPDATES,
229 };
230
231
232
233 if (no_version) {
234 int lpc;
235
236 for (lpc = 0; lpc < DIMOF(vfields); lpc++) {
237 crm_copy_xml_element(object_1, object_2, vfields[lpc]);
238 }
239 }
240
241 xml_track_changes(object_2, NULL, object_2, FALSE);
242 if(as_cib) {
243 xml_calculate_significant_changes(object_1, object_2);
244 } else {
245 xml_calculate_changes(object_1, object_2);
246 }
247 crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
248
249 output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
250
251 xml_log_changes(LOG_INFO, __func__, object_2);
252 xml_accept_changes(object_2);
253
254 if (output == NULL) {
255 return pcmk_ok;
256 }
257
258 patchset_process_digest(output, object_1, object_2, as_cib);
259
260 if (as_cib) {
261 log_patch_cib_versions(output);
262
263 } else if (no_version) {
264 strip_patch_cib_version(output, vfields, DIMOF(vfields));
265 }
266
267 xml_log_patchset(LOG_NOTICE, __func__, output);
268 print_patch(output);
269 free_xml(output);
270 return -pcmk_err_generic;
271 }
272
273 static GOptionContext *
274 build_arg_context(pcmk__common_args_t *args) {
275 GOptionContext *context = NULL;
276
277 const char *description = "Examples:\n\n"
278 "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
279 "\t# cibadmin --query > cib-old.xml\n\n"
280 "\t# cibadmin --query > cib-new.xml\n\n"
281 "Calculate and save the difference between the two files:\n\n"
282 "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
283 "Apply the patch to the original file:\n\n"
284 "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
285 "Apply the patch to the running cluster:\n\n"
286 "\t# cibadmin --patch -x patch.xml\n";
287
288 context = pcmk__build_arg_context(args, NULL, NULL, NULL);
289 g_option_context_set_description(context, description);
290
291 pcmk__add_arg_group(context, "xml", "Original XML:",
292 "Show original XML options", original_xml_entries);
293 pcmk__add_arg_group(context, "operation", "Operation:",
294 "Show operation options", operation_entries);
295 pcmk__add_arg_group(context, "additional", "Additional Options:",
296 "Show additional options", addl_entries);
297 return context;
298 }
299
300 int
301 main(int argc, char **argv)
302 {
303 int rc = pcmk_ok;
304 xmlNode *object_1 = NULL;
305 xmlNode *object_2 = NULL;
306
307 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
308
309 GError *error = NULL;
310 GOptionContext *context = NULL;
311 gchar **processed_args = NULL;
312
313 context = build_arg_context(args);
314
315 crm_log_cli_init("crm_diff");
316
317 processed_args = pcmk__cmdline_preproc(argv, "nopNO");
318
319 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
320 fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
321 rc = CRM_EX_USAGE;
322 goto done;
323 }
324
325 for (int i = 0; i < args->verbosity; i++) {
326 crm_bump_log_level(argc, argv);
327 }
328
329 if (args->version) {
330
331 pcmk__cli_help('v', CRM_EX_USAGE);
332 }
333
334 if (optind > argc) {
335 char *help = g_option_context_get_help(context, TRUE, NULL);
336
337 fprintf(stderr, "%s", help);
338 g_free(help);
339 rc = CRM_EX_USAGE;
340 goto done;
341 }
342
343 if (options.apply && options.no_version) {
344 fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
345 } else if (options.as_cib && options.no_version) {
346 fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
347 rc = CRM_EX_USAGE;
348 goto done;
349 }
350
351 if (options.raw_1) {
352 object_1 = string2xml(options.xml_file_1);
353
354 } else if (options.use_stdin) {
355 fprintf(stderr, "Input first XML fragment:");
356 object_1 = stdin2xml();
357
358 } else if (options.xml_file_1 != NULL) {
359 object_1 = filename2xml(options.xml_file_1);
360 }
361
362 if (options.raw_2) {
363 object_2 = string2xml(options.xml_file_2);
364
365 } else if (options.use_stdin) {
366 fprintf(stderr, "Input second XML fragment:");
367 object_2 = stdin2xml();
368
369 } else if (options.xml_file_2 != NULL) {
370 object_2 = filename2xml(options.xml_file_2);
371 }
372
373 if (object_1 == NULL) {
374 fprintf(stderr, "Could not parse the first XML fragment\n");
375 rc = CRM_EX_DATAERR;
376 goto done;
377 }
378 if (object_2 == NULL) {
379 fprintf(stderr, "Could not parse the second XML fragment\n");
380 rc = CRM_EX_DATAERR;
381 goto done;
382 }
383
384 if (options.apply) {
385 rc = apply_patch(object_1, object_2, options.as_cib);
386 } else {
387 rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
388 }
389
390 done:
391 g_strfreev(processed_args);
392 g_clear_error(&error);
393 pcmk__free_arg_context(context);
394 free(options.xml_file_1);
395 free(options.xml_file_2);
396 free_xml(object_1);
397 free_xml(object_2);
398 return crm_errno2exit(rc);
399 }