summaryrefslogtreecommitdiffstats
path: root/tools/cibadmin.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /tools/cibadmin.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/cibadmin.c')
-rw-r--r--tools/cibadmin.c954
1 files changed, 954 insertions, 0 deletions
diff --git a/tools/cibadmin.c b/tools/cibadmin.c
new file mode 100644
index 0000000..f80afae
--- /dev/null
+++ b/tools/cibadmin.c
@@ -0,0 +1,954 @@
+/*
+ * Copyright 2004-2023 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 <crm/crm.h>
+#include <crm/msg_xml.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/ipc.h>
+#include <crm/common/xml.h>
+#include <crm/cib/internal.h>
+
+#include <pacemaker-internal.h>
+
+#define SUMMARY "query and edit the Pacemaker configuration"
+
+#define INDENT " "
+
+enum cibadmin_section_type {
+ cibadmin_section_all = 0,
+ cibadmin_section_scope,
+ cibadmin_section_xpath,
+};
+
+static int request_id = 0;
+
+static cib_t *the_cib = NULL;
+static GMainLoop *mainloop = NULL;
+static crm_exit_t exit_code = CRM_EX_OK;
+
+static struct {
+ const char *cib_action;
+ int cmd_options;
+ enum cibadmin_section_type section_type;
+ char *cib_section;
+ char *validate_with;
+ gint message_timeout_sec;
+ enum pcmk__acl_render_how acl_render_mode;
+ gchar *cib_user;
+ gchar *dest_node;
+ gchar *input_file;
+ gchar *input_xml;
+ gboolean input_stdin;
+ bool delete_all;
+ gboolean allow_create;
+ gboolean force;
+ gboolean get_node_path;
+ gboolean local;
+ gboolean no_children;
+ gboolean sync_call;
+
+ /* @COMPAT: For "-!" version option. Not advertised nor marked as
+ * deprecated, but accepted.
+ */
+ gboolean extended_version;
+
+ //! \deprecated
+ gboolean no_bcast;
+} options;
+
+int do_init(void);
+static int do_work(xmlNode *input, xmlNode **output);
+void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
+ void *user_data);
+
+static void
+print_xml_output(xmlNode * xml)
+{
+ char *buffer;
+
+ if (!xml) {
+ return;
+ } else if (xml->type != XML_ELEMENT_NODE) {
+ return;
+ }
+
+ if (pcmk_is_set(options.cmd_options, cib_xpath_address)) {
+ const char *id = crm_element_value(xml, XML_ATTR_ID);
+
+ if (pcmk__str_eq((const char *)xml->name, "xpath-query", pcmk__str_casei)) {
+ xmlNode *child = NULL;
+
+ for (child = xml->children; child; child = child->next) {
+ print_xml_output(child);
+ }
+
+ } else if (id) {
+ printf("%s\n", id);
+ }
+
+ } else {
+ buffer = dump_xml_formatted(xml);
+ fprintf(stdout, "%s", pcmk__s(buffer, "<null>\n"));
+ free(buffer);
+ }
+}
+
+// Upgrade requested but already at latest schema
+static void
+report_schema_unchanged(void)
+{
+ const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
+
+ crm_info("Upgrade unnecessary: %s\n", err);
+ printf("Upgrade unnecessary: %s\n", err);
+ exit_code = CRM_EX_OK;
+}
+
+/*!
+ * \internal
+ * \brief Check whether the current CIB action is dangerous
+ * \return true if \p options.cib_action is dangerous, or false otherwise
+ */
+static inline bool
+cib_action_is_dangerous(void)
+{
+ return options.no_bcast || options.delete_all
+ || pcmk__str_any_of(options.cib_action,
+ PCMK__CIB_REQUEST_UPGRADE,
+ PCMK__CIB_REQUEST_ERASE,
+ NULL);
+}
+
+/*!
+ * \internal
+ * \brief Determine whether the given CIB scope is valid for \p cibadmin
+ *
+ * \param[in] scope Scope to validate
+ *
+ * \return true if \p scope is valid, or false otherwise
+ * \note An invalid scope applies the operation to the entire CIB.
+ */
+static inline bool
+scope_is_valid(const char *scope)
+{
+ return pcmk__str_any_of(scope,
+ XML_CIB_TAG_CONFIGURATION,
+ XML_CIB_TAG_NODES,
+ XML_CIB_TAG_RESOURCES,
+ XML_CIB_TAG_CONSTRAINTS,
+ XML_CIB_TAG_CRMCONFIG,
+ XML_CIB_TAG_RSCCONFIG,
+ XML_CIB_TAG_OPCONFIG,
+ XML_CIB_TAG_ACLS,
+ XML_TAG_FENCING_TOPOLOGY,
+ XML_CIB_TAG_TAGS,
+ XML_CIB_TAG_ALERTS,
+ XML_CIB_TAG_STATUS,
+ NULL);
+}
+
+static gboolean
+command_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ options.delete_all = false;
+
+ if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_UPGRADE;
+
+ } else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_QUERY;
+
+ } else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_ERASE;
+
+ } else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_BUMP;
+
+ } else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_CREATE;
+
+ } else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_MODIFY;
+
+ } else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH;
+
+ } else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_REPLACE;
+
+ } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_DELETE;
+
+ } else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) {
+ options.cib_action = PCMK__CIB_REQUEST_DELETE;
+ options.delete_all = true;
+
+ } else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) {
+ options.cib_action = "empty";
+ pcmk__str_update(&options.validate_with, optarg);
+
+ } else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) {
+ options.cib_action = "md5-sum";
+
+ } else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned",
+ NULL)) {
+ options.cib_action = "md5-sum-versioned";
+
+ } else {
+ // Should be impossible
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) {
+ options.acl_render_mode = pcmk__acl_render_default;
+
+ } else if (g_strcmp0(optarg, "namespace") == 0) {
+ options.acl_render_mode = pcmk__acl_render_namespace;
+
+ } else if (g_strcmp0(optarg, "text") == 0) {
+ options.acl_render_mode = pcmk__acl_render_text;
+
+ } else if (g_strcmp0(optarg, "color") == 0) {
+ options.acl_render_mode = pcmk__acl_render_color;
+
+ } else {
+ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
+ "Invalid value '%s' for option '%s'",
+ optarg, option_name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+section_cb(const gchar *option_name, const gchar *optarg, gpointer data,
+ GError **error)
+{
+ if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) {
+ options.section_type = cibadmin_section_scope;
+
+ } else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) {
+ options.section_type = cibadmin_section_xpath;
+
+ } else {
+ // Should be impossible
+ return FALSE;
+ }
+
+ pcmk__str_update(&options.cib_section, optarg);
+ return TRUE;
+}
+
+static GOptionEntry command_entries[] = {
+ { "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Upgrade the configuration to the latest syntax", NULL },
+
+ { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Query the contents of the CIB", NULL },
+
+ { "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Erase the contents of the whole CIB", NULL },
+
+ { "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Increase the CIB's epoch value by 1", NULL },
+
+ { "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Create an object in the CIB (will fail if object already exists)",
+ NULL },
+
+ { "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Find object somewhere in CIB's XML tree and update it (fails if object "
+ "does not exist unless -c is also specified)",
+ NULL },
+
+ { "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Supply an update in the form of an XML diff (see crm_diff(8))", NULL },
+
+ { "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Recursively replace an object in the CIB", NULL },
+
+ { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Delete first object matching supplied criteria (for example, "
+ "<" XML_ATTR_OP " " XML_ATTR_ID "=\"rsc1_op1\" "
+ XML_ATTR_NAME "=\"monitor\"/>).\n"
+ INDENT "The XML element name and all attributes must match in order for "
+ "the element to be deleted.",
+ NULL },
+
+ { "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ command_cb,
+ "When used with --xpath, remove all matching objects in the "
+ "configuration instead of just the first one",
+ NULL },
+
+ { "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ command_cb,
+ "Output an empty CIB. Accepts an optional schema name argument to use as "
+ "the " XML_ATTR_VALIDATION " value.\n"
+ INDENT "If no schema is given, the latest will be used.",
+ "[schema]" },
+
+ { "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb,
+ "Calculate the on-disk CIB digest", NULL },
+
+ { "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ command_cb, "Calculate an on-the-wire versioned CIB digest", NULL },
+
+ { NULL }
+};
+
+static GOptionEntry data_entries[] = {
+ /* @COMPAT: These arguments should be last-wins. We can have an enum option
+ * that stores the input type, along with a single string option that stores
+ * the XML string for --xml-text, filename for --xml-file, or NULL for
+ * --xml-pipe.
+ */
+ { "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
+ &options.input_xml, "Retrieve XML from the supplied string", "value" },
+
+ { "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME,
+ &options.input_file, "Retrieve XML from the named file", "value" },
+
+ { "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.input_stdin, "Retrieve XML from stdin", NULL },
+
+ { NULL }
+};
+
+static GOptionEntry addl_entries[] = {
+ { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
+ "Force the action to be performed", NULL },
+
+ { "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT,
+ &options.message_timeout_sec,
+ "Time (in seconds) to wait before declaring the operation failed",
+ "value" },
+
+ { "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user,
+ "Run the command with permissions of the named user (valid only for the "
+ "root and " CRM_DAEMON_USER " accounts)", "value" },
+
+ { "sync-call", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.sync_call, "Wait for call to complete before returning", NULL },
+
+ { "local", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.local,
+ "Command takes effect locally (should be used only for queries)", NULL },
+
+ { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
+ "Limit scope of operation to specific section of CIB\n"
+ INDENT "Valid values: " XML_CIB_TAG_CONFIGURATION ", " XML_CIB_TAG_NODES
+ ", " XML_CIB_TAG_RESOURCES ", " XML_CIB_TAG_CONSTRAINTS
+ ", " XML_CIB_TAG_CRMCONFIG ", " XML_CIB_TAG_RSCCONFIG ",\n"
+ INDENT " " XML_CIB_TAG_OPCONFIG ", " XML_CIB_TAG_ACLS
+ ", " XML_TAG_FENCING_TOPOLOGY ", " XML_CIB_TAG_TAGS
+ ", " XML_CIB_TAG_ALERTS ", " XML_CIB_TAG_STATUS "\n"
+ INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
+ "appear takes effect",
+ "value" },
+
+ { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb,
+ "A valid XPath to use instead of --scope/-o\n"
+ INDENT "If both --scope/-o and --xpath/-a are specified, the last one to "
+ "appear takes effect",
+ "value" },
+
+ { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.get_node_path,
+ "When performing XPath queries, return paths of any matches found\n"
+ INDENT "(for example, "
+ "\"/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION
+ "/" XML_CIB_TAG_RESOURCES "/" XML_CIB_TAG_INCARNATION
+ "[@" XML_ATTR_ID "='dummy-clone']"
+ "/" XML_CIB_TAG_RESOURCE "[@" XML_ATTR_ID "='dummy']\")",
+ NULL },
+
+ { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ show_access_cb,
+ "Whether to use syntax highlighting for ACLs (with -Q/--query and "
+ "-U/--user)\n"
+ INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, "
+ "default for non-terminal),\n"
+ INDENT " 'namespace', or 'auto' (use default value)\n"
+ INDENT "Default value: 'auto'",
+ "[value]" },
+
+ { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.allow_create,
+ "(Advanced) Allow target of --modify/-M to be created if it does not "
+ "exist",
+ NULL },
+
+ { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
+ &options.no_children,
+ "(Advanced) When querying an object, do not include its children in the "
+ "result",
+ NULL },
+
+ { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node,
+ "(Advanced) Send command to the specified host", "value" },
+
+ // @COMPAT: Deprecated
+ { "no-bcast", 'b', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
+ &options.no_bcast, "deprecated", NULL },
+
+ // @COMPAT: Deprecated
+ { "host", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING,
+ &options.dest_node, "deprecated", NULL },
+
+ { NULL }
+};
+
+static GOptionContext *
+build_arg_context(pcmk__common_args_t *args)
+{
+ const char *desc = NULL;
+ GOptionContext *context = NULL;
+
+ GOptionEntry extra_prog_entries[] = {
+ // @COMPAT: Deprecated
+ { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE,
+ &options.extended_version, "deprecated", NULL },
+
+ { NULL }
+ };
+
+ desc = "Examples:\n\n"
+ "Query the configuration from the local node:\n\n"
+ "\t# cibadmin --query --local\n\n"
+ "Query just the cluster options configuration:\n\n"
+ "\t# cibadmin --query --scope " XML_CIB_TAG_CRMCONFIG "\n\n"
+ "Query all '" XML_RSC_ATTR_TARGET_ROLE "' settings:\n\n"
+ "\t# cibadmin --query --xpath "
+ "\"//" XML_CIB_TAG_NVPAIR
+ "[@" XML_NVPAIR_ATTR_NAME "='" XML_RSC_ATTR_TARGET_ROLE"']\""
+ "\n\n"
+ "Remove all '" XML_RSC_ATTR_MANAGED "' settings:\n\n"
+ "\t# cibadmin --delete-all --xpath "
+ "\"//" XML_CIB_TAG_NVPAIR
+ "[@" XML_NVPAIR_ATTR_NAME "='" XML_RSC_ATTR_MANAGED "']\"\n\n"
+ "Remove the resource named 'old':\n\n"
+ "\t# cibadmin --delete --xml-text "
+ "'<" XML_CIB_TAG_RESOURCE " " XML_ATTR_ID "=\"old\"/>'\n\n"
+ "Remove all resources from the configuration:\n\n"
+ "\t# cibadmin --replace --scope " XML_CIB_TAG_RESOURCES
+ " --xml-text '<" XML_CIB_TAG_RESOURCES "/>'\n\n"
+ "Replace complete configuration with contents of "
+ "$HOME/pacemaker.xml:\n\n"
+ "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n"
+ "Replace " XML_CIB_TAG_CONSTRAINTS " section of configuration with "
+ "contents of $HOME/constraints.xml:\n\n"
+ "\t# cibadmin --replace --scope " XML_CIB_TAG_CONSTRAINTS
+ " --xml-file $HOME/constraints.xml\n\n"
+ "Increase configuration version to prevent old configurations from "
+ "being loaded accidentally:\n\n"
+ "\t# cibadmin --modify --xml-text "
+ "'<" XML_TAG_CIB " " XML_ATTR_GENERATION_ADMIN
+ "=\"" XML_ATTR_GENERATION_ADMIN "++\"/>'\n\n"
+ "Edit the configuration with your favorite $EDITOR:\n\n"
+ "\t# cibadmin --query > $HOME/local.xml\n\n"
+ "\t# $EDITOR $HOME/local.xml\n\n"
+ "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n"
+ "Assuming terminal, render configuration in color (green for "
+ "writable, blue for readable, red for\n"
+ "denied) to visualize permissions for user tony:\n\n"
+ "\t# cibadmin --show-access=color --query --user tony | less -r\n\n"
+ "SEE ALSO:\n"
+ " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n";
+
+ context = pcmk__build_arg_context(args, NULL, NULL, "<command>");
+ g_option_context_set_description(context, desc);
+
+ pcmk__add_main_args(context, extra_prog_entries);
+
+ pcmk__add_arg_group(context, "commands", "Commands:", "Show command help",
+ command_entries);
+ pcmk__add_arg_group(context, "data", "Data:", "Show data help",
+ data_entries);
+ pcmk__add_arg_group(context, "additional", "Additional Options:",
+ "Show additional options", addl_entries);
+ return context;
+}
+
+int
+main(int argc, char **argv)
+{
+ int rc = pcmk_rc_ok;
+ const char *source = NULL;
+ xmlNode *output = NULL;
+ xmlNode *input = NULL;
+ gchar *acl_cred = NULL;
+
+ GError *error = NULL;
+
+ pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
+ gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx");
+ GOptionContext *context = build_arg_context(args);
+
+ if (!g_option_context_parse_strv(context, &processed_args, &error)) {
+ exit_code = CRM_EX_USAGE;
+ goto done;
+ }
+
+ if (g_strv_length(processed_args) > 1) {
+ gchar *help = g_option_context_get_help(context, TRUE, NULL);
+ GString *extra = g_string_sized_new(128);
+
+ for (int lpc = 1; processed_args[lpc] != NULL; lpc++) {
+ if (extra->len > 0) {
+ g_string_append_c(extra, ' ');
+ }
+ g_string_append(extra, processed_args[lpc]);
+ }
+
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "non-option ARGV-elements: %s\n\n%s", extra->str, help);
+ g_free(help);
+ g_string_free(extra, TRUE);
+ goto done;
+ }
+
+ if (args->version || options.extended_version) {
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ /* FIXME: When cibadmin is converted to use formatted output, this can
+ * be replaced by out->version with the appropriate boolean flag.
+ *
+ * options.extended_version is deprecated and will be removed in a
+ * future release.
+ */
+ pcmk__cli_help(options.extended_version? '!' : 'v');
+ }
+
+ /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like
+ *
+ * (func@file:line) error: CIB <op> failures <XML>
+ *
+ * In cibadmin we explicitly output the XML portion without the prefixes. So
+ * we default to LOG_CRIT.
+ */
+ pcmk__cli_init_logging("cibadmin", 0);
+ set_crm_log_level(LOG_CRIT);
+
+ if (args->verbosity > 0) {
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_verbose);
+
+ for (int i = 0; i < args->verbosity; i++) {
+ crm_bump_log_level(argc, argv);
+ }
+ }
+
+ if (options.cib_action == NULL) {
+ // @COMPAT: Create a default command if other tools have one
+ gchar *help = g_option_context_get_help(context, TRUE, NULL);
+
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Must specify a command option\n\n%s", help);
+ g_free(help);
+ goto done;
+ }
+
+ if (strcmp(options.cib_action, "empty") == 0) {
+ // Output an empty CIB
+ char *buf = NULL;
+
+ output = createEmptyCib(1);
+ crm_xml_add(output, XML_ATTR_VALIDATION, options.validate_with);
+ buf = dump_xml_formatted(output);
+ fprintf(stdout, "%s", pcmk__s(buf, "<null>\n"));
+ free(buf);
+ goto done;
+ }
+
+ if (cib_action_is_dangerous() && !options.force) {
+ exit_code = CRM_EX_UNSAFE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "The supplied command is considered dangerous. To prevent "
+ "accidental destruction of the cluster, the --force flag "
+ "is required in order to proceed.");
+ goto done;
+ }
+
+ if (options.message_timeout_sec < 1) {
+ // Set default timeout
+ options.message_timeout_sec = 30;
+ }
+
+ if (options.section_type == cibadmin_section_xpath) {
+ // Enable getting section by XPath
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_xpath);
+
+ } else if (options.section_type == cibadmin_section_scope) {
+ if (!scope_is_valid(options.cib_section)) {
+ // @COMPAT: Consider requiring --force to proceed
+ fprintf(stderr,
+ "Invalid value '%s' for '--scope'. Operation will apply "
+ "to the entire CIB.\n", options.cib_section);
+ }
+ }
+
+ if (options.allow_create) {
+ // Allow target of --modify/-M to be created if it does not exist
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_can_create);
+ }
+
+ if (options.delete_all) {
+ // With cibadmin_section_xpath, remove all matching objects
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_multiple);
+ }
+
+ if (options.get_node_path) {
+ /* Enable getting node path of XPath query matches.
+ * Meaningful only if options.section_type == cibadmin_section_xpath.
+ */
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_xpath_address);
+ }
+
+ if (options.local) {
+ // Configure command to take effect only locally
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_scope_local);
+ }
+
+ // @COMPAT: Deprecated option
+ if (options.no_bcast) {
+ // Configure command to take effect only locally and not to broadcast
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_inhibit_bcast|cib_scope_local);
+ }
+
+ if (options.no_children) {
+ // When querying an object, don't include its children in the result
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_no_children);
+ }
+
+ if (options.sync_call
+ || (options.acl_render_mode != pcmk__acl_render_none)) {
+ /* Wait for call to complete before returning.
+ *
+ * The ACL render modes work only with sync calls due to differences in
+ * output handling between sync/async. It shouldn't matter to the user
+ * whether the call is synchronous; for a CIB query, we have to wait for
+ * the result in order to display it in any case.
+ */
+ cib__set_call_options(options.cmd_options, crm_system_name,
+ cib_sync_call);
+ }
+
+ if (options.input_file != NULL) {
+ input = filename2xml(options.input_file);
+ source = options.input_file;
+
+ } else if (options.input_xml != NULL) {
+ input = string2xml(options.input_xml);
+ source = "input string";
+
+ } else if (options.input_stdin) {
+ source = "STDIN";
+ input = stdin2xml();
+
+ } else if (options.acl_render_mode != pcmk__acl_render_none) {
+ char *username = pcmk__uid2username(geteuid());
+ bool required = pcmk_acl_required(username);
+
+ free(username);
+
+ if (required) {
+ if (options.force) {
+ fprintf(stderr, "The supplied command can provide skewed"
+ " result since it is run under user that also"
+ " gets guarded per ACLs on their own right."
+ " Continuing since --force flag was"
+ " provided.\n");
+
+ } else {
+ exit_code = CRM_EX_UNSAFE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "The supplied command can provide skewed result "
+ "since it is run under user that also gets guarded "
+ "per ACLs in their own right. To accept the risk "
+ "of such a possible distortion (without even "
+ "knowing it at this time), use the --force flag.");
+ goto done;
+ }
+ }
+
+ if (options.cib_user == NULL) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "The supplied command requires -U user specified.");
+ goto done;
+ }
+
+ /* We already stopped/warned ACL-controlled users about consequences.
+ *
+ * Note: acl_cred takes ownership of options.cib_user here.
+ * options.cib_user is set to NULL so that the CIB is obtained as the
+ * user running the cibadmin command. The CIB must be obtained as a user
+ * with full permissions in order to show the CIB correctly annotated
+ * for the options.cib_user's permissions.
+ */
+ acl_cred = options.cib_user;
+ options.cib_user = NULL;
+ }
+
+ if (input != NULL) {
+ crm_log_xml_debug(input, "[admin input]");
+
+ } else if (source != NULL) {
+ exit_code = CRM_EX_CONFIG;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Couldn't parse input from %s.", source);
+ goto done;
+ }
+
+ if (strcmp(options.cib_action, "md5-sum") == 0) {
+ char *digest = NULL;
+
+ if (input == NULL) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Please supply XML to process with -X, -x, or -p");
+ goto done;
+ }
+
+ digest = calculate_on_disk_digest(input);
+ fprintf(stderr, "Digest: ");
+ fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
+ free(digest);
+ goto done;
+
+ } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) {
+ char *digest = NULL;
+ const char *version = NULL;
+
+ if (input == NULL) {
+ exit_code = CRM_EX_USAGE;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Please supply XML to process with -X, -x, or -p");
+ goto done;
+ }
+
+ version = crm_element_value(input, XML_ATTR_CRM_VERSION);
+ digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version);
+ fprintf(stderr, "Versioned (%s) digest: ", version);
+ fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
+ free(digest);
+ goto done;
+ }
+
+ rc = do_init();
+ if (rc != pcmk_ok) {
+ rc = pcmk_legacy2rc(rc);
+ exit_code = pcmk_rc2exitc(rc);
+
+ crm_err("Init failed, could not perform requested operations: %s",
+ pcmk_rc_str(rc));
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Init failed, could not perform requested operations: %s",
+ pcmk_rc_str(rc));
+ goto done;
+ }
+
+ rc = do_work(input, &output);
+ if (rc > 0) {
+ /* wait for the reply by creating a mainloop and running it until
+ * the callbacks are invoked...
+ */
+ request_id = rc;
+
+ the_cib->cmds->register_callback(the_cib, request_id,
+ options.message_timeout_sec, FALSE,
+ NULL, "cibadmin_op_callback",
+ cibadmin_op_callback);
+
+ mainloop = g_main_loop_new(NULL, FALSE);
+
+ crm_trace("%s waiting for reply from the local CIB", crm_system_name);
+
+ crm_info("Starting mainloop");
+ g_main_loop_run(mainloop);
+
+ } else if ((rc == -pcmk_err_schema_unchanged)
+ && (strcmp(options.cib_action,
+ PCMK__CIB_REQUEST_UPGRADE) == 0)) {
+ report_schema_unchanged();
+
+ } else if (rc < 0) {
+ rc = pcmk_legacy2rc(rc);
+ crm_err("Call failed: %s", pcmk_rc_str(rc));
+ fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
+
+ if (rc == pcmk_rc_schema_validation) {
+ if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) {
+ xmlNode *obj = NULL;
+ int version = 0;
+
+ if (the_cib->cmds->query(the_cib, NULL, &obj,
+ options.cmd_options) == pcmk_ok) {
+ update_validation(&obj, &version, 0, TRUE, FALSE);
+ }
+ free_xml(obj);
+
+ } else if (output) {
+ validate_xml_verbose(output);
+ }
+ }
+ exit_code = pcmk_rc2exitc(rc);
+ }
+
+ if ((output != NULL)
+ && (options.acl_render_mode != pcmk__acl_render_none)) {
+
+ xmlDoc *acl_evaled_doc;
+ rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
+ if (rc == pcmk_rc_ok) {
+ xmlChar *rendered = NULL;
+
+ rc = pcmk__acl_evaled_render(acl_evaled_doc,
+ options.acl_render_mode, &rendered);
+ if (rc != pcmk_rc_ok) {
+ exit_code = CRM_EX_CONFIG;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Could not render evaluated access: %s",
+ pcmk_rc_str(rc));
+ goto done;
+ }
+ printf("%s\n", (char *) rendered);
+ free(rendered);
+
+ } else {
+ exit_code = CRM_EX_CONFIG;
+ g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
+ "Could not evaluate access per request (%s, error: %s)",
+ acl_cred, pcmk_rc_str(rc));
+ goto done;
+ }
+
+ } else if (output != NULL) {
+ print_xml_output(output);
+ }
+
+ crm_trace("%s exiting normally", crm_system_name);
+
+done:
+ g_strfreev(processed_args);
+ pcmk__free_arg_context(context);
+
+ g_free(options.cib_user);
+ g_free(options.dest_node);
+ g_free(options.input_file);
+ g_free(options.input_xml);
+ free(options.cib_section);
+ free(options.validate_with);
+
+ g_free(acl_cred);
+ free_xml(input);
+ free_xml(output);
+
+ rc = cib__clean_up_connection(&the_cib);
+ if (exit_code == CRM_EX_OK) {
+ exit_code = pcmk_rc2exitc(rc);
+ }
+
+ pcmk__output_and_clear_error(&error, NULL);
+ crm_exit(exit_code);
+}
+
+static int
+do_work(xmlNode *input, xmlNode **output)
+{
+ /* construct the request */
+ the_cib->call_timeout = options.message_timeout_sec;
+ if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0)
+ && pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) {
+ xmlNode *status = pcmk_find_cib_element(input, XML_CIB_TAG_STATUS);
+
+ if (status == NULL) {
+ create_xml_node(input, XML_CIB_TAG_STATUS);
+ }
+ }
+
+ crm_trace("Passing \"%s\" to variant_op...", options.cib_action);
+ return cib_internal_op(the_cib, options.cib_action, options.dest_node,
+ options.cib_section, input, output,
+ options.cmd_options, options.cib_user);
+}
+
+int
+do_init(void)
+{
+ int rc = pcmk_ok;
+
+ the_cib = cib_new();
+ rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
+ if (rc != pcmk_ok) {
+ crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
+ fprintf(stderr, "Could not connect to the CIB: %s\n",
+ pcmk_strerror(rc));
+ }
+
+ return rc;
+}
+
+void
+cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
+{
+ rc = pcmk_legacy2rc(rc);
+ exit_code = pcmk_rc2exitc(rc);
+
+ if (rc == pcmk_rc_schema_unchanged) {
+ report_schema_unchanged();
+
+ } else if (rc != pcmk_rc_ok) {
+ crm_warn("Call %s failed: %s " CRM_XS " rc=%d",
+ options.cib_action, pcmk_rc_str(rc), rc);
+ fprintf(stderr, "Call %s failed: %s\n",
+ options.cib_action, pcmk_rc_str(rc));
+ print_xml_output(output);
+
+ } else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0)
+ && (output == NULL)) {
+ crm_err("Query returned no output");
+ crm_log_xml_err(msg, "no output");
+
+ } else if (output == NULL) {
+ crm_info("Call passed");
+
+ } else {
+ crm_info("Call passed");
+ print_xml_output(output);
+ }
+
+ if (call_id == request_id) {
+ g_main_loop_quit(mainloop);
+
+ } else {
+ crm_info("Message was not the response we were looking for (%d vs. %d)",
+ call_id, request_id);
+ }
+}