diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index decccee4ee..b13aeb129e 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -1,1688 +1,1679 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include // uint32_t, uint64_t, UINT64_C() #include #include #include // PRIu64 #include #include #include #include #include #include #include #define EXIT_ESCALATION_MS 10000 #define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->uname) static unsigned long cib_local_bcast_num = 0; static pcmk__output_t *logger_out = NULL; typedef struct cib_local_notify_s { xmlNode *notify_src; char *client_id; gboolean from_peer; gboolean sync_reply; } cib_local_notify_t; int next_client_id = 0; gboolean legacy_mode = FALSE; qb_ipcs_service_t *ipcs_ro = NULL; qb_ipcs_service_t *ipcs_rw = NULL; qb_ipcs_service_t *ipcs_shm = NULL; void send_cib_replace(const xmlNode * sync_request, const char *host); static void cib_process_request(xmlNode *request, gboolean privileged, const pcmk__client_t *cib_client); static int cib_process_command(xmlNode *request, xmlNode **reply, xmlNode **cib_diff, gboolean privileged); gboolean cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged); gboolean cib_legacy_mode(void) { return legacy_mode; } static int32_t cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { if (cib_shutdown_flag) { crm_info("Ignoring new IPC client [%d] during shutdown", pcmk__client_pid(c)); return -EPERM; } if (pcmk__new_client(c, uid, gid) == NULL) { return -EIO; } return 0; } static int32_t cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size) { pcmk__client_t *client = pcmk__find_client(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, TRUE); } static int32_t cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size) { pcmk__client_t *client = pcmk__find_client(c); crm_trace("%p message from %s", c, client->id); return cib_common_callback(c, data, size, FALSE); } /* Error code means? */ static int32_t cib_ipc_closed(qb_ipcs_connection_t * c) { pcmk__client_t *client = pcmk__find_client(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); pcmk__free_client(client); return 0; } static void cib_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); cib_ipc_closed(c); if (cib_shutdown_flag) { cib_shutdown(0); } } struct qb_ipcs_service_handlers ipc_ro_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = NULL, .msg_process = cib_ipc_dispatch_ro, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; struct qb_ipcs_service_handlers ipc_rw_callbacks = { .connection_accept = cib_ipc_accept, .connection_created = NULL, .msg_process = cib_ipc_dispatch_rw, .connection_closed = cib_ipc_closed, .connection_destroyed = cib_ipc_destroy }; void cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request, pcmk__client_t *cib_client, gboolean privileged) { const char *op = crm_element_value(op_request, F_CIB_OPERATION); if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { if (flags & crm_ipc_client_response) { xmlNode *ack = create_xml_node(NULL, __func__); crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id); pcmk__ipc_send_xml(cib_client, id, ack, flags); cib_client->request_id = 0; free_xml(ack); } return; } else if (pcmk__str_eq(op, T_CIB_NOTIFY, pcmk__str_none)) { /* Update the notify filters for this client */ int on_off = 0; crm_exit_t status = CRM_EX_OK; uint64_t bit = UINT64_C(0); const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE); crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off); crm_debug("Setting %s callbacks %s for client %s", type, (on_off? "on" : "off"), pcmk__client_name(cib_client)); if (pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) { bit = cib_notify_post; } else if (pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) { bit = cib_notify_pre; } else if (pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) { bit = cib_notify_confirm; } else if (pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) { bit = cib_notify_diff; } else if (pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) { bit = cib_notify_replace; } else { status = CRM_EX_INVALID_PARAM; } if (bit != 0) { if (on_off) { pcmk__set_client_flags(cib_client, bit); } else { pcmk__clear_client_flags(cib_client, bit); } } pcmk__ipc_send_ack(cib_client, id, flags, "ack", NULL, status); return; } cib_process_request(op_request, privileged, cib_client); } int32_t cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged) { uint32_t id = 0; uint32_t flags = 0; int call_options = 0; pcmk__client_t *cib_client = pcmk__find_client(c); xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags); if (op_request) { crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options); } if (op_request == NULL) { crm_trace("Invalid message from %p", c); pcmk__ipc_send_ack(cib_client, id, flags, "nack", NULL, CRM_EX_PROTOCOL); return 0; } else if(cib_client == NULL) { crm_trace("Invalid client %p", c); return 0; } if (pcmk_is_set(call_options, cib_sync_call)) { CRM_LOG_ASSERT(flags & crm_ipc_client_response); CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */ cib_client->request_id = id; /* Reply only to the last one */ } if (cib_client->name == NULL) { const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME); if (value == NULL) { cib_client->name = pcmk__itoa(cib_client->pid); } else { cib_client->name = strdup(value); if (crm_is_daemon_name(value)) { pcmk__set_client_flags(cib_client, cib_is_daemon); } } } /* Allow cluster daemons more leeway before being evicted */ if (pcmk_is_set(cib_client->flags, cib_is_daemon)) { const char *qmax = cib_config_lookup("cluster-ipc-limit"); if (pcmk__set_client_queue_max(cib_client, qmax)) { crm_trace("IPC threshold for client %s[%u] is now %u", pcmk__client_name(cib_client), cib_client->pid, cib_client->queue_max); } } crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); CRM_LOG_ASSERT(cib_client->user != NULL); pcmk__update_acl_user(op_request, F_CIB_USER, cib_client->user); cib_common_callback_worker(id, flags, op_request, cib_client, privileged); free_xml(op_request); return 0; } static uint64_t ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = FALSE; int sync_our_cib(xmlNode * request, gboolean all); static gboolean cib_digester_cb(gpointer data) { if (based_is_primary) { char buffer[32]; xmlNode *ping = create_xml_node(NULL, "ping"); ping_seq++; free(ping_digest); ping_digest = NULL; ping_modified_since = FALSE; snprintf(buffer, 32, "%" PRIu64, ping_seq); crm_trace("Requesting peer digests (%s)", buffer); crm_xml_add(ping, F_TYPE, "cib"); crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING); crm_xml_add(ping, F_CIB_PING_ID, buffer); crm_xml_add(ping, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); send_cluster_message(NULL, crm_msg_cib, ping, TRUE); free_xml(ping); } return FALSE; } static void process_ping_reply(xmlNode *reply) { uint64_t seq = 0; const char *host = crm_element_value(reply, F_ORIG); xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA); const char *seq_s = crm_element_value(pong, F_CIB_PING_ID); const char *digest = crm_element_value(pong, XML_ATTR_DIGEST); if (seq_s == NULL) { crm_debug("Ignoring ping reply with no " F_CIB_PING_ID); return; } else { long long seq_ll; if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) { return; } seq = (uint64_t) seq_ll; } if(digest == NULL) { crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host); } else if(seq != ping_seq) { crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host); } else if(ping_modified_since) { crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host); } else { const char *version = crm_element_value(pong, XML_ATTR_CRM_VERSION); if(ping_digest == NULL) { crm_trace("Calculating new digest"); ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version); } crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest); if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA); crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p", crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN), crm_element_value(the_cib, XML_ATTR_GENERATION), crm_element_value(the_cib, XML_ATTR_NUMUPDATES), ping_digest, host, remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_", remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_", remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_", digest, remote_cib); if(remote_cib && remote_cib->children) { // Additional debug - int rc = pcmk_rc_ok; - - /* Unclear whether this is needed for its side effects (it may - * mark the_cib with pcmk__xf_skip) if we can't log the changes - */ - xml_calculate_changes(the_cib, remote_cib); - if (logger_out == NULL) { - rc = pcmk__log_output_new(&logger_out); - CRM_LOG_ASSERT(rc == pcmk_rc_ok); - } - - if (rc == pcmk_rc_ok) { - pcmk__output_set_log_level(logger_out, LOG_INFO); - pcmk__xml_show_changes(logger_out, remote_cib); - crm_trace("End of differences"); + CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, + {free_xml(remote_cib); return;}); } + pcmk__output_set_log_level(logger_out, LOG_INFO); + xml_calculate_changes(the_cib, remote_cib); + pcmk__xml_show_changes(logger_out, remote_cib); + crm_trace("End of differences"); } free_xml(remote_cib); sync_our_cib(reply, FALSE); } } } static void do_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { int rid = 0; int call_id = 0; pcmk__client_t *client_obj = NULL; CRM_ASSERT(notify_src && client_id); crm_element_value_int(notify_src, F_CIB_CALLID, &call_id); client_obj = pcmk__find_client_by_id(client_id); if (client_obj == NULL) { crm_debug("Could not send response %d: client %s not found", call_id, client_id); return; } if (sync_reply) { if (client_obj->ipcs) { CRM_LOG_ASSERT(client_obj->request_id); rid = client_obj->request_id; client_obj->request_id = 0; crm_trace("Sending response %d to client %s%s", rid, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } else { crm_trace("Sending response (call %d) to client %s%s", call_id, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } } else { crm_trace("Sending event %d to client %s%s", call_id, pcmk__client_name(client_obj), (from_peer? " (originator of delegated request)" : "")); } switch (PCMK__CLIENT_TYPE(client_obj)) { case pcmk__client_ipc: { int rc = pcmk__ipc_send_xml(client_obj, rid, notify_src, (sync_reply? crm_ipc_flags_none : crm_ipc_server_event)); if (rc != pcmk_rc_ok) { crm_warn("%s reply to client %s failed: %s " CRM_XS " rc=%d", (sync_reply? "Synchronous" : "Asynchronous"), pcmk__client_name(client_obj), pcmk_rc_str(rc), rc); } } break; #ifdef HAVE_GNUTLS_GNUTLS_H case pcmk__client_tls: #endif case pcmk__client_tcp: pcmk__remote_send_xml(client_obj->remote, notify_src); break; default: crm_err("Unknown transport for client %s " CRM_XS " flags=%#016" PRIx64, pcmk__client_name(client_obj), client_obj->flags); } } static void local_notify_destroy_callback(gpointer data) { cib_local_notify_t *notify = data; free_xml(notify->notify_src); free(notify->client_id); free(notify); } static void check_local_notify(int bcast_id) { cib_local_notify_t *notify = NULL; if (!local_notify_queue) { return; } notify = pcmk__intkey_table_lookup(local_notify_queue, bcast_id); if (notify) { do_local_notify(notify->notify_src, notify->client_id, notify->sync_reply, notify->from_peer); pcmk__intkey_table_remove(local_notify_queue, bcast_id); } } static void queue_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer) { cib_local_notify_t *notify = calloc(1, sizeof(cib_local_notify_t)); notify->notify_src = notify_src; notify->client_id = strdup(client_id); notify->sync_reply = sync_reply; notify->from_peer = from_peer; if (!local_notify_queue) { local_notify_queue = pcmk__intkey_table(local_notify_destroy_callback); } pcmk__intkey_table_insert(local_notify_queue, cib_local_bcast_num, notify); // cppcheck doesn't know notify will get freed when hash table is destroyed // cppcheck-suppress memleak } static void parse_local_options_v1(const pcmk__client_t *cib_client, int call_type, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if (cib_op_modifies(call_type) && !(call_options & cib_inhibit_bcast)) { /* we need to send an update anyway */ *needs_reply = TRUE; } else { *needs_reply = FALSE; } if (host == NULL && (call_options & cib_scope_local)) { crm_trace("Processing locally scoped %s op from client %s", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if ((host == NULL) && based_is_primary) { crm_trace("Processing %s op locally from client %s as primary", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing locally addressed %s op from client %s", op, pcmk__client_name(cib_client)); *local_notify = TRUE; } else if (stand_alone) { *needs_forward = FALSE; *local_notify = TRUE; *process = TRUE; } else { crm_trace("%s op from %s needs to be forwarded to client %s", op, pcmk__client_name(cib_client), pcmk__s(host, "the primary instance")); *needs_forward = TRUE; *process = FALSE; } } static void parse_local_options_v2(const pcmk__client_t *cib_client, int call_type, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if (cib_op_modifies(call_type)) { if (pcmk__str_any_of(op, PCMK__CIB_REQUEST_PRIMARY, PCMK__CIB_REQUEST_SECONDARY, NULL)) { /* Always handle these locally */ *process = TRUE; *needs_reply = FALSE; *local_notify = TRUE; *needs_forward = FALSE; return; } else { /* Redirect all other updates via CPG */ *needs_reply = TRUE; *needs_forward = TRUE; *process = FALSE; crm_trace("%s op from %s needs to be forwarded to client %s", op, pcmk__client_name(cib_client), pcmk__s(host, "the primary instance")); return; } } *process = TRUE; *needs_reply = FALSE; *local_notify = TRUE; *needs_forward = FALSE; if (stand_alone) { crm_trace("Processing %s op from client %s (stand-alone)", op, pcmk__client_name(cib_client)); } else if (host == NULL) { crm_trace("Processing unaddressed %s op from client %s", op, pcmk__client_name(cib_client)); } else if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing locally addressed %s op from client %s", op, pcmk__client_name(cib_client)); } else { crm_trace("%s op from %s needs to be forwarded to client %s", op, pcmk__client_name(cib_client), host); *needs_forward = TRUE; *process = FALSE; } } static void parse_local_options(const pcmk__client_t *cib_client, int call_type, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if(cib_legacy_mode()) { parse_local_options_v1(cib_client, call_type, call_options, host, op, local_notify, needs_reply, process, needs_forward); } else { parse_local_options_v2(cib_client, call_type, call_options, host, op, local_notify, needs_reply, process, needs_forward); } } static gboolean parse_peer_options_v1(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { const char *op = NULL; const char *host = NULL; const char *delegated = NULL; const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { *needs_reply = FALSE; if (is_reply) { *local_notify = TRUE; crm_trace("Processing global/peer update from %s" " that originated from us", originator); } else { crm_trace("Processing global/peer update from %s", originator); } return TRUE; } op = crm_element_value(request, F_CIB_OPERATION); crm_trace("Processing %s request sent by %s", op, originator); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { /* Always process these */ *local_notify = FALSE; if (reply_to == NULL || is_reply) { *process = TRUE; } if (is_reply) { *needs_reply = FALSE; } return *process; } if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { process_ping_reply(request); return FALSE; } if (is_reply) { crm_trace("Forward reply sent from %s to local clients", originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } host = crm_element_value(request, F_CIB_HOST); if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing %s request sent to us from %s", op, originator); return TRUE; } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator); *needs_reply = TRUE; return TRUE; } else if ((host == NULL) && based_is_primary) { crm_trace("Processing %s request sent to primary instance from %s", op, originator); return TRUE; } delegated = crm_element_value(request, F_CIB_DELEGATED); if (delegated != NULL) { crm_trace("Ignoring message for primary instance"); } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring msg for instance on %s", host); } else if ((reply_to == NULL) && !based_is_primary) { // This is for the primary instance, and we're not it crm_trace("Ignoring reply for primary instance"); } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { if (reply_to != NULL) { crm_debug("Processing %s from %s", op, originator); *needs_reply = FALSE; } else { crm_debug("Processing %s reply from %s", op, originator); } return TRUE; } else { crm_err("Nothing for us to do?"); crm_log_xml_err(request, "Peer[inbound]"); } return FALSE; } static gboolean parse_peer_options_v2(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { const char *host = NULL; const char *delegated = crm_element_value(request, F_CIB_DELEGATED); const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { /* sync_our_cib() sets F_CIB_ISREPLY */ if (reply_to) { delegated = reply_to; } goto skip_is_reply; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL, pcmk__str_none)) { // Nothing to do } else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { process_ping_reply(request); return FALSE; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) { /* Only the DC (node with the oldest software) should process * this operation if F_CIB_SCHEMA_MAX is unset * * If the DC is happy it will then send out another * PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual * upgrade. * * Except this time F_CIB_SCHEMA_MAX will be set which puts a * limit on how far newer nodes will go */ const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX); const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC); crm_trace("Parsing %s operation%s for %s with max=%s and upgrade_rc=%s", op, (is_reply? " reply" : ""), (based_is_primary? "primary" : "secondary"), (max? max : "none"), (upgrade_rc? upgrade_rc : "none")); if (upgrade_rc != NULL) { // Our upgrade request was rejected by DC, notify clients of result crm_xml_add(request, F_CIB_RC, upgrade_rc); } else if ((max == NULL) && based_is_primary) { /* We are the DC, check if this upgrade is allowed */ goto skip_is_reply; } else if(max) { /* Ok, go ahead and upgrade to 'max' */ goto skip_is_reply; } else { // Ignore broadcast client requests when we're not DC return FALSE; } } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { crm_info("Detected legacy %s global update from %s", op, originator); send_sync_request(NULL); legacy_mode = TRUE; return FALSE; } else if (is_reply && cib_op_modifies(call_type)) { crm_trace("Ignoring legacy %s reply sent from %s to local clients", op, originator); return FALSE; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { /* Legacy handling */ crm_debug("Legacy handling of %s message from %s", op, originator); *local_notify = FALSE; if (reply_to == NULL) { *process = TRUE; } return *process; } if(is_reply) { crm_trace("Handling %s reply sent from %s to local clients", op, originator); *process = FALSE; *needs_reply = FALSE; *local_notify = TRUE; return TRUE; } skip_is_reply: *process = TRUE; *needs_reply = FALSE; *local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei); host = crm_element_value(request, F_CIB_HOST); if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { crm_trace("Processing %s request sent to us from %s", op, originator); *needs_reply = TRUE; return TRUE; } else if (host != NULL) { /* this is for a specific instance and we're not it */ crm_trace("Ignoring %s operation for instance on %s", op, host); return FALSE; } else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { *needs_reply = TRUE; } crm_trace("Processing %s request sent to everyone by %s/%s on %s %s", op, crm_element_value(request, F_CIB_CLIENTNAME), crm_element_value(request, F_CIB_CALLID), originator, (*local_notify)?"(notify)":""); return TRUE; } static gboolean parse_peer_options(int call_type, xmlNode * request, gboolean * local_notify, gboolean * needs_reply, gboolean * process, gboolean * needs_forward) { /* TODO: What happens when an update comes in after node A * requests the CIB from node B, but before it gets the reply (and * sends out the replace operation) */ if(cib_legacy_mode()) { return parse_peer_options_v1( call_type, request, local_notify, needs_reply, process, needs_forward); } else { return parse_peer_options_v2( call_type, request, local_notify, needs_reply, process, needs_forward); } } static void forward_request(xmlNode *request, int call_options) { const char *op = crm_element_value(request, F_CIB_OPERATION); const char *host = crm_element_value(request, F_CIB_HOST); crm_xml_add(request, F_CIB_DELEGATED, OUR_NODENAME); if (host != NULL) { crm_trace("Forwarding %s op to %s", op, host); send_cluster_message(crm_get_peer(0, host), crm_msg_cib, request, FALSE); } else { crm_trace("Forwarding %s op to primary instance", op); send_cluster_message(NULL, crm_msg_cib, request, FALSE); } /* Return the request to its original state */ xml_remove_prop(request, F_CIB_DELEGATED); if (call_options & cib_discard_reply) { crm_trace("Client not interested in reply"); } } static gboolean send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast) { CRM_ASSERT(msg != NULL); if (broadcast) { /* this (successful) call modified the CIB _and_ the * change needs to be broadcast... * send via HA to other nodes */ int diff_add_updates = 0; int diff_add_epoch = 0; int diff_add_admin_epoch = 0; int diff_del_updates = 0; int diff_del_epoch = 0; int diff_del_admin_epoch = 0; const char *digest = NULL; int format = 1; CRM_LOG_ASSERT(result_diff != NULL); digest = crm_element_value(result_diff, XML_ATTR_DIGEST); crm_element_value_int(result_diff, "format", &format); cib_diff_version_details(result_diff, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s", diff_del_admin_epoch, diff_del_epoch, diff_del_updates, diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest); crm_xml_add(msg, F_CIB_ISREPLY, originator); pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true); crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH); crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER); if (format == 1) { CRM_ASSERT(digest != NULL); } add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff); crm_log_xml_explicit(msg, "copy"); return send_cluster_message(NULL, crm_msg_cib, msg, TRUE); } else if (originator != NULL) { /* send reply via HA to originating node */ crm_trace("Sending request result to %s only", originator); crm_xml_add(msg, F_CIB_ISREPLY, originator); return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE); } return FALSE; } /*! * \internal * \brief Handle an IPC or CPG message containing a request * * \param[in,out] request Request XML * \param[in] privileged Whether privileged commands may be run * (see cib_server_ops[] definition) * \param[in] cib_client IPC client that sent request (or NULL if CPG) */ static void cib_process_request(xmlNode *request, gboolean privileged, const pcmk__client_t *cib_client) { int call_type = 0; int call_options = 0; gboolean process = TRUE; // Whether to process request locally now gboolean is_update = TRUE; // Whether request would modify CIB gboolean needs_reply = TRUE; // Whether to build a reply gboolean local_notify = FALSE; // Whether to notify (local) requester gboolean needs_forward = FALSE; // Whether to forward request somewhere else xmlNode *op_reply = NULL; xmlNode *result_diff = NULL; int rc = pcmk_ok; const char *op = crm_element_value(request, F_CIB_OPERATION); const char *originator = crm_element_value(request, F_ORIG); const char *host = crm_element_value(request, F_CIB_HOST); const char *target = NULL; const char *call_id = crm_element_value(request, F_CIB_CALLID); const char *client_id = crm_element_value(request, F_CIB_CLIENTID); const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME); const char *reply_to = crm_element_value(request, F_CIB_ISREPLY); crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); if ((host != NULL) && (*host == '\0')) { host = NULL; } if (host) { target = host; } else if (call_options & cib_scope_local) { target = "local host"; } else { target = "primary"; } if (cib_client == NULL) { crm_trace("Processing peer %s operation from %s/%s on %s intended for %s (reply=%s)", op, client_name, call_id, originator, target, reply_to); } else { crm_xml_add(request, F_ORIG, OUR_NODENAME); crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target); } rc = cib_get_operation_id(op, &call_type); if (rc != pcmk_ok) { /* TODO: construct error reply? */ crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc)); return; } if (cib_client != NULL) { parse_local_options(cib_client, call_type, call_options, host, op, &local_notify, &needs_reply, &process, &needs_forward); } else if (parse_peer_options(call_type, request, &local_notify, &needs_reply, &process, &needs_forward) == FALSE) { return; } is_update = cib_op_modifies(call_type); if (call_options & cib_discard_reply) { /* If the request will modify the CIB, and we are in legacy mode, we * need to build a reply so we can broadcast a diff, even if the * requester doesn't want one. */ needs_reply = is_update && cib_legacy_mode(); local_notify = FALSE; } if (needs_forward) { const char *section = crm_element_value(request, F_CIB_SECTION); int log_level = LOG_INFO; if (pcmk__str_eq(op, PCMK__CIB_REQUEST_NOOP, pcmk__str_none)) { log_level = LOG_DEBUG; } do_crm_log(log_level, "Forwarding %s operation for section %s to %s (origin=%s/%s/%s)", op, section ? section : "'all'", pcmk__s(host, (cib_legacy_mode() ? "primary" : "all")), originator ? originator : "local", client_name, call_id); forward_request(request, call_options); return; } if (cib_status != pcmk_ok) { const char *call = crm_element_value(request, F_CIB_CALLID); rc = cib_status; crm_err("Operation ignored, cluster configuration is invalid." " Please repair and restart: %s", pcmk_strerror(cib_status)); op_reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(op_reply, F_TYPE, T_CIB); crm_xml_add(op_reply, F_CIB_OPERATION, op); crm_xml_add(op_reply, F_CIB_CALLID, call); crm_xml_add(op_reply, F_CIB_CLIENTID, client_id); crm_xml_add_int(op_reply, F_CIB_CALLOPTS, call_options); crm_xml_add_int(op_reply, F_CIB_RC, rc); crm_trace("Attaching reply output"); add_message_xml(op_reply, F_CIB_CALLDATA, the_cib); crm_log_xml_explicit(op_reply, "cib:reply"); } else if (process) { time_t finished = 0; time_t now = time(NULL); int level = LOG_INFO; const char *section = crm_element_value(request, F_CIB_SECTION); rc = cib_process_command(request, &op_reply, &result_diff, privileged); if (!is_update) { level = LOG_TRACE; } else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { switch (rc) { case pcmk_ok: level = LOG_INFO; break; case -pcmk_err_old_data: case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: level = LOG_TRACE; break; default: level = LOG_ERR; } } else if (rc != pcmk_ok) { level = LOG_WARNING; } do_crm_log(level, "Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)", op, section ? section : "'all'", pcmk_strerror(rc), rc, originator ? originator : "local", client_name, call_id, the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0", the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0"); finished = time(NULL); if ((finished - now) > 3) { crm_trace("%s operation took %lds to complete", op, (long)(finished - now)); crm_write_blackbox(0, NULL); } if (op_reply == NULL && (needs_reply || local_notify)) { crm_err("Unexpected NULL reply to message"); crm_log_xml_err(request, "null reply"); needs_reply = FALSE; local_notify = FALSE; } } if (is_update && !cib_legacy_mode()) { crm_trace("Completed pre-sync update from %s/%s/%s%s", originator ? originator : "local", client_name, call_id, local_notify?" with local notification":""); } else if (!needs_reply || stand_alone) { // This was a non-originating secondary update crm_trace("Completed update as secondary"); } else if (cib_legacy_mode() && rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) { gboolean broadcast = FALSE; cib_local_bcast_num++; crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num); broadcast = send_peer_reply(request, result_diff, originator, TRUE); if (broadcast && client_id && local_notify && op_reply) { /* If we have been asked to sync the reply, * and a bcast msg has gone out, we queue the local notify * until we know the bcast message has been received */ local_notify = FALSE; crm_trace("Queuing local %ssync notification for %s", (call_options & cib_sync_call) ? "" : "a-", client_id); queue_local_notify(op_reply, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); op_reply = NULL; /* the reply is queued, so don't free here */ } } else if (call_options & cib_discard_reply) { crm_trace("Caller isn't interested in reply"); } else if (cib_client == NULL) { if (is_update == FALSE || result_diff == NULL) { crm_trace("Request not broadcast: R/O call"); } else if (call_options & cib_inhibit_bcast) { crm_trace("Request not broadcast: inhibited"); } else if (rc != pcmk_ok) { crm_trace("Request not broadcast: call failed: %s", pcmk_strerror(rc)); } else { crm_trace("Directing reply to %s", originator); } send_peer_reply(op_reply, result_diff, originator, FALSE); } if (local_notify && client_id) { crm_trace("Performing local %ssync notification for %s", (pcmk_is_set(call_options, cib_sync_call)? "" : "a"), client_id); if (process == FALSE) { do_local_notify(request, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); } else { do_local_notify(op_reply, client_id, pcmk_is_set(call_options, cib_sync_call), (cib_client == NULL)); } } free_xml(op_reply); free_xml(result_diff); return; } static char * calculate_section_digest(const char *xpath, xmlNode * xml_obj) { xmlNode *xml_section = NULL; if (xml_obj == NULL) { return NULL; } xml_section = get_xpath_object(xpath, xml_obj, LOG_TRACE); if (xml_section == NULL) { return NULL; } return calculate_xml_versioned_digest(xml_section, FALSE, TRUE, CRM_FEATURE_SET); } static int cib_process_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged) { xmlNode *input = NULL; xmlNode *output = NULL; xmlNode *result_cib = NULL; xmlNode *current_cib = NULL; int call_type = 0; int call_options = 0; const char *op = NULL; const char *section = NULL; const char *call_id = crm_element_value(request, F_CIB_CALLID); int rc = pcmk_ok; int rc2 = pcmk_ok; gboolean send_r_notify = FALSE; gboolean global_update = FALSE; gboolean config_changed = FALSE; gboolean manage_counters = TRUE; static mainloop_timer_t *digest_timer = NULL; char *current_nodes_digest = NULL; char *current_alerts_digest = NULL; char *current_status_digest = NULL; uint32_t change_section = cib_change_section_nodes |cib_change_section_alerts |cib_change_section_status; CRM_ASSERT(cib_status == pcmk_ok); if(digest_timer == NULL) { digest_timer = mainloop_timer_add("digester", 5000, FALSE, cib_digester_cb, NULL); } *reply = NULL; *cib_diff = NULL; current_cib = the_cib; /* Start processing the request... */ op = crm_element_value(request, F_CIB_OPERATION); crm_element_value_int(request, F_CIB_CALLOPTS, &call_options); rc = cib_get_operation_id(op, &call_type); if (rc == pcmk_ok && privileged == FALSE) { rc = cib_op_can_run(call_type, call_options, privileged, global_update); } rc2 = cib_op_prepare(call_type, request, &input, §ion); if (rc == pcmk_ok) { rc = rc2; } if (rc != pcmk_ok) { crm_trace("Call setup failed: %s", pcmk_strerror(rc)); goto done; } else if (cib_op_modifies(call_type) == FALSE) { rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, current_cib, &result_cib, NULL, &output); CRM_CHECK(result_cib == NULL, free_xml(result_cib)); goto done; } /* Handle a valid write action */ if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) { /* legacy code */ manage_counters = FALSE; cib__set_call_options(call_options, "call", cib_force_diff); crm_trace("Global update detected"); CRM_CHECK(call_type == 3 || call_type == 4, crm_err("Call type: %d", call_type); crm_log_xml_err(request, "bad op")); } ping_modified_since = TRUE; if (pcmk_is_set(call_options, cib_inhibit_bcast)) { crm_trace("Skipping update: inhibit broadcast"); manage_counters = FALSE; } if (!pcmk_is_set(call_options, cib_dryrun) && pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { // Copying large CIBs accounts for a huge percentage of our CIB usage cib__set_call_options(call_options, "call", cib_zero_copy); } else { cib__clear_call_options(call_options, "call", cib_zero_copy); } #define XPATH_CONFIG "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION #define XPATH_NODES XPATH_CONFIG "/" XML_CIB_TAG_NODES #define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS #define XPATH_STATUS "//" XML_TAG_CIB "/" XML_CIB_TAG_STATUS // Calculate the hash value of the section before the change if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) { current_nodes_digest = calculate_section_digest(XPATH_NODES, current_cib); current_alerts_digest = calculate_section_digest(XPATH_ALERTS, current_cib); current_status_digest = calculate_section_digest(XPATH_STATUS, current_cib); crm_trace("current-digest %s:%s:%s", current_nodes_digest, current_alerts_digest, current_status_digest); } // result_cib must not be modified after cib_perform_op() returns rc = cib_perform_op(op, call_options, cib_op_func(call_type), FALSE, section, request, input, manage_counters, &config_changed, current_cib, &result_cib, cib_diff, &output); if (!manage_counters) { int format = 1; /* Legacy code * If the diff is NULL at this point, it's because nothing changed */ if (*cib_diff != NULL) { crm_element_value_int(*cib_diff, "format", &format); } if (format == 1) { config_changed = cib_config_changed(NULL, NULL, cib_diff); } } /* Always write to disk for successful replace and upgrade ops. This also * negates the need to detect ordering changes. */ if ((rc == pcmk_ok) && pcmk__str_any_of(op, PCMK__CIB_REQUEST_REPLACE, PCMK__CIB_REQUEST_UPGRADE, NULL)) { config_changed = TRUE; } if (rc == pcmk_ok && !pcmk_is_set(call_options, cib_dryrun)) { crm_trace("Activating %s->%s%s%s", crm_element_value(current_cib, XML_ATTR_NUMUPDATES), crm_element_value(result_cib, XML_ATTR_NUMUPDATES), (pcmk_is_set(call_options, cib_zero_copy)? " zero-copy" : ""), (config_changed? " changed" : "")); if (!pcmk_is_set(call_options, cib_zero_copy)) { rc = activateCibXml(result_cib, config_changed, op); crm_trace("Activated %s (%d)", crm_element_value(current_cib, XML_ATTR_NUMUPDATES), rc); } if (rc == pcmk_ok && cib_internal_config_changed(*cib_diff)) { cib_read_config(config_hash, result_cib); } if (pcmk__str_eq(PCMK__CIB_REQUEST_REPLACE, op, pcmk__str_none)) { char *result_nodes_digest = NULL; char *result_alerts_digest = NULL; char *result_status_digest = NULL; /* Calculate the hash value of the changed section. */ result_nodes_digest = calculate_section_digest(XPATH_NODES, result_cib); result_alerts_digest = calculate_section_digest(XPATH_ALERTS, result_cib); result_status_digest = calculate_section_digest(XPATH_STATUS, result_cib); crm_trace("result-digest %s:%s:%s", result_nodes_digest, result_alerts_digest, result_status_digest); if (pcmk__str_eq(current_nodes_digest, result_nodes_digest, pcmk__str_none)) { change_section = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "CIB change section", "change_section", change_section, cib_change_section_nodes, "nodes"); } if (pcmk__str_eq(current_alerts_digest, result_alerts_digest, pcmk__str_none)) { change_section = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "CIB change section", "change_section", change_section, cib_change_section_alerts, "alerts"); } if (pcmk__str_eq(current_status_digest, result_status_digest, pcmk__str_none)) { change_section = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, "CIB change section", "change_section", change_section, cib_change_section_status, "status"); } if (change_section != cib_change_section_none) { send_r_notify = TRUE; } free(result_nodes_digest); free(result_alerts_digest); free(result_status_digest); } else if (pcmk__str_eq(PCMK__CIB_REQUEST_ERASE, op, pcmk__str_none)) { send_r_notify = TRUE; } mainloop_timer_stop(digest_timer); mainloop_timer_start(digest_timer); } else if (rc == -pcmk_err_schema_validation) { CRM_ASSERT(!pcmk_is_set(call_options, cib_zero_copy)); if (output != NULL) { crm_log_xml_info(output, "cib:output"); free_xml(output); } output = result_cib; } else { crm_trace("Not activating %d %d %s", rc, pcmk_is_set(call_options, cib_dryrun), crm_element_value(result_cib, XML_ATTR_NUMUPDATES)); if (!pcmk_is_set(call_options, cib_zero_copy)) { free_xml(result_cib); } } if ((call_options & (cib_inhibit_notify|cib_dryrun)) == 0) { const char *client = crm_element_value(request, F_CIB_CLIENTNAME); crm_trace("Sending notifications %d", pcmk_is_set(call_options, cib_dryrun)); cib_diff_notify(call_options, client, call_id, op, input, rc, *cib_diff); } if (send_r_notify) { const char *origin = crm_element_value(request, F_ORIG); cib_replace_notify(origin, the_cib, rc, *cib_diff, change_section); } pcmk__xml_log_patchset(LOG_TRACE, *cib_diff); done: if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) { const char *caller = crm_element_value(request, F_CIB_CLIENTID); *reply = create_xml_node(NULL, "cib-reply"); crm_xml_add(*reply, F_TYPE, T_CIB); crm_xml_add(*reply, F_CIB_OPERATION, op); crm_xml_add(*reply, F_CIB_CALLID, call_id); crm_xml_add(*reply, F_CIB_CLIENTID, caller); crm_xml_add_int(*reply, F_CIB_CALLOPTS, call_options); crm_xml_add_int(*reply, F_CIB_RC, rc); if (output != NULL) { crm_trace("Attaching reply output"); add_message_xml(*reply, F_CIB_CALLDATA, output); } crm_log_xml_explicit(*reply, "cib:reply"); } crm_trace("cleanup"); if (cib_op_modifies(call_type) == FALSE && output != current_cib) { free_xml(output); output = NULL; } if (call_type >= 0) { cib_op_cleanup(call_type, call_options, &input, &output); } free(current_nodes_digest); free(current_alerts_digest); free(current_status_digest); crm_trace("done"); return rc; } void cib_peer_callback(xmlNode * msg, void *private_data) { const char *reason = NULL; const char *originator = crm_element_value(msg, F_ORIG); if (cib_legacy_mode() && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { /* message is from ourselves */ int bcast_id = 0; if (!(crm_element_value_int(msg, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) { check_local_notify(bcast_id); } return; } else if (crm_peer_cache == NULL) { reason = "membership not established"; goto bail; } if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) { crm_xml_add(msg, F_CIB_CLIENTNAME, originator); } /* crm_log_xml_trace(msg, "Peer[inbound]"); */ cib_process_request(msg, TRUE, NULL); return; bail: if (reason) { const char *seq = crm_element_value(msg, F_SEQ); const char *op = crm_element_value(msg, F_CIB_OPERATION); crm_warn("Discarding %s message (%s) from %s: %s", op, seq, originator, reason); } } static gboolean cib_force_exit(gpointer data) { crm_notice("Forcing exit!"); terminate_cib(__func__, CRM_EX_ERROR); return FALSE; } static void disconnect_remote_client(gpointer key, gpointer value, gpointer user_data) { pcmk__client_t *a_client = value; crm_err("Can't disconnect client %s: Not implemented", pcmk__client_name(a_client)); } void cib_shutdown(int nsig) { struct qb_ipcs_stats srv_stats; if (cib_shutdown_flag == FALSE) { int disconnects = 0; qb_ipcs_connection_t *c = NULL; cib_shutdown_flag = TRUE; c = qb_ipcs_connection_first_get(ipcs_rw); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_rw, last); crm_debug("Disconnecting r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_ro); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_ro, last); crm_debug("Disconnecting r/o client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } c = qb_ipcs_connection_first_get(ipcs_shm); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(ipcs_shm, last); crm_debug("Disconnecting non-blocking r/w client %p...", last); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); disconnects++; } disconnects += pcmk__ipc_client_count(); crm_debug("Disconnecting %d remote clients", pcmk__ipc_client_count()); pcmk__foreach_ipc_client(disconnect_remote_client, NULL); crm_info("Disconnected %d clients", disconnects); } qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE); if (pcmk__ipc_client_count() == 0) { crm_info("All clients disconnected (%d)", srv_stats.active_connections); initiate_exit(); } else { crm_info("Waiting on %d clients to disconnect (%d)", pcmk__ipc_client_count(), srv_stats.active_connections); } } void initiate_exit(void) { int active = 0; xmlNode *leaving = NULL; active = crm_active_peers(); if (active < 2) { terminate_cib(__func__, 0); return; } crm_info("Sending disconnect notification to %d peers...", active); leaving = create_xml_node(NULL, "exit-notification"); crm_xml_add(leaving, F_TYPE, "cib"); crm_xml_add(leaving, F_CIB_OPERATION, PCMK__CIB_REQUEST_SHUTDOWN); send_cluster_message(NULL, crm_msg_cib, leaving, TRUE); free_xml(leaving); g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL); } extern int remote_fd; extern int remote_tls_fd; /*! * \internal * \brief Close remote sockets, free the global CIB and quit * * \param[in] caller Name of calling function (for log message) * \param[in] fast If -1, skip disconnect; if positive, exit that */ void terminate_cib(const char *caller, int fast) { crm_info("%s: Exiting%s...", caller, (fast > 0)? " fast" : mainloop ? " from mainloop" : ""); if (remote_fd > 0) { close(remote_fd); remote_fd = 0; } if (remote_tls_fd > 0) { close(remote_tls_fd); remote_tls_fd = 0; } uninitializeCib(); if (logger_out != NULL) { logger_out->finish(logger_out, CRM_EX_OK, true, NULL); pcmk__output_free(logger_out); logger_out = NULL; } if (fast > 0) { /* Quit fast on error */ pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(fast); } else if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) { /* Quit via returning from the main loop. If fast == -1, we skip the * disconnect here, and it will be done when the main loop returns * (this allows the peer status callback to avoid messing with the * peer caches). */ if (fast == 0) { crm_cluster_disconnect(crm_cluster); } g_main_loop_quit(mainloop); } else { /* Quit via clean exit. Even the peer status callback can disconnect * here, because we're not returning control to the caller. */ crm_cluster_disconnect(crm_cluster); pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(CRM_EX_OK); } } diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 7df7657a93..b3be8310c5 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,837 +1,836 @@ /* * Original copyright 2004 International Business Machines * Later changes copyright 2008-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include xmlNode * cib_get_generation(cib_t * cib) { xmlNode *the_cib = NULL; xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE); cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call); if (the_cib != NULL) { copy_in_properties(generation, the_cib); free_xml(the_cib); } return generation; } gboolean cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates) { *epoch = -1; *updates = -1; *admin_epoch = -1; if (cib == NULL) { return FALSE; } else { crm_element_value_int(cib, XML_ATTR_GENERATION, epoch); crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates); crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch); } return TRUE; } gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; xml_patch_versions(diff, add, del); *admin_epoch = add[0]; *epoch = add[1]; *updates = add[2]; *_admin_epoch = del[0]; *_epoch = del[1]; *_updates = del[2]; return TRUE; } /*! * \brief Create XML for a new (empty) CIB * * \param[in] cib_epoch What to use as "epoch" CIB property * * \return Newly created XML for empty CIB * \note It is the caller's responsibility to free the result with free_xml(). */ xmlNode * createEmptyCib(int cib_epoch) { xmlNode *cib_root = NULL, *config = NULL; cib_root = create_xml_node(NULL, XML_TAG_CIB); crm_xml_add(cib_root, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); crm_xml_add(cib_root, XML_ATTR_VALIDATION, xml_latest_schema()); crm_xml_add_int(cib_root, XML_ATTR_GENERATION, cib_epoch); crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0); crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0); config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION); create_xml_node(cib_root, XML_CIB_TAG_STATUS); create_xml_node(config, XML_CIB_TAG_CRMCONFIG); create_xml_node(config, XML_CIB_TAG_NODES); create_xml_node(config, XML_CIB_TAG_RESOURCES); create_xml_node(config, XML_CIB_TAG_CONSTRAINTS); #if PCMK__RESOURCE_STICKINESS_DEFAULT != 0 { xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG); xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS); xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR); crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults"); crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS); crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS); crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE, PCMK__RESOURCE_STICKINESS_DEFAULT); } #endif return cib_root; } static bool cib_acl_enabled(xmlNode *xml, const char *user) { bool rc = FALSE; if(pcmk_acl_required(user)) { const char *value = NULL; GHashTable *options = pcmk__strkey_table(free, free); cib_read_config(options, xml); value = cib_pref(options, "enable-acl"); rc = crm_is_true(value); g_hash_table_destroy(options); } crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled"); return rc; } int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output) { int rc = pcmk_ok; gboolean check_schema = TRUE; xmlNode *top = NULL; xmlNode *scratch = NULL; xmlNode *local_diff = NULL; const char *new_version = NULL; const char *user = crm_element_value(req, F_CIB_USER); bool with_digest = FALSE; crm_trace("Begin %s%s%s op", (pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""), (is_query? "read-only " : ""), op); CRM_CHECK(output != NULL, return -ENOMSG); CRM_CHECK(result_cib != NULL, return -ENOMSG); CRM_CHECK(config_changed != NULL, return -ENOMSG); if(output) { *output = NULL; } *result_cib = NULL; *config_changed = FALSE; if (fn == NULL) { return -EINVAL; } if (is_query) { xmlNode *cib_ro = current_cib; xmlNode *cib_filtered = NULL; if(cib_acl_enabled(cib_ro, user)) { if(xml_acl_filtered_copy(user, current_cib, current_cib, &cib_filtered)) { if (cib_filtered == NULL) { crm_debug("Pre-filtered the entire cib"); return -EACCES; } cib_ro = cib_filtered; crm_log_xml_trace(cib_ro, "filtered"); } } rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output); if(output == NULL || *output == NULL) { /* nothing */ } else if(cib_filtered == *output) { cib_filtered = NULL; /* Let them have this copy */ } else if(*output == current_cib) { /* They already know not to free it */ } else if(cib_filtered && (*output)->doc == cib_filtered->doc) { /* We're about to free the document of which *output is a part */ *output = copy_xml(*output); } else if((*output)->doc == current_cib->doc) { /* Give them a copy they can free */ *output = copy_xml(*output); } free_xml(cib_filtered); return rc; } if (pcmk_is_set(call_options, cib_zero_copy)) { /* Conditional on v2 patch style */ scratch = current_cib; /* Create a shallow copy of current_cib for the version details */ current_cib = create_xml_node(NULL, (const char *)scratch->name); copy_in_properties(current_cib, scratch); top = current_cib; xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output); } else { scratch = copy_xml(current_cib); xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user)); rc = (*fn) (op, call_options, section, req, input, current_cib, &scratch, output); if(scratch && xml_tracking_changes(scratch) == FALSE) { crm_trace("Inferring changes after %s op", op); xml_track_changes(scratch, user, current_cib, cib_acl_enabled(current_cib, user)); xml_calculate_changes(current_cib, scratch); } CRM_CHECK(current_cib != scratch, return -EINVAL); } xml_acl_disable(scratch); /* Allow the system to make any additional changes */ if (rc == pcmk_ok && scratch == NULL) { rc = -EINVAL; goto done; } else if(rc == pcmk_ok && xml_acl_denied(scratch)) { crm_trace("ACL rejected part or all of the proposed changes"); rc = -EACCES; goto done; } else if (rc != pcmk_ok) { goto done; } if (scratch) { new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION); if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) { crm_err("Discarding update with feature set '%s' greater than our own '%s'", new_version, CRM_FEATURE_SET); rc = -EPROTONOSUPPORT; goto done; } } if (current_cib) { int old = 0; int new = 0; crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: %#x)", XML_ATTR_GENERATION_ADMIN, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = -pcmk_err_old_data; } else if (old == new) { crm_element_value_int(scratch, XML_ATTR_GENERATION, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old); if (old > new) { crm_err("%s went backwards: %d -> %d (Opts: %#x)", XML_ATTR_GENERATION, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = -pcmk_err_old_data; } } } crm_trace("Massaging CIB contents"); pcmk__strip_xml_text(scratch); fix_plus_plus_recursive(scratch); if (pcmk_is_set(call_options, cib_zero_copy)) { /* At this point, current_cib is just the 'cib' tag and its properties, * * The v1 format would barf on this, but we know the v2 patch * format only needs it for the top-level version fields */ local_diff = xml_create_patchset(2, current_cib, scratch, (bool*)config_changed, manage_counters); } else { static time_t expires = 0; time_t tm_now = time(NULL); if (expires < tm_now) { expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */ with_digest = TRUE; } local_diff = xml_create_patchset(0, current_cib, scratch, (bool*)config_changed, manage_counters); } // Create a log output object only if we're going to use it pcmk__if_tracing( { pcmk__output_t *out = NULL; - int rc = pcmk__log_output_new(&out); - - CRM_LOG_ASSERT(rc == pcmk_rc_ok); - if (rc == pcmk_rc_ok) { - pcmk__output_set_log_level(out, LOG_TRACE); - pcmk__xml_show_changes(out, scratch); - out->finish(out, CRM_EX_OK, true, NULL); - pcmk__output_free(out); - } + rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); + + CRM_CHECK(rc == pcmk_ok, goto done); + + pcmk__output_set_log_level(out, LOG_TRACE); + pcmk__xml_show_changes(out, scratch); + out->finish(out, CRM_EX_OK, true, NULL); + pcmk__output_free(out); }, {} ); xml_accept_changes(scratch); if(local_diff) { patchset_process_digest(local_diff, current_cib, scratch, with_digest); pcmk__xml_log_patchset(LOG_INFO, local_diff); crm_log_xml_trace(local_diff, "raw patch"); } if (!pcmk_is_set(call_options, cib_zero_copy) && (local_diff != NULL)) { // Original to compare against doesn't exist pcmk__if_tracing( { // Validate the calculated patch set int test_rc = pcmk_ok; int format = 1; xmlNode *cib_copy = copy_xml(current_cib); crm_element_value_int(local_diff, "format", &format); test_rc = xml_apply_patchset(cib_copy, local_diff, manage_counters); if (test_rc != pcmk_ok) { save_xml_to_file(cib_copy, "PatchApply:calculated", NULL); save_xml_to_file(current_cib, "PatchApply:input", NULL); save_xml_to_file(scratch, "PatchApply:actual", NULL); save_xml_to_file(local_diff, "PatchApply:diff", NULL); crm_err("v%d patchset error, patch failed to apply: %s " "(%d)", format, pcmk_rc_str(pcmk_legacy2rc(test_rc)), test_rc); } free_xml(cib_copy); }, {} ); } if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) { /* Throttle the amount of costly validation we perform due to status updates * a) we don't really care whats in the status section * b) we don't validate any of its contents at the moment anyway */ check_schema = FALSE; } /* === scratch must not be modified after this point === * Exceptions, anything in: static filter_t filter[] = { { 0, XML_ATTR_ORIGIN }, { 0, XML_CIB_ATTR_WRITTEN }, { 0, XML_ATTR_UPDATE_ORIG }, { 0, XML_ATTR_UPDATE_CLIENT }, { 0, XML_ATTR_UPDATE_USER }, }; */ if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) { const char *schema = crm_element_value(scratch, XML_ATTR_VALIDATION); pcmk__xe_add_last_written(scratch); if (schema) { static int minimum_schema = 0; int current_schema = get_schema_version(schema); if (minimum_schema == 0) { minimum_schema = get_schema_version("pacemaker-1.2"); } /* Does the CIB support the "update-*" attributes... */ if (current_schema >= minimum_schema) { const char *origin = crm_element_value(req, F_ORIG); CRM_LOG_ASSERT(origin != NULL); crm_xml_replace(scratch, XML_ATTR_UPDATE_ORIG, origin); crm_xml_replace(scratch, XML_ATTR_UPDATE_CLIENT, crm_element_value(req, F_CIB_CLIENTNAME)); crm_xml_replace(scratch, XML_ATTR_UPDATE_USER, crm_element_value(req, F_CIB_USER)); } } } crm_trace("Perform validation: %s", pcmk__btoa(check_schema)); if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, TRUE)) { const char *current_schema = crm_element_value(scratch, XML_ATTR_VALIDATION); crm_warn("Updated CIB does not validate against %s schema", pcmk__s(current_schema, "unspecified")); rc = -pcmk_err_schema_validation; } done: *result_cib = scratch; if(rc != pcmk_ok && cib_acl_enabled(current_cib, user)) { if(xml_acl_filtered_copy(user, current_cib, scratch, result_cib)) { if (*result_cib == NULL) { crm_debug("Pre-filtered the entire cib result"); } free_xml(scratch); } } if(diff) { *diff = local_diff; } else { free_xml(local_diff); } free_xml(top); crm_trace("Done"); return rc; } xmlNode * cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name) { xmlNode *op_msg = create_xml_node(NULL, "cib_command"); CRM_CHECK(op_msg != NULL, return NULL); CRM_CHECK(token != NULL, return NULL); crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command"); crm_xml_add(op_msg, F_TYPE, T_CIB); crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token); crm_xml_add(op_msg, F_CIB_OPERATION, op); crm_xml_add(op_msg, F_CIB_HOST, host); crm_xml_add(op_msg, F_CIB_SECTION, section); crm_xml_add_int(op_msg, F_CIB_CALLID, call_id); if (user_name) { crm_xml_add(op_msg, F_CIB_USER, user_name); } crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options); crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options); if (data != NULL) { add_message_xml(op_msg, F_CIB_CALLDATA, data); } if (call_options & cib_inhibit_bcast) { CRM_CHECK((call_options & cib_scope_local), return NULL); } return op_msg; } void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc) { xmlNode *output = NULL; cib_callback_client_t *blob = NULL; if (msg != NULL) { crm_element_value_int(msg, F_CIB_RC, &rc); crm_element_value_int(msg, F_CIB_CALLID, &call_id); output = get_message_xml(msg, F_CIB_CALLDATA); } blob = cib__lookup_id(call_id); if (blob == NULL) { crm_trace("No callback found for call %d", call_id); } if (cib == NULL) { crm_debug("No cib object supplied"); } if (rc == -pcmk_err_diff_resync) { /* This is an internal value that clients do not and should not care about */ rc = pcmk_ok; } if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) { crm_trace("Invoking callback %s for call %d", pcmk__s(blob->id, "without ID"), call_id); blob->callback(msg, call_id, rc, output, blob->user_data); } else if (cib && cib->op_callback == NULL && rc != pcmk_ok) { crm_warn("CIB command failed: %s", pcmk_strerror(rc)); crm_log_xml_debug(msg, "Failed CIB Update"); } /* This may free user_data, so do it after the callback */ if (blob) { remove_cib_op_callback(call_id, FALSE); } if (cib && cib->op_callback != NULL) { crm_trace("Invoking global callback for call %d", call_id); cib->op_callback(msg, call_id, rc, output); } crm_trace("OP callback activated for %d", call_id); } void cib_native_notify(gpointer data, gpointer user_data) { xmlNode *msg = user_data; cib_notify_client_t *entry = data; const char *event = NULL; if (msg == NULL) { crm_warn("Skipping callback - NULL message"); return; } event = crm_element_value(msg, F_SUBTYPE); if (entry == NULL) { crm_warn("Skipping callback - NULL callback client"); return; } else if (entry->callback == NULL) { crm_warn("Skipping callback - NULL callback"); return; } else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) { crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); return; } crm_trace("Invoking callback for %p/%s event...", entry, event); entry->callback(event, msg); crm_trace("Callback invoked..."); } static pcmk__cluster_option_t cib_opts[] = { /* name, legacy name, type, allowed values, * default value, validator, * short description, * long description */ { "enable-acl", NULL, "boolean", NULL, "false", pcmk__valid_boolean, N_("Enable Access Control Lists (ACLs) for the CIB"), NULL }, { "cluster-ipc-limit", NULL, "integer", NULL, "500", pcmk__valid_positive_number, N_("Maximum IPC message backlog before disconnecting a cluster daemon"), N_("Raise this if log has \"Evicting client\" messages for cluster daemon" " PIDs (a good value is the number of resources in the cluster" " multiplied by the number of nodes).") }, }; void cib_metadata(void) { const char *desc_short = "Cluster Information Base manager options"; const char *desc_long = "Cluster options used by Pacemaker's Cluster " "Information Base manager"; gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short, desc_long, cib_opts, PCMK__NELEM(cib_opts)); printf("%s", s); g_free(s); } void verify_cib_options(GHashTable * options) { pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts)); } const char * cib_pref(GHashTable * options, const char *name) { return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts), name); } gboolean cib_read_config(GHashTable * options, xmlNode * current_cib) { xmlNode *config = NULL; crm_time_t *now = NULL; if (options == NULL || current_cib == NULL) { return FALSE; } now = crm_time_new(NULL); g_hash_table_remove_all(options); config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG); if (config) { pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL, options, CIB_OPTIONS_FIRST, TRUE, now, NULL); } verify_cib_options(options); crm_time_free(now); return TRUE; } /* v2 and v2 patch formats */ #define XPATH_CONFIG_CHANGE \ "//" XML_CIB_TAG_CRMCONFIG " | " \ "//" XML_DIFF_CHANGE "[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]" gboolean cib_internal_config_changed(xmlNode *diff) { gboolean changed = FALSE; if (diff) { xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE); if (numXpathResults(xpathObj) > 0) { changed = TRUE; } freeXpathObject(xpathObj); } return changed; } 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 (*delegate) (cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name) = cib->delegate_fn; if(user_name == NULL) { user_name = getenv("CIB_user"); } return delegate(cib, op, host, section, data, output_data, call_options, user_name); } /*! * \brief Apply a CIB update patch to a given CIB * * \param[in] event CIB update patch * \param[in] input CIB to patch * \param[out] output Resulting CIB after patch * \param[in] level Log the patch at this log level (unless LOG_CRIT) * * \return Legacy Pacemaker return code * \note sbd calls this function */ int cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, int level) { int rc = pcmk_err_generic; xmlNode *diff = NULL; CRM_ASSERT(event); CRM_ASSERT(input); CRM_ASSERT(output); crm_element_value_int(event, F_CIB_RC, &rc); diff = get_message_xml(event, F_CIB_UPDATE_RESULT); if (rc < pcmk_ok || diff == NULL) { return rc; } if (level > LOG_CRIT) { pcmk__xml_log_patchset(level, diff); } if (input != NULL) { rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output, NULL); if (rc != pcmk_ok) { crm_debug("Update didn't apply: %s (%d) %p", pcmk_strerror(rc), rc, *output); if (rc == -pcmk_err_old_data) { crm_trace("Masking error, we already have the supplied update"); return pcmk_ok; } free_xml(*output); *output = NULL; return rc; } } return rc; } #define log_signon_query_err(out, fmt, args...) do { \ if (out != NULL) { \ out->err(out, fmt, ##args); \ } else { \ crm_err(fmt, ##args); \ } \ } while (0) int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object) { int rc = pcmk_rc_ok; cib_t *cib_conn = NULL; CRM_ASSERT(cib_object != NULL); if (cib == NULL) { cib_conn = cib_new(); } else { if (*cib == NULL) { *cib = cib_new(); } cib_conn = *cib; } if (cib_conn == NULL) { return ENOMEM; } if (cib_conn->state == cib_disconnected) { rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); } if (rc != pcmk_rc_ok) { log_signon_query_err(out, "Could not connect to the CIB: %s", pcmk_rc_str(rc)); goto done; } if (out != NULL) { out->transient(out, "Querying CIB..."); } rc = cib_conn->cmds->query(cib_conn, NULL, cib_object, cib_scope_local|cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc)); } done: if (cib == NULL) { cib__clean_up_connection(&cib_conn); } if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) { return pcmk_rc_no_input; } return rc; } int cib__clean_up_connection(cib_t **cib) { int rc; if (*cib == NULL) { return pcmk_rc_ok; } rc = (*cib)->cmds->signoff(*cib); cib_delete(*cib); *cib = NULL; return pcmk_legacy2rc(rc); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include const char * get_object_path(const char *object_type) { return pcmk_cib_xpath_for(object_type); } const char * get_object_parent(const char *object_type) { return pcmk_cib_parent_name_for(object_type); } xmlNode * get_object_root(const char *object_type, xmlNode *the_root) { return pcmk_find_cib_element(the_root, object_type); } // LCOV_EXCL_STOP // End deprecated API diff --git a/tools/crm_diff.c b/tools/crm_diff.c index 6d1ea37f9a..6eeb83b5a0 100644 --- a/tools/crm_diff.c +++ b/tools/crm_diff.c @@ -1,387 +1,386 @@ /* * Copyright 2005-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \ "or apply such an output as a patch" struct { gboolean apply; gboolean as_cib; gboolean no_version; gboolean raw_1; gboolean raw_2; gboolean use_stdin; char *xml_file_1; char *xml_file_2; } options; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GOptionEntry original_xml_entries[] = { { "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1, "XML is contained in the named file", "FILE" }, { "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb, "XML is contained in the supplied string", "STRING" }, { NULL } }; static GOptionEntry operation_entries[] = { { "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2, "Compare the original XML to the contents of the named file", "FILE" }, { "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb, "Compare the original XML with the contents of the supplied string", "STRING" }, { "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb, "Patch the original XML with the contents of the named file", "FILE" }, { NULL } }; static GOptionEntry addl_entries[] = { { "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib, "Compare/patch the inputs as a CIB (includes versions details)", NULL }, { "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin, "", NULL }, { "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version, "Generate the difference without versions details", NULL }, { NULL } }; gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_2 = TRUE; pcmk__str_update(&options.xml_file_2, optarg); return TRUE; } gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.raw_1 = TRUE; pcmk__str_update(&options.xml_file_1, optarg); return TRUE; } gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.apply = TRUE; pcmk__str_update(&options.xml_file_2, optarg); return TRUE; } static void print_patch(xmlNode *patch) { char *buffer = dump_xml_formatted(patch); printf("%s", pcmk__s(buffer, "\n")); free(buffer); fflush(stdout); } // \return Standard Pacemaker return code static int apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib) { xmlNode *output = copy_xml(input); int rc = xml_apply_patchset(output, patch, as_cib); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc)); free_xml(output); return rc; } if (output != NULL) { const char *version; char *buffer; print_patch(output); version = crm_element_value(output, XML_ATTR_CRM_VERSION); buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version); crm_trace("Digest: %s", pcmk__s(buffer, "\n")); free(buffer); free_xml(output); } return pcmk_rc_ok; } static void log_patch_cib_versions(xmlNode *patch) { int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; const char *fmt = NULL; const char *digest = NULL; xml_patch_versions(patch, add, del); fmt = crm_element_value(patch, "format"); digest = crm_element_value(patch, XML_ATTR_DIGEST); if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) { crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest); } } static void strip_patch_cib_version(xmlNode *patch, const char **vfields, size_t nvfields) { int format = 1; crm_element_value_int(patch, "format", &format); if (format == 2) { xmlNode *version_xml = find_xml_node(patch, "version", FALSE); if (version_xml) { free_xml(version_xml); } } else { int i = 0; const char *tags[] = { XML_TAG_DIFF_REMOVED, XML_TAG_DIFF_ADDED, }; for (i = 0; i < PCMK__NELEM(tags); i++) { xmlNode *tmp = NULL; int lpc; tmp = find_xml_node(patch, tags[i], FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { xml_remove_prop(tmp, vfields[lpc]); } } } } } } // \return Standard Pacemaker return code static int generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2, gboolean as_cib, gboolean no_version) { xmlNode *output = NULL; const char *vfields[] = { XML_ATTR_GENERATION_ADMIN, XML_ATTR_GENERATION, XML_ATTR_NUMUPDATES, }; /* If we're ignoring the version, make the version information * identical, so it isn't detected as a change. */ if (no_version) { int lpc; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_copy_xml_element(object_1, object_2, vfields[lpc]); } } xml_track_changes(object_2, NULL, object_2, FALSE); if(as_cib) { xml_calculate_significant_changes(object_1, object_2); } else { xml_calculate_changes(object_1, object_2); } crm_log_xml_debug(object_2, (xml_file_2? xml_file_2: "target")); output = xml_create_patchset(0, object_1, object_2, NULL, FALSE); { pcmk__output_t *logger_out = NULL; int rc = pcmk__log_output_new(&logger_out); - CRM_LOG_ASSERT(rc == pcmk_rc_ok); - if (rc == pcmk_rc_ok) { - pcmk__output_set_log_level(logger_out, LOG_INFO); - pcmk__xml_show_changes(logger_out, object_2); - logger_out->finish(logger_out, CRM_EX_OK, true, NULL); - pcmk__output_free(logger_out); - } + CRM_CHECK(rc == pcmk_rc_ok, {free_xml(output); return rc;}); + + pcmk__output_set_log_level(logger_out, LOG_INFO); + pcmk__xml_show_changes(logger_out, object_2); + logger_out->finish(logger_out, CRM_EX_OK, true, NULL); + pcmk__output_free(logger_out); } xml_accept_changes(object_2); if (output == NULL) { return pcmk_rc_ok; } patchset_process_digest(output, object_1, object_2, as_cib); if (as_cib) { log_patch_cib_versions(output); } else if (no_version) { strip_patch_cib_version(output, vfields, PCMK__NELEM(vfields)); } pcmk__xml_log_patchset(LOG_NOTICE, output); print_patch(output); free_xml(output); return pcmk_rc_error; } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; const char *description = "Examples:\n\n" "Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n" "\t# cibadmin --query > cib-old.xml\n\n" "\t# cibadmin --query > cib-new.xml\n\n" "Calculate and save the difference between the two files:\n\n" "\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n" "Apply the patch to the original file:\n\n" "\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n" "Apply the patch to the running cluster:\n\n" "\t# cibadmin --patch -x patch.xml\n"; context = pcmk__build_arg_context(args, NULL, NULL, NULL); g_option_context_set_description(context, description); pcmk__add_arg_group(context, "xml", "Original XML:", "Show original XML options", original_xml_entries); pcmk__add_arg_group(context, "operation", "Operation:", "Show operation options", operation_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { xmlNode *object_1 = NULL; xmlNode *object_2 = NULL; crm_exit_t exit_code = CRM_EX_OK; GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO"); GOptionContext *context = build_arg_context(args); int rc = pcmk_rc_ok; if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_diff", args->verbosity); if (args->version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_diff is converted to use formatted output, this can go. */ pcmk__cli_help('v'); } if (options.apply && options.no_version) { fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n"); } else if (options.as_cib && options.no_version) { fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n"); exit_code = CRM_EX_USAGE; goto done; } if (options.raw_1) { object_1 = string2xml(options.xml_file_1); } else if (options.use_stdin) { fprintf(stderr, "Input first XML fragment:"); object_1 = stdin2xml(); } else if (options.xml_file_1 != NULL) { object_1 = filename2xml(options.xml_file_1); } if (options.raw_2) { object_2 = string2xml(options.xml_file_2); } else if (options.use_stdin) { fprintf(stderr, "Input second XML fragment:"); object_2 = stdin2xml(); } else if (options.xml_file_2 != NULL) { object_2 = filename2xml(options.xml_file_2); } if (object_1 == NULL) { fprintf(stderr, "Could not parse the first XML fragment\n"); exit_code = CRM_EX_DATAERR; goto done; } if (object_2 == NULL) { fprintf(stderr, "Could not parse the second XML fragment\n"); exit_code = CRM_EX_DATAERR; goto done; } if (options.apply) { rc = apply_patch(object_1, object_2, options.as_cib); } else { rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version); } exit_code = pcmk_rc2exitc(rc); done: g_strfreev(processed_args); pcmk__free_arg_context(context); free(options.xml_file_1); free(options.xml_file_2); free_xml(object_1); free_xml(object_2); pcmk__output_and_clear_error(error, NULL); crm_exit(exit_code); } diff --git a/tools/crm_shadow.c b/tools/crm_shadow.c index 3f1e7ced9a..e87bce8053 100644 --- a/tools/crm_shadow.c +++ b/tools/crm_shadow.c @@ -1,682 +1,681 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #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, }; static crm_exit_t exit_code = CRM_EX_OK; static cib_t *real_cib = NULL; static struct { enum shadow_command cmd; int cmd_options; char *shadow; gboolean force; gboolean batch; gboolean full_upload; gchar *validate_with; } options = { .cmd_options = cib_sync_call, }; #if 0 // @COMPAT Possibly enable this at next backward compatibility break #define SET_COMMAND(command) do { \ if (options.cmd != shadow_cmd_none) { \ g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE, \ "Only one command option may be specified"); \ return FALSE; \ } \ options.cmd = (command); \ } while (0) #else #define SET_COMMAND(command) do { \ options.cmd = (command); \ } while (0) #endif static char * get_shadow_prompt(const char *name) { return crm_strdup_printf("shadow[%.40s] # ", name); } static void shadow_setup(char *name, gboolean do_switch) { const char *prompt = getenv("PS1"); const char *shell = getenv("SHELL"); char *new_prompt = get_shadow_prompt(name); printf("Setting up shadow instance\n"); if (pcmk__str_eq(new_prompt, prompt, pcmk__str_casei)) { /* nothing to do */ goto done; } else if (!options.batch && (shell != NULL)) { setenv("PS1", new_prompt, 1); setenv("CIB_shadow", name, 1); printf("Type Ctrl-D to exit the crm_shadow shell\n"); if (strstr(shell, "bash")) { execl(shell, shell, "--norc", "--noprofile", NULL); } else { execl(shell, shell, NULL); } } else if (do_switch) { printf("To switch to the named shadow instance, paste the following into your shell:\n"); } else { printf ("A new shadow instance was created. To begin using it paste the following into your shell:\n"); } printf(" CIB_shadow=%s ; export CIB_shadow\n", name); done: free(new_prompt); } static void shadow_teardown(char *name) { const char *prompt = getenv("PS1"); char *our_prompt = get_shadow_prompt(name); if (prompt != NULL && strstr(prompt, our_prompt)) { printf("Now type Ctrl-D to exit the crm_shadow shell\n"); } else { printf ("Please remember to unset the CIB_shadow variable by pasting the following into your shell:\n"); printf(" unset CIB_shadow\n"); } free(our_prompt); } static bool cmd_is_dangerous(enum shadow_command cmd) { switch (cmd) { case shadow_cmd_commit: case shadow_cmd_delete: return true; default: return false; } } 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)) { SET_COMMAND(shadow_cmd_which); } else if (pcmk__str_any_of(option_name, "-p", "--display", NULL)) { SET_COMMAND(shadow_cmd_display); } else if (pcmk__str_any_of(option_name, "-d", "--diff", NULL)) { SET_COMMAND(shadow_cmd_diff); } else if (pcmk__str_any_of(option_name, "-F", "--file", NULL)) { SET_COMMAND(shadow_cmd_file); } else if (pcmk__str_any_of(option_name, "-c", "--create", NULL)) { SET_COMMAND(shadow_cmd_create); } else if (pcmk__str_any_of(option_name, "-e", "--create-empty", NULL)) { SET_COMMAND(shadow_cmd_create_empty); } else if (pcmk__str_any_of(option_name, "-C", "--commit", NULL)) { SET_COMMAND(shadow_cmd_commit); } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) { SET_COMMAND(shadow_cmd_delete); } else if (pcmk__str_any_of(option_name, "-E", "--edit", NULL)) { SET_COMMAND(shadow_cmd_edit); } else if (pcmk__str_any_of(option_name, "-r", "--reset", NULL)) { SET_COMMAND(shadow_cmd_reset); } else if (pcmk__str_any_of(option_name, "-s", "--switch", NULL)) { SET_COMMAND(shadow_cmd_switch); } else { // Should be impossible return FALSE; } // optarg may be NULL and that's okay pcmk__str_update(&options.shadow, 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" }, { "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) { 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, NULL, NULL, "|"); 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_ok; char *shadow_file = NULL; bool needs_teardown = false; struct stat buf; GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "ceCDrsv"); GOptionContext *context = build_arg_context(args); crm_log_preinit(NULL, argc, argv); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; 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) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When crm_shadow is converted to use formatted output, * this can go. */ pcmk__cli_help('v'); } 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); } if (options.force) { cib__set_call_options(options.cmd_options, crm_system_name, cib_quorum_override); } // Some commands get options.shadow from the environment switch (options.cmd) { case shadow_cmd_which: case shadow_cmd_display: case shadow_cmd_diff: case shadow_cmd_file: case shadow_cmd_edit: pcmk__str_update(&options.shadow, getenv("CIB_shadow")); if (options.shadow == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "No active shadow configuration defined"); goto done; } break; default: // The rest already set options.shadow from their optarg break; } if (options.cmd == shadow_cmd_which) { // Show the active shadow instance printf("%s\n", options.shadow); goto done; } // Check for shadow instance mismatch if ((options.cmd != shadow_cmd_switch) && (options.cmd != shadow_cmd_create)) { const char *local = getenv("CIB_shadow"); if ((local != NULL) && !pcmk__str_eq(local, options.shadow, pcmk__str_none) && !options.force) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied shadow instance (%s) is not the same as " "the active one (%s).\n" "To prevent accidental destruction of the cluster, the " "--force flag is required in order to proceed.", options.shadow, local); goto done; } } // Check for dangerous commands if (cmd_is_dangerous(options.cmd) && !options.force) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command is considered dangerous.\n" "To prevent accidental destruction of the cluster, the " "--force flag is required in order to proceed."); goto done; } shadow_file = get_shadow_file(options.shadow); if (options.cmd == shadow_cmd_delete) { // Delete the shadow file if ((unlink(shadow_file) < 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.shadow, strerror(errno)); } needs_teardown = true; goto done; } if (options.cmd == shadow_cmd_file) { // Show the shadow file path printf("%s\n", shadow_file); goto done; } // Connect to the CIB if necessary switch (options.cmd) { case shadow_cmd_commit: case shadow_cmd_create: case shadow_cmd_diff: case shadow_cmd_reset: real_cib = cib_new_no_shadow(); rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to CIB: %s", pcmk_rc_str(rc)); goto done; } break; default: break; } // Check existence of the shadow file rc = stat(shadow_file, &buf); switch (options.cmd) { case shadow_cmd_create: case shadow_cmd_create_empty: if ((rc == 0) && !options.force) { exit_code = CRM_EX_CANTCREAT; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "A shadow instance '%s' already exists.\n" "To prevent accidental destruction of the cluster, " "the --force flag is required in order to proceed.", options.shadow); goto done; } break; default: if (rc < 0) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not access shadow instance '%s': %s", options.shadow, strerror(errno)); goto done; } break; } // Run the command if we haven't already switch (options.cmd) { case shadow_cmd_create: case shadow_cmd_create_empty: case shadow_cmd_reset: // Create or reset the shadow file { xmlNode *output = NULL; if (options.cmd == shadow_cmd_create_empty) { output = createEmptyCib(0); crm_xml_add(output, XML_ATTR_VALIDATION, options.validate_with); printf("Created new %s configuration\n", crm_element_value(output, XML_ATTR_VALIDATION)); } else { // Create a shadow instance based on the current CIB rc = real_cib->cmds->query(real_cib, NULL, &output, options.cmd_options); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to the CIB manager: %s", pcmk_rc_str(rc)); goto done; } } rc = write_xml_file(output, shadow_file, FALSE); free_xml(output); if (rc < 0) { const char *action = "create"; rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); if (options.cmd == shadow_cmd_reset) { action = "reset"; } g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not %s the shadow instance '%s': %s", action, options.shadow, pcmk_rc_str(rc)); goto done; } shadow_setup(options.shadow, FALSE); } break; case shadow_cmd_edit: // Open the shadow file in a text editor { const char *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, "--", shadow_file, NULL); exit_code = CRM_EX_OSFILE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not invoke EDITOR (%s %s): %s", editor, shadow_file, strerror(errno)); goto done; } break; case shadow_cmd_switch: // Switch to the named shadow instance shadow_setup(options.shadow, TRUE); break; case shadow_cmd_display: // Display the current shadow file contents { char *output_s = NULL; xmlNode *output = filename2xml(shadow_file); output_s = dump_xml_formatted(output); printf("%s", output_s); free(output_s); free_xml(output); } break; case shadow_cmd_diff: // Diff the shadow file against the cluster { xmlNode *diff = NULL; xmlNode *old_config = NULL; xmlNode *new_config = filename2xml(shadow_file); rc = real_cib->cmds->query(real_cib, NULL, &old_config, options.cmd_options); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not query the CIB: %s", pcmk_rc_str(rc)); 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__output_t *logger_out = NULL; - int rc = pcmk__log_output_new(&logger_out); - - CRM_LOG_ASSERT(rc == pcmk_rc_ok); - if (rc == pcmk_rc_ok) { - pcmk__output_set_log_level(logger_out, LOG_INFO); - pcmk__xml_show_changes(logger_out, new_config); - logger_out->finish(logger_out, CRM_EX_OK, true, NULL); - pcmk__output_free(logger_out); - } + rc = pcmk_rc2legacy(pcmk__log_output_new(&logger_out)); + + CRM_CHECK(rc == pcmk_ok, goto done); + + pcmk__output_set_log_level(logger_out, LOG_INFO); + pcmk__xml_show_changes(logger_out, new_config); + logger_out->finish(logger_out, CRM_EX_OK, true, NULL); + pcmk__output_free(logger_out); } xml_accept_changes(new_config); 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). */ pcmk__xml_log_patchset(LOG_STDOUT, diff); exit_code = CRM_EX_ERROR; } } break; case shadow_cmd_commit: // Commit the shadow file to the cluster { xmlNode *input = filename2xml(shadow_file); xmlNode *section_xml = input; const char *section = NULL; if (!options.full_upload) { section = XML_CIB_TAG_CONFIGURATION; section_xml = first_named_child(input, section); } rc = real_cib->cmds->replace(real_cib, section, section_xml, options.cmd_options); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); 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.shadow, pcmk_rc_str(rc)); goto done; } needs_teardown = true; free_xml(input); } break; default: // Should never reach this point break; } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(error, NULL); if (needs_teardown) { // Teardown message should be the last thing we output shadow_teardown(options.shadow); } free(shadow_file); free(options.shadow); g_free(options.validate_with); crm_exit(exit_code); }