diff --git a/daemons/controld/controld_globals.h b/daemons/controld/controld_globals.h index f995cc61c0..8f64f531cb 100644 --- a/daemons/controld/controld_globals.h +++ b/daemons/controld/controld_globals.h @@ -1,152 +1,151 @@ /* - * Copyright 2022-2024 the Pacemaker project contributors + * Copyright 2022-2025 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 CONTROLD_GLOBALS__H # define CONTROLD_GLOBALS__H #include // pcmk__output_t, etc. #include // uint32_t, uint64_t #include // GList, GMainLoop #include // cib_t #include // pcmk__graph_t #include // enum crmd_fsa_state typedef struct { // Group of \p controld_flags values uint32_t flags; /* Controller finite state automaton */ // FSA state enum crmd_fsa_state fsa_state; // FSA actions (group of \p A_* flags) uint64_t fsa_actions; // FSA input register contents (group of \p R_* flags) uint64_t fsa_input_register; // FSA message queue GList *fsa_message_queue; /* CIB */ // Connection to the CIB cib_t *cib_conn; /* Scheduler */ // Reference of the scheduler request being waited on char *fsa_pe_ref; /* Transitioner */ // Transitioner UUID char *te_uuid; // Graph of transition currently being processed pcmk__graph_t *transition_graph; /* Logging */ // Output object for controller log messages pcmk__output_t *logger_out; /* Cluster layer */ // Cluster name char *cluster_name; // Cluster connection pcmk_cluster_t *cluster; /* @TODO Figure out, document, and clean up the code involving * controld_globals.membership_id, controld_globals.peer_seq, and * highest_seq. It's convoluted with no comments. It has something to do * with corosync quorum notifications and the current ring ID, but it's * unclear why we need three separate variables for it. */ // Last saved cluster communication layer membership ID unsigned long long membership_id; unsigned long long peer_seq; /* Other */ // Designated controller name char *dc_name; // Designated controller's Pacemaker version char *dc_version; // Local node's UUID char *our_uuid; // Max lifetime (in seconds) of a resource's shutdown lock to a node guint shutdown_lock_limit; // Node pending timeout guint node_pending_timeout; // Main event loop GMainLoop *mainloop; } controld_globals_t; extern controld_globals_t controld_globals; /*! * \internal - * \enum controld_flags * \brief Bit flags to store various controller state and configuration info */ enum controld_flags { //! The DC left in a membership change that is being processed controld_dc_left = (1 << 0), //! The FSA is stalled waiting for further input controld_fsa_is_stalled = (1 << 1), //! The local node has been in a quorate partition at some point controld_ever_had_quorum = (1 << 2), //! The local node is currently in a quorate partition controld_has_quorum = (1 << 3), //! Panic the local node if it loses quorum controld_no_quorum_panic = (1 << 4), //! Lock resources to the local node when it shuts down cleanly controld_shutdown_lock_enabled = (1 << 5), }; # define controld_set_global_flags(flags_to_set) do { \ controld_globals.flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Global", "controller", \ controld_globals.flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) # define controld_clear_global_flags(flags_to_clear) do { \ controld_globals.flags \ = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "Global", \ "controller", controld_globals.flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) #endif // ifndef CONTROLD_GLOBALS__H diff --git a/daemons/controld/controld_membership.h b/daemons/controld/controld_membership.h index 49d99542e4..dfa57d8f5a 100644 --- a/daemons/controld/controld_membership.h +++ b/daemons/controld/controld_membership.h @@ -1,67 +1,66 @@ /* - * Copyright 2012-2024 the Pacemaker project contributors + * Copyright 2012-2025 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 MEMBERSHIP__H # define MEMBERSHIP__H #ifdef __cplusplus extern "C" { #endif #include /*! * \internal - * \enum controld_join_phase * \brief Phases that a node may pass through while joining controller group */ enum controld_join_phase { controld_join_nack, controld_join_none, controld_join_welcomed, controld_join_integrated, controld_join_finalized, controld_join_confirmed, }; //! User data for \c pcmk__node_status_t object struct controld_node_status_data { enum controld_join_phase join_phase; }; /*! * \internal * \brief Get the controller group join phase from a node status object * * \param[in] node Node status object * * \return Controller group join phase */ static inline enum controld_join_phase controld_get_join_phase(const pcmk__node_status_t *node) { if ((node != NULL) && (node->user_data != NULL)) { struct controld_node_status_data *data = node->user_data; return data->join_phase; } return controld_join_none; } void post_cache_update(int instance); extern gboolean check_join_state(enum crmd_fsa_state cur_state, const char *source); void controld_destroy_failed_sync_table(void); void controld_remove_failed_sync_node(const char *node_name); #ifdef __cplusplus } #endif #endif diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index febaf90f27..c06d8414b6 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,318 +1,316 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 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_INTERNAL__H #define PCMK__CRM_CIB_INTERNAL__H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif // Request types for CIB manager IPC/CPG #define PCMK__CIB_REQUEST_SECONDARY "cib_slave" #define PCMK__CIB_REQUEST_PRIMARY "cib_master" #define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync" #define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one" #define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster" #define PCMK__CIB_REQUEST_BUMP "cib_bump" #define PCMK__CIB_REQUEST_QUERY "cib_query" #define PCMK__CIB_REQUEST_CREATE "cib_create" #define PCMK__CIB_REQUEST_MODIFY "cib_modify" #define PCMK__CIB_REQUEST_DELETE "cib_delete" #define PCMK__CIB_REQUEST_ERASE "cib_erase" #define PCMK__CIB_REQUEST_REPLACE "cib_replace" #define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff" #define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade" #define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt" #define PCMK__CIB_REQUEST_NOOP "noop" #define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req" #define PCMK__CIB_REQUEST_COMMIT_TRANSACT "cib_commit_transact" #define PCMK__CIB_REQUEST_SCHEMAS "cib_schemas" /*! * \internal - * \enum cib__op_attr * \brief Flags for CIB operation attributes */ enum cib__op_attr { cib__op_attr_none = 0, //!< No special attributes cib__op_attr_modifies = (1 << 1), //!< Modifies CIB cib__op_attr_privileged = (1 << 2), //!< Requires privileges cib__op_attr_local = (1 << 3), //!< Must only be processed locally cib__op_attr_replaces = (1 << 4), //!< Replaces CIB cib__op_attr_writes_through = (1 << 5), //!< Writes to disk on success cib__op_attr_transaction = (1 << 6), //!< Supported in a transaction }; /*! * \internal - * \enum cib__op_type * \brief Types of CIB operations */ enum cib__op_type { cib__op_abs_delete, cib__op_apply_patch, cib__op_bump, cib__op_commit_transact, cib__op_create, cib__op_delete, cib__op_erase, cib__op_is_primary, cib__op_modify, cib__op_noop, cib__op_ping, cib__op_primary, cib__op_query, cib__op_replace, cib__op_secondary, cib__op_shutdown, cib__op_sync_all, cib__op_sync_one, cib__op_upgrade, cib__op_schemas, }; 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); typedef int (*cib__op_fn_t)(const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); typedef struct cib__operation_s { const char *name; enum cib__op_type type; uint32_t flags; //!< Group of enum cib__op_attr flags } cib__operation_t; 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) cib_t *cib_new_variant(void); /*! * \internal * \brief Check whether a given CIB client's update should trigger a refresh * * Here, "refresh" means that Pacemaker daemons write out their current state. * * If a Pacemaker daemon or one of certain Pacemaker CLI tools modifies the CIB, * we can assume that the CIB hasn't diverged from the true cluster state. A * "safe" CLI tool requests that all relevant daemons update their state before * the tool requests any CIB modifications directly. * * In contrast, other "unsafe" tools (for example, \c cibadmin and external * tools) may request arbitrary CIB changes. * * A Pacemaker daemon can write out its current state to the CIB when it's * notified of an update from an unsafe client, to ensure the CIB still contains * the daemon's correct state. * * \param[in] name CIB client name * * \return \c true if the CIB client should trigger a refresh, or \c false * otherwise */ static inline bool cib__client_triggers_refresh(const char *name) { return (pcmk__parse_server(name) == pcmk_ipc_unknown) && !pcmk__str_any_of(name, "attrd_updater", "crm_attribute", "crm_node", "crm_resource", "crm_ticket", NULL); } int cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset); int cib_perform_op(cib_t *cib, const char *op, uint32_t call_options, cib__op_fn_t fn, bool is_query, const char *section, xmlNode *req, xmlNode *input, bool manage_counters, bool *config_changed, xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff, xmlNode **output); int cib__create_op(cib_t *cib, const char *op, const char *host, const char *section, xmlNode *data, int call_options, const char *user_name, const char *client_name, xmlNode **op_msg); int cib__extend_transaction(cib_t *cib, xmlNode *request); 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__get_operation(const char *op, const cib__operation_t **operation); 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 PCMK__CIB_REQUEST_* 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 * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, and * PCMK__CIB_REQUEST_REPLACE) * \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY) * \param[in,out] result_cib CIB copy to make changes in (used with * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, * PCMK__CIB_REQUEST_DELETE, and * PCMK__CIB_REQUEST_REPLACE) * \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY) * * \return Legacy Pacemaker return code */ int cib_process_xpath(const char *op, int options, const char *section, const xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode ** answer); 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 * * Open a read-write connection to the CIB manager if an already connected * client is not passed in. Then query the CIB and store the resulting XML. * Finally, disconnect if the CIB connection isn't being returned to the caller. * * \param[in,out] out Output object (may be \p NULL) * \param[in,out] cib If not \p NULL, where to store CIB connection * \param[out] cib_object Where to store query result * * \return Standard Pacemaker return code * * \note If \p cib is not \p NULL, the caller is responsible for freeing \p *cib * using \p cib_delete(). * \note If \p *cib points to an existing \p cib_t object, this function will * reuse it instead of creating a new one. If the existing client is * already connected, the connection will be reused, even if it's * read-only. */ int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object); int cib__signon_attempts(cib_t *cib, enum cib_conn_type type, int attempts); 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__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, 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); #ifdef __cplusplus } #endif #endif // PCMK__CRM_CIB_INTERNAL__H diff --git a/include/crm/cluster.h b/include/crm/cluster.h index b51e02d974..0834aa71b9 100644 --- a/include/crm/cluster.h +++ b/include/crm/cluster.h @@ -1,89 +1,86 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 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_CLUSTER__H #define PCMK__CRM_CLUSTER__H #include // gpointer #if SUPPORT_COROSYNC #include // cpg_callbacks_t, etc. #endif #ifdef __cplusplus extern "C" { #endif //! \internal Do not use typedef struct pcmk__cluster_private pcmk__cluster_private_t; // Implementation of pcmk_cluster_t // @COMPAT Make contents internal when we can break API backward compatibility //!@{ //! \deprecated Do not use (public access will be removed in a future release) struct pcmk__cluster { /* @COMPAT Once all members are moved to pcmk__cluster_private_t, we can * make that the pcmk_cluster_t implementation and drop this struct * altogether, leaving pcmk_cluster_t as an opaque public type. */ //! \internal Do not use pcmk__cluster_private_t *priv; // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_cluster_set_destroy_fn() to set this void (*destroy) (gpointer); #if SUPPORT_COROSYNC // NOTE: sbd (as of at least 1.5.2) uses this /*! * \deprecated Call pcmk_cpg_set_deliver_fn() and pcmk_cpg_set_confchg_fn() * to set these */ cpg_callbacks_t cpg; #endif // SUPPORT_COROSYNC }; //!@} //! Connection to a cluster layer typedef struct pcmk__cluster pcmk_cluster_t; int pcmk_cluster_connect(pcmk_cluster_t *cluster); int pcmk_cluster_disconnect(pcmk_cluster_t *cluster); pcmk_cluster_t *pcmk_cluster_new(void); void pcmk_cluster_free(pcmk_cluster_t *cluster); int pcmk_cluster_set_destroy_fn(pcmk_cluster_t *cluster, void (*fn)(gpointer)); #if SUPPORT_COROSYNC int pcmk_cpg_set_deliver_fn(pcmk_cluster_t *cluster, cpg_deliver_fn_t fn); int pcmk_cpg_set_confchg_fn(pcmk_cluster_t *cluster, cpg_confchg_fn_t fn); #endif // SUPPORT_COROSYNC -/*! - * \enum pcmk_cluster_layer - * \brief Types of cluster layer - */ +//! Types of cluster layer enum pcmk_cluster_layer { pcmk_cluster_layer_unknown = 1, //!< Unknown cluster layer pcmk_cluster_layer_invalid = 2, //!< Invalid cluster layer pcmk_cluster_layer_corosync = 32, //!< Corosync Cluster Engine }; enum pcmk_cluster_layer pcmk_get_cluster_layer(void); const char *pcmk_cluster_layer_text(enum pcmk_cluster_layer layer); #ifdef __cplusplus } #endif #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #endif // PCMK__CRM_CLUSTER__H diff --git a/include/crm/cluster/internal.h b/include/crm/cluster/internal.h index 0d0ed59f2a..bf9438d303 100644 --- a/include/crm/cluster/internal.h +++ b/include/crm/cluster/internal.h @@ -1,326 +1,324 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 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_CLUSTER_INTERNAL__H #define PCMK__CRM_CLUSTER_INTERNAL__H #include #include // uint32_t, uint64_t #include // gboolean #include // xmlNode #include // enum crm_ipc_server #include #if SUPPORT_COROSYNC #include // cpg_name, cpg_handle_t #endif #ifdef __cplusplus extern "C" { #endif // @TODO Replace this with a pcmk__node_status_flags value enum crm_proc_flag { crm_proc_none = 0x00000001, // Cluster layers crm_proc_cpg = 0x04000000, }; /*! * \internal - * \enum pcmk__node_status_flags * \brief Boolean flags for a \c pcmk__node_status_t object * * Some flags may not be related to status specifically. However, we keep these * separate from enum pcmk__node_flags because they're used with * different object types. */ enum pcmk__node_status_flags { /*! * Node is a Pacemaker Remote node and should not be considered for cluster * membership */ pcmk__node_status_remote = (UINT32_C(1) << 0), //! Node's cache entry is dirty pcmk__node_status_dirty = (UINT32_C(1) << 1), }; // Used with node cache search functions enum pcmk__node_search_flags { //! Does not affect search pcmk__node_search_none = 0, //! Search for cluster nodes from membership cache pcmk__node_search_cluster_member = (1 << 0), //! Search for remote nodes pcmk__node_search_remote = (1 << 1), //! Search for cluster member nodes and remote nodes pcmk__node_search_any = pcmk__node_search_cluster_member |pcmk__node_search_remote, //! Search for cluster nodes from CIB (as of last cache refresh) pcmk__node_search_cluster_cib = (1 << 2), }; /*! * \internal - * \enum pcmk__node_update * \brief Type of update to a \c pcmk__node_status_t object */ enum pcmk__node_update { pcmk__node_update_name, //!< Node name updated pcmk__node_update_state, //!< Node connection state updated pcmk__node_update_processes, //!< Node process group membership updated }; typedef struct pcmk__election pcmk__election_t; //! Implementation of pcmk__cluster_private_t struct pcmk__cluster_private { enum pcmk_ipc_server server; //!< Server this connection is for (if any) char *node_name; //!< Local node name at cluster layer char *node_xml_id; //!< Local node XML ID in CIB pcmk__election_t *election; //!< Election state (if election is needed) /* @TODO Corosync uses an integer node ID, but cluster layers in the * abstract do not necessarily need to */ uint32_t node_id; //!< Local node ID at cluster layer #if SUPPORT_COROSYNC /* @TODO Make these members a separate struct and use void *cluster_data * here instead, to abstract the cluster layer further. */ struct cpg_name group; //!< Corosync CPG name cpg_handle_t cpg_handle; //!< Corosync CPG handle #endif // SUPPORT_COROSYNC }; //! Node status data (may be a cluster node or a Pacemaker Remote node) typedef struct pcmk__node_status { //! Node name as known to cluster layer, or Pacemaker Remote node name char *name; /* @COMPAT This is less than ideal since the value is not a valid XML ID * (for Corosync, it's the string equivalent of the node's numeric node ID, * but XML IDs can't start with a number) and the three elements should have * different IDs. * * Ideally, we would use something like node-NODEID, node_state-NODEID, and * transient_attributes-NODEID as the element IDs. Unfortunately changing it * would be impractical due to backward compatibility; older nodes in a * rolling upgrade will always write and expect the value in the old format. */ /*! * Value of the PCMK_XA_ID XML attribute to use with the node's * PCMK_XE_NODE, PCMK_XE_NODE_STATE, and PCMK_XE_TRANSIENT_ATTRIBUTES * XML elements in the CIB */ char *xml_id; char *state; // @TODO change to enum //! Group of enum pcmk__node_status_flags uint32_t flags; /*! * Most recent cluster membership in which node was seen (0 for Pacemaker * Remote nodes) */ uint64_t membership_id; uint32_t processes; // @TODO most not needed, merge into flags /* @TODO When we can break public API compatibility, we can make the rest of * these members separate structs and use void *cluster_data and * void *user_data here instead, to abstract the cluster layer further. */ //! Arbitrary data (must be freeable by \c free()) void *user_data; char *expected; time_t peer_lost; char *conn_host; time_t when_member; // Since when node has been a cluster member time_t when_online; // Since when peer has been online in CPG /* @TODO The following are currently needed only by the Corosync stack. * Eventually consider moving them to a cluster-layer-specific data object. */ uint32_t cluster_layer_id; //!< Cluster-layer numeric node ID time_t when_lost; //!< When CPG membership was last lost } pcmk__node_status_t; /*! * \internal * \brief Return the process bit corresponding to the current cluster stack * * \return Process flag if detectable, otherwise 0 */ static inline uint32_t crm_get_cluster_proc(void) { switch (pcmk_get_cluster_layer()) { case pcmk_cluster_layer_corosync: return crm_proc_cpg; default: break; } return crm_proc_none; } /*! * \internal * \brief Get log-friendly string description of a Corosync return code * * \param[in] error Corosync return code * * \return Log-friendly string description corresponding to \p error */ static inline const char * pcmk__cs_err_str(int error) { # if SUPPORT_COROSYNC switch (error) { case CS_OK: return "OK"; case CS_ERR_LIBRARY: return "Library error"; case CS_ERR_VERSION: return "Version error"; case CS_ERR_INIT: return "Initialization error"; case CS_ERR_TIMEOUT: return "Timeout"; case CS_ERR_TRY_AGAIN: return "Try again"; case CS_ERR_INVALID_PARAM: return "Invalid parameter"; case CS_ERR_NO_MEMORY: return "No memory"; case CS_ERR_BAD_HANDLE: return "Bad handle"; case CS_ERR_BUSY: return "Busy"; case CS_ERR_ACCESS: return "Access error"; case CS_ERR_NOT_EXIST: return "Doesn't exist"; case CS_ERR_NAME_TOO_LONG: return "Name too long"; case CS_ERR_EXIST: return "Exists"; case CS_ERR_NO_SPACE: return "No space"; case CS_ERR_INTERRUPT: return "Interrupt"; case CS_ERR_NAME_NOT_FOUND: return "Name not found"; case CS_ERR_NO_RESOURCES: return "No resources"; case CS_ERR_NOT_SUPPORTED: return "Not supported"; case CS_ERR_BAD_OPERATION: return "Bad operation"; case CS_ERR_FAILED_OPERATION: return "Failed operation"; case CS_ERR_MESSAGE_ERROR: return "Message error"; case CS_ERR_QUEUE_FULL: return "Queue full"; case CS_ERR_QUEUE_NOT_AVAILABLE: return "Queue not available"; case CS_ERR_BAD_FLAGS: return "Bad flags"; case CS_ERR_TOO_BIG: return "Too big"; case CS_ERR_NO_SECTIONS: return "No sections"; } # endif return "Corosync error"; } # if SUPPORT_COROSYNC #if 0 /* This is the new way to do it, but we still support all Corosync 2 versions, * and this isn't always available. A better alternative here would be to check * for support in the configure script and enable this conditionally. */ #define pcmk__init_cmap(handle) cmap_initialize_map((handle), CMAP_MAP_ICMAP) #else #define pcmk__init_cmap(handle) cmap_initialize(handle) #endif char *pcmk__corosync_cluster_name(void); bool pcmk__corosync_add_nodes(xmlNode *xml_parent); void pcmk__cpg_confchg_cb(cpg_handle_t handle, const struct cpg_name *group_name, const struct cpg_address *member_list, size_t member_list_entries, const struct cpg_address *left_list, size_t left_list_entries, const struct cpg_address *joined_list, size_t joined_list_entries); char *pcmk__cpg_message_data(cpg_handle_t handle, uint32_t sender_id, uint32_t pid, void *content, const char **from); # endif const char *pcmk__cluster_get_xml_id(pcmk__node_status_t *node); char *pcmk__cluster_node_name(uint32_t nodeid); const char *pcmk__cluster_local_node_name(void); const char *pcmk__node_name_from_uuid(const char *uuid); pcmk__node_status_t *crm_update_peer_proc(const char *source, pcmk__node_status_t *peer, uint32_t flag, const char *status); pcmk__node_status_t *pcmk__update_peer_state(const char *source, pcmk__node_status_t *node, const char *state, uint64_t membership); void pcmk__update_peer_expected(const char *source, pcmk__node_status_t *node, const char *expected); void pcmk__reap_unseen_nodes(uint64_t ring_id); void pcmk__corosync_quorum_connect(gboolean (*dispatch)(unsigned long long, gboolean), void (*destroy) (gpointer)); bool pcmk__cluster_send_message(const pcmk__node_status_t *node, enum pcmk_ipc_server service, const xmlNode *data); // Membership extern GHashTable *pcmk__peer_cache; extern GHashTable *pcmk__remote_peer_cache; bool pcmk__cluster_has_quorum(void); void pcmk__cluster_init_node_caches(void); void pcmk__cluster_destroy_node_caches(void); void pcmk__cluster_set_autoreap(bool enable); void pcmk__cluster_set_status_callback(void (*dispatch)(enum pcmk__node_update, pcmk__node_status_t *, const void *)); bool pcmk__cluster_is_node_active(const pcmk__node_status_t *node); unsigned int pcmk__cluster_num_active_nodes(void); unsigned int pcmk__cluster_num_remote_nodes(void); pcmk__node_status_t *pcmk__cluster_lookup_remote_node(const char *node_name); void pcmk__cluster_forget_cluster_node(uint32_t id, const char *node_name); void pcmk__cluster_forget_remote_node(const char *node_name); pcmk__node_status_t *pcmk__search_node_caches(unsigned int id, const char *uname, const char *xml_id, uint32_t flags); void pcmk__purge_node_from_cache(const char *node_name, uint32_t node_id); void pcmk__refresh_node_caches_from_cib(xmlNode *cib); pcmk__node_status_t *pcmk__get_node(unsigned int id, const char *uname, const char *xml_id, uint32_t flags); #ifdef __cplusplus } #endif #endif // PCMK__CRM_CLUSTER_INTERNAL__H diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h index 4ef15b4b8d..2acf037a95 100644 --- a/include/crm/common/options_internal.h +++ b/include/crm/common/options_internal.h @@ -1,251 +1,250 @@ /* - * Copyright 2006-2024 the Pacemaker project contributors + * Copyright 2006-2025 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_COMMON_OPTIONS_INTERNAL__H #define PCMK__CRM_COMMON_OPTIONS_INTERNAL__H #ifndef PCMK__CONFIG_H #define PCMK__CONFIG_H #include // _Noreturn #endif #include // GHashTable #include // bool #include // pcmk_parse_interval_spec() #include // pcmk__output_t #ifdef __cplusplus extern "C" { #endif _Noreturn void pcmk__cli_help(char cmd); /* * Environment variable option handling */ const char *pcmk__env_option(const char *option); void pcmk__set_env_option(const char *option, const char *value, bool compat); bool pcmk__env_option_enabled(const char *daemon, const char *option); /* * Cluster option handling */ /*! * \internal - * \enum pcmk__opt_flags * \brief Option flags */ enum pcmk__opt_flags { pcmk__opt_none = 0U, //!< No additional information /*! * \brief In CIB manager metadata * * \deprecated This flag will be removed with CIB manager metadata */ pcmk__opt_based = (1U << 0), /*! * \brief In controller metadata * * \deprecated This flag will be removed with controller metadata */ pcmk__opt_controld = (1U << 1), /*! * \brief In scheduler metadata * * \deprecated This flag will be removed with scheduler metadata */ pcmk__opt_schedulerd = (1U << 2), pcmk__opt_advanced = (1U << 3), //!< Advanced use only pcmk__opt_generated = (1U << 4), //!< Generated by Pacemaker pcmk__opt_deprecated = (1U << 5), //!< Option is deprecated pcmk__opt_fencing = (1U << 6), //!< Common fencing resource parameter pcmk__opt_primitive = (1U << 7), //!< Primitive resource meta-attribute }; typedef struct pcmk__cluster_option_s { const char *name; const char *alt_name; const char *type; const char *values; const char *default_value; bool (*is_valid)(const char *); uint32_t flags; //!< Group of enum pcmk__opt_flags const char *description_short; const char *description_long; } pcmk__cluster_option_t; const char *pcmk__cluster_option(GHashTable *options, const char *name); int pcmk__output_cluster_options(pcmk__output_t *out, const char *name, const char *desc_short, const char *desc_long, uint32_t filter, bool all); int pcmk__output_fencing_params(pcmk__output_t *out, const char *name, const char *desc_short, const char *desc_long, bool all); int pcmk__output_primitive_meta(pcmk__output_t *out, const char *name, const char *desc_short, const char *desc_long, bool all); int pcmk__daemon_metadata(pcmk__output_t *out, const char *name, const char *short_desc, const char *long_desc, enum pcmk__opt_flags filter); void pcmk__validate_cluster_options(GHashTable *options); bool pcmk__valid_interval_spec(const char *value); bool pcmk__valid_boolean(const char *value); bool pcmk__valid_int(const char *value); bool pcmk__valid_positive_int(const char *value); bool pcmk__valid_no_quorum_policy(const char *value); bool pcmk__valid_percentage(const char *value); bool pcmk__valid_placement_strategy(const char *value); // from watchdog.c long pcmk__get_sbd_watchdog_timeout(void); bool pcmk__get_sbd_sync_resource_startup(void); long pcmk__auto_stonith_watchdog_timeout(void); bool pcmk__valid_stonith_watchdog_timeout(const char *value); // Constants for environment variable names #define PCMK__ENV_AUTHKEY_LOCATION "authkey_location" #define PCMK__ENV_BLACKBOX "blackbox" #define PCMK__ENV_CA_FILE "ca_file" #define PCMK__ENV_CALLGRIND_ENABLED "callgrind_enabled" #define PCMK__ENV_CERT_FILE "cert_file" #define PCMK__ENV_CLUSTER_TYPE "cluster_type" #define PCMK__ENV_CRL_FILE "crl_file" #define PCMK__ENV_DEBUG "debug" #define PCMK__ENV_DH_MAX_BITS "dh_max_bits" #define PCMK__ENV_FAIL_FAST "fail_fast" #define PCMK__ENV_IPC_BUFFER "ipc_buffer" #define PCMK__ENV_IPC_TYPE "ipc_type" #define PCMK__ENV_KEY_FILE "key_file" #define PCMK__ENV_LOGFACILITY "logfacility" #define PCMK__ENV_LOGFILE "logfile" #define PCMK__ENV_LOGFILE_MODE "logfile_mode" #define PCMK__ENV_LOGPRIORITY "logpriority" #define PCMK__ENV_NODE_ACTION_LIMIT "node_action_limit" #define PCMK__ENV_NODE_START_STATE "node_start_state" #define PCMK__ENV_PANIC_ACTION "panic_action" #define PCMK__ENV_REMOTE_ADDRESS "remote_address" #define PCMK__ENV_REMOTE_SCHEMA_DIRECTORY "remote_schema_directory" #define PCMK__ENV_REMOTE_PID1 "remote_pid1" #define PCMK__ENV_REMOTE_PORT "remote_port" #define PCMK__ENV_RESPAWNED "respawned" #define PCMK__ENV_SCHEMA_DIRECTORY "schema_directory" #define PCMK__ENV_SERVICE "service" #define PCMK__ENV_STDERR "stderr" #define PCMK__ENV_TLS_PRIORITIES "tls_priorities" #define PCMK__ENV_TRACE_BLACKBOX "trace_blackbox" #define PCMK__ENV_TRACE_FILES "trace_files" #define PCMK__ENV_TRACE_FORMATS "trace_formats" #define PCMK__ENV_TRACE_FUNCTIONS "trace_functions" #define PCMK__ENV_TRACE_TAGS "trace_tags" #define PCMK__ENV_VALGRIND_ENABLED "valgrind_enabled" // Constants for meta-attribute names #define PCMK__META_CLONE "clone" #define PCMK__META_CONTAINER "container" #define PCMK__META_DIGESTS_ALL "digests-all" #define PCMK__META_DIGESTS_SECURE "digests-secure" #define PCMK__META_INTERNAL_RSC "internal_rsc" #define PCMK__META_MIGRATE_SOURCE "migrate_source" #define PCMK__META_MIGRATE_TARGET "migrate_target" #define PCMK__META_ON_NODE "on_node" #define PCMK__META_ON_NODE_UUID "on_node_uuid" #define PCMK__META_OP_NO_WAIT "op_no_wait" #define PCMK__META_OP_TARGET_RC "op_target_rc" #define PCMK__META_PHYSICAL_HOST "physical-host" #define PCMK__META_STONITH_ACTION "stonith_action" /* @TODO Plug these in. Currently, they're never set. These are op attrs for use * with https://projects.clusterlabs.org/T382. */ #define PCMK__META_CLEAR_FAILURE_OP "clear_failure_op" #define PCMK__META_CLEAR_FAILURE_INTERVAL "clear_failure_interval" // @COMPAT Deprecated alias for PCMK__META_PROMOTED_MAX since 2.0.0 #define PCMK__META_PROMOTED_MAX_LEGACY "master-max" // @COMPAT Deprecated alias for PCMK__META_PROMOTED_NODE_MAX since 2.0.0 #define PCMK__META_PROMOTED_NODE_MAX_LEGACY "master-node-max" // Constants for enumerated values #define PCMK__VALUE_ATTRD "attrd" #define PCMK__VALUE_BOLD "bold" #define PCMK__VALUE_BROADCAST "broadcast" #define PCMK__VALUE_CIB "cib" #define PCMK__VALUE_CIB_DIFF_NOTIFY "cib_diff_notify" #define PCMK__VALUE_CIB_NOTIFY "cib_notify" #define PCMK__VALUE_CIB_POST_NOTIFY "cib_post_notify" #define PCMK__VALUE_CIB_PRE_NOTIFY "cib_pre_notify" #define PCMK__VALUE_CIB_UPDATE_CONFIRMATION "cib_update_confirmation" #define PCMK__VALUE_CLUSTER "cluster" #define PCMK__VALUE_CRMD "crmd" #define PCMK__VALUE_EN "en" #define PCMK__VALUE_EPOCH "epoch" #define PCMK__VALUE_HEALTH_RED "health_red" #define PCMK__VALUE_HEALTH_YELLOW "health_yellow" #define PCMK__VALUE_INIT "init" #define PCMK__VALUE_LOCAL "local" #define PCMK__VALUE_LOST "lost" #define PCMK__VALUE_LRMD "lrmd" #define PCMK__VALUE_MAINT "maint" #define PCMK__VALUE_OUTPUT "output" #define PCMK__VALUE_PASSWORD "password" #define PCMK__VALUE_PRIMITIVE "primitive" #define PCMK__VALUE_REFRESH "refresh" #define PCMK__VALUE_REQUEST "request" #define PCMK__VALUE_RESPONSE "response" #define PCMK__VALUE_RSC_FAILED "rsc-failed" #define PCMK__VALUE_RSC_FAILURE_IGNORED "rsc-failure-ignored" #define PCMK__VALUE_RSC_MANAGED "rsc-managed" #define PCMK__VALUE_RSC_MULTIPLE "rsc-multiple" #define PCMK__VALUE_RSC_OK "rsc-ok" #define PCMK__VALUE_RUNNING "running" #define PCMK__VALUE_SCHEDULER "scheduler" #define PCMK__VALUE_SHUTDOWN_COMPLETE "shutdown_complete" #define PCMK__VALUE_SHUTTING_DOWN "shutting_down" #define PCMK__VALUE_ST_ASYNC_TIMEOUT_VALUE "st-async-timeout-value" #define PCMK__VALUE_ST_NOTIFY "st_notify" #define PCMK__VALUE_ST_NOTIFY_DISCONNECT "st_notify_disconnect" #define PCMK__VALUE_ST_NOTIFY_FENCE "st_notify_fence" #define PCMK__VALUE_ST_NOTIFY_HISTORY "st_notify_history" #define PCMK__VALUE_ST_NOTIFY_HISTORY_SYNCED "st_notify_history_synced" #define PCMK__VALUE_STARTING_DAEMONS "starting_daemons" #define PCMK__VALUE_STONITH_NG "stonith-ng" #define PCMK__VALUE_WAIT_FOR_PING "wait_for_ping" #define PCMK__VALUE_WARNING "warning" /* @COMPAT Deprecated since 2.1.7 (used with PCMK__XA_ORDERING attribute of * resource sets) */ #define PCMK__VALUE_GROUP "group" // @COMPAT Drop when daemon metadata commands are dropped #define PCMK__VALUE_TIME "time" #ifdef __cplusplus } #endif #endif // PCMK__OPTIONS_INTERNAL__H diff --git a/include/crm/common/results.h b/include/crm/common/results.h index 60a88ddbc0..7525282c3f 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,408 +1,403 @@ /* * Copyright 2012-2025 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_COMMON_RESULTS__H #define PCMK__CRM_COMMON_RESULTS__H #include // gboolean #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Function and executable result codes * \ingroup core */ // Lifted from config.h /* The _Noreturn keyword of C11. */ #ifndef _Noreturn # if (defined __cplusplus \ && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \ || (defined _MSC_VER && 1900 <= _MSC_VER))) # define _Noreturn [[noreturn]] # elif ((!defined __cplusplus || defined __clang__) \ && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \ || 4 < __GNUC__ + (7 <= __GNUC_MINOR__))) /* _Noreturn works as-is. */ # elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C # define _Noreturn __attribute__ ((__noreturn__)) # elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0) # define _Noreturn __declspec (noreturn) # else # define _Noreturn # endif #endif /* * Function return codes * * Most Pacemaker API functions return an integer return code. There are two * alternative interpretations. The legacy interpration is that the absolute * value of the return code is either a system error number or a custom * pcmk_err_* number. This is less than ideal because system error numbers are * constrained only to the positive int range, so there's the possibility that * system errors and custom errors could collide (which did in fact happen * already on one architecture). The new intepretation is that negative values * are from the pcmk_rc_e enum, and positive values are system error numbers. * Both use 0 for success. * * For system error codes, see: * - /usr/include/asm-generic/errno.h * - /usr/include/asm-generic/errno-base.h */ // Legacy custom return codes for Pacemaker API functions (deprecated) // NOTE: sbd (as of at least 1.5.2) uses this #define pcmk_ok 0 #define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */ #define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */ #define pcmk_err_generic 201 #define pcmk_err_no_quorum 202 #define pcmk_err_schema_validation 203 #define pcmk_err_transform_failed 204 #define pcmk_err_old_data 205 // NOTE: sbd (as of at least 1.5.2) uses this #define pcmk_err_diff_failed 206 // NOTE: sbd (as of at least 1.5.2) uses this #define pcmk_err_diff_resync 207 #define pcmk_err_cib_modified 208 #define pcmk_err_cib_backup 209 #define pcmk_err_cib_save 210 #define pcmk_err_schema_unchanged 211 #define pcmk_err_cib_corrupt 212 #define pcmk_err_multiple 213 #define pcmk_err_node_unknown 214 #define pcmk_err_already 215 /* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */ #ifdef __hppa__ #define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */ #define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */ #else #define pcmk_err_bad_nvpair 216 #define pcmk_err_unknown_format 217 #endif /*! - * \enum pcmk_rc_e * \brief Return codes for Pacemaker API functions * * Any Pacemaker API function documented as returning a "standard Pacemaker * return code" will return pcmk_rc_ok (0) on success, and one of this * enumeration's other (negative) values or a (positive) system error number * otherwise. The custom codes are at -1001 and lower, so that the caller may * use -1 through -1000 for their own custom values if desired. While generally * referred to as "errors", nonzero values simply indicate a result, which might * or might not be an error depending on the calling context. */ enum pcmk_rc_e { /* When adding new values, use consecutively lower numbers, update the array * in lib/common/results.c, and test with crm_error. */ pcmk_rc_no_dc = -1040, pcmk_rc_compression = -1039, pcmk_rc_ns_resolution = -1038, pcmk_rc_no_transaction = -1037, pcmk_rc_bad_xml_patch = -1036, pcmk_rc_bad_input = -1035, pcmk_rc_disabled = -1034, pcmk_rc_duplicate_id = -1033, pcmk_rc_unpack_error = -1032, pcmk_rc_invalid_transition = -1031, pcmk_rc_graph_error = -1030, pcmk_rc_dot_error = -1029, pcmk_rc_underflow = -1028, pcmk_rc_no_input = -1027, pcmk_rc_no_output = -1026, pcmk_rc_after_range = -1025, pcmk_rc_within_range = -1024, pcmk_rc_before_range = -1023, pcmk_rc_undetermined = -1022, pcmk_rc_op_unsatisfied = -1021, pcmk_rc_ipc_pid_only = -1020, pcmk_rc_ipc_unresponsive = -1019, pcmk_rc_ipc_unauthorized = -1018, pcmk_rc_no_quorum = -1017, pcmk_rc_schema_validation = -1016, pcmk_rc_schema_unchanged = -1015, pcmk_rc_transform_failed = -1014, pcmk_rc_old_data = -1013, pcmk_rc_diff_failed = -1012, pcmk_rc_diff_resync = -1011, pcmk_rc_cib_modified = -1010, pcmk_rc_cib_backup = -1009, pcmk_rc_cib_save = -1008, pcmk_rc_cib_corrupt = -1007, pcmk_rc_multiple = -1006, pcmk_rc_node_unknown = -1005, pcmk_rc_already = -1004, pcmk_rc_bad_nvpair = -1003, pcmk_rc_unknown_format = -1002, // Developers: Use a more specific code than pcmk_rc_error whenever possible pcmk_rc_error = -1001, // Values -1 through -1000 reserved for caller use // NOTE: sbd (as of at least 1.5.2) uses this pcmk_rc_ok = 0 // Positive values reserved for system error numbers }; /*! - * \enum ocf_exitcode * \brief Exit status codes for resource agents * * The OCF Resource Agent API standard enumerates the possible exit status codes * that agents should return. Besides being used with OCF agents, these values * are also used by the executor as a universal status for all agent standards; * actual results are mapped to these before returning them to clients. */ enum ocf_exitcode { PCMK_OCF_OK = 0, //!< Success // NOTE: booth (as of at least 1.1) uses this value PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context) PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently) // NOTE: booth (as of at least 1.1) uses this value PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon /* These two are Pacemaker extensions, not in the OCF standard. The * controller records PCMK_OCF_UNKNOWN for pending actions. * PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support * PCMK_EXEC_NOT_CONNECTED. */ PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED PCMK_OCF_UNKNOWN = 193, //!< Action is pending }; // NOTE: sbd (as of at least 1.5.2) uses this /*! - * \enum crm_exit_e * \brief Exit status codes for tools and daemons * * We want well-specified (i.e. OS-invariant) exit status codes for our daemons * and applications so they can be relied on by callers. (Function return codes * and errno's do not make good exit statuses.) * * The only hard rule is that exit statuses must be between 0 and 255; all else * is convention. Universally, 0 is success, and 1 is generic error (excluding * OSes we don't support -- for example, OpenVMS considers 1 success!). * * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for * application use. OCF adds 8-9 and 190-191. * * sysexits.h was an attempt to give additional meanings, but never really * caught on. It uses 0 and 64-78. * * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command * found but not executable", 127 is "command not found", 128 + n is * "interrupted by signal n"). * * tldp.org recommends 64-113 for application use. * * We try to overlap with the above conventions when practical. * * NOTE: When new exit codes are added here, remember to also update * python/pacemaker/exitstatus.py. */ typedef enum crm_exit_e { // Common convention CRM_EX_OK = 0, //!< Success CRM_EX_ERROR = 1, //!< Unspecified error // LSB + OCF CRM_EX_INVALID_PARAM = 2, //!< Parameter invalid (in local context) CRM_EX_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented CRM_EX_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges CRM_EX_NOT_INSTALLED = 5, //!< Dependencies not available locally CRM_EX_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently) CRM_EX_NOT_RUNNING = 7, //!< Service safely stopped CRM_EX_PROMOTED = 8, //!< Service active and promoted CRM_EX_FAILED_PROMOTED = 9, //!< Service failed and possibly promoted // sysexits.h CRM_EX_USAGE = 64, //!< Command line usage error CRM_EX_DATAERR = 65, //!< User-supplied data incorrect CRM_EX_NOINPUT = 66, //!< Input file not available CRM_EX_NOUSER = 67, //!< User does not exist CRM_EX_NOHOST = 68, //!< Host unknown CRM_EX_UNAVAILABLE = 69, //!< Needed service unavailable CRM_EX_SOFTWARE = 70, //!< Internal software bug CRM_EX_OSERR = 71, //!< External (OS/environmental) problem CRM_EX_OSFILE = 72, //!< System file not usable CRM_EX_CANTCREAT = 73, //!< File couldn't be created CRM_EX_IOERR = 74, //!< File I/O error CRM_EX_TEMPFAIL = 75, //!< Try again CRM_EX_PROTOCOL = 76, //!< Protocol violated CRM_EX_NOPERM = 77, //!< Non-file permission issue CRM_EX_CONFIG = 78, //!< Misconfiguration // Custom CRM_EX_FATAL = 100, //!< Do not respawn CRM_EX_PANIC = 101, //!< Panic the local host CRM_EX_DISCONNECT = 102, //!< Lost connection to something CRM_EX_OLD = 103, //!< Update older than existing config CRM_EX_DIGEST = 104, //!< Digest comparison failed CRM_EX_NOSUCH = 105, //!< Requested item does not exist CRM_EX_QUORUM = 106, //!< Local partition does not have quorum CRM_EX_UNSAFE = 107, //!< Requires --force or new conditions CRM_EX_EXISTS = 108, //!< Requested item already exists CRM_EX_MULTIPLE = 109, //!< Requested item has multiple matches CRM_EX_EXPIRED = 110, //!< Requested item has expired CRM_EX_NOT_YET_IN_EFFECT = 111, //!< Requested item is not in effect CRM_EX_INDETERMINATE = 112, //!< Could not determine status CRM_EX_UNSATISFIED = 113, //!< Requested item does not satisfy constraints CRM_EX_NO_DC = 114, //!< DC is not yet elected, e.g. right after cluster restart // Other CRM_EX_TIMEOUT = 124, //!< Convention from timeout(1) /* Anything above 128 overlaps with some shells' use of these values for * "interrupted by signal N", and so may be unreliable when detected by * shell scripts. */ // OCF Resource Agent API 1.1 CRM_EX_DEGRADED = 190, //!< Service active but more likely to fail soon CRM_EX_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon /* Custom * * This can be used to initialize exit status variables or to indicate that * a command is pending (which is what the controller uses it for). */ CRM_EX_NONE = 193, //!< No exit status available CRM_EX_MAX = 255, //!< Ensure crm_exit_t can hold this } crm_exit_t; /*! - * \enum pcmk_exec_status * \brief Execution status * * These codes are used to specify the result of the attempt to execute an * agent, rather than the agent's result itself. */ enum pcmk_exec_status { PCMK_EXEC_UNKNOWN = -2, //!< Used only to initialize variables PCMK_EXEC_PENDING = -1, //!< Action is in progress PCMK_EXEC_DONE, //!< Action completed, result is known PCMK_EXEC_CANCELLED, //!< Action was cancelled PCMK_EXEC_TIMEOUT, //!< Action did not complete in time PCMK_EXEC_NOT_SUPPORTED, //!< Agent does not implement requested action PCMK_EXEC_ERROR, //!< Execution failed, may be retried PCMK_EXEC_ERROR_HARD, //!< Execution failed, do not retry on node PCMK_EXEC_ERROR_FATAL, //!< Execution failed, do not retry anywhere PCMK_EXEC_NOT_INSTALLED, //!< Agent or dependency not available locally PCMK_EXEC_NOT_CONNECTED, //!< No connection to executor PCMK_EXEC_INVALID, //!< Action cannot be attempted (e.g. shutdown) PCMK_EXEC_NO_FENCE_DEVICE, //!< No fence device is configured for target PCMK_EXEC_NO_SECRETS, //!< Necessary CIB secrets are unavailable // Add new values above here then update this one below PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum }; /*! - * \enum pcmk_result_type * \brief Types of Pacemaker result codes * * A particular integer can have different meanings within different Pacemaker * result code families. It may be interpretable within zero, one, or multiple * families. * * These values are useful for specifying how an integer result code should be * interpreted in situations involving a generic integer value. For example, a * function that can process multiple types of result codes might accept an * arbitrary integer argument along with a \p pcmk_result_type argument that * specifies how to interpret the integer. */ enum pcmk_result_type { pcmk_result_legacy = 0, //!< Legacy API function return code pcmk_result_rc = 1, //!< Standard Pacemaker return code pcmk_result_exitcode = 2, //!< Exit status code pcmk_result_exec_status = 3, //!< Execution status }; int pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name, const char **desc); const char *pcmk_rc_name(int rc); // NOTE: sbd (as of at least 1.5.2) uses this const char *pcmk_rc_str(int rc); crm_exit_t pcmk_rc2exitc(int rc); enum ocf_exitcode pcmk_rc2ocf(int rc); int pcmk_rc2legacy(int rc); int pcmk_legacy2rc(int legacy_rc); // NOTE: sbd (as of at least 1.5.2) uses this const char *pcmk_strerror(int rc); const char *pcmk_errorname(int rc); const char *crm_exit_name(crm_exit_t exit_code); // NOTE: sbd (as of at least 1.5.2) uses this const char *crm_exit_str(crm_exit_t exit_code); _Noreturn crm_exit_t crm_exit(crm_exit_t rc); /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork); static inline const char * pcmk_exec_status_str(enum pcmk_exec_status status) { switch (status) { case PCMK_EXEC_PENDING: return "Pending"; case PCMK_EXEC_DONE: return "Done"; case PCMK_EXEC_CANCELLED: return "Cancelled"; case PCMK_EXEC_TIMEOUT: return "Timed out"; case PCMK_EXEC_NOT_SUPPORTED: return "Unsupported"; case PCMK_EXEC_ERROR: return "Error"; case PCMK_EXEC_ERROR_HARD: return "Hard error"; case PCMK_EXEC_ERROR_FATAL: return "Fatal error"; case PCMK_EXEC_NOT_INSTALLED: return "Not installed"; case PCMK_EXEC_NOT_CONNECTED: return "Internal communication failure"; case PCMK_EXEC_INVALID: return "Cannot execute now"; case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device"; case PCMK_EXEC_NO_SECRETS: return "CIB secrets unavailable"; default: return "Unrecognized status (bug?)"; } } #ifdef __cplusplus } #endif #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #endif diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index 72eae57ef5..c03292551a 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -1,456 +1,454 @@ /* - * Copyright 2017-2024 the Pacemaker project contributors + * Copyright 2017-2025 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_COMMON_XML_INTERNAL__H #define PCMK__CRM_COMMON_XML_INTERNAL__H /* * Internal-only wrappers for and extensions to libxml2 (libxslt) */ #include #include // uint32_t #include #include /* transitively imports qblog.h */ #include #include // PCMK_XA_ID, PCMK_XE_CLONE // This file is a wrapper for other xml_*_internal.h headers #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*! * \brief Base for directing lib{xml2,xslt} log into standard libqb backend * * This macro implements the core of what can be needed for directing * libxml2 or libxslt error messaging into standard, preconfigured * libqb-backed log stream. * * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt) * emits a single message by chunks (location is emitted separatedly from * the message itself), so we have to take the effort to combine these * chunks back to single message. Whether to do this or not is driven * with \p dechunk toggle. * * The form of a macro was chosen for implicit deriving of __FILE__, etc. * and also because static dechunking buffer should be differentiated per * library (here we assume different functions referring to this macro * will not ever be using both at once), preferably also per-library * context of use to avoid clashes altogether. * * Note that we cannot use qb_logt, because callsite data have to be known * at the moment of compilation, which it is not always the case -- xml_log * (and unfortunately there's no clear explanation of the fail to compile). * * Also note that there's no explicit guard against said libraries producing * never-newline-terminated chunks (which would just keep consuming memory), * as it's quite improbable. Termination of the program in between the * same-message chunks will raise a flag with valgrind and the likes, though. * * And lastly, regarding how dechunking combines with other non-message * parameters -- for \p priority, most important running specification * wins (possibly elevated to LOG_ERR in case of nonconformance with the * newline-termination "protocol"), \p dechunk is expected to always be * on once it was at the start, and the rest (\p postemit and \p prefix) * are picked directly from the last chunk entry finalizing the message * (also reasonable to always have it the same with all related entries). * * \param[in] priority Syslog priority for the message to be logged * \param[in] dechunk Whether to dechunk new-line terminated message * \param[in] postemit Code to be executed once message is sent out * \param[in] prefix How to prefix the message or NULL for raw passing * \param[in] fmt Format string as with printf-like functions * \param[in] ap Variable argument list to supplement \p fmt format string */ #define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \ do { \ if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \ qb_log_from_external_source_va(__func__, __FILE__, (fmt), \ (priority), __LINE__, 0, (ap)); \ (void) (postemit); \ } else { \ int CXLB_len = 0; \ char *CXLB_buf = NULL; \ static int CXLB_buffer_len = 0; \ static char *CXLB_buffer = NULL; \ static uint8_t CXLB_priority = 0; \ \ CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \ \ if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \ if (CXLB_len < 0) { \ CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\ CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \ } else if (CXLB_len > 0 /* && (dechunk) */ \ && CXLB_buf[CXLB_len - 1] == '\n') { \ CXLB_buf[CXLB_len - 1] = '\0'; \ } \ if (CXLB_buffer) { \ qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \ CXLB_priority, __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buffer, CXLB_buf); \ free(CXLB_buffer); \ } else { \ qb_log_from_external_source(__func__, __FILE__, "%s%s", \ (priority), __LINE__, 0, \ (prefix) != NULL ? (prefix) : "", \ CXLB_buf); \ } \ if (CXLB_len < 0) { \ CXLB_buf = NULL; /* restore temporary override */ \ } \ CXLB_buffer = NULL; \ CXLB_buffer_len = 0; \ (void) (postemit); \ \ } else if (CXLB_buffer == NULL) { \ CXLB_buffer_len = CXLB_len; \ CXLB_buffer = CXLB_buf; \ CXLB_buf = NULL; \ CXLB_priority = (priority); /* remember as a running severest */ \ \ } else { \ CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \ memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \ CXLB_buffer_len += CXLB_len; \ CXLB_buffer[CXLB_buffer_len] = '\0'; \ CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \ } \ free(CXLB_buf); \ } \ } while (0) -/* - * \enum pcmk__xml_fmt_options +/*! + * \internal * \brief Bit flags to control format in XML logs and dumps */ enum pcmk__xml_fmt_options { //! Exclude certain XML attributes (for calculating digests) pcmk__xml_fmt_filtered = (1 << 0), //! Include indentation and newlines pcmk__xml_fmt_pretty = (1 << 1), //! Include the opening tag of an XML element, and include XML comments pcmk__xml_fmt_open = (1 << 3), //! Include the children of an XML element pcmk__xml_fmt_children = (1 << 4), //! Include the closing tag of an XML element pcmk__xml_fmt_close = (1 << 5), // @COMPAT Can we start including text nodes unconditionally? //! Include XML text nodes pcmk__xml_fmt_text = (1 << 6), }; void pcmk__xml_init(void); void pcmk__xml_cleanup(void); int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options); int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml); /* XML search strings for guest, remote and pacemaker_remote nodes */ /* search string to find CIB resources entries for cluster nodes */ #define PCMK__XP_MEMBER_NODE_CONFIG \ "//" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_NODES \ "/" PCMK_XE_NODE \ "[not(@" PCMK_XA_TYPE ") or @" PCMK_XA_TYPE "='" PCMK_VALUE_MEMBER "']" /* search string to find CIB resources entries for guest nodes */ #define PCMK__XP_GUEST_NODE_CONFIG \ "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ "//" PCMK_XE_META_ATTRIBUTES "//" PCMK_XE_NVPAIR \ "[@" PCMK_XA_NAME "='" PCMK_META_REMOTE_NODE "']" /* search string to find CIB resources entries for remote nodes */ #define PCMK__XP_REMOTE_NODE_CONFIG \ "//" PCMK_XE_CIB "//" PCMK_XE_CONFIGURATION "//" PCMK_XE_PRIMITIVE \ "[@" PCMK_XA_TYPE "='" PCMK_VALUE_REMOTE "']" \ "[@" PCMK_XA_PROVIDER "='pacemaker']" /* search string to find CIB node status entries for pacemaker_remote nodes */ #define PCMK__XP_REMOTE_NODE_STATUS \ "//" PCMK_XE_CIB "//" PCMK_XE_STATUS "//" PCMK__XE_NODE_STATE \ "[@" PCMK_XA_REMOTE_NODE "='" PCMK_VALUE_TRUE "']" enum pcmk__xml_artefact_ns { pcmk__xml_artefact_ns_legacy_rng = 1, pcmk__xml_artefact_ns_legacy_xslt, pcmk__xml_artefact_ns_base_rng, pcmk__xml_artefact_ns_base_xslt, }; void pcmk__strip_xml_text(xmlNode *xml); GString *pcmk__element_xpath(const xmlNode *xml); /*! * \internal - * \enum pcmk__xml_escape_type * \brief Indicators of which XML characters to escape * * XML allows the escaping of special characters by replacing them with entity * references (for example, """) or character references (for * example, " "). * * The special characters '&' (except as the beginning of an entity * reference) and '<' are not allowed in their literal forms in XML * character data. Character data is non-markup text (for example, the content * of a text node). '>' is allowed under most circumstances; we escape * it for safety and symmetry. * * For more details, see the "Character Data and Markup" section of the XML * spec, currently section 2.4: * https://www.w3.org/TR/xml/#dt-markup * * Attribute values are handled specially. * * If an attribute value is delimited by single quotes, then single quotes * must be escaped within the value. * * Similarly, if an attribute value is delimited by double quotes, then double * quotes must be escaped within the value. * * A conformant XML processor replaces a literal whitespace character (tab, * newline, carriage return, space) in an attribute value with a space * (\c '#x20') character. However, a reference to a whitespace character (for * example, \c " " for \c '\n') does not get replaced. * * For more details, see the "Attribute-Value Normalization" section of the * XML spec, currently section 3.3.3. Note that the default attribute type * is CDATA; we don't deal with NMTOKENS, etc.: * https://www.w3.org/TR/xml/#AVNormalize * * Pacemaker always delimits attribute values with double quotes, so there's no * need to escape single quotes. * * Newlines and tabs should be escaped in attribute values when XML is * serialized to text, so that future parsing preserves them rather than * normalizing them to spaces. * * We always escape carriage returns, so that they're not converted to spaces * during attribute-value normalization and because displaying them as literals * is messy. */ enum pcmk__xml_escape_type { /*! * For text nodes. * * Escape \c '<', \c '>', and \c '&' using entity references. * * Do not escape \c '\n' and \c '\t'. * * Escape other non-printing characters using character references. */ pcmk__xml_escape_text, /*! * For attribute values. * * Escape \c '<', \c '>', \c '&', and \c '"' using entity references. * * Escape \c '\n', \c '\t', and other non-printing characters using * character references. */ pcmk__xml_escape_attr, /* @COMPAT Drop escaping of at least '\n' and '\t' for * pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip, * and openstack-virtual-ip resource agents no longer depend on it. * * At time of writing, openstack-info may set a multiline value for the * openstack_ports node attribute. The other two agents query the value and * require it to be on one line with no spaces. */ /*! * For attribute values displayed in text output delimited by double quotes. * * Escape \c '\n' as \c "\\n" * * Escape \c '\r' as \c "\\r" * * Escape \c '\t' as \c "\\t" * * Escape \c '"' as \c "\\"" */ pcmk__xml_escape_attr_pretty, }; bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type); char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type); /*! * \internal * \brief Get the root directory to scan XML artefacts of given kind for * * \param[in] ns governs the hierarchy nesting against the inherent root dir * * \return root directory to scan XML artefacts of given kind for */ char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); /*! * \internal * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) * * \param[in] ns denotes path forming details (parent dir, suffix) * \param[in] filespec symbolic file specification to be combined with * #artefact_ns to form the final path * \return unwrapped path to particular XML artifact (RNG/XSLT) */ char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec); /*! * \internal * \brief Return first non-text child node of an XML node * * \param[in] parent XML node to check * * \return First non-text child node of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xml_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type == XML_TEXT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling node of an XML node * * \param[in] child XML node to check * * \return Next non-text sibling of \p child (or NULL if none) */ static inline xmlNode * pcmk__xml_next(const xmlNode *child) { xmlNode *next = (child? child->next : NULL); while (next && (next->type == XML_TEXT_NODE)) { next = next->next; } return next; } void pcmk__xml_free(xmlNode *xml); void pcmk__xml_free_doc(xmlDoc *doc); xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src); /*! * \internal - * \enum pcmk__xa_flags * \brief Flags for operations affecting XML attributes */ enum pcmk__xa_flags { //! Flag has no effect pcmk__xaf_none = 0U, //! Don't overwrite existing values pcmk__xaf_no_overwrite = (1U << 0), /*! * Treat values as score updates where possible (see * \c pcmk__xe_set_score()) */ pcmk__xaf_score_update = (1U << 1), }; void pcmk__xml_sanitize_id(char *id); /*! * \internal * \brief Extract the ID attribute from an XML element * * \param[in] xpath String to search * \param[in] node Node to get the ID for * * \return ID attribute of \p node in xpath string \p xpath */ char * pcmk__xpath_node_id(const char *xpath, const char *node); /*! * \internal * \brief Print an informational message if an xpath query returned multiple * items with the same ID. * * \param[in,out] out The output object * \param[in] search The xpath search result, most typically the result of * calling cib->cmds->query(). * \param[in] name The name searched for */ void pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search, const char *name); /* internal XML-related utilities */ enum xml_private_flags { pcmk__xf_none = 0x0000, pcmk__xf_dirty = 0x0001, pcmk__xf_deleted = 0x0002, pcmk__xf_created = 0x0004, pcmk__xf_modified = 0x0008, pcmk__xf_tracking = 0x0010, pcmk__xf_processed = 0x0020, pcmk__xf_skip = 0x0040, pcmk__xf_moved = 0x0080, pcmk__xf_acl_enabled = 0x0100, pcmk__xf_acl_read = 0x0200, pcmk__xf_acl_write = 0x0400, pcmk__xf_acl_deny = 0x0800, pcmk__xf_acl_create = 0x1000, pcmk__xf_acl_denied = 0x2000, pcmk__xf_lazy = 0x4000, }; void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *), void *user_data); static inline const char * pcmk__xml_attr_value(const xmlAttr *attr) { return ((attr == NULL) || (attr->children == NULL))? NULL : (const char *) attr->children->content; } /*! * \internal * \brief Check whether a given CIB element was modified in a CIB patchset * * \param[in] patchset CIB XML patchset * \param[in] element XML tag of CIB element to check (\c NULL is equivalent * to \c PCMK_XE_CIB). Supported values include any CIB * element supported by \c pcmk__cib_abs_xpath_for(). * * \return \c true if \p element was modified, or \c false otherwise */ bool pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element); #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_XML_INTERNAL__H diff --git a/include/pacemaker.h b/include/pacemaker.h index e9d39f6a09..cac2e21765 100644 --- a/include/pacemaker.h +++ b/include/pacemaker.h @@ -1,701 +1,698 @@ /* * Copyright 2019-2025 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__PACEMAKER__H # define PCMK__PACEMAKER__H # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief High Level API * \ingroup pacemaker */ /*! * \brief Modify operation of running a cluster simulation. */ enum pcmk_sim_flags { // @COMPAT Use UINT32_C(1); should not affect behavior pcmk_sim_none = 0, pcmk_sim_all_actions = 1 << 0, pcmk_sim_show_pending = 1 << 1, pcmk_sim_process = 1 << 2, pcmk_sim_show_scores = 1 << 3, pcmk_sim_show_utilization = 1 << 4, pcmk_sim_simulate = 1 << 5, pcmk_sim_sanitized = 1 << 6, pcmk_sim_verbose = 1 << 7, }; /*! * \brief Synthetic cluster events that can be injected into the cluster * for running simulations. */ typedef struct { /*! A list of node names (gchar *) to simulate bringing online */ GList *node_up; /*! A list of node names (gchar *) to simulate bringing offline */ GList *node_down; /*! A list of node names (gchar *) to simulate failing */ GList *node_fail; /*! A list of operations (gchar *) to inject. The format of these strings * is described in the "Operation Specification" section of crm_simulate * help output. */ GList *op_inject; /*! A list of operations (gchar *) that should return a given error code * if they fail. The format of these strings is described in the * "Operation Specification" section of crm_simulate help output. */ GList *op_fail; /*! A list of tickets (gchar *) to simulate granting */ GList *ticket_grant; /*! A list of tickets (gchar *) to simulate revoking */ GList *ticket_revoke; /*! A list of tickets (gchar *) to simulate putting on standby */ GList *ticket_standby; /*! A list of tickets (gchar *) to simulate activating */ GList *ticket_activate; /*! Does the cluster have an active watchdog device? */ char *watchdog; /*! Does the cluster have quorum? */ char *quorum; } pcmk_injections_t; /*! * \brief Get and output controller status * * \param[in,out] xml Destination for the result, as an XML tree * \param[in] node_name Name of node whose status is desired * (\p NULL for DC) * \param[in] message_timeout_ms How long to wait for a reply from the * controller API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code */ int pcmk_controller_status(xmlNodePtr *xml, const char *node_name, unsigned int message_timeout_ms); /*! * \brief Get and output designated controller node name * * \param[in,out] xml Destination for the result, as an XML tree * \param[in] message_timeout_ms How long to wait for a reply from the * controller API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code */ int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms); /*! * \brief Free a :pcmk_injections_t structure * * \param[in,out] injections The structure to be freed */ void pcmk_free_injections(pcmk_injections_t *injections); /*! * \brief Get and optionally output node info corresponding to a node ID from * the controller * * \param[in,out] xml Destination for the result, as an XML tree * \param[in,out] node_id ID of node whose name to get. If \p NULL * or 0, get the local node name. If not * \p NULL, store the true node ID here on * success. * \param[out] node_name If not \p NULL, where to store the node * name * \param[out] uuid If not \p NULL, where to store the node * UUID * \param[out] state If not \p NULL, where to store the * membership state * \param[out] is_remote If not \p NULL, where to store whether the * node is a Pacemaker Remote node * \param[out] have_quorum If not \p NULL, where to store whether the * node has quorum * \param[in] show_output Whether to output the node info * \param[in] message_timeout_ms How long to wait for a reply from the * controller API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code * * \note The caller is responsible for freeing \p *node_name, \p *uuid, and * \p *state using \p free(). */ int pcmk_query_node_info(xmlNodePtr *xml, uint32_t *node_id, char **node_name, char **uuid, char **state, bool *have_quorum, bool *is_remote, bool show_output, unsigned int message_timeout_ms); /*! * \brief Get the node name corresponding to a node ID from the controller * * \param[in,out] xml Destination for the result, as an XML tree * \param[in,out] node_id ID of node whose name to get (or 0 for the * local node) * \param[out] node_name If not \p NULL, where to store the node * name * \param[in] message_timeout_ms How long to wait for a reply from the * controller API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code * * \note The caller is responsible for freeing \p *node_name using \p free(). */ static inline int pcmk_query_node_name(xmlNodePtr *xml, uint32_t node_id, char **node_name, unsigned int message_timeout_ms) { return pcmk_query_node_info(xml, &node_id, node_name, NULL, NULL, NULL, NULL, false, message_timeout_ms); } /*! * \brief Get and output \p pacemakerd status * * \param[in,out] xml Destination for the result, as an XML tree * \param[in] ipc_name IPC name for request * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemakerd API. If 0, * \p pcmk_ipc_dispatch_sync will be used. * Otherwise, \p pcmk_ipc_dispatch_poll will * be used. * * \return Standard Pacemaker return code */ int pcmk_pacemakerd_status(xmlNodePtr *xml, const char *ipc_name, unsigned int message_timeout_ms); /*! * \brief Remove a resource * * \param[in,out] xml Destination for the result, as an XML tree * \param[in] rsc_id Resource to remove * \param[in] rsc_type Type of the resource ("primitive", "group", etc.) * * \return Standard Pacemaker return code * \note This function will return \p pcmk_rc_ok if \p rsc_id doesn't exist * or if \p rsc_type is incorrect for \p rsc_id (deleting something * that doesn't exist always succeeds). */ int pcmk_resource_delete(xmlNodePtr *xml, const char *rsc_id, const char *rsc_type); /*! * \brief Calculate and output resource operation digests * * \param[out] xml Where to store XML with result * \param[in,out] rsc Resource to calculate digests for * \param[in] node Node whose operation history should be used * \param[in] overrides Hash table of configuration parameters to override * * \return Standard Pacemaker return code */ int pcmk_resource_digests(xmlNodePtr *xml, pcmk_resource_t *rsc, const pcmk_node_t *node, GHashTable *overrides); /*! * \brief Simulate a cluster's response to events * * This high-level function essentially implements crm_simulate(8). It operates * on an input CIB file and various lists of events that can be simulated. It * optionally writes out a variety of artifacts to show the results of the * simulation. Output can be modified with various flags. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in,out] scheduler Scheduler data * \param[in] injections A structure containing cluster events * (node up/down, tickets, injected operations) * \param[in] flags Group of enum pcmk_sim_flags * \param[in] section_opts Which portions of the cluster status output * should be displayed? * \param[in] use_date Date to set the cluster's time to (may be NULL) * \param[in] input_file The source CIB file, which may be overwritten by * this function (may be NULL) * \param[in] graph_file Where to write the XML-formatted transition graph * (may be NULL, in which case no file will be * written) * \param[in] dot_file Where to write the dot(1) formatted transition * graph (may be NULL, in which case no file will * be written) * * \return Standard Pacemaker return code */ int pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler, const pcmk_injections_t *injections, unsigned int flags, unsigned int section_opts, const char *use_date, const char *input_file, const char *graph_file, const char *dot_file); /*! * \brief Verify that a CIB is error-free or output errors and warnings * * This high-level function essentially implements crm_verify(8). It operates * on an input CIB file, which can be inputted through one of several ways. It * writes out XML-formatted output. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] cib_source Source of the CIB: * NULL -> use live cib, "-" -> stdin * "<..." -> xml str, otherwise -> xml file name * * \return Standard Pacemaker return code */ int pcmk_verify(xmlNodePtr *xml, const char *cib_source); /*! * \brief Get nodes list * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] node_types Node type(s) to return (default: all) * * \return Standard Pacemaker return code */ int pcmk_list_nodes(xmlNodePtr *xml, const char *node_types); /*! * \brief Output cluster status formatted like `crm_mon --output-as=xml` * * \param[in,out] xml The destination for the result, as an XML tree * * \return Standard Pacemaker return code */ int pcmk_status(xmlNodePtr *xml); /*! * \brief Check whether each rule in a list is in effect * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date and * time (if \c NULL, use current date and time) * \param[in] rule_ids The IDs of the rules to check, as a NULL- * terminated list. * * \return Standard Pacemaker return code */ int pcmk_check_rules(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char **rule_ids); /*! * \brief Check whether a given rule is in effect * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] input The CIB XML to check (if \c NULL, use current CIB) * \param[in] date Check whether the rule is in effect at this date and * time (if \c NULL, use current date and time) * \param[in] rule_ids The ID of the rule to check * * \return Standard Pacemaker return code */ static inline int pcmk_check_rule(xmlNodePtr *xml, xmlNodePtr input, const crm_time_t *date, const char *rule_id) { const char *rule_ids[] = {rule_id, NULL}; return pcmk_check_rules(xml, input, date, rule_ids); } -/*! - * \enum pcmk_rc_disp_flags - * \brief Bit flags to control which fields of result code info are displayed - */ +//! Bit flags to control which fields of result code info are displayed enum pcmk_rc_disp_flags { pcmk_rc_disp_none = 0, //!< (Does nothing) pcmk_rc_disp_code = (1 << 0), //!< Display result code number pcmk_rc_disp_name = (1 << 1), //!< Display result code name pcmk_rc_disp_desc = (1 << 2), //!< Display result code description }; /*! * \brief Display the name and/or description of a result code * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] code The result code * \param[in] type Interpret \c code as this type of result code. * Supported values: \c pcmk_result_legacy, * \c pcmk_result_rc, \c pcmk_result_exitcode. * \param[in] flags Group of \c pcmk_rc_disp_flags * * \return Standard Pacemaker return code */ int pcmk_show_result_code(xmlNodePtr *xml, int code, enum pcmk_result_type type, uint32_t flags); /*! * \brief List all valid result codes in a particular family * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] type The family of result codes to list. Supported * values: \c pcmk_result_legacy, \c pcmk_result_rc, * \c pcmk_result_exitcode. * \param[in] flags Group of \c pcmk_rc_disp_flags * * \return Standard Pacemaker return code */ int pcmk_list_result_codes(xmlNodePtr *xml, enum pcmk_result_type type, uint32_t flags); /*! * \brief List available providers for the given OCF agent * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec Resource agent name * * \return Standard Pacemaker return code */ int pcmk_list_alternatives(xmlNodePtr *xml, const char *agent_spec); /*! * \brief List all agents available for the named standard and/or provider * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec STD[:PROV] * * \return Standard Pacemaker return code */ int pcmk_list_agents(xmlNodePtr *xml, char *agent_spec); /*! * \brief List all available OCF providers for the given agent * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] agent_spec Resource agent name * * \return Standard Pacemaker return code */ int pcmk_list_providers(xmlNodePtr *xml, const char *agent_spec); /*! * \brief List all available resource agent standards * * \param[in,out] xml The destination for the result, as an XML tree * * \return Standard Pacemaker return code */ int pcmk_list_standards(xmlNodePtr *xml); /*! * \brief List all available cluster options * * These are options that affect the entire cluster. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] all If \c true, include advanced and deprecated options * (currently always treated as true) * * \return Standard Pacemaker return code */ int pcmk_list_cluster_options(xmlNode **xml, bool all); /*! * \brief List common fencing resource parameters * * These are parameters that are available for all fencing resources, regardless * of type. They are processed by Pacemaker, rather than by the fence agent or * the fencing library. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] all If \c true, include advanced and deprecated options * (currently always treated as true) * * \return Standard Pacemaker return code */ int pcmk_list_fencing_params(xmlNode **xml, bool all); /*! * \internal * \brief List meta-attributes applicable to primitive resources as OCF-like XML * * \param[in,out] out Output object * \param[in] all If \c true, include advanced and deprecated options (this * is always treated as true for XML output objects) * * \return Standard Pacemaker return code */ int pcmk_list_primitive_meta(xmlNode **xml, bool all); /*! * \brief Return constraints that apply to the given ticket * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find constraint for, or \c NULL for * all ticket constraints * * \return Standard Pacemaker return code */ int pcmk_ticket_constraints(xmlNodePtr *xml, const char *ticket_id); /*! * \brief Delete a ticket's state from the local cluster site * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to delete * \param[in] force If \c true, delete the ticket even if it has * been granted * * \return Standard Pacemaker return code */ int pcmk_ticket_delete(xmlNodePtr *xml, const char *ticket_id, bool force); /*! * \brief Return the value of a ticket's attribute * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find attribute value for * \param[in] attr_name Attribute's name to find value for * \param[in] attr_default If either the ticket or the attribute do not * exist, use this as the value in \p xml * * \return Standard Pacemaker return code */ int pcmk_ticket_get_attr(xmlNodePtr *xml, const char *ticket_id, const char *attr_name, const char *attr_default); /*! * \brief Return information about the given ticket * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find info value for, or \c NULL for * all tickets * * \return Standard Pacemaker return code */ int pcmk_ticket_info(xmlNodePtr *xml, const char *ticket_id); /*! * \brief Remove the given attribute(s) from a ticket * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to remove attributes from * \param[in] attr_delete A list of attribute names * \param[in] force Attempting to remove the granted attribute of * \p ticket_id will cause this function to return * \c EACCES unless \p force is set to \c true * * \return Standard Pacemaker return code */ int pcmk_ticket_remove_attr(xmlNodePtr *xml, const char *ticket_id, GList *attr_delete, bool force); /*! * \brief Set the given attribute(s) on a ticket * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to set attributes on * \param[in] attr_set A hash table of attributes, where keys are the * attribute names and the values are the attribute * values * \param[in] force Attempting to change the granted status of * \p ticket_id will cause this function to return * \c EACCES unless \p force is set to \c true * * \return Standard Pacemaker return code * * \note If no \p ticket_id attribute exists but \p attr_set is non-NULL, the * ticket will be created with the given attributes. */ int pcmk_ticket_set_attr(xmlNodePtr *xml, const char *ticket_id, GHashTable *attr_set, bool force); /*! * \brief Return a ticket's state XML * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] ticket_id Ticket to find state for, or \c NULL for all * tickets * * \return Standard Pacemaker return code * * \note If \p ticket_id is not \c NULL and more than one ticket exists with * that ID, this function returns \c pcmk_rc_duplicate_id. */ int pcmk_ticket_state(xmlNodePtr *xml, const char *ticket_id); /*! * \brief Ask the cluster to perform fencing * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] target The node that should be fenced * \param[in] action The fencing action (on, off, reboot) to perform * \param[in] name Who requested the fence action? * \param[in] timeout How long to wait for operation to complete (in ms) * \param[in] tolerance If a successful action for \p target happened within * this many ms, return 0 without performing the action * again * \param[in] delay Apply this delay (in milliseconds) before initiating * fencing action (-1 applies no delay and also * disables any fencing delay from pcmk_delay_base and * pcmk_delay_max) * \param[out] reason If not NULL, where to put descriptive failure reason * * \return Standard Pacemaker return code * \note If \p reason is not NULL, the caller is responsible for freeing its * returned value. */ int pcmk_request_fencing(xmlNodePtr *xml, const char *target, const char *action, const char *name, unsigned int timeout, unsigned int tolerance, int delay, char **reason); /*! * \brief List the fencing operations that have occurred for a specific node * * \note If \p xml is not NULL, it will be freed first and the previous * contents lost. * * \param[in,out] xml The destination for the result, as an XML tree * \param[in] target The node to get history for * \param[in] timeout How long to wait for operation to complete (in ms) * \param[in] quiet Suppress most output * \param[in] verbose Include additional output * \param[in] broadcast Gather fencing history from all nodes * \param[in] cleanup Clean up fencing history after listing * * \return Standard Pacemaker return code */ int pcmk_fence_history(xmlNodePtr *xml, const char *target, unsigned int timeout, bool quiet, int verbose, bool broadcast, bool cleanup); /*! * \brief List all installed fence agents * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_installed(xmlNodePtr *xml, unsigned int timeout); /*! * \brief When was a device last fenced? * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] target The node that was fenced * \param[in] as_nodeid If true, \p target has node ID rather than name * * \return Standard Pacemaker return code */ int pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid); /*! * \brief List nodes that can be fenced * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] device_id Resource ID of fence device to check * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_list_targets(xmlNodePtr *xml, const char *device_id, unsigned int timeout); /*! * \brief Get metadata for a fence agent * * \note If \p xml is not NULL, it will be freed first and the previous * contents lost. * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] agent The fence agent to get metadata for * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_metadata(xmlNodePtr *xml, const char *agent, unsigned int timeout); /*! * \brief List registered fence devices * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] target If not NULL, return only devices that can fence this * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_registered(xmlNodePtr *xml, const char *target, unsigned int timeout); /*! * \brief Register a fencing topology level * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] target What fencing level targets (as "name=value" to * target by given node attribute, or "@pattern" to * target by node name pattern, or a node name) * \param[in] fence_level Index number of level to add * \param[in] devices Devices to use in level as a list of char * * * \return Standard Pacemaker return code */ int pcmk_fence_register_level(xmlNodePtr *xml, const char *target, int fence_level, GList *devices); /*! * \brief Unregister a fencing topology level * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] target What fencing level targets (as "name=value" to * target by given node attribute, or "@pattern" to * target by node name pattern, or a node name) * \param[in] fence_level Index number of level to remove * * \return Standard Pacemaker return code */ int pcmk_fence_unregister_level(xmlNodePtr *xml, const char *target, int fence_level); /*! * \brief Validate a fence device configuration * * \param[in,out] xml The destination for the result, as an XML tree (if * not NULL, previous contents will be freed and lost) * \param[in] agent The agent to validate (for example, "fence_xvm") * \param[in] id Fence device ID (may be NULL) * \param[in] params Fence device configuration parameters * \param[in] timeout How long to wait for operation to complete (in ms) * * \return Standard Pacemaker return code */ int pcmk_fence_validate(xmlNodePtr *xml, const char *agent, const char *id, GHashTable *params, unsigned int timeout); #ifdef __cplusplus } #endif #endif diff --git a/tools/crm_shadow.c b/tools/crm_shadow.c index 90651bac5a..d28b2e7c83 100644 --- a/tools/crm_shadow.c +++ b/tools/crm_shadow.c @@ -1,1309 +1,1308 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 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 #define SUMMARY "perform Pacemaker configuration changes in a sandbox\n\n" \ "This command sets up an environment in which " \ "configuration tools (cibadmin,\n" \ "crm_resource, etc.) work offline instead of against a " \ "live cluster, allowing\n" \ "changes to be previewed and tested for side effects." #define INDENT " " enum shadow_command { shadow_cmd_none = 0, shadow_cmd_which, shadow_cmd_display, shadow_cmd_diff, shadow_cmd_file, shadow_cmd_create, shadow_cmd_create_empty, shadow_cmd_commit, shadow_cmd_delete, shadow_cmd_edit, shadow_cmd_reset, shadow_cmd_switch, }; /*! * \internal - * \enum shadow_disp_flags * \brief Bit flags to control which fields of shadow CIB info are displayed * * \note Ignored for XML output. */ enum shadow_disp_flags { shadow_disp_instance = (1 << 0), shadow_disp_file = (1 << 1), shadow_disp_content = (1 << 2), shadow_disp_diff = (1 << 3), }; static crm_exit_t exit_code = CRM_EX_OK; static struct { enum shadow_command cmd; int cmd_options; char *instance; gboolean force; gboolean batch; gboolean full_upload; gchar *validate_with; } options = { .cmd_options = cib_sync_call, }; /*! * \internal * \brief Display an instruction to the user * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Instructional message */ PCMK__OUTPUT_ARGS("instruction", "const char *") static int instruction_default(pcmk__output_t *out, va_list args) { const char *msg = va_arg(args, const char *); if (msg == NULL) { return pcmk_rc_no_output; } return out->info(out, "%s", msg); } /*! * \internal * \brief Display an instruction to the user * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Instructional message */ PCMK__OUTPUT_ARGS("instruction", "const char *") static int instruction_xml(pcmk__output_t *out, va_list args) { const char *msg = va_arg(args, const char *); if (msg == NULL) { return pcmk_rc_no_output; } pcmk__output_create_xml_text_node(out, "instruction", msg); return pcmk_rc_ok; } /*! * \internal * \brief Display information about a shadow CIB instance * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Instance name (can be \p NULL) * -# Shadow file name (can be \p NULL) * -# Shadow file content (can be \p NULL) * -# Patchset containing the changes in the shadow CIB (can be \p NULL) * -# Group of \p shadow_disp_flags indicating which fields to display */ PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *", "const xmlNode *", "enum shadow_disp_flags") static int shadow_default(pcmk__output_t *out, va_list args) { const char *instance = va_arg(args, const char *); const char *filename = va_arg(args, const char *); const xmlNode *content = va_arg(args, const xmlNode *); const xmlNode *diff = va_arg(args, const xmlNode *); enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int); int rc = pcmk_rc_no_output; if (pcmk_is_set(flags, shadow_disp_instance)) { rc = out->info(out, "Instance: %s", pcmk__s(instance, "")); } if (pcmk_is_set(flags, shadow_disp_file)) { rc = out->info(out, "File name: %s", pcmk__s(filename, "")); } if (pcmk_is_set(flags, shadow_disp_content)) { rc = out->info(out, "Content:"); if (content != NULL) { GString *buf = g_string_sized_new(1024); gchar *str = NULL; pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf, 0); str = g_string_free(buf, FALSE); str = pcmk__trim(str); if (!pcmk__str_empty(str)) { out->info(out, "%s", str); } g_free(str); } else { out->info(out, ""); } } if (pcmk_is_set(flags, shadow_disp_diff)) { rc = out->info(out, "Diff:"); if (diff != NULL) { out->message(out, "xml-patchset", diff); } else { out->info(out, ""); } } return rc; } /*! * \internal * \brief Display information about a shadow CIB instance * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Instance name (can be \p NULL) * -# Shadow file name (can be \p NULL) * -# Shadow file content (can be \p NULL) * -# Patchset containing the changes in the shadow CIB (can be \p NULL) * -# Group of \p shadow_disp_flags indicating which fields to display */ PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *", "const xmlNode *", "enum shadow_disp_flags") static int shadow_text(pcmk__output_t *out, va_list args) { if (!out->is_quiet(out)) { return shadow_default(out, args); } else { const char *instance = va_arg(args, const char *); const char *filename = va_arg(args, const char *); const xmlNode *content = va_arg(args, const xmlNode *); const xmlNode *diff = va_arg(args, const xmlNode *); enum shadow_disp_flags flags = (enum shadow_disp_flags) va_arg(args, int); int rc = pcmk_rc_no_output; bool quiet_orig = out->quiet; /* We have to disable quiet mode for the "xml-patchset" message if we * call it, so we might as well do so for this whole section. */ out->quiet = false; if (pcmk_is_set(flags, shadow_disp_instance) && (instance != NULL)) { rc = out->info(out, "%s", instance); } if (pcmk_is_set(flags, shadow_disp_file) && (filename != NULL)) { rc = out->info(out, "%s", filename); } if (pcmk_is_set(flags, shadow_disp_content) && (content != NULL)) { GString *buf = g_string_sized_new(1024); gchar *str = NULL; pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf, 0); str = g_string_free(buf, FALSE); str = pcmk__trim(str); rc = out->info(out, "%s", str); g_free(str); } if (pcmk_is_set(flags, shadow_disp_diff) && (diff != NULL)) { rc = out->message(out, "xml-patchset", diff); } out->quiet = quiet_orig; return rc; } } /*! * \internal * \brief Display information about a shadow CIB instance * * \param[in,out] out Output object * \param[in] args Message-specific arguments * * \return Standard Pacemaker return code * * \note \p args should contain the following: * -# Instance name (can be \p NULL) * -# Shadow file name (can be \p NULL) * -# Shadow file content (can be \p NULL) * -# Patchset containing the changes in the shadow CIB (can be \p NULL) * -# Group of \p shadow_disp_flags indicating which fields to display * (ignored) */ PCMK__OUTPUT_ARGS("shadow", "const char *", "const char *", "const xmlNode *", "const xmlNode *", "enum shadow_disp_flags") static int shadow_xml(pcmk__output_t *out, va_list args) { const char *instance = va_arg(args, const char *); const char *filename = va_arg(args, const char *); const xmlNode *content = va_arg(args, const xmlNode *); const xmlNode *diff = va_arg(args, const xmlNode *); enum shadow_disp_flags flags G_GNUC_UNUSED = (enum shadow_disp_flags) va_arg(args, int); pcmk__output_xml_create_parent(out, PCMK_XE_SHADOW, PCMK_XA_INSTANCE, instance, PCMK_XA_FILE, filename, NULL); if (content != NULL) { GString *buf = g_string_sized_new(1024); pcmk__xml_string(content, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf, 0); out->output_xml(out, PCMK_XE_CONTENT, buf->str); g_string_free(buf, TRUE); } if (diff != NULL) { out->message(out, "xml-patchset", diff); } pcmk__output_xml_pop_parent(out); return pcmk_rc_ok; } static const pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static const pcmk__message_entry_t fmt_functions[] = { { "instruction", "default", instruction_default }, { "instruction", "xml", instruction_xml }, { "shadow", "default", shadow_default }, { "shadow", "text", shadow_text }, { "shadow", "xml", shadow_xml }, { NULL, NULL, NULL } }; /*! * \internal * \brief Set the error when \p --force is not passed with a dangerous command * * \param[in] reason Why command is dangerous * \param[in] for_shadow If true, command is dangerous to the shadow file. * Otherwise, command is dangerous to the active * cluster. * \param[in] show_mismatch If true and the supplied shadow instance is not * the same as the active shadow instance, report * this * \param[out] error Where to store error */ static void set_danger_error(const char *reason, bool for_shadow, bool show_mismatch, GError **error) { const char *active = getenv("CIB_shadow"); char *full = NULL; if (show_mismatch && !pcmk__str_eq(active, options.instance, pcmk__str_null_matches)) { full = crm_strdup_printf("%s.\nAdditionally, the supplied shadow " "instance (%s) is not the same as the active " "one (%s)", reason, options.instance, active); reason = full; } g_set_error(error, PCMK__EXITC_ERROR, exit_code, "%s%sTo prevent accidental destruction of the %s, the --force " "flag is required in order to proceed.", pcmk__s(reason, ""), ((reason != NULL)? ".\n" : ""), (for_shadow? "shadow file" : "cluster")); free(full); } /*! * \internal * \brief Get the active shadow instance from the environment * * This sets \p options.instance to the value of the \p CIB_shadow env variable. * * \param[out] error Where to store error */ static int get_instance_from_env(GError **error) { int rc = pcmk_rc_ok; pcmk__str_update(&options.instance, getenv("CIB_shadow")); if (options.instance == NULL) { rc = ENXIO; exit_code = pcmk_rc2exitc(rc); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "No active shadow configuration defined"); } return rc; } /*! * \internal * \brief Validate that the shadow file does or does not exist, as appropriate * * \param[in] filename Absolute path of shadow file * \param[in] should_exist Whether the shadow file is expected to exist * \param[out] error Where to store error * * \return Standard Pacemaker return code */ static int check_file_exists(const char *filename, bool should_exist, GError **error) { struct stat buf; if (!should_exist && (stat(filename, &buf) == 0)) { char *reason = crm_strdup_printf("A shadow instance '%s' already " "exists", options.instance); exit_code = CRM_EX_CANTCREAT; set_danger_error(reason, true, false, error); free(reason); return EEXIST; } if (should_exist && (stat(filename, &buf) < 0)) { int rc = errno; exit_code = pcmk_rc2exitc(rc); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not access shadow instance '%s': %s", options.instance, strerror(rc)); return errno; } return pcmk_rc_ok; } /*! * \internal * \brief Connect to the "real" (non-shadow) CIB * * \param[out] real_cib Where to store CIB connection * \param[out] error Where to store error * * \return Standard Pacemaker return code */ static int connect_real_cib(cib_t **real_cib, GError **error) { int rc = pcmk_rc_ok; *real_cib = cib_new_no_shadow(); if (*real_cib == NULL) { rc = ENOMEM; exit_code = pcmk_rc2exitc(rc); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not create a CIB connection object"); return rc; } rc = cib__signon_attempts(*real_cib, cib_command, 5); 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 CIB: %s", pcmk_rc_str(rc)); } return rc; } /*! * \internal * \brief Query the "real" (non-shadow) CIB and store the result * * \param[out] output Where to store query output * \param[out] error Where to store error * * \return Standard Pacemaker return code */ static int query_real_cib(xmlNode **output, GError **error) { cib_t *real_cib = NULL; int rc = connect_real_cib(&real_cib, error); if (rc != pcmk_rc_ok) { goto done; } rc = real_cib->cmds->query(real_cib, NULL, output, options.cmd_options); 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 query the non-shadow CIB: %s", pcmk_rc_str(rc)); } done: cib_delete(real_cib); return rc; } /*! * \internal * \brief Read XML from the given file * * \param[in] filename Path of input file * \param[out] output Where to store XML read from \p filename * \param[out] error Where to store error * * \return Standard Pacemaker return code */ static int read_xml(const char *filename, xmlNode **output, GError **error) { int rc = pcmk_rc_ok; *output = pcmk__xml_read(filename); if (*output == NULL) { rc = pcmk_rc_no_input; exit_code = pcmk_rc2exitc(rc); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not parse XML from input file '%s'", filename); } return rc; } /*! * \internal * \brief Write the shadow XML to a file * * \param[in] xml Shadow XML * \param[in] filename Name of destination file * \param[in] reset Whether the write is a reset (for logging only) * \param[out] error Where to store error */ static int write_shadow_file(const xmlNode *xml, const char *filename, bool reset, GError **error) { int rc = pcmk__xml_write_file(xml, filename, false); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not %s the shadow instance '%s': %s", reset? "reset" : "create", options.instance, pcmk_rc_str(rc)); } return rc; } /*! * \internal * \brief Create a shell prompt based on the given shadow instance name * * \return Newly created prompt * * \note The caller is responsible for freeing the return value using \p free(). */ static inline char * get_shadow_prompt(void) { return crm_strdup_printf("shadow[%.40s] # ", options.instance); } /*! * \internal * \brief Set up environment variables for a shadow instance * * \param[in,out] out Output object * \param[in] do_switch If true, switch to an existing instance (logging * only) * \param[out] error Where to store error */ static void shadow_setup(pcmk__output_t *out, bool do_switch, GError **error) { const char *active = getenv("CIB_shadow"); const char *prompt = getenv("PS1"); const char *shell = getenv("SHELL"); char *new_prompt = get_shadow_prompt(); if (pcmk__str_eq(active, options.instance, pcmk__str_none) && pcmk__str_eq(new_prompt, prompt, pcmk__str_none)) { // CIB_shadow and prompt environment variables are already set up goto done; } if (!options.batch && (shell != NULL)) { out->info(out, "Setting up shadow instance"); setenv("PS1", new_prompt, 1); setenv("CIB_shadow", options.instance, 1); out->message(out, PCMK_XE_INSTRUCTION, "Press Ctrl+D to exit the crm_shadow shell"); if (pcmk__str_eq(shell, "(^|/)bash$", pcmk__str_regex)) { execl(shell, shell, "--norc", "--noprofile", NULL); } else { execl(shell, shell, NULL); } exit_code = pcmk_rc2exitc(errno); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Failed to launch shell '%s': %s", shell, pcmk_rc_str(errno)); } else { char *msg = NULL; const char *prefix = "A new shadow instance was created. To begin " "using it"; if (do_switch) { prefix = "To switch to the named shadow instance"; } msg = crm_strdup_printf("%s, enter the following into your shell:\n" "\texport CIB_shadow=%s", prefix, options.instance); out->message(out, "instruction", msg); free(msg); } done: free(new_prompt); } /*! * \internal * \brief Remind the user to clean up the shadow environment * * \param[in,out] out Output object */ static void shadow_teardown(pcmk__output_t *out) { const char *active = getenv("CIB_shadow"); const char *prompt = getenv("PS1"); if (pcmk__str_eq(active, options.instance, pcmk__str_none)) { char *our_prompt = get_shadow_prompt(); if (pcmk__str_eq(prompt, our_prompt, pcmk__str_none)) { out->message(out, "instruction", "Press Ctrl+D to exit the crm_shadow shell"); } else { out->message(out, "instruction", "Remember to unset the CIB_shadow variable by " "entering the following into your shell:\n" "\tunset CIB_shadow"); } free(our_prompt); } } /*! * \internal * \brief Commit the shadow file contents to the active cluster * * \param[out] error Where to store error */ static void commit_shadow_file(GError **error) { char *filename = NULL; cib_t *real_cib = NULL; xmlNodePtr input = NULL; xmlNodePtr section_xml = NULL; const char *section = NULL; int rc = pcmk_rc_ok; if (!options.force) { const char *reason = "The commit command overwrites the active cluster " "configuration"; exit_code = CRM_EX_USAGE; set_danger_error(reason, false, true, error); return; } filename = get_shadow_file(options.instance); if (check_file_exists(filename, true, error) != pcmk_rc_ok) { goto done; } if (connect_real_cib(&real_cib, error) != pcmk_rc_ok) { goto done; } if (read_xml(filename, &input, error) != pcmk_rc_ok) { goto done; } section_xml = input; if (!options.full_upload) { section = PCMK_XE_CONFIGURATION; section_xml = pcmk__xe_first_child(input, section, NULL, NULL); } rc = real_cib->cmds->replace(real_cib, section, section_xml, options.cmd_options); 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 commit shadow instance '%s' to the CIB: %s", options.instance, pcmk_rc_str(rc)); } done: free(filename); cib_delete(real_cib); pcmk__xml_free(input); } /*! * \internal * \brief Create a new empty shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error * * \note If \p --force is given, we try to write the file regardless of whether * it already exists. */ static void create_shadow_empty(pcmk__output_t *out, GError **error) { char *filename = get_shadow_file(options.instance); xmlNode *output = NULL; if (!options.force && (check_file_exists(filename, false, error) != pcmk_rc_ok)) { goto done; } output = createEmptyCib(0); crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with); out->info(out, "Created new %s configuration", crm_element_value(output, PCMK_XA_VALIDATE_WITH)); if (write_shadow_file(output, filename, false, error) != pcmk_rc_ok) { goto done; } shadow_setup(out, false, error); done: free(filename); pcmk__xml_free(output); } /*! * \internal * \brief Create a shadow instance based on the active CIB * * \param[in,out] out Output object * \param[in] reset If true, overwrite the given existing shadow instance. * Otherwise, create a new shadow instance with the given * name. * \param[out] error Where to store error * * \note If \p --force is given, we try to write the file regardless of whether * it already exists. */ static void create_shadow_from_cib(pcmk__output_t *out, bool reset, GError **error) { char *filename = get_shadow_file(options.instance); xmlNode *output = NULL; if (!options.force) { if (reset) { const char *reason = "The reset command overwrites the active " "shadow configuration"; exit_code = CRM_EX_USAGE; set_danger_error(reason, true, true, error); goto done; } if (check_file_exists(filename, reset, error) != pcmk_rc_ok) { goto done; } } if (query_real_cib(&output, error) != pcmk_rc_ok) { goto done; } if (write_shadow_file(output, filename, reset, error) != pcmk_rc_ok) { goto done; } shadow_setup(out, false, error); done: free(filename); pcmk__xml_free(output); } /*! * \internal * \brief Delete the shadow file * * \param[in,out] out Output object * \param[out] error Where to store error */ static void delete_shadow_file(pcmk__output_t *out, GError **error) { char *filename = NULL; if (!options.force) { const char *reason = "The delete command removes the specified shadow " "file"; exit_code = CRM_EX_USAGE; set_danger_error(reason, true, true, error); return; } filename = get_shadow_file(options.instance); if ((unlink(filename) < 0) && (errno != ENOENT)) { exit_code = pcmk_rc2exitc(errno); g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not remove shadow instance '%s': %s", options.instance, strerror(errno)); } else { shadow_teardown(out); } free(filename); } /*! * \internal * \brief Open the shadow file in a text editor * * \param[out] error Where to store error * * \note The \p EDITOR environment variable must be set. */ static void edit_shadow_file(GError **error) { char *filename = NULL; const char *editor = NULL; if (get_instance_from_env(error) != pcmk_rc_ok) { return; } filename = get_shadow_file(options.instance); if (check_file_exists(filename, true, error) != pcmk_rc_ok) { goto done; } editor = getenv("EDITOR"); if (editor == NULL) { exit_code = CRM_EX_NOT_CONFIGURED; g_set_error(error, PCMK__EXITC_ERROR, exit_code, "No value for EDITOR defined"); goto done; } execlp(editor, "--", filename, NULL); exit_code = CRM_EX_OSFILE; g_set_error(error, PCMK__EXITC_ERROR, exit_code, "Could not invoke EDITOR (%s %s): %s", editor, filename, strerror(errno)); done: free(filename); } /*! * \internal * \brief Show the contents of the active shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error */ static void show_shadow_contents(pcmk__output_t *out, GError **error) { char *filename = NULL; if (get_instance_from_env(error) != pcmk_rc_ok) { return; } filename = get_shadow_file(options.instance); if (check_file_exists(filename, true, error) == pcmk_rc_ok) { xmlNode *output = NULL; bool quiet_orig = out->quiet; if (read_xml(filename, &output, error) != pcmk_rc_ok) { goto done; } out->quiet = true; out->message(out, "shadow", options.instance, NULL, output, NULL, shadow_disp_content); out->quiet = quiet_orig; pcmk__xml_free(output); } done: free(filename); } /*! * \internal * \brief Show the changes in the active shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error */ static void show_shadow_diff(pcmk__output_t *out, GError **error) { char *filename = NULL; xmlNodePtr old_config = NULL; xmlNodePtr new_config = NULL; xmlNodePtr diff = NULL; bool quiet_orig = out->quiet; if (get_instance_from_env(error) != pcmk_rc_ok) { return; } filename = get_shadow_file(options.instance); if (check_file_exists(filename, true, error) != pcmk_rc_ok) { goto done; } if (query_real_cib(&old_config, error) != pcmk_rc_ok) { goto done; } if (read_xml(filename, &new_config, error) != pcmk_rc_ok) { goto done; } xml_track_changes(new_config, NULL, new_config, false); xml_calculate_changes(old_config, new_config); diff = xml_create_patchset(0, old_config, new_config, NULL, false); pcmk__log_xml_changes(LOG_INFO, new_config); xml_accept_changes(new_config); out->quiet = true; out->message(out, "shadow", options.instance, NULL, NULL, diff, shadow_disp_diff); out->quiet = quiet_orig; if (diff != NULL) { /* @COMPAT: Exit with CRM_EX_DIGEST? This is not really an error; we * just want to indicate that there are differences (as the diff command * does). */ exit_code = CRM_EX_ERROR; } done: free(filename); pcmk__xml_free(old_config); pcmk__xml_free(new_config); pcmk__xml_free(diff); } /*! * \internal * \brief Show the absolute path of the active shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error */ static void show_shadow_filename(pcmk__output_t *out, GError **error) { if (get_instance_from_env(error) == pcmk_rc_ok) { char *filename = get_shadow_file(options.instance); bool quiet_orig = out->quiet; out->quiet = true; out->message(out, "shadow", options.instance, filename, NULL, NULL, shadow_disp_file); out->quiet = quiet_orig; free(filename); } } /*! * \internal * \brief Show the active shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error */ static void show_shadow_instance(pcmk__output_t *out, GError **error) { if (get_instance_from_env(error) == pcmk_rc_ok) { bool quiet_orig = out->quiet; out->quiet = true; out->message(out, "shadow", options.instance, NULL, NULL, NULL, shadow_disp_instance); out->quiet = quiet_orig; } } /*! * \internal * \brief Switch to the given shadow instance * * \param[in,out] out Output object * \param[out] error Where to store error */ static void switch_shadow_instance(pcmk__output_t *out, GError **error) { char *filename = NULL; filename = get_shadow_file(options.instance); if (check_file_exists(filename, true, error) == pcmk_rc_ok) { shadow_setup(out, true, error); } free(filename); } static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-w", "--which", NULL)) { options.cmd = shadow_cmd_which; } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) { options.cmd = shadow_cmd_display; } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) { options.cmd = shadow_cmd_diff; } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) { options.cmd = shadow_cmd_file; } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) { options.cmd = shadow_cmd_create; } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) { options.cmd = shadow_cmd_create_empty; } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) { options.cmd = shadow_cmd_commit; } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) { options.cmd = shadow_cmd_delete; } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) { options.cmd = shadow_cmd_edit; } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) { options.cmd = shadow_cmd_reset; } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) { options.cmd = shadow_cmd_switch; } else { // Should be impossible return FALSE; } // optarg may be NULL and that's okay pcmk__str_update(&options.instance, optarg); return TRUE; } static GOptionEntry query_entries[] = { { "which", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Indicate the active shadow copy", NULL }, { "display", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the contents of the active shadow copy", NULL }, { "diff", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the changes in the active shadow copy", NULL }, { "file", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the location of the active shadow copy file", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Create the named shadow copy of the active cluster configuration", "name" }, { "create-empty", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Create the named shadow copy with an empty cluster configuration.\n" INDENT "Optional: --validate-with", "name" }, { "commit", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Upload the contents of the named shadow copy to the cluster", "name" }, { "delete", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Delete the contents of the named shadow copy", "name" }, { "edit", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Edit the contents of the active shadow copy with your favorite $EDITOR", NULL }, { "reset", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Recreate named shadow copy from the active cluster configuration", "name. Required: --force." }, { "switch", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Switch to the named shadow copy", "name" }, { NULL } }; static GOptionEntry addl_entries[] = { { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force, "(Advanced) Force the action to be performed", NULL }, { "batch", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.batch, "(Advanced) Don't spawn a new shell", NULL }, { "all", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.full_upload, "(Advanced) Upload entire CIB, including status, with --commit", NULL }, { "validate-with", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.validate_with, "(Advanced) Create an older configuration version", NULL }, { NULL } }; static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { const char *desc = NULL; GOptionContext *context = NULL; desc = "Examples:\n\n" "Create a blank shadow configuration:\n\n" "\t# crm_shadow --create-empty myShadow\n\n" "Create a shadow configuration from the running cluster\n\n" "\t# crm_shadow --create myShadow\n\n" "Display the current shadow configuration:\n\n" "\t# crm_shadow --display\n\n" "Discard the current shadow configuration (named myShadow):\n\n" "\t# crm_shadow --delete myShadow --force\n\n" "Upload current shadow configuration (named myShadow) to running " "cluster:\n\n" "\t# crm_shadow --commit myShadow\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, "|"); g_option_context_set_description(context, desc); pcmk__add_arg_group(context, "queries", "Queries:", "Show query help", query_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; pcmk__output_t *out = NULL; GError *error = NULL; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "CDcersv"); GOptionContext *context = build_arg_context(args, &output_group); crm_log_preinit(NULL, argc, argv); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } 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; } if (g_strv_length(processed_args) > 1) { gchar *help = g_option_context_get_help(context, TRUE, NULL); GString *extra = g_string_sized_new(128); for (int lpc = 1; processed_args[lpc] != NULL; lpc++) { if (extra->len > 0) { g_string_append_c(extra, ' '); } g_string_append(extra, processed_args[lpc]); } exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "non-option ARGV-elements: %s\n\n%s", extra->str, help); g_free(help); g_string_free(extra, TRUE); goto done; } if (args->version) { out->version(out, false); goto done; } pcmk__register_messages(out, fmt_functions); if (options.cmd == shadow_cmd_none) { // @COMPAT: Create a default command if other tools have one gchar *help = g_option_context_get_help(context, TRUE, NULL); exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Must specify a query or command option\n\n%s", help); g_free(help); goto done; } pcmk__cli_init_logging("crm_shadow", args->verbosity); if (args->verbosity > 0) { cib__set_call_options(options.cmd_options, crm_system_name, cib_verbose); } // Run the command switch (options.cmd) { case shadow_cmd_commit: commit_shadow_file(&error); break; case shadow_cmd_create: create_shadow_from_cib(out, false, &error); break; case shadow_cmd_create_empty: create_shadow_empty(out, &error); break; case shadow_cmd_reset: create_shadow_from_cib(out, true, &error); break; case shadow_cmd_delete: delete_shadow_file(out, &error); break; case shadow_cmd_diff: show_shadow_diff(out, &error); break; case shadow_cmd_display: show_shadow_contents(out, &error); break; case shadow_cmd_edit: edit_shadow_file(&error); break; case shadow_cmd_file: show_shadow_filename(out, &error); break; case shadow_cmd_switch: switch_shadow_instance(out, &error); break; case shadow_cmd_which: show_shadow_instance(out, &error); break; default: // Should never reach this point break; } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(&error, out); free(options.instance); g_free(options.validate_with); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } crm_exit(exit_code); }