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_original;
35 gboolean raw_new;
36 gboolean use_stdin;
37 char *xml_file_original;
38 char *xml_file_new;
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_original,
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_new,
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_new = TRUE;
87 pcmk__str_update(&options.xml_file_new, 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_original = TRUE;
94 pcmk__str_update(&options.xml_file_original, 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_new, 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_original, xmlNode *object_new, const char *xml_file_new,
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_original, object_new, vfields[lpc]);
183 }
184 }
185
186 if (as_cib) {
187 pcmk__xml_doc_set_flags(object_new->doc, pcmk__xf_ignore_attr_pos);
188 }
189 pcmk__xml_mark_changes(object_original, object_new);
190 crm_log_xml_debug(object_new, (xml_file_new? xml_file_new: "target"));
191
192 output = xml_create_patchset(0, object_original, object_new, NULL, FALSE);
193
194 pcmk__log_xml_changes(LOG_INFO, object_new);
195 pcmk__xml_commit_changes(object_new->doc);
196
197 if (output == NULL) {
198 return pcmk_rc_ok;
199 }
200
201 patchset_process_digest(output, object_original, object_new, as_cib);
202
203 if (as_cib) {
204 log_patch_cib_versions(output);
205
206 } else if (no_version) {
207 pcmk__xml_free(pcmk__xe_first_child(output, PCMK_XE_VERSION, NULL,
208 NULL));
209 }
210
211 pcmk__log_xml_patchset(LOG_NOTICE, output);
212 print_patch(output);
213 pcmk__xml_free(output);
214
215
216
217
218
219 return pcmk_rc_error;
220 }
221
222 static GOptionContext *
223 build_arg_context(pcmk__common_args_t *args) {
224 GOptionContext *context = NULL;
225
226 const char *description = "Examples:\n\n"
227 "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
228 "\t# cibadmin --query > cib-old.xml\n\n"
229 "\t# cibadmin --query > cib-new.xml\n\n"
230 "Calculate and save the difference between the two files:\n\n"
231 "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
232 "Apply the patch to the original file:\n\n"
233 "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
234 "Apply the patch to the running cluster:\n\n"
235 "\t# cibadmin --patch -x patch.xml\n";
236
237 context = pcmk__build_arg_context(args, NULL, NULL, NULL);
238 g_option_context_set_description(context, description);
239
240 pcmk__add_arg_group(context, "xml", "Original XML:",
241 "Show original XML options", original_xml_entries);
242 pcmk__add_arg_group(context, "operation", "Operation:",
243 "Show operation options", operation_entries);
244 pcmk__add_arg_group(context, "additional", "Additional Options:",
245 "Show additional options", addl_entries);
246 return context;
247 }
248
249 int
250 main(int argc, char **argv)
251 {
252 xmlNode *object_original = NULL;
253 xmlNode *object_new = NULL;
254
255 crm_exit_t exit_code = CRM_EX_OK;
256 GError *error = NULL;
257
258 pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
259 gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
260 GOptionContext *context = build_arg_context(args);
261
262 int rc = pcmk_rc_ok;
263
264 if (!g_option_context_parse_strv(context, &processed_args, &error)) {
265 exit_code = CRM_EX_USAGE;
266 goto done;
267 }
268
269 pcmk__cli_init_logging("crm_diff", args->verbosity);
270
271 if (args->version) {
272 g_strfreev(processed_args);
273 pcmk__free_arg_context(context);
274
275 pcmk__cli_help('v');
276 }
277
278 if (options.apply && options.no_version) {
279 fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
280 } else if (options.as_cib && options.no_version) {
281 fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
282 exit_code = CRM_EX_USAGE;
283 goto done;
284 }
285
286 if (options.raw_original) {
287 object_original = pcmk__xml_parse(options.xml_file_original);
288
289 } else if (options.use_stdin) {
290 fprintf(stderr, "Input first XML fragment:");
291 object_original = pcmk__xml_read(NULL);
292
293 } else if (options.xml_file_original != NULL) {
294 object_original = pcmk__xml_read(options.xml_file_original);
295 }
296
297 if (options.raw_new) {
298 object_new = pcmk__xml_parse(options.xml_file_new);
299
300 } else if (options.use_stdin) {
301 fprintf(stderr, "Input second XML fragment:");
302 object_new = pcmk__xml_read(NULL);
303
304 } else if (options.xml_file_new != NULL) {
305 object_new = pcmk__xml_read(options.xml_file_new);
306 }
307
308 if (object_original == NULL) {
309 fprintf(stderr, "Could not parse the first XML fragment\n");
310 exit_code = CRM_EX_DATAERR;
311 goto done;
312 }
313 if (object_new == NULL) {
314 fprintf(stderr, "Could not parse the second XML fragment\n");
315 exit_code = CRM_EX_DATAERR;
316 goto done;
317 }
318
319 if (options.apply) {
320 rc = apply_patch(object_original, object_new, options.as_cib);
321 } else {
322 rc = generate_patch(object_original, object_new, options.xml_file_new, options.as_cib, options.no_version);
323 }
324 exit_code = pcmk_rc2exitc(rc);
325
326 done:
327 g_strfreev(processed_args);
328 pcmk__free_arg_context(context);
329 free(options.xml_file_original);
330 free(options.xml_file_new);
331 pcmk__xml_free(object_original);
332 pcmk__xml_free(object_new);
333
334 pcmk__output_and_clear_error(&error, NULL);
335 crm_exit(exit_code);
336 }