diff --git a/lib/common/attrd_client.c b/lib/common/attrd_client.c index 6439ee6920..ebb184697d 100644 --- a/lib/common/attrd_client.c +++ b/lib/common/attrd_client.c @@ -1,334 +1,329 @@ /* * Copyright 2011-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include /*! * \internal * \brief Create a generic pacemaker-attrd operation * * \param[in] user_name If not NULL, ACL user to set for operation * * \return XML of pacemaker-attrd operation */ static xmlNode * create_attrd_op(const char *user_name) { xmlNode *attrd_op = create_xml_node(NULL, __func__); crm_xml_add(attrd_op, F_TYPE, T_ATTRD); crm_xml_add(attrd_op, F_ORIG, (crm_system_name? crm_system_name: "unknown")); crm_xml_add(attrd_op, PCMK__XA_ATTR_USER, user_name); return attrd_op; } /*! * \internal * \brief Send an operation to pacemaker-attrd via IPC * * \param[in] ipc Connection to pacemaker-attrd (or create one if NULL) * \param[in] attrd_op XML of pacemaker-attrd operation to send * * \return Standard Pacemaker return code */ static int send_attrd_op(crm_ipc_t *ipc, xmlNode *attrd_op) { int rc = -ENOTCONN; // initially handled as legacy return code int max = 5; static gboolean connected = TRUE; static crm_ipc_t *local_ipc = NULL; static enum crm_ipc_flags flags = crm_ipc_flags_none; if (ipc == NULL && local_ipc == NULL) { local_ipc = crm_ipc_new(T_ATTRD, 0); pcmk__set_ipc_flags(flags, "client", crm_ipc_client_response); connected = FALSE; } if (ipc == NULL) { ipc = local_ipc; } while (max > 0) { if (connected == FALSE) { crm_info("Connecting to cluster... %d retries remaining", max); connected = crm_ipc_connect(ipc); } if (connected) { rc = crm_ipc_send(ipc, attrd_op, flags, 0, NULL); } else { crm_perror(LOG_INFO, "Connection to cluster attribute manager failed"); } if (ipc != local_ipc) { break; } else if (rc > 0) { break; } else if (rc == -EAGAIN || rc == -EALREADY) { sleep(5 - max); max--; } else { crm_ipc_close(ipc); connected = FALSE; sleep(5 - max); max--; } } if (rc > 0) { rc = pcmk_ok; } return pcmk_legacy2rc(rc); } /*! * \internal * \brief Send a request to pacemaker-attrd * * \param[in] ipc Connection to pacemaker-attrd (or NULL to use a local connection) * \param[in] command A character indicating the type of pacemaker-attrd request: * U or v: update attribute (or refresh if name is NULL) * u: update attributes matching regular expression in name * D: delete attribute (value must be NULL) * R: refresh * B: update both attribute and its dampening * Y: update attribute dampening only * Q: query attribute * C: remove peer specified by host * \param[in] host Affect only this host (or NULL for all hosts) * \param[in] name Name of attribute to affect * \param[in] value Attribute value to set * \param[in] section Status or nodes * \param[in] set ID of attribute set to use (or NULL to choose first) * \param[in] dampen Attribute dampening to use with B/Y, and U/v if creating * \param[in] user_name ACL user to pass to pacemaker-attrd * \param[in] options Bitmask of pcmk__node_attr_opts * * \return Standard Pacemaker return code */ int pcmk__node_attr_request(crm_ipc_t *ipc, char command, const char *host, const char *name, const char *value, const char *section, const char *set, const char *dampen, const char *user_name, int options) { int rc = pcmk_rc_ok; const char *task = NULL; const char *name_as = NULL; const char *display_host = (host ? host : "localhost"); const char *display_command = NULL; /* for commands without name/value */ xmlNode *update = create_attrd_op(user_name); /* remap common aliases */ if (pcmk__str_eq(section, "reboot", pcmk__str_casei)) { section = XML_CIB_TAG_STATUS; } else if (pcmk__str_eq(section, "forever", pcmk__str_casei)) { section = XML_CIB_TAG_NODES; } if (name == NULL && command == 'U') { command = 'R'; } switch (command) { case 'u': task = PCMK__ATTRD_CMD_UPDATE; name_as = PCMK__XA_ATTR_PATTERN; break; case 'D': case 'U': case 'v': task = PCMK__ATTRD_CMD_UPDATE; name_as = PCMK__XA_ATTR_NAME; break; case 'R': task = PCMK__ATTRD_CMD_REFRESH; display_command = "refresh"; break; case 'B': task = PCMK__ATTRD_CMD_UPDATE_BOTH; name_as = PCMK__XA_ATTR_NAME; break; case 'Y': task = PCMK__ATTRD_CMD_UPDATE_DELAY; name_as = PCMK__XA_ATTR_NAME; break; case 'Q': task = PCMK__ATTRD_CMD_QUERY; name_as = PCMK__XA_ATTR_NAME; break; case 'C': task = PCMK__ATTRD_CMD_PEER_REMOVE; display_command = "purge"; break; } if (name_as != NULL) { if (name == NULL) { rc = EINVAL; goto done; } crm_xml_add(update, name_as, name); } crm_xml_add(update, PCMK__XA_TASK, task); crm_xml_add(update, PCMK__XA_ATTR_VALUE, value); crm_xml_add(update, PCMK__XA_ATTR_DAMPENING, dampen); crm_xml_add(update, PCMK__XA_ATTR_SECTION, section); crm_xml_add(update, PCMK__XA_ATTR_NODE_NAME, host); crm_xml_add(update, PCMK__XA_ATTR_SET, set); crm_xml_add_int(update, PCMK__XA_ATTR_IS_REMOTE, pcmk_is_set(options, pcmk__node_attr_remote)); crm_xml_add_int(update, PCMK__XA_ATTR_IS_PRIVATE, pcmk_is_set(options, pcmk__node_attr_private)); rc = send_attrd_op(ipc, update); done: free_xml(update); if (display_command) { crm_debug("Asked pacemaker-attrd to %s %s: %s (%d)", display_command, display_host, pcmk_rc_str(rc), rc); } else { crm_debug("Asked pacemaker-attrd to update %s=%s for %s: %s (%d)", name, value, display_host, pcmk_rc_str(rc), rc); } return rc; } /*! * \internal * \brief Send a request to pacemaker-attrd to clear resource failure * * \param[in] ipc Connection to pacemaker-attrd (NULL to use local connection) * \param[in] host Affect only this host (or NULL for all hosts) * \param[in] resource Name of resource to clear (or NULL for all) * \param[in] operation Name of operation to clear (or NULL for all) * \param[in] interval_spec If operation is not NULL, its interval * \param[in] user_name ACL user to pass to pacemaker-attrd * \param[in] options Bitmask of pcmk__node_attr_opts * * \return pcmk_ok if request was successfully submitted to pacemaker-attrd, else -errno */ int pcmk__node_attr_request_clear(crm_ipc_t *ipc, const char *host, const char *resource, const char *operation, const char *interval_spec, const char *user_name, int options) { int rc = pcmk_rc_ok; xmlNode *clear_op = create_attrd_op(user_name); const char *interval_desc = NULL; const char *op_desc = NULL; crm_xml_add(clear_op, PCMK__XA_TASK, PCMK__ATTRD_CMD_CLEAR_FAILURE); crm_xml_add(clear_op, PCMK__XA_ATTR_NODE_NAME, host); crm_xml_add(clear_op, PCMK__XA_ATTR_RESOURCE, resource); crm_xml_add(clear_op, PCMK__XA_ATTR_OPERATION, operation); crm_xml_add(clear_op, PCMK__XA_ATTR_INTERVAL, interval_spec); crm_xml_add_int(clear_op, PCMK__XA_ATTR_IS_REMOTE, pcmk_is_set(options, pcmk__node_attr_remote)); rc = send_attrd_op(ipc, clear_op); free_xml(clear_op); if (operation) { interval_desc = interval_spec? interval_spec : "nonrecurring"; op_desc = operation; } else { interval_desc = "all"; op_desc = "operations"; } crm_debug("Asked pacemaker-attrd to clear failure of %s %s for %s on %s: %s (%d)", interval_desc, op_desc, (resource? resource : "all resources"), (host? host : "all nodes"), pcmk_rc_str(rc), rc); return rc; } #define LRM_TARGET_ENV "OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET /*! * \internal */ const char * pcmk__node_attr_target(const char *name) { - if (pcmk__strcase_any_of(name, "auto", "localhost", NULL)) { - name = NULL; - } - - if(name != NULL) { - return name; - - } else { + if (name == NULL || pcmk__strcase_any_of(name, "auto", "localhost", NULL)) { char *target_var = crm_meta_name(XML_RSC_ATTR_TARGET); char *phys_var = crm_meta_name(PCMK__ENV_PHYSICAL_HOST); const char *target = getenv(target_var); const char *host_physical = getenv(phys_var); // It is important to use the name by which the scheduler knows us if (host_physical && pcmk__str_eq(target, "host", pcmk__str_casei)) { name = host_physical; } else { const char *host_pcmk = getenv(LRM_TARGET_ENV); if (host_pcmk) { name = host_pcmk; } } free(target_var); free(phys_var); - } - // TODO? Call get_local_node_name() if name == NULL - // (currently would require linkage against libcrmcluster) - return name; + // TODO? Call get_local_node_name() if name == NULL + // (currently would require linkage against libcrmcluster) + return name; + } else { + return NULL; + } } /*! * \brief Return the name of the node attribute used as a promotion score * * \param[in] rsc_id Resource ID that promotion score is for (or NULL to * check the OCF_RESOURCE_INSTANCE environment variable) * * \return Newly allocated string with the node attribute name (or NULL on * error, including no ID or environment variable specified) * \note It is the caller's responsibility to free() the result. */ char * pcmk_promotion_score_name(const char *rsc_id) { if (rsc_id == NULL) { rsc_id = getenv("OCF_RESOURCE_INSTANCE"); if (rsc_id == NULL) { return NULL; } } return crm_strdup_printf("master-%s", rsc_id); } diff --git a/mk/common.mk b/mk/common.mk index 464e43d700..b2476700ee 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -1,99 +1,100 @@ # # Copyright 2014-2020 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. # # # Some variables to help with silent rules # https://www.gnu.org/software/automake/manual/html_node/Automake-silent_002drules-Option.html V ?= $(AM_DEFAULT_VERBOSITY) # When a make command is prefixed with one of the AM_V_* macros, it may also be # desirable to suffix the command with this, to silence stdout. PCMK_quiet = $(pcmk_quiet_$(V)) pcmk_quiet_0 = >/dev/null pcmk_quiet_1 = # AM_V_GEN is intended to be used in custom pattern rules, and replaces echoing # the command used with a more concise line with "GEN" and the name of the file # being generated. Our AM_V_* macros are similar but more descriptive. AM_V_MAN = $(am__v_MAN_$(V)) am__v_MAN_0 = @echo " MAN $@"; am__v_MAN_1 = AM_V_SCHEMA = $(am__v_SCHEMA_$(V)) am__v_SCHEMA_0 = @echo " SCHEMA $@"; am__v_SCHEMA_1 = AM_V_BOOK = $(am__v_BOOK_$(V)) am__v_BOOK_0 = @echo " BOOK $(@:%/_build=%): $(BOOK_FORMATS)"; am__v_BOOK_1 = MAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl # # Man page builders # # We have three types of man pages: # - man pages for the tools # - man pages for OCF agents # - man pages for cluster properties used by daemons # # "BUILD_HELP" actually means "help2man is available", so it only controls the # tool man pages, which are generated by help2man. The other man pages are # generated via XSL transforms. # if BUILD_HELP man8_MANS = $(sbin_PROGRAMS:%=%.8) $(sbin_SCRIPTS:%=%.8) HELP2MAN_ARGS = -N --section 8 --name "Part of the Pacemaker cluster resource manager" # Some of our tools' help are just shell script invocations of another tool's # help. Putting the real tool in MAN8DEPS helps detect when the wrapped help # needs updating. # # If a ".inc" file exists, the tool has been converted to use glib for # argument parsing, otherwise it still uses the libcrmcommon functions. # # @TODO Drop MAN8DEPS once we've converted all tools to libpacemaker API calls # and all wrappers to C code. %.8: % $(MAN8DEPS) $(AM_V_at)chmod a+x $(abs_builddir)/$< $(AM_V_MAN)if [ -f $(top_srcdir)/tools/$@.inc ]; then \ PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) \ -h --help-all \ --no-discard-stderr \ -i $(top_srcdir)/tools/$@.inc $(abs_builddir)/$< \ | sed -f $(top_srcdir)/tools/fix-manpages > $@ ; \ else \ PATH=$(abs_builddir):$$PATH $(HELP2MAN) $(HELP2MAN_ARGS) \ + --no-discard-stderr \ $(abs_builddir)/$< --output $@ ; \ fi endif # Save raw XML meta-data from daemon executables, for later conversion into man # pages. (Note that more specific rules may override this for creating other # types of XML files.) %.xml: % $(AM_V_at)$(abs_builddir)/$< metadata > $@ # Process the raw daemon and OCF agent meta-data output using our # meta-data-to-docbook-XML tranform. %.dbook: %.xml $(AM_V_at)$(XSLTPROC) --nonet --novalid --stringparam man.name $* \ $(DBOOK_OPTS) $(top_srcdir)/xml/ocf-meta2man.xsl \ $(abs_builddir)/$< > $(abs_builddir)/$@ # Generate the actual man page for an OCF resource agent from the intermediate # docbook XML. %.7: %.dbook $(AM_V_MAN)$(XSLTPROC) $(MANPAGE_XSLT) $(abs_builddir)/$< $(PCMK_quiet) diff --git a/tools/Makefile.am b/tools/Makefile.am index 8ce42d7d89..2c6de7f125 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,170 +1,171 @@ # # Copyright 2004-2021 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)/mk/common.mk 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_diff.8.inc \ +EXTRA_DIST = crm_attribute.8.inc \ + crm_diff.8.inc \ crm_error.8.inc \ crm_mon.8.inc \ crm_node.8.inc \ crm_resource.8.inc \ crm_rule.8.inc \ crm_simulate.8.inc \ crm_verify.8.inc \ crmadmin.8.inc \ fix-manpages \ 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 mk/common.mk). 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 \ $(top_builddir)/lib/pacemaker/libpacemaker.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_print.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_print.c \ crm_resource_runtime.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/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/common/libcrmcommon.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/attrd_updater.c b/tools/attrd_updater.c index a461ba9550..2a6c471aee 100644 --- a/tools/attrd_updater.c +++ b/tools/attrd_updater.c @@ -1,471 +1,476 @@ /* * Copyright 2004-2020 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 #include #include #include #include #include #include #include #include #include #include #include static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', "\tThis text", pcmk__option_default }, { "version", no_argument, NULL, '$', "\tVersion information", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\tIncrease debug output\n", pcmk__option_default }, { "name", required_argument, NULL, 'n', "The attribute's name", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nCommands:", pcmk__option_default }, { "update", required_argument, NULL, 'U', "Update attribute's value in pacemaker-attrd. If this causes the value " "to change, it will also be updated in the cluster configuration.", pcmk__option_default }, { "update-both", required_argument, NULL, 'B', "Update attribute's value and time to wait (dampening) in " "pacemaker-attrd. If this causes the value or dampening to change, " "the attribute will also be written to the cluster configuration, " "so be aware that repeatedly changing the dampening reduces its " "effectiveness.", pcmk__option_default }, { "update-delay", no_argument, NULL, 'Y', "Update attribute's dampening in pacemaker-attrd (requires " "-d/--delay). If this causes the dampening to change, the " "attribute will also be written to the cluster configuration, so " "be aware that repeatedly changing the dampening reduces its " "effectiveness.", pcmk__option_default }, { "query", no_argument, NULL, 'Q', "\tQuery the attribute's value from pacemaker-attrd", pcmk__option_default }, { "delete", no_argument, NULL, 'D', "\tDelete attribute from pacemaker-attrd. If a value was previously " "set, it will also be removed from the cluster configuration", pcmk__option_default }, { "refresh", no_argument, NULL, 'R', "\t(Advanced) Force the pacemaker-attrd daemon to resend all current " "values to the CIB", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nAdditional options:", pcmk__option_default }, { "delay", required_argument, NULL, 'd', "The time to wait (dampening) in seconds for further changes " "before writing", pcmk__option_default }, { "set", required_argument, NULL, 's', "(Advanced) The attribute set in which to place the value", pcmk__option_default }, { "node", required_argument, NULL, 'N', "Set the attribute for the named node (instead of the local one)", pcmk__option_default }, { "all", no_argument, NULL, 'A', "Show values of the attribute for all nodes (query only)", pcmk__option_default }, // @TODO Implement --lifetime { "lifetime", required_argument, NULL, 'l', "(Not yet implemented) Lifetime of the node attribute (silently " "ignored by cluster)", pcmk__option_default }, { "private", no_argument, NULL, 'p', "\tIf this creates a new attribute, never write the attribute to CIB", pcmk__option_default }, /* Legacy options */ { "quiet", no_argument, NULL, 'q', NULL, pcmk__option_hidden }, { "update", required_argument, NULL, 'v', NULL, pcmk__option_hidden }, { "section", required_argument, NULL, 'S', NULL, pcmk__option_hidden }, { 0, 0, 0, 0 } }; static int do_query(const char *attr_name, const char *attr_node, gboolean query_all); static int do_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_section, const char *attr_set, const char *attr_dampen, int attr_options); // Free memory at exit to make analyzers happy #define cleanup_memory() \ free(attr_dampen); \ free(attr_name); \ free(attr_node); \ free(attr_section); \ free(attr_set); #define set_option(option_var) \ if (option_var) { \ free(option_var); \ } \ option_var = strdup(optarg); int main(int argc, char **argv) { int index = 0; int argerr = 0; int attr_options = pcmk__node_attr_none; int flag; crm_exit_t exit_code = CRM_EX_OK; char *attr_node = NULL; char *attr_name = NULL; char *attr_set = NULL; char *attr_section = NULL; char *attr_dampen = NULL; const char *attr_value = NULL; char command = 'Q'; gboolean query_all = FALSE; pcmk__cli_init_logging("attrd_updater", 0); pcmk__set_cli_options(NULL, "-n [options]", long_options, "query and update Pacemaker node attributes"); if (argc < 2) { pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case '?': case '$': cleanup_memory(); pcmk__cli_help(flag, CRM_EX_OK); break; case 'n': set_option(attr_name); break; case 's': set_option(attr_set); break; case 'd': set_option(attr_dampen); break; case 'l': case 'S': set_option(attr_section); break; case 'N': set_option(attr_node); break; case 'A': query_all = TRUE; break; case 'p': pcmk__set_node_attr_flags(attr_options, pcmk__node_attr_private); break; case 'q': break; case 'Y': command = flag; crm_log_args(argc, argv); /* Too much? */ break; case 'Q': case 'B': case 'R': case 'D': case 'U': case 'v': command = flag; attr_value = optarg; crm_log_args(argc, argv); /* Too much? */ break; default: ++argerr; break; } } if (optind > argc) { ++argerr; } if (command != 'R' && attr_name == NULL) { ++argerr; } if (argerr) { cleanup_memory(); pcmk__cli_help('?', CRM_EX_USAGE); } if (command == 'Q') { exit_code = crm_errno2exit(do_query(attr_name, attr_node, query_all)); } else { /* @TODO We don't know whether the specified node is a Pacemaker Remote * node or not, so we can't set pcmk__node_attr_remote when appropriate. * However, it's not a big problem, because pacemaker-attrd will learn * and remember a node's "remoteness". */ + const char *target = pcmk__node_attr_target(attr_node); + exit_code = pcmk_rc2exitc(do_update(command, - pcmk__node_attr_target(attr_node), + target == NULL ? attr_node : target, attr_name, attr_value, attr_section, attr_set, attr_dampen, attr_options)); } cleanup_memory(); crm_exit(exit_code); } /*! * \internal * \brief Submit a query request to pacemaker-attrd and wait for reply * * \param[in] name Name of attribute to query * \param[in] host Query applies to this host only (or all hosts if NULL) * \param[out] reply On success, will be set to new XML tree with reply * * \return pcmk_ok on success, -errno on error * \note On success, caller is responsible for freeing result via free_xml(*reply) */ static int send_attrd_query(const char *name, const char *host, xmlNode **reply) { int rc; crm_ipc_t *ipc; xmlNode *query; /* Build the query XML */ query = create_xml_node(NULL, __func__); if (query == NULL) { return -ENOMEM; } crm_xml_add(query, F_TYPE, T_ATTRD); crm_xml_add(query, F_ORIG, crm_system_name); crm_xml_add(query, PCMK__XA_ATTR_NODE_NAME, host); crm_xml_add(query, PCMK__XA_TASK, PCMK__ATTRD_CMD_QUERY); crm_xml_add(query, PCMK__XA_ATTR_NAME, name); /* Connect to pacemaker-attrd, send query XML and get reply */ crm_debug("Sending query for value of %s on %s", name, (host? host : "all nodes")); ipc = crm_ipc_new(T_ATTRD, 0); if (crm_ipc_connect(ipc) == FALSE) { crm_perror(LOG_ERR, "Connection to cluster attribute manager failed"); rc = -ENOTCONN; } else { rc = crm_ipc_send(ipc, query, crm_ipc_client_response, 0, reply); if (rc > 0) { rc = pcmk_ok; } crm_ipc_close(ipc); } crm_ipc_destroy(ipc); free_xml(query); return(rc); } /*! * \brief Validate pacemaker-attrd's XML reply to an query * * param[in] reply Root of reply XML tree to validate * param[in] attr_name Name of attribute that was queried * * \return pcmk_ok on success, * -errno on error (-ENXIO = requested attribute does not exist) */ static int validate_attrd_reply(xmlNode *reply, const char *attr_name) { const char *reply_attr; if (reply == NULL) { fprintf(stderr, "Could not query value of %s: reply did not contain valid XML\n", attr_name); return -pcmk_err_schema_validation; } crm_log_xml_trace(reply, "Reply"); reply_attr = crm_element_value(reply, PCMK__XA_ATTR_NAME); if (reply_attr == NULL) { fprintf(stderr, "Could not query value of %s: attribute does not exist\n", attr_name); return -ENXIO; } if (!pcmk__str_eq(crm_element_value(reply, F_TYPE), T_ATTRD, pcmk__str_casei) || (crm_element_value(reply, PCMK__XA_ATTR_VERSION) == NULL) || strcmp(reply_attr, attr_name)) { fprintf(stderr, "Could not query value of %s: reply did not contain expected identification\n", attr_name); return -pcmk_err_schema_validation; } return pcmk_ok; } /*! * \brief Print the attribute values in a pacemaker-attrd XML query reply * * \param[in] reply Root of XML tree with query reply * \param[in] attr_name Name of attribute that was queried * * \return TRUE if any values were printed */ static gboolean print_attrd_values(xmlNode *reply, const char *attr_name) { xmlNode *child; const char *reply_host, *reply_value; gboolean have_values = FALSE; /* Iterate through reply's XML tags (a node tag for each host-value pair) */ for (child = pcmk__xml_first_child(reply); child != NULL; child = pcmk__xml_next(child)) { if (!pcmk__str_eq((const char *)child->name, XML_CIB_TAG_NODE, pcmk__str_casei)) { crm_warn("Ignoring unexpected %s tag in query reply", child->name); } else { reply_host = crm_element_value(child, PCMK__XA_ATTR_NODE_NAME); reply_value = crm_element_value(child, PCMK__XA_ATTR_VALUE); if (reply_host == NULL) { crm_warn("Ignoring %s tag without %s attribute in query reply", XML_CIB_TAG_NODE, PCMK__XA_ATTR_NODE_NAME); } else { printf("name=\"%s\" host=\"%s\" value=\"%s\"\n", attr_name, reply_host, (reply_value? reply_value : "")); have_values = TRUE; } } } return have_values; } /*! * \brief Submit a query to pacemaker-attrd and print reply * * \param[in] attr_name Name of attribute to be affected by request * \param[in] attr_node Name of host to query for (or NULL for localhost) * \param[in] query_all If TRUE, ignore attr_node and query all nodes instead * * \return pcmk_ok on success, -errno on error */ static int do_query(const char *attr_name, const char *attr_node, gboolean query_all) { xmlNode *reply = NULL; int rc; /* Decide which node(s) to query */ if (query_all == TRUE) { attr_node = NULL; } else { - attr_node = pcmk__node_attr_target(attr_node); + const char *target = pcmk__node_attr_target(attr_node); + if (target != NULL) { + attr_node = target; + } } /* Build and send pacemaker-attrd request, and get XML reply */ rc = send_attrd_query(attr_name, attr_node, &reply); if (rc != pcmk_ok) { fprintf(stderr, "Could not query value of %s: %s (%d)\n", attr_name, pcmk_strerror(rc), rc); return rc; } /* Validate the XML reply */ rc = validate_attrd_reply(reply, attr_name); if (rc != pcmk_ok) { if (reply != NULL) { free_xml(reply); } return rc; } /* Print the values from the reply */ if (print_attrd_values(reply, attr_name) == FALSE) { fprintf(stderr, "Could not query value of %s: reply had attribute name but no host values\n", attr_name); free_xml(reply); return -pcmk_err_schema_validation; } return pcmk_ok; } static int do_update(char command, const char *attr_node, const char *attr_name, const char *attr_value, const char *attr_section, const char *attr_set, const char *attr_dampen, int attr_options) { int rc = pcmk__node_attr_request(NULL, command, attr_node, attr_name, attr_value, attr_section, attr_set, attr_dampen, NULL, attr_options); if (rc != pcmk_rc_ok) { fprintf(stderr, "Could not update %s=%s: %s (%d)\n", attr_name, attr_value, pcmk_rc_str(rc), rc); } return rc; } diff --git a/tools/crm_attribute.8.inc b/tools/crm_attribute.8.inc new file mode 100644 index 0000000000..2be825315c --- /dev/null +++ b/tools/crm_attribute.8.inc @@ -0,0 +1,5 @@ +[synopsis] +crm_attribute -n [options] + +/and node attributes/ +.SH OPTIONS diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c index 1a4ee9af81..2cc8d26650 100644 --- a/tools/crm_attribute.c +++ b/tools/crm_attribute.c @@ -1,534 +1,525 @@ /* * Copyright 2004-2021 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 +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include +#define SUMMARY "crm_attribute - query and update Pacemaker cluster options and node attributes" + +crm_exit_t exit_code = CRM_EX_OK; +uint64_t cib_opts = cib_sync_call; + +struct { + char command; + gchar *attr_default; + gchar *attr_id; + gchar *attr_name; + 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 +}; + gboolean BE_QUIET = FALSE; -char command = 'G'; - -const char *dest_uname = NULL; -char *dest_node = NULL; -char *set_name = NULL; -char *attr_id = NULL; -char *attr_name = NULL; -char *attr_pattern = NULL; -const char *type = NULL; -const char *rsc_id = NULL; -const char *attr_value = NULL; -const char *attr_default = NULL; -const char *set_type = NULL; - -static pcmk__cli_option_t long_options[] = { - // long option, argument type, storage, short option, description, flags - { - "help", no_argument, NULL, '?', - "\tThis text", pcmk__option_default - }, - { - "version", no_argument, NULL, '$', - "\tVersion information", pcmk__option_default - }, - { - "verbose", no_argument, NULL, 'V', - "\tIncrease debug output", pcmk__option_default - }, - { - "quiet", no_argument, NULL, 'q', - "\tPrint only the value on stdout\n", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nOptions for selecting attribute:", pcmk__option_default - }, - { - "name", required_argument, NULL, 'n', - "Operate on attribute or option with this name", - pcmk__option_default - }, - { - "pattern", required_argument, NULL, 'P', - "Operate on all attributes matching this pattern " - "(with -v/-D and -l reboot)", - pcmk__option_default - }, - { - "promotion", optional_argument, NULL, 'p', - "Operate on node attribute used as promotion score for specified " - "resource, or resource given in OCF_RESOURCE_INSTANCE environment " - "variable if none is specified; this also defaults -l/--lifetime " - "to reboot (normally invoked from an OCF resource agent)", - pcmk__option_default - }, - { - "set-name", required_argument, NULL, 's', - "(Advanced) Operate on instance of specified attribute that is " - "within set with this XML ID", - pcmk__option_default - }, - { - "id", required_argument, NULL, 'i', - "\t(Advanced) Operate on instance of specified attribute with this " - "XML ID", - pcmk__option_default - }, +#define INDENT " " - { - "-spacer-", no_argument, NULL, '-', - "\nCommands:", pcmk__option_default - }, - { - "query", no_argument, NULL, 'G', - "\tQuery the current value of the attribute/option", - pcmk__option_default - }, - { - "update", required_argument, NULL, 'v', - "Update the value of the attribute/option", pcmk__option_default - }, - { - "delete", no_argument, NULL, 'D', - "\tDelete the attribute/option", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nAdditional Options:", pcmk__option_default - }, - { - "node", required_argument, NULL, 'N', - "Set a node attribute for named node (instead of a cluster option). " - "See also: -l", - pcmk__option_default - }, - { - "type", required_argument, NULL, 't', - "Which part of the configuration to update/delete/query the option in", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\t\tValid values: crm_config, rsc_defaults, op_defaults, tickets", - pcmk__option_default - }, - { - "lifetime", required_argument, NULL, 'l', - "Lifetime of the node attribute", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\t\t\tValid values: reboot, forever", pcmk__option_default - }, - { - "utilization", no_argument, NULL, 'z', - "Set an utilization attribute for the node.", pcmk__option_default - }, - { - "default", required_argument, NULL, 'd', - "(Advanced) Default value to display if none is found in configuration", - pcmk__option_default - }, - { - "inhibit-policy-engine", no_argument, NULL, '!', - NULL, pcmk__option_hidden - }, +static gboolean +delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'D'; + + if (options.attr_value) { + free(options.attr_value); + } + + 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 = 'v'; + + if (options.attr_value) { + free(options.attr_value); + } + + options.attr_value = strdup(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); + + if (options.set_type) { + free(options.set_type); + } + + options.set_type = strdup(XML_TAG_UTILIZATION); + return TRUE; +} - /* legacy */ - { - "quiet", no_argument, NULL, 'Q', - NULL, pcmk__option_hidden +static gboolean +value_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.command = 'G'; + + if (options.attr_value) { + free(options.attr_value); + } + + options.attr_value = NULL; + return TRUE; +} + +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" }, - { - "node-uname", required_argument, NULL, 'U', - NULL, pcmk__option_hidden + + { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name, + "Operate on attribute or option with this name", + "NAME" }, - { - "get-value", no_argument, NULL, 'G', - NULL, pcmk__option_hidden + + { "pattern", 'P', 0, G_OPTION_ARG_STRING, &options.attr_pattern, + "Operate on all attributes matching this pattern\n" + INDENT "(with -v/-D and -l reboot)", + "PATTERN" }, - { - "delete-attr", no_argument, NULL, 'D', - NULL, pcmk__option_hidden + + { "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" }, - { - "attr-value", required_argument, NULL, 'v', - NULL, pcmk__option_hidden + + { "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" }, - { - "attr-name", required_argument, NULL, 'n', - NULL, pcmk__option_hidden + + { NULL } +}; + +static GOptionEntry command_entries[] = { + { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb, + "Delete the attribute/option", + NULL }, - { - "attr-id", required_argument, NULL, 'i', - NULL, pcmk__option_hidden + + { "query", 'G', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb, + "Query the current value of the attribute/option", + NULL }, - { - "-spacer-", no_argument, NULL, '-', - "\nExamples:", pcmk__option_paragraph + { "update", 'v', 0, G_OPTION_ARG_CALLBACK, update_cb, + "Update the value of the attribute/option", + "VALUE" }, - { - "-spacer-", no_argument, NULL, '-', - "Add new node attribute called 'location' with the value of 'office' " - "for host 'myhost':", - pcmk__option_paragraph + + { 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" }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --node myhost --name location --update office", - pcmk__option_example + + { "lifetime", 'l', 0, G_OPTION_ARG_STRING, &options.type, + "Lifetime of the node attribute.\n" + INDENT "Valid values: reboot, forever", + "LIFETIME" }, - { - "-spacer-", no_argument, NULL, '-', - "Query the value of the 'location' node attribute for host 'myhost':", - pcmk__option_paragraph + + { "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" }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --node myhost --name location --query", - pcmk__option_example + + { "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" }, - { - "-spacer-", no_argument, NULL, '-', - "Change the value of the 'location' node attribute for host 'myhost':", - pcmk__option_paragraph + + { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, utilization_cb, + "Set an utilization attribute for the node.", + NULL }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --node myhost --name location --update backoffice", - pcmk__option_example + + { "inhibit-policy-engine", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.inhibit, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - "Delete the 'location' node attribute for host 'myhost':", - pcmk__option_paragraph + + { NULL } +}; + +static GOptionEntry deprecated_entries[] = { + { "attr-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_id, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --node myhost --name location --delete", - pcmk__option_example + + { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_name, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - "Query the value of the cluster-delay cluster option:", - pcmk__option_paragraph + + { "attr-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, update_cb, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --type crm_config --name cluster-delay --query", - pcmk__option_example + + { "delete-attr", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, delete_cb, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - "Query value of the \"cluster-delay\" cluster option and print only " - "the value:", - pcmk__option_paragraph + + { "get-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, value_cb, + NULL, NULL }, - { - "-spacer-", no_argument, NULL, '-', - " crm_attribute --type crm_config --name cluster-delay --query --quiet", - pcmk__option_example + + { "node-uname", 'U', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_uname, + NULL, NULL }, - { 0, 0, 0, 0 } + + { 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, NULL, 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 rc = pcmk_ok; - - int cib_opts = cib_sync_call; - int argerr = 0; - int flag; - - int option_index = 0; int is_remote_node = 0; - bool try_attrd = true; - bool promotion_score = false; int attrd_opts = pcmk__node_attr_none; - pcmk__cli_init_logging("crm_attribute", 0); - pcmk__set_cli_options(NULL, "-n [options]", - long_options, - "query and update Pacemaker cluster options " - "and node attributes"); + int rc = pcmk_ok; + GError *error = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, "DGNPdilnpstv"); + GOptionContext *context = build_arg_context(args, &output_group); - if (argc < 2) { - pcmk__cli_help('?', CRM_EX_USAGE); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; } - while (1) { - flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); - if (flag == -1) - break; - - switch (flag) { - case 'V': - crm_bump_log_level(argc, argv); - break; - case '$': - case '?': - pcmk__cli_help(flag, CRM_EX_OK); - break; - case 'G': - command = flag; - attr_value = optarg; - break; - case 'D': - case 'v': - command = flag; - attr_value = optarg; - crm_log_args(argc, argv); - break; - case 'q': - case 'Q': - BE_QUIET = TRUE; - break; - case 'U': - case 'N': - dest_uname = strdup(optarg); - break; - case 's': - set_name = strdup(optarg); - break; - case 'l': - case 't': - type = optarg; - break; - case 'z': - type = XML_CIB_TAG_NODES; - set_type = XML_TAG_UTILIZATION; - break; - case 'n': - attr_name = strdup(optarg); - break; - case 'p': - promotion_score = true; - attr_name = pcmk_promotion_score_name(optarg); - if (attr_name == NULL) { - fprintf(stderr, "-p/--promotion must be called from an " - " OCF resource agent or with a resource ID " - " specified\n\n"); - ++argerr; - } - break; - case 'P': - attr_pattern = strdup(optarg); - break; - case 'i': - attr_id = strdup(optarg); - break; - case 'r': - rsc_id = optarg; - break; - case 'd': - attr_default = optarg; - break; - case '!': - crm_warn("Inhibiting notifications for this update"); - cib__set_call_options(cib_opts, crm_system_name, - cib_inhibit_notify); - break; - default: - printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); - ++argerr; - break; - } + pcmk__cli_init_logging("crm_attribute", 0); + + if (args->version) { + g_strfreev(processed_args); + pcmk__free_arg_context(context); + /* FIXME: When crm_attribute is converted to use formatted output, this can go. */ + pcmk__cli_help('v', CRM_EX_USAGE); } - if (optind < argc) { - printf("non-option ARGV-elements: "); - while (optind < argc) - printf("%s ", argv[optind++]); - printf("\n"); + if (options.promotion_score && options.attr_name == NULL) { + fprintf(stderr, "-p/--promotion must be called from an " + " OCF resource agent or with a resource ID " + " specified\n\n"); + exit_code = CRM_EX_USAGE; + goto done; } - if (optind > argc) { - ++argerr; + if (options.inhibit) { + crm_warn("Inhibiting notifications for this update"); + cib__set_call_options(cib_opts, crm_system_name, cib_inhibit_notify); } - if (argerr) { - pcmk__cli_help('?', CRM_EX_USAGE); + if (args->quiet) { + BE_QUIET = TRUE; } the_cib = cib_new(); rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { fprintf(stderr, "Could not connect to the CIB: %s\n", pcmk_strerror(rc)); - crm_exit(crm_errno2exit(rc)); + exit_code = crm_errno2exit(rc); + goto done; } // Use default CIB location if not given - if (type == NULL) { - if (promotion_score) { + if (options.type == NULL) { + if (options.promotion_score) { // Updating a promotion score node attribute - type = "reboot"; + options.type = g_strdup(XML_CIB_TAG_STATUS); - } else if (dest_uname != NULL) { + } else if (options.dest_uname != NULL) { // Updating some other node attribute - type = "forever"; + options.type = g_strdup(XML_CIB_TAG_NODES); } else { // Updating cluster options - type = XML_CIB_TAG_CRMCONFIG; + 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); - if (pcmk__str_eq(type, "reboot", pcmk__str_casei)) { - type = XML_CIB_TAG_STATUS; - - } else if (pcmk__str_eq(type, "forever", pcmk__str_casei)) { - type = XML_CIB_TAG_NODES; + } else if (pcmk__str_eq(options.type, "forever", pcmk__str_casei)) { + options.type = g_strdup(XML_CIB_TAG_NODES); } // Use default node if not given (except for cluster options and tickets) - if (!pcmk__strcase_any_of(type, XML_CIB_TAG_CRMCONFIG, XML_CIB_TAG_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. */ - dest_uname = pcmk__node_attr_target(dest_uname); - if (dest_uname == NULL) { - dest_uname = get_local_node_name(); + const char *target = pcmk__node_attr_target(options.dest_uname); + + if (target != NULL) { + g_free(options.dest_uname); + options.dest_uname = g_strdup(target); + } + + if (options.dest_uname == NULL) { + options.dest_uname = g_strdup(get_local_node_name()); } - rc = query_node_uuid(the_cib, dest_uname, &dest_node, &is_remote_node); + rc = query_node_uuid(the_cib, options.dest_uname, &options.dest_node, &is_remote_node); if (pcmk_ok != rc) { - fprintf(stderr, "Could not map name=%s to a UUID\n", dest_uname); - the_cib->cmds->signoff(the_cib); - cib_delete(the_cib); - crm_exit(crm_errno2exit(rc)); + fprintf(stderr, "Could not map name=%s to a UUID\n", options.dest_uname); + exit_code = crm_errno2exit(rc); + goto done; } } - if ((command == 'D') && (attr_name == NULL) && (attr_pattern == NULL)) { + if ((options.command == 'D') && (options.attr_name == NULL) && (options.attr_pattern == NULL)) { fprintf(stderr, "Error: must specify attribute name or pattern to delete\n"); - crm_exit(CRM_EX_USAGE); + exit_code = CRM_EX_USAGE; + goto done; } - if (attr_pattern) { - if (((command != 'v') && (command != 'D')) - || !pcmk__str_eq(type, XML_CIB_TAG_STATUS, pcmk__str_casei)) { + if (options.attr_pattern) { + if (((options.command != 'v') && (options.command != 'D')) + || !pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei)) { fprintf(stderr, "Error: pattern can only be used with till-reboot update or delete\n"); - crm_exit(CRM_EX_USAGE); + exit_code = CRM_EX_USAGE; + goto done; } - command = 'u'; - free(attr_name); - attr_name = attr_pattern; + options.command = 'u'; + g_free(options.attr_name); + options.attr_name = options.attr_pattern; } // Only go through attribute manager for transient attributes - try_attrd = pcmk__str_eq(type, XML_CIB_TAG_STATUS, pcmk__str_casei); + try_attrd = pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei); // Don't try to contact attribute manager if we're using a file as CIB if (getenv("CIB_file") || getenv("CIB_shadow")) { try_attrd = FALSE; } if (is_remote_node) { attrd_opts = pcmk__node_attr_remote; } - if (((command == 'v') || (command == 'D') || (command == 'u')) && try_attrd - && (pcmk__node_attr_request(NULL, command, dest_uname, attr_name, - attr_value, type, set_name, NULL, NULL, + if (((options.command == 'v') || (options.command == 'D') || (options.command == 'u')) && try_attrd + && (pcmk__node_attr_request(NULL, options.command, options.dest_uname, options.attr_name, + options.attr_value, options.type, options.set_name, NULL, NULL, attrd_opts) == pcmk_rc_ok)) { crm_info("Update %s=%s sent via pacemaker-attrd", - attr_name, ((command == 'D')? "" : attr_value)); + options.attr_name, ((options.command == 'D')? "" : options.attr_value)); - } else if (command == 'D') { - rc = delete_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name, - attr_id, attr_name, attr_value, TRUE, NULL); + } else if (options.command == 'D') { + rc = delete_attr_delegate(the_cib, cib_opts, options.type, options.dest_node, options.set_type, options.set_name, + options.attr_id, options.attr_name, options.attr_value, TRUE, NULL); if (rc == -ENXIO) { /* Nothing to delete... * which means it's not there... * which is what the admin wanted */ rc = pcmk_ok; } - } else if (command == 'v') { - CRM_LOG_ASSERT(type != NULL); - CRM_LOG_ASSERT(attr_name != NULL); - CRM_LOG_ASSERT(attr_value != NULL); + } else if (options.command == 'v') { + CRM_LOG_ASSERT(options.type != NULL); + CRM_LOG_ASSERT(options.attr_name != NULL); + CRM_LOG_ASSERT(options.attr_value != NULL); - rc = update_attr_delegate(the_cib, cib_opts, type, dest_node, set_type, set_name, - attr_id, attr_name, attr_value, TRUE, NULL, is_remote_node ? "remote" : NULL); + rc = update_attr_delegate(the_cib, cib_opts, options.type, options.dest_node, options.set_type, options.set_name, + options.attr_id, options.attr_name, options.attr_value, TRUE, NULL, is_remote_node ? "remote" : NULL); } else { /* query */ char *read_value = NULL; - rc = read_attr_delegate(the_cib, type, dest_node, set_type, set_name, - attr_id, attr_name, &read_value, TRUE, NULL); + rc = read_attr_delegate(the_cib, options.type, options.dest_node, options.set_type, options.set_name, + options.attr_id, options.attr_name, &read_value, TRUE, NULL); - if (rc == -ENXIO && attr_default) { - read_value = strdup(attr_default); + if (rc == -ENXIO && options.attr_default) { + read_value = strdup(options.attr_default); rc = pcmk_ok; } crm_info("Read %s=%s %s%s", - attr_name, crm_str(read_value), set_name ? "in " : "", set_name ? set_name : ""); + options.attr_name, crm_str(read_value), options.set_name ? "in " : "", options.set_name ? options.set_name : ""); if (rc == -ENOTUNIQ) { // Multiple matches (already displayed) are not error for queries rc = pcmk_ok; } else if (BE_QUIET == FALSE) { fprintf(stdout, "%s%s %s%s %s%s value=%s\n", - type ? "scope=" : "", type ? type : "", - attr_id ? "id=" : "", attr_id ? attr_id : "", - attr_name ? "name=" : "", attr_name ? attr_name : "", + options.type ? "scope=" : "", options.type ? options.type : "", + options.attr_id ? "id=" : "", options.attr_id ? options.attr_id : "", + options.attr_name ? "name=" : "", options.attr_name ? options.attr_name : "", read_value ? read_value : "(null)"); } else if (read_value != NULL) { fprintf(stdout, "%s\n", read_value); } free(read_value); } if (rc == -ENOTUNIQ) { printf("Please choose from one of the matches above and supply the 'id' with --attr-id\n"); + exit_code = crm_errno2exit(rc); } else if (rc != pcmk_ok) { fprintf(stderr, "Error performing operation: %s\n", pcmk_strerror(rc)); + exit_code = crm_errno2exit(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); + + if (the_cib) { + the_cib->cmds->signoff(the_cib); + cib_delete(the_cib); } - the_cib->cmds->signoff(the_cib); - cib_delete(the_cib); - crm_exit(crm_errno2exit(rc)); + pcmk__output_and_clear_error(error, NULL); + return crm_exit(exit_code); }