diff --git a/tools/Makefile.am b/tools/Makefile.am
index 5a7b3825bd..62b3bc0359 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,158 +1,159 @@
 #
 # Copyright 2004-2019 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU General Public License version 2
 # or later (GPLv2+) WITHOUT ANY WARRANTY.
 #
 include $(top_srcdir)/Makefile.common
 
 if BUILD_SYSTEMD
 systemdsystemunit_DATA	= crm_mon.service
 endif
 
 noinst_HEADERS		= crm_mon.h crm_resource.h
 
 pcmkdir			= $(datadir)/$(PACKAGE)
 pcmk_DATA		= report.common report.collector
 
 sbin_SCRIPTS		= crm_report crm_standby crm_master crm_failcount
 if BUILD_CIBSECRETS
 sbin_SCRIPTS		+= cibsecret
 endif
 noinst_SCRIPTS		= pcmk_simtimes
 
-EXTRA_DIST		= crm_mon.sysconfig		\
+EXTRA_DIST		= crm_diff.8.inc \
+			  crm_mon.sysconfig		\
 			  crm_mon.8.inc			\
 			  crm_node.8.inc \
 			  stonith_admin.8.inc
 
 sbin_PROGRAMS		= attrd_updater \
 			  cibadmin \
 			  crmadmin \
 			  crm_simulate \
 			  crm_attribute \
 			  crm_diff \
 			  crm_error \
 			  crm_mon \
 			  crm_node \
 			  crm_resource \
 			  crm_rule \
 			  crm_shadow \
 			  crm_verify \
 			  crm_ticket \
 			  iso8601 \
 			  stonith_admin
 
 if BUILD_SERVICELOG
 sbin_PROGRAMS		+= notifyServicelogEvent
 endif
 if BUILD_OPENIPMI_SERVICELOG
 sbin_PROGRAMS		+= ipmiservicelogd
 endif
 
 ## SOURCES
 
 # A few tools are just thin wrappers around crm_attribute.
 # This makes their help get updated when crm_attribute changes
 # (see Makefile.common).
 MAN8DEPS		= crm_attribute
 
 crmadmin_SOURCES	= crmadmin.c
 crmadmin_LDADD		= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_error_SOURCES	= crm_error.c
 crm_error_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
 cibadmin_SOURCES	= cibadmin.c
 cibadmin_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_shadow_SOURCES	= crm_shadow.c
 crm_shadow_LDADD	= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_node_SOURCES	= crm_node.c
 crm_node_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_simulate_SOURCES	= crm_simulate.c
 
 crm_simulate_LDADD	= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_diff_SOURCES	= crm_diff.c
 crm_diff_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_mon_SOURCES		= crm_mon.c crm_mon_curses.c crm_mon_output.c crm_mon_print.c crm_mon_runtime.c crm_mon_xml.c
 crm_mon_LDADD		= $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/fencing/libstonithd.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la	\
 			  $(CURSESLIBS)
 
 crm_verify_SOURCES	= crm_verify.c
 crm_verify_LDADD	= $(top_builddir)/lib/pengine/libpe_status.la 	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_attribute_SOURCES	= crm_attribute.c
 crm_attribute_LDADD	= $(top_builddir)/lib/cluster/libcrmcluster.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_resource_SOURCES	= crm_resource.c crm_resource_ban.c crm_resource_runtime.c crm_resource_print.c
 crm_resource_LDADD	= $(top_builddir)/lib/pengine/libpe_rules.la  	\
 			  $(top_builddir)/lib/fencing/libstonithd.la	\
 			  $(top_builddir)/lib/lrmd/liblrmd.la 		\
 			  $(top_builddir)/lib/services/libcrmservice.la \
 			  $(top_builddir)/lib/pengine/libpe_status.la 	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_rule_SOURCES 	= crm_rule.c
 crm_rule_LDADD		= $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/pengine/libpe_rules.la   \
 			  $(top_builddir)/lib/pengine/libpe_status.la   \
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 iso8601_SOURCES		= iso8601.c
 iso8601_LDADD		= $(top_builddir)/lib/common/libcrmcommon.la
 
 attrd_updater_SOURCES	= attrd_updater.c
 attrd_updater_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la
 
 crm_ticket_SOURCES	= crm_ticket.c
 crm_ticket_LDADD	= $(top_builddir)/lib/pengine/libpe_rules.la	\
 			  $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/pacemaker/libpacemaker.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/common/libcrmcommon.la
 
 stonith_admin_SOURCES	= stonith_admin.c
 stonith_admin_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la	\
 			  $(top_builddir)/lib/cib/libcib.la		\
 			  $(top_builddir)/lib/pengine/libpe_status.la	\
 			  $(top_builddir)/lib/fencing/libstonithd.la
 
 if BUILD_SERVICELOG
 notifyServicelogEvent_SOURCES	= notifyServicelogEvent.c
 notifyServicelogEvent_CFLAGS	= $(SERVICELOG_CFLAGS)
 notifyServicelogEvent_LDADD	= $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS)
 endif
 
 if BUILD_OPENIPMI_SERVICELOG
 ipmiservicelogd_SOURCES	= ipmiservicelogd.c
 ipmiservicelogd_CFLAGS	= $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS)
 ipmiservicelogd_LDFLAGS	= $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS)
 endif
 
 CLEANFILES = $(man8_MANS)
diff --git a/tools/crm_diff.8.inc b/tools/crm_diff.8.inc
new file mode 100644
index 0000000000..4229cbf8f3
--- /dev/null
+++ b/tools/crm_diff.8.inc
@@ -0,0 +1,5 @@
+[synopsis]
+crm_diff original_xml operation [options]
+
+/as a patch/
+.SH OPTIONS
diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index 6a03040129..8722646ea5 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,351 +1,394 @@
 /*
  * Copyright 2005-2018 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #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/cmdline_internal.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}
+#define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
+                "or apply such an output as a patch"
+
+struct {
+    gboolean apply;
+    gboolean as_cib;
+    gboolean no_version;
+    gboolean raw_1;
+    gboolean raw_2;
+    gboolean use_stdin;
+    char *xml_file_1;
+    char *xml_file_2;
+} options;
+
+gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
+
+static GOptionEntry original_xml_entries[] = {
+    { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
+      "XML is contained in the named file",
+      "FILE" },
+    { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
+      "XML is contained in the supplied string",
+      "STRING" },
+
+    { NULL }
 };
-/* *INDENT-ON* */
+
+static GOptionEntry operation_entries[] = {
+    { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
+      "Compare the original XML to the contents of the named file",
+      "FILE" },
+    { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
+      "Compare the original XML with the contents of the supplied string",
+      "STRING" },
+    { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
+      "Patch the original XML with the contents of the named file",
+      "FILE" },
+
+    { NULL }
+};
+
+static GOptionEntry addl_entries[] = {
+    { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
+      "Compare/patch the inputs as a CIB (includes versions details)",
+      NULL },
+    { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
+      "",
+      NULL },
+    { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
+      "Generate the difference without versions details",
+      NULL },
+
+    { NULL }
+};
+
+gboolean
+new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    options.raw_2 = TRUE;
+
+    if (options.xml_file_2 != NULL) {
+        free(options.xml_file_2);
+    }
+
+    options.xml_file_2 = strdup(optarg);
+    return TRUE;
+}
+
+gboolean
+original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    options.raw_1 = TRUE;
+
+    if (options.xml_file_1 != NULL) {
+        free(options.xml_file_1);
+    }
+
+    options.xml_file_1 = strdup(optarg);
+    return TRUE;
+}
+
+gboolean
+patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
+    options.apply = TRUE;
+
+    if (options.xml_file_2 != NULL) {
+        free(options.xml_file_2);
+    }
+
+    options.xml_file_2 = strdup(optarg);
+    return TRUE;
+}
 
 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 -pcmk_err_generic;
 }
 
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args) {
+    GOptionContext *context = NULL;
+
+    const char *description = "*Examples*\n\n"
+                              "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
+                              "\tcibadmin --query > cib-old.xml\n"
+                              "\tcibadmin --query > cib-new.xml\n\n"
+                              "Calculate and save the difference between the two files:\n\n"
+                              "\tcrm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
+                              "Apply the patch to the original file:\n\n"
+                              "\tcrm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
+                              "Apply the patch to the running cluster:\n\n"
+                              "\tcibadmin --patch patch.xml\n";
+
+    context = pcmk__build_arg_context(args, NULL, NULL);
+    g_option_context_set_description(context, description);
+
+    pcmk__add_arg_group(context, "xml", "Original XML:",
+                        "Show original XML options", original_xml_entries);
+    pcmk__add_arg_group(context, "operation", "Operation:",
+                        "Show operation options", operation_entries);
+    pcmk__add_arg_group(context, "additional", "Additional Options:",
+                        "Show additional options", addl_entries);
+    return context;
+}
+
 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;
+    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+
+    GError *error = NULL;
+    GOptionContext *context = NULL;
+    gchar **processed_args = NULL;
+
+    context = build_arg_context(args);
 
     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('?', CRM_EX_USAGE);
-    }
+    processed_args = pcmk__cmdline_preproc(argc, argv, "nopNO");
 
-    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, CRM_EX_OK);
-                break;
-            default:
-                printf("Argument %c (0%o) is not (yet?) supported\n", flag, flag);
-                ++argerr;
-                break;
-        }
+    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+        fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
+        rc = CRM_EX_USAGE;
+        goto done;
     }
 
-    if (optind < argc) {
-        printf("non-option ARGV-elements: ");
-        while (optind < argc)
-            printf("%s ", argv[optind++]);
-        printf("\n");
+    for (int i = 0; i < args->verbosity; i++) {
+        crm_bump_log_level(argc, argv);
     }
 
-    if (optind > argc) {
-        ++argerr;
+    if (args->version) {
+        /* FIXME:  When crm_diff is converted to use formatted output, this can go. */
+        crm_help('v', CRM_EX_USAGE);
     }
 
-    if (argerr) {
-        crm_help('?', CRM_EX_USAGE);
+    if (optind > argc) {
+        fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL));
+        rc = CRM_EX_USAGE;
+        goto done;
     }
 
-    if (apply && no_version) {
+    if (options.apply && options.no_version) {
         fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
-    } else if (as_cib && no_version) {
+    } else if (options.as_cib && options.no_version) {
         fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
-        return CRM_EX_USAGE;
+        rc = CRM_EX_USAGE;
+        goto done;
     }
 
-    if (raw_1) {
-        object_1 = string2xml(xml_file_1);
+    if (options.raw_1) {
+        object_1 = string2xml(options.xml_file_1);
 
-    } else if (use_stdin) {
+    } else if (options.use_stdin) {
         fprintf(stderr, "Input first XML fragment:");
         object_1 = stdin2xml();
 
-    } else if (xml_file_1 != NULL) {
-        object_1 = filename2xml(xml_file_1);
+    } else if (options.xml_file_1 != NULL) {
+        object_1 = filename2xml(options.xml_file_1);
     }
 
-    if (raw_2) {
-        object_2 = string2xml(xml_file_2);
+    if (options.raw_2) {
+        object_2 = string2xml(options.xml_file_2);
 
-    } else if (use_stdin) {
+    } else if (options.use_stdin) {
         fprintf(stderr, "Input second XML fragment:");
         object_2 = stdin2xml();
 
-    } else if (xml_file_2 != NULL) {
-        object_2 = filename2xml(xml_file_2);
+    } else if (options.xml_file_2 != NULL) {
+        object_2 = filename2xml(options.xml_file_2);
     }
 
     if (object_1 == NULL) {
         fprintf(stderr, "Could not parse the first XML fragment\n");
-        return CRM_EX_DATAERR;
+        rc = CRM_EX_DATAERR;
+        goto done;
     }
     if (object_2 == NULL) {
         fprintf(stderr, "Could not parse the second XML fragment\n");
-        return CRM_EX_DATAERR;
+        rc = CRM_EX_DATAERR;
+        goto done;
     }
 
-    if (apply) {
-        rc = apply_patch(object_1, object_2, as_cib);
+    if (options.apply) {
+        rc = apply_patch(object_1, object_2, options.as_cib);
     } else {
-        rc = generate_patch(object_1, object_2, xml_file_2, as_cib, no_version);
+        rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
     }
 
+done:
+    pcmk__free_arg_context(context);
+    free(options.xml_file_1);
+    free(options.xml_file_2);
     free_xml(object_1);
     free_xml(object_2);
     return crm_errno2exit(rc);
 }