diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index 6a84620a5c..dfdac7e918 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -1,1761 +1,1761 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include // uint32_t, uint64_t, UINT64_C() #include #include #include // PRIu64 #include #include #include #include #include #include #include #include #define EXIT_ESCALATION_MS 10000 static unsigned long cib_local_bcast_num = 0; 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; static int cib_process_command(xmlNode *request, const cib__operation_t *operation, cib__op_fn_t op_function, xmlNode **reply, xmlNode **cib_diff, bool privileged); static 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 -ECONNREFUSED; } if (pcmk__new_client(c, uid, gid) == NULL) { return -ENOMEM; } 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 }; /*! * \internal * \brief Create reply XML for a CIB request * * \param[in] op CIB operation type * \param[in] call_id CIB call ID * \param[in] client_id CIB client ID * \param[in] call_options Group of enum cib_call_options flags * \param[in] rc Request return code * \param[in] call_data Request output data * * \return Reply XML * * \note The caller is responsible for freeing the return value using * \p pcmk__xml_free(). */ static xmlNode * create_cib_reply(const char *op, const char *call_id, const char *client_id, int call_options, int rc, xmlNode *call_data) { xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_CIB_REPLY); crm_xml_add(reply, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(reply, PCMK__XA_CIB_OP, op); crm_xml_add(reply, PCMK__XA_CIB_CALLID, call_id); crm_xml_add(reply, PCMK__XA_CIB_CLIENTID, client_id); crm_xml_add_int(reply, PCMK__XA_CIB_CALLOPT, call_options); crm_xml_add_int(reply, PCMK__XA_CIB_RC, rc); if (call_data != NULL) { xmlNode *wrapper = pcmk__xe_create(reply, PCMK__XE_CIB_CALLDATA); crm_trace("Attaching reply output"); pcmk__xml_copy(wrapper, call_data); } crm_log_xml_explicit(reply, "cib:reply"); return reply; } static void do_local_notify(const xmlNode *notify_src, const char *client_id, bool sync_reply, bool 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, PCMK__XA_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; case pcmk__client_tls: 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); } } 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, PCMK__XA_CIB_OP); int call_options = cib_none; crm_element_value_int(op_request, PCMK__XA_CIB_CALLOPT, &call_options); /* Requests with cib_transaction set should not be sent to based directly * (outside of a commit-transaction request) */ if (pcmk_is_set(call_options, cib_transaction)) { return; } if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { if (flags & crm_ipc_client_response) { xmlNode *ack = pcmk__xe_create(NULL, __func__); crm_xml_add(ack, PCMK__XA_CIB_OP, CRM_OP_REGISTER); crm_xml_add(ack, PCMK__XA_CIB_CLIENTID, cib_client->id); pcmk__ipc_send_xml(cib_client, id, ack, flags); cib_client->request_id = 0; pcmk__xml_free(ack); } return; } else if (pcmk__str_eq(op, PCMK__VALUE_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, PCMK__XA_CIB_NOTIFY_TYPE); crm_element_value_int(op_request, PCMK__XA_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, PCMK__VALUE_CIB_POST_NOTIFY, pcmk__str_none)) { bit = cib_notify_post; } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_PRE_NOTIFY, pcmk__str_none)) { bit = cib_notify_pre; } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_UPDATE_CONFIRMATION, pcmk__str_none)) { bit = cib_notify_confirm; } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_DIFF_NOTIFY, pcmk__str_none)) { bit = cib_notify_diff; } 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, PCMK__XE_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, PCMK__XA_CIB_CALLOPT, &call_options); } if (op_request == NULL) { crm_trace("Invalid message from %p", c); pcmk__ipc_send_ack(cib_client, id, flags, PCMK__XE_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, PCMK__XA_CIB_CLIENTNAME); if (value == NULL) { cib_client->name = pcmk__itoa(cib_client->pid); } else { cib_client->name = pcmk__str_copy(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(PCMK_OPT_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, PCMK__XA_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, PCMK__XA_CIB_CLIENTNAME, cib_client->name); CRM_LOG_ASSERT(cib_client->user != NULL); pcmk__update_acl_user(op_request, PCMK__XA_CIB_USER, cib_client->user); cib_common_callback_worker(id, flags, op_request, cib_client, privileged); pcmk__xml_free(op_request); return 0; } static uint64_t ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = FALSE; static gboolean cib_digester_cb(gpointer data) { if (based_is_primary) { char buffer[32]; xmlNode *ping = pcmk__xe_create(NULL, PCMK__XE_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, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(ping, PCMK__XA_CIB_OP, CRM_OP_PING); crm_xml_add(ping, PCMK__XA_CIB_PING_ID, buffer); crm_xml_add(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); pcmk__cluster_send_message(NULL, crm_msg_cib, ping); pcmk__xml_free(ping); } return FALSE; } static void process_ping_reply(xmlNode *reply) { uint64_t seq = 0; const char *host = crm_element_value(reply, PCMK__XA_SRC); xmlNode *wrapper = pcmk__xe_first_child(reply, PCMK__XE_CIB_CALLDATA, NULL, NULL); xmlNode *pong = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); const char *seq_s = crm_element_value(pong, PCMK__XA_CIB_PING_ID); const char *digest = crm_element_value(pong, PCMK__XA_DIGEST); if (seq_s == NULL) { crm_debug("Ignoring ping reply with no " PCMK__XA_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, PCMK_XA_CRM_FEATURE_SET); if(ping_digest == NULL) { crm_trace("Calculating new digest"); - ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version); + ping_digest = pcmk__digest_xml(the_cib, 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 *wrapper = pcmk__xe_first_child(pong, PCMK__XE_CIB_CALLDATA, NULL, NULL); xmlNode *remote_cib = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); const char *admin_epoch_s = NULL; const char *epoch_s = NULL; const char *num_updates_s = NULL; if (remote_cib != NULL) { admin_epoch_s = crm_element_value(remote_cib, PCMK_XA_ADMIN_EPOCH); epoch_s = crm_element_value(remote_cib, PCMK_XA_EPOCH); num_updates_s = crm_element_value(remote_cib, PCMK_XA_NUM_UPDATES); } crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p", crm_element_value(the_cib, PCMK_XA_ADMIN_EPOCH), crm_element_value(the_cib, PCMK_XA_EPOCH), crm_element_value(the_cib, PCMK_XA_NUM_UPDATES), ping_digest, host, pcmk__s(admin_epoch_s, "_"), pcmk__s(epoch_s, "_"), pcmk__s(num_updates_s, "_"), digest, remote_cib); if(remote_cib && remote_cib->children) { // Additional debug xml_calculate_changes(the_cib, remote_cib); pcmk__log_xml_changes(LOG_INFO, remote_cib); crm_trace("End of differences"); } pcmk__xml_free(remote_cib); sync_our_cib(reply, FALSE); } } } static void local_notify_destroy_callback(gpointer data) { cib_local_notify_t *notify = data; pcmk__xml_free(notify->notify_src); free(notify->client_id); free(notify); } static void check_local_notify(int bcast_id) { const 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 = pcmk__assert_alloc(1, sizeof(cib_local_notify_t)); notify->notify_src = notify_src; notify->client_id = pcmk__str_copy(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, const cib__operation_t *operation, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { if (pcmk_is_set(operation->flags, cib__op_attr_modifies) && !pcmk_is_set(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, const cib__operation_t *operation, int call_options, const char *host, const char *op, gboolean *local_notify, gboolean *needs_reply, gboolean *process, gboolean *needs_forward) { // Process locally and notify local client *process = TRUE; *needs_reply = FALSE; *local_notify = TRUE; *needs_forward = FALSE; if (pcmk_is_set(operation->flags, cib__op_attr_local)) { /* Always process locally if cib__op_attr_local is set. * * @COMPAT: Currently host is ignored. At a compatibility break, throw * an error (from cib_process_request() or earlier) if host is not NULL or * OUR_NODENAME. */ crm_trace("Processing always-local %s op from client %s", op, pcmk__client_name(cib_client)); if (!pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { crm_warn("Operation '%s' is always local but its target host is " "set to '%s'", op, host); } return; } if (pcmk_is_set(operation->flags, cib__op_attr_modifies) || !pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { // Forward modifying and non-local requests via cluster *process = FALSE; *needs_reply = FALSE; *local_notify = FALSE; *needs_forward = TRUE; crm_trace("%s op from %s needs to be forwarded to %s", op, pcmk__client_name(cib_client), pcmk__s(host, "all nodes")); return; } if (stand_alone) { crm_trace("Processing %s op from client %s (stand-alone)", op, pcmk__client_name(cib_client)); } else { crm_trace("Processing %saddressed %s op from client %s", ((host != NULL)? "locally " : "un"), op, pcmk__client_name(cib_client)); } } static void parse_local_options(const pcmk__client_t *cib_client, const cib__operation_t *operation, 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, operation, call_options, host, op, local_notify, needs_reply, process, needs_forward); } else { parse_local_options_v2(cib_client, operation, call_options, host, op, local_notify, needs_reply, process, needs_forward); } } static gboolean parse_peer_options_v1(const cib__operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { const char *op = NULL; const char *host = NULL; const char *delegated = NULL; const char *originator = crm_element_value(request, PCMK__XA_SRC); const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_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, PCMK__XA_CIB_OP); crm_trace("Processing legacy %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, PCMK__XA_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, PCMK__XA_CIB_DELEGATED_FROM); 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(const cib__operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { const char *host = NULL; const char *delegated = crm_element_value(request, PCMK__XA_CIB_DELEGATED_FROM); const char *op = crm_element_value(request, PCMK__XA_CIB_OP); const char *originator = crm_element_value(request, PCMK__XA_SRC); const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO); gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); if (originator == NULL) { // Shouldn't be possible originator = "peer"; } if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { // sync_our_cib() sets PCMK__XA_CIB_ISREPLYTO 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 PCMK__XA_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 PCMK__XA_CIB_SCHEMA_MAX will be set which puts a * limit on how far newer nodes will go */ const char *max = crm_element_value(request, PCMK__XA_CIB_SCHEMA_MAX); const char *upgrade_rc = crm_element_value(request, PCMK__XA_CIB_UPGRADE_RC); crm_trace("Parsing upgrade %s for %s with max=%s and upgrade_rc=%s", (is_reply? "reply" : "request"), (based_is_primary? "primary" : "secondary"), pcmk__s(max, "none"), pcmk__s(upgrade_rc, "none")); if (upgrade_rc != NULL) { // Our upgrade request was rejected by DC, notify clients of result crm_xml_add(request, PCMK__XA_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 primary return FALSE; } } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_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 && pcmk_is_set(operation->flags, cib__op_attr_modifies)) { 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)) { *local_notify = FALSE; if (reply_to == NULL) { *process = TRUE; } else { // Not possible? crm_debug("Ignoring shutdown request from %s because reply_to=%s", originator, reply_to); } return *process; } if (is_reply) { crm_trace("Will notify local clients for %s reply from %s", 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, PCMK__XA_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) { crm_trace("Ignoring %s request intended for CIB manager 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 broadcast by %s call %s on %s " "(local clients will%s be notified)", op, pcmk__s(crm_element_value(request, PCMK__XA_CIB_CLIENTNAME), "client"), pcmk__s(crm_element_value(request, PCMK__XA_CIB_CALLID), "without ID"), originator, (*local_notify? "" : "not")); return TRUE; } static gboolean parse_peer_options(const cib__operation_t *operation, xmlNode *request, gboolean *local_notify, gboolean *needs_reply, gboolean *process) { /* 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(operation, request, local_notify, needs_reply, process); } else { return parse_peer_options_v2(operation, request, local_notify, needs_reply, process); } } /*! * \internal * \brief Forward a CIB request to the appropriate target host(s) * * \param[in] request CIB request to forward */ static void forward_request(xmlNode *request) { const char *op = crm_element_value(request, PCMK__XA_CIB_OP); const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION); const char *host = crm_element_value(request, PCMK__XA_CIB_HOST); const char *originator = crm_element_value(request, PCMK__XA_SRC); const char *client_name = crm_element_value(request, PCMK__XA_CIB_CLIENTNAME); const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID); crm_node_t *peer = NULL; 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)", pcmk__s(op, "invalid"), pcmk__s(section, "all"), pcmk__s(host, (cib_legacy_mode()? "primary" : "all")), pcmk__s(originator, "local"), pcmk__s(client_name, "unspecified"), pcmk__s(call_id, "unspecified")); crm_xml_add(request, PCMK__XA_CIB_DELEGATED_FROM, OUR_NODENAME); if (host != NULL) { peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); } pcmk__cluster_send_message(peer, crm_msg_cib, request); // Return the request to its original state pcmk__xe_remove_attr(request, PCMK__XA_CIB_DELEGATED_FROM); } static gboolean send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast) { CRM_ASSERT(msg != NULL); if (broadcast) { /* @COMPAT: Legacy code * * This successful call modified the CIB, and the change needs to be * broadcast (sent via cluster to all 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; xmlNode *wrapper = NULL; CRM_LOG_ASSERT(result_diff != NULL); digest = crm_element_value(result_diff, PCMK__XA_DIGEST); crm_element_value_int(result_diff, PCMK_XA_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, PCMK__XA_CIB_ISREPLYTO, originator); pcmk__xe_set_bool_attr(msg, PCMK__XA_CIB_UPDATE, true); crm_xml_add(msg, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_APPLY_PATCH); crm_xml_add(msg, PCMK__XA_CIB_USER, CRM_DAEMON_USER); if (format == 1) { CRM_ASSERT(digest != NULL); } wrapper = pcmk__xe_create(msg, PCMK__XE_CIB_UPDATE_DIFF); pcmk__xml_copy(wrapper, result_diff); crm_log_xml_explicit(msg, "copy"); return pcmk__cluster_send_message(NULL, crm_msg_cib, msg); } else if (originator != NULL) { /* send reply via HA to originating node */ const crm_node_t *node = pcmk__get_node(0, originator, NULL, pcmk__node_search_cluster_member); crm_trace("Sending request result to %s only", originator); crm_xml_add(msg, PCMK__XA_CIB_ISREPLYTO, originator); return pcmk__cluster_send_message(node, crm_msg_cib, msg); } 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) * * \return Legacy Pacemaker return code */ int cib_process_request(xmlNode *request, gboolean privileged, const pcmk__client_t *cib_client) { // @TODO: Break into multiple smaller functions 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, PCMK__XA_CIB_OP); const char *originator = crm_element_value(request, PCMK__XA_SRC); const char *host = crm_element_value(request, PCMK__XA_CIB_HOST); const char *target = NULL; const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID); const char *client_id = crm_element_value(request, PCMK__XA_CIB_CLIENTID); const char *client_name = crm_element_value(request, PCMK__XA_CIB_CLIENTNAME); const char *reply_to = crm_element_value(request, PCMK__XA_CIB_ISREPLYTO); const cib__operation_t *operation = NULL; cib__op_fn_t op_function = NULL; crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options); if ((host != NULL) && (*host == '\0')) { host = NULL; } // @TODO: Improve trace messages. Target is accurate only for legacy mode. 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, PCMK__XA_SRC, OUR_NODENAME); crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target); } rc = cib__get_operation(op, &operation); rc = pcmk_rc2legacy(rc); if (rc != pcmk_ok) { /* TODO: construct error reply? */ crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc)); return rc; } op_function = based_get_op_function(operation); if (op_function == NULL) { crm_err("Operation %s not supported by CIB manager", op); return -EOPNOTSUPP; } if (cib_client != NULL) { parse_local_options(cib_client, operation, call_options, host, op, &local_notify, &needs_reply, &process, &needs_forward); } else if (!parse_peer_options(operation, request, &local_notify, &needs_reply, &process)) { return rc; } if (pcmk_is_set(call_options, cib_transaction)) { /* All requests in a transaction are processed locally against a working * CIB copy, and we don't notify for individual requests because the * entire transaction is atomic. * * We still call the option parser functions above, for the sake of log * messages and checking whether we're the target for peer requests. */ process = TRUE; needs_reply = FALSE; local_notify = FALSE; needs_forward = FALSE; } is_update = pcmk_is_set(operation->flags, cib__op_attr_modifies); if (pcmk_is_set(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; crm_trace("Client is not interested in the reply"); } if (needs_forward) { forward_request(request); return rc; } if (cib_status != pcmk_ok) { rc = cib_status; crm_err("Operation ignored, cluster configuration is invalid." " Please repair and restart: %s", pcmk_strerror(cib_status)); op_reply = create_cib_reply(op, call_id, client_id, call_options, rc, the_cib); } else if (process) { time_t finished = 0; time_t now = time(NULL); int level = LOG_INFO; const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION); const char *admin_epoch_s = NULL; const char *epoch_s = NULL; const char *num_updates_s = NULL; rc = cib_process_command(request, operation, op_function, &op_reply, &result_diff, privileged); if (!is_update) { level = LOG_TRACE; } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_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; } if (the_cib != NULL) { admin_epoch_s = crm_element_value(the_cib, PCMK_XA_ADMIN_EPOCH); epoch_s = crm_element_value(the_cib, PCMK_XA_EPOCH); num_updates_s = crm_element_value(the_cib, PCMK_XA_NUM_UPDATES); } 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, pcmk__s(admin_epoch_s, "0"), pcmk__s(epoch_s, "0"), pcmk__s(num_updates_s, "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, PCMK__XA_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 ((cib_client == NULL) && !pcmk_is_set(call_options, cib_discard_reply)) { 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)); } } pcmk__xml_free(op_reply); pcmk__xml_free(result_diff); return rc; } /*! * \internal * \brief Get a CIB operation's input from the request XML * * \param[in] request CIB request XML * \param[in] type CIB operation type * \param[out] section Where to store CIB section name * * \return Input XML for CIB operation * * \note If not \c NULL, the return value is a non-const pointer to part of * \p request. The caller should not free it directly. */ static xmlNode * prepare_input(const xmlNode *request, enum cib__op_type type, const char **section) { xmlNode *wrapper = NULL; xmlNode *input = NULL; *section = NULL; switch (type) { case cib__op_apply_patch: { const char *wrapper_name = PCMK__XE_CIB_CALLDATA; if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) { wrapper_name = PCMK__XE_CIB_UPDATE_DIFF; } wrapper = pcmk__xe_first_child(request, wrapper_name, NULL, NULL); input = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); } break; default: wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, NULL, NULL); input = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); *section = crm_element_value(request, PCMK__XA_CIB_SECTION); break; } // Grab the specified section if ((*section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { input = pcmk_find_cib_element(input, *section); } return input; } // v1 and v2 patch formats #define XPATH_CONFIG_CHANGE \ "//" PCMK_XE_CRM_CONFIG " | " \ "//" PCMK_XE_CHANGE \ "[contains(@" PCMK_XA_PATH ",'/" PCMK_XE_CRM_CONFIG "/')]" static bool contains_config_change(xmlNode *diff) { bool changed = false; if (diff) { xmlXPathObject *xpathObj = xpath_search(diff, XPATH_CONFIG_CHANGE); if (numXpathResults(xpathObj) > 0) { changed = true; } freeXpathObject(xpathObj); } return changed; } static int cib_process_command(xmlNode *request, const cib__operation_t *operation, cib__op_fn_t op_function, xmlNode **reply, xmlNode **cib_diff, bool privileged) { xmlNode *input = NULL; xmlNode *output = NULL; xmlNode *result_cib = NULL; int call_options = 0; const char *op = NULL; const char *section = NULL; const char *call_id = crm_element_value(request, PCMK__XA_CIB_CALLID); const char *client_id = crm_element_value(request, PCMK__XA_CIB_CLIENTID); const char *client_name = crm_element_value(request, PCMK__XA_CIB_CLIENTNAME); const char *originator = crm_element_value(request, PCMK__XA_SRC); int rc = pcmk_ok; bool config_changed = false; bool manage_counters = true; static mainloop_timer_t *digest_timer = NULL; 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; /* Start processing the request... */ op = crm_element_value(request, PCMK__XA_CIB_OP); crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options); if (!privileged && pcmk_is_set(operation->flags, cib__op_attr_privileged)) { rc = -EACCES; crm_trace("Failed due to lack of privileges: %s", pcmk_strerror(rc)); goto done; } input = prepare_input(request, operation->type, §ion); if (!pcmk_is_set(operation->flags, cib__op_attr_modifies)) { rc = cib_perform_op(NULL, op, call_options, op_function, true, section, request, input, false, &config_changed, &the_cib, &result_cib, NULL, &output); CRM_CHECK(result_cib == NULL, pcmk__xml_free(result_cib)); goto done; } /* @COMPAT: Handle a valid write action (legacy) * * @TODO: Re-evaluate whether this is all truly legacy. The cib_force_diff * portion is. However, PCMK__XA_CIB_UPDATE may be set by a sync operation * even in non-legacy mode, and manage_counters tells xml_create_patchset() * whether to update version/epoch info. */ if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) { manage_counters = false; cib__set_call_options(call_options, "call", cib_force_diff); crm_trace("Global update detected"); CRM_LOG_ASSERT(pcmk__str_any_of(op, PCMK__CIB_REQUEST_APPLY_PATCH, PCMK__CIB_REQUEST_REPLACE, NULL)); } ping_modified_since = TRUE; if (pcmk_is_set(call_options, cib_inhibit_bcast)) { crm_trace("Skipping update: inhibit broadcast"); manage_counters = false; } // result_cib must not be modified after cib_perform_op() returns rc = cib_perform_op(NULL, op, call_options, op_function, false, section, request, input, manage_counters, &config_changed, &the_cib, &result_cib, cib_diff, &output); // @COMPAT: Legacy code if (!manage_counters) { int format = 1; // If the diff is NULL at this point, it's because nothing changed if (*cib_diff != NULL) { crm_element_value_int(*cib_diff, PCMK_XA_FORMAT, &format); } if (format == 1) { config_changed = cib__config_changed_v1(NULL, NULL, cib_diff); } } /* Always write to disk for successful ops with the flag set. This also * negates the need to detect ordering changes. */ if ((rc == pcmk_ok) && pcmk_is_set(operation->flags, cib__op_attr_writes_through)) { config_changed = true; } if ((rc == pcmk_ok) && !pcmk_any_flags_set(call_options, cib_dryrun|cib_transaction)) { if (result_cib != the_cib) { if (pcmk_is_set(operation->flags, cib__op_attr_writes_through)) { config_changed = true; } crm_trace("Activating %s->%s%s", crm_element_value(the_cib, PCMK_XA_NUM_UPDATES), crm_element_value(result_cib, PCMK_XA_NUM_UPDATES), (config_changed? " changed" : "")); rc = activateCibXml(result_cib, config_changed, op); if (rc != pcmk_ok) { crm_err("Failed to activate new CIB: %s", pcmk_strerror(rc)); } } if ((rc == pcmk_ok) && contains_config_change(*cib_diff)) { cib_read_config(config_hash, result_cib); } /* @COMPAT Nodes older than feature set 3.19.0 don't support * transactions. In a mixed-version cluster with nodes <3.19.0, we must * sync the updated CIB, so that the older nodes receive the changes. * Any node that has already applied the transaction will ignore the * synced CIB. * * To ensure the updated CIB is synced from only one node, we sync it * from the originator. */ if ((operation->type == cib__op_commit_transact) && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei) && compare_version(crm_element_value(the_cib, PCMK_XA_CRM_FEATURE_SET), "3.19.0") < 0) { sync_our_cib(request, TRUE); } mainloop_timer_stop(digest_timer); mainloop_timer_start(digest_timer); } else if (rc == -pcmk_err_schema_validation) { CRM_ASSERT(result_cib != the_cib); if (output != NULL) { crm_log_xml_info(output, "cib:output"); pcmk__xml_free(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, PCMK_XA_NUM_UPDATES)); if (result_cib != the_cib) { pcmk__xml_free(result_cib); } } if (!pcmk_any_flags_set(call_options, cib_dryrun|cib_inhibit_notify|cib_transaction)) { crm_trace("Sending notifications %d", pcmk_is_set(call_options, cib_dryrun)); cib_diff_notify(op, rc, call_id, client_id, client_name, originator, input, *cib_diff); } pcmk__log_xml_patchset(LOG_TRACE, *cib_diff); done: if (!pcmk_is_set(call_options, cib_discard_reply) || cib_legacy_mode()) { *reply = create_cib_reply(op, call_id, client_id, call_options, rc, output); } if (output != the_cib) { pcmk__xml_free(output); } 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, PCMK__XA_SRC); 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, PCMK__XA_CIB_LOCAL_NOTIFY_ID, &bcast_id) == 0) { check_local_notify(bcast_id); } return; } else if (crm_peer_cache == NULL) { reason = "membership not established"; goto bail; } if (crm_element_value(msg, PCMK__XA_CIB_CLIENTNAME) == NULL) { crm_xml_add(msg, PCMK__XA_CIB_CLIENTNAME, originator); } /* crm_log_xml_trace(msg, "Peer[inbound]"); */ cib_process_request(msg, TRUE, NULL); return; bail: if (reason) { const char *op = crm_element_value(msg, PCMK__XA_CIB_OP); crm_warn("Discarding %s message from %s: %s", op, 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)); } static void initiate_exit(void) { int active = 0; xmlNode *leaving = NULL; active = pcmk__cluster_num_active_nodes(); if (active < 2) { // This is the last active node terminate_cib(__func__, 0); return; } crm_info("Sending shutdown request to %d peers", active); leaving = pcmk__xe_create(NULL, PCMK__XE_EXIT_NOTIFICATION); crm_xml_add(leaving, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(leaving, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SHUTDOWN); pcmk__cluster_send_message(NULL, crm_msg_cib, leaving); pcmk__xml_free(leaving); g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL); } 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); } } 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 (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) { pcmk_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. */ pcmk_cluster_disconnect(crm_cluster); pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(CRM_EX_OK); } } diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c index 3ca1a5732c..341eccfd2b 100644 --- a/daemons/based/based_messages.c +++ b/daemons/based/based_messages.c @@ -1,538 +1,538 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Maximum number of diffs to ignore while waiting for a resync */ #define MAX_DIFF_RETRY 5 bool based_is_primary = false; xmlNode *the_cib = NULL; int cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *host = crm_element_value(req, PCMK__XA_SRC); *answer = NULL; if (crm_element_value(req, PCMK__XA_CIB_ISREPLYTO) == NULL) { crm_info("Peer %s is requesting to shut down", host); return pcmk_ok; } if (cib_shutdown_flag == FALSE) { crm_err("Peer %s mistakenly thinks we wanted to shut down", host); return -EINVAL; } crm_info("Peer %s has acknowledged our shutdown request", host); terminate_cib(__func__, 0); return pcmk_ok; } // @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed int cib_process_noop(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { crm_trace("Processing \"%s\" event", op); *answer = NULL; return pcmk_ok; } int cib_process_readwrite(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) { if (based_is_primary) { result = pcmk_ok; } else { result = -EPERM; } return result; } if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) { if (!based_is_primary) { crm_info("We are now in R/W mode"); based_is_primary = true; } else { crm_debug("We are still in R/W mode"); } } else if (based_is_primary) { crm_info("We are now in R/O mode"); based_is_primary = false; } return result; } /* Set to 1 when a sync is requested, incremented when a diff is ignored, * reset to 0 when a sync is received */ static int sync_in_progress = 0; void send_sync_request(const char *host) { xmlNode *sync_me = pcmk__xe_create(NULL, "sync-me"); crm_node_t *peer = NULL; crm_info("Requesting re-sync from %s", (host? host : "all peers")); sync_in_progress = 1; crm_xml_add(sync_me, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(sync_me, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SYNC_TO_ONE); crm_xml_add(sync_me, PCMK__XA_CIB_DELEGATED_FROM, stand_alone? "localhost" : crm_cluster->uname); if (host != NULL) { peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); } pcmk__cluster_send_message(peer, crm_msg_cib, sync_me); pcmk__xml_free(sync_me); } int cib_process_ping(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *host = crm_element_value(req, PCMK__XA_SRC); const char *seq = crm_element_value(req, PCMK__XA_CIB_PING_ID); - char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); + char *digest = pcmk__digest_xml(the_cib, true, CRM_FEATURE_SET); xmlNode *wrapper = NULL; crm_trace("Processing \"%s\" event %s from %s", op, seq, host); *answer = pcmk__xe_create(NULL, PCMK__XE_PING_RESPONSE); crm_xml_add(*answer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); crm_xml_add(*answer, PCMK__XA_DIGEST, digest); crm_xml_add(*answer, PCMK__XA_CIB_PING_ID, seq); wrapper = pcmk__xe_create(*answer, PCMK__XE_CIB_CALLDATA); if (the_cib != NULL) { pcmk__if_tracing( { /* Append additional detail so the receiver can log the * differences */ pcmk__xml_copy(wrapper, the_cib); }, { // Always include at least the version details const char *name = (const char *) the_cib->name; xmlNode *shallow = pcmk__xe_create(wrapper, name); pcmk__xe_copy_attrs(shallow, the_cib, pcmk__xaf_none); } ); } crm_info("Reporting our current digest to %s: %s for %s.%s.%s", host, digest, crm_element_value(existing_cib, PCMK_XA_ADMIN_EPOCH), crm_element_value(existing_cib, PCMK_XA_EPOCH), crm_element_value(existing_cib, PCMK_XA_NUM_UPDATES)); free(digest); return pcmk_ok; } int cib_process_sync(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return sync_our_cib(req, TRUE); } int cib_process_upgrade_server(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = pcmk_ok; *answer = NULL; if (crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX) != NULL) { /* The originator of an upgrade request sends it to the DC, without * PCMK__XA_CIB_SCHEMA_MAX. If an upgrade is needed, the DC * re-broadcasts the request with PCMK__XA_CIB_SCHEMA_MAX, and each node * performs the upgrade (and notifies its local clients) here. */ return cib_process_upgrade( op, options, section, req, input, existing_cib, result_cib, answer); } else { xmlNode *scratch = pcmk__xml_copy(NULL, existing_cib); const char *host = crm_element_value(req, PCMK__XA_SRC); const char *original_schema = NULL; const char *new_schema = NULL; const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID); const char *call_opts = crm_element_value(req, PCMK__XA_CIB_CALLOPT); const char *call_id = crm_element_value(req, PCMK__XA_CIB_CALLID); crm_trace("Processing \"%s\" event", op); original_schema = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH); rc = pcmk__update_schema(&scratch, NULL, true, true); rc = pcmk_rc2legacy(rc); new_schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH); if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { xmlNode *up = pcmk__xe_create(NULL, __func__); rc = pcmk_ok; crm_notice("Upgrade request from %s verified", host); crm_xml_add(up, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); crm_xml_add(up, PCMK__XA_CIB_SCHEMA_MAX, new_schema); crm_xml_add(up, PCMK__XA_CIB_DELEGATED_FROM, host); crm_xml_add(up, PCMK__XA_CIB_CLIENTID, client_id); crm_xml_add(up, PCMK__XA_CIB_CALLOPT, call_opts); crm_xml_add(up, PCMK__XA_CIB_CALLID, call_id); if (cib_legacy_mode() && based_is_primary) { rc = cib_process_upgrade( op, options, section, up, input, existing_cib, result_cib, answer); } else { pcmk__cluster_send_message(NULL, crm_msg_cib, up); } pcmk__xml_free(up); } else if(rc == pcmk_ok) { rc = -pcmk_err_schema_unchanged; } if (rc != pcmk_ok) { // Notify originating peer so it can notify its local clients crm_node_t *origin = NULL; origin = pcmk__search_node_caches(0, host, pcmk__node_search_cluster_member); crm_info("Rejecting upgrade request from %s: %s " CRM_XS " rc=%d peer=%s", host, pcmk_strerror(rc), rc, (origin? origin->uname : "lost")); if (origin) { xmlNode *up = pcmk__xe_create(NULL, __func__); crm_xml_add(up, PCMK__XA_T, PCMK__VALUE_CIB); crm_xml_add(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); crm_xml_add(up, PCMK__XA_CIB_DELEGATED_FROM, host); crm_xml_add(up, PCMK__XA_CIB_ISREPLYTO, host); crm_xml_add(up, PCMK__XA_CIB_CLIENTID, client_id); crm_xml_add(up, PCMK__XA_CIB_CALLOPT, call_opts); crm_xml_add(up, PCMK__XA_CIB_CALLID, call_id); crm_xml_add_int(up, PCMK__XA_CIB_UPGRADE_RC, rc); if (!pcmk__cluster_send_message(origin, crm_msg_cib, up)) { crm_warn("Could not send CIB upgrade result to %s", host); } pcmk__xml_free(up); } } pcmk__xml_free(scratch); } return rc; } int cib_process_sync_one(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return sync_our_cib(req, FALSE); } int cib_server_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = pcmk_ok; if (sync_in_progress > MAX_DIFF_RETRY) { /* Don't ignore diffs forever; the last request may have been lost. * If the diff fails, we'll ask for another full resync. */ sync_in_progress = 0; } // The primary instance should never ignore a diff if (sync_in_progress && !based_is_primary) { 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; cib_diff_version_details(input, &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); sync_in_progress++; crm_notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in progress)", diff_del_admin_epoch, diff_del_epoch, diff_del_updates, diff_add_admin_epoch, diff_add_epoch, diff_add_updates); return -pcmk_err_diff_resync; } rc = cib_process_diff(op, options, section, req, input, existing_cib, result_cib, answer); crm_trace("result: %s (%d), %s", pcmk_strerror(rc), rc, (based_is_primary? "primary": "secondary")); if ((rc == -pcmk_err_diff_resync) && !based_is_primary) { pcmk__xml_free(*result_cib); *result_cib = NULL; send_sync_request(NULL); } else if (rc == -pcmk_err_diff_resync) { rc = -pcmk_err_diff_failed; if (options & cib_force_diff) { crm_warn("Not requesting full refresh in R/W mode"); } } else if ((rc != pcmk_ok) && !based_is_primary && cib_legacy_mode()) { crm_warn("Requesting full CIB refresh because update failed: %s" CRM_XS " rc=%d", pcmk_strerror(rc), rc); pcmk__log_xml_patchset(LOG_INFO, input); pcmk__xml_free(*result_cib); *result_cib = NULL; send_sync_request(NULL); } return rc; } int cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer); if ((rc == pcmk_ok) && pcmk__xe_is(input, PCMK_XE_CIB)) { sync_in_progress = 0; } return rc; } // @COMPAT: Remove when PCMK__CIB_REQUEST_ABS_DELETE is removed int cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { return -EINVAL; } static xmlNode * cib_msg_copy(xmlNode *msg) { static const char *field_list[] = { PCMK__XA_T, PCMK__XA_CIB_CLIENTID, PCMK__XA_CIB_CALLOPT, PCMK__XA_CIB_CALLID, PCMK__XA_CIB_OP, PCMK__XA_CIB_ISREPLYTO, PCMK__XA_CIB_SECTION, PCMK__XA_CIB_HOST, PCMK__XA_CIB_RC, PCMK__XA_CIB_DELEGATED_FROM, PCMK__XA_CIB_OBJECT, PCMK__XA_CIB_OBJECT_TYPE, PCMK__XA_CIB_UPDATE, PCMK__XA_CIB_CLIENTNAME, PCMK__XA_CIB_USER, PCMK__XA_CIB_NOTIFY_TYPE, PCMK__XA_CIB_NOTIFY_ACTIVATE, }; xmlNode *copy = pcmk__xe_create(NULL, PCMK__XE_COPY); for (int lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) { const char *field = field_list[lpc]; const char *value = crm_element_value(msg, field); if (value != NULL) { crm_xml_add(copy, field, value); } } return copy; } int sync_our_cib(xmlNode * request, gboolean all) { int result = pcmk_ok; char *digest = NULL; const char *host = crm_element_value(request, PCMK__XA_SRC); const char *op = crm_element_value(request, PCMK__XA_CIB_OP); crm_node_t *peer = NULL; xmlNode *replace_request = NULL; xmlNode *wrapper = NULL; CRM_CHECK(the_cib != NULL, return -EINVAL); CRM_CHECK(all || (host != NULL), return -EINVAL); crm_debug("Syncing CIB to %s", all ? "all peers" : host); replace_request = cib_msg_copy(request); if (host != NULL) { crm_xml_add(replace_request, PCMK__XA_CIB_ISREPLYTO, host); } if (all) { pcmk__xe_remove_attr(replace_request, PCMK__XA_CIB_HOST); } crm_xml_add(replace_request, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_REPLACE); // @TODO Keep for tracing, or drop? crm_xml_add(replace_request, PCMK__XA_ORIGINAL_CIB_OP, op); pcmk__xe_set_bool_attr(replace_request, PCMK__XA_CIB_UPDATE, true); crm_xml_add(replace_request, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); - digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET); + digest = pcmk__digest_xml(the_cib, true, CRM_FEATURE_SET); crm_xml_add(replace_request, PCMK__XA_DIGEST, digest); wrapper = pcmk__xe_create(replace_request, PCMK__XE_CIB_CALLDATA); pcmk__xml_copy(wrapper, the_cib); if (!all) { peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); } if (!pcmk__cluster_send_message(peer, crm_msg_cib, replace_request)) { result = -ENOTCONN; } pcmk__xml_free(replace_request); free(digest); return result; } int cib_process_commit_transaction(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { /* On success, our caller will activate *result_cib locally, trigger a * replace notification if appropriate, and sync *result_cib to all nodes. * On failure, our caller will free *result_cib. */ int rc = pcmk_rc_ok; const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID); const char *origin = crm_element_value(req, PCMK__XA_SRC); pcmk__client_t *client = pcmk__find_client_by_id(client_id); rc = based_commit_transaction(input, client, origin, result_cib); if (rc != pcmk_rc_ok) { char *source = based_transaction_source_str(client, origin); crm_err("Could not commit transaction for %s: %s", source, pcmk_rc_str(rc)); free(source); } return pcmk_rc2legacy(rc); } int cib_process_schemas(const char *op, int options, const char *section, xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { xmlNode *wrapper = NULL; xmlNode *data = NULL; const char *after_ver = NULL; GList *schemas = NULL; GList *already_included = NULL; *answer = pcmk__xe_create(NULL, PCMK__XA_SCHEMAS); wrapper = pcmk__xe_first_child(req, PCMK__XE_CIB_CALLDATA, NULL, NULL); data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); if (data == NULL) { crm_warn("No data specified in request"); return -EPROTO; } after_ver = crm_element_value(data, PCMK_XA_VERSION); if (after_ver == NULL) { crm_warn("No version specified in request"); return -EPROTO; } /* The client requested all schemas after the latest one we know about, which * means the client is fully up-to-date. Return a properly formatted reply * with no schemas. */ if (pcmk__str_eq(after_ver, pcmk__highest_schema_name(), pcmk__str_none)) { return pcmk_ok; } schemas = pcmk__schema_files_later_than(after_ver); for (GList *iter = schemas; iter != NULL; iter = iter->next) { pcmk__build_schema_xml_node(*answer, iter->data, &already_included); } g_list_free_full(schemas, free); g_list_free_full(already_included, free); return pcmk_ok; } diff --git a/daemons/schedulerd/schedulerd_messages.c b/daemons/schedulerd/schedulerd_messages.c index 538583ac68..fd7216f71e 100644 --- a/daemons/schedulerd/schedulerd_messages.c +++ b/daemons/schedulerd/schedulerd_messages.c @@ -1,340 +1,339 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include "pacemaker-schedulerd.h" static GHashTable *schedulerd_handlers = NULL; static pcmk_scheduler_t * init_working_set(void) { pcmk_scheduler_t *scheduler = pe_new_working_set(); pcmk__mem_assert(scheduler); crm_config_error = FALSE; crm_config_warning = FALSE; was_processing_error = FALSE; was_processing_warning = FALSE; scheduler->priv = logger_out; return scheduler; } static xmlNode * handle_pecalc_request(pcmk__request_t *request) { static struct series_s { const char *name; const char *param; /* Maximum number of inputs of this kind to save to disk. * If -1, save all; if 0, save none. */ int wrap; } series[] = { { "pe-error", PCMK_OPT_PE_ERROR_SERIES_MAX, -1 }, { "pe-warn", PCMK_OPT_PE_WARN_SERIES_MAX, 5000 }, { "pe-input", PCMK_OPT_PE_INPUT_SERIES_MAX, 4000 }, }; xmlNode *msg = request->xml; xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CRM_XML, NULL, NULL); xmlNode *xml_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); static char *last_digest = NULL; static char *filename = NULL; unsigned int seq; int series_id = 0; int series_wrap = 0; char *digest = NULL; const char *value = NULL; time_t execution_date = time(NULL); xmlNode *converted = NULL; xmlNode *reply = NULL; bool is_repoke = false; bool process = true; pcmk_scheduler_t *scheduler = init_working_set(); pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE); - digest = calculate_xml_versioned_digest(xml_data, FALSE, FALSE, - CRM_FEATURE_SET); + digest = pcmk__digest_xml(xml_data, false, CRM_FEATURE_SET); converted = pcmk__xml_copy(NULL, xml_data); if (pcmk_update_configured_schema(&converted, true) != pcmk_rc_ok) { scheduler->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH); crm_xml_add_int(scheduler->graph, "transition_id", 0); crm_xml_add_int(scheduler->graph, PCMK_OPT_CLUSTER_DELAY, 0); process = false; free(digest); } else if (pcmk__str_eq(digest, last_digest, pcmk__str_casei)) { is_repoke = true; free(digest); } else { free(last_digest); last_digest = digest; } if (process) { pcmk__schedule_actions(converted, pcmk_sched_no_counts |pcmk_sched_no_compat |pcmk_sched_show_utilization, scheduler); } // Get appropriate index into series[] array if (was_processing_error || crm_config_error) { series_id = 0; } else if (was_processing_warning || crm_config_warning) { series_id = 1; } else { series_id = 2; } value = pcmk__cluster_option(scheduler->config_hash, series[series_id].param); if ((value == NULL) || (pcmk__scan_min_int(value, &series_wrap, -1) != pcmk_rc_ok)) { series_wrap = series[series_id].wrap; } if (pcmk__read_series_sequence(PE_STATE_DIR, series[series_id].name, &seq) != pcmk_rc_ok) { // @TODO maybe handle errors better ... seq = 0; } crm_trace("Series %s: wrap=%d, seq=%u, pref=%s", series[series_id].name, series_wrap, seq, value); scheduler->input = NULL; reply = create_reply(msg, scheduler->graph); if (reply == NULL) { pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR, "Failed building ping reply for client %s", pcmk__client_name(request->ipc_client)); goto done; } if (series_wrap == 0) { // Don't save any inputs of this kind free(filename); filename = NULL; } else if (!is_repoke) { // Input changed, save to disk free(filename); filename = pcmk__series_filename(PE_STATE_DIR, series[series_id].name, seq, true); } crm_xml_add(reply, PCMK__XA_CRM_TGRAPH_IN, filename); crm_xml_add_int(reply, PCMK__XA_GRAPH_ERRORS, was_processing_error); crm_xml_add_int(reply, PCMK__XA_GRAPH_WARNINGS, was_processing_warning); crm_xml_add_int(reply, PCMK__XA_CONFIG_ERRORS, crm_config_error); crm_xml_add_int(reply, PCMK__XA_CONFIG_WARNINGS, crm_config_warning); pcmk__log_transition_summary(filename); if (series_wrap == 0) { crm_debug("Not saving input to disk (disabled by configuration)"); } else if (is_repoke) { crm_info("Input has not changed since last time, not saving to disk"); } else { unlink(filename); crm_xml_add_ll(xml_data, PCMK_XA_EXECUTION_DATE, (long long) execution_date); pcmk__xml_write_file(xml_data, filename, true, NULL); pcmk__write_series_sequence(PE_STATE_DIR, series[series_id].name, ++seq, series_wrap); } pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); done: pcmk__xml_free(converted); pe_free_working_set(scheduler); return reply; } static xmlNode * handle_unknown_request(pcmk__request_t *request) { pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, PCMK__XE_ACK, NULL, CRM_EX_INVALID_PARAM); pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID, "Unknown IPC request type '%s' (bug?)", pcmk__client_name(request->ipc_client)); return NULL; } static xmlNode * handle_hello_request(pcmk__request_t *request) { pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags, PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE); crm_trace("Received IPC hello from %s", pcmk__client_name(request->ipc_client)); pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); return NULL; } static void schedulerd_register_handlers(void) { pcmk__server_command_t handlers[] = { { CRM_OP_HELLO, handle_hello_request }, { CRM_OP_PECALC, handle_pecalc_request }, { NULL, handle_unknown_request }, }; schedulerd_handlers = pcmk__register_handlers(handlers); } static int32_t pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (pcmk__new_client(c, uid, gid) == NULL) { return -ENOMEM; } return 0; } static int32_t pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; xmlNode *msg = NULL; pcmk__client_t *c = pcmk__find_client(qbc); const char *sys_to = NULL; CRM_CHECK(c != NULL, return 0); if (schedulerd_handlers == NULL) { schedulerd_register_handlers(); } msg = pcmk__client_data2xml(c, data, &id, &flags); if (msg == NULL) { pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, CRM_EX_PROTOCOL); return 0; } sys_to = crm_element_value(msg, PCMK__XA_CRM_SYS_TO); if (pcmk__str_eq(crm_element_value(msg, PCMK__XA_SUBT), PCMK__VALUE_RESPONSE, pcmk__str_none)) { pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE); crm_info("Ignoring IPC reply from %s", pcmk__client_name(c)); } else if (!pcmk__str_eq(sys_to, CRM_SYSTEM_PENGINE, pcmk__str_none)) { pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE); crm_info("Ignoring invalid IPC message: to '%s' not " CRM_SYSTEM_PENGINE, pcmk__s(sys_to, "")); } else { char *log_msg = NULL; const char *reason = NULL; xmlNode *reply = NULL; pcmk__request_t request = { .ipc_client = c, .ipc_id = id, .ipc_flags = flags, .peer = NULL, .xml = msg, .call_options = 0, .result = PCMK__UNKNOWN_RESULT, }; request.op = crm_element_value_copy(request.xml, PCMK__XA_CRM_TASK); CRM_CHECK(request.op != NULL, return 0); reply = pcmk__process_request(&request, schedulerd_handlers); if (reply != NULL) { pcmk__ipc_send_xml(c, id, reply, crm_ipc_server_event); pcmk__xml_free(reply); } reason = request.result.exit_reason; log_msg = crm_strdup_printf("Processed %s request from %s %s: %s%s%s%s", request.op, pcmk__request_origin_type(&request), pcmk__request_origin(&request), pcmk_exec_status_str(request.result.execution_status), (reason == NULL)? "" : " (", (reason == NULL)? "" : reason, (reason == NULL)? "" : ")"); if (!pcmk__result_ok(&request.result)) { crm_warn("%s", log_msg); } else { crm_debug("%s", log_msg); } free(log_msg); pcmk__reset_request(&request); } pcmk__xml_free(msg); return 0; } /* Error code means? */ static int32_t pe_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 pe_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); pe_ipc_closed(c); } struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = pe_ipc_accept, .connection_created = NULL, .msg_process = pe_ipc_dispatch, .connection_closed = pe_ipc_closed, .connection_destroyed = pe_ipc_destroy }; diff --git a/include/crm/common/digest_internal.h b/include/crm/common/digest_internal.h index ad446fdef9..43565d5c41 100644 --- a/include/crm/common/digest_internal.h +++ b/include/crm/common/digest_internal.h @@ -1,45 +1,46 @@ /* * Copyright 2015-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_DIGEST_INTERNAL__H #define PCMK__CRM_COMMON_DIGEST_INTERNAL__H /* * Internal-only functions to create digest strings from XML */ #include #include // xmlNode // Digest comparison results enum pcmk__digest_result { pcmk__digest_unknown, // No digest available for comparison pcmk__digest_match, // Digests match pcmk__digest_mismatch, // Any parameter changed (potentially reloadable) pcmk__digest_restart, // Parameters that require a restart changed }; // Information needed to compare operation digests typedef struct { enum pcmk__digest_result rc; // Result of digest comparison xmlNode *params_all; // All operation parameters xmlNode *params_secure; // Parameters marked private xmlNode *params_restart; // Parameters marked not reloadable char *digest_all_calc; // Digest of params_all char *digest_secure_calc; // Digest of params_secure char *digest_restart_calc; // Digest of params_restart } pcmk__op_digest_t; char *pcmk__digest_on_disk_cib(xmlNode *input); char *pcmk__digest_operation(xmlNode *input); +char *pcmk__digest_xml(xmlNode *input, bool filter, const char *version); bool pcmk__verify_digest(xmlNode *input, const char *expected); #endif // PCMK__CRM_COMMON_DIGEST_INTERNAL__H diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c index 024d3ea020..c014d37875 100644 --- a/lib/cib/cib_ops.c +++ b/lib/cib/cib_ops.c @@ -1,909 +1,908 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon static GHashTable *operation_table = NULL; static const cib__operation_t cib_ops[] = { { PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete, cib__op_attr_modifies|cib__op_attr_privileged }, { PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_BUMP, cib__op_bump, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_writes_through }, { PCMK__CIB_REQUEST_CREATE, cib__op_create, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_DELETE, cib__op_delete, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_ERASE, cib__op_erase, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_MODIFY, cib__op_modify, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none }, { CRM_OP_PING, cib__op_ping, cib__op_attr_none }, { // @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support PCMK__CIB_REQUEST_PRIMARY, cib__op_primary, cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local }, { PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none }, { PCMK__CIB_REQUEST_REPLACE, cib__op_replace, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_replaces |cib__op_attr_writes_through |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary, cib__op_attr_privileged|cib__op_attr_local }, { PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade, cib__op_attr_modifies |cib__op_attr_privileged |cib__op_attr_writes_through |cib__op_attr_transaction }, { PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local } }; /*! * \internal * \brief Get the \c cib__operation_t object for a given CIB operation name * * \param[in] op CIB operation name * \param[out] operation Where to store CIB operation object * * \return Standard Pacemaker return code */ int cib__get_operation(const char *op, const cib__operation_t **operation) { CRM_ASSERT((op != NULL) && (operation != NULL)); if (operation_table == NULL) { operation_table = pcmk__strkey_table(NULL, NULL); for (int lpc = 0; lpc < PCMK__NELEM(cib_ops); lpc++) { const cib__operation_t *oper = &(cib_ops[lpc]); g_hash_table_insert(operation_table, (gpointer) oper->name, (gpointer) oper); } } *operation = g_hash_table_lookup(operation_table, op); if (*operation == NULL) { crm_err("Operation %s is invalid", op); return EINVAL; } return pcmk_rc_ok; } int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } CRM_CHECK(*answer == NULL, pcmk__xml_free(*answer)); *answer = NULL; if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } obj_root = pcmk_find_cib_element(existing_cib, section); if (obj_root == NULL) { result = -ENXIO; } else if (options & cib_no_children) { xmlNode *shallow = pcmk__xe_create(*answer, (const char *) obj_root->name); pcmk__xe_copy_attrs(shallow, obj_root, pcmk__xaf_none); *answer = shallow; } else { *answer = obj_root; } if (result == pcmk_ok && *answer == NULL) { crm_err("Error creating query response"); result = -ENOMSG; } return result; } static int update_counter(xmlNode *xml_obj, const char *field, bool reset) { char *new_value = NULL; char *old_value = NULL; int int_value = -1; if (!reset && crm_element_value(xml_obj, field) != NULL) { old_value = crm_element_value_copy(xml_obj, field); } if (old_value != NULL) { int_value = atoi(old_value); new_value = pcmk__itoa(++int_value); } else { new_value = pcmk__str_copy("1"); } crm_trace("Update %s from %s to %s", field, pcmk__s(old_value, "unset"), new_value); crm_xml_add(xml_obj, field, new_value); free(new_value); free(old_value); return pcmk_ok; } int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = createEmptyCib(0); pcmk__xe_copy_attrs(*result_cib, existing_cib, pcmk__xaf_none); update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); *answer = NULL; return result; } int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = 0; const char *max_schema = crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX); const char *original_schema = NULL; const char *new_schema = NULL; *answer = NULL; crm_trace("Processing \"%s\" event with max=%s", op, max_schema); original_schema = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH); rc = pcmk__update_schema(result_cib, max_schema, true, !pcmk_is_set(options, cib_verbose)); rc = pcmk_rc2legacy(rc); new_schema = crm_element_value(*result_cib, PCMK_XA_VALIDATE_WITH); if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); update_counter(*result_cib, PCMK_XA_EPOCH, true); update_counter(*result_cib, PCMK_XA_NUM_UPDATES, true); return pcmk_ok; } return rc; } int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing %s for epoch='%s'", op, pcmk__s(crm_element_value(existing_cib, PCMK_XA_EPOCH), "")); *answer = NULL; update_counter(*result_cib, PCMK_XA_EPOCH, false); return result; } int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } *answer = NULL; if (input == NULL) { return -EINVAL; } if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__xe_is(input, section)) { section = NULL; } if (pcmk__xe_is(input, PCMK_XE_CIB)) { int updates = 0; int epoch = 0; int admin_epoch = 0; int replace_updates = 0; int replace_epoch = 0; int replace_admin_epoch = 0; const char *reason = NULL; const char *peer = crm_element_value(req, PCMK__XA_SRC); const char *digest = crm_element_value(req, PCMK__XA_DIGEST); if (digest) { - const char *version = crm_element_value(req, - PCMK_XA_CRM_FEATURE_SET); - char *digest_verify = calculate_xml_versioned_digest(input, FALSE, TRUE, - version ? version : - CRM_FEATURE_SET); + const char *version = + pcmk__s(crm_element_value(req, PCMK_XA_CRM_FEATURE_SET), + CRM_FEATURE_SET); + char *digest_verify = pcmk__digest_xml(input, true, version); if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) { crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer, digest_verify, digest); reason = "digest mismatch"; } else { crm_info("Digest matched on replace from %s: %s", peer, digest); } free(digest_verify); } else { crm_trace("No digest to verify"); } cib_version_details(existing_cib, &admin_epoch, &epoch, &updates); cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates); if (replace_admin_epoch < admin_epoch) { reason = PCMK_XA_ADMIN_EPOCH; } else if (replace_admin_epoch > admin_epoch) { /* no more checks */ } else if (replace_epoch < epoch) { reason = PCMK_XA_EPOCH; } else if (replace_epoch > epoch) { /* no more checks */ } else if (replace_updates < updates) { reason = PCMK_XA_NUM_UPDATES; } if (reason != NULL) { crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:" " current %s is greater than the replacement", replace_admin_epoch, replace_epoch, replace_updates, peer, admin_epoch, epoch, updates, reason); result = -pcmk_err_old_data; } else { crm_info("Replaced %d.%d.%d with %d.%d.%d from %s", admin_epoch, epoch, updates, replace_admin_epoch, replace_epoch, replace_updates, peer); } if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = pcmk__xml_copy(NULL, input); } else { xmlNode *obj_root = NULL; obj_root = pcmk_find_cib_element(*result_cib, section); result = pcmk__xe_replace_match(obj_root, input); result = pcmk_rc2legacy(result); if (result != pcmk_ok) { crm_trace("No matching object to replace"); } } return result; } static int delete_child(xmlNode *child, void *userdata) { xmlNode *obj_root = userdata; if (pcmk__xe_delete_match(obj_root, child) != pcmk_rc_ok) { crm_trace("No matching object to delete: %s=%s", child->name, pcmk__xe_id(child)); } return pcmk_rc_ok; } int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if (pcmk__xe_is(input, section)) { pcmk__xe_foreach_child(input, NULL, delete_child, obj_root); } else { delete_child(input, obj_root); } return pcmk_ok; } int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; uint32_t flags = pcmk__xaf_none; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if (obj_root == NULL) { xmlNode *tmp_section = NULL; const char *path = pcmk_cib_parent_name_for(section); if (path == NULL) { return -EINVAL; } tmp_section = pcmk__xe_create(NULL, section); cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section, NULL, result_cib, answer); pcmk__xml_free(tmp_section); obj_root = pcmk_find_cib_element(*result_cib, section); } CRM_CHECK(obj_root != NULL, return -EINVAL); if (pcmk_is_set(options, cib_score_update)) { flags |= pcmk__xaf_score_update; } if (pcmk__xe_update_match(obj_root, input, flags) != pcmk_rc_ok) { if (options & cib_can_create) { pcmk__xml_copy(obj_root, input); } else { return -ENXIO; } } // @COMPAT cib_mixed_update is deprecated as of 2.1.7 if (pcmk_is_set(options, cib_mixed_update)) { int max = 0, lpc; xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__"); if (xpathObj) { max = numXpathResults(xpathObj); crm_log_xml_trace(*result_cib, "Mixed result"); } for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); xmlChar *match_path = xmlGetNodePath(match); crm_debug("Destroying %s", match_path); free(match_path); pcmk__xml_free(match); } freeXpathObject(xpathObj); } return pcmk_ok; } static int add_cib_object(xmlNode * parent, xmlNode * new_obj) { const char *object_name = NULL; const char *object_id = NULL; if ((parent == NULL) || (new_obj == NULL)) { return -EINVAL; } object_name = (const char *) new_obj->name; if (object_name == NULL) { return -EINVAL; } object_id = pcmk__xe_id(new_obj); if (pcmk__xe_first_child(parent, object_name, ((object_id != NULL)? PCMK_XA_ID : NULL), object_id)) { return -EEXIST; } if (object_id != NULL) { crm_trace("Processing creation of <%s " PCMK_XA_ID "='%s'>", object_name, object_id); } else { crm_trace("Processing creation of <%s>", object_name); } /* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use * case, PCMK__XA_REPLACE has special meaning and should not be included in * the newly created object until we can break behavioral backward * compatibility. * * At a compatibility break, drop this and drop the definition of * PCMK__XA_REPLACE. Treat it like any other attribute. */ pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb, (void *) PCMK__XA_REPLACE); pcmk__xml_copy(parent, new_obj); return pcmk_ok; } static bool update_results(xmlNode *failed, xmlNode *target, const char *operation, int return_code) { xmlNode *xml_node = NULL; bool was_error = false; const char *error_msg = NULL; if (return_code != pcmk_ok) { error_msg = pcmk_strerror(return_code); was_error = true; xml_node = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE); pcmk__xml_copy(xml_node, target); crm_xml_add(xml_node, PCMK_XA_ID, pcmk__xe_id(target)); crm_xml_add(xml_node, PCMK_XA_OBJECT_TYPE, (const char *) target->name); crm_xml_add(xml_node, PCMK_XA_OPERATION, operation); crm_xml_add(xml_node, PCMK_XA_REASON, error_msg); crm_warn("Action %s failed: %s (cde=%d)", operation, error_msg, return_code); } return was_error; } int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *failed = NULL; int result = pcmk_ok; xmlNode *update_section = NULL; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) { section = NULL; } else if (pcmk__xe_is(input, PCMK_XE_CIB)) { section = NULL; } CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL); if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } if (section == NULL) { return cib_process_modify(op, options, section, req, input, existing_cib, result_cib, answer); } // @COMPAT Deprecated since 2.1.8 failed = pcmk__xe_create(NULL, PCMK__XE_FAILED); update_section = pcmk_find_cib_element(*result_cib, section); if (pcmk__xe_is(input, section)) { xmlNode *a_child = NULL; for (a_child = pcmk__xml_first_child(input); a_child != NULL; a_child = pcmk__xml_next(a_child)) { result = add_cib_object(update_section, a_child); if (update_results(failed, a_child, op, result)) { break; } } } else { result = add_cib_object(update_section, input); update_results(failed, input, op, result); } if ((result == pcmk_ok) && (failed->children != NULL)) { result = -EINVAL; } if (result != pcmk_ok) { crm_log_xml_err(failed, "CIB Update failures"); *answer = failed; } else { pcmk__xml_free(failed); } return result; } int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *originator = NULL; if (req != NULL) { originator = crm_element_value(req, PCMK__XA_SRC); } crm_trace("Processing \"%s\" event from %s%s", op, originator, (pcmk_is_set(options, cib_force_diff)? " (global update)" : "")); if (*result_cib != existing_cib) { pcmk__xml_free(*result_cib); } *result_cib = pcmk__xml_copy(NULL, existing_cib); return xml_apply_patchset(*result_cib, input, TRUE); } // @COMPAT: v1-only bool cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff) { int lpc = 0, max = 0; bool config_changes = false; xmlXPathObject *xpathObj = NULL; int format = 1; CRM_ASSERT(diff != NULL); if (*diff == NULL && last != NULL && next != NULL) { *diff = pcmk__diff_v1_xml_object(last, next, false); } if (*diff == NULL) { goto done; } crm_element_value_int(*diff, PCMK_XA_FORMAT, &format); CRM_LOG_ASSERT(format == 1); xpathObj = xpath_search(*diff, "//" PCMK_XE_CONFIGURATION); if (numXpathResults(xpathObj) > 0) { config_changes = true; goto done; } freeXpathObject(xpathObj); /* * Do not check PCMK__XE_DIFF_ADDED "//" PCMK_XE_CIB * This always contains every field and would produce a false positive * every time if the checked value existed */ xpathObj = xpath_search(*diff, "//" PCMK__XE_DIFF_REMOVED "//" PCMK_XE_CIB); max = numXpathResults(xpathObj); for (lpc = 0; lpc < max; lpc++) { xmlNode *top = getXpathResult(xpathObj, lpc); if (crm_element_value(top, PCMK_XA_EPOCH) != NULL) { config_changes = true; goto done; } if (crm_element_value(top, PCMK_XA_ADMIN_EPOCH) != NULL) { config_changes = true; goto done; } if (crm_element_value(top, PCMK_XA_VALIDATE_WITH) != NULL) { config_changes = true; goto done; } if (crm_element_value(top, PCMK_XA_CRM_FEATURE_SET) != NULL) { config_changes = true; goto done; } if (crm_element_value(top, PCMK_XA_REMOTE_CLEAR_PORT) != NULL) { config_changes = true; goto done; } if (crm_element_value(top, PCMK_XA_REMOTE_TLS_PORT) != NULL) { config_changes = true; goto done; } } done: freeXpathObject(xpathObj); return config_changes; } int cib_process_xpath(const char *op, int options, const char *section, const xmlNode *req, xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, xmlNode **answer) { int lpc = 0; int max = 0; int rc = pcmk_ok; bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none); xmlXPathObjectPtr xpathObj = NULL; crm_trace("Processing \"%s\" event", op); if (is_query) { xpathObj = xpath_search(existing_cib, section); } else { xpathObj = xpath_search(*result_cib, section); } max = numXpathResults(xpathObj); if ((max < 1) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { crm_debug("%s was already removed", section); } else if (max < 1) { crm_debug("%s: %s does not exist", op, section); rc = -ENXIO; } else if (is_query) { if (max > 1) { *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } } if (pcmk_is_set(options, cib_multiple) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { dedupXpathResults(xpathObj); } for (lpc = 0; lpc < max; lpc++) { xmlChar *path = NULL; xmlNode *match = getXpathResult(xpathObj, lpc); if (match == NULL) { continue; } path = xmlGetNodePath(match); crm_debug("Processing %s op for %s with %s", op, section, path); free(path); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { if (match == *result_cib) { /* Attempting to delete the whole "/cib" */ crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section); rc = -EINVAL; break; } pcmk__xml_free(match); if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { uint32_t flags = pcmk__xaf_none; if (pcmk_is_set(options, cib_score_update)) { flags |= pcmk__xaf_score_update; } if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) { rc = -ENXIO; } else if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) { pcmk__xml_copy(match, input); break; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { if (options & cib_no_children) { xmlNode *shallow = pcmk__xe_create(*answer, (const char *) match->name); pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); if (*answer == NULL) { *answer = shallow; } } else if (options & cib_xpath_address) { char *path = NULL; xmlNode *parent = match; while (parent && parent->type == XML_ELEMENT_NODE) { const char *id = crm_element_value(parent, PCMK_XA_ID); char *new_path = NULL; if (id) { new_path = crm_strdup_printf("/%s[@" PCMK_XA_ID "='%s']" "%s", parent->name, id, pcmk__s(path, "")); } else { new_path = crm_strdup_printf("/%s%s", parent->name, pcmk__s(path, "")); } free(path); path = new_path; parent = parent->parent; } crm_trace("Got: %s", path); if (*answer == NULL) { *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH); crm_xml_add(parent, PCMK_XA_ID, path); free(path); } else if (*answer) { pcmk__xml_copy(*answer, match); } else { *answer = match; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { xmlNode *parent = match->parent; pcmk__xml_free(match); pcmk__xml_copy(parent, input); if ((options & cib_multiple) == 0) { break; } } } freeXpathObject(xpathObj); return rc; } diff --git a/lib/common/digest.c b/lib/common/digest.c index 98e9762e04..4d5aa0f20e 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -1,369 +1,402 @@ /* * Copyright 2015-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include // GString, etc. #include // gnutls_hash_fast(), gnutls_hash_get_len() #include // gnutls_strerror() #include #include #include "crmcommon_private.h" #define BEST_EFFORT_STATUS 0 /*! * \internal * \brief Dump XML in a format used with v1 digests * * \param[in] xml Root of XML to dump * * \return Newly allocated buffer containing dumped XML */ static GString * dump_xml_for_digest(xmlNodePtr xml) { GString *buffer = g_string_sized_new(1024); /* for compatibility with the old result which is used for v1 digests */ g_string_append_c(buffer, ' '); pcmk__xml_string(xml, 0, buffer, 0); g_string_append_c(buffer, '\n'); return buffer; } /*! * \internal * \brief Calculate and return v1 digest of XML tree * * \param[in] input Root of XML to digest * \param[in] sort Whether to sort the XML before calculating digest * * \return Newly allocated string containing digest * * \note Example return value: "c048eae664dba840e1d2060f00299e9d" */ static char * calculate_xml_digest_v1(xmlNode *input, bool sort) { char *digest = NULL; GString *buffer = NULL; xmlNode *copy = NULL; if (sort) { crm_trace("Sorting xml..."); copy = sorted_xml(input, NULL, TRUE); crm_trace("Done"); input = copy; } buffer = dump_xml_for_digest(input); CRM_CHECK(buffer->len > 0, pcmk__xml_free(copy); g_string_free(buffer, TRUE); return NULL); digest = crm_md5sum((const char *) buffer->str); crm_log_xml_trace(input, "digest:source"); g_string_free(buffer, TRUE); pcmk__xml_free(copy); return digest; } /*! * \internal * \brief Calculate and return v2 digest of XML tree * * \param[in] source Root of XML to digest * \param[in] filter Whether to filter certain XML attributes * * \return Newly allocated string containing digest */ static char * calculate_xml_digest_v2(const xmlNode *source, bool filter) { char *digest = NULL; GString *buf = g_string_sized_new(1024); pcmk__xml_string(source, (filter? pcmk__xml_fmt_filtered : 0), buf, 0); digest = crm_md5sum(buf->str); pcmk__if_tracing( { char *trace_file = crm_strdup_printf("%s/digest-%s", pcmk__get_tmpdir(), digest); crm_trace("Saving %s.%s.%s to %s", crm_element_value(source, PCMK_XA_ADMIN_EPOCH), crm_element_value(source, PCMK_XA_EPOCH), crm_element_value(source, PCMK_XA_NUM_UPDATES), trace_file); save_xml_to_file(source, "digest input", trace_file); free(trace_file); }, {} ); g_string_free(buf, TRUE); return digest; } /*! * \internal * \brief Calculate and return the digest of a CIB, suitable for storing on disk * * \param[in] input Root of XML to digest * * \return Newly allocated string containing digest */ char * pcmk__digest_on_disk_cib(xmlNode *input) { /* Always use the v1 format for on-disk digests. * * Switching to v2 is a compatibility nightmare. * * We only use this once at startup. All other invocations are in a * separate child process. */ return calculate_xml_digest_v1(input, false); } /*! * \internal * \brief Calculate and return digest of an operation XML element * * \param[in] input Root of XML to digest * * \return Newly allocated string containing digest */ char * pcmk__digest_operation(xmlNode *input) { return calculate_xml_digest_v1(input, true); } +/*! + * \internal + * \brief Calculate and return the digest of an XML tree + * + * \param[in] input Root of XML to digest + * \param[in] filter Whether to filter certain XML attributes (ignored if + * version is less than or equal to "3.0.5") + * \param[in] version CRM feature set version (used to select v1/v2 digest) + * + * \return Newly allocated string containing digest + */ +char * +pcmk__digest_xml(xmlNode *input, bool filter, const char *version) +{ + /* @COMPAT Digests (on-disk or in diffs/patchsets) created <1.1.4 (commit + * 3032878) were always v1. Removing this affects even full-restart upgrades + * from old versions. + * + * The sorting associated with v1 digest creation accounted for 23% of + * the CIB manager's CPU usage on the server. v2 drops this. + * + * The filtering accounts for an additional 2.5% and we may want to + * remove it in future. + */ + if ((version == NULL) || (compare_version("3.0.5", version) > 0)) { + crm_trace("Using v1 digest algorithm for %s", + pcmk__s(version, "unknown feature set")); + return calculate_xml_digest_v1(input, false); + } + crm_trace("Using v2 digest algorithm for %s", version); + return calculate_xml_digest_v2(input, filter); +} + /*! * \brief Calculate and return digest of XML tree * * \param[in] input Root of XML to digest * \param[in] sort Whether to sort XML before calculating digest * \param[in] do_filter Whether to filter certain XML attributes * \param[in] version CRM feature set version (used to select v1/v2 digest) * * \return Newly allocated string containing digest */ char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version) { /* * @COMPAT digests (on-disk or in diffs/patchsets) created <1.1.4; * removing this affects even full-restart upgrades from old versions * * The sorting associated with v1 digest creation accounted for 23% of * the CIB manager's CPU usage on the server. v2 drops this. * * The filtering accounts for an additional 2.5% and we may want to * remove it in future. * * v2 also uses the xmlBuffer contents directly to avoid additional copying */ if ((version == NULL) || (compare_version("3.0.5", version) > 0)) { crm_trace("Using v1 digest algorithm for %s", pcmk__s(version, "unknown feature set")); return calculate_xml_digest_v1(input, sort); } crm_trace("Using v2 digest algorithm for %s", version); return calculate_xml_digest_v2(input, do_filter); } /*! * \internal * \brief Check whether calculated digest of given XML matches expected digest * * \param[in] input Root of XML tree to digest * \param[in] expected Expected digest in on-disk format * * \return true if digests match, false on mismatch or error */ bool pcmk__verify_digest(xmlNode *input, const char *expected) { char *calculated = NULL; bool passed; if (input != NULL) { calculated = pcmk__digest_on_disk_cib(input); if (calculated == NULL) { crm_perror(LOG_ERR, "Could not calculate digest for comparison"); return false; } } passed = pcmk__str_eq(expected, calculated, pcmk__str_casei); if (passed) { crm_trace("Digest comparison passed: %s", calculated); } else { crm_err("Digest comparison failed: expected %s, calculated %s", expected, calculated); } free(calculated); return passed; } /*! * \internal * \brief Check whether an XML attribute should be excluded from CIB digests * * \param[in] name XML attribute name * * \return true if XML attribute should be excluded from CIB digest calculation */ bool pcmk__xa_filterable(const char *name) { static const char *filter[] = { PCMK_XA_CRM_DEBUG_ORIGIN, PCMK_XA_CIB_LAST_WRITTEN, PCMK_XA_UPDATE_ORIGIN, PCMK_XA_UPDATE_CLIENT, PCMK_XA_UPDATE_USER, }; for (int i = 0; i < PCMK__NELEM(filter); i++) { if (strcmp(name, filter[i]) == 0) { return true; } } return false; } char * crm_md5sum(const char *buffer) { unsigned int dlen = gnutls_hash_get_len(GNUTLS_DIG_MD5); unsigned char *raw_digest = NULL; char *digest = NULL; int rc = 0; if (dlen == 0) { return NULL; } if (buffer == NULL) { return NULL; } raw_digest = pcmk__assert_alloc(dlen, sizeof(unsigned char)); rc = gnutls_hash_fast(GNUTLS_DIG_MD5, buffer, strlen(buffer), raw_digest); if (rc < 0) { free(raw_digest); crm_err("Failed to calculate hash: %s", gnutls_strerror(rc)); return NULL; } digest = pcmk__assert_alloc(1 + (2 * dlen), sizeof(char)); for (int i = 0; i < dlen; i++) { sprintf(digest + (2 * i), "%02x", raw_digest[i]); } digest[(2 * dlen)] = 0; free(raw_digest); crm_trace("Digest %s.", digest); return digest; } // Return true if a is an attribute that should be filtered static bool should_filter_for_digest(xmlAttrPtr a, void *user_data) { if (strncmp((const char *) a->name, CRM_META "_", sizeof(CRM_META " ") - 1) == 0) { return true; } return pcmk__str_any_of((const char *) a->name, PCMK_XA_ID, PCMK_XA_CRM_FEATURE_SET, PCMK__XA_OP_DIGEST, PCMK__META_ON_NODE, PCMK__META_ON_NODE_UUID, "pcmk_external_ip", NULL); } /*! * \internal * \brief Remove XML attributes not needed for operation digest * * \param[in,out] param_set XML with operation parameters */ void pcmk__filter_op_for_digest(xmlNode *param_set) { char *key = NULL; char *timeout = NULL; guint interval_ms = 0; if (param_set == NULL) { return; } /* Timeout is useful for recurring operation digests, so grab it before * removing meta-attributes */ key = crm_meta_name(PCMK_META_INTERVAL); if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) { interval_ms = 0; } free(key); key = NULL; if (interval_ms != 0) { key = crm_meta_name(PCMK_META_TIMEOUT); timeout = crm_element_value_copy(param_set, key); } // Remove all CRM_meta_* attributes and certain other attributes pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL); // Add timeout back for recurring operation digests if (timeout != NULL) { crm_xml_add(param_set, key, timeout); } free(timeout); free(key); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include char * calculate_on_disk_digest(xmlNode *input) { return calculate_xml_digest_v1(input, false); } char * calculate_operation_digest(xmlNode *input, const char *version) { return calculate_xml_digest_v1(input, true); } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 9c155674d2..78b71becdc 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -1,1473 +1,1473 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU 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 // CRM_XML_LOG_BASE, etc. #include "crmcommon_private.h" /* Add changes for specified XML to patchset. * For patchset format, refer to diff schema. */ static void add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) { xmlNode *cIter = NULL; xmlAttr *pIter = NULL; xmlNode *change = NULL; xml_node_private_t *nodepriv = xml->_private; const char *value = NULL; if (nodepriv == NULL) { /* Elements that shouldn't occur in a CIB don't have _private set. They * should be stripped out, ignored, or have an error thrown by any code * that processes their parent, so we ignore any changes to them. */ return; } // If this XML node is new, just report that if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(xml->parent); if (xpath != NULL) { int position = pcmk__xml_position(xml, pcmk__xf_deleted); change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); crm_xml_add_int(change, PCMK_XE_POSITION, position); pcmk__xml_copy(change, xml); g_string_free(xpath, TRUE); } return; } // Check each of the XML node's attributes for changes for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; pIter = pIter->next) { xmlNode *attr = NULL; nodepriv = pIter->_private; if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) { continue; } if (change == NULL) { GString *xpath = pcmk__element_xpath(xml); if (xpath != NULL) { change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST); g_string_free(xpath, TRUE); } } attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR); crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name); if (nodepriv->flags & pcmk__xf_deleted) { crm_xml_add(attr, PCMK_XA_OPERATION, "unset"); } else { crm_xml_add(attr, PCMK_XA_OPERATION, "set"); value = pcmk__xml_attr_value(pIter); crm_xml_add(attr, PCMK_XA_VALUE, value); } } if (change) { xmlNode *result = NULL; change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT); result = pcmk__xe_create(change, (const char *)xml->name); for (pIter = pcmk__xe_first_attr(xml); pIter != NULL; pIter = pIter->next) { nodepriv = pIter->_private; if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { value = crm_element_value(xml, (const char *) pIter->name); crm_xml_add(result, (const char *)pIter->name, value); } } } // Now recursively do the same for each child node of this node for (cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { add_xml_changes_to_patchset(cIter, patchset); } nodepriv = xml->_private; if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) { GString *xpath = pcmk__element_xpath(xml); crm_trace("%s.%s moved to position %d", xml->name, pcmk__xe_id(xml), pcmk__xml_position(xml, pcmk__xf_skip)); if (xpath != NULL) { change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE); crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str); crm_xml_add_int(change, PCMK_XE_POSITION, pcmk__xml_position(xml, pcmk__xf_deleted)); g_string_free(xpath, TRUE); } } } static bool is_config_change(xmlNode *xml) { GList *gIter = NULL; xml_node_private_t *nodepriv = NULL; xml_doc_private_t *docpriv; xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL, NULL); if (config) { nodepriv = config->_private; } if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) { return TRUE; } if ((xml->doc != NULL) && (xml->doc->_private != NULL)) { docpriv = xml->doc->_private; for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { pcmk__deleted_xml_t *deleted_obj = gIter->data; if (strstr(deleted_obj->path, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) { return TRUE; } } } return FALSE; } // @COMPAT Remove when v1 patchsets are removed static void xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff, gboolean changed) { int lpc = 0; xmlNode *cib = NULL; xmlNode *diff_child = NULL; const char *tag = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; if (local_diff == NULL) { crm_trace("Nothing to do"); return; } tag = PCMK__XE_DIFF_REMOVED; diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL); if (diff_child == NULL) { diff_child = pcmk__xe_create(local_diff, tag); } tag = PCMK_XE_CIB; cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL); if (cib == NULL) { cib = pcmk__xe_create(diff_child, tag); } for (lpc = 0; (last != NULL) && (lpc < PCMK__NELEM(vfields)); lpc++) { const char *value = crm_element_value(last, vfields[lpc]); crm_xml_add(diff_child, vfields[lpc], value); if (changed || lpc == 2) { crm_xml_add(cib, vfields[lpc], value); } } tag = PCMK__XE_DIFF_ADDED; diff_child = pcmk__xe_first_child(local_diff, tag, NULL, NULL); if (diff_child == NULL) { diff_child = pcmk__xe_create(local_diff, tag); } tag = PCMK_XE_CIB; cib = pcmk__xe_first_child(diff_child, tag, NULL, NULL); if (cib == NULL) { cib = pcmk__xe_create(diff_child, tag); } for (lpc = 0; next && lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(next, vfields[lpc]); crm_xml_add(diff_child, vfields[lpc], value); } for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) { const char *p_value = pcmk__xml_attr_value(a); xmlSetProp(cib, a->name, (pcmkXmlStr) p_value); } crm_log_xml_explicit(local_diff, "Repaired-diff"); } // @COMPAT Remove when v1 patchsets are removed static xmlNode * xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress) { xmlNode *patchset = pcmk__diff_v1_xml_object(source, target, suppress); if (patchset) { CRM_LOG_ASSERT(xml_document_dirty(target)); xml_repair_v1_diff(source, target, patchset, config); crm_xml_add(patchset, PCMK_XA_FORMAT, "1"); } return patchset; } static xmlNode * xml_create_patchset_v2(xmlNode *source, xmlNode *target) { int lpc = 0; GList *gIter = NULL; xml_doc_private_t *docpriv; xmlNode *v = NULL; xmlNode *version = NULL; xmlNode *patchset = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; CRM_ASSERT(target); if (!xml_document_dirty(target)) { return NULL; } CRM_ASSERT(target->doc); docpriv = target->doc->_private; patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF); crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2); version = pcmk__xe_create(patchset, PCMK_XE_VERSION); v = pcmk__xe_create(version, PCMK_XE_SOURCE); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(source, vfields[lpc]); if (value == NULL) { value = "1"; } crm_xml_add(v, vfields[lpc], value); } v = pcmk__xe_create(version, PCMK_XE_TARGET); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { const char *value = crm_element_value(target, vfields[lpc]); if (value == NULL) { value = "1"; } crm_xml_add(v, vfields[lpc], value); } for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) { pcmk__deleted_xml_t *deleted_obj = gIter->data; xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE); crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE); crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path); if (deleted_obj->position >= 0) { crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position); } } add_xml_changes_to_patchset(target, patchset); return patchset; } xmlNode * xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version) { int counter = 0; bool config = FALSE; xmlNode *patch = NULL; const char *version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET); xml_acl_disable(target); if (!xml_document_dirty(target)) { crm_trace("No change %d", format); return NULL; /* No change */ } config = is_config_change(target); if (config_changed) { *config_changed = config; } if (manage_version && config) { crm_trace("Config changed %d", format); crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0"); crm_element_value_int(target, PCMK_XA_EPOCH, &counter); crm_xml_add_int(target, PCMK_XA_EPOCH, counter+1); } else if (manage_version) { crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter); crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, PCMK_XA_NUM_UPDATES)); crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, (counter + 1)); } if (format == 0) { if (compare_version("3.0.8", version) < 0) { format = 2; } else { format = 1; } crm_trace("Using patch format %d for version: %s", format, version); } switch (format) { case 1: // @COMPAT Remove when v1 patchsets are removed patch = xml_create_patchset_v1(source, target, config, FALSE); break; case 2: patch = xml_create_patchset_v2(source, target); break; default: crm_err("Unknown patch format: %d", format); return NULL; } return patch; } void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest) { int format = 1; const char *version = NULL; char *digest = NULL; if ((patch == NULL) || (source == NULL) || (target == NULL)) { return; } /* We should always call xml_accept_changes() before calculating a digest. * Otherwise, with an on-tracking dirty target, we could get a wrong digest. */ CRM_LOG_ASSERT(!xml_document_dirty(target)); crm_element_value_int(patch, PCMK_XA_FORMAT, &format); if ((format > 1) && !with_digest) { return; } version = crm_element_value(source, PCMK_XA_CRM_FEATURE_SET); - digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version); + digest = pcmk__digest_xml(target, true, version); crm_xml_add(patch, PCMK__XA_DIGEST, digest); free(digest); return; } // @COMPAT Remove when v1 patchsets are removed static xmlNode * subtract_v1_xml_comment(xmlNode *parent, xmlNode *left, xmlNode *right, gboolean *changed) { CRM_CHECK(left != NULL, return NULL); CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL); if ((right == NULL) || !pcmk__str_eq((const char *)left->content, (const char *)right->content, pcmk__str_casei)) { xmlNode *deleted = NULL; deleted = pcmk__xml_copy(parent, left); *changed = TRUE; return deleted; } return NULL; } // @COMPAT Remove when v1 patchsets are removed static xmlNode * subtract_v1_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, bool full, gboolean *changed, const char *marker) { gboolean dummy = FALSE; xmlNode *diff = NULL; xmlNode *right_child = NULL; xmlNode *left_child = NULL; xmlAttrPtr xIter = NULL; const char *id = NULL; const char *name = NULL; const char *value = NULL; const char *right_val = NULL; if (changed == NULL) { changed = &dummy; } if (left == NULL) { return NULL; } if (left->type == XML_COMMENT_NODE) { return subtract_v1_xml_comment(parent, left, right, changed); } id = pcmk__xe_id(left); name = (const char *) left->name; if (right == NULL) { xmlNode *deleted = NULL; crm_trace("Processing <%s " PCMK_XA_ID "=%s> (complete copy)", name, id); deleted = pcmk__xml_copy(parent, left); crm_xml_add(deleted, PCMK__XA_CRM_DIFF_MARKER, marker); *changed = TRUE; return deleted; } CRM_CHECK(name != NULL, return NULL); CRM_CHECK(pcmk__xe_is(left, (const char *) right->name), return NULL); // Check for PCMK__XA_CRM_DIFF_MARKER in a child value = crm_element_value(right, PCMK__XA_CRM_DIFF_MARKER); if ((value != NULL) && (strcmp(value, "removed:top") == 0)) { crm_trace("We are the root of the deletion: %s.id=%s", name, id); *changed = TRUE; return NULL; } // @TODO Avoiding creating the full hierarchy would save work here diff = pcmk__xe_create(parent, name); // Changes to child objects for (left_child = pcmk__xml_first_child(left); left_child != NULL; left_child = pcmk__xml_next(left_child)) { gboolean child_changed = FALSE; right_child = pcmk__xml_match(right, left_child, false); subtract_v1_xml_object(diff, left_child, right_child, full, &child_changed, marker); if (child_changed) { *changed = TRUE; } } if (!*changed) { /* Nothing to do */ } else if (full) { xmlAttrPtr pIter = NULL; for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *)pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } // We have everything we need goto done; } // Changes to name/value pairs for (xIter = pcmk__xe_first_attr(left); xIter != NULL; xIter = xIter->next) { const char *prop_name = (const char *) xIter->name; xmlAttrPtr right_attr = NULL; xml_node_private_t *nodepriv = NULL; if (strcmp(prop_name, PCMK_XA_ID) == 0) { // id already obtained when present ~ this case, so just reuse xmlSetProp(diff, (pcmkXmlStr) PCMK_XA_ID, (pcmkXmlStr) id); continue; } if (pcmk__xa_filterable(prop_name)) { continue; } right_attr = xmlHasProp(right, (pcmkXmlStr) prop_name); if (right_attr) { nodepriv = right_attr->_private; } right_val = crm_element_value(right, prop_name); if ((right_val == NULL) || (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted))) { /* new */ *changed = TRUE; if (full) { xmlAttrPtr pIter = NULL; for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *) pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } break; } else { const char *left_value = pcmk__xml_attr_value(xIter); xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value); crm_xml_add(diff, prop_name, left_value); } } else { /* Only now do we need the left value */ const char *left_value = pcmk__xml_attr_value(xIter); if (strcmp(left_value, right_val) == 0) { /* unchanged */ } else { *changed = TRUE; if (full) { xmlAttrPtr pIter = NULL; crm_trace("Changes detected to %s in " "<%s " PCMK_XA_ID "=%s>", prop_name, name, id); for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *) pIter->name; const char *p_value = pcmk__xml_attr_value(pIter); xmlSetProp(diff, (pcmkXmlStr) p_name, (pcmkXmlStr) p_value); } break; } else { crm_trace("Changes detected to %s (%s -> %s) in " "<%s " PCMK_XA_ID "=%s>", prop_name, left_value, right_val, name, id); crm_xml_add(diff, prop_name, left_value); } } } } if (!*changed) { pcmk__xml_free(diff); return NULL; } else if (!full && (id != NULL)) { crm_xml_add(diff, PCMK_XA_ID, id); } done: return diff; } /* @COMPAT Remove when v1 patchsets are removed. * * Return true if attribute name is not \c PCMK_XML_ID. */ static bool not_id(xmlAttrPtr attr, void *user_data) { return strcmp((const char *) attr->name, PCMK_XA_ID) != 0; } /* @COMPAT Remove when v1 patchsets are removed. * * Apply the removals section of a v1 patchset to an XML node. */ static void process_v1_removals(xmlNode *target, xmlNode *patch) { xmlNode *patch_child = NULL; xmlNode *cIter = NULL; char *id = NULL; const char *value = NULL; if ((target == NULL) || (patch == NULL)) { return; } if (target->type == XML_COMMENT_NODE) { gboolean dummy; subtract_v1_xml_comment(target->parent, target, patch, &dummy); } CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return); CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch), pcmk__str_none), return); // Check for PCMK__XA_CRM_DIFF_MARKER in a child id = crm_element_value_copy(target, PCMK_XA_ID); value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER); if ((value != NULL) && (strcmp(value, "removed:top") == 0)) { crm_trace("We are the root of the deletion: %s.id=%s", target->name, id); pcmk__xml_free(target); free(id); return; } // Removing then restoring id would change ordering of properties pcmk__xe_remove_matching_attrs(patch, not_id, NULL); // Changes to child objects cIter = pcmk__xml_first_child(target); while (cIter) { xmlNode *target_child = cIter; cIter = pcmk__xml_next(cIter); patch_child = pcmk__xml_match(patch, target_child, false); process_v1_removals(target_child, patch_child); } free(id); } /* @COMPAT Remove when v1 patchsets are removed. * * Apply the additions section of a v1 patchset to an XML node. */ static void process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch) { xmlNode *patch_child = NULL; xmlNode *target_child = NULL; xmlAttrPtr xIter = NULL; const char *id = NULL; const char *name = NULL; const char *value = NULL; if (patch == NULL) { return; } else if ((parent == NULL) && (target == NULL)) { return; } // Check for PCMK__XA_CRM_DIFF_MARKER in a child name = (const char *) patch->name; value = crm_element_value(patch, PCMK__XA_CRM_DIFF_MARKER); if ((target == NULL) && (value != NULL) && (strcmp(value, "added:top") == 0)) { id = pcmk__xe_id(patch); crm_trace("We are the root of the addition: %s.id=%s", name, id); pcmk__xml_copy(parent, patch); return; } else if (target == NULL) { id = pcmk__xe_id(patch); crm_err("Could not locate: %s.id=%s", name, id); return; } if (target->type == XML_COMMENT_NODE) { pcmk__xc_update(parent, target, patch); } CRM_CHECK(pcmk__xe_is(target, name), return); CRM_CHECK(pcmk__str_eq(pcmk__xe_id(target), pcmk__xe_id(patch), pcmk__str_none), return); for (xIter = pcmk__xe_first_attr(patch); xIter != NULL; xIter = xIter->next) { const char *p_name = (const char *) xIter->name; const char *p_value = pcmk__xml_attr_value(xIter); pcmk__xe_remove_attr(target, p_name); // Preserve patch order crm_xml_add(target, p_name, p_value); } // Changes to child objects for (patch_child = pcmk__xml_first_child(patch); patch_child != NULL; patch_child = pcmk__xml_next(patch_child)) { target_child = pcmk__xml_match(target, patch_child, false); process_v1_additions(target, target_child, patch_child); } } /*! * \internal * \brief Find additions or removals in a patch set * * \param[in] patchset XML of patch * \param[in] format Patch version * \param[in] added TRUE if looking for additions, FALSE if removals * \param[in,out] patch_node Will be set to node if found * * \return TRUE if format is valid, FALSE if invalid */ static bool find_patch_xml_node(const xmlNode *patchset, int format, bool added, xmlNode **patch_node) { xmlNode *cib_node; const char *label; switch (format) { case 1: // @COMPAT Remove when v1 patchsets are removed label = added? PCMK__XE_DIFF_ADDED : PCMK__XE_DIFF_REMOVED; *patch_node = pcmk__xe_first_child(patchset, label, NULL, NULL); cib_node = pcmk__xe_first_child(*patch_node, PCMK_XE_CIB, NULL, NULL); if (cib_node != NULL) { *patch_node = cib_node; } break; case 2: label = added? PCMK_XE_TARGET : PCMK_XE_SOURCE; *patch_node = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL, NULL); *patch_node = pcmk__xe_first_child(*patch_node, label, NULL, NULL); break; default: crm_warn("Unknown patch format: %d", format); *patch_node = NULL; return FALSE; } return TRUE; } // Get CIB versions used for additions and deletions in a patchset bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]) { int lpc = 0; int format = 1; xmlNode *tmp = NULL; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); /* Process removals */ if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) { return -EINVAL; } if (tmp != NULL) { for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(tmp, vfields[lpc], &(del[lpc])); crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]); } } /* Process additions */ if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) { return -EINVAL; } if (tmp != NULL) { for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(tmp, vfields[lpc], &(add[lpc])); crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]); } } return pcmk_ok; } /*! * \internal * \brief Check whether patchset can be applied to current CIB * * \param[in] xml Root of current CIB * \param[in] patchset Patchset to check * * \return Standard Pacemaker return code */ static int xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset) { int lpc = 0; bool changed = FALSE; int this[] = { 0, 0, 0 }; int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { crm_element_value_int(xml, vfields[lpc], &(this[lpc])); crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]); if (this[lpc] < 0) { this[lpc] = 0; } } /* Set some defaults in case nothing is present */ add[0] = this[0]; add[1] = this[1]; add[2] = this[2] + 1; for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { del[lpc] = this[lpc]; } xml_patch_versions(patchset, add, del); for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { if (this[lpc] < del[lpc]) { crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc], this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]); return pcmk_rc_diff_resync; } else if (this[lpc] > del[lpc]) { crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc], this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset); crm_log_xml_info(patchset, "OldPatch"); return pcmk_rc_old_data; } } for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) { if (add[lpc] > del[lpc]) { changed = TRUE; } } if (!changed) { crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]); return pcmk_rc_old_data; } crm_debug("Can apply patch %d.%d.%d to %d.%d.%d", add[0], add[1], add[2], this[0], this[1], this[2]); return pcmk_rc_ok; } // @COMPAT Remove when v1 patchsets are removed static void purge_v1_diff_markers(xmlNode *node) { xmlNode *child = NULL; CRM_CHECK(node != NULL, return); pcmk__xe_remove_attr(node, PCMK__XA_CRM_DIFF_MARKER); for (child = pcmk__xml_first_child(node); child != NULL; child = pcmk__xml_next(child)) { purge_v1_diff_markers(child); } } // @COMPAT Remove when v1 patchsets are removed /*! * \internal * \brief Apply a version 1 patchset to an XML node * * \param[in,out] xml XML to apply patchset to * \param[in] patchset Patchset to apply * * \return Standard Pacemaker return code */ static int apply_v1_patchset(xmlNode *xml, const xmlNode *patchset) { int rc = pcmk_rc_ok; int root_nodes_seen = 0; xmlNode *child_diff = NULL; xmlNode *added = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_ADDED, NULL, NULL); xmlNode *removed = pcmk__xe_first_child(patchset, PCMK__XE_DIFF_REMOVED, NULL, NULL); xmlNode *old = pcmk__xml_copy(NULL, xml); crm_trace("Subtraction Phase"); for (child_diff = pcmk__xml_first_child(removed); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, rc = FALSE); if (root_nodes_seen == 0) { process_v1_removals(xml, child_diff); } root_nodes_seen++; } if (root_nodes_seen > 1) { crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); rc = ENOTUNIQ; } root_nodes_seen = 0; crm_trace("Addition Phase"); if (rc == pcmk_rc_ok) { xmlNode *child_diff = NULL; for (child_diff = pcmk__xml_first_child(added); child_diff != NULL; child_diff = pcmk__xml_next(child_diff)) { CRM_CHECK(root_nodes_seen == 0, rc = FALSE); if (root_nodes_seen == 0) { process_v1_additions(NULL, xml, child_diff); } root_nodes_seen++; } } if (root_nodes_seen > 1) { crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen); rc = ENOTUNIQ; } purge_v1_diff_markers(xml); // Purge prior to checking digest pcmk__xml_free(old); return rc; } // Return first child matching element name and optionally id or position static xmlNode * first_matching_xml_child(const xmlNode *parent, const char *name, const char *id, int position) { xmlNode *cIter = NULL; for (cIter = pcmk__xml_first_child(parent); cIter != NULL; cIter = pcmk__xml_next(cIter)) { if (strcmp((const char *) cIter->name, name) != 0) { continue; } else if (id) { const char *cid = pcmk__xe_id(cIter); if ((cid == NULL) || (strcmp(cid, id) != 0)) { continue; } } // "position" makes sense only for XML comments for now if ((cIter->type == XML_COMMENT_NODE) && (position >= 0) && (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) { continue; } return cIter; } return NULL; } /*! * \internal * \brief Simplified, more efficient alternative to get_xpath_object() * * \param[in] top Root of XML to search * \param[in] key Search xpath * \param[in] target_position If deleting, where to delete * * \return XML child matching xpath if found, NULL otherwise * * \note This only works on simplified xpaths found in v2 patchset diffs, * i.e. the only allowed search predicate is [@id='XXX']. */ static xmlNode * search_v2_xpath(const xmlNode *top, const char *key, int target_position) { xmlNode *target = (xmlNode *) top->doc; const char *current = key; char *section; char *remainder; char *id; char *tag; char *path = NULL; int rc; size_t key_len; CRM_CHECK(key != NULL, return NULL); key_len = strlen(key); /* These are scanned from key after a slash, so they can't be bigger * than key_len - 1 characters plus a null terminator. */ remainder = pcmk__assert_alloc(key_len, sizeof(char)); section = pcmk__assert_alloc(key_len, sizeof(char)); id = pcmk__assert_alloc(key_len, sizeof(char)); tag = pcmk__assert_alloc(key_len, sizeof(char)); do { // Look for /NEXT_COMPONENT/REMAINING_COMPONENTS rc = sscanf(current, "/%[^/]%s", section, remainder); if (rc > 0) { // Separate FIRST_COMPONENT into TAG[@id='ID'] int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id); int current_position = -1; /* The target position is for the final component tag, so only use * it if there is nothing left to search after this component. */ if ((rc == 1) && (target_position >= 0)) { current_position = target_position; } switch (f) { case 1: // @COMPAT Remove when v1 patchsets are removed target = first_matching_xml_child(target, tag, NULL, current_position); break; case 2: target = first_matching_xml_child(target, tag, id, current_position); break; default: // This should not be possible target = NULL; break; } current = remainder; } // Continue if something remains to search, and we've matched so far } while ((rc == 2) && target); if (target) { crm_trace("Found %s for %s", (path = (char *) xmlGetNodePath(target)), key); free(path); } else { crm_debug("No match for %s", key); } free(remainder); free(section); free(tag); free(id); return target; } typedef struct xml_change_obj_s { const xmlNode *change; xmlNode *match; } xml_change_obj_t; static gint sort_change_obj_by_position(gconstpointer a, gconstpointer b) { const xml_change_obj_t *change_obj_a = a; const xml_change_obj_t *change_obj_b = b; int position_a = -1; int position_b = -1; crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a); crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b); if (position_a < position_b) { return -1; } else if (position_a > position_b) { return 1; } return 0; } /*! * \internal * \brief Apply a version 2 patchset to an XML node * * \param[in,out] xml XML to apply patchset to * \param[in] patchset Patchset to apply * * \return Standard Pacemaker return code */ static int apply_v2_patchset(xmlNode *xml, const xmlNode *patchset) { int rc = pcmk_rc_ok; const xmlNode *change = NULL; GList *change_objs = NULL; GList *gIter = NULL; for (change = pcmk__xml_first_child(patchset); change != NULL; change = pcmk__xml_next(change)) { xmlNode *match = NULL; const char *op = crm_element_value(change, PCMK_XA_OPERATION); const char *xpath = crm_element_value(change, PCMK_XA_PATH); int position = -1; if (op == NULL) { continue; } crm_trace("Processing %s %s", change->name, op); /* PCMK_VALUE_DELETE changes for XML comments are generated with * PCMK_XE_POSITION */ if (strcmp(op, PCMK_VALUE_DELETE) == 0) { crm_element_value_int(change, PCMK_XE_POSITION, &position); } match = search_v2_xpath(xml, xpath, position); crm_trace("Performing %s on %s with %p", op, xpath, match); if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) { crm_debug("No %s match for %s in %p", op, xpath, xml->doc); continue; } else if (match == NULL) { crm_err("No %s match for %s in %p", op, xpath, xml->doc); rc = pcmk_rc_diff_failed; continue; } else if (pcmk__str_any_of(op, PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) { // Delay the adding of a PCMK_VALUE_CREATE object xml_change_obj_t *change_obj = pcmk__assert_alloc(1, sizeof(xml_change_obj_t)); change_obj->change = change; change_obj->match = match; change_objs = g_list_append(change_objs, change_obj); if (strcmp(op, PCMK_VALUE_MOVE) == 0) { // Temporarily put the PCMK_VALUE_MOVE object after the last sibling if ((match->parent != NULL) && (match->parent->last != NULL)) { xmlAddNextSibling(match->parent->last, match); } } } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) { pcmk__xml_free(match); } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) { const xmlNode *child = pcmk__xe_first_child(change, PCMK_XE_CHANGE_RESULT, NULL, NULL); const xmlNode *attrs = pcmk__xml_first_child(child); if (attrs == NULL) { rc = ENOMSG; continue; } pcmk__xe_remove_matching_attrs(match, NULL, NULL); // Remove all for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL; pIter = pIter->next) { const char *name = (const char *) pIter->name; const char *value = pcmk__xml_attr_value(pIter); crm_xml_add(match, name, value); } } else { crm_err("Unknown operation: %s", op); rc = pcmk_rc_diff_failed; } } // Changes should be generated in the right order. Double checking. change_objs = g_list_sort(change_objs, sort_change_obj_by_position); for (gIter = change_objs; gIter; gIter = gIter->next) { xml_change_obj_t *change_obj = gIter->data; xmlNode *match = change_obj->match; const char *op = NULL; const char *xpath = NULL; change = change_obj->change; op = crm_element_value(change, PCMK_XA_OPERATION); xpath = crm_element_value(change, PCMK_XA_PATH); crm_trace("Continue performing %s on %s with %p", op, xpath, match); if (strcmp(op, PCMK_VALUE_CREATE) == 0) { int position = 0; xmlNode *child = NULL; xmlNode *match_child = NULL; match_child = match->children; crm_element_value_int(change, PCMK_XE_POSITION, &position); while ((match_child != NULL) && (position != pcmk__xml_position(match_child, pcmk__xf_skip))) { match_child = match_child->next; } child = xmlDocCopyNode(change->children, match->doc, 1); if (child == NULL) { return ENOMEM; } if (match_child) { crm_trace("Adding %s at position %d", child->name, position); xmlAddPrevSibling(match_child, child); } else if (match->last) { crm_trace("Adding %s at position %d (end)", child->name, position); xmlAddNextSibling(match->last, child); } else { crm_trace("Adding %s at position %d (first)", child->name, position); CRM_LOG_ASSERT(position == 0); xmlAddChild(match, child); } pcmk__xml_mark_created(child); } else if (strcmp(op, PCMK_VALUE_MOVE) == 0) { int position = 0; crm_element_value_int(change, PCMK_XE_POSITION, &position); if (position != pcmk__xml_position(match, pcmk__xf_skip)) { xmlNode *match_child = NULL; int p = position; if (p > pcmk__xml_position(match, pcmk__xf_skip)) { p++; // Skip ourselves } CRM_ASSERT(match->parent != NULL); match_child = match->parent->children; while ((match_child != NULL) && (p != pcmk__xml_position(match_child, pcmk__xf_skip))) { match_child = match_child->next; } crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)", match->name, position, pcmk__xml_position(match, pcmk__xf_skip), match->prev, (match_child? "next":"last"), (match_child? match_child : match->parent->last)); if (match_child) { xmlAddPrevSibling(match_child, match); } else { CRM_ASSERT(match->parent->last != NULL); xmlAddNextSibling(match->parent->last, match); } } else { crm_trace("%s is already in position %d", match->name, position); } if (position != pcmk__xml_position(match, pcmk__xf_skip)) { crm_err("Moved %s.%s to position %d instead of %d (%p)", match->name, pcmk__xe_id(match), pcmk__xml_position(match, pcmk__xf_skip), position, match->prev); rc = pcmk_rc_diff_failed; } } } g_list_free_full(change_objs, free); return rc; } int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version) { int format = 1; int rc = pcmk_ok; xmlNode *old = NULL; const char *digest = NULL; if (patchset == NULL) { return rc; } pcmk__log_xml_patchset(LOG_TRACE, patchset); if (check_version) { rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset)); if (rc != pcmk_ok) { return rc; } } digest = crm_element_value(patchset, PCMK__XA_DIGEST); if (digest != NULL) { /* Make original XML available for logging in case result doesn't have * expected digest */ pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {}); } if (rc == pcmk_ok) { crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: // @COMPAT Remove when v1 patchsets are removed rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset)); break; case 2: rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset)); break; default: crm_err("Unknown patch format: %d", format); rc = -EINVAL; } } if ((rc == pcmk_ok) && (digest != NULL)) { char *new_digest = NULL; char *version = crm_element_value_copy(xml, PCMK_XA_CRM_FEATURE_SET); - new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version); + new_digest = pcmk__digest_xml(xml, true, version); if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) { crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest); rc = -pcmk_err_diff_failed; pcmk__if_tracing( { save_xml_to_file(old, "PatchDigest:input", NULL); save_xml_to_file(xml, "PatchDigest:result", NULL); save_xml_to_file(patchset, "PatchDigest:diff", NULL); }, {} ); } else { crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest); } free(new_digest); free(version); } pcmk__xml_free(old); return rc; } // @COMPAT Remove when v1 patchsets are removed static bool can_prune_leaf_v1(xmlNode *node) { xmlNode *cIter = NULL; bool can_prune = true; CRM_CHECK(node != NULL, return false); /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed for * rolling upgrades) */ if (pcmk__strcase_any_of((const char *) node->name, PCMK_XE_RESOURCE_REF, PCMK_XE_OBJ_REF, PCMK_XE_ROLE, PCMK__XE_ROLE_REF, NULL)) { return false; } for (xmlAttrPtr a = pcmk__xe_first_attr(node); a != NULL; a = a->next) { const char *p_name = (const char *) a->name; if (strcmp(p_name, PCMK_XA_ID) == 0) { continue; } can_prune = false; } cIter = pcmk__xml_first_child(node); while (cIter) { xmlNode *child = cIter; cIter = pcmk__xml_next(cIter); if (can_prune_leaf_v1(child)) { pcmk__xml_free(child); } else { can_prune = false; } } return can_prune; } // @COMPAT Remove when v1 patchsets are removed xmlNode * pcmk__diff_v1_xml_object(xmlNode *old, xmlNode *new, bool suppress) { xmlNode *tmp1 = NULL; xmlNode *diff = pcmk__xe_create(NULL, PCMK_XE_DIFF); xmlNode *removed = pcmk__xe_create(diff, PCMK__XE_DIFF_REMOVED); xmlNode *added = pcmk__xe_create(diff, PCMK__XE_DIFF_ADDED); crm_xml_add(diff, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); tmp1 = subtract_v1_xml_object(removed, old, new, false, NULL, "removed:top"); if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) { pcmk__xml_free(tmp1); } tmp1 = subtract_v1_xml_object(added, new, old, true, NULL, "added:top"); if (suppress && (tmp1 != NULL) && can_prune_leaf_v1(tmp1)) { pcmk__xml_free(tmp1); } if ((added->children == NULL) && (removed->children == NULL)) { pcmk__xml_free(diff); diff = NULL; } return diff; } diff --git a/tools/cibadmin.c b/tools/cibadmin.c index 2e9eb351ee..39230822b7 100644 --- a/tools/cibadmin.c +++ b/tools/cibadmin.c @@ -1,997 +1,997 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #define SUMMARY "query and edit the Pacemaker configuration" #define INDENT " " enum cibadmin_section_type { cibadmin_section_all = 0, cibadmin_section_scope, cibadmin_section_xpath, }; static int request_id = 0; static cib_t *the_cib = NULL; static GMainLoop *mainloop = NULL; static crm_exit_t exit_code = CRM_EX_OK; static struct { const char *cib_action; int cmd_options; enum cibadmin_section_type section_type; char *cib_section; char *validate_with; gint message_timeout_sec; enum pcmk__acl_render_how acl_render_mode; gchar *cib_user; gchar *dest_node; gchar *input_file; gchar *input_xml; gboolean input_stdin; bool delete_all; gboolean allow_create; gboolean force; gboolean get_node_path; gboolean local; gboolean no_children; gboolean score_update; gboolean sync_call; /* @COMPAT: For "-!" version option. Not advertised nor marked as * deprecated, but accepted. */ gboolean extended_version; //! \deprecated gboolean no_bcast; } options; int do_init(void); static int do_work(xmlNode *input, xmlNode **output); void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data); static void print_xml_output(xmlNode * xml) { if (!xml) { return; } else if (xml->type != XML_ELEMENT_NODE) { return; } if (pcmk_is_set(options.cmd_options, cib_xpath_address)) { const char *id = crm_element_value(xml, PCMK_XA_ID); if (pcmk__xe_is(xml, PCMK__XE_XPATH_QUERY)) { xmlNode *child = NULL; for (child = xml->children; child; child = child->next) { print_xml_output(child); } } else if (id) { printf("%s\n", id); } } else { GString *buf = g_string_sized_new(1024); pcmk__xml_string(xml, pcmk__xml_fmt_pretty, buf, 0); fprintf(stdout, "%s", buf->str); g_string_free(buf, TRUE); } } // Upgrade requested but already at latest schema static void report_schema_unchanged(void) { const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged); crm_info("Upgrade unnecessary: %s\n", err); printf("Upgrade unnecessary: %s\n", err); exit_code = CRM_EX_OK; } /*! * \internal * \brief Check whether the current CIB action is dangerous * \return true if \p options.cib_action is dangerous, or false otherwise */ static inline bool cib_action_is_dangerous(void) { return options.no_bcast || options.delete_all || pcmk__str_any_of(options.cib_action, PCMK__CIB_REQUEST_UPGRADE, PCMK__CIB_REQUEST_ERASE, NULL); } /*! * \internal * \brief Determine whether the given CIB scope is valid for \p cibadmin * * \param[in] scope Scope to validate * * \return true if \p scope is valid, or false otherwise * \note An invalid scope applies the operation to the entire CIB. */ static inline bool scope_is_valid(const char *scope) { return pcmk__str_any_of(scope, PCMK_XE_CONFIGURATION, PCMK_XE_NODES, PCMK_XE_RESOURCES, PCMK_XE_CONSTRAINTS, PCMK_XE_CRM_CONFIG, PCMK_XE_RSC_DEFAULTS, PCMK_XE_OP_DEFAULTS, PCMK_XE_ACLS, PCMK_XE_FENCING_TOPOLOGY, PCMK_XE_TAGS, PCMK_XE_ALERTS, PCMK_XE_STATUS, NULL); } static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.delete_all = false; if (pcmk__str_any_of(option_name, "-u", "--upgrade", NULL)) { options.cib_action = PCMK__CIB_REQUEST_UPGRADE; } else if (pcmk__str_any_of(option_name, "-Q", "--query", NULL)) { options.cib_action = PCMK__CIB_REQUEST_QUERY; } else if (pcmk__str_any_of(option_name, "-E", "--erase", NULL)) { options.cib_action = PCMK__CIB_REQUEST_ERASE; } else if (pcmk__str_any_of(option_name, "-B", "--bump", NULL)) { options.cib_action = PCMK__CIB_REQUEST_BUMP; } else if (pcmk__str_any_of(option_name, "-C", "--create", NULL)) { options.cib_action = PCMK__CIB_REQUEST_CREATE; } else if (pcmk__str_any_of(option_name, "-M", "--modify", NULL)) { options.cib_action = PCMK__CIB_REQUEST_MODIFY; } else if (pcmk__str_any_of(option_name, "-P", "--patch", NULL)) { options.cib_action = PCMK__CIB_REQUEST_APPLY_PATCH; } else if (pcmk__str_any_of(option_name, "-R", "--replace", NULL)) { options.cib_action = PCMK__CIB_REQUEST_REPLACE; } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) { options.cib_action = PCMK__CIB_REQUEST_DELETE; } else if (pcmk__str_any_of(option_name, "-d", "--delete-all", NULL)) { options.cib_action = PCMK__CIB_REQUEST_DELETE; options.delete_all = true; } else if (pcmk__str_any_of(option_name, "-a", "--empty", NULL)) { options.cib_action = "empty"; pcmk__str_update(&options.validate_with, optarg); } else if (pcmk__str_any_of(option_name, "-5", "--md5-sum", NULL)) { options.cib_action = "md5-sum"; } else if (pcmk__str_any_of(option_name, "-6", "--md5-sum-versioned", NULL)) { options.cib_action = "md5-sum-versioned"; } else { // Should be impossible return FALSE; } return TRUE; } static gboolean show_access_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_eq(optarg, "auto", pcmk__str_null_matches)) { options.acl_render_mode = pcmk__acl_render_default; } else if (g_strcmp0(optarg, "namespace") == 0) { options.acl_render_mode = pcmk__acl_render_namespace; } else if (g_strcmp0(optarg, "text") == 0) { options.acl_render_mode = pcmk__acl_render_text; } else if (g_strcmp0(optarg, "color") == 0) { options.acl_render_mode = pcmk__acl_render_color; } else { g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE, "Invalid value '%s' for option '%s'", optarg, option_name); return FALSE; } return TRUE; } static gboolean section_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-o", "--scope", NULL)) { options.section_type = cibadmin_section_scope; } else if (pcmk__str_any_of(option_name, "-A", "--xpath", NULL)) { options.section_type = cibadmin_section_xpath; } else { // Should be impossible return FALSE; } pcmk__str_update(&options.cib_section, optarg); return TRUE; } static GOptionEntry command_entries[] = { { "upgrade", 'u', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Upgrade the configuration to the latest syntax", NULL }, { "query", 'Q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Query the contents of the CIB", NULL }, { "erase", 'E', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Erase the contents of the whole CIB", NULL }, { "bump", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Increase the CIB's epoch value by 1", NULL }, { "create", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Create an object in the CIB (will fail if object already exists)", NULL }, { "modify", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Find object somewhere in CIB's XML tree and update it (fails if object " "does not exist unless -c is also specified)", NULL }, { "patch", 'P', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Supply an update in the form of an XML diff (see crm_diff(8))", NULL }, { "replace", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Recursively replace an object in the CIB", NULL }, { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Delete first object matching supplied criteria (for example, " "<" PCMK_XE_OP " " PCMK_XA_ID "=\"rsc1_op1\" " PCMK_XA_NAME "=\"monitor\"/>).\n" INDENT "The XML element name and all attributes must match in order for " "the element to be deleted.", NULL }, { "delete-all", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "When used with --xpath, remove all matching objects in the " "configuration instead of just the first one", NULL }, { "empty", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Output an empty CIB. Accepts an optional schema name argument to use as " "the " PCMK_XA_VALIDATE_WITH " value.\n" INDENT "If no schema is given, the latest will be used.", "[schema]" }, { "md5-sum", '5', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Calculate the on-disk CIB digest", NULL }, { "md5-sum-versioned", '6', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Calculate an on-the-wire versioned CIB digest", NULL }, { NULL } }; static GOptionEntry data_entries[] = { /* @COMPAT: These arguments should be last-wins. We can have an enum option * that stores the input type, along with a single string option that stores * the XML string for --xml-text, filename for --xml-file, or NULL for * --xml-pipe. */ { "xml-text", 'X', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.input_xml, "Retrieve XML from the supplied string", "value" }, { "xml-file", 'x', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &options.input_file, "Retrieve XML from the named file", "value" }, { "xml-pipe", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.input_stdin, "Retrieve XML from stdin", NULL }, { NULL } }; static GOptionEntry addl_entries[] = { { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force, "Force the action to be performed", NULL }, { "timeout", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &options.message_timeout_sec, "Time (in seconds) to wait before declaring the operation failed", "value" }, { "user", 'U', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.cib_user, "Run the command with permissions of the named user (valid only for the " "root and " CRM_DAEMON_USER " accounts)", "value" }, { "sync-call", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.sync_call, "Wait for call to complete before returning", NULL }, { "local", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.local, "Command takes effect locally (should be used only for queries)", NULL }, { "scope", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb, "Limit scope of operation to specific section of CIB\n" INDENT "Valid values: " PCMK_XE_CONFIGURATION ", " PCMK_XE_NODES ", " PCMK_XE_RESOURCES ", " PCMK_XE_CONSTRAINTS ", " PCMK_XE_CRM_CONFIG ", " PCMK_XE_RSC_DEFAULTS ",\n" INDENT " " PCMK_XE_OP_DEFAULTS ", " PCMK_XE_ACLS ", " PCMK_XE_FENCING_TOPOLOGY ", " PCMK_XE_TAGS ", " PCMK_XE_ALERTS ", " PCMK_XE_STATUS "\n" INDENT "If both --scope/-o and --xpath/-a are specified, the last one to " "appear takes effect", "value" }, { "xpath", 'A', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, section_cb, "A valid XPath to use instead of --scope/-o\n" INDENT "If both --scope/-o and --xpath/-a are specified, the last one to " "appear takes effect", "value" }, { "node-path", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.get_node_path, "When performing XPath queries, return paths of any matches found\n" INDENT "(for example, " "\"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES "/" PCMK_XE_CLONE "[@" PCMK_XA_ID "='dummy-clone']" "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='dummy']\")", NULL }, { "show-access", 'S', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_access_cb, "Whether to use syntax highlighting for ACLs (with -Q/--query and " "-U/--user)\n" INDENT "Allowed values: 'color' (default for terminal), 'text' (plain text, " "default for non-terminal),\n" INDENT " 'namespace', or 'auto' (use default value)\n" INDENT "Default value: 'auto'", "[value]" }, { "score", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.score_update, "Treat new attribute values as atomic score updates where possible " "(with --modify/-M).\n" INDENT "This currently happens by default and cannot be disabled, but\n" INDENT "this default behavior is deprecated and will be removed in a\n" INDENT "future release. Set this flag if this behavior is desired.\n" INDENT "This option takes effect when updating XML attributes. For an\n" INDENT "attribute named \"name\", if the new value is \"name++\" or\n" INDENT "\"name+=X\" for some score X, the new value is set as follows:\n" INDENT "If attribute \"name\" is not already set to some value in\n" INDENT "the element being updated, the new value is set as a literal\n" INDENT "string.\n" INDENT "If the new value is \"name++\", then the attribute is set to \n" INDENT "its existing value (parsed as a score) plus 1.\n" INDENT "If the new value is \"name+=X\" for some score X, then the\n" INDENT "attribute is set to its existing value plus X, where the\n" INDENT "existing value and X are parsed and added as scores.\n" INDENT "Scores are integer values capped at INFINITY and -INFINITY.\n" INDENT "Refer to Pacemaker Explained and to the char2score() function\n" INDENT "for more details on scores, including how they're parsed and\n" INDENT "added.", NULL }, { "allow-create", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.allow_create, "(Advanced) Allow target of --modify/-M to be created if it does not " "exist", NULL }, { "no-children", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.no_children, "(Advanced) When querying an object, do not include its children in the " "result", NULL }, { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.dest_node, "(Advanced) Send command to the specified host", "value" }, // @COMPAT: Deprecated { "no-bcast", 'b', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.no_bcast, "deprecated", NULL }, // @COMPAT: Deprecated { "host", 'h', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.dest_node, "deprecated", NULL }, { NULL } }; static GOptionContext * build_arg_context(pcmk__common_args_t *args) { const char *desc = NULL; GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { // @COMPAT: Deprecated { "extended-version", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.extended_version, "deprecated", NULL }, { NULL } }; desc = "Examples:\n\n" "Query the configuration from the local node:\n\n" "\t# cibadmin --query --local\n\n" "Query just the cluster options configuration:\n\n" "\t# cibadmin --query --scope " PCMK_XE_CRM_CONFIG "\n\n" "Query all '" PCMK_META_TARGET_ROLE "' settings:\n\n" "\t# cibadmin --query --xpath " "\"//" PCMK_XE_NVPAIR "[@" PCMK_XA_NAME "='" PCMK_META_TARGET_ROLE"']\"\n\n" "Remove all '" PCMK_META_IS_MANAGED "' settings:\n\n" "\t# cibadmin --delete-all --xpath " "\"//" PCMK_XE_NVPAIR "[@" PCMK_XA_NAME "='" PCMK_META_IS_MANAGED "']\"\n\n" "Remove the resource named 'old':\n\n" "\t# cibadmin --delete --xml-text " "'<" PCMK_XE_PRIMITIVE " " PCMK_XA_ID "=\"old\"/>'\n\n" "Remove all resources from the configuration:\n\n" "\t# cibadmin --replace --scope " PCMK_XE_RESOURCES " --xml-text '<" PCMK_XE_RESOURCES "/>'\n\n" "Replace complete configuration with contents of " "$HOME/pacemaker.xml:\n\n" "\t# cibadmin --replace --xml-file $HOME/pacemaker.xml\n\n" "Replace " PCMK_XE_CONSTRAINTS " section of configuration with " "contents of $HOME/constraints.xml:\n\n" "\t# cibadmin --replace --scope " PCMK_XE_CONSTRAINTS " --xml-file $HOME/constraints.xml\n\n" "Increase configuration version to prevent old configurations from " "being loaded accidentally:\n\n" "\t# cibadmin --modify --score --xml-text " "'<" PCMK_XE_CIB " " PCMK_XA_ADMIN_EPOCH "=\"" PCMK_XA_ADMIN_EPOCH "++\"/>'\n\n" "Edit the configuration with your favorite $EDITOR:\n\n" "\t# cibadmin --query > $HOME/local.xml\n\n" "\t# $EDITOR $HOME/local.xml\n\n" "\t# cibadmin --replace --xml-file $HOME/local.xml\n\n" "Assuming terminal, render configuration in color (green for " "writable, blue for readable, red for\n" "denied) to visualize permissions for user tony:\n\n" "\t# cibadmin --show-access=color --query --user tony | less -r\n\n" "SEE ALSO:\n" " crm(8), pcs(8), crm_shadow(8), crm_diff(8)\n"; context = pcmk__build_arg_context(args, NULL, NULL, ""); g_option_context_set_description(context, desc); pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "data", "Data:", "Show data help", data_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; const char *source = NULL; xmlNode *output = NULL; xmlNode *input = NULL; gchar *acl_cred = NULL; GError *error = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "ANSUXhotx"); GOptionContext *context = build_arg_context(args); 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 || options.extended_version) { g_strfreev(processed_args); pcmk__free_arg_context(context); /* FIXME: When cibadmin is converted to use formatted output, this can * be replaced by out->version with the appropriate boolean flag. * * options.extended_version is deprecated and will be removed in a * future release. */ pcmk__cli_help(options.extended_version? '!' : 'v'); } /* At LOG_ERR, stderr for CIB calls is rather verbose. Several lines like * * (func@file:line) error: CIB failures * * In cibadmin we explicitly output the XML portion without the prefixes. So * we default to LOG_CRIT. */ pcmk__cli_init_logging("cibadmin", 0); set_crm_log_level(LOG_CRIT); if (args->verbosity > 0) { cib__set_call_options(options.cmd_options, crm_system_name, cib_verbose); for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } } if (options.cib_action == NULL) { // @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 command option\n\n%s", help); g_free(help); goto done; } if (strcmp(options.cib_action, "empty") == 0) { // Output an empty CIB GString *buf = g_string_sized_new(1024); output = createEmptyCib(1); crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with); pcmk__xml_string(output, pcmk__xml_fmt_pretty, buf, 0); fprintf(stdout, "%s", buf->str); g_string_free(buf, TRUE); goto done; } if (cib_action_is_dangerous() && !options.force) { exit_code = CRM_EX_UNSAFE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command is considered dangerous. To prevent " "accidental destruction of the cluster, the --force flag " "is required in order to proceed."); goto done; } if (options.message_timeout_sec < 1) { // Set default timeout options.message_timeout_sec = 30; } if (options.section_type == cibadmin_section_xpath) { // Enable getting section by XPath cib__set_call_options(options.cmd_options, crm_system_name, cib_xpath); } else if (options.section_type == cibadmin_section_scope) { if (!scope_is_valid(options.cib_section)) { // @COMPAT: Consider requiring --force to proceed fprintf(stderr, "Invalid value '%s' for '--scope'. Operation will apply " "to the entire CIB.\n", options.cib_section); } } if (options.allow_create) { // Allow target of --modify/-M to be created if it does not exist cib__set_call_options(options.cmd_options, crm_system_name, cib_can_create); } if (options.delete_all) { // With cibadmin_section_xpath, remove all matching objects cib__set_call_options(options.cmd_options, crm_system_name, cib_multiple); } if (options.get_node_path) { /* Enable getting node path of XPath query matches. * Meaningful only if options.section_type == cibadmin_section_xpath. */ cib__set_call_options(options.cmd_options, crm_system_name, cib_xpath_address); } if (options.local) { // Configure command to take effect only locally cib__set_call_options(options.cmd_options, crm_system_name, cib_scope_local); } // @COMPAT: Deprecated option if (options.no_bcast) { // Configure command to take effect only locally and not to broadcast cib__set_call_options(options.cmd_options, crm_system_name, cib_inhibit_bcast|cib_scope_local); } if (options.no_children) { // When querying an object, don't include its children in the result cib__set_call_options(options.cmd_options, crm_system_name, cib_no_children); } if (options.sync_call || (options.acl_render_mode != pcmk__acl_render_none)) { /* Wait for call to complete before returning. * * The ACL render modes work only with sync calls due to differences in * output handling between sync/async. It shouldn't matter to the user * whether the call is synchronous; for a CIB query, we have to wait for * the result in order to display it in any case. */ cib__set_call_options(options.cmd_options, crm_system_name, cib_sync_call); } if (options.input_file != NULL) { input = pcmk__xml_read(options.input_file); source = options.input_file; } else if (options.input_xml != NULL) { input = pcmk__xml_parse(options.input_xml); source = "input string"; } else if (options.input_stdin) { input = pcmk__xml_read(NULL); source = "STDIN"; } else if (options.acl_render_mode != pcmk__acl_render_none) { char *username = pcmk__uid2username(geteuid()); bool required = pcmk_acl_required(username); free(username); if (required) { if (options.force) { fprintf(stderr, "The supplied command can provide skewed" " result since it is run under user that also" " gets guarded per ACLs on their own right." " Continuing since --force flag was" " provided.\n"); } else { exit_code = CRM_EX_UNSAFE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command can provide skewed result " "since it is run under user that also gets guarded " "per ACLs in their own right. To accept the risk " "of such a possible distortion (without even " "knowing it at this time), use the --force flag."); goto done; } } if (options.cib_user == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command requires -U user specified."); goto done; } /* We already stopped/warned ACL-controlled users about consequences. * * Note: acl_cred takes ownership of options.cib_user here. * options.cib_user is set to NULL so that the CIB is obtained as the * user running the cibadmin command. The CIB must be obtained as a user * with full permissions in order to show the CIB correctly annotated * for the options.cib_user's permissions. */ acl_cred = options.cib_user; options.cib_user = NULL; } if (input != NULL) { crm_log_xml_debug(input, "[admin input]"); } else if (source != NULL) { exit_code = CRM_EX_CONFIG; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Couldn't parse input from %s.", source); goto done; } if (pcmk__str_eq(options.cib_action, "md5-sum", pcmk__str_casei)) { char *digest = NULL; if (input == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Please supply XML to process with -X, -x, or -p"); goto done; } digest = pcmk__digest_on_disk_cib(input); fprintf(stderr, "Digest: "); fprintf(stdout, "%s\n", pcmk__s(digest, "")); free(digest); goto done; } else if (strcmp(options.cib_action, "md5-sum-versioned") == 0) { char *digest = NULL; const char *version = NULL; if (input == NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Please supply XML to process with -X, -x, or -p"); goto done; } version = crm_element_value(input, PCMK_XA_CRM_FEATURE_SET); - digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version); + digest = pcmk__digest_xml(input, true, version); fprintf(stderr, "Versioned (%s) digest: ", version); fprintf(stdout, "%s\n", pcmk__s(digest, "")); free(digest); goto done; } else if (pcmk__str_eq(options.cib_action, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { /* @COMPAT When we drop default support for expansion in cibadmin, guard * with `if (options.score_update)` */ cib__set_call_options(options.cmd_options, crm_system_name, cib_score_update); } rc = do_init(); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); crm_err("Init failed, could not perform requested operations: %s", pcmk_rc_str(rc)); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Init failed, could not perform requested operations: %s", pcmk_rc_str(rc)); goto done; } rc = do_work(input, &output); if (!pcmk_is_set(options.cmd_options, cib_sync_call) && (the_cib->variant != cib_file) && (rc >= 0)) { /* For async call, positive rc is the call ID (file always synchronous). * * Wait for the reply by creating a mainloop and running it until the * callbacks are invoked. */ request_id = rc; the_cib->cmds->register_callback(the_cib, request_id, options.message_timeout_sec, FALSE, NULL, "cibadmin_op_callback", cibadmin_op_callback); mainloop = g_main_loop_new(NULL, FALSE); crm_trace("%s waiting for reply from the local CIB", crm_system_name); crm_info("Starting mainloop"); g_main_loop_run(mainloop); } else { rc = pcmk_legacy2rc(rc); if ((rc == pcmk_rc_schema_unchanged) && (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0)) { report_schema_unchanged(); } else if (rc != pcmk_rc_ok) { crm_err("Call failed: %s", pcmk_rc_str(rc)); fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc)); exit_code = pcmk_rc2exitc(rc); if (rc == pcmk_rc_schema_validation) { if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) { xmlNode *obj = NULL; if (the_cib->cmds->query(the_cib, NULL, &obj, options.cmd_options) == pcmk_ok) { pcmk__update_schema(&obj, NULL, true, false); } pcmk__xml_free(obj); } else if (output != NULL) { // Show validation errors to stderr pcmk__validate_xml(output, NULL, NULL, NULL); } } } } if ((output != NULL) && (options.acl_render_mode != pcmk__acl_render_none)) { xmlDoc *acl_evaled_doc; rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc); if (rc == pcmk_rc_ok) { xmlChar *rendered = NULL; rc = pcmk__acl_evaled_render(acl_evaled_doc, options.acl_render_mode, &rendered); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_CONFIG; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not render evaluated access: %s", pcmk_rc_str(rc)); goto done; } printf("%s\n", (char *) rendered); free(rendered); } else { exit_code = CRM_EX_CONFIG; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not evaluate access per request (%s, error: %s)", acl_cred, pcmk_rc_str(rc)); goto done; } } else if (output != NULL) { print_xml_output(output); } crm_trace("%s exiting normally", crm_system_name); done: g_strfreev(processed_args); pcmk__free_arg_context(context); g_free(options.cib_user); g_free(options.dest_node); g_free(options.input_file); g_free(options.input_xml); free(options.cib_section); free(options.validate_with); g_free(acl_cred); pcmk__xml_free(input); pcmk__xml_free(output); rc = cib__clean_up_connection(&the_cib); if (exit_code == CRM_EX_OK) { exit_code = pcmk_rc2exitc(rc); } pcmk__output_and_clear_error(&error, NULL); crm_exit(exit_code); } static int do_work(xmlNode *input, xmlNode **output) { /* construct the request */ the_cib->call_timeout = options.message_timeout_sec; if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_REPLACE) == 0) && pcmk__xe_is(input, PCMK_XE_CIB)) { xmlNode *status = pcmk_find_cib_element(input, PCMK_XE_STATUS); if (status == NULL) { pcmk__xe_create(input, PCMK_XE_STATUS); } } crm_trace("Passing \"%s\" to variant_op...", options.cib_action); return cib_internal_op(the_cib, options.cib_action, options.dest_node, options.cib_section, input, output, options.cmd_options, options.cib_user); } int do_init(void) { int rc = pcmk_ok; the_cib = cib_new(); rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc)); fprintf(stderr, "Could not connect to the CIB: %s\n", pcmk_strerror(rc)); } return rc; } void cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { rc = pcmk_legacy2rc(rc); exit_code = pcmk_rc2exitc(rc); if (rc == pcmk_rc_schema_unchanged) { report_schema_unchanged(); } else if (rc != pcmk_rc_ok) { crm_warn("Call %s failed: %s " CRM_XS " rc=%d", options.cib_action, pcmk_rc_str(rc), rc); fprintf(stderr, "Call %s failed: %s\n", options.cib_action, pcmk_rc_str(rc)); print_xml_output(output); } else if ((strcmp(options.cib_action, PCMK__CIB_REQUEST_QUERY) == 0) && (output == NULL)) { crm_err("Query returned no output"); crm_log_xml_err(msg, "no output"); } else if (output == NULL) { crm_info("Call passed"); } else { crm_info("Call passed"); print_xml_output(output); } if (call_id == request_id) { g_main_loop_quit(mainloop); } else { crm_info("Message was not the response we were looking for (%d vs. %d)", call_id, request_id); } } diff --git a/tools/crm_diff.c b/tools/crm_diff.c index 009b198748..34a3eb12bf 100644 --- a/tools/crm_diff.c +++ b/tools/crm_diff.c @@ -1,382 +1,382 @@ /* * Copyright 2005-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) { GString *buffer = g_string_sized_new(1024); pcmk__xml_string(patch, pcmk__xml_fmt_pretty, buffer, 0); printf("%s", buffer->str); g_string_free(buffer, TRUE); fflush(stdout); } // \return Standard Pacemaker return code static int apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib) { xmlNode *output = pcmk__xml_copy(NULL, 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)); pcmk__xml_free(output); return rc; } if (output != NULL) { const char *version; char *buffer; print_patch(output); version = crm_element_value(output, PCMK_XA_CRM_FEATURE_SET); - buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version); + buffer = pcmk__digest_xml(output, true, version); crm_trace("Digest: %s", pcmk__s(buffer, "\n")); free(buffer); pcmk__xml_free(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, PCMK_XA_FORMAT); digest = crm_element_value(patch, PCMK__XA_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, PCMK_XA_FORMAT, &format); if (format == 2) { xmlNode *version_xml = pcmk__xe_first_child(patch, PCMK_XE_VERSION, NULL, NULL); if (version_xml) { pcmk__xml_free(version_xml); } } else { int i = 0; const char *tags[] = { PCMK__XE_DIFF_REMOVED, PCMK__XE_DIFF_ADDED, }; for (i = 0; i < PCMK__NELEM(tags); i++) { xmlNode *tmp = NULL; int lpc; tmp = pcmk__xe_first_child(patch, tags[i], NULL, NULL); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { pcmk__xe_remove_attr(tmp, vfields[lpc]); } tmp = pcmk__xe_first_child(tmp, PCMK_XE_CIB, NULL, NULL); if (tmp) { for (lpc = 0; lpc < nvfields; lpc++) { pcmk__xe_remove_attr(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) { const char *vfields[] = { PCMK_XA_ADMIN_EPOCH, PCMK_XA_EPOCH, PCMK_XA_NUM_UPDATES, }; xmlNode *output = NULL; /* 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__log_xml_changes(LOG_INFO, object_2); xml_accept_changes(object_2); if (output == NULL) { return pcmk_rc_ok; // No changes } 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__log_xml_patchset(LOG_NOTICE, output); print_patch(output); pcmk__xml_free(output); /* pcmk_rc_error means there's a non-empty diff. * @COMPAT Choose a more descriptive return code, like one that maps to * CRM_EX_DIGEST? */ 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 = pcmk__xml_parse(options.xml_file_1); } else if (options.use_stdin) { fprintf(stderr, "Input first XML fragment:"); object_1 = pcmk__xml_read(NULL); } else if (options.xml_file_1 != NULL) { object_1 = pcmk__xml_read(options.xml_file_1); } if (options.raw_2) { object_2 = pcmk__xml_parse(options.xml_file_2); } else if (options.use_stdin) { fprintf(stderr, "Input second XML fragment:"); object_2 = pcmk__xml_read(NULL); } else if (options.xml_file_2 != NULL) { object_2 = pcmk__xml_read(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); pcmk__xml_free(object_1); pcmk__xml_free(object_2); pcmk__output_and_clear_error(&error, NULL); crm_exit(exit_code); }