diff --git a/tools/crm_diff.c b/tools/crm_diff.c index d19eb07df4..6a65aafa2f 100644 --- a/tools/crm_diff.c +++ b/tools/crm_diff.c @@ -1,335 +1,357 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* *INDENT-OFF* */ static struct crm_option long_options[] = { /* Top-level Options */ {"help", 0, 0, '?', "\t\tThis text"}, {"version", 0, 0, '$', "\t\tVersion information" }, {"verbose", 0, 0, 'V', "\t\tIncrease debug output\n"}, {"-spacer-", 1, 0, '-', "\nOriginal XML:"}, {"original", 1, 0, 'o', "\tXML is contained in the named file"}, {"original-string", 1, 0, 'O', "XML is contained in the supplied string"}, {"-spacer-", 1, 0, '-', "\nOperation:"}, {"new", 1, 0, 'n', "\tCompare the original XML to the contents of the named file"}, {"new-string", 1, 0, 'N', "\tCompare the original XML to the contents of the supplied string"}, {"patch", 1, 0, 'p', "\tPatch the original XML with the contents of the named file"}, {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, {"cib", 0, 0, 'c', "\t\tCompare/patch the inputs as a CIB (includes versions details)"}, {"stdin", 0, 0, 's', NULL, 1}, {"no-version", 0, 0, 'u', "\tGenerate the difference without versions details"}, {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " cibadmin --query > cib-old.xml", pcmk_option_example}, {"-spacer-", 1, 0, '-', " cibadmin --query > cib-new.xml", pcmk_option_example}, {"-spacer-", 1, 0, '-', "Calculate and save the difference between the two files:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_diff --original cib-old.xml --new cib-new.xml > patch.xml", pcmk_option_example }, {"-spacer-", 1, 0, '-', "Apply the patch to the original file:", pcmk_option_paragraph }, {"-spacer-", 1, 0, '-', " crm_diff --original cib-old.xml --patch patch.xml > updated.xml", pcmk_option_example }, {"-spacer-", 1, 0, '-', "Apply the patch to the running cluster:", pcmk_option_paragraph }, {"-spacer-", 1, 0, '-', " cibadmin --patch patch.xml", pcmk_option_example }, {0, 0, 0, 0} }; /* *INDENT-ON* */ static void print_patch(xmlNode *patch) { char *buffer = dump_xml_formatted(patch); printf("%s\n", crm_str(buffer)); free(buffer); fflush(stdout); } static int apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib) { int rc; xmlNode *output = copy_xml(input); rc = xml_apply_patchset(output, patch, as_cib); if (rc != pcmk_ok) { fprintf(stderr, "Could not apply patch: %s\n", pcmk_strerror(rc)); free_xml(output); return rc; } if (output != NULL) { const char *version; char *buffer; print_patch(output); free_xml(output); version = crm_element_value(output, XML_ATTR_CRM_VERSION); buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version); crm_trace("Digest: %s\n", crm_str(buffer)); free(buffer); } return pcmk_ok; } +static void +log_patch_cib_versions(xmlNode *patch) +{ + int add[] = { 0, 0, 0 }; + int del[] = { 0, 0, 0 }; + + const char *fmt = NULL; + const char *digest = NULL; + + xml_patch_versions(patch, add, del); + fmt = crm_element_value(patch, "format"); + digest = crm_element_value(patch, XML_ATTR_DIGEST); + + if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) { + crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); + crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); + } +} + +static void +strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields) +{ + int format = 1; + + crm_element_value_int(patch, "format", &format); + if (format == 2) { + xmlNode *version_xml = find_xml_node(patch, "version", FALSE); + + if (version_xml) { + free_xml(version_xml); + } + + } else { + int i = 0; + + const char *tags[] = { + XML_TAG_DIFF_REMOVED, + XML_TAG_DIFF_ADDED, + }; + + for (i = 0; i < DIMOF(tags); i++) { + xmlNode *tmp = NULL; + int lpc; + + tmp = find_xml_node(patch, tags[i], FALSE); + if (tmp) { + for (lpc = 0; lpc < nvfields; lpc++) { + xml_remove_prop(tmp, vfields[lpc]); + } + + tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); + if (tmp) { + for (lpc = 0; lpc < nvfields; lpc++) { + xml_remove_prop(tmp, vfields[lpc]); + } + } + } + } + } +} + +static int +generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2, + gboolean as_cib, gboolean no_version) +{ + xmlNode *output = NULL; + + const char *vfields[] = { + XML_ATTR_GENERATION_ADMIN, + XML_ATTR_GENERATION, + XML_ATTR_NUMUPDATES, + }; + + /* If we're ignoring the version, make the version information + * identical, so it isn't detected as a change. */ + if (no_version) { + int lpc; + + for (lpc = 0; lpc < DIMOF(vfields); lpc++) { + crm_copy_xml_element(object_1, object_2, vfields[lpc]); + } + } + + xml_track_changes(object_2, NULL, object_2, FALSE); + xml_calculate_changes(object_1, object_2); + crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target")); + + output = xml_create_patchset(0, object_1, object_2, NULL, FALSE); + + xml_log_changes(LOG_INFO, __FUNCTION__, object_2); + xml_accept_changes(object_2); + + if (output == NULL) { + return pcmk_ok; + } + + patchset_process_digest(output, object_1, object_2, as_cib); + + if (as_cib) { + log_patch_cib_versions(output); + + } else if (no_version) { + strip_patch_cib_version(output, vfields, DIMOF(vfields)); + } + + xml_log_patchset(LOG_NOTICE, __FUNCTION__, output); + print_patch(output); + free_xml(output); + return 1; +} + int main(int argc, char **argv) { gboolean apply = FALSE; gboolean raw_1 = FALSE; gboolean raw_2 = FALSE; gboolean use_stdin = FALSE; gboolean as_cib = FALSE; gboolean no_version = FALSE; int argerr = 0; int flag; int rc = pcmk_ok; xmlNode *object_1 = NULL; xmlNode *object_2 = NULL; - xmlNode *output = NULL; const char *xml_file_1 = NULL; const char *xml_file_2 = NULL; int option_index = 0; crm_log_cli_init("crm_diff"); crm_set_options(NULL, "original_xml operation [options]", long_options, "crm_diff can compare two Pacemaker configurations (in XML format) to\n" "produce a custom diff-like output, or apply such an output as a patch\n"); if (argc < 2) { crm_help('?', EX_USAGE); } while (1) { flag = crm_get_option(argc, argv, &option_index); if (flag == -1) break; switch (flag) { case 'o': xml_file_1 = optarg; break; case 'O': xml_file_1 = optarg; raw_1 = TRUE; break; case 'n': xml_file_2 = optarg; break; case 'N': xml_file_2 = optarg; raw_2 = TRUE; break; case 'p': xml_file_2 = optarg; apply = TRUE; break; case 's': use_stdin = TRUE; break; case 'c': as_cib = TRUE; break; case 'u': no_version = TRUE; break; case 'V': crm_bump_log_level(argc, argv); break; case '?': case '$': crm_help(flag, EX_OK); break; default: printf("Argument %c (0%o) is not (yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', EX_USAGE); } if (apply && no_version) { fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n"); } else if (as_cib && no_version) { fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n"); return 1; } if (raw_1) { object_1 = string2xml(xml_file_1); } else if (use_stdin) { fprintf(stderr, "Input first XML fragment:"); object_1 = stdin2xml(); } else if (xml_file_1 != NULL) { object_1 = filename2xml(xml_file_1); } if (raw_2) { object_2 = string2xml(xml_file_2); } else if (use_stdin) { fprintf(stderr, "Input second XML fragment:"); object_2 = stdin2xml(); } else if (xml_file_2 != NULL) { object_2 = filename2xml(xml_file_2); } if (object_1 == NULL) { fprintf(stderr, "Could not parse the first XML fragment\n"); return 1; } if (object_2 == NULL) { fprintf(stderr, "Could not parse the second XML fragment\n"); return 1; } if (apply) { rc = apply_patch(object_1, object_2, as_cib); } else { - int lpc = 0; - const char *vfields[] = { - XML_ATTR_GENERATION_ADMIN, - XML_ATTR_GENERATION, - XML_ATTR_NUMUPDATES, - }; - - /* If we're ignoring the version, make the version information - * identical, so it isn't detected as a change. */ - if (no_version) { - for (lpc = 0; lpc < DIMOF(vfields); lpc++) { - crm_copy_xml_element(object_1, object_2, vfields[lpc]); - } - } - - xml_track_changes(object_2, NULL, object_2, FALSE); - xml_calculate_changes(object_1, object_2); - crm_log_xml_debug(object_2, xml_file_2?xml_file_2:"target"); - - output = xml_create_patchset(0, object_1, object_2, NULL, FALSE); - - xml_log_changes(LOG_INFO, __FUNCTION__, object_2); - xml_accept_changes(object_2); - - if (output) { - patchset_process_digest(output, object_1, object_2, as_cib); - } - - if(as_cib && output) { - int add[] = { 0, 0, 0 }; - int del[] = { 0, 0, 0 }; - - const char *fmt = NULL; - const char *digest = NULL; - - xml_patch_versions(output, add, del); - fmt = crm_element_value(output, "format"); - digest = crm_element_value(output, XML_ATTR_DIGEST); - - if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) { - crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); - crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); - } - - } else if (output && no_version) { - int format = 1; - - crm_element_value_int(output, "format", &format); - if (format == 2) { - xmlNode *version_xml = find_xml_node(output, "version", FALSE); - - if (version_xml) { - free_xml(version_xml); - } - - } else { - int i = 0; - - const char *tags[] = { - XML_TAG_DIFF_REMOVED, - XML_TAG_DIFF_ADDED, - }; - - for (i = 0; i < DIMOF(tags); i++) { - xmlNode *tmp = NULL; - - tmp = find_xml_node(output, tags[i], FALSE); - if (tmp) { - for (lpc = 0; lpc < DIMOF(vfields); lpc++) { - xml_remove_prop(tmp, vfields[lpc]); - } - - tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); - if (tmp) { - for (lpc = 0; lpc < DIMOF(vfields); lpc++) { - xml_remove_prop(tmp, vfields[lpc]); - } - } - } - } - } - } - xml_log_patchset(LOG_NOTICE, __FUNCTION__, output); - - if (output != NULL) { - print_patch(output); - free_xml(output); - rc = 1; - } + rc = generate_patch(object_1, object_2, xml_file_2, as_cib, no_version); } free_xml(object_1); free_xml(object_2); return rc; }