diff --git a/lib/pacemaker/pcmk_cluster_queries.c b/lib/pacemaker/pcmk_cluster_queries.c index fd77fb54b4..027c69511f 100644 --- a/lib/pacemaker/pcmk_cluster_queries.c +++ b/lib/pacemaker/pcmk_cluster_queries.c @@ -1,902 +1,902 @@ /* * Copyright 2020-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include // xmlNode #include #include #include #include #include #include #include #include #include #include #include //! Object to store node info from the controller API typedef struct { /* Adapted from pcmk_controld_api_reply_t:data:node_info. * (char **) are convenient here for use within callbacks: we can skip * copying strings unless the caller passes a non-NULL value. */ uint32_t id; char **node_name; char **uuid; char **state; bool have_quorum; bool is_remote; } node_info_t; //! Object to store API results, a timeout, and an output object typedef struct { pcmk__output_t *out; bool show_output; int rc; unsigned int message_timeout_ms; enum pcmk_pacemakerd_state pcmkd_state; node_info_t node_info; } data_t; /*! * \internal * \brief Validate that an IPC API event is a good reply * * \param[in,out] data API results and options * \param[in] api IPC API connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * * \return Standard Pacemaker return code */ static int validate_reply_event(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status) { pcmk__output_t *out = data->out; switch (event_type) { case pcmk_ipc_event_reply: break; case pcmk_ipc_event_disconnect: if (data->rc == ECONNRESET) { // Unexpected out->err(out, "error: Lost connection to %s", pcmk_ipc_name(api, true)); } // Nothing bad but not the reply we're looking for return ENOTSUP; default: // Ditto return ENOTSUP; } if (status != CRM_EX_OK) { out->err(out, "error: Bad reply from %s: %s", pcmk_ipc_name(api, true), crm_exit_str(status)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Validate that a controller API event is a good reply of expected type * * \param[in,out] data API results and options * \param[in] api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in] event_data Event-specific data * \param[in] expected_type Expected reply type * * \return Standard Pacemaker return code */ static int validate_controld_reply(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, const void *event_data, enum pcmk_controld_api_reply expected_type) { pcmk__output_t *out = data->out; int rc = pcmk_rc_ok; const pcmk_controld_api_reply_t *reply = NULL; rc = validate_reply_event(data, api, event_type, status); if (rc != pcmk_rc_ok) { return rc; } reply = (const pcmk_controld_api_reply_t *) event_data; if (reply->reply_type != expected_type) { out->err(out, "error: Unexpected reply type '%s' from controller", pcmk__controld_api_reply2str(reply->reply_type)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Validate that a \p pacemakerd API event is a good reply of expected * type * * \param[in,out] data API results and options * \param[in] api \p pacemakerd connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in] event_data Event-specific data * \param[in] expected_type Expected reply type * * \return Standard Pacemaker return code */ static int validate_pcmkd_reply(data_t *data, const pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, const void *event_data, enum pcmk_pacemakerd_api_reply expected_type) { pcmk__output_t *out = data->out; const pcmk_pacemakerd_api_reply_t *reply = NULL; int rc = validate_reply_event(data, api, event_type, status); if (rc != pcmk_rc_ok) { return rc; } reply = (const pcmk_pacemakerd_api_reply_t *) event_data; if (reply->reply_type != expected_type) { out->err(out, "error: Unexpected reply type '%s' from pacemakerd", pcmk__pcmkd_api_reply2str(reply->reply_type)); data->rc = EBADMSG; return data->rc; } return pcmk_rc_ok; } /*! * \internal * \brief Process a controller status IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void controller_status_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_ping); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; out->message(out, "health", reply->data.ping.sys_from, reply->host_from, reply->data.ping.fsa_state, reply->data.ping.result); data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a designated controller IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void designated_controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_ping); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; out->message(out, "dc", reply->host_from); data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a node info IPC event * * \param[in,out] controld_api Controller connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_controld_api_reply_t object containing * event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void node_info_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = (data_t *) user_data; pcmk__output_t *out = data->out; const pcmk_controld_api_reply_t *reply = NULL; int rc = validate_controld_reply(data, controld_api, event_type, status, event_data, pcmk_controld_reply_info); if (rc != pcmk_rc_ok) { return; } reply = (const pcmk_controld_api_reply_t *) event_data; if (reply->data.node_info.uname == NULL) { out->err(out, "Node is not known to cluster"); data->rc = pcmk_rc_node_unknown; return; } data->node_info.have_quorum = reply->data.node_info.have_quorum; data->node_info.is_remote = reply->data.node_info.is_remote; data->node_info.id = (uint32_t) reply->data.node_info.id; pcmk__str_update(data->node_info.node_name, reply->data.node_info.uname); pcmk__str_update(data->node_info.uuid, reply->data.node_info.uuid); pcmk__str_update(data->node_info.state, reply->data.node_info.state); if (data->show_output) { out->message(out, "node-info", (uint32_t) reply->data.node_info.id, reply->data.node_info.uname, reply->data.node_info.uuid, reply->data.node_info.state, reply->data.node_info.have_quorum, reply->data.node_info.is_remote); } data->rc = pcmk_rc_ok; } /*! * \internal * \brief Process a \p pacemakerd status IPC event * * \param[in,out] pacemakerd_api \p pacemakerd connection * \param[in] event_type Type of event that occurred * \param[in] status Event status * \param[in,out] event_data \p pcmk_pacemakerd_api_reply_t object * containing event-specific data * \param[in,out] user_data \p data_t object for API results and options */ static void pacemakerd_event_cb(pcmk_ipc_api_t *pacemakerd_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { data_t *data = user_data; pcmk__output_t *out = data->out; const pcmk_pacemakerd_api_reply_t *reply = NULL; int rc = validate_pcmkd_reply(data, pacemakerd_api, event_type, status, event_data, pcmk_pacemakerd_reply_ping); if (rc != pcmk_rc_ok) { return; } // Parse desired information from reply reply = (const pcmk_pacemakerd_api_reply_t *) event_data; data->pcmkd_state = reply->data.ping.state; data->rc = pcmk_rc_ok; if (!data->show_output) { return; } if (reply->data.ping.status == pcmk_rc_ok) { out->message(out, "pacemakerd-health", reply->data.ping.sys_from, reply->data.ping.state, NULL, reply->data.ping.last_good); } else { out->message(out, "pacemakerd-health", reply->data.ping.sys_from, reply->data.ping.state, "query failed", time(NULL)); } } static pcmk_ipc_api_t * ipc_connect(data_t *data, enum pcmk_ipc_server server, pcmk_ipc_callback_t cb, enum pcmk_ipc_dispatch dispatch_type, bool eremoteio_ok) { int rc; pcmk__output_t *out = data->out; pcmk_ipc_api_t *api = NULL; rc = pcmk_new_ipc_api(&api, server); if (api == NULL) { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); data->rc = rc; return NULL; } if (cb != NULL) { pcmk_register_ipc_callback(api, cb, data); } rc = pcmk__connect_ipc(api, dispatch_type, 5); if (rc != pcmk_rc_ok) { if (rc == EREMOTEIO) { data->pcmkd_state = pcmk_pacemakerd_state_remote; if (eremoteio_ok) { /* EREMOTEIO may be expected and acceptable for some callers * on a Pacemaker Remote node */ crm_debug("Ignoring %s connection failure: No " "Pacemaker Remote connection", pcmk_ipc_name(api, true)); rc = pcmk_rc_ok; } else { out->err(out, "error: Could not connect to %s: %s", pcmk_ipc_name(api, true), pcmk_rc_str(rc)); } } data->rc = rc; pcmk_free_ipc_api(api); return NULL; } return api; } /*! * \internal * \brief Poll an IPC API connection until timeout or a reply is received * * \param[in,out] data API results and options * \param[in,out] api IPC API connection * \param[in] on_node If not \p NULL, name of the node to poll (used only * for logging) * * \note Sets the \p rc member of \p data on error */ static void poll_until_reply(data_t *data, pcmk_ipc_api_t *api, const char *on_node) { pcmk__output_t *out = data->out; uint64_t start_nsec = qb_util_nano_current_get(); uint64_t end_nsec = 0; uint64_t elapsed_ms = 0; uint64_t remaining_ms = data->message_timeout_ms; while (remaining_ms > 0) { int rc = pcmk_poll_ipc(api, remaining_ms); if (rc == EAGAIN) { // Poll timed out break; } if (rc != pcmk_rc_ok) { out->err(out, "error: Failed to poll %s API%s%s: %s", pcmk_ipc_name(api, true), (on_node != NULL)? " on " : "", pcmk__s(on_node, ""), pcmk_rc_str(rc)); data->rc = rc; return; } pcmk_dispatch_ipc(api); if (data->rc != EAGAIN) { // Received a reply return; } end_nsec = qb_util_nano_current_get(); elapsed_ms = (end_nsec - start_nsec) / QB_TIME_NS_IN_MSEC; remaining_ms = data->message_timeout_ms - elapsed_ms; } out->err(out, "error: Timed out after %ums waiting for reply from %s API%s%s", data->message_timeout_ms, pcmk_ipc_name(api, true), (on_node != NULL)? " on " : "", pcmk__s(on_node, "")); data->rc = EAGAIN; } /*! * \internal * \brief Get and output controller status * * \param[in,out] out Output object * \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 * \p pacemaker-controld 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(pcmk__output_t *out, const char *node_name, unsigned int message_timeout_ms) { data_t data = { .out = out, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, controller_status_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, node_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on %s: %s", pcmk__s(node_name, "DC"), pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, pcmk__s(node_name, "DC")); } pcmk_free_ipc_api(controld_api); } return data.rc; } // Documented in header int pcmk_controller_status(xmlNodePtr *xml, const char *node_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__controller_status(out, node_name, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and output designated controller node name * * \param[in,out] out Output object * \param[in] message_timeout_ms How long to wait for a reply from the * \p pacemaker-controld 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(pcmk__output_t *out, unsigned int message_timeout_ms) { data_t data = { .out = out, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, designated_controller_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_ping(controld_api, NULL); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping controller API on DC: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, "DC"); } pcmk_free_ipc_api(controld_api); } return data.rc; } // Documented in header int pcmk_designated_controller(xmlNodePtr *xml, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__designated_controller(out, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and optionally output node info corresponding to a node ID from * the controller * * \param[in,out] out Output object - * \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 + * \param[in,out] node_id ID of node whose info to get. If \p NULL + * or 0, get the local node's info. If not + * \c NULL, store the true node ID here on * success. - * \param[out] node_name If not \p NULL, where to store the node + * \param[out] node_name If not \c NULL, where to store the node * name - * \param[out] uuid If not \p NULL, where to store the node + * \param[out] uuid If not \c NULL, where to store the node * UUID - * \param[out] state If not \p NULL, where to store the + * \param[out] state If not \c NULL, where to store the * membership state - * \param[out] is_remote If not \p NULL, where to store whether the + * \param[out] is_remote If not \c 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 + * \param[out] have_quorum If not \c NULL, where to store whether the * node has quorum * \param[in] show_output Whether to show the node info * \param[in] message_timeout_ms How long to wait for a reply from the - * \p pacemaker-controld API. If 0, - * \p pcmk_ipc_dispatch_sync will be used. - * Otherwise, \p pcmk_ipc_dispatch_poll will + * \c pacemaker-controld API. If 0, + * \c pcmk_ipc_dispatch_sync will be used. + * Otherwise, \c 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(pcmk__output_t *out, 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) { data_t data = { .out = out, .show_output = show_output, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, .node_info = { .id = (node_id == NULL)? 0 : *node_id, .node_name = node_name, .uuid = uuid, .state = state, }, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *controld_api = NULL; if (node_name != NULL) { *node_name = NULL; } if (uuid != NULL) { *uuid = NULL; } if (state != NULL) { *state = NULL; } if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } controld_api = ipc_connect(&data, pcmk_ipc_controld, node_info_event_cb, dispatch_type, false); if (controld_api != NULL) { int rc = pcmk_controld_api_node_info(controld_api, (node_id != NULL)? *node_id : 0); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not send request to controller API on local " "node: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, controld_api, "local node"); } pcmk_free_ipc_api(controld_api); } if (data.rc != pcmk_rc_ok) { return data.rc; } // String outputs are set in callback if (node_id != NULL) { *node_id = data.node_info.id; } if (have_quorum != NULL) { *have_quorum = data.node_info.have_quorum; } if (is_remote != NULL) { *is_remote = data.node_info.is_remote; } return data.rc; } // Documented in header 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) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; CRM_ASSERT(node_name != NULL); rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__query_node_info(out, node_id, node_name, uuid, state, have_quorum, is_remote, show_output, message_timeout_ms); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /*! * \internal * \brief Get and optionally output \p pacemakerd status * * \param[in,out] out Output object * \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. * \param[in] show_output Whether to output the \p pacemakerd state * \param[out] state Where to store the \p pacemakerd state, if * not \p NULL * * \return Standard Pacemaker return code * * \note This function sets \p state to \p pcmk_pacemakerd_state_remote and * returns \p pcmk_rc_ok if the IPC connection attempt returns * \p EREMOTEIO. That code indicates that this is a Pacemaker Remote node * with \p pacemaker-remoted running. The node may be connected to the * cluster. */ int pcmk__pacemakerd_status(pcmk__output_t *out, const char *ipc_name, unsigned int message_timeout_ms, bool show_output, enum pcmk_pacemakerd_state *state) { data_t data = { .out = out, .show_output = show_output, .rc = EAGAIN, .message_timeout_ms = message_timeout_ms, .pcmkd_state = pcmk_pacemakerd_state_invalid, }; enum pcmk_ipc_dispatch dispatch_type = pcmk_ipc_dispatch_poll; pcmk_ipc_api_t *pacemakerd_api = NULL; if (message_timeout_ms == 0) { dispatch_type = pcmk_ipc_dispatch_sync; } pacemakerd_api = ipc_connect(&data, pcmk_ipc_pacemakerd, pacemakerd_event_cb, dispatch_type, true); if (pacemakerd_api != NULL) { int rc = pcmk_pacemakerd_api_ping(pacemakerd_api, ipc_name); if (rc != pcmk_rc_ok) { out->err(out, "error: Could not ping launcher API: %s", pcmk_rc_str(rc)); data.rc = rc; } if (dispatch_type == pcmk_ipc_dispatch_poll) { poll_until_reply(&data, pacemakerd_api, NULL); } pcmk_free_ipc_api(pacemakerd_api); } else if ((data.pcmkd_state == pcmk_pacemakerd_state_remote) && show_output) { // No API connection so the callback wasn't run out->message(out, "pacemakerd-health", NULL, data.pcmkd_state, NULL, time(NULL)); } if (state != NULL) { *state = data.pcmkd_state; } return data.rc; } // Documented in header int pcmk_pacemakerd_status(xmlNodePtr *xml, const char *ipc_name, unsigned int message_timeout_ms) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__pacemakerd_status(out, ipc_name, message_timeout_ms, true, NULL); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } /* user data for looping through remote node xpath searches */ struct node_data { pcmk__output_t *out; int found; const char *field; /* XML attribute to check for node name */ const char *type; bool bash_export; }; static void remote_node_print_helper(xmlNode *result, void *user_data) { struct node_data *data = user_data; pcmk__output_t *out = data->out; const char *name = crm_element_value(result, PCMK_XA_UNAME); const char *id = crm_element_value(result, data->field); // node name and node id are the same for remote/guest nodes out->message(out, "crmadmin-node", data->type, pcmk__s(name, id), id, data->bash_export); data->found++; } // \return Standard Pacemaker return code int pcmk__list_nodes(pcmk__output_t *out, const char *node_types, bool bash_export) { xmlNode *xml_node = NULL; int rc; rc = cib__signon_query(out, NULL, &xml_node); if (rc == pcmk_rc_ok) { struct node_data data = { .out = out, .found = 0, .bash_export = bash_export }; /* PCMK_XE_NODES acts as the list's element name for CLI tools that * use pcmk__output_enable_list_element. Otherwise PCMK_XE_NODES is * the value of the list's PCMK_XA_NAME attribute. */ out->begin_list(out, NULL, NULL, PCMK_XE_NODES); if (!pcmk__str_empty(node_types) && strstr(node_types, "all")) { node_types = NULL; } if (pcmk__str_empty(node_types) || strstr(node_types, "cluster")) { data.field = PCMK_XA_ID; data.type = "cluster"; crm_foreach_xpath_result(xml_node, PCMK__XP_MEMBER_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || strstr(node_types, "guest")) { data.field = PCMK_XA_VALUE; data.type = "guest"; crm_foreach_xpath_result(xml_node, PCMK__XP_GUEST_NODE_CONFIG, remote_node_print_helper, &data); } if (pcmk__str_empty(node_types) || pcmk__str_eq(node_types, ",|^remote", pcmk__str_regex)) { data.field = PCMK_XA_ID; data.type = "remote"; crm_foreach_xpath_result(xml_node, PCMK__XP_REMOTE_NODE_CONFIG, remote_node_print_helper, &data); } out->end_list(out); if (data.found == 0) { out->info(out, "No nodes configured"); } pcmk__xml_free(xml_node); } return rc; } int pcmk_list_nodes(xmlNodePtr *xml, const char *node_types) { pcmk__output_t *out = NULL; int rc = pcmk_rc_ok; rc = pcmk__xml_output_new(&out, xml); if (rc != pcmk_rc_ok) { return rc; } pcmk__register_lib_messages(out); rc = pcmk__list_nodes(out, node_types, FALSE); pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml); return rc; } diff --git a/tools/crm_node.c b/tools/crm_node.c index c180d073d2..d200523714 100644 --- a/tools/crm_node.c +++ b/tools/crm_node.c @@ -1,875 +1,875 @@ /* * Copyright 2004-2024 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 "crm_node - Tool for displaying low-level node information" struct { gboolean corosync; gboolean dangerous_cmd; gboolean force_flag; char command; int nodeid; char *target_uname; } options = { .command = '\0', .force_flag = FALSE }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GError *error = NULL; static GMainLoop *mainloop = NULL; static crm_exit_t exit_code = CRM_EX_OK; static pcmk__output_t *out = NULL; #define INDENT " " static GOptionEntry command_entries[] = { { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display this node's cluster id", NULL }, { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display all known members (past and present) of this cluster", NULL }, { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the name used by the cluster for this node", NULL }, { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the members of this partition", NULL }, { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display a 1 if our partition has quorum, 0 if not", NULL }, { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb, "Display the name used by the cluster for the node with the specified ID", "ID" }, { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb, "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n" INDENT "configuration and caches (the node must already have been removed from\n" INDENT "the underlying cluster stack configuration", "NAME" }, { NULL } }; static GOptionEntry addl_entries[] = { { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag, NULL, NULL }, #if SUPPORT_COROSYNC /* Unused and deprecated */ { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync, NULL, NULL }, #endif // @TODO add timeout option for when IPC replies are needed { NULL } }; static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_eq("-i", option_name, pcmk__str_casei) || pcmk__str_eq("--cluster-id", option_name, pcmk__str_casei)) { options.command = 'i'; } else if (pcmk__str_eq("-l", option_name, pcmk__str_casei) || pcmk__str_eq("--list", option_name, pcmk__str_casei)) { options.command = 'l'; } else if (pcmk__str_eq("-n", option_name, pcmk__str_casei) || pcmk__str_eq("--name", option_name, pcmk__str_casei)) { options.command = 'n'; } else if (pcmk__str_eq("-p", option_name, pcmk__str_casei) || pcmk__str_eq("--partition", option_name, pcmk__str_casei)) { options.command = 'p'; } else if (pcmk__str_eq("-q", option_name, pcmk__str_casei) || pcmk__str_eq("--quorum", option_name, pcmk__str_casei)) { options.command = 'q'; } else { g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s", option_name); return FALSE; } return TRUE; } gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.command = 'N'; pcmk__scan_min_int(optarg, &(options.nodeid), 0); return TRUE; } gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (optarg == NULL) { g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument"); return FALSE; } options.command = 'R'; options.dangerous_cmd = TRUE; pcmk__str_update(&options.target_uname, optarg); return TRUE; } PCMK__OUTPUT_ARGS("node-id", "uint32_t") static int node_id_default(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); out->info(out, "%" PRIu32, node_id); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-id", "uint32_t") static int node_id_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); char *id_s = crm_strdup_printf("%" PRIu32, node_id); pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO, PCMK_XA_NODEID, id_s, NULL); free(id_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("simple-node-list", "GList *") static int simple_node_list_default(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; out->info(out, "%" PRIu32 " %s %s", node->id, pcmk__s(node->uname, ""), pcmk__s(node->state, "")); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("simple-node-list", "GList *") static int simple_node_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); out->begin_list(out, NULL, NULL, PCMK_XE_NODES); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; char *id_s = crm_strdup_printf("%" PRIu32, node->id); pcmk__output_create_xml_node(out, PCMK_XE_NODE, PCMK_XA_ID, id_s, PCMK_XA_NAME, node->uname, PCMK_XA_STATE, node->state, NULL); free(id_s); } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-name", "uint32_t", "const char *") static int node_name_default(pcmk__output_t *out, va_list args) { uint32_t node_id G_GNUC_UNUSED = va_arg(args, uint32_t); const char *node_name = va_arg(args, const char *); out->info(out, "%s", node_name); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-name", "uint32_t", "const char *") static int node_name_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); const char *node_name = va_arg(args, const char *); char *id_s = crm_strdup_printf("%" PRIu32, node_id); pcmk__output_create_xml_node(out, PCMK_XE_NODE_INFO, PCMK_XA_NODEID, id_s, PCMK_XA_UNAME, node_name, NULL); free(id_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("partition-list", "GList *") static int partition_list_default(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); GString *buffer = NULL; for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; if (pcmk__str_eq(node->state, "member", pcmk__str_none)) { pcmk__add_separated_word(&buffer, 128, pcmk__s(node->uname, ""), " "); } } if (buffer != NULL) { out->info(out, "%s", buffer->str); g_string_free(buffer, TRUE); return pcmk_rc_ok; } return pcmk_rc_no_output; } PCMK__OUTPUT_ARGS("partition-list", "GList *") static int partition_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); out->begin_list(out, NULL, NULL, PCMK_XE_NODES); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; if (pcmk__str_eq(node->state, "member", pcmk__str_none)) { char *id_s = crm_strdup_printf("%" PRIu32, node->id); pcmk__output_create_xml_node(out, PCMK_XE_NODE, PCMK_XA_ID, id_s, PCMK_XA_NAME, node->uname, PCMK_XA_STATE, node->state, NULL); free(id_s); } } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("quorum", "bool") static int quorum_default(pcmk__output_t *out, va_list args) { bool have_quorum = va_arg(args, int); out->info(out, "%d", have_quorum); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("quorum", "bool") static int quorum_xml(pcmk__output_t *out, va_list args) { bool have_quorum = va_arg(args, int); pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_INFO, PCMK_XA_QUORUM, pcmk__btoa(have_quorum), NULL); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "node-id", "default", node_id_default }, { "node-id", "xml", node_id_xml }, { "node-name", "default", node_name_default }, { "node-name", "xml", node_name_xml }, { "partition-list", "default", partition_list_default }, { "partition-list", "xml", partition_list_xml }, { "quorum", "default", quorum_default }, { "quorum", "xml", quorum_xml }, { "simple-node-list", "default", simple_node_list_default }, { "simple-node-list", "xml", simple_node_list_xml }, { NULL, NULL, NULL } }; static gint sort_node(gconstpointer a, gconstpointer b) { const pcmk_controld_api_node_t *node_a = a; const pcmk_controld_api_node_t *node_b = b; return pcmk__numeric_strcasecmp((node_a->uname? node_a->uname : ""), (node_b->uname? node_b->uname : "")); } static void controller_event_cb(pcmk_ipc_api_t *controld_api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_controld_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Lost connection to controller"); } goto done; break; case pcmk_ipc_event_reply: break; default: return; } if (status != CRM_EX_OK) { exit_code = status; g_set_error(&error, PCMK__EXITC_ERROR, status, "Bad reply from controller: %s", crm_exit_str(status)); goto done; } if (reply->reply_type != pcmk_controld_reply_nodes) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_INDETERMINATE, "Unknown reply type %d from controller", reply->reply_type); goto done; } reply->data.nodes = g_list_sort(reply->data.nodes, sort_node); if (options.command == 'p') { out->message(out, "partition-list", reply->data.nodes); } else if (options.command == 'l') { out->message(out, "simple-node-list", reply->data.nodes); } // Success exit_code = CRM_EX_OK; done: pcmk_disconnect_ipc(controld_api); pcmk_quit_main_loop(mainloop, 10); } static void run_controller_mainloop(void) { pcmk_ipc_api_t *controld_api = NULL; int rc; // Set disconnect exit code to handle unexpected disconnects exit_code = CRM_EX_DISCONNECT; // Create controller IPC object rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s", pcmk_rc_str(rc)); return; } pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); // Connect to controller rc = pcmk__connect_ipc(controld_api, pcmk_ipc_dispatch_main, 5); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to %s: %s", pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc)); return; } rc = pcmk_controld_api_list_nodes(controld_api); if (rc != pcmk_rc_ok) { pcmk_disconnect_ipc(controld_api); exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not ping controller: %s", pcmk_rc_str(rc)); return; } // Run main loop to get controller reply via controller_event_cb() mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); g_main_loop_unref(mainloop); mainloop = NULL; pcmk_free_ipc_api(controld_api); } static void print_node_id(void) { - uint32_t nodeid; + uint32_t nodeid = 0; int rc = pcmk__query_node_info(out, &nodeid, NULL, NULL, NULL, NULL, NULL, false, 0); if (rc != pcmk_rc_ok) { /* pcmk__query_node_info already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "node-id", nodeid); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print node ID: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } static void print_node_name(uint32_t nodeid) { int rc = pcmk_rc_ok; char *node_name = NULL; if (nodeid == 0) { // Check environment first (i.e. when called by resource agent) const char *name = getenv("OCF_RESKEY_" CRM_META "_" PCMK__META_ON_NODE); if (name != NULL) { rc = out->message(out, "node-name", 0UL, name); goto done; } } // Otherwise ask the controller /* pcmk__query_node_name already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ rc = pcmk__query_node_name(out, nodeid, &node_name, 0); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "node-name", 0UL, node_name); done: if (node_name != NULL) { free(node_name); } if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print node name: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } static void print_quorum(void) { bool quorum; int rc = pcmk__query_node_info(out, NULL, NULL, NULL, NULL, &quorum, NULL, false, 0); if (rc != pcmk_rc_ok) { /* pcmk__query_node_info already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "quorum", quorum); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print quorum status: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } /*! * \internal * \brief Extend a transaction by removing a node from a CIB section * * \param[in,out] cib Active CIB connection * \param[in] element CIB element containing node name and/or ID * \param[in] section CIB section that \p element is in * \param[in] node_name Name of node to purge (NULL to leave unspecified) * \param[in] node_id Node ID of node to purge (0 to leave unspecified) * * \note At least one of node_name and node_id must be specified. * \return Standard Pacemaker return code */ static int remove_from_section(cib_t *cib, const char *element, const char *section, const char *node_name, long node_id) { int rc = pcmk_rc_ok; xmlNode *xml = pcmk__xe_create(NULL, element); crm_xml_add(xml, PCMK_XA_UNAME, node_name); if (node_id > 0) { pcmk__xe_set_id(xml, "%ld", node_id); } rc = cib->cmds->remove(cib, section, xml, cib_transaction); pcmk__xml_free(xml); return (rc >= 0)? pcmk_rc_ok : pcmk_legacy2rc(rc); } /*! * \internal * \brief Purge a node from CIB * * \param[in] node_name Name of node to purge (or NULL to leave unspecified) * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) * * \note At least one of node_name and node_id must be specified. * \return Standard Pacemaker return code */ static int purge_node_from_cib(const char *node_name, long node_id) { int rc = pcmk_rc_ok; int commit_rc = pcmk_rc_ok; cib_t *cib = NULL; // Connect to CIB and start a transaction cib = cib_new(); if (cib == NULL) { return ENOTCONN; } rc = cib__signon_attempts(cib, crm_system_name, cib_command, 5); if (rc == pcmk_ok) { rc = cib->cmds->init_transaction(cib); } if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); cib__clean_up_connection(&cib); return rc; } // Remove from configuration and status rc = remove_from_section(cib, PCMK_XE_NODE, PCMK_XE_NODES, node_name, node_id); if (rc == pcmk_rc_ok) { rc = remove_from_section(cib, PCMK__XE_NODE_STATE, PCMK_XE_STATUS, node_name, node_id); } // Commit the transaction commit_rc = cib->cmds->end_transaction(cib, (rc == pcmk_rc_ok), cib_sync_call); cib__clean_up_connection(&cib); if ((rc == pcmk_rc_ok) && (commit_rc == pcmk_ok)) { crm_debug("Purged node %s (%ld) from CIB", pcmk__s(node_name, "by ID"), node_id); } return rc; } /*! * \internal * \brief Purge a node from a single server's peer cache * * \param[in] server IPC server to send request to * \param[in] node_name Name of node to purge (or NULL to leave unspecified) * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) * * \note At least one of node_name and node_id must be specified. * \return Standard Pacemaker return code */ static int purge_node_from(enum pcmk_ipc_server server, const char *node_name, long node_id) { pcmk_ipc_api_t *api = NULL; int rc; rc = pcmk_new_ipc_api(&api, server); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk__connect_ipc(api, pcmk_ipc_dispatch_sync, 5); if (rc != pcmk_rc_ok) { goto done; } rc = pcmk_ipc_purge_node(api, node_name, node_id); done: if (rc != pcmk_rc_ok) { // Debug message already logged on success g_set_error(&error, PCMK__RC_ERROR, rc, "Could not purge node %s from %s: %s", pcmk__s(node_name, "by ID"), pcmk_ipc_name(api, true), pcmk_rc_str(rc)); } pcmk_free_ipc_api(api); return rc; } /*! * \internal * \brief Purge a node from the fencer's peer cache * * \param[in] node_name Name of node to purge (or NULL to leave unspecified) * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) * * \note At least one of node_name and node_id must be specified. * \return Standard Pacemaker return code */ static int purge_node_from_fencer(const char *node_name, long node_id) { int rc = pcmk_rc_ok; crm_ipc_t *conn = NULL; xmlNode *cmd = NULL; conn = crm_ipc_new("stonith-ng", 0); if (conn == NULL) { rc = ENOTCONN; exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to fencer to purge node %s", pcmk__s(node_name, "by ID")); return rc; } rc = pcmk__connect_generic_ipc(conn); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to fencer to purge node %s: %s", pcmk__s(node_name, "by ID"), pcmk_rc_str(rc)); crm_ipc_destroy(conn); return rc; } cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, "stonith-ng", crm_system_name, NULL); if (node_id > 0) { pcmk__xe_set_id(cmd, "%ld", node_id); } crm_xml_add(cmd, PCMK_XA_UNAME, node_name); rc = crm_ipc_send(conn, cmd, 0, 0, NULL); if (rc >= 0) { rc = pcmk_rc_ok; crm_debug("Purged node %s (%ld) from fencer", pcmk__s(node_name, "by ID"), node_id); } else { rc = pcmk_legacy2rc(rc); fprintf(stderr, "Could not purge node %s from fencer: %s\n", pcmk__s(node_name, "by ID"), pcmk_rc_str(rc)); } pcmk__xml_free(cmd); crm_ipc_close(conn); crm_ipc_destroy(conn); return rc; } static void remove_node(const char *target_uname) { int rc = pcmk_rc_ok; long nodeid = 0; const char *node_name = NULL; char *endptr = NULL; const enum pcmk_ipc_server servers[] = { pcmk_ipc_controld, pcmk_ipc_attrd, }; // Check whether node was specified by name or numeric ID errno = 0; nodeid = strtol(target_uname, &endptr, 10); if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0') || (nodeid <= 0)) { // It's not a positive integer, so assume it's a node name nodeid = 0; node_name = target_uname; } for (int i = 0; i < PCMK__NELEM(servers); ++i) { rc = purge_node_from(servers[i], node_name, nodeid); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); return; } } // The fencer hasn't been converted to pcmk_ipc_api_t yet rc = purge_node_from_fencer(node_name, nodeid); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); return; } // Lastly, purge the node from the CIB itself rc = purge_node_from_cib(node_name, nodeid); exit_code = pcmk_rc2exitc(rc); } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { NULL } }; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); /* Add the -q option, which cannot be part of the globally supported options * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "NR"); GOptionContext *context = build_arg_context(args, &output_group); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_node", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); 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 (args->version) { out->version(out, false); goto done; } if (options.command == 0) { char *help = g_option_context_get_help(context, TRUE, NULL); out->err(out, "%s", help); g_free(help); exit_code = CRM_EX_USAGE; goto done; } if (options.dangerous_cmd && options.force_flag == FALSE) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed."); goto done; } pcmk__register_lib_messages(out); pcmk__register_messages(out, fmt_functions); switch (options.command) { case 'i': print_node_id(); break; case 'n': print_node_name(0); break; case 'q': print_quorum(); break; case 'N': print_node_name(options.nodeid); break; case 'R': remove_node(options.target_uname); break; case 'l': case 'p': run_controller_mainloop(); break; default: break; } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(&error, out); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); return crm_exit(exit_code); }