diff options
Diffstat (limited to 'tools/crm_attribute.c')
-rw-r--r-- | tools/crm_attribute.c | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c new file mode 100644 index 0000000..358b150 --- /dev/null +++ b/tools/crm_attribute.c @@ -0,0 +1,883 @@ +/* + * 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 <stdint.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <time.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/common/util.h> +#include <crm/cluster.h> + +#include <crm/cib.h> +#include <crm/cib/internal.h> +#include <crm/common/attrd_internal.h> +#include <crm/common/cmdline_internal.h> +#include <crm/common/ipc_attrd_internal.h> +#include <crm/common/ipc_controld.h> +#include <crm/common/output_internal.h> +#include <sys/utsname.h> + +#include <pacemaker-internal.h> + +#define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes" + +GError *error = NULL; +crm_exit_t exit_code = CRM_EX_OK; +uint64_t cib_opts = cib_sync_call; + +PCMK__OUTPUT_ARGS("attribute", "const char *", "const char *", "const char *", + "const char *", "const char *") +static int +attribute_text(pcmk__output_t *out, va_list args) +{ + const char *scope = va_arg(args, const char *); + const char *instance = va_arg(args, const char *); + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + const char *host G_GNUC_UNUSED = va_arg(args, const char *); + + if (out->quiet) { + if (value != NULL) { + pcmk__formatted_printf(out, "%s\n", value); + } + } else { + out->info(out, "%s%s %s%s %s%s value=%s", + scope ? "scope=" : "", scope ? scope : "", + instance ? "id=" : "", instance ? instance : "", + name ? "name=" : "", name ? name : "", + value ? value : "(null)"); + } + + return pcmk_rc_ok; +} + +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +static pcmk__message_entry_t fmt_functions[] = { + { "attribute", "text", attribute_text }, + + { NULL, NULL, NULL } +}; + +struct { + char command; + gchar *attr_default; + gchar *attr_id; + gchar *attr_name; + uint32_t attr_options; + gchar *attr_pattern; + char *attr_value; + char *dest_node; + gchar *dest_uname; + gboolean inhibit; + gchar *set_name; + char *set_type; + gchar *type; + gboolean promotion_score; +} options = { + .command = 'G', + .promotion_score = FALSE +}; + +#define INDENT " " + +static gboolean +delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'D'; + pcmk__str_update(&options.attr_value, NULL); + return TRUE; +} + +static gboolean +promotion_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + char *score_name = NULL; + + options.promotion_score = TRUE; + + if (options.attr_name) { + g_free(options.attr_name); + } + + score_name = pcmk_promotion_score_name(optarg); + if (score_name != NULL) { + options.attr_name = g_strdup(score_name); + free(score_name); + } else { + options.attr_name = NULL; + } + + return TRUE; +} + +static gboolean +update_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'u'; + pcmk__str_update(&options.attr_value, optarg); + return TRUE; +} + +static gboolean +utilization_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (options.type) { + g_free(options.type); + } + + options.type = g_strdup(XML_CIB_TAG_NODES); + pcmk__str_update(&options.set_type, XML_TAG_UTILIZATION); + return TRUE; +} + +static gboolean +value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'G'; + pcmk__str_update(&options.attr_value, NULL); + return TRUE; +} + +static gboolean +wait_cb (const gchar *option_name, const gchar *optarg, gpointer data, GError **err) { + if (pcmk__str_eq(optarg, "no", pcmk__str_none)) { + pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); + return TRUE; + } else if (pcmk__str_eq(optarg, PCMK__VALUE_LOCAL, pcmk__str_none)) { + pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); + pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local); + return TRUE; + } else if (pcmk__str_eq(optarg, PCMK__VALUE_CLUSTER, pcmk__str_none)) { + pcmk__clear_node_attr_flags(options.attr_options, pcmk__node_attr_sync_local | pcmk__node_attr_sync_cluster); + pcmk__set_node_attr_flags(options.attr_options, pcmk__node_attr_sync_cluster); + return TRUE; + } else { + g_set_error(err, PCMK__EXITC_ERROR, CRM_EX_USAGE, + "--wait= must be one of 'no', 'local', 'cluster'"); + return FALSE; + } +} + +static GOptionEntry selecting_entries[] = { + { "id", 'i', 0, G_OPTION_ARG_STRING, &options.attr_id, + "(Advanced) Operate on instance of specified attribute with this\n" + INDENT "XML ID", + "XML_ID" + }, + + { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name, + "Operate on attribute or option with this name. For queries, this\n" + INDENT "is optional, in which case all matching attributes will be\n" + INDENT "returned.", + "NAME" + }, + + { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern, + "Operate on all attributes matching this pattern\n" + INDENT "(with -v, -D, or -G)", + "PATTERN" + }, + + { "promotion", 'p', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, promotion_cb, + "Operate on node attribute used as promotion score for specified\n" + INDENT "resource, or resource given in OCF_RESOURCE_INSTANCE environment\n" + INDENT "variable if none is specified; this also defaults -l/--lifetime\n" + INDENT "to reboot (normally invoked from an OCF resource agent)", + "RESOURCE" + }, + + { "set-name", 's', 0, G_OPTION_ARG_STRING, &options.set_name, + "(Advanced) Operate on instance of specified attribute that is\n" + INDENT "within set with this XML ID", + "NAME" + }, + + { NULL } +}; + +static GOptionEntry command_entries[] = { + { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb, + "Delete the attribute/option", + NULL + }, + + { "query", 'G', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb, + "Query the current value of the attribute/option.\n" + INDENT "See also: -n, -P", + NULL + }, + + { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb, + "Update the value of the attribute/option", + "VALUE" + }, + + { NULL } +}; + +static GOptionEntry addl_entries[] = { + { "default", 'd', 0, G_OPTION_ARG_STRING, &options.attr_default, + "(Advanced) Default value to display if none is found in configuration", + "VALUE" + }, + + { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.type, + "Lifetime of the node attribute.\n" + INDENT "Valid values: reboot, forever", + "LIFETIME" + }, + + { "node", 'N', 0, G_OPTION_ARG_STRING, &options.dest_uname, + "Set a node attribute for named node (instead of a cluster option).\n" + INDENT "See also: -l", + "NODE" + }, + + { "type", 't', 0, G_OPTION_ARG_STRING, &options.type, + "Which part of the configuration to update/delete/query the option in.\n" + INDENT "Valid values: crm_config, rsc_defaults, op_defaults, tickets", + "SECTION" + }, + + { "wait", 'W', 0, G_OPTION_ARG_CALLBACK, wait_cb, + "Wait for some event to occur before returning. Values are 'no' (wait\n" + INDENT "only for the attribute daemon to acknowledge the request),\n" + INDENT "'local' (wait until the change has propagated to where a local\n" + INDENT "query will return the request value, or the value set by a\n" + INDENT "later request), or 'cluster' (wait until the change has propagated\n" + INDENT "to where a query anywhere on the cluster will return the requested\n" + INDENT "value, or the value set by a later request). Default is 'no'.\n" + INDENT "(with -N, and one of -D or -u)", + "UNTIL" }, + + { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb, + "Set an utilization attribute for the node.", + NULL + }, + + { "inhibit-policy-engine", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.inhibit, + NULL, NULL + }, + + { NULL } +}; + +static GOptionEntry deprecated_entries[] = { + { "attr-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_id, + NULL, NULL + }, + + { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_name, + NULL, NULL + }, + + { "attr-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, update_cb, + NULL, NULL + }, + + { "delete-attr", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, delete_cb, + NULL, NULL + }, + + { "get-value", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb, + NULL, NULL + }, + + { "node-uname", 'U', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_uname, + NULL, NULL + }, + + { NULL } +}; + +static void +get_node_name_from_local(void) +{ + char *hostname = pcmk_hostname(); + + g_free(options.dest_uname); + + /* This silliness is so that dest_uname is always a glib-managed + * string so we know how to free it later. pcmk_hostname returns + * a newly allocated string via strdup. + */ + options.dest_uname = g_strdup(hostname); + free(hostname); +} + +static int +send_attrd_update(char command, const char *attr_node, const char *attr_name, + const char *attr_value, const char *attr_set, + const char *attr_dampen, uint32_t attr_options) +{ + int rc = pcmk_rc_ok; + uint32_t opts = attr_options; + + switch (command) { + case 'D': + rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, opts); + break; + + case 'u': + rc = pcmk__attrd_api_update(NULL, attr_node, attr_name, + attr_value, NULL, attr_set, NULL, + opts | pcmk__node_attr_value); + break; + } + + if (rc != pcmk_rc_ok) { + g_set_error(&error, PCMK__RC_ERROR, rc, "Could not update %s=%s: %s (%d)", + attr_name, attr_value, pcmk_rc_str(rc), rc); + } + + return rc; +} + +struct delete_data_s { + pcmk__output_t *out; + cib_t *cib; +}; + +static int +delete_attr_on_node(xmlNode *child, void *userdata) +{ + struct delete_data_s *dd = (struct delete_data_s *) userdata; + + const char *attr_name = crm_element_value(child, XML_NVPAIR_ATTR_NAME); + int rc = pcmk_rc_ok; + + if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) { + return pcmk_rc_ok; + } + + rc = cib__delete_node_attr(dd->out, dd->cib, cib_opts, options.type, + options.dest_node, options.set_type, + options.set_name, options.attr_id, + attr_name, options.attr_value, NULL); + + if (rc == ENXIO) { + rc = pcmk_rc_ok; + } + + return rc; +} + +static int +command_delete(pcmk__output_t *out, cib_t *cib) +{ + int rc = pcmk_rc_ok; + + xmlNode *result = NULL; + bool use_pattern = options.attr_pattern != NULL; + + /* See the comment in command_query regarding xpath and regular expressions. */ + if (use_pattern) { + struct delete_data_s dd = { out, cib }; + + rc = cib__get_node_attrs(out, cib, options.type, options.dest_node, + options.set_type, options.set_name, NULL, NULL, + NULL, &result); + + if (rc != pcmk_rc_ok) { + goto done_deleting; + } + + rc = pcmk__xe_foreach_child(result, NULL, delete_attr_on_node, &dd); + + if (rc != pcmk_rc_ok) { + goto done_deleting; + } + + } else { + rc = cib__delete_node_attr(out, cib, cib_opts, options.type, options.dest_node, + options.set_type, options.set_name, options.attr_id, + options.attr_name, options.attr_value, NULL); + } + +done_deleting: + free_xml(result); + + if (rc == ENXIO) { + /* Nothing to delete... + * which means it's not there... + * which is what the admin wanted + */ + rc = pcmk_rc_ok; + } + + return rc; +} + +struct update_data_s { + pcmk__output_t *out; + cib_t *cib; + int is_remote_node; +}; + +static int +update_attr_on_node(xmlNode *child, void *userdata) +{ + struct update_data_s *ud = (struct update_data_s *) userdata; + + const char *attr_name = crm_element_value(child, XML_NVPAIR_ATTR_NAME); + + if (!pcmk__str_eq(attr_name, options.attr_pattern, pcmk__str_regex)) { + return pcmk_rc_ok; + } + + return cib__update_node_attr(ud->out, ud->cib, cib_opts, options.type, + options.dest_node, options.set_type, + options.set_name, options.attr_id, + attr_name, options.attr_value, NULL, + ud->is_remote_node ? "remote" : NULL); +} + +static int +command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node) +{ + int rc = pcmk_rc_ok; + + xmlNode *result = NULL; + bool use_pattern = options.attr_pattern != NULL; + + CRM_LOG_ASSERT(options.type != NULL); + CRM_LOG_ASSERT(options.attr_name != NULL); + CRM_LOG_ASSERT(options.attr_value != NULL); + + /* See the comment in command_query regarding xpath and regular expressions. */ + if (use_pattern) { + struct update_data_s ud = { out, cib, is_remote_node }; + + rc = cib__get_node_attrs(out, cib, options.type, options.dest_node, + options.set_type, options.set_name, NULL, NULL, + NULL, &result); + + if (rc != pcmk_rc_ok) { + goto done_updating; + } + + rc = pcmk__xe_foreach_child(result, NULL, update_attr_on_node, &ud); + + if (rc != pcmk_rc_ok) { + goto done_updating; + } + + } else { + rc = cib__update_node_attr(out, cib, cib_opts, options.type, + options.dest_node, options.set_type, + options.set_name, options.attr_id, + options.attr_name, options.attr_value, + NULL, is_remote_node ? "remote" : NULL); + } + +done_updating: + free_xml(result); + return rc; +} + +struct output_data_s { + pcmk__output_t *out; + bool use_pattern; + bool did_output; +}; + +static int +output_one_attribute(xmlNode *node, void *userdata) +{ + struct output_data_s *od = (struct output_data_s *) userdata; + + const char *name = crm_element_value(node, XML_NVPAIR_ATTR_NAME); + const char *value = crm_element_value(node, XML_NVPAIR_ATTR_VALUE); + const char *host = crm_element_value(node, PCMK__XA_ATTR_NODE_NAME); + + const char *type = options.type; + const char *attr_id = options.attr_id; + + if (od->use_pattern && !pcmk__str_eq(name, options.attr_pattern, pcmk__str_regex)) { + return pcmk_rc_ok; + } + + od->out->message(od->out, "attribute", type, attr_id, name, value, host); + od->did_output = true; + crm_info("Read %s='%s' %s%s", + pcmk__s(name, "<null>"), pcmk__s(value, ""), + options.set_name ? "in " : "", options.set_name ? options.set_name : ""); + + return pcmk_rc_ok; +} + +static int +command_query(pcmk__output_t *out, cib_t *cib) +{ + int rc = pcmk_rc_ok; + + xmlNode *result = NULL; + bool use_pattern = options.attr_pattern != NULL; + + /* libxml2 doesn't support regular expressions in xpath queries (which is how + * cib__get_node_attrs -> find_attr finds attributes). So instead, we'll just + * find all the attributes for a given node here by passing NULL for attr_id + * and attr_name, and then later see if they match the given pattern. + */ + if (use_pattern) { + rc = cib__get_node_attrs(out, cib, options.type, options.dest_node, + options.set_type, options.set_name, NULL, + NULL, NULL, &result); + } else { + rc = cib__get_node_attrs(out, cib, options.type, options.dest_node, + options.set_type, options.set_name, options.attr_id, + options.attr_name, NULL, &result); + } + + if (rc == ENXIO && options.attr_default) { + /* Make static analysis happy */ + const char *type = options.type; + const char *attr_id = options.attr_id; + const char *attr_name = options.attr_name; + const char *attr_default = options.attr_default; + const char *dest_uname = options.dest_uname; + + out->message(out, "attribute", type, attr_id, attr_name, attr_default, + dest_uname); + rc = pcmk_rc_ok; + + } else if (rc != pcmk_rc_ok) { + // Don't do anything. + + } else if (xml_has_children(result)) { + struct output_data_s od = { out, use_pattern, false }; + + pcmk__xe_foreach_child(result, NULL, output_one_attribute, &od); + + if (!od.did_output) { + rc = ENXIO; + } + + } else { + struct output_data_s od = { out, use_pattern, false }; + output_one_attribute(result, &od); + } + + free_xml(result); + return rc; +} + +static void +set_type(void) +{ + if (options.type == NULL) { + if (options.promotion_score) { + // Updating a promotion score node attribute + options.type = g_strdup(XML_CIB_TAG_STATUS); + + } else if (options.dest_uname != NULL) { + // Updating some other node attribute + options.type = g_strdup(XML_CIB_TAG_NODES); + + } else { + // Updating cluster options + options.type = g_strdup(XML_CIB_TAG_CRMCONFIG); + } + + } else if (pcmk__str_eq(options.type, "reboot", pcmk__str_casei)) { + options.type = g_strdup(XML_CIB_TAG_STATUS); + + } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) { + options.type = g_strdup(XML_CIB_TAG_NODES); + } +} + +static bool +use_attrd(void) +{ + /* Only go through the attribute manager for transient attributes, and + * then only if we're not using a file as the CIB. + */ + return pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei) && + getenv("CIB_file") == NULL && getenv("CIB_shadow") == NULL; +} + +static bool +try_ipc_update(void) +{ + return use_attrd() && (options.command == 'D' || options.command == 'u'); +} + +static bool +pattern_used_correctly(void) +{ + /* --pattern can only be used with: + * -G (query), -v (update), or -D (delete) + */ + return options.command == 'G' || options.command == 'u' || options.command == 'D'; +} + +static bool +delete_used_correctly(void) +{ + return options.command != 'D' || options.attr_name != NULL || options.attr_pattern != NULL; +} + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + GOptionContext *context = NULL; + + GOptionEntry extra_prog_entries[] = { + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet), + "Print only the value on stdout", + NULL }, + + { "quiet", 'Q', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &(args->quiet), + NULL, NULL + }, + + { NULL } + }; + + const char *description = "Examples:\n\n" + "Add new node attribute called 'location' with the value of 'office' for host 'myhost':\n\n" + "\tcrm_attribute --node myhost --name location --update office\n\n" + "Query the value of the 'location' node attribute for host 'myhost':\n\n" + "\tcrm_attribute --node myhost --name location --query\n\n" + "Change the value of the 'location' node attribute for host 'myhost':\n\n" + "\tcrm_attribute --node myhost --name location --update backoffice\n\n" + "Delete the 'location' node attribute for host 'myhost':\n\n" + "\tcrm_attribute --node myhost --name location --delete\n\n" + "Query the value of the 'cluster-delay' cluster option:\n\n" + "\tcrm_attribute --type crm_config --name cluster-delay --query\n\n" + "Query value of the 'cluster-delay' cluster option and print only the value:\n\n" + "\tcrm_attribute --type crm_config --name cluster-delay --query --quiet\n\n"; + + context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); + pcmk__add_main_args(context, extra_prog_entries); + g_option_context_set_description(context, description); + + pcmk__add_arg_group(context, "selections", "Selecting attributes:", + "Show selecting options", selecting_entries); + pcmk__add_arg_group(context, "command", "Commands:", + "Show command options", command_entries); + pcmk__add_arg_group(context, "additional", "Additional options:", + "Show additional options", addl_entries); + pcmk__add_arg_group(context, "deprecated", "Deprecated Options:", + "Show deprecated options", deprecated_entries); + + return context; +} + +int +main(int argc, char **argv) +{ + cib_t *the_cib = NULL; + int is_remote_node = 0; + + int rc = pcmk_rc_ok; + + pcmk__output_t *out = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, "NPUdilnpstv"); + GOptionContext *context = build_arg_context(args, &output_group); + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; + } + + pcmk__cli_init_logging("crm_attribute", args->verbosity); + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", + args->output_ty, pcmk_rc_str(rc)); + goto done; + } + + pcmk__register_lib_messages(out); + pcmk__register_messages(out, fmt_functions); + + if (args->version) { + out->version(out, false); + goto done; + } + + out->quiet = args->quiet; + + if (options.promotion_score && options.attr_name == NULL) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "-p/--promotion must be called from an OCF resource agent " + "or with a resource ID specified"); + goto done; + } + + if (options.inhibit) { + crm_warn("Inhibiting notifications for this update"); + cib__set_call_options(cib_opts, crm_system_name, cib_inhibit_notify); + } + + the_cib = cib_new(); + rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); + rc = pcmk_legacy2rc(rc); + + if (rc != pcmk_rc_ok) { + exit_code = pcmk_rc2exitc(rc); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Could not connect to the CIB: %s", pcmk_rc_str(rc)); + goto done; + } + + set_type(); + + // Use default node if not given (except for cluster options and tickets) + if (!pcmk__strcase_any_of(options.type, XML_CIB_TAG_CRMCONFIG, XML_CIB_TAG_TICKETS, + NULL)) { + /* If we are being called from a resource agent via the cluster, + * the correct local node name will be passed as an environment + * variable. Otherwise, we have to ask the cluster. + */ + const char *target = pcmk__node_attr_target(options.dest_uname); + + if (target != NULL) { + g_free(options.dest_uname); + options.dest_uname = g_strdup(target); + } else if (getenv("CIB_file") != NULL && options.dest_uname == NULL) { + get_node_name_from_local(); + } + + if (options.dest_uname == NULL) { + char *node_name = NULL; + + rc = pcmk__query_node_name(out, 0, &node_name, 0); + + if (rc != pcmk_rc_ok) { + exit_code = pcmk_rc2exitc(rc); + free(node_name); + goto done; + } + options.dest_uname = g_strdup(node_name); + free(node_name); + } + + rc = query_node_uuid(the_cib, options.dest_uname, &options.dest_node, &is_remote_node); + rc = pcmk_legacy2rc(rc); + + if (rc != pcmk_rc_ok) { + exit_code = pcmk_rc2exitc(rc); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Could not map name=%s to a UUID", options.dest_uname); + goto done; + } + } + + if (!delete_used_correctly()) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error: must specify attribute name or pattern to delete"); + goto done; + } + + if (options.attr_pattern) { + if (options.attr_name) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error: --name and --pattern cannot be used at the same time"); + goto done; + } + + if (!pattern_used_correctly()) { + exit_code = CRM_EX_USAGE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error: pattern can only be used with delete, query, or update"); + goto done; + } + + g_free(options.attr_name); + options.attr_name = options.attr_pattern; + options.attr_options |= pcmk__node_attr_pattern; + } + + if (is_remote_node) { + options.attr_options |= pcmk__node_attr_remote; + } + + if (pcmk__str_eq(options.set_type, XML_TAG_UTILIZATION, pcmk__str_none)) { + options.attr_options |= pcmk__node_attr_utilization; + } + + if (try_ipc_update() && + (send_attrd_update(options.command, options.dest_uname, options.attr_name, + options.attr_value, options.set_name, NULL, options.attr_options) == pcmk_rc_ok)) { + crm_info("Update %s=%s sent via pacemaker-attrd", + options.attr_name, ((options.command == 'D')? "<none>" : options.attr_value)); + + } else if (options.command == 'D') { + rc = command_delete(out, the_cib); + + } else if (options.command == 'u') { + rc = command_update(out, the_cib, is_remote_node); + + } else { + rc = command_query(out, the_cib); + } + + if (rc == ENOTUNIQ) { + exit_code = pcmk_rc2exitc(rc); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Please choose from one of the matches below and supply the 'id' with --attr-id"); + + } else if (rc != pcmk_rc_ok) { + exit_code = pcmk_rc2exitc(rc); + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Error performing operation: %s", pcmk_strerror(rc)); + } + +done: + g_strfreev(processed_args); + pcmk__free_arg_context(context); + + free(options.attr_default); + g_free(options.attr_id); + g_free(options.attr_name); + free(options.attr_value); + free(options.dest_node); + g_free(options.dest_uname); + g_free(options.set_name); + free(options.set_type); + g_free(options.type); + + cib__clean_up_connection(&the_cib); + + pcmk__output_and_clear_error(&error, out); + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } + + pcmk__unregister_formats(); + return crm_exit(exit_code); +} |