This source file includes following definitions.
- new_string_cb
- original_string_cb
- patch_cb
- print_patch
- apply_patch
- log_patch_cib_versions
- 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/common/cmdline_internal.h>
22 #include <crm/common/output_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 pcmk__str_update(&options.xml_file_2, optarg);
88 return TRUE;
89 }
90
91 gboolean
92 original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
93 options.raw_1 = TRUE;
94 pcmk__str_update(&options.xml_file_1, optarg);
95 return TRUE;
96 }
97
98 gboolean
99 patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
100 options.apply = TRUE;
101 pcmk__str_update(&options.xml_file_2, optarg);
102 return TRUE;
103 }
104
105 static void
106 print_patch(xmlNode *patch)
107 {
108 GString *buffer = g_string_sized_new(1024);
109
110 pcmk__xml_string(patch, pcmk__xml_fmt_pretty, buffer, 0);
111
112 printf("%s", buffer->str);
113 g_string_free(buffer, TRUE);
114 fflush(stdout);
115 }
116
117
118 static int
119 apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
120 {
121 xmlNode *output = pcmk__xml_copy(NULL, input);
122 int rc = xml_apply_patchset(output, patch, as_cib);
123
124 rc = pcmk_legacy2rc(rc);
125 if (rc != pcmk_rc_ok) {
126 fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc));
127 pcmk__xml_free(output);
128 return rc;
129 }
130
131 if (output != NULL) {
132 char *buffer;
133
134 print_patch(output);
135
136 buffer = pcmk__digest_xml(output, true);
137 crm_trace("Digest: %s", pcmk__s(buffer, "<null>\n"));
138 free(buffer);
139 pcmk__xml_free(output);
140 }
141 return pcmk_rc_ok;
142 }
143
144 static void
145 log_patch_cib_versions(xmlNode *patch)
146 {
147 int add[] = { 0, 0, 0 };
148 int del[] = { 0, 0, 0 };
149
150 const char *fmt = NULL;
151 const char *digest = NULL;
152
153 xml_patch_versions(patch, add, del);
154 fmt = crm_element_value(patch, PCMK_XA_FORMAT);
155 digest = crm_element_value(patch, PCMK__XA_DIGEST);
156
157 if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
158 crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
159 crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
160 }
161 }
162
163
164 static int
165 generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
166 gboolean as_cib, gboolean no_version)
167 {
168 const char *vfields[] = {
169 PCMK_XA_ADMIN_EPOCH,
170 PCMK_XA_EPOCH,
171 PCMK_XA_NUM_UPDATES,
172 };
173
174 xmlNode *output = NULL;
175
176
177
178 if (no_version) {
179 int lpc;
180
181 for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
182 crm_copy_xml_element(object_1, object_2, vfields[lpc]);
183 }
184 }
185
186 xml_track_changes(object_2, NULL, object_2, FALSE);
187 if(as_cib) {
188 xml_calculate_significant_changes(object_1, object_2);
189 } else {
190 xml_calculate_changes(object_1, object_2);
191 }
192 crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target"));
193
194 output = xml_create_patchset(0, object_1, object_2, NULL, FALSE);
195
196 pcmk__log_xml_changes(LOG_INFO, object_2);
197 xml_accept_changes(object_2);
198
199 if (output == NULL) {
200 return pcmk_rc_ok;
201 }
202
203 patchset_process_digest(output, object_1, object_2, as_cib);
204
205 if (as_cib) {
206 log_patch_cib_versions(output);
207
208 } else if (no_version) {
209 pcmk__xml_free(pcmk__xe_first_child(output, PCMK_XE_VERSION, NULL,
210 NULL));
211 }
212
213 pcmk__log_xml_patchset(LOG_NOTICE, output);
214 print_patch(output);
215 pcmk__xml_free(output);
216
217
218
219
220
221 return pcmk_rc_error;
222 }
223
224 static GOptionContext *
225 build_arg_context(pcmk__common_args_t *args) {
226 GOptionContext *context = NULL;
227
228 const char *description = "Examples:\n\n"
229 "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
230 "\t# cibadmin --query > cib-old.xml\n\n"
231 "\t# cibadmin --query > cib-new.xml\n\n"
232 "Calculate and save the difference between the two files:\n\n"
233 "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
234 "Apply the patch to the original file:\n\n"
235 "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
236 "Apply the patch to the running cluster:\n\n"
237 "\t# cibadmin --patch -x patch.xml\n";
238
239 context = pcmk__build_arg_context(args, NULL, NULL, NULL);
240 g_option_context_set_description(context, description);
241
242 pcmk__add_arg_group(context, "xml", "Original XML:",
243 "Show original XML options", original_xml_entries);
244 pcmk__add_arg_group(context, "operation", "Operation:",
245 "Show operation options", operation_entries);
246 pcmk__add_arg_group(context, "additional", "Additional Options:",
247 "Show additional options", addl_entries);
248 return context;
249 }
250
251 int
252 main(int argc, char **argv)
253 {
254 xmlNode *object_1 = NULL;
255 xmlNode *object_2 = NULL;
256
257 crm_exit_t exit_code = CRM_EX_OK;
258 GError *error = NULL;
259
260 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
261 gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
262 GOptionContext *context = build_arg_context(args);
263
264 int rc = pcmk_rc_ok;
265
266 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
267 exit_code = CRM_EX_USAGE;
268 goto done;
269 }
270
271 pcmk__cli_init_logging("crm_diff", args->verbosity);
272
273 if (args->version) {
274 g_strfreev(processed_args);
275 pcmk__free_arg_context(context);
276
277 pcmk__cli_help('v');
278 }
279
280 if (options.apply && options.no_version) {
281 fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
282 } else if (options.as_cib && options.no_version) {
283 fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
284 exit_code = CRM_EX_USAGE;
285 goto done;
286 }
287
288 if (options.raw_1) {
289 object_1 = pcmk__xml_parse(options.xml_file_1);
290
291 } else if (options.use_stdin) {
292 fprintf(stderr, "Input first XML fragment:");
293 object_1 = pcmk__xml_read(NULL);
294
295 } else if (options.xml_file_1 != NULL) {
296 object_1 = pcmk__xml_read(options.xml_file_1);
297 }
298
299 if (options.raw_2) {
300 object_2 = pcmk__xml_parse(options.xml_file_2);
301
302 } else if (options.use_stdin) {
303 fprintf(stderr, "Input second XML fragment:");
304 object_2 = pcmk__xml_read(NULL);
305
306 } else if (options.xml_file_2 != NULL) {
307 object_2 = pcmk__xml_read(options.xml_file_2);
308 }
309
310 if (object_1 == NULL) {
311 fprintf(stderr, "Could not parse the first XML fragment\n");
312 exit_code = CRM_EX_DATAERR;
313 goto done;
314 }
315 if (object_2 == NULL) {
316 fprintf(stderr, "Could not parse the second XML fragment\n");
317 exit_code = CRM_EX_DATAERR;
318 goto done;
319 }
320
321 if (options.apply) {
322 rc = apply_patch(object_1, object_2, options.as_cib);
323 } else {
324 rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
325 }
326 exit_code = pcmk_rc2exitc(rc);
327
328 done:
329 g_strfreev(processed_args);
330 pcmk__free_arg_context(context);
331 free(options.xml_file_1);
332 free(options.xml_file_2);
333 pcmk__xml_free(object_1);
334 pcmk__xml_free(object_2);
335
336 pcmk__output_and_clear_error(&error, NULL);
337 crm_exit(exit_code);
338 }