diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h index fb8c99d37b..908ae64d8a 100644 --- a/daemons/based/pacemaker-based.h +++ b/daemons/based/pacemaker-based.h @@ -1,158 +1,157 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 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 PACEMAKER_BASED__H # define PACEMAKER_BASED__H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GNUTLS_GNUTLS_H # include #endif // CIB-specific client flags enum cib_client_flags { // Notifications cib_notify_pre = (UINT64_C(1) << 0), cib_notify_post = (UINT64_C(1) << 1), cib_notify_replace = (UINT64_C(1) << 2), cib_notify_confirm = (UINT64_C(1) << 3), cib_notify_diff = (UINT64_C(1) << 4), // Whether client is another cluster daemon cib_is_daemon = (UINT64_C(1) << 12), }; typedef struct cib_operation_s { const char *operation; gboolean modifies_cib; gboolean needs_privileges; gboolean needs_quorum; int (*prepare) (xmlNode *, xmlNode **, const char **); int (*cleanup) (int, xmlNode **, xmlNode **); int (*fn) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); } cib_operation_t; extern gboolean cib_is_master; extern GHashTable *peer_hash; extern GHashTable *config_hash; extern xmlNode *the_cib; extern crm_trigger_t *cib_writer; extern gboolean cib_writes_enabled; extern GMainLoop *mainloop; extern crm_cluster_t crm_cluster; extern GHashTable *local_notify_queue; extern gboolean legacy_mode; extern gboolean stand_alone; extern gboolean cib_shutdown_flag; extern const char *cib_root; extern char *cib_our_uname; extern int cib_status; extern FILE *msg_cib_strm; extern struct qb_ipcs_service_handlers ipc_ro_callbacks; extern struct qb_ipcs_service_handlers ipc_rw_callbacks; extern qb_ipcs_service_t *ipcs_ro; extern qb_ipcs_service_t *ipcs_rw; extern qb_ipcs_service_t *ipcs_shm; void cib_peer_callback(xmlNode *msg, void *private_data); void cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode *op_request, pcmk__client_t *cib_client, gboolean privileged); void cib_shutdown(int nsig); void initiate_exit(void); void terminate_cib(const char *caller, int fast); gboolean cib_legacy_mode(void); gboolean uninitializeCib(void); -gboolean verifyCibXml(xmlNode *cib); xmlNode *readCibXml(char *buffer); xmlNode *readCibXmlFile(const char *dir, const char *file, gboolean discard_status); int activateCibXml(xmlNode *doc, gboolean to_disk, const char *op); xmlNode *createCibRequest(gboolean isLocal, const char *operation, const char *section, const char *verbose, xmlNode *data); int cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_default(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_ping(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_readwrite(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_replace_svr(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_server_process_diff(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_sync(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_sync_one(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); int cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer); void send_sync_request(const char *host); xmlNode *cib_msg_copy(xmlNode *msg, gboolean with_data); xmlNode *cib_construct_reply(xmlNode *request, xmlNode *output, int rc); int cib_get_operation_id(const char *op, int *operation); cib_op_t *cib_op_func(int call_type); gboolean cib_op_modifies(int call_type); int cib_op_prepare(int call_type, xmlNode *request, xmlNode **input, const char **section); int cib_op_cleanup(int call_type, int options, xmlNode **input, xmlNode **output); int cib_op_can_run(int call_type, int call_options, gboolean privileged, gboolean global_update); void cib_diff_notify(int options, const char *client, const char *call_id, const char *op, xmlNode *update, int result, xmlNode *old_cib); void cib_replace_notify(const char *origin, xmlNode *update, int result, xmlNode *diff, int change_section); static inline const char * cib_config_lookup(const char *opt) { return g_hash_table_lookup(config_hash, opt); } #endif // PACEMAKER_BASED__H diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 336222e13c..46eacbce77 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,260 +1,260 @@ /* * Copyright 2004-2022 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 CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # include # define CIB_OP_SLAVE "cib_slave" # define CIB_OP_SLAVEALL "cib_slave_all" # define CIB_OP_MASTER "cib_master" # define CIB_OP_SYNC "cib_sync" # define CIB_OP_SYNC_ONE "cib_sync_one" # define CIB_OP_ISMASTER "cib_ismaster" # define CIB_OP_BUMP "cib_bump" # define CIB_OP_QUERY "cib_query" # define CIB_OP_CREATE "cib_create" # define CIB_OP_MODIFY "cib_modify" # define CIB_OP_DELETE "cib_delete" # define CIB_OP_ERASE "cib_erase" # define CIB_OP_REPLACE "cib_replace" # define CIB_OP_APPLY_DIFF "cib_apply_diff" # define CIB_OP_UPGRADE "cib_upgrade" # define CIB_OP_DELETE_ALT "cib_delete_alt" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_UPGRADE_RC "cib_upgrade_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_CALLBACK_TOKEN "cib_async_id" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define F_CIB_SCHEMA_MAX "cib_schema_max" # define F_CIB_CHANGE_SECTION "cib_change_section" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" enum cib_change_section_info { cib_change_section_none = 0x00000000, cib_change_section_nodes = 0x00000001, cib_change_section_alerts = 0x00000002, cib_change_section_status = 0x00000004 }; gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); void verify_cib_options(GHashTable * options); gboolean cib_internal_config_changed(xmlNode * diff); typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; void (*free_func)(void *); } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; #define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \ cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_set), #flags_to_set); \ } while (0) #define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \ cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_clear), #flags_to_clear); \ } while (0) typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output); xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_native_register_notification(cib_t * cib, const char *callback, int enabled); gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)); gboolean cib_client_register_callback_full(cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode *, int, int, xmlNode *, void *), void (*free_func)(void *)); int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); /*! * \internal * \brief Query or modify a CIB * * \param[in] op CIB_OP_* operation to be performed * \param[in] options Flag set of \c cib_call_options * \param[in] section XPath to query or modify * \param[in] req unused * \param[in] input Portion of CIB to modify (used with * CIB_OP_CREATE, CIB_OP_MODIFY, and * CIB_OP_REPLACE) * \param[in] existing_cib Input CIB (used with CIB_OP_QUERY) * \param[in,out] result_cib CIB copy to make changes in (used with * CIB_OP_CREATE, CIB_OP_MODIFY, CIB_OP_DELETE, and * CIB_OP_REPLACE) * \param[out] answer Query result (used with CIB_OP_QUERY) * * \return Legacy Pacemaker return code */ int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff); gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code); int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name); int cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root); int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, const char *cib_filename); void cib__set_output(cib_t *cib, pcmk__output_t *out); cib_callback_client_t* cib__lookup_id (int call_id); /*! * \internal * \brief Connect to, query, and optionally disconnect from the CIB, returning * the resulting XML object. * * \param[out] cib If non-NULL, a pointer to where to store the CIB * connection. In this case, it is up to the caller to * disconnect from the CIB when finished. * \param[out] cib_object A pointer to where to store the XML query result. * * \return A standard Pacemaker return code */ int cib__signon_query(cib_t **cib, xmlNode **cib_object); int cib__clean_up_connection(cib_t **cib); int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name, const char *node_type); -int cib__read_node_attr(pcmk__output_t *out, cib_t *cib, const char *section, +int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, - const char *attr_id, const char *attr_name, char **attr_value, - const char *user_name); + const char *attr_id, const char *attr_name, const char *user_name, + xmlNode **result); int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name); #endif diff --git a/include/crm/cib/util.h b/include/crm/cib/util.h index 4581c6bfd9..18726bbe3e 100644 --- a/include/crm/cib/util.h +++ b/include/crm/cib/util.h @@ -1,76 +1,73 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 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 PCMK__CRM_CIB_UTIL__H # define PCMK__CRM_CIB_UTIL__H #include // gboolean #include // xmlNode #include // cib_t #ifdef __cplusplus extern "C" { #endif /* Utility functions */ -xmlNode *create_cib_fragment_adv(xmlNode * update, const char *section, const char *source); - xmlNode *createEmptyCib(int cib_epoch); -gboolean verifyCibXml(xmlNode * cib); gboolean cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates); int update_attr_delegate(cib_t * the_cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, gboolean to_console, const char *user_name, const char *node_type); int find_nvpair_attr_delegate(cib_t * the_cib, const char *attr, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, gboolean to_console, char **value, const char *user_name); int read_attr_delegate(cib_t * the_cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, char **attr_value, gboolean to_console, const char *user_name); int delete_attr_delegate(cib_t * the_cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, gboolean to_console, const char *user_name); int query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node); int query_node_uname(cib_t * the_cib, const char *uuid, char **uname); int set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value); xmlNode *cib_get_generation(cib_t * cib); void cib_metadata(void); const char *cib_pref(GHashTable * options, const char *name); int cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, int level); #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus } #endif #endif diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c index d80bbd0e90..1575f0875c 100644 --- a/lib/cib/cib_attrs.c +++ b/lib/cib/cib_attrs.c @@ -1,707 +1,743 @@ /* * Copyright 2004-2022 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* could also check for possible truncation */ #define attr_snprintf(_str, _offset, _limit, ...) do { \ _offset += snprintf(_str + _offset, \ (_limit > _offset) ? _limit - _offset : 0, \ __VA_ARGS__); \ } while(0) #define XPATH_MAX 1024 static pcmk__output_t * new_output_object(const char *ty) { int rc = pcmk_rc_ok; pcmk__output_t *out = NULL; const char* argv[] = { "", NULL }; pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_LOG, PCMK__SUPPORTED_FORMAT_TEXT, { NULL, NULL, NULL } }; pcmk__register_formats(NULL, formats); rc = pcmk__output_new(&out, ty, NULL, (char**)argv); if ((rc != pcmk_rc_ok) || (out == NULL)) { crm_err("Can't out due to internal error: %s", pcmk_rc_str(rc)); return NULL; } return out; } static int -find_attr(pcmk__output_t *out, cib_t *cib, const char *attr, const char *section, - const char *node_uuid, const char *attr_set_type, const char *set_name, - const char *attr_id, const char *attr_name, char **value, - const char *user_name) +find_attr(cib_t *cib, const char *section, const char *node_uuid, + const char *attr_set_type, const char *set_name, const char *attr_id, + const char *attr_name, const char *user_name, xmlNode **result) { int offset = 0; int rc = pcmk_rc_ok; const char *xpath_base = NULL; char *xpath_string = NULL; xmlNode *xml_search = NULL; const char *set_type = NULL; const char *node_type = NULL; if (attr_set_type) { set_type = attr_set_type; } else { set_type = XML_TAG_ATTR_SETS; } - CRM_ASSERT(value != NULL); - *value = NULL; - if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) { node_uuid = NULL; set_type = XML_CIB_TAG_PROPSET; } else if (pcmk__strcase_any_of(section, XML_CIB_TAG_OPCONFIG, XML_CIB_TAG_RSCCONFIG, NULL)) { node_uuid = NULL; set_type = XML_TAG_META_SETS; } else if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) { node_uuid = NULL; section = XML_CIB_TAG_STATUS; node_type = XML_CIB_TAG_TICKETS; } else if (node_uuid == NULL) { return EINVAL; } xpath_base = pcmk_cib_xpath_for(section); if (xpath_base == NULL) { crm_warn("%s CIB section not known", section); return ENOMSG; } xpath_string = calloc(1, XPATH_MAX); if (xpath_string == NULL) { crm_perror(LOG_CRIT, "Could not create xpath"); return ENOMEM; } attr_snprintf(xpath_string, offset, XPATH_MAX, "%s", xpath_base); if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) { attr_snprintf(xpath_string, offset, XPATH_MAX, "//%s", node_type); } else if (node_uuid) { const char *node_type = XML_CIB_TAG_NODE; if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { node_type = XML_CIB_TAG_STATE; set_type = XML_TAG_TRANSIENT_NODEATTRS; } attr_snprintf(xpath_string, offset, XPATH_MAX, "//%s[@id='%s']", node_type, node_uuid); } if (set_name) { attr_snprintf(xpath_string, offset, XPATH_MAX, "//%s[@id='%.128s']", set_type, set_name); } else { attr_snprintf(xpath_string, offset, XPATH_MAX, "//%s", set_type); } - attr_snprintf(xpath_string, offset, XPATH_MAX, "//nvpair["); - if (attr_id) { - attr_snprintf(xpath_string, offset, XPATH_MAX, "@id='%s'", attr_id); - } + attr_snprintf(xpath_string, offset, XPATH_MAX, "//nvpair"); - if (attr_name) { - if (attr_id) { - attr_snprintf(xpath_string, offset, XPATH_MAX, " and "); - } - attr_snprintf(xpath_string, offset, XPATH_MAX, "@name='%.128s'", attr_name); + if (attr_id && attr_name) { + attr_snprintf(xpath_string, offset, XPATH_MAX, "[@id='%s' and @name='%.128s']", + attr_id, attr_name); + } else if (attr_id) { + attr_snprintf(xpath_string, offset, XPATH_MAX, "[@id='%s']", attr_id); + } else if (attr_name) { + attr_snprintf(xpath_string, offset, XPATH_MAX, "[@name='%.128s']", attr_name); } - attr_snprintf(xpath_string, offset, XPATH_MAX, "]"); + CRM_LOG_ASSERT(offset > 0); rc = cib_internal_op(cib, CIB_OP_QUERY, NULL, xpath_string, NULL, &xml_search, cib_sync_call | cib_scope_local | cib_xpath, user_name); if (rc < 0) { rc = pcmk_legacy2rc(rc); crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s, xpath=%s): %s", attr_name, section, crm_str(node_uuid), crm_str(set_name), xpath_string, pcmk_rc_str(rc)); goto done; } else { rc = pcmk_rc_ok; } crm_log_xml_debug(xml_search, "Match"); - if (xml_has_children(xml_search)) { - xmlNode *child = NULL; - rc = ENOTUNIQ; + done: + free(xpath_string); + *result = xml_search; + return rc; +} + +static int +handle_multiples(pcmk__output_t *out, xmlNode *search, const char *attr_name) +{ + if (xml_has_children(search)) { + xmlNode *child = NULL; out->info(out, "Multiple attributes match name=%s", attr_name); - for (child = pcmk__xml_first_child(xml_search); child != NULL; + for (child = pcmk__xml_first_child(search); child != NULL; child = pcmk__xml_next(child)) { out->info(out, " Value: %s \t(id=%s)", crm_element_value(child, XML_NVPAIR_ATTR_VALUE), ID(child)); } + return ENOTUNIQ; + } else { - pcmk__str_update(value, crm_element_value(xml_search, attr)); + return pcmk_rc_ok; } - - done: - free(xpath_string); - free_xml(xml_search); - return rc; } int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name, const char *node_type) { const char *tag = NULL; int rc = pcmk_rc_ok; xmlNode *xml_top = NULL; xmlNode *xml_obj = NULL; + xmlNode *xml_search = NULL; char *local_attr_id = NULL; char *local_set_name = NULL; CRM_CHECK(section != NULL, return EINVAL); CRM_CHECK(attr_value != NULL, return EINVAL); CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL); - rc = find_attr(out, cib, XML_ATTR_ID, section, node_uuid, set_type, set_name, - attr_id, attr_name, &local_attr_id, user_name); + rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, + attr_name, user_name, &xml_search); + if (rc == pcmk_rc_ok) { - attr_id = local_attr_id; - goto do_modify; + if (handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) { + free_xml(xml_search); + return ENOTUNIQ; + } else { + pcmk__str_update(&local_attr_id, crm_element_value(xml_search, XML_ATTR_ID)); + attr_id = local_attr_id; + free_xml(xml_search); + goto do_modify; + } } else if (rc != ENXIO) { + free_xml(xml_search); return rc; /* } else if(attr_id == NULL) { */ /* return EINVAL; */ } else { + free_xml(xml_search); crm_trace("%s does not exist, create it", attr_name); if (pcmk__str_eq(section, XML_CIB_TAG_TICKETS, pcmk__str_casei)) { node_uuid = NULL; section = XML_CIB_TAG_STATUS; node_type = XML_CIB_TAG_TICKETS; xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATUS); xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS); } else if (pcmk__str_eq(section, XML_CIB_TAG_NODES, pcmk__str_casei)) { if (node_uuid == NULL) { return EINVAL; } if (pcmk__str_eq(node_type, "remote", pcmk__str_casei)) { xml_top = create_xml_node(xml_obj, XML_CIB_TAG_NODES); xml_obj = create_xml_node(xml_top, XML_CIB_TAG_NODE); crm_xml_add(xml_obj, XML_ATTR_TYPE, "remote"); crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid); crm_xml_add(xml_obj, XML_ATTR_UNAME, node_uuid); } else { tag = XML_CIB_TAG_NODE; } } else if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { tag = XML_TAG_TRANSIENT_NODEATTRS; if (node_uuid == NULL) { return EINVAL; } xml_top = create_xml_node(xml_obj, XML_CIB_TAG_STATE); crm_xml_add(xml_top, XML_ATTR_ID, node_uuid); xml_obj = xml_top; } else { tag = section; node_uuid = NULL; } if (set_name == NULL) { if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) { local_set_name = strdup(CIB_OPTIONS_FIRST); } else if (pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) { local_set_name = crm_strdup_printf("%s-%s", section, XML_CIB_TAG_TICKETS); } else if (node_uuid) { local_set_name = crm_strdup_printf("%s-%s", section, node_uuid); if (set_type) { char *tmp_set_name = local_set_name; local_set_name = crm_strdup_printf("%s-%s", tmp_set_name, set_type); free(tmp_set_name); } } else { local_set_name = crm_strdup_printf("%s-options", section); } set_name = local_set_name; } if (attr_id == NULL) { local_attr_id = crm_strdup_printf("%s-%s", set_name, attr_name); crm_xml_sanitize_id(local_attr_id); attr_id = local_attr_id; } else if (attr_name == NULL) { attr_name = attr_id; } crm_trace("Creating %s/%s", section, tag); if (tag != NULL) { xml_obj = create_xml_node(xml_obj, tag); crm_xml_add(xml_obj, XML_ATTR_ID, node_uuid); if (xml_top == NULL) { xml_top = xml_obj; } } if (node_uuid == NULL && !pcmk__str_eq(node_type, XML_CIB_TAG_TICKETS, pcmk__str_casei)) { if (pcmk__str_eq(section, XML_CIB_TAG_CRMCONFIG, pcmk__str_casei)) { xml_obj = create_xml_node(xml_obj, XML_CIB_TAG_PROPSET); } else { xml_obj = create_xml_node(xml_obj, XML_TAG_META_SETS); } } else if (set_type) { xml_obj = create_xml_node(xml_obj, set_type); } else { xml_obj = create_xml_node(xml_obj, XML_TAG_ATTR_SETS); } crm_xml_add(xml_obj, XML_ATTR_ID, set_name); if (xml_top == NULL) { xml_top = xml_obj; } } do_modify: xml_obj = crm_create_nvpair_xml(xml_obj, attr_id, attr_name, attr_value); if (xml_top == NULL) { xml_top = xml_obj; } crm_log_xml_trace(xml_top, "update_attr"); rc = cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, xml_top, NULL, call_options | cib_quorum_override, user_name); if (rc < 0) { rc = pcmk_legacy2rc(rc); out->err(out, "Error setting %s=%s (section=%s, set=%s): %s", attr_name, attr_value, section, crm_str(set_name), pcmk_rc_str(rc)); crm_log_xml_info(xml_top, "Update"); } else { rc = pcmk_rc_ok; } free(local_set_name); free(local_attr_id); free_xml(xml_top); return rc; } int -cib__read_node_attr(pcmk__output_t *out, cib_t *cib, const char *section, +cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, - const char *attr_id, const char *attr_name, char **attr_value, - const char *user_name) + const char *attr_id, const char *attr_name, const char *user_name, + xmlNode **result) { int rc = pcmk_rc_ok; - CRM_ASSERT(attr_value != NULL); + CRM_ASSERT(result != NULL); CRM_CHECK(section != NULL, return EINVAL); - CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL); - *attr_value = NULL; + *result = NULL; + + rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, attr_name, + user_name, result); - rc = find_attr(out, cib, XML_NVPAIR_ATTR_VALUE, section, node_uuid, set_type, - set_name, attr_id, attr_name, attr_value, user_name); if (rc != pcmk_rc_ok) { crm_trace("Query failed for attribute %s (section=%s, node=%s, set=%s): %s", - attr_name, section, crm_str(set_name), crm_str(node_uuid), pcmk_strerror(rc)); + crm_str(attr_name), section, crm_str(set_name), crm_str(node_uuid), pcmk_strerror(rc)); } return rc; } int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name) { int rc = pcmk_rc_ok; xmlNode *xml_obj = NULL; + xmlNode *xml_search = NULL; char *local_attr_id = NULL; CRM_CHECK(section != NULL, return EINVAL); CRM_CHECK(attr_name != NULL || attr_id != NULL, return EINVAL); if (attr_id == NULL) { - rc = find_attr(out, cib, XML_ATTR_ID, section, node_uuid, set_type, - set_name, attr_id, attr_name, &local_attr_id, - user_name); - if (rc != pcmk_rc_ok) { + rc = find_attr(cib, section, node_uuid, set_type, set_name, attr_id, + attr_name, user_name, &xml_search); + + if (rc != pcmk_rc_ok || handle_multiples(out, xml_search, attr_name) == ENOTUNIQ) { + free_xml(xml_search); return rc; + } else { + pcmk__str_update(&local_attr_id, crm_element_value(xml_search, XML_ATTR_ID)); + attr_id = local_attr_id; + free_xml(xml_search); } - attr_id = local_attr_id; } xml_obj = crm_create_nvpair_xml(NULL, attr_id, attr_name, attr_value); rc = cib_internal_op(cib, CIB_OP_DELETE, NULL, section, xml_obj, NULL, options | cib_quorum_override, user_name); if (rc < 0) { rc = pcmk_legacy2rc(rc); } else { rc = pcmk_rc_ok; out->info(out, "Deleted %s %s: id=%s%s%s%s%s", section, node_uuid ? "attribute" : "option", local_attr_id, set_name ? " set=" : "", set_name ? set_name : "", attr_name ? " name=" : "", attr_name ? attr_name : ""); } free(local_attr_id); free_xml(xml_obj); return rc; } int find_nvpair_attr_delegate(cib_t *cib, const char *attr, const char *section, const char *node_uuid, const char *attr_set_type, const char *set_name, const char *attr_id, const char *attr_name, gboolean to_console, char **value, const char *user_name) { pcmk__output_t *out = NULL; + xmlNode *xml_search = NULL; int rc = pcmk_ok; out = new_output_object(to_console ? "text" : "log"); if (out == NULL) { return pcmk_err_generic; } - rc = find_attr(out, cib, attr, section, node_uuid, attr_set_type, - set_name, attr_id, attr_name, value, user_name); + rc = find_attr(cib, section, node_uuid, attr_set_type, set_name, attr_id, + attr_name, user_name, &xml_search); + + if (rc == pcmk_rc_ok) { + rc = handle_multiples(out, xml_search, attr_name); + + if (rc == pcmk_rc_ok) { + pcmk__str_update(value, crm_element_value(xml_search, attr)); + } + } out->finish(out, CRM_EX_OK, true, NULL); + free_xml(xml_search); pcmk__output_free(out); return pcmk_rc2legacy(rc); } int update_attr_delegate(cib_t *cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, gboolean to_console, const char *user_name, const char *node_type) { pcmk__output_t *out = NULL; int rc = pcmk_ok; out = new_output_object(to_console ? "text" : "log"); if (out == NULL) { return pcmk_err_generic; } rc = cib__update_node_attr(out, cib, call_options, section, node_uuid, set_type, set_name, attr_id, attr_name, attr_value, user_name, node_type); out->finish(out, CRM_EX_OK, true, NULL); pcmk__output_free(out); return pcmk_rc2legacy(rc); } int read_attr_delegate(cib_t *cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, char **attr_value, gboolean to_console, const char *user_name) { pcmk__output_t *out = NULL; + xmlNode *result = NULL; int rc = pcmk_ok; out = new_output_object(to_console ? "text" : "log"); if (out == NULL) { return pcmk_err_generic; } - rc = cib__read_node_attr(out, cib, section, node_uuid, set_type, set_name, - attr_id, attr_name, attr_value, user_name); + rc = cib__get_node_attrs(out, cib, section, node_uuid, set_type, set_name, + attr_id, attr_name, user_name, &result); + + if (rc == pcmk_rc_ok) { + if (!xml_has_children(result)) { + pcmk__str_update(attr_value, crm_element_value(result, XML_NVPAIR_ATTR_VALUE)); + } else { + rc = ENOTUNIQ; + } + } out->finish(out, CRM_EX_OK, true, NULL); + free_xml(result); pcmk__output_free(out); return pcmk_rc2legacy(rc); } int delete_attr_delegate(cib_t *cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, gboolean to_console, const char *user_name) { pcmk__output_t *out = NULL; int rc = pcmk_ok; out = new_output_object(to_console ? "text" : "log"); if (out == NULL) { return pcmk_err_generic; } rc = cib__delete_node_attr(out, cib, options, section, node_uuid, set_type, set_name, attr_id, attr_name, attr_value, user_name); out->finish(out, CRM_EX_OK, true, NULL); pcmk__output_free(out); return pcmk_rc2legacy(rc); } /*! * \internal * \brief Parse node UUID from search result * * \param[in] result XML search result * \param[out] uuid If non-NULL, where to store parsed UUID * \param[out] is_remote If non-NULL, set TRUE if result is remote node * * \return pcmk_ok if UUID was successfully parsed, -ENXIO otherwise */ static int get_uuid_from_result(xmlNode *result, char **uuid, int *is_remote) { int rc = -ENXIO; const char *tag; const char *parsed_uuid = NULL; int parsed_is_remote = FALSE; if (result == NULL) { return rc; } /* If there are multiple results, the first is sufficient */ tag = (const char *) (result->name); if (pcmk__str_eq(tag, "xpath-query", pcmk__str_casei)) { result = pcmk__xml_first_child(result); CRM_CHECK(result != NULL, return rc); tag = (const char *) (result->name); } if (pcmk__str_eq(tag, XML_CIB_TAG_NODE, pcmk__str_casei)) { /* Result is tag from section */ if (pcmk__str_eq(crm_element_value(result, XML_ATTR_TYPE), "remote", pcmk__str_casei)) { parsed_uuid = crm_element_value(result, XML_ATTR_UNAME); parsed_is_remote = TRUE; } else { parsed_uuid = ID(result); parsed_is_remote = FALSE; } } else if (pcmk__str_eq(tag, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) { /* Result is for ocf:pacemaker:remote resource */ parsed_uuid = ID(result); parsed_is_remote = TRUE; } else if (pcmk__str_eq(tag, XML_CIB_TAG_NVPAIR, pcmk__str_casei)) { /* Result is remote-node parameter of for guest node */ parsed_uuid = crm_element_value(result, XML_NVPAIR_ATTR_VALUE); parsed_is_remote = TRUE; } else if (pcmk__str_eq(tag, XML_CIB_TAG_STATE, pcmk__str_casei)) { /* Result is tag from section */ parsed_uuid = crm_element_value(result, XML_ATTR_UNAME); if (pcmk__xe_attr_is_true(result, XML_NODE_IS_REMOTE)) { parsed_is_remote = TRUE; } } if (parsed_uuid) { if (uuid) { *uuid = strdup(parsed_uuid); } if (is_remote) { *is_remote = parsed_is_remote; } rc = pcmk_ok; } return rc; } /* Search string to find a node by name, as: * - cluster or remote node in nodes section * - remote node in resources section * - guest node in resources section * - orphaned remote node or bundle guest node in status section */ #define XPATH_UPPER_TRANS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #define XPATH_LOWER_TRANS "abcdefghijklmnopqrstuvwxyz" #define XPATH_NODE \ "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \ "/" XML_CIB_TAG_NODE "[translate(@" XML_ATTR_UNAME ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \ "|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \ "/" XML_CIB_TAG_RESOURCE \ "[@class='ocf'][@provider='pacemaker'][@type='remote'][translate(@id,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \ "|/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_RESOURCES \ "/" XML_CIB_TAG_RESOURCE "/" XML_TAG_META_SETS "/" XML_CIB_TAG_NVPAIR \ "[@name='" XML_RSC_ATTR_REMOTE_NODE "'][translate(@value,'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" \ "|/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS "/" XML_CIB_TAG_STATE \ "[@" XML_NODE_IS_REMOTE "='true'][translate(@" XML_ATTR_UUID ",'" XPATH_UPPER_TRANS "','" XPATH_LOWER_TRANS "') ='%s']" int query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node) { int rc = pcmk_ok; char *xpath_string; xmlNode *xml_search = NULL; char *host_lowercase = NULL; CRM_ASSERT(uname != NULL); host_lowercase = g_ascii_strdown(uname, -1); if (uuid) { *uuid = NULL; } if (is_remote_node) { *is_remote_node = FALSE; } xpath_string = crm_strdup_printf(XPATH_NODE, host_lowercase, host_lowercase, host_lowercase, host_lowercase); if (cib_internal_op(the_cib, CIB_OP_QUERY, NULL, xpath_string, NULL, &xml_search, cib_sync_call|cib_scope_local|cib_xpath, NULL) == pcmk_ok) { rc = get_uuid_from_result(xml_search, uuid, is_remote_node); } else { rc = -ENXIO; } free(xpath_string); free_xml(xml_search); g_free(host_lowercase); if (rc != pcmk_ok) { crm_debug("Could not map node name '%s' to a UUID: %s", uname, pcmk_strerror(rc)); } else { crm_info("Mapped node name '%s' to UUID %s", uname, (uuid? *uuid : "")); } return rc; } int query_node_uname(cib_t * the_cib, const char *uuid, char **uname) { int rc = pcmk_ok; xmlNode *a_child = NULL; xmlNode *xml_obj = NULL; xmlNode *fragment = NULL; const char *child_name = NULL; CRM_ASSERT(uname != NULL); CRM_ASSERT(uuid != NULL); rc = the_cib->cmds->query(the_cib, XML_CIB_TAG_NODES, &fragment, cib_sync_call | cib_scope_local); if (rc != pcmk_ok) { return rc; } xml_obj = fragment; CRM_CHECK(pcmk__str_eq(crm_element_name(xml_obj), XML_CIB_TAG_NODES, pcmk__str_casei), return -ENOMSG); CRM_ASSERT(xml_obj != NULL); crm_log_xml_trace(xml_obj, "Result section"); rc = -ENXIO; *uname = NULL; for (a_child = pcmk__xml_first_child(xml_obj); a_child != NULL; a_child = pcmk__xml_next(a_child)) { if (pcmk__str_eq((const char *)a_child->name, XML_CIB_TAG_NODE, pcmk__str_none)) { child_name = ID(a_child); if (pcmk__str_eq(uuid, child_name, pcmk__str_casei)) { child_name = crm_element_value(a_child, XML_ATTR_UNAME); if (child_name != NULL) { *uname = strdup(child_name); rc = pcmk_ok; } break; } } } free_xml(fragment); return rc; } int set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value) { int rc = pcmk_ok; char *attr_id = NULL; CRM_CHECK(uuid != NULL, return -EINVAL); CRM_CHECK(standby_value != NULL, return -EINVAL); if (pcmk__strcase_any_of(scope, "reboot", XML_CIB_TAG_STATUS, NULL)) { scope = XML_CIB_TAG_STATUS; attr_id = crm_strdup_printf("transient-standby-%.256s", uuid); } else { scope = XML_CIB_TAG_NODES; attr_id = crm_strdup_printf("standby-%.256s", uuid); } rc = update_attr_delegate(the_cib, cib_sync_call, scope, uuid, NULL, NULL, attr_id, "standby", standby_value, TRUE, NULL, NULL); free(attr_id); return rc; } diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c index d3ff563241..6fe783b0c5 100644 --- a/tools/crm_attribute.c +++ b/tools/crm_attribute.c @@ -1,732 +1,807 @@ /* * Copyright 2004-2022 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 #include #include #include #include #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", "char *", "char *", "char *", "char *") static int attribute_text(pcmk__output_t *out, va_list args) { char *scope = va_arg(args, char *); char *instance = va_arg(args, char *); char *name = va_arg(args, char *); char *value = va_arg(args, char *); char *host G_GNUC_UNUSED = va_arg(args, char *); if (out->quiet) { 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; 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'; - - if (options.attr_value) { - free(options.attr_value); - } - - options.attr_value = NULL; + 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 = 'v'; + 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); - - if (options.set_type) { - free(options.set_type); - } - - options.set_type = strdup(XML_TAG_UTILIZATION); + pcmk__str_update(&options.attr_value, XML_TAG_UTILIZATION); return TRUE; } 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; + pcmk__str_update(&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" }, { "name", 'n', 0, G_OPTION_ARG_STRING, &options.attr_name, - "Operate on attribute or option with this 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 and -l reboot)", + INDENT "(with -G, or with -v/-D and -l reboot)", "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", + "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" }, { "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 controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_controld_api_reply_t *reply = event_data; if (event_type != pcmk_ipc_event_reply) { return; } if (status != CRM_EX_OK) { exit_code = status; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Bad reply from controller: %s", crm_exit_str(exit_code)); return; } if (reply->reply_type != pcmk_controld_reply_info) { exit_code = CRM_EX_PROTOCOL; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Unknown reply type %d from controller", reply->reply_type); return; } if (reply->data.node_info.uname == NULL) { exit_code = CRM_EX_NOHOST; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Node is not known to cluster"); } exit_code = CRM_EX_OK; pcmk__str_update(&options.dest_uname, reply->data.node_info.uname); } 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 get_node_name_from_controller(void) { int rc = pcmk_rc_ok; pcmk_ipc_api_t *controld_api = NULL; rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (controld_api == NULL) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s", pcmk_rc_str(rc)); return rc; } pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_sync); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s", pcmk_rc_str(rc)); pcmk_free_ipc_api(controld_api); return rc; } rc = pcmk_controld_api_node_info(controld_api, 0); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not ping controller: %s", pcmk_rc_str(rc)); } /* This is a synchronous call, so we have already received and processed * the reply, which means controller_event_cb has been called. If * exit_code was set, return some generic error here. The caller can * then check for that and fail with exit_code. */ if (exit_code != CRM_EX_OK) { rc = pcmk_rc_error; } pcmk_disconnect_ipc(controld_api); pcmk_free_ipc_api(controld_api); return rc; } 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; if (options.attr_pattern) { opts |= pcmk__node_attr_pattern; } switch (command) { case 'D': rc = pcmk__attrd_api_delete(NULL, attr_node, attr_name, opts); break; case 'u': - case 'v': 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; } +static int +command_delete(pcmk__output_t *out, cib_t *cib) +{ + int rc = pcmk_rc_ok; + + 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); + + if (rc == ENXIO) { + /* Nothing to delete... + * which means it's not there... + * which is what the admin wanted + */ + rc = pcmk_rc_ok; + } + + return rc; +} + +static int +command_update(pcmk__output_t *out, cib_t *cib, int is_remote_node) +{ + int rc = pcmk_rc_ok; + + CRM_LOG_ASSERT(options.type != NULL); + CRM_LOG_ASSERT(options.attr_name != NULL); + CRM_LOG_ASSERT(options.attr_value != NULL); + + 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); + + return rc; +} + +static bool +output_one_attribute(pcmk__output_t *out, xmlNode *node, bool use_pattern) +{ + 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); + + if (use_pattern && !pcmk__str_eq(name, options.attr_pattern, pcmk__str_regex)) { + return false; + } + + out->message(out, "attribute", options.type, options.attr_id, name, value, host); + crm_info("Read %s=%s %s%s", + crm_str(name), crm_str(value), + options.set_name ? "in " : "", options.set_name ? options.set_name : ""); + return true; +} + +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) { + out->message(out, "attribute", options.type, options.attr_id, + options.attr_name, options.attr_default, options.dest_uname); + rc = pcmk_rc_ok; + + } else if (rc != pcmk_rc_ok) { + // Don't do anything. + + } else if (xml_has_children(result)) { + xmlNode *child = NULL; + bool did_output = false; + + for (child = pcmk__xml_first_child(result); child != NULL; + child = pcmk__xml_next(child)) { + if (output_one_attribute(out, child, use_pattern)) { + did_output = true; + } + } + + if (!did_output) { + rc = ENXIO; + } + + } else { + output_one_attribute(out, result, use_pattern); + } + + 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), or + * -v (update) or -D (delete), with till-reboot + */ + return options.command == 'G' || + ((options.command == 'u' || options.command == 'D') && + pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei)); +} + +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; - bool try_attrd = true; int attrd_opts = pcmk__node_attr_none; 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; } - // Use default CIB location if not given - 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); - } + 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) { rc = get_node_name_from_controller(); if (rc == pcmk_rc_error) { /* The callback failed with some error condition that is stored in * exit_code. */ goto done; } else if (rc != pcmk_rc_ok) { /* get_node_name_from_controller failed in some other way. Convert * the return code to an exit code. */ exit_code = pcmk_rc2exitc(rc); goto done; } } 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 ((options.command == 'D') && (options.attr_name == NULL) && (options.attr_pattern == NULL)) { + 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.command != 'v') && (options.command != 'D')) - || !pcmk__str_eq(options.type, XML_CIB_TAG_STATUS, pcmk__str_casei)) { - + 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 till-reboot update or delete"); + "Error: pattern can only be used with query, or with " + "till-reboot update or delete"); goto done; } - if (options.command != 'D') { - 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(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 (((options.command == 'v') || (options.command == 'D') || (options.command == 'u')) - && try_attrd - && (send_attrd_update(options.command, options.dest_uname, options.attr_name, - options.attr_value, options.set_name, NULL, attrd_opts) == pcmk_rc_ok)) { + + if (try_ipc_update() && + (send_attrd_update(options.command, options.dest_uname, options.attr_name, + options.attr_value, options.set_name, NULL, attrd_opts) == pcmk_rc_ok)) { crm_info("Update %s=%s sent via pacemaker-attrd", options.attr_name, ((options.command == 'D')? "" : options.attr_value)); } else if (options.command == 'D') { - rc = cib__delete_node_attr(out, the_cib, cib_opts, options.type, options.dest_node, - options.set_type, options.set_name, options.attr_id, - options.attr_name, options.attr_value, NULL); - - if (rc == ENXIO) { - /* Nothing to delete... - * which means it's not there... - * which is what the admin wanted - */ - rc = pcmk_rc_ok; - } + rc = command_delete(out, the_cib); - } 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); + } else if (options.command == 'u') { + rc = command_update(out, the_cib, is_remote_node); - rc = cib__update_node_attr(out, the_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); - - } else { /* query */ - - char *read_value = NULL; - - if (options.attr_id == NULL && options.attr_name == NULL) { - exit_code = CRM_EX_USAGE; - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Error: must specify attribute name or pattern to query"); - goto done; - } - - rc = cib__read_node_attr(out, the_cib, options.type, options.dest_node, - options.set_type, options.set_name, options.attr_id, - options.attr_name, &read_value, NULL); - - if (rc == ENXIO && options.attr_default) { - read_value = strdup(options.attr_default); - rc = pcmk_rc_ok; - } - - crm_info("Read %s=%s %s%s", - 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_rc_ok; - } else { - out->message(out, "attribute", options.type, options.attr_id, - options.attr_name, read_value); - } - - free(read_value); + } 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); } return crm_exit(exit_code); }