blob: 0ec8e44deac5c46768f093afd3057101d3b538d0 [file] [log] [blame]
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* 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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
/* *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);
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);
free_xml(output);
}
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);
if(as_cib) {
xml_calculate_significant_changes(object_1, object_2);
} else {
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;
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 {
rc = generate_patch(object_1, object_2, xml_file_2, as_cib, no_version);
}
free_xml(object_1);
free_xml(object_2);
return rc;
}