Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c
index dfcb5adacd..7059af69ab 100644
--- a/daemons/based/based_callbacks.c
+++ b/daemons/based/based_callbacks.c
@@ -1,1713 +1,1716 @@
/*
* 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 <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h> // uint32_t, uint64_t, UINT64_C()
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h> // PRIu64
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/cluster/internal.h>
#include <crm/common/xml.h>
#include <crm/common/remote_internal.h>
#include <pacemaker-based.h>
#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 -EPERM;
}
if (pcmk__new_client(c, uid, gid) == NULL) {
return -EIO;
}
return 0;
}
static int32_t
cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size)
{
pcmk__client_t *client = pcmk__find_client(c);
crm_trace("%p message from %s", c, client->id);
return cib_common_callback(c, data, size, TRUE);
}
static int32_t
cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size)
{
pcmk__client_t *client = pcmk__find_client(c);
crm_trace("%p message from %s", c, client->id);
return cib_common_callback(c, data, size, FALSE);
}
/* Error code means? */
static int32_t
cib_ipc_closed(qb_ipcs_connection_t * c)
{
pcmk__client_t *client = pcmk__find_client(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
pcmk__free_client(client);
return 0;
}
static void
cib_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
cib_ipc_closed(c);
if (cib_shutdown_flag) {
cib_shutdown(0);
}
}
struct qb_ipcs_service_handlers ipc_ro_callbacks = {
.connection_accept = cib_ipc_accept,
.connection_created = NULL,
.msg_process = cib_ipc_dispatch_ro,
.connection_closed = cib_ipc_closed,
.connection_destroyed = cib_ipc_destroy
};
struct qb_ipcs_service_handlers ipc_rw_callbacks = {
.connection_accept = cib_ipc_accept,
.connection_created = NULL,
.msg_process = cib_ipc_dispatch_rw,
.connection_closed = cib_ipc_closed,
.connection_destroyed = cib_ipc_destroy
};
/*!
* \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 <tt>enum cib_call_options</tt> 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 free_xml().
*/
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 = create_xml_node(NULL, "cib-reply");
CRM_ASSERT(reply != NULL);
crm_xml_add(reply, F_TYPE, T_CIB);
crm_xml_add(reply, F_CIB_OPERATION, op);
crm_xml_add(reply, F_CIB_CALLID, call_id);
crm_xml_add(reply, F_CIB_CLIENTID, client_id);
crm_xml_add_int(reply, F_CIB_CALLOPTS, call_options);
crm_xml_add_int(reply, F_CIB_RC, rc);
if (call_data != NULL) {
crm_trace("Attaching reply output");
add_message_xml(reply, F_CIB_CALLDATA, 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, F_CIB_CALLID, &call_id);
client_obj = pcmk__find_client_by_id(client_id);
if (client_obj == NULL) {
crm_debug("Could not send response %d: client %s not found",
call_id, client_id);
return;
}
if (sync_reply) {
if (client_obj->ipcs) {
CRM_LOG_ASSERT(client_obj->request_id);
rid = client_obj->request_id;
client_obj->request_id = 0;
crm_trace("Sending response %d to client %s%s",
rid, pcmk__client_name(client_obj),
(from_peer? " (originator of delegated request)" : ""));
} else {
crm_trace("Sending response (call %d) to client %s%s",
call_id, pcmk__client_name(client_obj),
(from_peer? " (originator of delegated request)" : ""));
}
} else {
crm_trace("Sending event %d to client %s%s",
call_id, pcmk__client_name(client_obj),
(from_peer? " (originator of delegated request)" : ""));
}
switch (PCMK__CLIENT_TYPE(client_obj)) {
case pcmk__client_ipc:
{
int rc = pcmk__ipc_send_xml(client_obj, rid, notify_src,
(sync_reply? crm_ipc_flags_none
: crm_ipc_server_event));
if (rc != pcmk_rc_ok) {
crm_warn("%s reply to client %s failed: %s " CRM_XS " rc=%d",
(sync_reply? "Synchronous" : "Asynchronous"),
pcmk__client_name(client_obj), pcmk_rc_str(rc),
rc);
}
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case pcmk__client_tls:
#endif
case pcmk__client_tcp:
pcmk__remote_send_xml(client_obj->remote, notify_src);
break;
default:
crm_err("Unknown transport for client %s "
CRM_XS " flags=%#016" PRIx64,
pcmk__client_name(client_obj), client_obj->flags);
}
}
void
cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request,
pcmk__client_t *cib_client, gboolean privileged)
{
const char *op = crm_element_value(op_request, F_CIB_OPERATION);
int call_options = cib_none;
crm_element_value_int(op_request, F_CIB_CALLOPTS, &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 = create_xml_node(NULL, __func__);
crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER);
crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id);
pcmk__ipc_send_xml(cib_client, id, ack, flags);
cib_client->request_id = 0;
free_xml(ack);
}
return;
} else if (pcmk__str_eq(op, T_CIB_NOTIFY, pcmk__str_none)) {
/* Update the notify filters for this client */
int on_off = 0;
crm_exit_t status = CRM_EX_OK;
uint64_t bit = UINT64_C(0);
const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE);
crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off);
crm_debug("Setting %s callbacks %s for client %s",
type, (on_off? "on" : "off"), pcmk__client_name(cib_client));
if (pcmk__str_eq(type, T_CIB_POST_NOTIFY, pcmk__str_casei)) {
bit = cib_notify_post;
} else if (pcmk__str_eq(type, T_CIB_PRE_NOTIFY, pcmk__str_casei)) {
bit = cib_notify_pre;
} else if (pcmk__str_eq(type, T_CIB_UPDATE_CONFIRM, pcmk__str_casei)) {
bit = cib_notify_confirm;
} else if (pcmk__str_eq(type, T_CIB_DIFF_NOTIFY, pcmk__str_casei)) {
bit = cib_notify_diff;
} else {
status = CRM_EX_INVALID_PARAM;
}
if (bit != 0) {
if (on_off) {
pcmk__set_client_flags(cib_client, bit);
} else {
pcmk__clear_client_flags(cib_client, bit);
}
}
pcmk__ipc_send_ack(cib_client, id, flags, "ack", NULL, status);
return;
}
cib_process_request(op_request, privileged, cib_client);
}
int32_t
cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged)
{
uint32_t id = 0;
uint32_t flags = 0;
int call_options = 0;
pcmk__client_t *cib_client = pcmk__find_client(c);
xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags);
if (op_request) {
crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options);
}
if (op_request == NULL) {
crm_trace("Invalid message from %p", c);
pcmk__ipc_send_ack(cib_client, id, flags, "nack", NULL, CRM_EX_PROTOCOL);
return 0;
} else if(cib_client == NULL) {
crm_trace("Invalid client %p", c);
return 0;
}
if (pcmk_is_set(call_options, cib_sync_call)) {
CRM_LOG_ASSERT(flags & crm_ipc_client_response);
CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */
cib_client->request_id = id; /* Reply only to the last one */
}
if (cib_client->name == NULL) {
const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME);
if (value == NULL) {
cib_client->name = pcmk__itoa(cib_client->pid);
} else {
cib_client->name = strdup(value);
if (crm_is_daemon_name(value)) {
pcmk__set_client_flags(cib_client, cib_is_daemon);
}
}
}
/* Allow cluster daemons more leeway before being evicted */
if (pcmk_is_set(cib_client->flags, cib_is_daemon)) {
const char *qmax = cib_config_lookup(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, F_CIB_CLIENTID, cib_client->id);
crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name);
CRM_LOG_ASSERT(cib_client->user != NULL);
pcmk__update_acl_user(op_request, F_CIB_USER, cib_client->user);
cib_common_callback_worker(id, flags, op_request, cib_client, privileged);
free_xml(op_request);
return 0;
}
static uint64_t ping_seq = 0;
static char *ping_digest = NULL;
static bool ping_modified_since = FALSE;
static gboolean
cib_digester_cb(gpointer data)
{
if (based_is_primary) {
char buffer[32];
xmlNode *ping = create_xml_node(NULL, "ping");
ping_seq++;
free(ping_digest);
ping_digest = NULL;
ping_modified_since = FALSE;
snprintf(buffer, 32, "%" PRIu64, ping_seq);
crm_trace("Requesting peer digests (%s)", buffer);
crm_xml_add(ping, F_TYPE, "cib");
crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING);
crm_xml_add(ping, F_CIB_PING_ID, buffer);
crm_xml_add(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
send_cluster_message(NULL, crm_msg_cib, ping, TRUE);
free_xml(ping);
}
return FALSE;
}
static void
process_ping_reply(xmlNode *reply)
{
uint64_t seq = 0;
const char *host = crm_element_value(reply, F_ORIG);
xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA);
const char *seq_s = crm_element_value(pong, F_CIB_PING_ID);
const char *digest = crm_element_value(pong, PCMK__XA_DIGEST);
if (seq_s == NULL) {
crm_debug("Ignoring ping reply with no " F_CIB_PING_ID);
return;
} else {
long long seq_ll;
if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) {
return;
}
seq = (uint64_t) seq_ll;
}
if(digest == NULL) {
crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host);
} else if(seq != ping_seq) {
crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host);
} else if(ping_modified_since) {
crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host);
} else {
const char *version = crm_element_value(pong, 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);
}
crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest);
if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) {
xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA);
+ const char *admin_epoch_s = "_";
const char *epoch_s = "_";
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);
}
crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p",
- crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(the_cib, PCMK_XA_ADMIN_EPOCH),
crm_element_value(the_cib, PCMK_XA_EPOCH),
crm_element_value(the_cib, XML_ATTR_NUMUPDATES),
ping_digest, host,
- remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_",
- epoch_s,
+ admin_epoch_s, epoch_s,
remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_",
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");
}
free_xml(remote_cib);
sync_our_cib(reply, FALSE);
}
}
}
static void
local_notify_destroy_callback(gpointer data)
{
cib_local_notify_t *notify = data;
free_xml(notify->notify_src);
free(notify->client_id);
free(notify);
}
static void
check_local_notify(int bcast_id)
{
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 = calloc(1, sizeof(cib_local_notify_t));
notify->notify_src = notify_src;
notify->client_id = strdup(client_id);
notify->sync_reply = sync_reply;
notify->from_peer = from_peer;
if (!local_notify_queue) {
local_notify_queue = pcmk__intkey_table(local_notify_destroy_callback);
}
pcmk__intkey_table_insert(local_notify_queue, cib_local_bcast_num, notify);
// cppcheck doesn't know notify will get freed when hash table is destroyed
// cppcheck-suppress memleak
}
static void
parse_local_options_v1(const pcmk__client_t *cib_client,
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, F_ORIG);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
*needs_reply = FALSE;
if (is_reply) {
*local_notify = TRUE;
crm_trace("Processing global/peer update from %s"
" that originated from us", originator);
} else {
crm_trace("Processing global/peer update from %s", originator);
}
return TRUE;
}
op = crm_element_value(request, F_CIB_OPERATION);
crm_trace("Processing 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, F_CIB_HOST);
if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
crm_trace("Processing %s request sent to us from %s", op, originator);
return TRUE;
} else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator);
*needs_reply = TRUE;
return TRUE;
} else if ((host == NULL) && based_is_primary) {
crm_trace("Processing %s request sent to primary instance from %s",
op, originator);
return TRUE;
}
delegated = crm_element_value(request, F_CIB_DELEGATED);
if (delegated != NULL) {
crm_trace("Ignoring message for primary instance");
} else if (host != NULL) {
/* this is for a specific instance and we're not it */
crm_trace("Ignoring msg for instance on %s", host);
} else if ((reply_to == NULL) && !based_is_primary) {
// This is for the primary instance, and we're not it
crm_trace("Ignoring reply for primary instance");
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) {
if (reply_to != NULL) {
crm_debug("Processing %s from %s", op, originator);
*needs_reply = FALSE;
} else {
crm_debug("Processing %s reply from %s", op, originator);
}
return TRUE;
} else {
crm_err("Nothing for us to do?");
crm_log_xml_err(request, "Peer[inbound]");
}
return FALSE;
}
static gboolean
parse_peer_options_v2(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, F_CIB_DELEGATED);
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *originator = crm_element_value(request, F_ORIG);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
gboolean is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei);
if (originator == NULL) { // Shouldn't be possible
originator = "peer";
}
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) {
/* sync_our_cib() sets F_CIB_ISREPLY */
if (reply_to) {
delegated = reply_to;
}
goto skip_is_reply;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL,
pcmk__str_none)) {
// Nothing to do
} else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
process_ping_reply(request);
return FALSE;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) {
/* Only the DC (node with the oldest software) should process
* this operation if F_CIB_SCHEMA_MAX is unset
*
* If the DC is happy it will then send out another
* PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual
* upgrade.
*
* Except this time F_CIB_SCHEMA_MAX will be set which puts a
* limit on how far newer nodes will go
*/
const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX);
const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC);
crm_trace("Parsing 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, F_CIB_RC, upgrade_rc);
} else if ((max == NULL) && based_is_primary) {
/* We are the DC, check if this upgrade is allowed */
goto skip_is_reply;
} else if(max) {
/* Ok, go ahead and upgrade to 'max' */
goto skip_is_reply;
} else {
// Ignore broadcast client requests when we're not primary
return FALSE;
}
} else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
crm_info("Detected legacy %s global update from %s", op, originator);
send_sync_request(NULL);
legacy_mode = TRUE;
return FALSE;
} else if (is_reply
&& 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, F_CIB_HOST);
if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
crm_trace("Processing %s request sent to us from %s", op, originator);
*needs_reply = TRUE;
return TRUE;
} else if (host != NULL) {
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, F_CIB_CLIENTNAME), "client"),
pcmk__s(crm_element_value(request, F_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, F_CIB_OPERATION);
const char *section = crm_element_value(request, F_CIB_SECTION);
const char *host = crm_element_value(request, F_CIB_HOST);
const char *originator = crm_element_value(request, F_ORIG);
const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
const char *call_id = crm_element_value(request, F_CIB_CALLID);
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, F_CIB_DELEGATED, OUR_NODENAME);
send_cluster_message(((host != NULL)? crm_get_peer(0, host) : NULL),
crm_msg_cib, request, FALSE);
// Return the request to its original state
xml_remove_prop(request, F_CIB_DELEGATED);
}
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;
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, F_CIB_ISREPLY, originator);
pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true);
crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH);
crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER);
if (format == 1) {
CRM_ASSERT(digest != NULL);
}
add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff);
crm_log_xml_explicit(msg, "copy");
return send_cluster_message(NULL, crm_msg_cib, msg, TRUE);
} else if (originator != NULL) {
/* send reply via HA to originating node */
crm_trace("Sending request result to %s only", originator);
crm_xml_add(msg, F_CIB_ISREPLY, originator);
return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE);
}
return FALSE;
}
/*!
* \internal
* \brief Handle an IPC or CPG message containing a request
*
* \param[in,out] request Request XML
* \param[in] privileged Whether privileged commands may be run
* (see cib_server_ops[] definition)
* \param[in] cib_client IPC client that sent request (or NULL if CPG)
*
* \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, F_CIB_OPERATION);
const char *originator = crm_element_value(request, F_ORIG);
const char *host = crm_element_value(request, F_CIB_HOST);
const char *target = NULL;
const char *call_id = crm_element_value(request, F_CIB_CALLID);
const char *client_id = crm_element_value(request, F_CIB_CLIENTID);
const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
const cib__operation_t *operation = NULL;
cib__op_fn_t op_function = NULL;
crm_element_value_int(request, F_CIB_CALLOPTS, &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, F_ORIG, OUR_NODENAME);
crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target);
}
rc = cib__get_operation(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, F_CIB_SECTION);
+ const char *admin_epoch_s = "0";
const char *epoch_s = "0";
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);
}
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, F_CIB_GLOBAL_UPDATE)) {
switch (rc) {
case pcmk_ok:
level = LOG_INFO;
break;
case -pcmk_err_old_data:
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
level = LOG_TRACE;
break;
default:
level = LOG_ERR;
}
} else if (rc != pcmk_ok) {
level = LOG_WARNING;
}
do_crm_log(level,
"Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)",
op, section ? section : "'all'", pcmk_strerror(rc), rc,
originator ? originator : "local", client_name, call_id,
- the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0",
- epoch_s,
+ admin_epoch_s, epoch_s,
the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0");
finished = time(NULL);
if ((finished - now) > 3) {
crm_trace("%s operation took %lds to complete", op, (long)(finished - now));
crm_write_blackbox(0, NULL);
}
if (op_reply == NULL && (needs_reply || local_notify)) {
crm_err("Unexpected NULL reply to message");
crm_log_xml_err(request, "null reply");
needs_reply = FALSE;
local_notify = FALSE;
}
}
if (is_update && !cib_legacy_mode()) {
crm_trace("Completed pre-sync update from %s/%s/%s%s",
originator ? originator : "local", client_name, call_id,
local_notify?" with local notification":"");
} else if (!needs_reply || stand_alone) {
// This was a non-originating secondary update
crm_trace("Completed update as secondary");
} else if (cib_legacy_mode() &&
rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) {
gboolean broadcast = FALSE;
cib_local_bcast_num++;
crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num);
broadcast = send_peer_reply(request, result_diff, originator, TRUE);
if (broadcast && client_id && local_notify && op_reply) {
/* If we have been asked to sync the reply,
* and a bcast msg has gone out, we queue the local notify
* until we know the bcast message has been received */
local_notify = FALSE;
crm_trace("Queuing local %ssync notification for %s",
(call_options & cib_sync_call) ? "" : "a-", client_id);
queue_local_notify(op_reply, client_id,
pcmk_is_set(call_options, cib_sync_call),
(cib_client == NULL));
op_reply = NULL; /* the reply is queued, so don't free here */
}
} else if ((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));
}
}
free_xml(op_reply);
free_xml(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 *input = NULL;
*section = NULL;
switch (type) {
case cib__op_apply_patch:
if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
input = get_message_xml(request, F_CIB_UPDATE_DIFF);
} else {
input = get_message_xml(request, F_CIB_CALLDATA);
}
break;
default:
input = get_message_xml(request, F_CIB_CALLDATA);
*section = crm_element_value(request, F_CIB_SECTION);
break;
}
// Grab the specified section
if ((*section != NULL) && pcmk__xe_is(input, XML_TAG_CIB)) {
input = pcmk_find_cib_element(input, *section);
}
return input;
}
// v1 and v2 patch formats
#define XPATH_CONFIG_CHANGE \
"//" XML_CIB_TAG_CRMCONFIG " | " \
"//" XML_DIFF_CHANGE \
"[contains(@" XML_DIFF_PATH ",'/" XML_CIB_TAG_CRMCONFIG "/')]"
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, F_CIB_CALLID);
const char *client_id = crm_element_value(request, F_CIB_CLIENTID);
const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
const char *originator = crm_element_value(request, F_ORIG);
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, F_CIB_OPERATION);
crm_element_value_int(request, F_CIB_CALLOPTS, &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, &section);
if (!pcmk_is_set(operation->flags, cib__op_attr_modifies)) {
rc = cib_perform_op(op, call_options, op_function, true, section,
request, input, false, &config_changed, &the_cib,
&result_cib, NULL, &output);
CRM_CHECK(result_cib == NULL, free_xml(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, F_CIB_GLOBAL_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, F_CIB_GLOBAL_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(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, XML_ATTR_NUMUPDATES),
crm_element_value(result_cib, XML_ATTR_NUMUPDATES),
(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");
free_xml(output);
}
output = result_cib;
} else {
crm_trace("Not activating %d %d %s", rc,
pcmk_is_set(call_options, cib_dryrun),
crm_element_value(result_cib, XML_ATTR_NUMUPDATES));
if (result_cib != the_cib) {
free_xml(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) {
free_xml(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, F_ORIG);
if (cib_legacy_mode()
&& pcmk__str_eq(originator, OUR_NODENAME,
pcmk__str_casei|pcmk__str_null_matches)) {
/* message is from ourselves */
int bcast_id = 0;
if (!(crm_element_value_int(msg, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) {
check_local_notify(bcast_id);
}
return;
} else if (crm_peer_cache == NULL) {
reason = "membership not established";
goto bail;
}
if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) {
crm_xml_add(msg, F_CIB_CLIENTNAME, originator);
}
/* crm_log_xml_trace(msg, "Peer[inbound]"); */
cib_process_request(msg, TRUE, NULL);
return;
bail:
if (reason) {
const char *seq = crm_element_value(msg, F_SEQ);
const char *op = crm_element_value(msg, F_CIB_OPERATION);
crm_warn("Discarding %s message (%s) from %s: %s", op, seq, originator, reason);
}
}
static gboolean
cib_force_exit(gpointer data)
{
crm_notice("Forcing exit!");
terminate_cib(__func__, CRM_EX_ERROR);
return FALSE;
}
static void
disconnect_remote_client(gpointer key, gpointer value, gpointer user_data)
{
pcmk__client_t *a_client = value;
crm_err("Can't disconnect client %s: Not implemented",
pcmk__client_name(a_client));
}
static void
initiate_exit(void)
{
int active = 0;
xmlNode *leaving = NULL;
active = crm_active_peers();
if (active < 2) { // This is the last active node
terminate_cib(__func__, 0);
return;
}
crm_info("Sending shutdown request to %d peers", active);
leaving = create_xml_node(NULL, "exit-notification");
crm_xml_add(leaving, F_TYPE, "cib");
crm_xml_add(leaving, F_CIB_OPERATION, PCMK__CIB_REQUEST_SHUTDOWN);
send_cluster_message(NULL, crm_msg_cib, leaving, TRUE);
free_xml(leaving);
g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL);
}
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) {
crm_cluster_disconnect(crm_cluster);
}
g_main_loop_quit(mainloop);
} else {
/* Quit via clean exit. Even the peer status callback can disconnect
* here, because we're not returning control to the caller. */
crm_cluster_disconnect(crm_cluster);
pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
crm_exit(CRM_EX_OK);
}
}
diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c
index f530b4f4d5..284b232c7c 100644
--- a/daemons/based/based_io.c
+++ b/daemons/based/based_io.c
@@ -1,479 +1,479 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/common/util.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/cib/internal.h>
#include <crm/cluster.h>
#include <pacemaker-based.h>
crm_trigger_t *cib_writer = NULL;
int write_cib_contents(gpointer p);
static void
cib_rename(const char *old)
{
int new_fd;
char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
umask(S_IWGRP | S_IWOTH | S_IROTH);
new_fd = mkstemp(new);
if ((new_fd < 0) || (rename(old, new) < 0)) {
crm_err("Couldn't archive unusable file %s (disabling disk writes and continuing)",
old);
cib_writes_enabled = FALSE;
} else {
crm_err("Archived unusable file %s as %s", old, new);
}
if (new_fd > 0) {
close(new_fd);
}
free(new);
}
/*
* It is the callers responsibility to free the output of this function
*/
static xmlNode *
retrieveCib(const char *filename, const char *sigfile)
{
xmlNode *root = NULL;
crm_info("Reading cluster configuration file %s (digest: %s)",
filename, sigfile);
switch (cib_file_read_and_verify(filename, sigfile, &root)) {
case -pcmk_err_cib_corrupt:
crm_warn("Continuing but %s will NOT be used.", filename);
break;
case -pcmk_err_cib_modified:
/* Archive the original files so the contents are not lost */
crm_warn("Continuing but %s will NOT be used.", filename);
cib_rename(filename);
cib_rename(sigfile);
break;
}
return root;
}
/*
* for OSs without support for direntry->d_type, like Solaris
*/
#ifndef DT_UNKNOWN
# define DT_UNKNOWN 0
# define DT_FIFO 1
# define DT_CHR 2
# define DT_DIR 4
# define DT_BLK 6
# define DT_REG 8
# define DT_LNK 10
# define DT_SOCK 12
# define DT_WHT 14
#endif /*DT_UNKNOWN*/
static int cib_archive_filter(const struct dirent * a)
{
int rc = 0;
/* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */
struct stat s;
char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name);
if(stat(a_path, &s) != 0) {
rc = errno;
crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_rc_str(rc), rc);
rc = 0;
} else if ((s.st_mode & S_IFREG) != S_IFREG) {
unsigned char dtype;
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
dtype = a->d_type;
#else
switch (s.st_mode & S_IFMT) {
case S_IFREG: dtype = DT_REG; break;
case S_IFDIR: dtype = DT_DIR; break;
case S_IFCHR: dtype = DT_CHR; break;
case S_IFBLK: dtype = DT_BLK; break;
case S_IFLNK: dtype = DT_LNK; break;
case S_IFIFO: dtype = DT_FIFO; break;
case S_IFSOCK: dtype = DT_SOCK; break;
default: dtype = DT_UNKNOWN; break;
}
#endif
crm_trace("%s - wrong type (%d)", a->d_name, dtype);
} else if(strstr(a->d_name, "cib-") != a->d_name) {
crm_trace("%s - wrong prefix", a->d_name);
} else if (pcmk__ends_with_ext(a->d_name, ".sig")) {
crm_trace("%s - wrong suffix", a->d_name);
} else {
crm_debug("%s - candidate", a->d_name);
rc = 1;
}
free(a_path);
return rc;
}
static int cib_archive_sort(const struct dirent ** a, const struct dirent **b)
{
/* Order by creation date - most recently created file first */
int rc = 0;
struct stat buf;
time_t a_age = 0;
time_t b_age = 0;
char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name);
char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name);
if(stat(a_path, &buf) == 0) {
a_age = buf.st_ctime;
}
if(stat(b_path, &buf) == 0) {
b_age = buf.st_ctime;
}
free(a_path);
free(b_path);
if(a_age > b_age) {
rc = 1;
} else if(a_age < b_age) {
rc = -1;
}
crm_trace("%s (%lu) vs. %s (%lu) : %d",
a[0]->d_name, (unsigned long)a_age,
b[0]->d_name, (unsigned long)b_age, rc);
return rc;
}
xmlNode *
readCibXmlFile(const char *dir, const char *file, gboolean discard_status)
{
struct dirent **namelist = NULL;
int lpc = 0;
char *sigfile = NULL;
char *sigfilepath = NULL;
char *filename = NULL;
const char *name = NULL;
const char *value = NULL;
const char *validation = NULL;
const char *use_valgrind = pcmk__env_option(PCMK__ENV_VALGRIND_ENABLED);
xmlNode *root = NULL;
xmlNode *status = NULL;
sigfile = crm_strdup_printf("%s.sig", file);
if (pcmk__daemon_can_write(dir, file) == FALSE
|| pcmk__daemon_can_write(dir, sigfile) == FALSE) {
cib_status = -EACCES;
return NULL;
}
filename = crm_strdup_printf("%s/%s", dir, file);
sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile);
free(sigfile);
cib_status = pcmk_ok;
root = retrieveCib(filename, sigfilepath);
free(filename);
free(sigfilepath);
if (root == NULL) {
crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root);
lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort);
if (lpc < 0) {
crm_err("scandir(%s) failed: %s", cib_root, pcmk_rc_str(errno));
}
}
while (root == NULL && lpc > 1) {
crm_debug("Testing %d candidates", lpc);
lpc--;
filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
sigfile = crm_strdup_printf("%s.sig", filename);
crm_info("Reading cluster configuration file %s (digest: %s)",
filename, sigfile);
if (cib_file_read_and_verify(filename, sigfile, &root) < 0) {
crm_warn("Continuing but %s will NOT be used.", filename);
} else {
crm_notice("Continuing with last valid configuration archive: %s", filename);
}
free(namelist[lpc]);
free(filename);
free(sigfile);
}
free(namelist);
if (root == NULL) {
root = createEmptyCib(0);
crm_warn("Continuing with an empty configuration.");
}
if (cib_writes_enabled && use_valgrind &&
(crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) {
cib_writes_enabled = FALSE;
crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
}
status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE);
if (discard_status && status != NULL) {
/* strip out the status section if there is one */
free_xml(status);
status = NULL;
}
if (status == NULL) {
create_xml_node(root, XML_CIB_TAG_STATUS);
}
/* Do this before schema validation happens */
/* fill in some defaults */
- name = XML_ATTR_GENERATION_ADMIN;
+ name = PCMK_XA_ADMIN_EPOCH;
value = crm_element_value(root, name);
if (value == NULL) {
crm_warn("No value for %s was specified in the configuration.", name);
crm_warn("The recommended course of action is to shutdown,"
" run crm_verify and fix any errors it reports.");
crm_warn("We will default to zero and continue but may get"
" confused about which configuration to use if"
" multiple nodes are powered up at the same time.");
crm_xml_add_int(root, name, 0);
}
name = PCMK_XA_EPOCH;
value = crm_element_value(root, name);
if (value == NULL) {
crm_xml_add_int(root, name, 0);
}
name = XML_ATTR_NUMUPDATES;
value = crm_element_value(root, name);
if (value == NULL) {
crm_xml_add_int(root, name, 0);
}
// Unset (DC should set appropriate value)
xml_remove_prop(root, XML_ATTR_DC_UUID);
if (discard_status) {
crm_log_xml_trace(root, "[on-disk]");
}
validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
if (validate_xml(root, NULL, TRUE) == FALSE) {
crm_err("CIB does not validate with %s",
pcmk__s(validation, "no schema specified"));
cib_status = -pcmk_err_schema_validation;
} else if (validation == NULL) {
int version = 0;
update_validation(&root, &version, 0, FALSE, FALSE);
if (version > 0) {
crm_notice("Enabling %s validation on"
" the existing (sane) configuration", get_schema_name(version));
} else {
crm_err("CIB does not validate with any known schema");
cib_status = -pcmk_err_schema_validation;
}
}
return root;
}
gboolean
uninitializeCib(void)
{
xmlNode *tmp_cib = the_cib;
if (tmp_cib == NULL) {
crm_debug("The CIB has already been deallocated.");
return FALSE;
}
the_cib = NULL;
crm_debug("Deallocating the CIB.");
free_xml(tmp_cib);
crm_debug("The CIB has been deallocated.");
return TRUE;
}
/*
* This method will free the old CIB pointer on success and the new one
* on failure.
*/
int
activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
{
if (new_cib) {
xmlNode *saved_cib = the_cib;
CRM_ASSERT(new_cib != saved_cib);
the_cib = new_cib;
free_xml(saved_cib);
if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) {
crm_debug("Triggering CIB write for %s op", op);
mainloop_set_trigger(cib_writer);
}
return pcmk_ok;
}
crm_err("Ignoring invalid CIB");
if (the_cib) {
crm_warn("Reverting to last known CIB");
} else {
crm_crit("Could not write out new CIB and no saved version to revert to");
}
return -ENODATA;
}
static void
cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
{
const char *errmsg = "Could not write CIB to disk";
if ((exitcode != 0) && cib_writes_enabled) {
cib_writes_enabled = FALSE;
errmsg = "Disabling CIB disk writes after failure";
}
if ((signo == 0) && (exitcode == 0)) {
crm_trace("Disk write [%d] succeeded", (int) pid);
} else if (signo == 0) {
crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
} else {
crm_err("%s: process %d terminated with signal %d (%s)%s",
errmsg, (int) pid, signo, strsignal(signo),
(core? " and dumped core" : ""));
}
mainloop_trigger_complete(cib_writer);
}
int
write_cib_contents(gpointer p)
{
int exit_rc = pcmk_ok;
xmlNode *cib_local = NULL;
/* Make a copy of the CIB to write (possibly in a forked child) */
if (p) {
/* Synchronous write out */
cib_local = copy_xml(p);
} else {
int pid = 0;
int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0);
/* Turn it off before the fork() to avoid:
* - 2 processes writing to the same shared mem
* - the child needing to disable it
* (which would close it from underneath the parent)
* This way, the shared mem files are already closed
*/
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
pid = fork();
if (pid < 0) {
crm_err("Disabling disk writes after fork failure: %s", pcmk_rc_str(errno));
cib_writes_enabled = FALSE;
return FALSE;
}
if (pid) {
/* Parent */
mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete);
if (bb_state == QB_LOG_STATE_ENABLED) {
/* Re-enable now that it it safe */
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
}
return -1; /* -1 means 'still work to do' */
}
/* Asynchronous write-out after a fork() */
/* In theory, we can scribble on the_cib here and not affect the parent,
* but let's be safe anyway.
*/
cib_local = copy_xml(the_cib);
}
/* Write the CIB */
exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml");
/* A nonzero exit code will cause further writes to be disabled */
free_xml(cib_local);
if (p == NULL) {
crm_exit_t exit_code = CRM_EX_OK;
switch (exit_rc) {
case pcmk_ok:
exit_code = CRM_EX_OK;
break;
case pcmk_err_cib_modified:
exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest
break;
case pcmk_err_cib_backup: // Existing CIB couldn't be backed up
case pcmk_err_cib_save: // New CIB couldn't be saved
exit_code = CRM_EX_CANTCREAT;
break;
default:
exit_code = CRM_EX_ERROR;
break;
}
/* Use _exit() because exit() could affect the parent adversely */
_exit(exit_code);
}
return exit_rc;
}
diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c
index 15b4730e00..33c85af52e 100644
--- a/daemons/based/based_messages.c
+++ b/daemons/based/based_messages.c
@@ -1,524 +1,524 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/xml_internal.h>
#include <crm/cluster/internal.h>
#include <pacemaker-based.h>
/* 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, F_ORIG);
*answer = NULL;
if (crm_element_value(req, F_CIB_ISREPLY) == 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 = create_xml_node(NULL, "sync-me");
crm_info("Requesting re-sync from %s", (host? host : "all peers"));
sync_in_progress = 1;
crm_xml_add(sync_me, F_TYPE, "cib");
crm_xml_add(sync_me, F_CIB_OPERATION, PCMK__CIB_REQUEST_SYNC_TO_ONE);
crm_xml_add(sync_me, F_CIB_DELEGATED,
stand_alone? "localhost" : crm_cluster->uname);
send_cluster_message(host ? crm_get_peer(0, host) : NULL, crm_msg_cib, sync_me, FALSE);
free_xml(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, F_ORIG);
const char *seq = crm_element_value(req, F_CIB_PING_ID);
char *digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, CRM_FEATURE_SET);
crm_trace("Processing \"%s\" event %s from %s", op, seq, host);
*answer = create_xml_node(NULL, XML_CRM_TAG_PING);
crm_xml_add(*answer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
crm_xml_add(*answer, PCMK__XA_DIGEST, digest);
crm_xml_add(*answer, F_CIB_PING_ID, seq);
pcmk__if_tracing(
{
// Append additional detail so the receiver can log the differences
add_message_xml(*answer, F_CIB_CALLDATA, the_cib);
},
if (the_cib != NULL) {
// Always include at least the version details
xmlNode *shallow = create_xml_node(NULL,
(const char *) the_cib->name);
copy_in_properties(shallow, the_cib);
add_message_xml(*answer, F_CIB_CALLDATA, shallow);
free_xml(shallow);
}
);
crm_info("Reporting our current digest to %s: %s for %s.%s.%s",
host, digest,
- crm_element_value(existing_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(existing_cib, PCMK_XA_ADMIN_EPOCH),
crm_element_value(existing_cib, PCMK_XA_EPOCH),
crm_element_value(existing_cib, XML_ATTR_NUMUPDATES));
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, F_CIB_SCHEMA_MAX)) {
/* The originator of an upgrade request sends it to the DC, without
* F_CIB_SCHEMA_MAX. If an upgrade is needed, the DC re-broadcasts the
* request with F_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 {
int new_version = 0;
int current_version = 0;
xmlNode *scratch = copy_xml(existing_cib);
const char *host = crm_element_value(req, F_ORIG);
const char *value = crm_element_value(existing_cib,
PCMK_XA_VALIDATE_WITH);
const char *client_id = crm_element_value(req, F_CIB_CLIENTID);
const char *call_opts = crm_element_value(req, F_CIB_CALLOPTS);
const char *call_id = crm_element_value(req, F_CIB_CALLID);
crm_trace("Processing \"%s\" event", op);
if (value != NULL) {
current_version = get_schema_version(value);
}
rc = update_validation(&scratch, &new_version, 0, TRUE, TRUE);
if (new_version > current_version) {
xmlNode *up = create_xml_node(NULL, __func__);
rc = pcmk_ok;
crm_notice("Upgrade request from %s verified", host);
crm_xml_add(up, F_TYPE, "cib");
crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE);
crm_xml_add(up, F_CIB_SCHEMA_MAX, get_schema_name(new_version));
crm_xml_add(up, F_CIB_DELEGATED, host);
crm_xml_add(up, F_CIB_CLIENTID, client_id);
crm_xml_add(up, F_CIB_CALLOPTS, call_opts);
crm_xml_add(up, F_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 {
send_cluster_message(NULL, crm_msg_cib, up, FALSE);
}
free_xml(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 = pcmk__search_cluster_node_cache(0, host, NULL);
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 = create_xml_node(NULL, __func__);
crm_xml_add(up, F_TYPE, "cib");
crm_xml_add(up, F_CIB_OPERATION, PCMK__CIB_REQUEST_UPGRADE);
crm_xml_add(up, F_CIB_DELEGATED, host);
crm_xml_add(up, F_CIB_ISREPLY, host);
crm_xml_add(up, F_CIB_CLIENTID, client_id);
crm_xml_add(up, F_CIB_CALLOPTS, call_opts);
crm_xml_add(up, F_CIB_CALLID, call_id);
crm_xml_add_int(up, F_CIB_UPGRADE_RC, rc);
if (send_cluster_message(origin, crm_msg_cib, up, TRUE)
== FALSE) {
crm_warn("Could not send CIB upgrade result to %s", host);
}
free_xml(up);
}
}
free_xml(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) {
free_xml(*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);
free_xml(*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, XML_TAG_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[] = {
F_XML_TAGNAME,
F_TYPE,
F_CIB_CLIENTID,
F_CIB_CALLOPTS,
F_CIB_CALLID,
F_CIB_OPERATION,
F_CIB_ISREPLY,
F_CIB_SECTION,
F_CIB_HOST,
F_CIB_RC,
F_CIB_DELEGATED,
F_CIB_OBJID,
F_CIB_OBJTYPE,
F_CIB_EXISTING,
F_CIB_SEENCOUNT,
F_CIB_TIMEOUT,
F_CIB_GLOBAL_UPDATE,
F_CIB_CLIENTNAME,
F_CIB_USER,
F_CIB_NOTIFY_TYPE,
F_CIB_NOTIFY_ACTIVATE
};
xmlNode *copy = create_xml_node(NULL, "copy");
CRM_ASSERT(copy != NULL);
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, F_ORIG);
const char *op = crm_element_value(request, F_CIB_OPERATION);
xmlNode *replace_request = 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, F_CIB_ISREPLY, host);
}
if (all) {
xml_remove_prop(replace_request, F_CIB_HOST);
}
crm_xml_add(replace_request, F_CIB_OPERATION, PCMK__CIB_REQUEST_REPLACE);
crm_xml_add(replace_request, "original_" F_CIB_OPERATION, op);
pcmk__xe_set_bool_attr(replace_request, F_CIB_GLOBAL_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);
crm_xml_add(replace_request, PCMK__XA_DIGEST, digest);
add_message_xml(replace_request, F_CIB_CALLDATA, the_cib);
if (send_cluster_message
(all ? NULL : crm_get_peer(0, host), crm_msg_cib, replace_request, FALSE) == FALSE) {
result = -ENOTCONN;
}
free_xml(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, F_CIB_CLIENTID);
const char *origin = crm_element_value(req, F_ORIG);
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 *data = NULL;
const char *after_ver = NULL;
GList *schemas = NULL;
GList *already_included = NULL;
*answer = create_xml_node(NULL, PCMK__XA_SCHEMAS);
data = get_message_xml(req, F_CIB_CALLDATA);
if (data == NULL) {
crm_warn("No data specified in request");
return -EPROTO;
}
after_ver = crm_element_value(data, XML_ATTR_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, xml_latest_schema(), 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/controld/controld_join_dc.c b/daemons/controld/controld_join_dc.c
index 67487b8320..49da8607af 100644
--- a/daemons/controld/controld_join_dc.c
+++ b/daemons/controld/controld_join_dc.c
@@ -1,1034 +1,1034 @@
/*
* 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 <crm_internal.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/cluster.h>
#include <pacemaker-controld.h>
static char *max_generation_from = NULL;
static xmlNodePtr max_generation_xml = NULL;
/*!
* \internal
* \brief Nodes from which a CIB sync has failed since the peer joined
*
* This table is of the form (<tt>node_name -> join_id</tt>). \p node_name is
* the name of a client node from which a CIB \p sync_from() call has failed in
* \p do_dc_join_finalize() since the client joined the cluster as a peer.
* \p join_id is the ID of the join round in which the \p sync_from() failed,
* and is intended for use in nack log messages.
*/
static GHashTable *failed_sync_nodes = NULL;
void finalize_join_for(gpointer key, gpointer value, gpointer user_data);
void finalize_sync_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data);
gboolean check_join_state(enum crmd_fsa_state cur_state, const char *source);
/* Numeric counter used to identify join rounds (an unsigned int would be
* appropriate, except we get and set it in XML as int)
*/
static int current_join_id = 0;
/*!
* \internal
* \brief Destroy the hash table containing failed sync nodes
*/
void
controld_destroy_failed_sync_table(void)
{
if (failed_sync_nodes != NULL) {
g_hash_table_destroy(failed_sync_nodes);
failed_sync_nodes = NULL;
}
}
/*!
* \internal
* \brief Remove a node from the failed sync nodes table if present
*
* \param[in] node_name Node name to remove
*/
void
controld_remove_failed_sync_node(const char *node_name)
{
if (failed_sync_nodes != NULL) {
g_hash_table_remove(failed_sync_nodes, (gchar *) node_name);
}
}
/*!
* \internal
* \brief Add to a hash table a node whose CIB failed to sync
*
* \param[in] node_name Name of node whose CIB failed to sync
* \param[in] join_id Join round when the failure occurred
*/
static void
record_failed_sync_node(const char *node_name, gint join_id)
{
if (failed_sync_nodes == NULL) {
failed_sync_nodes = pcmk__strikey_table(g_free, NULL);
}
/* If the node is already in the table then we failed to nack it during the
* filter offer step
*/
CRM_LOG_ASSERT(g_hash_table_insert(failed_sync_nodes, g_strdup(node_name),
GINT_TO_POINTER(join_id)));
}
/*!
* \internal
* \brief Look up a node name in the failed sync table
*
* \param[in] node_name Name of node to look up
* \param[out] join_id Where to store the join ID of when the sync failed
*
* \return Standard Pacemaker return code. Specifically, \p pcmk_rc_ok if the
* node name was found, or \p pcmk_rc_node_unknown otherwise.
* \note \p *join_id is set to -1 if the node is not found.
*/
static int
lookup_failed_sync_node(const char *node_name, gint *join_id)
{
*join_id = -1;
if (failed_sync_nodes != NULL) {
gpointer result = g_hash_table_lookup(failed_sync_nodes,
(gchar *) node_name);
if (result != NULL) {
*join_id = GPOINTER_TO_INT(result);
return pcmk_rc_ok;
}
}
return pcmk_rc_node_unknown;
}
void
crm_update_peer_join(const char *source, crm_node_t * node, enum crm_join_phase phase)
{
enum crm_join_phase last = 0;
CRM_CHECK(node != NULL, return);
/* Remote nodes do not participate in joins */
if (pcmk_is_set(node->flags, crm_remote_node)) {
return;
}
last = node->join;
if(phase == last) {
crm_trace("Node %s join-%d phase is still %s "
CRM_XS " nodeid=%u source=%s",
node->uname, current_join_id, crm_join_phase_str(last),
node->id, source);
} else if ((phase <= crm_join_none) || (phase == (last + 1))) {
node->join = phase;
crm_trace("Node %s join-%d phase is now %s (was %s) "
CRM_XS " nodeid=%u source=%s",
node->uname, current_join_id, crm_join_phase_str(phase),
crm_join_phase_str(last), node->id, source);
} else {
crm_warn("Rejecting join-%d phase update for node %s because "
"can't go from %s to %s " CRM_XS " nodeid=%u source=%s",
current_join_id, node->uname, crm_join_phase_str(last),
crm_join_phase_str(phase), node->id, source);
}
}
static void
start_join_round(void)
{
GHashTableIter iter;
crm_node_t *peer = NULL;
crm_debug("Starting new join round join-%d", current_join_id);
g_hash_table_iter_init(&iter, crm_peer_cache);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
crm_update_peer_join(__func__, peer, crm_join_none);
}
if (max_generation_from != NULL) {
free(max_generation_from);
max_generation_from = NULL;
}
if (max_generation_xml != NULL) {
free_xml(max_generation_xml);
max_generation_xml = NULL;
}
controld_clear_fsa_input_flags(R_HAVE_CIB);
}
/*!
* \internal
* \brief Create a join message from the DC
*
* \param[in] join_op Join operation name
* \param[in] host_to Recipient of message
*/
static xmlNode *
create_dc_message(const char *join_op, const char *host_to)
{
xmlNode *msg = create_request(join_op, NULL, host_to, CRM_SYSTEM_CRMD,
CRM_SYSTEM_DC, NULL);
/* Identify which election this is a part of */
crm_xml_add_int(msg, F_CRM_JOIN_ID, current_join_id);
/* Add a field specifying whether the DC is shutting down. This keeps the
* joining node from fencing the old DC if it becomes the new DC.
*/
pcmk__xe_set_bool_attr(msg, F_CRM_DC_LEAVING,
pcmk_is_set(controld_globals.fsa_input_register,
R_SHUTDOWN));
return msg;
}
static void
join_make_offer(gpointer key, gpointer value, gpointer user_data)
{
xmlNode *offer = NULL;
crm_node_t *member = (crm_node_t *)value;
CRM_ASSERT(member != NULL);
if (crm_is_peer_active(member) == FALSE) {
crm_info("Not making join-%d offer to inactive node %s",
current_join_id,
(member->uname? member->uname : "with unknown name"));
if(member->expected == NULL && pcmk__str_eq(member->state, CRM_NODE_LOST, pcmk__str_casei)) {
/* You would think this unsafe, but in fact this plus an
* active resource is what causes it to be fenced.
*
* Yes, this does mean that any node that dies at the same
* time as the old DC and is not running resource (still)
* won't be fenced.
*
* I'm not happy about this either.
*/
pcmk__update_peer_expected(__func__, member, CRMD_JOINSTATE_DOWN);
}
return;
}
if (member->uname == NULL) {
crm_info("Not making join-%d offer to node uuid %s with unknown name",
current_join_id, member->uuid);
return;
}
if (controld_globals.membership_id != crm_peer_seq) {
controld_globals.membership_id = crm_peer_seq;
crm_info("Making join-%d offers based on membership event %llu",
current_join_id, crm_peer_seq);
}
if(user_data && member->join > crm_join_none) {
crm_info("Not making join-%d offer to already known node %s (%s)",
current_join_id, member->uname,
crm_join_phase_str(member->join));
return;
}
crm_update_peer_join(__func__, (crm_node_t*)member, crm_join_none);
offer = create_dc_message(CRM_OP_JOIN_OFFER, member->uname);
// Advertise our feature set so the joining node can bail if not compatible
crm_xml_add(offer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
crm_info("Sending join-%d offer to %s", current_join_id, member->uname);
send_cluster_message(member, crm_msg_crmd, offer, TRUE);
free_xml(offer);
crm_update_peer_join(__func__, member, crm_join_welcomed);
}
/* A_DC_JOIN_OFFER_ALL */
void
do_dc_join_offer_all(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
int count;
/* Reset everyone's status back to down or in_ccm in the CIB.
* Any nodes that are active in the CIB but not in the cluster membership
* will be seen as offline by the scheduler anyway.
*/
current_join_id++;
start_join_round();
update_dc(NULL);
if (cause == C_HA_MESSAGE && current_input == I_NODE_JOIN) {
crm_info("A new node joined the cluster");
}
g_hash_table_foreach(crm_peer_cache, join_make_offer, NULL);
count = crmd_join_phase_count(crm_join_welcomed);
crm_info("Waiting on join-%d requests from %d outstanding node%s",
current_join_id, count, pcmk__plural_s(count));
// Don't waste time by invoking the scheduler yet
}
/* A_DC_JOIN_OFFER_ONE */
void
do_dc_join_offer_one(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
crm_node_t *member;
ha_msg_input_t *welcome = NULL;
int count;
const char *join_to = NULL;
if (msg_data->data == NULL) {
crm_info("Making join-%d offers to any unconfirmed nodes "
"because an unknown node joined", current_join_id);
g_hash_table_foreach(crm_peer_cache, join_make_offer, &member);
check_join_state(cur_state, __func__);
return;
}
welcome = fsa_typed_data(fsa_dt_ha_msg);
if (welcome == NULL) {
// fsa_typed_data() already logged an error
return;
}
join_to = crm_element_value(welcome->msg, F_CRM_HOST_FROM);
if (join_to == NULL) {
crm_err("Can't make join-%d offer to unknown node", current_join_id);
return;
}
member = crm_get_peer(0, join_to);
/* It is possible that a node will have been sick or starting up when the
* original offer was made. However, it will either re-announce itself in
* due course, or we can re-store the original offer on the client.
*/
crm_update_peer_join(__func__, member, crm_join_none);
join_make_offer(NULL, member, NULL);
/* If the offer isn't to the local node, make an offer to the local node as
* well, to ensure the correct value for max_generation_from.
*/
if (strcasecmp(join_to, controld_globals.our_nodename) != 0) {
member = crm_get_peer(0, controld_globals.our_nodename);
join_make_offer(NULL, member, NULL);
}
/* This was a genuine join request; cancel any existing transition and
* invoke the scheduler.
*/
abort_transition(INFINITY, pcmk__graph_restart, "Node join", NULL);
count = crmd_join_phase_count(crm_join_welcomed);
crm_info("Waiting on join-%d requests from %d outstanding node%s",
current_join_id, count, pcmk__plural_s(count));
// Don't waste time by invoking the scheduler yet
}
static int
compare_int_fields(xmlNode * left, xmlNode * right, const char *field)
{
const char *elem_l = crm_element_value(left, field);
const char *elem_r = crm_element_value(right, field);
long long int_elem_l;
long long int_elem_r;
pcmk__scan_ll(elem_l, &int_elem_l, -1LL);
pcmk__scan_ll(elem_r, &int_elem_r, -1LL);
if (int_elem_l < int_elem_r) {
return -1;
} else if (int_elem_l > int_elem_r) {
return 1;
}
return 0;
}
/* A_DC_JOIN_PROCESS_REQ */
void
do_dc_join_filter_offer(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
xmlNode *generation = NULL;
int cmp = 0;
int join_id = -1;
int count = 0;
gint value = 0;
gboolean ack_nack_bool = TRUE;
ha_msg_input_t *join_ack = fsa_typed_data(fsa_dt_ha_msg);
const char *join_from = crm_element_value(join_ack->msg, F_CRM_HOST_FROM);
const char *ref = crm_element_value(join_ack->msg, F_CRM_REFERENCE);
const char *join_version = crm_element_value(join_ack->msg,
PCMK_XA_CRM_FEATURE_SET);
crm_node_t *join_node = NULL;
if (join_from == NULL) {
crm_err("Ignoring invalid join request without node name");
return;
}
join_node = crm_get_peer(0, join_from);
crm_element_value_int(join_ack->msg, F_CRM_JOIN_ID, &join_id);
if (join_id != current_join_id) {
crm_debug("Ignoring join-%d request from %s because we are on join-%d",
join_id, join_from, current_join_id);
check_join_state(cur_state, __func__);
return;
}
generation = join_ack->xml;
if (max_generation_xml != NULL && generation != NULL) {
int lpc = 0;
const char *attributes[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
for (lpc = 0; cmp == 0 && lpc < PCMK__NELEM(attributes); lpc++) {
cmp = compare_int_fields(max_generation_xml, generation, attributes[lpc]);
}
}
if (ref == NULL) {
ref = "none"; // for logging only
}
if (lookup_failed_sync_node(join_from, &value) == pcmk_rc_ok) {
crm_err("Rejecting join-%d request from node %s because we failed to "
"sync its CIB in join-%d " CRM_XS " ref=%s",
join_id, join_from, value, ref);
ack_nack_bool = FALSE;
} else if (!crm_is_peer_active(join_node)) {
if (match_down_event(join_from) != NULL) {
/* The join request was received after the node was fenced or
* otherwise shutdown in a way that we're aware of. No need to log
* an error in this rare occurrence; we know the client was recently
* shut down, and receiving a lingering in-flight request is not
* cause for alarm.
*/
crm_debug("Rejecting join-%d request from inactive node %s "
CRM_XS " ref=%s", join_id, join_from, ref);
} else {
crm_err("Rejecting join-%d request from inactive node %s "
CRM_XS " ref=%s", join_id, join_from, ref);
}
ack_nack_bool = FALSE;
} else if (generation == NULL) {
crm_err("Rejecting invalid join-%d request from node %s "
"missing CIB generation " CRM_XS " ref=%s",
join_id, join_from, ref);
ack_nack_bool = FALSE;
} else if ((join_version == NULL)
|| !feature_set_compatible(CRM_FEATURE_SET, join_version)) {
crm_err("Rejecting join-%d request from node %s because feature set %s"
" is incompatible with ours (%s) " CRM_XS " ref=%s",
join_id, join_from, (join_version? join_version : "pre-3.1.0"),
CRM_FEATURE_SET, ref);
ack_nack_bool = FALSE;
} else if (max_generation_xml == NULL) {
const char *validation = crm_element_value(generation,
PCMK_XA_VALIDATE_WITH);
if (get_schema_version(validation) < 0) {
crm_err("Rejecting join-%d request from %s (with first CIB "
"generation) due to unknown schema version %s "
CRM_XS " ref=%s",
join_id, join_from, validation, ref);
ack_nack_bool = FALSE;
} else {
crm_debug("Accepting join-%d request from %s (with first CIB "
"generation) " CRM_XS " ref=%s",
join_id, join_from, ref);
max_generation_xml = copy_xml(generation);
pcmk__str_update(&max_generation_from, join_from);
}
} else if ((cmp < 0)
|| ((cmp == 0)
&& pcmk__str_eq(join_from, controld_globals.our_nodename,
pcmk__str_casei))) {
const char *validation = crm_element_value(generation,
PCMK_XA_VALIDATE_WITH);
if (get_schema_version(validation) < 0) {
crm_err("Rejecting join-%d request from %s (with better CIB "
"generation than current best from %s) due to unknown "
"schema version %s " CRM_XS " ref=%s",
join_id, join_from, max_generation_from, validation, ref);
ack_nack_bool = FALSE;
} else {
crm_debug("Accepting join-%d request from %s (with better CIB "
"generation than current best from %s) " CRM_XS " ref=%s",
join_id, join_from, max_generation_from, ref);
crm_log_xml_debug(max_generation_xml, "Old max generation");
crm_log_xml_debug(generation, "New max generation");
free_xml(max_generation_xml);
max_generation_xml = copy_xml(join_ack->xml);
pcmk__str_update(&max_generation_from, join_from);
}
} else {
crm_debug("Accepting join-%d request from %s " CRM_XS " ref=%s",
join_id, join_from, ref);
}
if (!ack_nack_bool) {
if (compare_version(join_version, "3.17.0") < 0) {
/* Clients with CRM_FEATURE_SET < 3.17.0 may respawn infinitely
* after a nack message, don't send one
*/
crm_update_peer_join(__func__, join_node, crm_join_nack_quiet);
} else {
crm_update_peer_join(__func__, join_node, crm_join_nack);
}
pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_NACK);
} else {
crm_update_peer_join(__func__, join_node, crm_join_integrated);
pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_MEMBER);
}
count = crmd_join_phase_count(crm_join_integrated);
crm_debug("%d node%s currently integrated in join-%d",
count, pcmk__plural_s(count), join_id);
if (check_join_state(cur_state, __func__) == FALSE) {
// Don't waste time by invoking the scheduler yet
count = crmd_join_phase_count(crm_join_welcomed);
crm_debug("Waiting on join-%d requests from %d outstanding node%s",
join_id, count, pcmk__plural_s(count));
}
}
/* A_DC_JOIN_FINALIZE */
void
do_dc_join_finalize(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
char *sync_from = NULL;
int rc = pcmk_ok;
int count_welcomed = crmd_join_phase_count(crm_join_welcomed);
int count_finalizable = crmd_join_phase_count(crm_join_integrated)
+ crmd_join_phase_count(crm_join_nack)
+ crmd_join_phase_count(crm_join_nack_quiet);
/* This we can do straight away and avoid clients timing us out
* while we compute the latest CIB
*/
if (count_welcomed != 0) {
crm_debug("Waiting on join-%d requests from %d outstanding node%s "
"before finalizing join", current_join_id, count_welcomed,
pcmk__plural_s(count_welcomed));
crmd_join_phase_log(LOG_DEBUG);
/* crmd_fsa_stall(FALSE); Needed? */
return;
} else if (count_finalizable == 0) {
crm_debug("Finalization not needed for join-%d at the current time",
current_join_id);
crmd_join_phase_log(LOG_DEBUG);
check_join_state(controld_globals.fsa_state, __func__);
return;
}
controld_clear_fsa_input_flags(R_HAVE_CIB);
if (pcmk__str_eq(max_generation_from, controld_globals.our_nodename,
pcmk__str_null_matches|pcmk__str_casei)) {
controld_set_fsa_input_flags(R_HAVE_CIB);
}
if (!controld_globals.transition_graph->complete) {
crm_warn("Delaying join-%d finalization while transition in progress",
current_join_id);
crmd_join_phase_log(LOG_DEBUG);
crmd_fsa_stall(FALSE);
return;
}
if (pcmk_is_set(controld_globals.fsa_input_register, R_HAVE_CIB)) {
// Send our CIB out to everyone
pcmk__str_update(&sync_from, controld_globals.our_nodename);
crm_debug("Finalizing join-%d for %d node%s (sync'ing from local CIB)",
current_join_id, count_finalizable,
pcmk__plural_s(count_finalizable));
crm_log_xml_debug(max_generation_xml, "Requested CIB version");
} else {
// Ask for the agreed best CIB
pcmk__str_update(&sync_from, max_generation_from);
crm_notice("Finalizing join-%d for %d node%s (sync'ing CIB from %s)",
current_join_id, count_finalizable,
pcmk__plural_s(count_finalizable), sync_from);
crm_log_xml_notice(max_generation_xml, "Requested CIB version");
}
crmd_join_phase_log(LOG_DEBUG);
rc = controld_globals.cib_conn->cmds->sync_from(controld_globals.cib_conn,
sync_from, NULL, cib_none);
fsa_register_cib_callback(rc, sync_from, finalize_sync_callback);
}
void
free_max_generation(void)
{
free(max_generation_from);
max_generation_from = NULL;
free_xml(max_generation_xml);
max_generation_xml = NULL;
}
void
finalize_sync_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
CRM_LOG_ASSERT(-EPERM != rc);
if (rc != pcmk_ok) {
const char *sync_from = (const char *) user_data;
do_crm_log(((rc == -pcmk_err_old_data)? LOG_WARNING : LOG_ERR),
"Could not sync CIB from %s in join-%d: %s",
sync_from, current_join_id, pcmk_strerror(rc));
if (rc != -pcmk_err_old_data) {
record_failed_sync_node(sync_from, current_join_id);
}
/* restart the whole join process */
register_fsa_error_adv(C_FSA_INTERNAL, I_ELECTION_DC, NULL, NULL,
__func__);
} else if (!AM_I_DC) {
crm_debug("Sync'ed CIB for join-%d but no longer DC", current_join_id);
} else if (controld_globals.fsa_state != S_FINALIZE_JOIN) {
crm_debug("Sync'ed CIB for join-%d but no longer in S_FINALIZE_JOIN "
"(%s)", current_join_id,
fsa_state2string(controld_globals.fsa_state));
} else {
controld_set_fsa_input_flags(R_HAVE_CIB);
/* make sure dc_uuid is re-set to us */
if (!check_join_state(controld_globals.fsa_state, __func__)) {
int count_finalizable = 0;
count_finalizable = crmd_join_phase_count(crm_join_integrated)
+ crmd_join_phase_count(crm_join_nack)
+ crmd_join_phase_count(crm_join_nack_quiet);
crm_debug("Notifying %d node%s of join-%d results",
count_finalizable, pcmk__plural_s(count_finalizable),
current_join_id);
g_hash_table_foreach(crm_peer_cache, finalize_join_for, NULL);
}
}
}
static void
join_node_state_commit_callback(xmlNode *msg, int call_id, int rc,
xmlNode *output, void *user_data)
{
const char *node = user_data;
if (rc != pcmk_ok) {
fsa_data_t *msg_data = NULL; // for register_fsa_error() macro
crm_crit("join-%d node history update (via CIB call %d) for node %s "
"failed: %s",
current_join_id, call_id, node, pcmk_strerror(rc));
crm_log_xml_debug(msg, "failed");
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
crm_debug("join-%d node history update (via CIB call %d) for node %s "
"complete",
current_join_id, call_id, node);
check_join_state(controld_globals.fsa_state, __func__);
}
/* A_DC_JOIN_PROCESS_ACK */
void
do_dc_join_ack(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
int join_id = -1;
ha_msg_input_t *join_ack = fsa_typed_data(fsa_dt_ha_msg);
const char *op = crm_element_value(join_ack->msg, F_CRM_TASK);
char *join_from = crm_element_value_copy(join_ack->msg, F_CRM_HOST_FROM);
crm_node_t *peer = NULL;
enum controld_section_e section = controld_section_lrm;
char *xpath = NULL;
xmlNode *state = join_ack->xml;
xmlNode *execd_state = NULL;
cib_t *cib = controld_globals.cib_conn;
int rc = pcmk_ok;
// Sanity checks
if (join_from == NULL) {
crm_warn("Ignoring message received without node identification");
goto done;
}
if (op == NULL) {
crm_warn("Ignoring message received from %s without task", join_from);
goto done;
}
if (strcmp(op, CRM_OP_JOIN_CONFIRM)) {
crm_debug("Ignoring '%s' message from %s while waiting for '%s'",
op, join_from, CRM_OP_JOIN_CONFIRM);
goto done;
}
if (crm_element_value_int(join_ack->msg, F_CRM_JOIN_ID, &join_id) != 0) {
crm_warn("Ignoring join confirmation from %s without valid join ID",
join_from);
goto done;
}
peer = crm_get_peer(0, join_from);
if (peer->join != crm_join_finalized) {
crm_info("Ignoring out-of-sequence join-%d confirmation from %s "
"(currently %s not %s)",
join_id, join_from, crm_join_phase_str(peer->join),
crm_join_phase_str(crm_join_finalized));
goto done;
}
if (join_id != current_join_id) {
crm_err("Rejecting join-%d confirmation from %s "
"because currently on join-%d",
join_id, join_from, current_join_id);
crm_update_peer_join(__func__, peer, crm_join_nack);
goto done;
}
crm_update_peer_join(__func__, peer, crm_join_confirmed);
/* Update CIB with node's current executor state. A new transition will be
* triggered later, when the CIB manager notifies us of the change.
*
* The delete and modify requests are part of an atomic transaction.
*/
rc = cib->cmds->init_transaction(cib);
if (rc != pcmk_ok) {
goto done;
}
// Delete relevant parts of node's current executor state from CIB
if (pcmk_is_set(controld_globals.flags, controld_shutdown_lock_enabled)) {
section = controld_section_lrm_unlocked;
}
controld_node_state_deletion_strings(join_from, section, &xpath, NULL);
rc = cib->cmds->remove(cib, xpath, NULL,
cib_scope_local
|cib_xpath
|cib_multiple
|cib_transaction);
if (rc != pcmk_ok) {
goto done;
}
// Update CIB with node's latest known executor state
if (pcmk__str_eq(join_from, controld_globals.our_nodename,
pcmk__str_casei)) {
// Use the latest possible state if processing our own join ack
execd_state = controld_query_executor_state();
if (execd_state != NULL) {
crm_debug("Updating local node history for join-%d from query "
"result",
current_join_id);
state = execd_state;
} else {
crm_warn("Updating local node history from join-%d confirmation "
"because query failed",
current_join_id);
}
} else {
crm_debug("Updating node history for %s from join-%d confirmation",
join_from, current_join_id);
}
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, state,
cib_scope_local|cib_can_create|cib_transaction);
free_xml(execd_state);
if (rc != pcmk_ok) {
goto done;
}
// Commit the transaction
rc = cib->cmds->end_transaction(cib, true, cib_scope_local);
fsa_register_cib_callback(rc, join_from, join_node_state_commit_callback);
if (rc > 0) {
// join_from will be freed after callback
join_from = NULL;
rc = pcmk_ok;
}
done:
if (rc != pcmk_ok) {
crm_crit("join-%d node history update for node %s failed: %s",
current_join_id, join_from, pcmk_strerror(rc));
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
free(join_from);
free(xpath);
}
void
finalize_join_for(gpointer key, gpointer value, gpointer user_data)
{
xmlNode *acknak = NULL;
xmlNode *tmp1 = NULL;
crm_node_t *join_node = value;
const char *join_to = join_node->uname;
bool integrated = false;
switch (join_node->join) {
case crm_join_integrated:
integrated = true;
break;
case crm_join_nack:
case crm_join_nack_quiet:
break;
default:
crm_trace("Not updating non-integrated and non-nacked node %s (%s) "
"for join-%d", join_to,
crm_join_phase_str(join_node->join), current_join_id);
return;
}
/* Update the <node> element with the node's name and UUID, in case they
* weren't known before
*/
crm_trace("Updating node name and UUID in CIB for %s", join_to);
tmp1 = create_xml_node(NULL, XML_CIB_TAG_NODE);
crm_xml_add(tmp1, XML_ATTR_ID, crm_peer_uuid(join_node));
crm_xml_add(tmp1, XML_ATTR_UNAME, join_to);
fsa_cib_anon_update(XML_CIB_TAG_NODES, tmp1);
free_xml(tmp1);
if (join_node->join == crm_join_nack_quiet) {
crm_trace("Not sending nack message to node %s with feature set older "
"than 3.17.0", join_to);
return;
}
join_node = crm_get_peer(0, join_to);
if (!crm_is_peer_active(join_node)) {
/*
* NACK'ing nodes that the membership layer doesn't know about yet
* simply creates more churn
*
* Better to leave them waiting and let the join restart when
* the new membership event comes in
*
* All other NACKs (due to versions etc) should still be processed
*/
pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_PENDING);
return;
}
// Acknowledge or nack node's join request
crm_debug("%sing join-%d request from %s",
integrated? "Acknowledg" : "Nack", current_join_id, join_to);
acknak = create_dc_message(CRM_OP_JOIN_ACKNAK, join_to);
pcmk__xe_set_bool_attr(acknak, CRM_OP_JOIN_ACKNAK, integrated);
if (integrated) {
// No change needed for a nacked node
crm_update_peer_join(__func__, join_node, crm_join_finalized);
pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_MEMBER);
/* Iterate through the remote peer cache and add information on which
* node hosts each to the ACK message. This keeps new controllers in
* sync with what has already happened.
*/
if (crm_remote_peer_cache_size() != 0) {
GHashTableIter iter;
crm_node_t *node = NULL;
xmlNode *remotes = create_xml_node(acknak, XML_CIB_TAG_NODES);
g_hash_table_iter_init(&iter, crm_remote_peer_cache);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
xmlNode *remote = NULL;
if (!node->conn_host) {
continue;
}
remote = create_xml_node(remotes, XML_CIB_TAG_NODE);
pcmk__xe_set_props(remote,
XML_ATTR_ID, node->uname,
XML_CIB_TAG_STATE, node->state,
PCMK__XA_CONN_HOST, node->conn_host,
NULL);
}
}
}
send_cluster_message(join_node, crm_msg_crmd, acknak, TRUE);
free_xml(acknak);
return;
}
gboolean
check_join_state(enum crmd_fsa_state cur_state, const char *source)
{
static unsigned long long highest_seq = 0;
if (controld_globals.membership_id != crm_peer_seq) {
crm_debug("join-%d: Membership changed from %llu to %llu "
CRM_XS " highest=%llu state=%s for=%s",
current_join_id, controld_globals.membership_id, crm_peer_seq,
highest_seq, fsa_state2string(cur_state), source);
if(highest_seq < crm_peer_seq) {
/* Don't spam the FSA with duplicates */
highest_seq = crm_peer_seq;
register_fsa_input_before(C_FSA_INTERNAL, I_NODE_JOIN, NULL);
}
} else if (cur_state == S_INTEGRATION) {
if (crmd_join_phase_count(crm_join_welcomed) == 0) {
int count = crmd_join_phase_count(crm_join_integrated);
crm_debug("join-%d: Integration of %d peer%s complete "
CRM_XS " state=%s for=%s",
current_join_id, count, pcmk__plural_s(count),
fsa_state2string(cur_state), source);
register_fsa_input_before(C_FSA_INTERNAL, I_INTEGRATED, NULL);
return TRUE;
}
} else if (cur_state == S_FINALIZE_JOIN) {
if (!pcmk_is_set(controld_globals.fsa_input_register, R_HAVE_CIB)) {
crm_debug("join-%d: Delaying finalization until we have CIB "
CRM_XS " state=%s for=%s",
current_join_id, fsa_state2string(cur_state), source);
return TRUE;
} else if (crmd_join_phase_count(crm_join_welcomed) != 0) {
int count = crmd_join_phase_count(crm_join_welcomed);
crm_debug("join-%d: Still waiting on %d welcomed node%s "
CRM_XS " state=%s for=%s",
current_join_id, count, pcmk__plural_s(count),
fsa_state2string(cur_state), source);
crmd_join_phase_log(LOG_DEBUG);
} else if (crmd_join_phase_count(crm_join_integrated) != 0) {
int count = crmd_join_phase_count(crm_join_integrated);
crm_debug("join-%d: Still waiting on %d integrated node%s "
CRM_XS " state=%s for=%s",
current_join_id, count, pcmk__plural_s(count),
fsa_state2string(cur_state), source);
crmd_join_phase_log(LOG_DEBUG);
} else if (crmd_join_phase_count(crm_join_finalized) != 0) {
int count = crmd_join_phase_count(crm_join_finalized);
crm_debug("join-%d: Still waiting on %d finalized node%s "
CRM_XS " state=%s for=%s",
current_join_id, count, pcmk__plural_s(count),
fsa_state2string(cur_state), source);
crmd_join_phase_log(LOG_DEBUG);
} else {
crm_debug("join-%d: Complete " CRM_XS " state=%s for=%s",
current_join_id, fsa_state2string(cur_state), source);
register_fsa_input_later(C_FSA_INTERNAL, I_FINALIZED, NULL);
return TRUE;
}
}
return FALSE;
}
void
do_dc_join_final(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
crm_debug("Ensuring DC, quorum and node attributes are up-to-date");
crm_update_quorum(crm_have_quorum, TRUE);
}
int crmd_join_phase_count(enum crm_join_phase phase)
{
int count = 0;
crm_node_t *peer;
GHashTableIter iter;
g_hash_table_iter_init(&iter, crm_peer_cache);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
if(peer->join == phase) {
count++;
}
}
return count;
}
void crmd_join_phase_log(int level)
{
crm_node_t *peer;
GHashTableIter iter;
g_hash_table_iter_init(&iter, crm_peer_cache);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
do_crm_log(level, "join-%d: %s=%s", current_join_id, peer->uname,
crm_join_phase_str(peer->join));
}
}
diff --git a/daemons/fenced/fenced_cib.c b/daemons/fenced/fenced_cib.c
index d4bdcc6700..e52ca05a79 100644
--- a/daemons/fenced/fenced_cib.c
+++ b/daemons/fenced/fenced_cib.c
@@ -1,738 +1,738 @@
/*
* Copyright 2009-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 <crm_internal.h>
#include <stdbool.h>
#include <stdio.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/cluster/internal.h>
#include <crm/cib.h>
#include <crm/cib/internal.h>
#include <pacemaker-fenced.h>
static xmlNode *local_cib = NULL;
static cib_t *cib_api = NULL;
static bool have_cib_devices = FALSE;
/*!
* \internal
* \brief Check whether a node has a specific attribute name/value
*
* \param[in] node Name of node to check
* \param[in] name Name of an attribute to look for
* \param[in] value The value the named attribute needs to be set to in order to be considered a match
*
* \return TRUE if the locally cached CIB has the specified node attribute
*/
gboolean
node_has_attr(const char *node, const char *name, const char *value)
{
GString *xpath = NULL;
xmlNode *match;
CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL)
&& (value != NULL), return FALSE);
/* Search for the node's attributes in the CIB. While the schema allows
* multiple sets of instance attributes, and allows instance attributes to
* use id-ref to reference values elsewhere, that is intended for resources,
* so we ignore that here.
*/
xpath = g_string_sized_new(256);
pcmk__g_strcat(xpath,
"//" XML_CIB_TAG_NODES "/" XML_CIB_TAG_NODE
"[@" XML_ATTR_UNAME "='", node, "']/" XML_TAG_ATTR_SETS
"/" XML_CIB_TAG_NVPAIR
"[@" XML_NVPAIR_ATTR_NAME "='", name, "' "
"and @" XML_NVPAIR_ATTR_VALUE "='", value, "']", NULL);
match = get_xpath_object((const char *) xpath->str, local_cib, LOG_NEVER);
g_string_free(xpath, TRUE);
return (match != NULL);
}
static void
add_topology_level(xmlNode *match)
{
char *desc = NULL;
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
CRM_CHECK(match != NULL, return);
fenced_register_level(match, &desc, &result);
fenced_send_level_notification(STONITH_OP_LEVEL_ADD, &result, desc);
pcmk__reset_result(&result);
free(desc);
}
static void
topology_remove_helper(const char *node, int level)
{
char *desc = NULL;
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
crm_xml_add(data, F_STONITH_ORIGIN, __func__);
crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level);
crm_xml_add(data, XML_ATTR_STONITH_TARGET, node);
fenced_unregister_level(data, &desc, &result);
fenced_send_level_notification(STONITH_OP_LEVEL_DEL, &result, desc);
pcmk__reset_result(&result);
free_xml(data);
free(desc);
}
static void
remove_topology_level(xmlNode *match)
{
int index = 0;
char *key = NULL;
CRM_CHECK(match != NULL, return);
key = stonith_level_key(match, fenced_target_by_unknown);
crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
topology_remove_helper(key, index);
free(key);
}
static void
register_fencing_topology(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
remove_topology_level(match);
add_topology_level(match);
}
}
/* Fencing
<diff crm_feature_set="3.0.6">
<diff-removed>
<fencing-topology>
<fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
<fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
<fencing-level devices="disk,network" id="f-p2.1"/>
</fencing-topology>
</diff-removed>
<diff-added>
<fencing-topology>
<fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
<fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
<fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
</fencing-topology>
</diff-added>
</diff>
*/
void
fencing_topology_init(void)
{
xmlXPathObjectPtr xpathObj = NULL;
const char *xpath = "//" XML_TAG_FENCING_LEVEL;
crm_trace("Full topology refresh");
free_topology_list();
init_topology_list();
/* Grab everything */
xpathObj = xpath_search(local_cib, xpath);
register_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
}
static void
remove_cib_device(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
const char *rsc_id = NULL;
const char *standard = NULL;
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match != NULL) {
standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
}
if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
continue;
}
rsc_id = crm_element_value(match, XML_ATTR_ID);
stonith_device_remove(rsc_id, true);
}
}
#define XPATH_WATCHDOG_TIMEOUT "//" XML_CIB_TAG_NVPAIR \
"[@" XML_NVPAIR_ATTR_NAME "='" \
PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "']"
static void
update_stonith_watchdog_timeout_ms(xmlNode *cib)
{
long timeout_ms = 0;
xmlNode *stonith_watchdog_xml = NULL;
const char *value = NULL;
stonith_watchdog_xml = get_xpath_object(XPATH_WATCHDOG_TIMEOUT, cib,
LOG_NEVER);
if (stonith_watchdog_xml) {
value = crm_element_value(stonith_watchdog_xml, XML_NVPAIR_ATTR_VALUE);
}
if (value) {
timeout_ms = crm_get_msec(value);
}
if (timeout_ms < 0) {
timeout_ms = pcmk__auto_watchdog_timeout();
}
stonith_watchdog_timeout_ms = timeout_ms;
}
/*!
* \internal
* \brief Update all STONITH device definitions based on current CIB
*/
static void
cib_devices_update(void)
{
GHashTableIter iter;
stonith_device_t *device = NULL;
crm_info("Updating devices to version %s.%s.%s",
- crm_element_value(local_cib, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(local_cib, PCMK_XA_ADMIN_EPOCH),
crm_element_value(local_cib, PCMK_XA_EPOCH),
crm_element_value(local_cib, XML_ATTR_NUMUPDATES));
g_hash_table_iter_init(&iter, device_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
if (device->cib_registered) {
device->dirty = TRUE;
}
}
/* have list repopulated if cib has a watchdog-fencing-resource
TODO: keep a cached list for queries happening while we are refreshing
*/
g_list_free_full(stonith_watchdog_targets, free);
stonith_watchdog_targets = NULL;
fenced_scheduler_run(local_cib);
g_hash_table_iter_init(&iter, device_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) {
if (device->dirty) {
g_hash_table_iter_remove(&iter);
}
}
}
static void
update_cib_stonith_devices_v1(const char *event, xmlNode * msg)
{
const char *reason = "none";
gboolean needs_update = FALSE;
xmlXPathObjectPtr xpath_obj = NULL;
/* process new constraints */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION);
if (numXpathResults(xpath_obj) > 0) {
int max = numXpathResults(xpath_obj), lpc = 0;
/* Safest and simplest to always recompute */
needs_update = TRUE;
reason = "new location constraint";
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpath_obj, lpc);
crm_log_xml_trace(match, "new constraint");
}
}
freeXpathObject(xpath_obj);
/* process deletions */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_RESOURCE);
if (numXpathResults(xpath_obj) > 0) {
remove_cib_device(xpath_obj);
}
freeXpathObject(xpath_obj);
/* process additions */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_RESOURCE);
if (numXpathResults(xpath_obj) > 0) {
int max = numXpathResults(xpath_obj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
const char *rsc_id = NULL;
const char *standard = NULL;
xmlNode *match = getXpathResult(xpath_obj, lpc);
rsc_id = crm_element_value(match, XML_ATTR_ID);
standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
if (!pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
continue;
}
crm_trace("Fencing resource %s was added or modified", rsc_id);
reason = "new resource";
needs_update = TRUE;
}
}
freeXpathObject(xpath_obj);
if(needs_update) {
crm_info("Updating device list from CIB: %s", reason);
cib_devices_update();
}
}
static void
update_cib_stonith_devices_v2(const char *event, xmlNode * msg)
{
xmlNode *change = NULL;
char *reason = NULL;
bool needs_update = FALSE;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
for (change = pcmk__xml_first_child(patchset); change != NULL;
change = pcmk__xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
const char *shortpath = NULL;
if ((op == NULL) ||
(strcmp(op, "move") == 0) ||
strstr(xpath, "/"XML_CIB_TAG_STATUS)) {
continue;
} else if (pcmk__str_eq(op, "delete", pcmk__str_casei) && strstr(xpath, "/"XML_CIB_TAG_RESOURCE)) {
const char *rsc_id = NULL;
char *search = NULL;
char *mutable = NULL;
if (strstr(xpath, XML_TAG_ATTR_SETS) ||
strstr(xpath, XML_TAG_META_SETS)) {
needs_update = TRUE;
pcmk__str_update(&reason,
"(meta) attribute deleted from resource");
break;
}
pcmk__str_update(&mutable, xpath);
rsc_id = strstr(mutable, "primitive[@" XML_ATTR_ID "=\'");
if (rsc_id != NULL) {
rsc_id += strlen("primitive[@" XML_ATTR_ID "=\'");
search = strchr(rsc_id, '\'');
}
if (search != NULL) {
*search = 0;
stonith_device_remove(rsc_id, true);
/* watchdog_device_update called afterwards
to fall back to implicit definition if needed */
} else {
crm_warn("Ignoring malformed CIB update (resource deletion)");
}
free(mutable);
} else if (strstr(xpath, "/"XML_CIB_TAG_RESOURCES) ||
strstr(xpath, "/"XML_CIB_TAG_CONSTRAINTS) ||
strstr(xpath, "/"XML_CIB_TAG_RSCCONFIG)) {
shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath);
reason = crm_strdup_printf("%s %s", op, shortpath+1);
needs_update = TRUE;
break;
}
}
if(needs_update) {
crm_info("Updating device list from CIB: %s", reason);
cib_devices_update();
} else {
crm_trace("No updates for device list found in CIB");
}
free(reason);
}
static void
update_cib_stonith_devices(const char *event, xmlNode * msg)
{
int format = 1;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
CRM_ASSERT(patchset);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch(format) {
case 1:
update_cib_stonith_devices_v1(event, msg);
break;
case 2:
update_cib_stonith_devices_v2(event, msg);
break;
default:
crm_warn("Unknown patch format: %d", format);
}
}
static void
watchdog_device_update(void)
{
if (stonith_watchdog_timeout_ms > 0) {
if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) &&
!stonith_watchdog_targets) {
/* getting here watchdog-fencing enabled, no device there yet
and reason isn't stonith_watchdog_targets preventing that
*/
int rc;
xmlNode *xml;
xml = create_device_registration_xml(
STONITH_WATCHDOG_ID,
st_namespace_internal,
STONITH_WATCHDOG_AGENT,
NULL, /* stonith_device_register will add our
own name as PCMK_STONITH_HOST_LIST param
so we can skip that here
*/
NULL);
rc = stonith_device_register(xml, TRUE);
free_xml(xml);
if (rc != pcmk_ok) {
rc = pcmk_legacy2rc(rc);
exit_code = CRM_EX_FATAL;
crm_crit("Cannot register watchdog pseudo fence agent: %s",
pcmk_rc_str(rc));
stonith_shutdown(0);
}
}
} else if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) != NULL) {
/* be silent if no device - todo parameter to stonith_device_remove */
stonith_device_remove(STONITH_WATCHDOG_ID, true);
}
}
/*!
* \internal
* \brief Query the full CIB
*
* \return Standard Pacemaker return code
*/
static int
fenced_query_cib(void)
{
int rc = pcmk_ok;
crm_trace("Re-requesting full CIB");
rc = cib_api->cmds->query(cib_api, NULL, &local_cib,
cib_scope_local|cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
CRM_ASSERT(local_cib != NULL);
} else {
crm_err("Couldn't retrieve the CIB: %s " CRM_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
return rc;
}
static void
remove_fencing_topology(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if (match && crm_element_value(match, XML_DIFF_MARKER)) {
/* Deletion */
int index = 0;
char *target = stonith_level_key(match, fenced_target_by_unknown);
crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
if (target == NULL) {
crm_err("Invalid fencing target in element %s", ID(match));
} else if (index <= 0) {
crm_err("Invalid level for %s in element %s", target, ID(match));
} else {
topology_remove_helper(target, index);
}
/* } else { Deal with modifications during the 'addition' stage */
}
}
}
static void
update_fencing_topology(const char *event, xmlNode * msg)
{
int format = 1;
const char *xpath;
xmlXPathObjectPtr xpathObj = NULL;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
CRM_ASSERT(patchset);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if(format == 1) {
/* Process deletions (only) */
xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_FENCING_LEVEL;
xpathObj = xpath_search(msg, xpath);
remove_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
/* Process additions and changes */
xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_TAG_FENCING_LEVEL;
xpathObj = xpath_search(msg, xpath);
register_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
} else if(format == 2) {
xmlNode *change = NULL;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(patchset, add, del);
for (change = pcmk__xml_first_child(patchset); change != NULL;
change = pcmk__xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if(op == NULL) {
continue;
} else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) {
/* Change to a specific entry */
crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath);
if(strcmp(op, "move") == 0) {
continue;
} else if(strcmp(op, "create") == 0) {
add_topology_level(change->children);
} else if(strcmp(op, "modify") == 0) {
xmlNode *match = first_named_child(change, XML_DIFF_RESULT);
if(match) {
remove_topology_level(match->children);
add_topology_level(match->children);
}
} else if(strcmp(op, "delete") == 0) {
/* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */
crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
} else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) {
/* Change to the topology in general */
crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
} else if (strstr(xpath, "/" XML_CIB_TAG_CONFIGURATION)) {
/* Changes to the whole config section, possibly including the topology as a whild */
if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) {
crm_trace("Nothing for us in %s operation %d.%d.%d for %s.",
op, add[0], add[1], add[2], xpath);
} else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) {
crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
} else {
crm_trace("Nothing for us in %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
}
}
} else {
crm_warn("Unknown patch format: %d", format);
}
}
static void
update_cib_cache_cb(const char *event, xmlNode * msg)
{
long timeout_ms_saved = stonith_watchdog_timeout_ms;
bool need_full_refresh = false;
if(!have_cib_devices) {
crm_trace("Skipping updates until we get a full dump");
return;
} else if(msg == NULL) {
crm_trace("Missing %s update", event);
return;
}
/* Maintain a local copy of the CIB so that we have full access
* to device definitions, location constraints, and node attributes
*/
if (local_cib != NULL) {
int rc = pcmk_ok;
xmlNode *patchset = NULL;
crm_element_value_int(msg, F_CIB_RC, &rc);
if (rc != pcmk_ok) {
return;
}
patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
rc = xml_apply_patchset(local_cib, patchset, TRUE);
switch (rc) {
case pcmk_ok:
case -pcmk_err_old_data:
break;
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(local_cib);
local_cib = NULL;
break;
default:
crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(local_cib);
local_cib = NULL;
}
}
if (local_cib == NULL) {
if (fenced_query_cib() != pcmk_rc_ok) {
return;
}
need_full_refresh = true;
}
pcmk__refresh_node_caches_from_cib(local_cib);
update_stonith_watchdog_timeout_ms(local_cib);
if (timeout_ms_saved != stonith_watchdog_timeout_ms) {
need_full_refresh = true;
}
if (need_full_refresh) {
fencing_topology_init();
cib_devices_update();
} else {
// Partial refresh
update_fencing_topology(event, msg);
update_cib_stonith_devices(event, msg);
}
watchdog_device_update();
}
static void
init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
crm_info("Updating device list from CIB");
have_cib_devices = TRUE;
local_cib = copy_xml(output);
pcmk__refresh_node_caches_from_cib(local_cib);
update_stonith_watchdog_timeout_ms(local_cib);
fencing_topology_init();
cib_devices_update();
watchdog_device_update();
}
static void
cib_connection_destroy(gpointer user_data)
{
if (stonith_shutdown_flag) {
crm_info("Connection to the CIB manager closed");
return;
} else {
crm_crit("Lost connection to the CIB manager, shutting down");
}
if (cib_api) {
cib_api->cmds->signoff(cib_api);
}
stonith_shutdown(0);
}
/*!
* \internal
* \brief Disconnect from CIB manager
*/
void
fenced_cib_cleanup(void)
{
if (cib_api != NULL) {
cib_api->cmds->del_notify_callback(cib_api, T_CIB_DIFF_NOTIFY,
update_cib_cache_cb);
cib__clean_up_connection(&cib_api);
}
free_xml(local_cib);
local_cib = NULL;
}
void
setup_cib(void)
{
int rc, retries = 0;
cib_api = cib_new();
if (cib_api == NULL) {
crm_err("No connection to the CIB manager");
return;
}
do {
sleep(retries);
rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command);
} while (rc == -ENOTCONN && ++retries < 5);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc);
} else if (pcmk_ok !=
cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) {
crm_err("Could not set CIB notification callback");
} else {
rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local);
cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb",
init_cib_cache_cb);
cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
crm_info("Watching for fencing topology changes");
}
}
diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h
index 27b2dbbb91..1b05d322c3 100644
--- a/include/crm/msg_xml.h
+++ b/include/crm/msg_xml.h
@@ -1,467 +1,468 @@
/*
* 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.
*/
#ifndef PCMK__CRM_MSG_XML__H
# define PCMK__CRM_MSG_XML__H
# include <crm/common/xml.h>
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include <crm/msg_xml_compat.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* This file defines constants for various XML syntax (mainly element and
* attribute names).
*
* For consistency, new constants should start with "PCMK_", followed by "XE"
* for XML element names, "XA" for XML attribute names, and "META" for meta
* attribute names. Old names that don't follow this policy should eventually be
* deprecated and replaced with names that do.
*/
/*
* XML elements
*/
#define PCMK_XE_DATE_EXPRESSION "date_expression"
#define PCMK_XE_OP_EXPRESSION "op_expression"
/* This has been deprecated as a CIB element (an alias for <clone> with
* "promotable" set to "true") since 2.0.0.
*/
#define PCMK_XE_PROMOTABLE_LEGACY "master"
#define PCMK_XE_RSC_EXPRESSION "rsc_expression"
/*
* XML attributes
*/
+#define PCMK_XA_ADMIN_EPOCH "admin_epoch"
#define PCMK_XA_CRM_FEATURE_SET "crm_feature_set"
#define PCMK_XA_EPOCH "epoch"
#define PCMK_XA_FORMAT "format"
#define PCMK_XA_HAVE_QUORUM "have-quorum"
#define PCMK_XA_NO_QUORUM_PANIC "no-quorum-panic"
#define PCMK_XA_VALIDATE_WITH "validate-with"
/* These have been deprecated as CIB <clone> element attributes (aliases for
* "promoted-max" and "promoted-node-max") since 2.0.0.
*/
#define PCMK_XA_PROMOTED_MAX_LEGACY "master-max"
#define PCMK_XA_PROMOTED_NODE_MAX_LEGACY "master-node-max"
/*
* Meta attributes
*/
#define PCMK_META_CLONE_MAX "clone-max"
#define PCMK_META_CLONE_MIN "clone-min"
#define PCMK_META_CLONE_NODE_MAX "clone-node-max"
#define PCMK_META_ENABLED "enabled"
#define PCMK_META_FAILURE_TIMEOUT "failure-timeout"
#define PCMK_META_MIGRATION_THRESHOLD "migration-threshold"
#define PCMK_META_PROMOTED_MAX "promoted-max"
#define PCMK_META_PROMOTED_NODE_MAX "promoted-node-max"
/*
* Older constants that don't follow current naming
*/
# ifndef F_ORIG
# define F_ORIG "src"
# endif
# ifndef F_SEQ
# define F_SEQ "seq"
# endif
# ifndef F_SUBTYPE
# define F_SUBTYPE "subt"
# endif
# ifndef F_TYPE
# define F_TYPE "t"
# endif
# ifndef F_CLIENTNAME
# define F_CLIENTNAME "cn"
# endif
# ifndef F_XML_TAGNAME
# define F_XML_TAGNAME "__name__"
# endif
# ifndef T_CRM
# define T_CRM "crmd"
# endif
# ifndef T_ATTRD
# define T_ATTRD "attrd"
# endif
# define CIB_OPTIONS_FIRST "cib-bootstrap-options"
# define F_CRM_DATA "crm_xml"
# define F_CRM_TASK "crm_task"
# define F_CRM_HOST_TO "crm_host_to"
# define F_CRM_MSG_TYPE F_SUBTYPE
# define F_CRM_SYS_TO "crm_sys_to"
# define F_CRM_SYS_FROM "crm_sys_from"
# define F_CRM_HOST_FROM F_ORIG
# define F_CRM_REFERENCE XML_ATTR_REFERENCE
# define F_CRM_VERSION XML_ATTR_VERSION
# define F_CRM_ORIGIN "origin"
# define F_CRM_USER "crm_user"
# define F_CRM_JOIN_ID "join_id"
# define F_CRM_DC_LEAVING "dc-leaving"
# define F_CRM_ELECTION_ID "election-id"
# define F_CRM_ELECTION_AGE_S "election-age-sec"
# define F_CRM_ELECTION_AGE_US "election-age-nano-sec"
# define F_CRM_ELECTION_OWNER "election-owner"
# define F_CRM_TGRAPH "crm-tgraph-file"
# define F_CRM_TGRAPH_INPUT "crm-tgraph-in"
# define F_CRM_THROTTLE_MODE "crm-limit-mode"
# define F_CRM_THROTTLE_MAX "crm-limit-max"
/*---- Common tags/attrs */
# define XML_DIFF_MARKER "__crm_diff_marker__"
# define XML_TAG_CIB "cib"
# define XML_TAG_FAILED "failed"
# define XML_ATTR_GENERATION_ADMIN "admin_epoch"
# define XML_ATTR_NUMUPDATES "num_updates"
# define XML_ATTR_TIMEOUT "timeout"
# define XML_ATTR_ORIGIN "crm-debug-origin"
# define XML_ATTR_TSTAMP "crm-timestamp"
# define XML_CIB_ATTR_WRITTEN "cib-last-written"
# define XML_ATTR_VERSION "version"
# define XML_ATTR_DESC "description"
# define XML_ATTR_ID "id"
# define XML_ATTR_NAME "name"
# define XML_ATTR_IDREF "id-ref"
# define XML_ATTR_ID_LONG "long-id"
# define XML_ATTR_TYPE "type"
# define XML_ATTR_OP "op"
# define XML_ATTR_DC_UUID "dc-uuid"
# define XML_ATTR_UPDATE_ORIG "update-origin"
# define XML_ATTR_UPDATE_CLIENT "update-client"
# define XML_ATTR_UPDATE_USER "update-user"
# define XML_BOOLEAN_TRUE "true"
# define XML_BOOLEAN_FALSE "false"
# define XML_BOOLEAN_YES XML_BOOLEAN_TRUE
# define XML_BOOLEAN_NO XML_BOOLEAN_FALSE
# define XML_TAG_OPTIONS "options"
/*---- top level tags/attrs */
# define XML_ATTR_REQUEST "request"
# define XML_ATTR_RESPONSE "response"
# define XML_ATTR_UNAME "uname"
# define XML_ATTR_REFERENCE "reference"
# define XML_CRM_TAG_PING "ping_response"
# define XML_PING_ATTR_STATUS "result"
# define XML_PING_ATTR_SYSFROM "crm_subsystem"
# define XML_PING_ATTR_CRMDSTATE "crmd_state"
# define XML_PING_ATTR_PACEMAKERDSTATE "pacemakerd_state"
# define XML_PING_ATTR_PACEMAKERDSTATE_INIT "init"
# define XML_PING_ATTR_PACEMAKERDSTATE_STARTINGDAEMONS "starting_daemons"
# define XML_PING_ATTR_PACEMAKERDSTATE_WAITPING "wait_for_ping"
# define XML_PING_ATTR_PACEMAKERDSTATE_RUNNING "running"
# define XML_PING_ATTR_PACEMAKERDSTATE_SHUTTINGDOWN "shutting_down"
# define XML_PING_ATTR_PACEMAKERDSTATE_SHUTDOWNCOMPLETE "shutdown_complete"
# define XML_PING_ATTR_PACEMAKERDSTATE_REMOTE "remote"
# define XML_FAIL_TAG_CIB "failed_update"
# define XML_FAILCIB_ATTR_ID "id"
# define XML_FAILCIB_ATTR_OBJTYPE "object_type"
# define XML_FAILCIB_ATTR_OP "operation"
# define XML_FAILCIB_ATTR_REASON "reason"
/*---- CIB specific tags/attrs */
# define XML_CIB_TAG_SECTION_ALL "all"
# define XML_CIB_TAG_CONFIGURATION "configuration"
# define XML_CIB_TAG_STATUS "status"
# define XML_CIB_TAG_RESOURCES "resources"
# define XML_CIB_TAG_NODES "nodes"
# define XML_CIB_TAG_CONSTRAINTS "constraints"
# define XML_CIB_TAG_CRMCONFIG "crm_config"
# define XML_CIB_TAG_OPCONFIG "op_defaults"
# define XML_CIB_TAG_RSCCONFIG "rsc_defaults"
# define XML_CIB_TAG_ACLS "acls"
# define XML_CIB_TAG_ALERTS "alerts"
# define XML_CIB_TAG_ALERT "alert"
# define XML_CIB_TAG_ALERT_RECIPIENT "recipient"
# define XML_CIB_TAG_ALERT_SELECT "select"
# define XML_CIB_TAG_ALERT_ATTRIBUTES "select_attributes"
# define XML_CIB_TAG_ALERT_FENCING "select_fencing"
# define XML_CIB_TAG_ALERT_NODES "select_nodes"
# define XML_CIB_TAG_ALERT_RESOURCES "select_resources"
# define XML_CIB_TAG_ALERT_ATTR "attribute"
# define XML_CIB_TAG_STATE "node_state"
# define XML_CIB_TAG_NODE "node"
# define XML_CIB_TAG_NVPAIR "nvpair"
# define XML_CIB_TAG_PROPSET "cluster_property_set"
# define XML_TAG_ATTR_SETS "instance_attributes"
# define XML_TAG_META_SETS "meta_attributes"
# define XML_TAG_ATTRS "attributes"
# define XML_TAG_PARAMS "parameters"
# define XML_TAG_PARAM "param"
# define XML_TAG_UTILIZATION "utilization"
# define XML_TAG_RESOURCE_REF "resource_ref"
# define XML_CIB_TAG_RESOURCE "primitive"
# define XML_CIB_TAG_GROUP "group"
# define XML_CIB_TAG_INCARNATION "clone"
# define XML_CIB_TAG_CONTAINER "bundle"
# define XML_CIB_TAG_RSC_TEMPLATE "template"
# define XML_RSC_ATTR_TARGET "container-attribute-target"
# define XML_RSC_ATTR_RESTART "restart-type"
# define XML_RSC_ATTR_ORDERED "ordered"
# define XML_RSC_ATTR_INTERLEAVE "interleave"
# define XML_RSC_ATTR_INCARNATION "clone"
# define XML_RSC_ATTR_PROMOTABLE "promotable"
# define XML_RSC_ATTR_MANAGED "is-managed"
# define XML_RSC_ATTR_TARGET_ROLE "target-role"
# define XML_RSC_ATTR_UNIQUE "globally-unique"
# define XML_RSC_ATTR_NOTIFY "notify"
# define XML_RSC_ATTR_STICKINESS "resource-stickiness"
# define XML_RSC_ATTR_MULTIPLE "multiple-active"
# define XML_RSC_ATTR_REQUIRES "requires"
# define XML_RSC_ATTR_CONTAINER "container"
# define XML_RSC_ATTR_INTERNAL_RSC "internal_rsc"
# define XML_RSC_ATTR_MAINTENANCE "maintenance"
# define XML_RSC_ATTR_REMOTE_NODE "remote-node"
# define XML_RSC_ATTR_CLEAR_OP "clear_failure_op"
# define XML_RSC_ATTR_CLEAR_INTERVAL "clear_failure_interval"
# define XML_RSC_ATTR_REMOTE_RA_ADDR "addr"
# define XML_RSC_ATTR_REMOTE_RA_SERVER "server"
# define XML_RSC_ATTR_REMOTE_RA_PORT "port"
# define XML_RSC_ATTR_CRITICAL "critical"
# define XML_REMOTE_ATTR_RECONNECT_INTERVAL "reconnect_interval"
# define XML_OP_ATTR_ON_FAIL "on-fail"
# define XML_OP_ATTR_START_DELAY "start-delay"
# define XML_OP_ATTR_ALLOW_MIGRATE "allow-migrate"
# define XML_OP_ATTR_ORIGIN "interval-origin"
# define XML_OP_ATTR_PENDING "record-pending"
# define XML_OP_ATTR_DIGESTS_ALL "digests-all"
# define XML_OP_ATTR_DIGESTS_SECURE "digests-secure"
# define XML_CIB_TAG_LRM "lrm"
# define XML_LRM_TAG_RESOURCES "lrm_resources"
# define XML_LRM_TAG_RESOURCE "lrm_resource"
# define XML_LRM_TAG_RSC_OP "lrm_rsc_op"
# define XML_AGENT_ATTR_CLASS "class"
# define XML_AGENT_ATTR_PROVIDER "provider"
//! \deprecated Do not use (will be removed in a future release)
# define XML_CIB_ATTR_REPLACE "replace"
# define XML_CIB_ATTR_PRIORITY "priority"
# define XML_NODE_IS_REMOTE "remote_node"
# define XML_NODE_IS_FENCED "node_fenced"
# define XML_NODE_IS_MAINTENANCE "node_in_maintenance"
# define XML_CIB_ATTR_SHUTDOWN "shutdown"
/* Aside from being an old name for the executor, LRM is a misnomer here because
* the controller and scheduler use these to track actions, which are not always
* executor operations.
*/
// XML attribute that takes interval specification (user-facing configuration)
# define XML_LRM_ATTR_INTERVAL "interval"
// XML attribute that takes interval in milliseconds (daemon APIs)
// (identical value as above, but different constant allows clearer code intent)
# define XML_LRM_ATTR_INTERVAL_MS XML_LRM_ATTR_INTERVAL
# define XML_LRM_ATTR_TASK "operation"
# define XML_LRM_ATTR_TASK_KEY "operation_key"
# define XML_LRM_ATTR_TARGET "on_node"
# define XML_LRM_ATTR_TARGET_UUID "on_node_uuid"
/*! Actions to be executed on Pacemaker Remote nodes are routed through the
* controller on the cluster node hosting the remote connection. That cluster
* node is considered the router node for the action.
*/
# define XML_LRM_ATTR_ROUTER_NODE "router_node"
# define XML_LRM_ATTR_RSCID "rsc-id"
# define XML_LRM_ATTR_OPSTATUS "op-status"
# define XML_LRM_ATTR_RC "rc-code"
# define XML_LRM_ATTR_CALLID "call-id"
# define XML_LRM_ATTR_OP_DIGEST "op-digest"
# define XML_LRM_ATTR_OP_RESTART "op-force-restart"
# define XML_LRM_ATTR_OP_SECURE "op-secure-params"
# define XML_LRM_ATTR_RESTART_DIGEST "op-restart-digest"
# define XML_LRM_ATTR_SECURE_DIGEST "op-secure-digest"
# define XML_LRM_ATTR_EXIT_REASON "exit-reason"
# define XML_RSC_OP_LAST_CHANGE "last-rc-change"
# define XML_RSC_OP_T_EXEC "exec-time"
# define XML_RSC_OP_T_QUEUE "queue-time"
# define XML_LRM_ATTR_MIGRATE_SOURCE "migrate_source"
# define XML_LRM_ATTR_MIGRATE_TARGET "migrate_target"
# define XML_TAG_GRAPH "transition_graph"
# define XML_GRAPH_TAG_RSC_OP "rsc_op"
# define XML_GRAPH_TAG_PSEUDO_EVENT "pseudo_event"
# define XML_GRAPH_TAG_CRM_EVENT "crm_event"
# define XML_GRAPH_TAG_DOWNED "downed"
# define XML_GRAPH_TAG_MAINTENANCE "maintenance"
# define XML_TAG_RULE "rule"
# define XML_RULE_ATTR_SCORE "score"
# define XML_RULE_ATTR_SCORE_ATTRIBUTE "score-attribute"
# define XML_RULE_ATTR_ROLE "role"
# define XML_RULE_ATTR_BOOLEAN_OP "boolean-op"
# define XML_TAG_EXPRESSION "expression"
# define XML_EXPR_ATTR_ATTRIBUTE "attribute"
# define XML_EXPR_ATTR_OPERATION "operation"
# define XML_EXPR_ATTR_VALUE "value"
# define XML_EXPR_ATTR_TYPE "type"
# define XML_EXPR_ATTR_VALUE_SOURCE "value-source"
# define XML_CONS_TAG_RSC_DEPEND "rsc_colocation"
# define XML_CONS_TAG_RSC_ORDER "rsc_order"
# define XML_CONS_TAG_RSC_LOCATION "rsc_location"
# define XML_CONS_TAG_RSC_TICKET "rsc_ticket"
# define XML_CONS_TAG_RSC_SET "resource_set"
# define XML_CONS_ATTR_SYMMETRICAL "symmetrical"
# define XML_LOCATION_ATTR_DISCOVERY "resource-discovery"
# define XML_COLOC_ATTR_SOURCE "rsc"
# define XML_COLOC_ATTR_SOURCE_ROLE "rsc-role"
# define XML_COLOC_ATTR_TARGET "with-rsc"
# define XML_COLOC_ATTR_TARGET_ROLE "with-rsc-role"
# define XML_COLOC_ATTR_NODE_ATTR "node-attribute"
# define XML_COLOC_ATTR_INFLUENCE "influence"
//! \deprecated Deprecated since 2.1.5
# define XML_COLOC_ATTR_SOURCE_INSTANCE "rsc-instance"
//! \deprecated Deprecated since 2.1.5
# define XML_COLOC_ATTR_TARGET_INSTANCE "with-rsc-instance"
# define XML_LOC_ATTR_SOURCE "rsc"
# define XML_LOC_ATTR_SOURCE_PATTERN "rsc-pattern"
# define XML_ORDER_ATTR_FIRST "first"
# define XML_ORDER_ATTR_THEN "then"
# define XML_ORDER_ATTR_FIRST_ACTION "first-action"
# define XML_ORDER_ATTR_THEN_ACTION "then-action"
# define XML_ORDER_ATTR_KIND "kind"
//! \deprecated Deprecated since 2.1.5
# define XML_ORDER_ATTR_FIRST_INSTANCE "first-instance"
//! \deprecated Deprecated since 2.1.5
# define XML_ORDER_ATTR_THEN_INSTANCE "then-instance"
# define XML_TICKET_ATTR_TICKET "ticket"
# define XML_TICKET_ATTR_LOSS_POLICY "loss-policy"
# define XML_NVPAIR_ATTR_NAME "name"
# define XML_NVPAIR_ATTR_VALUE "value"
# define XML_NODE_ATTR_RSC_DISCOVERY "resource-discovery-enabled"
# define XML_ALERT_ATTR_PATH "path"
# define XML_ALERT_ATTR_TIMEOUT "timeout"
# define XML_ALERT_ATTR_TSTAMP_FORMAT "timestamp-format"
# define XML_ALERT_ATTR_REC_VALUE "value"
# define XML_CIB_TAG_GENERATION_TUPPLE "generation_tuple"
# define XML_ATTR_TRANSITION_MAGIC "transition-magic"
# define XML_ATTR_TRANSITION_KEY "transition-key"
# define XML_ATTR_TE_NOWAIT "op_no_wait"
# define XML_ATTR_TE_TARGET_RC "op_target_rc"
# define XML_TAG_TRANSIENT_NODEATTRS "transient_attributes"
//! \deprecated Do not use (will be removed in a future release)
# define XML_TAG_DIFF_ADDED "diff-added"
//! \deprecated Do not use (will be removed in a future release)
# define XML_TAG_DIFF_REMOVED "diff-removed"
# define XML_ACL_TAG_USER "acl_target"
# define XML_ACL_TAG_USERv1 "acl_user"
# define XML_ACL_TAG_GROUP "acl_group"
# define XML_ACL_TAG_ROLE "acl_role"
# define XML_ACL_TAG_PERMISSION "acl_permission"
# define XML_ACL_TAG_ROLE_REF "role"
# define XML_ACL_TAG_ROLE_REFv1 "role_ref"
# define XML_ACL_ATTR_KIND "kind"
# define XML_ACL_TAG_READ "read"
# define XML_ACL_TAG_WRITE "write"
# define XML_ACL_TAG_DENY "deny"
# define XML_ACL_ATTR_REF "reference"
# define XML_ACL_ATTR_REFv1 "ref"
# define XML_ACL_ATTR_TAG "object-type"
# define XML_ACL_ATTR_TAGv1 "tag"
# define XML_ACL_ATTR_XPATH "xpath"
# define XML_ACL_ATTR_ATTRIBUTE "attribute"
# define XML_CIB_TAG_TICKETS "tickets"
# define XML_CIB_TAG_TICKET_STATE "ticket_state"
# define XML_CIB_TAG_TAGS "tags"
# define XML_CIB_TAG_TAG "tag"
# define XML_CIB_TAG_OBJ_REF "obj_ref"
# define XML_TAG_FENCING_TOPOLOGY "fencing-topology"
# define XML_TAG_FENCING_LEVEL "fencing-level"
# define XML_ATTR_STONITH_INDEX "index"
# define XML_ATTR_STONITH_TARGET "target"
# define XML_ATTR_STONITH_TARGET_VALUE "target-value"
# define XML_ATTR_STONITH_TARGET_PATTERN "target-pattern"
# define XML_ATTR_STONITH_TARGET_ATTRIBUTE "target-attribute"
# define XML_ATTR_STONITH_DEVICES "devices"
# define XML_TAG_DIFF "diff"
# define XML_DIFF_VERSION "version"
# define XML_DIFF_VSOURCE "source"
# define XML_DIFF_VTARGET "target"
# define XML_DIFF_CHANGE "change"
# define XML_DIFF_LIST "change-list"
# define XML_DIFF_ATTR "change-attr"
# define XML_DIFF_RESULT "change-result"
# define XML_DIFF_OP "operation"
# define XML_DIFF_PATH "path"
# define XML_DIFF_POSITION "position"
# define ID(x) crm_element_value(x, XML_ATTR_ID)
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index b6d4e33aa9..fdaaf2c3e9 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,1178 +1,1177 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-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 <crm_internal.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#define CIB_SERIES "cib"
#define CIB_SERIES_MAX 100
#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
created with hard links
*/
#define CIB_LIVE_NAME CIB_SERIES ".xml"
// key: client ID (const char *) -> value: client (cib_t *)
static GHashTable *client_table = NULL;
enum cib_file_flags {
cib_file_flag_dirty = (1 << 0),
cib_file_flag_live = (1 << 1),
};
typedef struct cib_file_opaque_s {
char *id;
char *filename;
uint32_t flags; // Group of enum cib_file_flags
xmlNode *cib_xml;
} cib_file_opaque_t;
static int cib_file_process_commit_transaction(const char *op, int options,
const char *section,
xmlNode *req, xmlNode *input,
xmlNode *existing_cib,
xmlNode **result_cib,
xmlNode **answer);
/*!
* \internal
* \brief Add a CIB file client to client table
*
* \param[in] cib CIB client
*/
static void
register_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
client_table = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(client_table, private->id, (gpointer) cib);
}
/*!
* \internal
* \brief Remove a CIB file client from client table
*
* \param[in] cib CIB client
*/
static void
unregister_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
return;
}
g_hash_table_remove(client_table, private->id);
/* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged,
* instead of destroying the client table when there are no more clients.
*/
if (g_hash_table_size(client_table) == 0) {
g_hash_table_destroy(client_table);
client_table = NULL;
}
}
/*!
* \internal
* \brief Look up a CIB file client by its ID
*
* \param[in] client_id CIB client ID
*
* \return CIB client with matching ID if found, or \p NULL otherwise
*/
static cib_t *
get_client(const char *client_id)
{
if (client_table == NULL) {
return NULL;
}
return g_hash_table_lookup(client_table, (gpointer) client_id);
}
static const cib__op_fn_t cib_op_functions[] = {
[cib__op_apply_patch] = cib_process_diff,
[cib__op_bump] = cib_process_bump,
[cib__op_commit_transact] = cib_file_process_commit_transaction,
[cib__op_create] = cib_process_create,
[cib__op_delete] = cib_process_delete,
[cib__op_erase] = cib_process_erase,
[cib__op_modify] = cib_process_modify,
[cib__op_query] = cib_process_query,
[cib__op_replace] = cib_process_replace,
[cib__op_upgrade] = cib_process_upgrade,
};
/* cib_file_backup() and cib_file_write_with_digest() need to chown the
* written files only in limited circumstances, so these variables allow
* that to be indicated without affecting external callers
*/
static uid_t cib_file_owner = 0;
static uid_t cib_file_group = 0;
static gboolean cib_do_chown = FALSE;
#define cib_set_file_flags(cibfile, flags_to_set) do { \
(cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_set), \
#flags_to_set); \
} while (0)
#define cib_clear_file_flags(cibfile, flags_to_clear) do { \
(cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_clear), \
#flags_to_clear); \
} while (0)
/*!
* \internal
* \brief Get the function that performs a given CIB file operation
*
* \param[in] operation Operation whose function to look up
*
* \return Function that performs \p operation for a CIB file client
*/
static cib__op_fn_t
file_get_op_function(const cib__operation_t *operation)
{
enum cib__op_type type = operation->type;
CRM_ASSERT(type >= 0);
if (type >= PCMK__NELEM(cib_op_functions)) {
return NULL;
}
return cib_op_functions[type];
}
/*!
* \internal
* \brief Check whether a file is the live CIB
*
* \param[in] filename Name of file to check
*
* \return TRUE if file exists and its real path is same as live CIB's
*/
static gboolean
cib_file_is_live(const char *filename)
{
gboolean same = FALSE;
if (filename != NULL) {
// Canonicalize file names for true comparison
char *real_filename = NULL;
if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
char *real_livename = NULL;
if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
&real_livename) == pcmk_rc_ok) {
same = !strcmp(real_filename, real_livename);
free(real_livename);
}
free(real_filename);
}
}
return same;
}
static int
cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output)
{
int rc = pcmk_ok;
const cib__operation_t *operation = NULL;
cib__op_fn_t op_function = NULL;
int call_id = 0;
int call_options = cib_none;
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *section = crm_element_value(request, F_CIB_SECTION);
xmlNode *data = get_message_xml(request, F_CIB_CALLDATA);
bool changed = false;
bool read_only = false;
xmlNode *result_cib = NULL;
xmlNode *cib_diff = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
// We error checked these in callers
cib__get_operation(op, &operation);
op_function = file_get_op_function(operation);
crm_element_value_int(request, F_CIB_CALLID, &call_id);
crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
read_only = !pcmk_is_set(operation->flags, cib__op_attr_modifies);
// Mirror the logic in prepare_input() in pacemaker-based
if ((section != NULL) && pcmk__xe_is(data, XML_TAG_CIB)) {
data = pcmk_find_cib_element(data, section);
}
rc = cib_perform_op(op, call_options, op_function, read_only, section,
request, data, true, &changed, &private->cib_xml,
&result_cib, &cib_diff, output);
if (pcmk_is_set(call_options, cib_transaction)) {
/* The rest of the logic applies only to the transaction as a whole, not
* to individual requests.
*/
goto done;
}
if (rc == -pcmk_err_schema_validation) {
validate_xml_verbose(result_cib);
} else if ((rc == pcmk_ok) && !read_only) {
pcmk__log_xml_patchset(LOG_DEBUG, cib_diff);
if (result_cib != private->cib_xml) {
free_xml(private->cib_xml);
private->cib_xml = result_cib;
}
cib_set_file_flags(private, cib_file_flag_dirty);
}
// Global operation callback (deprecated)
if (cib->op_callback != NULL) {
cib->op_callback(NULL, call_id, rc, *output);
}
done:
if ((result_cib != private->cib_xml) && (result_cib != *output)) {
free_xml(result_cib);
}
free_xml(cib_diff);
return rc;
}
static int
cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data,
xmlNode **output_data, int call_options,
const char *user_name)
{
int rc = pcmk_ok;
xmlNode *request = NULL;
xmlNode *output = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
const cib__operation_t *operation = NULL;
crm_info("Handling %s operation for %s as %s",
pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"),
pcmk__s(user_name, "default user"));
if (output_data != NULL) {
*output_data = NULL;
}
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
rc = cib__get_operation(op, &operation);
rc = pcmk_rc2legacy(rc);
if (rc != pcmk_ok) {
// @COMPAT: At compatibility break, use rc directly
return -EPROTONOSUPPORT;
}
if (file_get_op_function(operation) == NULL) {
// @COMPAT: At compatibility break, use EOPNOTSUPP
crm_err("Operation %s is not supported by CIB file clients", op);
return -EPROTONOSUPPORT;
}
cib__set_call_options(call_options, "file operation", cib_no_mtime);
rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
NULL, &request);
if (rc != pcmk_ok) {
return rc;
}
crm_xml_add(request, XML_ACL_TAG_USER, user_name);
crm_xml_add(request, F_CIB_CLIENTID, private->id);
if (pcmk_is_set(call_options, cib_transaction)) {
rc = cib__extend_transaction(cib, request);
goto done;
}
rc = cib_file_process_request(cib, request, &output);
if ((output_data != NULL) && (output != NULL)) {
if (output->doc == private->cib_xml->doc) {
*output_data = copy_xml(output);
} else {
*output_data = output;
}
}
done:
if ((output != NULL)
&& (output->doc != private->cib_xml->doc)
&& ((output_data == NULL) || (output != *output_data))) {
free_xml(output);
}
free_xml(request);
return rc;
}
/*!
* \internal
* \brief Read CIB from disk and validate it against XML schema
*
* \param[in] filename Name of file to read CIB from
* \param[out] output Where to store the read CIB XML
*
* \return pcmk_ok on success,
* -ENXIO if file does not exist (or stat() otherwise fails), or
* -pcmk_err_schema_validation if XML doesn't parse or validate
* \note If filename is the live CIB, this will *not* verify its digest,
* though that functionality would be trivial to add here.
* Also, this will *not* verify that the file is writable,
* because some callers might not need to write.
*/
static int
load_file_cib(const char *filename, xmlNode **output)
{
struct stat buf;
xmlNode *root = NULL;
/* Ensure file is readable */
if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
return -ENXIO;
}
/* Parse XML from file */
root = filename2xml(filename);
if (root == NULL) {
return -pcmk_err_schema_validation;
}
/* Add a status section if not already present */
if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) {
create_xml_node(root, XML_CIB_TAG_STATUS);
}
/* Validate XML against its specified schema */
if (validate_xml(root, NULL, TRUE) == FALSE) {
const char *schema = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
crm_err("CIB does not validate against %s", schema);
free_xml(root);
return -pcmk_err_schema_validation;
}
/* Remember the parsed XML for later use */
*output = root;
return pcmk_ok;
}
static int
cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
if (private->filename == NULL) {
rc = -EINVAL;
} else {
rc = load_file_cib(private->filename, &private->cib_xml);
}
if (rc == pcmk_ok) {
crm_debug("Opened connection to local file '%s' for %s",
private->filename, name);
cib->state = cib_connected_command;
cib->type = cib_command;
register_client(cib);
} else {
crm_info("Connection to local file '%s' for %s (client %s) failed: %s",
private->filename, name, private->id, pcmk_strerror(rc));
}
return rc;
}
/*!
* \internal
* \brief Write out the in-memory CIB to a live CIB file
*
* param[in] cib_root Root of XML tree to write
* param[in,out] path Full path to file to write
*
* \return 0 on success, -1 on failure
*/
static int
cib_file_write_live(xmlNode *cib_root, char *path)
{
uid_t uid = geteuid();
struct passwd *daemon_pwent;
char *sep = strrchr(path, '/');
const char *cib_dirname, *cib_filename;
int rc = 0;
/* Get the desired uid/gid */
errno = 0;
daemon_pwent = getpwnam(CRM_DAEMON_USER);
if (daemon_pwent == NULL) {
crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
return -1;
}
/* If we're root, we can change the ownership;
* if we're daemon, anything we create will be OK;
* otherwise, block access so we don't create wrong owner
*/
if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
CRM_DAEMON_USER);
return 0;
}
/* fancy footwork to separate dirname from filename
* (we know the canonical name maps to the live CIB,
* but the given name might be relative, or symlinked)
*/
if (sep == NULL) { /* no directory component specified */
cib_dirname = "./";
cib_filename = path;
} else if (sep == path) { /* given name is in / */
cib_dirname = "/";
cib_filename = path + 1;
} else { /* typical case; split given name into parts */
*sep = '\0';
cib_dirname = path;
cib_filename = sep + 1;
}
/* if we're root, we want to update the file ownership */
if (uid == 0) {
cib_file_owner = daemon_pwent->pw_uid;
cib_file_group = daemon_pwent->pw_gid;
cib_do_chown = TRUE;
}
/* write the file */
if (cib_file_write_with_digest(cib_root, cib_dirname,
cib_filename) != pcmk_ok) {
rc = -1;
}
/* turn off file ownership changes, for other callers */
if (uid == 0) {
cib_do_chown = FALSE;
}
/* undo fancy stuff */
if ((sep != NULL) && (*sep == '\0')) {
*sep = '/';
}
return rc;
}
/*!
* \internal
* \brief Sign-off method for CIB file variants
*
* This will write the file to disk if needed, and free the in-memory CIB. If
* the file is the live CIB, it will compute and write a signature as well.
*
* \param[in,out] cib CIB object to sign off
*
* \return pcmk_ok on success, pcmk_err_generic on failure
* \todo This method should refuse to write the live CIB if the CIB manager is
* running.
*/
static int
cib_file_signoff(cib_t *cib)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
crm_debug("Disconnecting from the CIB manager");
cib->state = cib_disconnected;
cib->type = cib_no_connection;
unregister_client(cib);
cib->cmds->end_transaction(cib, false, cib_none);
/* If the in-memory CIB has been changed, write it to disk */
if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
/* If this is the live CIB, write it out with a digest */
if (pcmk_is_set(private->flags, cib_file_flag_live)) {
if (cib_file_write_live(private->cib_xml, private->filename) < 0) {
rc = pcmk_err_generic;
}
/* Otherwise, it's a simple write */
} else {
gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2");
if (write_xml_file(private->cib_xml, private->filename,
do_bzip) <= 0) {
rc = pcmk_err_generic;
}
}
if (rc == pcmk_ok) {
crm_info("Wrote CIB to %s", private->filename);
cib_clear_file_flags(private, cib_file_flag_dirty);
} else {
crm_err("Could not write CIB to %s", private->filename);
}
}
/* Free the in-memory CIB */
free_xml(private->cib_xml);
private->cib_xml = NULL;
return rc;
}
static int
cib_file_free(cib_t *cib)
{
int rc = pcmk_ok;
if (cib->state != cib_disconnected) {
rc = cib_file_signoff(cib);
}
if (rc == pcmk_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
free(private->id);
free(private->filename);
free(private);
free(cib->cmds);
free(cib->user);
free(cib);
} else {
fprintf(stderr, "Couldn't sign off: %d\n", rc);
}
return rc;
}
static int
cib_file_inputfd(cib_t *cib)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_set_connection_dnotify(cib_t *cib,
void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Get the given CIB connection's unique client identifier
*
* \param[in] cib CIB connection
* \param[out] async_id If not \p NULL, where to store asynchronous client ID
* \param[out] sync_id If not \p NULL, where to store synchronous client ID
*
* \return Legacy Pacemaker return code
*
* \note This is the \p cib_file variant implementation of
* \p cib_api_operations_t:client_id().
*/
static int
cib_file_client_id(const cib_t *cib, const char **async_id,
const char **sync_id)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (async_id != NULL) {
*async_id = private->id;
}
if (sync_id != NULL) {
*sync_id = private->id;
}
return pcmk_ok;
}
cib_t *
cib_file_new(const char *cib_location)
{
cib_file_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
private = calloc(1, sizeof(cib_file_opaque_t));
if (private == NULL) {
free(cib);
return NULL;
}
private->id = crm_generate_uuid();
cib->variant = cib_file;
cib->variant_opaque = private;
if (cib_location == NULL) {
cib_location = getenv("CIB_file");
CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible
}
private->flags = 0;
if (cib_file_is_live(cib_location)) {
cib_set_file_flags(private, cib_file_flag_live);
crm_trace("File %s detected as live CIB", cib_location);
}
private->filename = strdup(cib_location);
/* assign variant specific ops */
cib->delegate_fn = cib_file_perform_op_delegate;
cib->cmds->signon = cib_file_signon;
cib->cmds->signoff = cib_file_signoff;
cib->cmds->free = cib_file_free;
cib->cmds->inputfd = cib_file_inputfd; // Deprecated method
cib->cmds->register_notification = cib_file_register_notification;
cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
cib->cmds->client_id = cib_file_client_id;
return cib;
}
/*!
* \internal
* \brief Compare the calculated digest of an XML tree against a signature file
*
* \param[in] root Root of XML tree to compare
* \param[in] sigfile Name of signature file containing digest to compare
*
* \return TRUE if digests match or signature file does not exist, else FALSE
*/
static gboolean
cib_file_verify_digest(xmlNode *root, const char *sigfile)
{
gboolean passed = FALSE;
char *expected;
int rc = pcmk__file_contents(sigfile, &expected);
switch (rc) {
case pcmk_rc_ok:
if (expected == NULL) {
crm_err("On-disk digest at %s is empty", sigfile);
return FALSE;
}
break;
case ENOENT:
crm_warn("No on-disk digest present at %s", sigfile);
return TRUE;
default:
crm_err("Could not read on-disk digest from %s: %s",
sigfile, pcmk_rc_str(rc));
return FALSE;
}
passed = pcmk__verify_digest(root, expected);
free(expected);
return passed;
}
/*!
* \internal
* \brief Read an XML tree from a file and verify its digest
*
* \param[in] filename Name of XML file to read
* \param[in] sigfile Name of signature file containing digest to compare
* \param[out] root If non-NULL, will be set to pointer to parsed XML tree
*
* \return 0 if file was successfully read, parsed and verified, otherwise:
* -errno on stat() failure,
* -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
* -pcmk_err_cib_modified if digests do not match
* \note If root is non-NULL, it is the caller's responsibility to free *root on
* successful return.
*/
int
cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
{
int s_res;
struct stat buf;
char *local_sigfile = NULL;
xmlNode *local_root = NULL;
CRM_ASSERT(filename != NULL);
if (root) {
*root = NULL;
}
/* Verify that file exists and its size is nonzero */
s_res = stat(filename, &buf);
if (s_res < 0) {
crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
return -errno;
} else if (buf.st_size == 0) {
crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
return -pcmk_err_cib_corrupt;
}
/* Parse XML */
local_root = filename2xml(filename);
if (local_root == NULL) {
crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
return -pcmk_err_cib_corrupt;
}
/* If sigfile is not specified, use original file name plus .sig */
if (sigfile == NULL) {
sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
}
/* Verify that digests match */
if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
free(local_sigfile);
free_xml(local_root);
return -pcmk_err_cib_modified;
}
free(local_sigfile);
if (root) {
*root = local_root;
} else {
free_xml(local_root);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Back up a CIB
*
* \param[in] cib_dirname Directory containing CIB file and backups
* \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
*
* \return 0 on success, -1 on error
*/
static int
cib_file_backup(const char *cib_dirname, const char *cib_filename)
{
int rc = 0;
unsigned int seq;
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
char *backup_path;
char *backup_digest;
// Determine backup and digest file names
if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
seq = 0;
}
backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
CIB_SERIES_BZIP);
backup_digest = crm_strdup_printf("%s.sig", backup_path);
/* Remove the old backups if they exist */
unlink(backup_path);
unlink(backup_digest);
/* Back up the CIB, by hard-linking it to the backup name */
if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_path, backup_path);
rc = -1;
/* Back up the CIB signature similarly */
} else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_digest, backup_digest);
rc = -1;
/* Update the last counter and ensure everything is sync'd to media */
} else {
pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
CIB_SERIES_MAX);
if (cib_do_chown) {
int rc2;
if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
rc = -1;
}
if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
rc = -1;
}
rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
cib_file_owner, cib_file_group);
if (rc2 != pcmk_rc_ok) {
crm_err("Could not set owner of sequence file in %s: %s",
cib_dirname, pcmk_rc_str(rc2));
rc = -1;
}
}
pcmk__sync_directory(cib_dirname);
crm_info("Archived previous version as %s", backup_path);
}
free(cib_path);
free(cib_digest);
free(backup_path);
free(backup_digest);
return rc;
}
/*!
* \internal
* \brief Prepare CIB XML to be written to disk
*
* Set num_updates to 0, set cib-last-written to the current timestamp,
* and strip out the status section.
*
* \param[in,out] root Root of CIB XML tree
*
* \return void
*/
static void
cib_file_prepare_xml(xmlNode *root)
{
xmlNode *cib_status_root = NULL;
/* Always write out with num_updates=0 and current last-written timestamp */
crm_xml_add(root, XML_ATTR_NUMUPDATES, "0");
pcmk__xe_add_last_written(root);
/* Delete status section before writing to file, because
* we discard it on startup anyway, and users get confused by it */
cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE);
CRM_LOG_ASSERT(cib_status_root != NULL);
if (cib_status_root != NULL) {
free_xml(cib_status_root);
}
}
/*!
* \internal
* \brief Write CIB to disk, along with a signature file containing its digest
*
* \param[in,out] cib_root Root of XML tree to write
* \param[in] cib_dirname Directory containing CIB and signature files
* \param[in] cib_filename Name (relative to cib_dirname) of file to write
*
* \return pcmk_ok on success,
* pcmk_err_cib_modified if existing cib_filename doesn't match digest,
* pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
* or pcmk_err_cib_save if new cib_filename couldn't be saved
*/
int
cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename)
{
int exit_rc = pcmk_ok;
int rc, fd;
char *digest = NULL;
/* Detect CIB version for diagnostic purposes */
const char *epoch = crm_element_value(cib_root, PCMK_XA_EPOCH);
- const char *admin_epoch = crm_element_value(cib_root,
- XML_ATTR_GENERATION_ADMIN);
+ const char *admin_epoch = crm_element_value(cib_root, PCMK_XA_ADMIN_EPOCH);
/* Determine full CIB and signature pathnames */
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *digest_path = crm_strdup_printf("%s.sig", cib_path);
/* Create temporary file name patterns for writing out CIB and signature */
char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
CRM_ASSERT((cib_path != NULL) && (digest_path != NULL)
&& (tmp_cib != NULL) && (tmp_digest != NULL));
/* Ensure the admin didn't modify the existing CIB underneath us */
crm_trace("Reading cluster configuration file %s", cib_path);
rc = cib_file_read_and_verify(cib_path, NULL, NULL);
if ((rc != pcmk_ok) && (rc != -ENOENT)) {
crm_err("%s was manually modified while the cluster was active!",
cib_path);
exit_rc = pcmk_err_cib_modified;
goto cleanup;
}
/* Back up the existing CIB */
if (cib_file_backup(cib_dirname, cib_filename) < 0) {
exit_rc = pcmk_err_cib_backup;
goto cleanup;
}
crm_debug("Writing CIB to disk");
umask(S_IWGRP | S_IWOTH | S_IROTH);
cib_file_prepare_xml(cib_root);
/* Write the CIB to a temporary file, so we can deploy (near) atomically */
fd = mkstemp(tmp_cib);
if (fd < 0) {
crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Protect the temporary file */
if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Write out the CIB */
if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) {
crm_err("Changes couldn't be written to %s", tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Calculate CIB digest */
digest = calculate_on_disk_digest(cib_root);
CRM_ASSERT(digest != NULL);
crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
(admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
/* Write the CIB digest to a temporary file */
fd = mkstemp(tmp_digest);
if (fd < 0) {
crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
rc = pcmk__write_sync(fd, digest);
if (rc != pcmk_rc_ok) {
crm_err("Could not write digest to %s: %s",
tmp_digest, pcmk_rc_str(rc));
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
close(fd);
crm_debug("Wrote digest %s to disk", digest);
/* Verify that what we wrote is sane */
crm_info("Reading cluster configuration file %s (digest: %s)",
tmp_cib, tmp_digest);
rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
CRM_ASSERT(rc == 0);
/* Rename temporary files to live, and sync directory changes to media */
crm_debug("Activating %s", tmp_cib);
if (rename(tmp_cib, cib_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
exit_rc = pcmk_err_cib_save;
}
if (rename(tmp_digest, digest_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
digest_path);
exit_rc = pcmk_err_cib_save;
}
pcmk__sync_directory(cib_dirname);
cleanup:
free(cib_path);
free(digest_path);
free(digest);
free(tmp_digest);
free(tmp_cib);
return exit_rc;
}
/*!
* \internal
* \brief Process requests in a CIB transaction
*
* Stop when a request fails or when all requests have been processed.
*
* \param[in,out] cib CIB client
* \param[in,out] transaction CIB transaction
*
* \return Standard Pacemaker return code
*/
static int
cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction)
{
cib_file_opaque_t *private = cib->variant_opaque;
for (xmlNode *request = first_named_child(transaction, T_CIB_COMMAND);
request != NULL; request = crm_next_same_xml(request)) {
xmlNode *output = NULL;
const char *op = crm_element_value(request, F_CIB_OPERATION);
int rc = cib_file_process_request(cib, request, &output);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Aborting transaction for CIB file client (%s) on file "
"'%s' due to failed %s request: %s",
private->id, private->filename, op, pcmk_rc_str(rc));
crm_log_xml_info(request, "Failed request");
return rc;
}
crm_trace("Applied %s request to transaction working CIB for CIB file "
"client (%s) on file '%s'",
op, private->id, private->filename);
crm_log_xml_trace(request, "Successful request");
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Commit a given CIB file client's transaction to a working CIB copy
*
* \param[in,out] cib CIB file client
* \param[in] transaction CIB transaction
* \param[in,out] result_cib Where to store result CIB
*
* \return Standard Pacemaker return code
*
* \note The caller is responsible for replacing the \p cib argument's
* \p private->cib_xml with \p result_cib on success, and for freeing
* \p result_cib using \p free_xml() on failure.
*/
static int
cib_file_commit_transaction(cib_t *cib, xmlNode *transaction,
xmlNode **result_cib)
{
int rc = pcmk_rc_ok;
cib_file_opaque_t *private = cib->variant_opaque;
xmlNode *saved_cib = private->cib_xml;
CRM_CHECK(pcmk__xe_is(transaction, T_CIB_TRANSACTION),
return pcmk_rc_no_transaction);
/* *result_cib should be a copy of private->cib_xml (created by
* cib_perform_op()). If not, make a copy now. Change tracking isn't
* strictly required here because:
* * Each request in the transaction will have changes tracked and ACLs
* checked if appropriate.
* * cib_perform_op() will infer changes for the commit request at the end.
*/
CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml),
*result_cib = copy_xml(private->cib_xml));
crm_trace("Committing transaction for CIB file client (%s) on file '%s' to "
"working CIB",
private->id, private->filename);
// Apply all changes to a working copy of the CIB
private->cib_xml = *result_cib;
rc = cib_file_process_transaction_requests(cib, transaction);
crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'",
((rc == pcmk_rc_ok)? "succeeded" : "failed"),
private->id, private->filename);
/* Some request types (for example, erase) may have freed private->cib_xml
* (the working copy) and pointed it at a new XML object. In that case, it
* follows that *result_cib (the working copy) was freed.
*
* Point *result_cib at the updated working copy stored in private->cib_xml.
*/
*result_cib = private->cib_xml;
// Point private->cib_xml back to the unchanged original copy
private->cib_xml = saved_cib;
return rc;
}
static int
cib_file_process_commit_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
int rc = pcmk_rc_ok;
const char *client_id = crm_element_value(req, F_CIB_CLIENTID);
cib_t *cib = NULL;
CRM_CHECK(client_id != NULL, return -EINVAL);
cib = get_client(client_id);
CRM_CHECK(cib != NULL, return -EINVAL);
rc = cib_file_commit_transaction(cib, input, result_cib);
if (rc != pcmk_rc_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
crm_err("Could not commit transaction for CIB file client (%s) on "
"file '%s': %s",
private->id, private->filename, pcmk_rc_str(rc));
}
return pcmk_rc2legacy(rc);
}
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
index 715637b9eb..2e4499c25e 100644
--- a/lib/cib/cib_ops.c
+++ b/lib/cib/cib_ops.c
@@ -1,1007 +1,1007 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
// @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, free_xml(*answer));
*answer = NULL;
if (pcmk__str_eq(XML_CIB_TAG_SECTION_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 = create_xml_node(*answer,
(const char *) obj_root->name);
copy_in_properties(shallow, obj_root);
*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 = strdup("1");
CRM_ASSERT(new_value != NULL);
}
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) {
free_xml(*result_cib);
}
*result_cib = createEmptyCib(0);
copy_in_properties(*result_cib, existing_cib);
- update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, false);
+ 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;
int new_version = 0;
int current_version = 0;
int max_version = 0;
const char *max = crm_element_value(req, F_CIB_SCHEMA_MAX);
const char *value = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH);
*answer = NULL;
crm_trace("Processing \"%s\" event with max=%s", op, max);
if (value != NULL) {
current_version = get_schema_version(value);
}
if (max) {
max_version = get_schema_version(max);
}
rc = update_validation(result_cib, &new_version, max_version, TRUE,
!(options & cib_verbose));
if (new_version > current_version) {
- update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, false);
+ update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false);
update_counter(*result_cib, PCMK_XA_EPOCH, true);
update_counter(*result_cib, XML_ATTR_NUMUPDATES, 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(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, section)) {
section = NULL;
}
if (pcmk__xe_is(input, XML_TAG_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, F_ORIG);
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);
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 = XML_ATTR_GENERATION_ADMIN;
+ 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 = XML_ATTR_NUMUPDATES;
}
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) {
free_xml(*result_cib);
}
*result_cib = copy_xml(input);
} else {
xmlNode *obj_root = NULL;
gboolean ok = TRUE;
obj_root = pcmk_find_cib_element(*result_cib, section);
ok = replace_xml_child(NULL, obj_root, input, FALSE);
if (ok == FALSE) {
crm_trace("No matching object to replace");
result = -ENXIO;
}
}
return result;
}
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)) {
xmlNode *child = NULL;
for (child = pcmk__xml_first_child(input); child;
child = pcmk__xml_next(child)) {
if (replace_xml_child(NULL, obj_root, child, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s", child->name, ID(child));
}
}
} else if (replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s", input->name, ID(input));
}
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;
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 = create_xml_node(NULL, section);
cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section,
NULL, result_cib, answer);
free_xml(tmp_section);
obj_root = pcmk_find_cib_element(*result_cib, section);
}
CRM_CHECK(obj_root != NULL, return -EINVAL);
if (update_xml_child(obj_root, input) == FALSE) {
if (options & cib_can_create) {
add_node_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);
free_xml(match);
}
freeXpathObject(xpathObj);
}
return pcmk_ok;
}
static int
update_cib_object(xmlNode * parent, xmlNode * update)
{
int result = pcmk_ok;
xmlNode *target = NULL;
xmlNode *a_child = NULL;
const char *replace = NULL;
const char *object_id = NULL;
const char *object_name = NULL;
CRM_CHECK(update != NULL, return -EINVAL);
CRM_CHECK(parent != NULL, return -EINVAL);
object_name = (const char *) update->name;
CRM_CHECK(object_name != NULL, return -EINVAL);
object_id = ID(update);
crm_trace("Processing update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
/* placeholder object */
target = find_xml_node(parent, object_name, FALSE);
} else {
target = pcmk__xe_match(parent, object_name, XML_ATTR_ID, object_id);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
}
crm_trace("Found node <%s%s%s%s> to update", object_name,
((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
// @COMPAT: XML_CIB_ATTR_REPLACE is unused internally. Remove at break.
replace = crm_element_value(update, XML_CIB_ATTR_REPLACE);
if (replace != NULL) {
int last = 0;
int len = strlen(replace);
for (int lpc = 0; lpc <= len; ++lpc) {
if (replace[lpc] == ',' || replace[lpc] == 0) {
if (last != lpc) {
char *replace_item = strndup(replace + last, lpc - last);
xmlNode *remove = find_xml_node(target, replace_item,
FALSE);
if (remove != NULL) {
crm_trace("Replacing node <%s> in <%s>",
replace_item, target->name);
free_xml(remove);
}
free(replace_item);
}
last = lpc + 1;
}
}
xml_remove_prop(update, XML_CIB_ATTR_REPLACE);
xml_remove_prop(target, XML_CIB_ATTR_REPLACE);
}
copy_in_properties(target, update);
if (xml_acl_denied(target)) {
crm_notice("Cannot update <%s " XML_ATTR_ID "=%s>",
pcmk__s(object_name, "<null>"),
pcmk__s(object_id, "<null>"));
return -EACCES;
}
crm_trace("Processing children of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
for (a_child = pcmk__xml_first_child(update); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
int tmp_result = 0;
crm_trace("Updating child <%s%s%s%s>", a_child->name,
((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'"));
tmp_result = update_cib_object(target, a_child);
/* only the first error is likely to be interesting */
if (tmp_result != pcmk_ok) {
crm_err("Error updating child <%s%s%s%s>",
a_child->name,
((ID(a_child) == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(ID(a_child), ""),
((ID(a_child) == NULL)? "" : "'"));
if (result == pcmk_ok) {
result = tmp_result;
}
}
}
crm_trace("Finished handling update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
return result;
}
static int
add_cib_object(xmlNode * parent, xmlNode * new_obj)
{
const char *object_name = NULL;
const char *object_id = NULL;
xmlNode *equiv_node = NULL;
if ((parent == NULL) || (new_obj == NULL)) {
return -EINVAL;
}
object_name = (const char *) new_obj->name;
if (object_name == NULL) {
return -EINVAL;
}
object_id = ID(new_obj);
crm_trace("Processing creation of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " " XML_ATTR_ID "='"),
pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
equiv_node = find_xml_node(parent, object_name, FALSE);
} else {
equiv_node = pcmk__xe_match(parent, object_name, XML_ATTR_ID,
object_id);
}
if (equiv_node != NULL) {
return -EEXIST;
}
return update_cib_object(parent, new_obj);
}
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 = create_xml_node(failed, XML_FAIL_TAG_CIB);
add_node_copy(xml_node, target);
crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID, ID(target));
crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE,
(const char *) target->name);
crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP, operation);
crm_xml_add(xml_node, XML_FAILCIB_ATTR_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(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(XML_TAG_CIB, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, XML_TAG_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);
}
failed = create_xml_node(NULL, XML_TAG_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 {
free_xml(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, F_ORIG);
}
crm_trace("Processing \"%s\" event from %s%s",
op, originator,
(pcmk_is_set(options, cib_force_diff)? " (global update)" : ""));
if (*result_cib != existing_cib) {
free_xml(*result_cib);
}
*result_cib = copy_xml(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 = diff_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, "//" XML_CIB_TAG_CONFIGURATION);
if (numXpathResults(xpathObj) > 0) {
config_changes = true;
goto done;
}
freeXpathObject(xpathObj);
/*
* Do not check XML_TAG_DIFF_ADDED "//" XML_TAG_CIB
* This always contains every field and would produce a false positive
* every time if the checked value existed
*/
xpathObj = xpath_search(*diff, "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_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, XML_ATTR_GENERATION_ADMIN) != NULL) {
+ 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, "remote-clear-port") != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, "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 = create_xml_node(NULL, "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;
}
free_xml(match);
if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) {
if (update_xml_child(match, input) == FALSE) {
rc = -ENXIO;
} else if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) {
add_node_copy(match, input);
break;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
if (options & cib_no_children) {
xmlNode *shallow = create_xml_node(*answer,
(const char *) match->name);
copy_in_properties(shallow, match);
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, XML_ATTR_ID);
char *new_path = NULL;
if (id) {
new_path = crm_strdup_printf("/%s[@" XML_ATTR_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 = create_xml_node(NULL, "xpath-query");
}
parent = create_xml_node(*answer, "xpath-query-path");
crm_xml_add(parent, XML_ATTR_ID, path);
free(path);
} else if (*answer) {
add_node_copy(*answer, match);
} else {
*answer = match;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE,
pcmk__str_none)) {
xmlNode *parent = match->parent;
free_xml(match);
if (input != NULL) {
add_node_copy(parent, input);
}
if ((options & cib_multiple) == 0) {
break;
}
}
}
freeXpathObject(xpathObj);
return rc;
}
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index b59a6fd6d7..dfaf333e31 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,1114 +1,1114 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-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 <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sys/utsname.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/cib_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/pengine/rules.h>
xmlNode *
cib_get_generation(cib_t * cib)
{
xmlNode *the_cib = NULL;
xmlNode *generation = create_xml_node(NULL, XML_CIB_TAG_GENERATION_TUPPLE);
cib->cmds->query(cib, NULL, &the_cib, cib_scope_local | cib_sync_call);
if (the_cib != NULL) {
copy_in_properties(generation, the_cib);
free_xml(the_cib);
}
return generation;
}
gboolean
cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
{
*epoch = -1;
*updates = -1;
*admin_epoch = -1;
if (cib == NULL) {
return FALSE;
} else {
crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates);
- crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch);
+ crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
}
return TRUE;
}
gboolean
cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(diff, add, del);
*admin_epoch = add[0];
*epoch = add[1];
*updates = add[2];
*_admin_epoch = del[0];
*_epoch = del[1];
*_updates = del[2];
return TRUE;
}
/*!
* \internal
* \brief Get the XML patchset from a CIB diff notification
*
* \param[in] msg CIB diff notification
* \param[out] patchset Where to store XML patchset
*
* \return Standard Pacemaker return code
*/
int
cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
{
int rc = pcmk_err_generic;
CRM_ASSERT(patchset != NULL);
*patchset = NULL;
if (msg == NULL) {
crm_err("CIB diff notification received with no XML");
return ENOMSG;
}
if ((crm_element_value_int(msg, F_CIB_RC, &rc) != 0) || (rc != pcmk_ok)) {
crm_warn("Ignore failed CIB update: %s " CRM_XS " rc=%d",
pcmk_strerror(rc), rc);
crm_log_xml_debug(msg, "failed");
return pcmk_legacy2rc(rc);
}
*patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
if (*patchset == NULL) {
crm_err("CIB diff notification received with no patchset");
return ENOMSG;
}
return pcmk_rc_ok;
}
#define XPATH_DIFF_V1 "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset (v1)
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c XML_TAG_CIB)
*
* \return \c true if \p element was modified, or \c false otherwise
*/
static bool
element_in_patchset_v1(const xmlNode *patchset, const char *element)
{
char *xpath = crm_strdup_printf(XPATH_DIFF_V1 "//%s",
pcmk__s(element, XML_TAG_CIB));
xmlXPathObject *xpath_obj = xpath_search(patchset, xpath);
free(xpath);
if (xpath_obj == NULL) {
return false;
}
freeXpathObject(xpath_obj);
return true;
}
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset (v2)
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c XML_TAG_CIB). Supported values include any CIB
* element supported by \c pcmk__cib_abs_xpath_for().
*
* \return \c true if \p element was modified, or \c false otherwise
*/
static bool
element_in_patchset_v2(const xmlNode *patchset, const char *element)
{
const char *element_xpath = pcmk__cib_abs_xpath_for(element);
const char *parent_xpath = pcmk_cib_parent_name_for(element);
char *element_regex = NULL;
bool rc = false;
CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
// Matches if and only if element_xpath is part of a changed path
element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
for (const xmlNode *change = first_named_child(patchset, XML_DIFF_CHANGE);
change != NULL; change = crm_next_same_xml(change)) {
const char *op = crm_element_value(change, F_CIB_OPERATION);
const char *diff_xpath = crm_element_value(change, XML_DIFF_PATH);
if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
// Change to an existing element
rc = true;
break;
}
if (pcmk__str_eq(op, "create", pcmk__str_none)
&& pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
&& pcmk__xe_is(pcmk__xml_first_child(change), element)) {
// Newly added element
rc = true;
break;
}
}
free(element_regex);
return rc;
}
/*!
* \internal
* \brief Check whether a given CIB element was modified in a CIB patchset
*
* \param[in] patchset CIB XML patchset
* \param[in] element XML tag of CIB element to check (\c NULL is equivalent
* to \c XML_TAG_CIB). Supported values include any CIB
* element supported by \c pcmk__cib_abs_xpath_for().
*
* \return \c true if \p element was modified, or \c false otherwise
*/
bool
cib__element_in_patchset(const xmlNode *patchset, const char *element)
{
int format = 1;
CRM_ASSERT(patchset != NULL);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
return element_in_patchset_v1(patchset, element);
case 2:
return element_in_patchset_v2(patchset, element);
default:
crm_warn("Unknown patch format: %d", format);
return false;
}
}
/*!
* \brief Create XML for a new (empty) CIB
*
* \param[in] cib_epoch What to use as \c PCMK_XA_EPOCH CIB attribute
*
* \return Newly created XML for empty CIB
* \note It is the caller's responsibility to free the result with free_xml().
*/
xmlNode *
createEmptyCib(int cib_epoch)
{
xmlNode *cib_root = NULL, *config = NULL;
cib_root = create_xml_node(NULL, XML_TAG_CIB);
crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, xml_latest_schema());
crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
crm_xml_add_int(cib_root, XML_ATTR_NUMUPDATES, 0);
- crm_xml_add_int(cib_root, XML_ATTR_GENERATION_ADMIN, 0);
+ crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION);
create_xml_node(cib_root, XML_CIB_TAG_STATUS);
create_xml_node(config, XML_CIB_TAG_CRMCONFIG);
create_xml_node(config, XML_CIB_TAG_NODES);
create_xml_node(config, XML_CIB_TAG_RESOURCES);
create_xml_node(config, XML_CIB_TAG_CONSTRAINTS);
#if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
{
xmlNode *rsc_defaults = create_xml_node(config, XML_CIB_TAG_RSCCONFIG);
xmlNode *meta = create_xml_node(rsc_defaults, XML_TAG_META_SETS);
xmlNode *nvpair = create_xml_node(meta, XML_CIB_TAG_NVPAIR);
crm_xml_add(meta, XML_ATTR_ID, "build-resource-defaults");
crm_xml_add(nvpair, XML_ATTR_ID, "build-" XML_RSC_ATTR_STICKINESS);
crm_xml_add(nvpair, XML_NVPAIR_ATTR_NAME, XML_RSC_ATTR_STICKINESS);
crm_xml_add_int(nvpair, XML_NVPAIR_ATTR_VALUE,
PCMK__RESOURCE_STICKINESS_DEFAULT);
}
#endif
return cib_root;
}
static bool
cib_acl_enabled(xmlNode *xml, const char *user)
{
bool rc = FALSE;
if(pcmk_acl_required(user)) {
const char *value = NULL;
GHashTable *options = pcmk__strkey_table(free, free);
cib_read_config(options, xml);
value = cib_pref(options, PCMK_OPT_ENABLE_ACL);
rc = crm_is_true(value);
g_hash_table_destroy(options);
}
crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
return rc;
}
/*!
* \internal
* \brief Determine whether to perform operations on a scratch copy of the CIB
*
* \param[in] op CIB operation
* \param[in] section CIB section
* \param[in] call_options CIB call options
*
* \return \p true if we should make a copy of the CIB, or \p false otherwise
*/
static bool
should_copy_cib(const char *op, const char *section, int call_options)
{
if (pcmk_is_set(call_options, cib_dryrun)) {
// cib_dryrun implies a scratch copy by definition; no side effects
return true;
}
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
/* Commit-transaction must make a copy for atomicity. We must revert to
* the original CIB if the entire transaction cannot be applied
* successfully.
*/
return true;
}
if (pcmk_is_set(call_options, cib_transaction)) {
/* If cib_transaction is set, then we're in the process of committing a
* transaction. The commit-transaction request already made a scratch
* copy, and we're accumulating changes in that copy.
*/
return false;
}
if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_none)) {
/* Copying large CIBs accounts for a huge percentage of our CIB usage,
* and this avoids some of it.
*
* @TODO: Is this safe? See discussion at
* https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
*/
return false;
}
// Default behavior is to operate on a scratch copy
return true;
}
int
cib_perform_op(const char *op, int call_options, cib__op_fn_t fn, bool is_query,
const char *section, xmlNode *req, xmlNode *input,
bool manage_counters, bool *config_changed,
xmlNode **current_cib, xmlNode **result_cib, xmlNode **diff,
xmlNode **output)
{
int rc = pcmk_ok;
bool check_schema = true;
bool make_copy = true;
xmlNode *top = NULL;
xmlNode *scratch = NULL;
xmlNode *patchset_cib = NULL;
xmlNode *local_diff = NULL;
const char *new_version = NULL;
const char *user = crm_element_value(req, F_CIB_USER);
bool with_digest = false;
crm_trace("Begin %s%s%s op",
(pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
(is_query? "read-only " : ""), op);
CRM_CHECK(output != NULL, return -ENOMSG);
CRM_CHECK(current_cib != NULL, return -ENOMSG);
CRM_CHECK(result_cib != NULL, return -ENOMSG);
CRM_CHECK(config_changed != NULL, return -ENOMSG);
if(output) {
*output = NULL;
}
*result_cib = NULL;
*config_changed = false;
if (fn == NULL) {
return -EINVAL;
}
if (is_query) {
xmlNode *cib_ro = *current_cib;
xmlNode *cib_filtered = NULL;
if (cib_acl_enabled(cib_ro, user)
&& xml_acl_filtered_copy(user, *current_cib, *current_cib,
&cib_filtered)) {
if (cib_filtered == NULL) {
crm_debug("Pre-filtered the entire cib");
return -EACCES;
}
cib_ro = cib_filtered;
crm_log_xml_trace(cib_ro, "filtered");
}
rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
if(output == NULL || *output == NULL) {
/* nothing */
} else if(cib_filtered == *output) {
cib_filtered = NULL; /* Let them have this copy */
} else if (*output == *current_cib) {
/* They already know not to free it */
} else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
/* We're about to free the document of which *output is a part */
*output = copy_xml(*output);
} else if ((*output)->doc == (*current_cib)->doc) {
/* Give them a copy they can free */
*output = copy_xml(*output);
}
free_xml(cib_filtered);
return rc;
}
make_copy = should_copy_cib(op, section, call_options);
if (!make_copy) {
/* Conditional on v2 patch style */
scratch = *current_cib;
// Make a copy of the top-level element to store version details
top = create_xml_node(NULL, (const char *) scratch->name);
copy_in_properties(top, scratch);
patchset_cib = top;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
/* If scratch points to a new object now (for example, after an erase
* operation), then *current_cib should point to the same object.
*/
*current_cib = scratch;
} else {
scratch = copy_xml(*current_cib);
patchset_cib = *current_cib;
xml_track_changes(scratch, user, NULL, cib_acl_enabled(scratch, user));
rc = (*fn) (op, call_options, section, req, input, *current_cib,
&scratch, output);
if ((scratch != NULL) && !xml_tracking_changes(scratch)) {
crm_trace("Inferring changes after %s op", op);
xml_track_changes(scratch, user, *current_cib,
cib_acl_enabled(*current_cib, user));
xml_calculate_changes(*current_cib, scratch);
}
CRM_CHECK(*current_cib != scratch, return -EINVAL);
}
xml_acl_disable(scratch); /* Allow the system to make any additional changes */
if (rc == pcmk_ok && scratch == NULL) {
rc = -EINVAL;
goto done;
} else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
crm_trace("ACL rejected part or all of the proposed changes");
rc = -EACCES;
goto done;
} else if (rc != pcmk_ok) {
goto done;
}
if (scratch) {
new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
if (new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) {
crm_err("Discarding update with feature set '%s' greater than our own '%s'",
new_version, CRM_FEATURE_SET);
rc = -EPROTONOSUPPORT;
goto done;
}
}
if (patchset_cib != NULL) {
int old = 0;
int new = 0;
- crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new);
- crm_element_value_int(patchset_cib, XML_ATTR_GENERATION_ADMIN, &old);
+ crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
+ crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
- XML_ATTR_GENERATION_ADMIN, old, new, call_options);
+ PCMK_XA_ADMIN_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
} else if (old == new) {
crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
PCMK_XA_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
}
}
}
crm_trace("Massaging CIB contents");
pcmk__strip_xml_text(scratch);
fix_plus_plus_recursive(scratch);
if (!make_copy) {
/* At this point, patchset_cib is just the "cib" tag and its properties.
*
* The v1 format would barf on this, but we know the v2 patch
* format only needs it for the top-level version fields
*/
local_diff = xml_create_patchset(2, patchset_cib, scratch,
config_changed, manage_counters);
} else {
static time_t expires = 0;
time_t tm_now = time(NULL);
if (expires < tm_now) {
expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
with_digest = true;
}
local_diff = xml_create_patchset(0, patchset_cib, scratch,
config_changed, manage_counters);
}
pcmk__log_xml_changes(LOG_TRACE, scratch);
xml_accept_changes(scratch);
if(local_diff) {
patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
pcmk__log_xml_patchset(LOG_INFO, local_diff);
crm_log_xml_trace(local_diff, "raw patch");
}
if (make_copy && (local_diff != NULL)) {
// Original to compare against doesn't exist
pcmk__if_tracing(
{
// Validate the calculated patch set
int test_rc = pcmk_ok;
int format = 1;
xmlNode *cib_copy = copy_xml(patchset_cib);
crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
test_rc = xml_apply_patchset(cib_copy, local_diff,
manage_counters);
if (test_rc != pcmk_ok) {
save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
save_xml_to_file(scratch, "PatchApply:actual", NULL);
save_xml_to_file(local_diff, "PatchApply:diff", NULL);
crm_err("v%d patchset error, patch failed to apply: %s "
"(%d)",
format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
test_rc);
}
free_xml(cib_copy);
},
{}
);
}
if (pcmk__str_eq(section, XML_CIB_TAG_STATUS, pcmk__str_casei)) {
/* Throttle the amount of costly validation we perform due to status updates
* a) we don't really care whats in the status section
* b) we don't validate any of its contents at the moment anyway
*/
check_schema = false;
}
/* === scratch must not be modified after this point ===
* Exceptions, anything in:
static filter_t filter[] = {
{ 0, XML_ATTR_ORIGIN },
{ 0, XML_CIB_ATTR_WRITTEN },
{ 0, XML_ATTR_UPDATE_ORIG },
{ 0, XML_ATTR_UPDATE_CLIENT },
{ 0, XML_ATTR_UPDATE_USER },
};
*/
if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
pcmk__xe_add_last_written(scratch);
if (schema) {
static int minimum_schema = 0;
int current_schema = get_schema_version(schema);
if (minimum_schema == 0) {
minimum_schema = get_schema_version("pacemaker-1.2");
}
/* Does the CIB support the "update-*" attributes... */
if (current_schema >= minimum_schema) {
/* Ensure values of origin, client, and user in scratch match
* the values in req
*/
const char *origin = crm_element_value(req, F_ORIG);
const char *client = crm_element_value(req, F_CIB_CLIENTNAME);
if (origin != NULL) {
crm_xml_add(scratch, XML_ATTR_UPDATE_ORIG, origin);
} else {
xml_remove_prop(scratch, XML_ATTR_UPDATE_ORIG);
}
if (client != NULL) {
crm_xml_add(scratch, XML_ATTR_UPDATE_CLIENT, user);
} else {
xml_remove_prop(scratch, XML_ATTR_UPDATE_CLIENT);
}
if (user != NULL) {
crm_xml_add(scratch, XML_ATTR_UPDATE_USER, user);
} else {
xml_remove_prop(scratch, XML_ATTR_UPDATE_USER);
}
}
}
}
crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
if ((rc == pcmk_ok) && check_schema && !validate_xml(scratch, NULL, true)) {
const char *current_schema = crm_element_value(scratch,
PCMK_XA_VALIDATE_WITH);
crm_warn("Updated CIB does not validate against %s schema",
pcmk__s(current_schema, "unspecified"));
rc = -pcmk_err_schema_validation;
}
done:
*result_cib = scratch;
/* @TODO: This may not work correctly with !make_copy, since we don't
* keep the original CIB.
*/
if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
&& xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
if (*result_cib == NULL) {
crm_debug("Pre-filtered the entire cib result");
}
free_xml(scratch);
}
if(diff) {
*diff = local_diff;
} else {
free_xml(local_diff);
}
free_xml(top);
crm_trace("Done");
return rc;
}
int
cib__create_op(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data, int call_options,
const char *user_name, const char *client_name,
xmlNode **op_msg)
{
CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
*op_msg = create_xml_node(NULL, T_CIB_COMMAND);
if (*op_msg == NULL) {
return -EPROTO;
}
cib->call_id++;
if (cib->call_id < 1) {
cib->call_id = 1;
}
crm_xml_add(*op_msg, F_XML_TAGNAME, T_CIB_COMMAND);
crm_xml_add(*op_msg, F_TYPE, T_CIB);
crm_xml_add(*op_msg, F_CIB_OPERATION, op);
crm_xml_add(*op_msg, F_CIB_HOST, host);
crm_xml_add(*op_msg, F_CIB_SECTION, section);
crm_xml_add(*op_msg, F_CIB_USER, user_name);
crm_xml_add(*op_msg, F_CIB_CLIENTNAME, client_name);
crm_xml_add_int(*op_msg, F_CIB_CALLID, cib->call_id);
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(*op_msg, F_CIB_CALLOPTS, call_options);
if (data != NULL) {
add_message_xml(*op_msg, F_CIB_CALLDATA, data);
}
if (pcmk_is_set(call_options, cib_inhibit_bcast)) {
CRM_CHECK(pcmk_is_set(call_options, cib_scope_local),
free_xml(*op_msg); return -EPROTO);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Check whether a CIB request is supported in a transaction
*
* \param[in] request CIB request
*
* \return Standard Pacemaker return code
*/
static int
validate_transaction_request(const xmlNode *request)
{
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *host = crm_element_value(request, F_CIB_HOST);
const cib__operation_t *operation = NULL;
int rc = cib__get_operation(op, &operation);
if (rc != pcmk_rc_ok) {
// cib__get_operation() logs error
return rc;
}
if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
crm_err("Operation %s is not supported in CIB transactions", op);
return EOPNOTSUPP;
}
if (host != NULL) {
crm_err("Operation targeting a specific node (%s) is not supported in "
"a CIB transaction",
host);
return EOPNOTSUPP;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Append a CIB request to a CIB transaction
*
* \param[in,out] cib CIB client whose transaction to extend
* \param[in,out] request Request to add to transaction
*
* \return Legacy Pacemaker return code
*/
int
cib__extend_transaction(cib_t *cib, xmlNode *request)
{
int rc = pcmk_rc_ok;
CRM_ASSERT((cib != NULL) && (request != NULL));
rc = validate_transaction_request(request);
if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
rc = pcmk_rc_no_transaction;
}
if (rc == pcmk_rc_ok) {
add_node_copy(cib->transaction, request);
} else {
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *client_id = NULL;
cib->cmds->client_id(cib, NULL, &client_id);
crm_err("Failed to add '%s' operation to transaction for client %s: %s",
op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
crm_log_xml_info(request, "failed");
}
return pcmk_rc2legacy(rc);
}
void
cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
{
xmlNode *output = NULL;
cib_callback_client_t *blob = NULL;
if (msg != NULL) {
crm_element_value_int(msg, F_CIB_RC, &rc);
crm_element_value_int(msg, F_CIB_CALLID, &call_id);
output = get_message_xml(msg, F_CIB_CALLDATA);
}
blob = cib__lookup_id(call_id);
if (blob == NULL) {
crm_trace("No callback found for call %d", call_id);
}
if (cib == NULL) {
crm_debug("No cib object supplied");
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
crm_trace("Invoking callback %s for call %d",
pcmk__s(blob->id, "without ID"), call_id);
blob->callback(msg, call_id, rc, output, blob->user_data);
} else if (cib && cib->op_callback == NULL && rc != pcmk_ok) {
crm_warn("CIB command failed: %s", pcmk_strerror(rc));
crm_log_xml_debug(msg, "Failed CIB Update");
}
/* This may free user_data, so do it after the callback */
if (blob) {
remove_cib_op_callback(call_id, FALSE);
}
if (cib && cib->op_callback != NULL) {
crm_trace("Invoking global callback for call %d", call_id);
cib->op_callback(msg, call_id, rc, output);
}
crm_trace("OP callback activated for %d", call_id);
}
void
cib_native_notify(gpointer data, gpointer user_data)
{
xmlNode *msg = user_data;
cib_notify_client_t *entry = data;
const char *event = NULL;
if (msg == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(msg, F_SUBTYPE);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->callback == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->callback(event, msg);
crm_trace("Callback invoked...");
}
static pcmk__cluster_option_t cib_opts[] = {
/* name, legacy name, type, allowed values,
* default value, validator,
* short description,
* long description
*/
{
PCMK_OPT_ENABLE_ACL, NULL, "boolean", NULL,
"false", pcmk__valid_boolean,
N_("Enable Access Control Lists (ACLs) for the CIB"),
NULL
},
{
PCMK_OPT_CLUSTER_IPC_LIMIT, NULL, "integer", NULL,
"500", pcmk__valid_positive_int,
N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
N_("Raise this if log has \"Evicting client\" messages for cluster daemon"
" PIDs (a good value is the number of resources in the cluster"
" multiplied by the number of nodes).")
},
};
void
cib_metadata(void)
{
const char *desc_short = "Cluster Information Base manager options";
const char *desc_long = "Cluster options used by Pacemaker's Cluster "
"Information Base manager";
gchar *s = pcmk__format_option_metadata("pacemaker-based", desc_short,
desc_long, cib_opts,
PCMK__NELEM(cib_opts));
printf("%s", s);
g_free(s);
}
static void
verify_cib_options(GHashTable *options)
{
pcmk__validate_cluster_options(options, cib_opts, PCMK__NELEM(cib_opts));
}
const char *
cib_pref(GHashTable * options, const char *name)
{
return pcmk__cluster_option(options, cib_opts, PCMK__NELEM(cib_opts),
name);
}
gboolean
cib_read_config(GHashTable * options, xmlNode * current_cib)
{
xmlNode *config = NULL;
crm_time_t *now = NULL;
if (options == NULL || current_cib == NULL) {
return FALSE;
}
now = crm_time_new(NULL);
g_hash_table_remove_all(options);
config = pcmk_find_cib_element(current_cib, XML_CIB_TAG_CRMCONFIG);
if (config) {
pe_unpack_nvpairs(current_cib, config, XML_CIB_TAG_PROPSET, NULL,
options, CIB_OPTIONS_FIRST, TRUE, now, NULL);
}
verify_cib_options(options);
crm_time_free(now);
return TRUE;
}
int
cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name)
{
int (*delegate) (cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name) =
cib->delegate_fn;
if(user_name == NULL) {
user_name = getenv("CIB_user");
}
return delegate(cib, op, host, section, data, output_data, call_options, user_name);
}
/*!
* \brief Apply a CIB update patch to a given CIB
*
* \param[in] event CIB update patch
* \param[in] input CIB to patch
* \param[out] output Resulting CIB after patch
* \param[in] level Log the patch at this log level (unless LOG_CRIT)
*
* \return Legacy Pacemaker return code
* \note sbd calls this function
*/
int
cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
int level)
{
int rc = pcmk_err_generic;
xmlNode *diff = NULL;
CRM_ASSERT(event);
CRM_ASSERT(input);
CRM_ASSERT(output);
crm_element_value_int(event, F_CIB_RC, &rc);
diff = get_message_xml(event, F_CIB_UPDATE_RESULT);
if (rc < pcmk_ok || diff == NULL) {
return rc;
}
if (level > LOG_CRIT) {
pcmk__log_xml_patchset(level, diff);
}
if (input != NULL) {
rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
NULL);
if (rc != pcmk_ok) {
crm_debug("Update didn't apply: %s (%d) %p",
pcmk_strerror(rc), rc, *output);
if (rc == -pcmk_err_old_data) {
crm_trace("Masking error, we already have the supplied update");
return pcmk_ok;
}
free_xml(*output);
*output = NULL;
return rc;
}
}
return rc;
}
#define log_signon_query_err(out, fmt, args...) do { \
if (out != NULL) { \
out->err(out, fmt, ##args); \
} else { \
crm_err(fmt, ##args); \
} \
} while (0)
int
cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
{
int rc = pcmk_rc_ok;
cib_t *cib_conn = NULL;
CRM_ASSERT(cib_object != NULL);
if (cib == NULL) {
cib_conn = cib_new();
} else {
if (*cib == NULL) {
*cib = cib_new();
}
cib_conn = *cib;
}
if (cib_conn == NULL) {
return ENOMEM;
}
if (cib_conn->state == cib_disconnected) {
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
}
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "Could not connect to the CIB: %s",
pcmk_rc_str(rc));
goto done;
}
if (out != NULL) {
out->transient(out, "Querying CIB...");
}
rc = cib_conn->cmds->query(cib_conn, NULL, cib_object,
cib_scope_local|cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
}
done:
if (cib == NULL) {
cib__clean_up_connection(&cib_conn);
}
if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
return pcmk_rc_no_input;
}
return rc;
}
int
cib__clean_up_connection(cib_t **cib)
{
int rc;
if (*cib == NULL) {
return pcmk_rc_ok;
}
rc = (*cib)->cmds->signoff(*cib);
cib_delete(*cib);
*cib = NULL;
return pcmk_legacy2rc(rc);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/cib/util_compat.h>
const char *
get_object_path(const char *object_type)
{
return pcmk_cib_xpath_for(object_type);
}
const char *
get_object_parent(const char *object_type)
{
return pcmk_cib_parent_name_for(object_type);
}
xmlNode *
get_object_root(const char *object_type, xmlNode *the_root)
{
return pcmk_find_cib_element(the_root, object_type);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/digest.c b/lib/common/digest.c
index a58f8555db..9d038480a0 100644
--- a/lib/common/digest.c
+++ b/lib/common/digest.c
@@ -1,278 +1,278 @@
/*
* Copyright 2015-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <md5.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#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__xml2text(xml, 0, buffer, 0);
g_string_append_c(buffer, '\n');
return buffer;
}
/*!
* \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
* \param[in] ignored Not used
*
* \return Newly allocated string containing digest
* \note Example return value: "c048eae664dba840e1d2060f00299e9d"
*/
static char *
calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored)
{
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, free_xml(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);
free_xml(copy);
return digest;
}
/*!
* \brief Calculate and return v2 digest of XML tree
*
* \param[in] source Root of XML to digest
* \param[in] do_filter Whether to filter certain XML attributes
*
* \return Newly allocated string containing digest
*/
static char *
calculate_xml_digest_v2(const xmlNode *source, gboolean do_filter)
{
char *digest = NULL;
GString *buffer = g_string_sized_new(1024);
crm_trace("Begin digest %s", do_filter?"filtered":"");
pcmk__xml2text(source, (do_filter? pcmk__xml_fmt_filtered : 0), buffer, 0);
CRM_ASSERT(buffer != NULL);
digest = crm_md5sum((const char *) buffer->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, XML_ATTR_GENERATION_ADMIN),
+ crm_element_value(source, PCMK_XA_ADMIN_EPOCH),
crm_element_value(source, PCMK_XA_EPOCH),
crm_element_value(source, XML_ATTR_NUMUPDATES),
trace_file);
save_xml_to_file(source, "digest input", trace_file);
free(trace_file);
},
{}
);
g_string_free(buffer, TRUE);
crm_trace("End digest");
return digest;
}
/*!
* \brief Calculate and return digest of XML tree, suitable for storing on disk
*
* \param[in] input Root of XML to digest
*
* \return Newly allocated string containing digest
*/
char *
calculate_on_disk_digest(xmlNode *input)
{
/* Always use the v1 format for on-disk digests
* a) it's a compatibility nightmare
* b) we only use this once at startup, all other
* invocations are in a separate child process
*/
return calculate_xml_digest_v1(input, FALSE, FALSE);
}
/*!
* \brief Calculate and return digest of XML operation
*
* \param[in] input Root of XML to digest
* \param[in] version Unused
*
* \return Newly allocated string containing digest
*/
char *
calculate_operation_digest(xmlNode *input, const char *version)
{
/* We still need the sorting for operation digests */
return calculate_xml_digest_v1(input, TRUE, FALSE);
}
/*!
* \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, do_filter);
}
crm_trace("Using v2 digest algorithm for %s",
pcmk__s(version, "unknown feature set"));
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 = calculate_on_disk_digest(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[] = {
XML_ATTR_ORIGIN,
XML_CIB_ATTR_WRITTEN,
XML_ATTR_UPDATE_ORIG,
XML_ATTR_UPDATE_CLIENT,
XML_ATTR_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)
{
int lpc = 0, len = 0;
char *digest = NULL;
unsigned char raw_digest[MD5_DIGEST_SIZE];
if (buffer == NULL) {
buffer = "";
}
len = strlen(buffer);
crm_trace("Beginning digest of %d bytes", len);
digest = malloc(2 * MD5_DIGEST_SIZE + 1);
if (digest) {
md5_buffer(buffer, len, raw_digest);
for (lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
sprintf(digest + (2 * lpc), "%02x", raw_digest[lpc]);
}
digest[(2 * MD5_DIGEST_SIZE)] = 0;
crm_trace("Digest %s.", digest);
} else {
crm_err("Could not create digest");
}
return digest;
}
diff --git a/lib/common/patchset.c b/lib/common/patchset.c
index 68646aea6d..96a438c54e 100644
--- a/lib/common/patchset.c
+++ b/lib/common/patchset.c
@@ -1,1503 +1,1503 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <bzlib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
#include "crmcommon_private.h"
static xmlNode *subtract_xml_comment(xmlNode *parent, xmlNode *left,
xmlNode *right, gboolean *changed);
/* 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 = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "create");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
crm_xml_add_int(change, XML_DIFF_POSITION, position);
add_node_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 = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "modify");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
change = create_xml_node(change, XML_DIFF_LIST);
g_string_free(xpath, TRUE);
}
}
attr = create_xml_node(change, XML_DIFF_ATTR);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
if (nodepriv->flags & pcmk__xf_deleted) {
crm_xml_add(attr, XML_DIFF_OP, "unset");
} else {
crm_xml_add(attr, XML_DIFF_OP, "set");
value = pcmk__xml_attr_value(pIter);
crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
}
}
if (change) {
xmlNode *result = NULL;
change = create_xml_node(change->parent, XML_DIFF_RESULT);
result = create_xml_node(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, ID(xml), pcmk__xml_position(xml, pcmk__xf_skip));
if (xpath != NULL) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "move");
crm_xml_add(change, XML_DIFF_PATH, (const char *) xpath->str);
crm_xml_add_int(change, XML_DIFF_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 = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
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,
"/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION) != NULL) {
return TRUE;
}
}
}
return FALSE;
}
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[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
if (local_diff == NULL) {
crm_trace("Nothing to do");
return;
}
tag = XML_TAG_DIFF_REMOVED;
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(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 = XML_TAG_DIFF_ADDED;
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(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");
}
static xmlNode *
xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config,
bool suppress)
{
xmlNode *patchset = diff_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[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
CRM_ASSERT(target);
if (!xml_document_dirty(target)) {
return NULL;
}
CRM_ASSERT(target->doc);
docpriv = target->doc->_private;
patchset = create_xml_node(NULL, XML_TAG_DIFF);
crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
version = create_xml_node(patchset, XML_DIFF_VERSION);
v = create_xml_node(version, XML_DIFF_VSOURCE);
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 = create_xml_node(version, XML_DIFF_VTARGET);
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 = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "delete");
crm_xml_add(change, XML_DIFF_PATH, deleted_obj->path);
if (deleted_obj->position >= 0) {
crm_xml_add_int(change, XML_DIFF_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, XML_ATTR_NUMUPDATES, "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, XML_ATTR_NUMUPDATES, &counter);
crm_trace("Status changed %d - %d %s", format, counter,
crm_element_value(source, XML_ATTR_NUMUPDATES));
crm_xml_add_int(target, XML_ATTR_NUMUPDATES, (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:
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);
crm_xml_add(patch, PCMK__XA_DIGEST, digest);
free(digest);
return;
}
// Return true if attribute name is not "id"
static bool
not_id(xmlAttrPtr attr, void *user_data)
{
return strcmp((const char *) attr->name, XML_ATTR_ID) != 0;
}
// Apply the removals section of an 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_xml_comment(target->parent, target, patch, &dummy);
}
CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return);
CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return);
// Check for XML_DIFF_MARKER in a child
id = crm_element_value_copy(target, XML_ATTR_ID);
value = crm_element_value(patch, XML_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);
free_xml(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);
}
// Apply the additions section of an 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 XML_DIFF_MARKER in a child
name = (const char *) patch->name;
value = crm_element_value(patch, XML_DIFF_MARKER);
if ((target == NULL) && (value != NULL)
&& (strcmp(value, "added:top") == 0)) {
id = ID(patch);
crm_trace("We are the root of the addition: %s.id=%s", name, id);
add_node_copy(parent, patch);
return;
} else if (target == NULL) {
id = 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(ID(target), ID(patch), pcmk__str_casei), 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);
xml_remove_prop(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:
label = added? XML_TAG_DIFF_ADDED : XML_TAG_DIFF_REMOVED;
*patch_node = find_xml_node(patchset, label, FALSE);
cib_node = find_xml_node(*patch_node, "cib", FALSE);
if (cib_node != NULL) {
*patch_node = cib_node;
}
break;
case 2:
label = added? "target" : "source";
*patch_node = find_xml_node(patchset, "version", FALSE);
*patch_node = find_xml_node(*patch_node, label, FALSE);
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[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
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[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
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;
}
/*!
* \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 = find_xml_node(patchset, XML_TAG_DIFF_ADDED, FALSE);
xmlNode *removed = find_xml_node(patchset, XML_TAG_DIFF_REMOVED, FALSE);
xmlNode *old = copy_xml(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_diff_markers(xml); // Purge prior to checking digest
free_xml(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 = 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 = calloc(key_len, sizeof(char));
CRM_ASSERT(remainder != NULL);
section = calloc(key_len, sizeof(char));
CRM_ASSERT(section != NULL);
id = calloc(key_len, sizeof(char));
CRM_ASSERT(id != NULL);
tag = calloc(key_len, sizeof(char));
CRM_ASSERT(tag != NULL);
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, "%[^[][@" XML_ATTR_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:
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, XML_DIFF_POSITION, &position_a);
crm_element_value_int(change_obj_b->change, XML_DIFF_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, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
int position = -1;
if (op == NULL) {
continue;
}
crm_trace("Processing %s %s", change->name, op);
// "delete" changes for XML comments are generated with "position"
if (strcmp(op, "delete") == 0) {
crm_element_value_int(change, XML_DIFF_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, "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 ((strcmp(op, "create") == 0) || (strcmp(op, "move") == 0)) {
// Delay the adding of a "create" object
xml_change_obj_t *change_obj = calloc(1, sizeof(xml_change_obj_t));
CRM_ASSERT(change_obj != NULL);
change_obj->change = change;
change_obj->match = match;
change_objs = g_list_append(change_objs, change_obj);
if (strcmp(op, "move") == 0) {
// Temporarily put the "move" object after the last sibling
if ((match->parent != NULL) && (match->parent->last != NULL)) {
xmlAddNextSibling(match->parent->last, match);
}
}
} else if (strcmp(op, "delete") == 0) {
free_xml(match);
} else if (strcmp(op, "modify") == 0) {
xmlNode *attrs = NULL;
attrs = pcmk__xml_first_child(first_named_child(change,
XML_DIFF_RESULT));
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, XML_DIFF_OP);
xpath = crm_element_value(change, XML_DIFF_PATH);
crm_trace("Continue performing %s on %s with %p", op, xpath, match);
if (strcmp(op, "create") == 0) {
int position = 0;
xmlNode *child = NULL;
xmlNode *match_child = NULL;
match_child = match->children;
crm_element_value_int(change, XML_DIFF_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__mark_xml_created(child);
} else if (strcmp(op, "move") == 0) {
int position = 0;
crm_element_value_int(change, XML_DIFF_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, 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 = copy_xml(xml), {});
}
if (rc == pcmk_ok) {
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
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);
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);
}
free_xml(old);
return rc;
}
void
purge_diff_markers(xmlNode *a_node)
{
xmlNode *child = NULL;
CRM_CHECK(a_node != NULL, return);
xml_remove_prop(a_node, XML_DIFF_MARKER);
for (child = pcmk__xml_first_child(a_node); child != NULL;
child = pcmk__xml_next(child)) {
purge_diff_markers(child);
}
}
xmlNode *
diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress)
{
xmlNode *tmp1 = NULL;
xmlNode *diff = create_xml_node(NULL, XML_TAG_DIFF);
xmlNode *removed = create_xml_node(diff, XML_TAG_DIFF_REMOVED);
xmlNode *added = create_xml_node(diff, XML_TAG_DIFF_ADDED);
crm_xml_add(diff, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
if (suppress && (tmp1 != NULL) && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
if ((added->children == NULL) && (removed->children == NULL)) {
free_xml(diff);
diff = NULL;
}
return diff;
}
static xmlNode *
subtract_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 = add_node_copy(parent, left);
*changed = TRUE;
return deleted;
}
return NULL;
}
xmlNode *
subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right,
gboolean 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_xml_comment(parent, left, right, changed);
}
id = ID(left);
name = (const char *) left->name;
if (right == NULL) {
xmlNode *deleted = NULL;
crm_trace("Processing <%s " XML_ATTR_ID "=%s> (complete copy)",
name, id);
deleted = add_node_copy(parent, left);
crm_xml_add(deleted, XML_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 XML_DIFF_MARKER in a child
value = crm_element_value(right, XML_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 = create_xml_node(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_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, XML_ATTR_ID) == 0) {
// id already obtained when present ~ this case, so just reuse
xmlSetProp(diff, (pcmkXmlStr) XML_ATTR_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 " XML_ATTR_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 " XML_ATTR_ID "=%s>",
prop_name, left_value, right_val, name, id);
crm_xml_add(diff, prop_name, left_value);
}
}
}
}
if (!*changed) {
free_xml(diff);
return NULL;
} else if (!full && (id != NULL)) {
crm_xml_add(diff, XML_ATTR_ID, id);
}
done:
return diff;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
gboolean
apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml)
{
gboolean result = TRUE;
int root_nodes_seen = 0;
const char *digest = crm_element_value(diff, PCMK__XA_DIGEST);
const char *version = crm_element_value(diff, PCMK_XA_CRM_FEATURE_SET);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(diff, XML_TAG_DIFF_ADDED, FALSE);
xmlNode *removed = find_xml_node(diff, XML_TAG_DIFF_REMOVED, FALSE);
CRM_CHECK(new_xml != NULL, return FALSE);
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, result = FALSE);
if (root_nodes_seen == 0) {
*new_xml = subtract_xml_object(NULL, old_xml, child_diff, FALSE,
NULL, NULL);
}
root_nodes_seen++;
}
if (root_nodes_seen == 0) {
*new_xml = copy_xml(old_xml);
} else if (root_nodes_seen > 1) {
crm_err("(-) Diffs cannot contain more than one change set... saw %d",
root_nodes_seen);
result = FALSE;
}
root_nodes_seen = 0;
crm_trace("Addition Phase");
if (result) {
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, result = FALSE);
if (root_nodes_seen == 0) {
pcmk__xml_update(NULL, *new_xml, child_diff, true);
}
root_nodes_seen++;
}
}
if (root_nodes_seen > 1) {
crm_err("(+) Diffs cannot contain more than one change set... saw %d",
root_nodes_seen);
result = FALSE;
} else if (result && (digest != NULL)) {
char *new_digest = NULL;
purge_diff_markers(*new_xml); // Purge now so diff is ok
new_digest = calculate_xml_versioned_digest(*new_xml, FALSE, TRUE,
version);
if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
crm_info("Digest mis-match: expected %s, calculated %s",
digest, new_digest);
result = FALSE;
pcmk__if_tracing(
{
save_xml_to_file(old_xml, "diff:original", NULL);
save_xml_to_file(diff, "diff:input", NULL);
save_xml_to_file(*new_xml, "diff:new", NULL);
},
{}
);
} else {
crm_trace("Digest matched: expected %s, calculated %s",
digest, new_digest);
}
free(new_digest);
} else if (result) {
purge_diff_markers(*new_xml); // Purge now so diff is ok
}
return result;
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c
index c9258a71f5..7c6d881040 100644
--- a/lib/common/patchset_display.c
+++ b/lib/common/patchset_display.c
@@ -1,519 +1,519 @@
/*
* 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 <crm_internal.h>
#include <crm/msg_xml.h>
#include "crmcommon_private.h"
/*!
* \internal
* \brief Output an XML patchset header
*
* This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
* element and its children).
*
* All header lines contain three integers separated by dots, of the form
* <tt>{0}.{1}.{2}</tt>:
- * * \p {0}: \p XML_ATTR_GENERATION_ADMIN
+ * * \p {0}: \c PCMK_XA_ADMIN_EPOCH
* * \p {1}: \c PCMK_XA_EPOCH
* * \p {2}: \p XML_ATTR_NUMUPDATES
*
* Lines containing \p "---" describe removals and end with the patch format
* number. Lines containing \p "+++" describe additions and end with the patch
* digest.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = pcmk_rc_no_output;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(patchset, add, del);
if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
const char *fmt = crm_element_value(patchset, PCMK_XA_FORMAT);
const char *digest = crm_element_value(patchset, PCMK__XA_DIGEST);
out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
rc = out->info(out, "Diff: +++ %d.%d.%d %s",
add[0], add[1], add[2], digest);
} else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
rc = out->info(out, "Local-only Change: %d.%d.%d",
add[0], add[1], add[2]);
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of XML additions or removals
*
* \param[in,out] out Output object
* \param[in] prefix String to prepend to every line of output
* \param[in] data XML node to output
* \param[in] depth Current indentation level
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
const xmlNode *data, int depth, uint32_t options)
{
if ((data->children == NULL)
|| (crm_element_value(data, XML_DIFF_MARKER) != NULL)) {
// Found a change; clear the pcmk__xml_fmt_diff_short option if set
options &= ~pcmk__xml_fmt_diff_short;
if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
prefix = PCMK__XML_PREFIX_CREATED;
} else { // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
prefix = PCMK__XML_PREFIX_DELETED;
}
}
if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
int rc = pcmk_rc_no_output;
// Keep looking for the actual change
for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child,
depth + 1, options);
rc = pcmk__output_select_rc(rc, temp_rc);
}
return rc;
}
return pcmk__xml_show(out, prefix, data, depth,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 1)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
uint32_t options)
{
const xmlNode *removed = NULL;
const xmlNode *added = NULL;
const xmlNode *child = NULL;
bool is_first = true;
int rc = xml_show_patchset_header(out, patchset);
/* It's not clear whether "- " or "+ " ever does *not* get overridden by
* PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
* However, v1 patchsets can only exist during rolling upgrades from
* Pacemaker 1.1.11, so not worth worrying about.
*/
removed = find_xml_node(patchset, XML_TAG_DIFF_REMOVED, FALSE);
for (child = pcmk__xml_first_child(removed); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0,
options
|pcmk__xml_fmt_diff_minus);
rc = pcmk__output_select_rc(rc, temp_rc);
if (is_first) {
is_first = false;
} else {
rc = pcmk__output_select_rc(rc, out->info(out, " --- "));
}
}
is_first = true;
added = find_xml_node(patchset, XML_TAG_DIFF_ADDED, FALSE);
for (child = pcmk__xml_first_child(added); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0,
options
|pcmk__xml_fmt_diff_plus);
rc = pcmk__output_select_rc(rc, temp_rc);
if (is_first) {
is_first = false;
} else {
rc = pcmk__output_select_rc(rc, out->info(out, " +++ "));
}
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 2)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = xml_show_patchset_header(out, patchset);
int temp_rc = pcmk_rc_no_output;
for (const xmlNode *change = pcmk__xml_first_child(patchset);
change != NULL; change = pcmk__xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if (op == NULL) {
continue;
}
if (strcmp(op, "create") == 0) {
char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
xpath);
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
rc = pcmk__output_select_rc(rc, temp_rc);
// Overwrite all except the first two characters with spaces
for (char *ch = prefix + 2; *ch != '\0'; ch++) {
*ch = ' ';
}
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
rc = pcmk__output_select_rc(rc, temp_rc);
free(prefix);
} else if (strcmp(op, "move") == 0) {
const char *position = crm_element_value(change, XML_DIFF_POSITION);
temp_rc = out->info(out,
PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
xpath, position);
rc = pcmk__output_select_rc(rc, temp_rc);
} else if (strcmp(op, "modify") == 0) {
xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
GString *buffer_set = NULL;
GString *buffer_unset = NULL;
for (const xmlNode *child = pcmk__xml_first_child(clist);
child != NULL; child = pcmk__xml_next(child)) {
const char *name = crm_element_value(child, "name");
op = crm_element_value(child, XML_DIFF_OP);
if (op == NULL) {
continue;
}
if (strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, "value");
pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
pcmk__g_strcat(buffer_set, name, "=", value, NULL);
} else if (strcmp(op, "unset") == 0) {
pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
g_string_append(buffer_unset, name);
}
}
if (buffer_set != NULL) {
temp_rc = out->info(out, "+ %s: %s", xpath, buffer_set->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_set, TRUE);
}
if (buffer_unset != NULL) {
temp_rc = out->info(out, "-- %s: %s",
xpath, buffer_unset->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_unset, TRUE);
}
} else if (strcmp(op, "delete") == 0) {
int position = -1;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if (position >= 0) {
temp_rc = out->info(out, "-- %s (%d)", xpath, position);
} else {
temp_rc = out->info(out, "-- %s", xpath);
}
rc = pcmk__output_select_rc(rc, temp_rc);
}
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_default(pcmk__output_t *out, va_list args)
{
const xmlNode *patchset = va_arg(args, const xmlNode *);
int format = 1;
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
case 2:
return xml_show_patchset_v2(out, patchset);
default:
crm_err("Unknown patch format: %d", format);
return pcmk_rc_bad_xml_patch;
}
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_log(pcmk__output_t *out, va_list args)
{
static struct qb_log_callsite *patchset_cs = NULL;
const xmlNode *patchset = va_arg(args, const xmlNode *);
uint8_t log_level = pcmk__output_get_log_level(out);
int format = 1;
if (log_level == LOG_NEVER) {
return pcmk_rc_no_output;
}
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
log_level, __LINE__,
crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
// Nothing would be logged, so skip all the work
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
if (log_level < LOG_DEBUG) {
return xml_show_patchset_v1(out, patchset,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_diff_short);
}
return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
case 2:
return xml_show_patchset_v2(out, patchset);
default:
crm_err("Unknown patch format: %d", format);
return pcmk_rc_bad_xml_patch;
}
}
/*!
* \internal
* \brief Output an XML patchset
*
* This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
* children) without modification, as a CDATA block.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_xml(pcmk__output_t *out, va_list args)
{
const xmlNode *patchset = va_arg(args, const xmlNode *);
if (patchset != NULL) {
char *buf = dump_xml_formatted_with_text(patchset);
out->output_xml(out, "xml-patchset", buf);
free(buf);
return pcmk_rc_ok;
}
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "xml-patchset", "default", xml_patchset_default },
{ "xml-patchset", "log", xml_patchset_log },
{ "xml-patchset", "xml", xml_patchset_xml },
{ NULL, NULL, NULL }
};
/*!
* \internal
* \brief Register the formatting functions for XML patchsets
*
* \param[in,out] out Output object
*/
void
pcmk__register_patchset_messages(pcmk__output_t *out) {
pcmk__register_messages(out, fmt_functions);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/xml_compat.h>
void
xml_log_patchset(uint8_t log_level, const char *function,
const xmlNode *patchset)
{
/* This function has some duplication relative to the message functions.
* This way, we can maintain the const xmlNode * in the signature. The
* message functions must be non-const. They have to support XML output
* objects, which must make a copy of a the patchset, requiring a non-const
* function call.
*
* In contrast, this legacy function doesn't need to support XML output.
*/
static struct qb_log_callsite *patchset_cs = NULL;
pcmk__output_t *out = NULL;
int format = 1;
int rc = pcmk_rc_no_output;
switch (log_level) {
case LOG_NEVER:
return;
case LOG_STDOUT:
CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
break;
default:
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__,
"xml-patchset", log_level,
__LINE__, crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level,
crm_trace_nonlog)) {
return;
}
CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
pcmk__output_set_log_level(out, log_level);
break;
}
if (patchset == NULL) {
// Should come after the LOG_NEVER check
crm_trace("Empty patch");
goto done;
}
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
switch (format) {
case 1:
if (log_level < LOG_DEBUG) {
rc = xml_show_patchset_v1(out, patchset,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_diff_short);
} else { // Note: LOG_STDOUT > LOG_DEBUG
rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
}
break;
case 2:
rc = xml_show_patchset_v2(out, patchset);
break;
default:
crm_err("Unknown patch format: %d", format);
rc = pcmk_rc_bad_xml_patch;
break;
}
done:
out->finish(out, pcmk_rc2exitc(rc), true, NULL);
pcmk__output_free(out);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/tools/cibadmin.c b/tools/cibadmin.c
index a8920eb290..892fed2945 100644
--- a/tools/cibadmin.c
+++ b/tools/cibadmin.c
@@ -1,952 +1,952 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <crm/cib/internal.h>
#include <pacemaker-internal.h>
#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 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, XML_ATTR_ID);
if (pcmk__str_eq((const char *)xml->name, "xpath-query", pcmk__str_casei)) {
xmlNode *child = NULL;
for (child = xml->children; child; child = child->next) {
print_xml_output(child);
}
} else if (id) {
printf("%s\n", id);
}
} else {
char *buffer = dump_xml_formatted(xml);
fprintf(stdout, "%s", buffer);
free(buffer);
}
}
// 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,
XML_CIB_TAG_CONFIGURATION,
XML_CIB_TAG_NODES,
XML_CIB_TAG_RESOURCES,
XML_CIB_TAG_CONSTRAINTS,
XML_CIB_TAG_CRMCONFIG,
XML_CIB_TAG_RSCCONFIG,
XML_CIB_TAG_OPCONFIG,
XML_CIB_TAG_ACLS,
XML_TAG_FENCING_TOPOLOGY,
XML_CIB_TAG_TAGS,
XML_CIB_TAG_ALERTS,
XML_CIB_TAG_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, "
"<" XML_ATTR_OP " " XML_ATTR_ID "=\"rsc1_op1\" "
XML_ATTR_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: " XML_CIB_TAG_CONFIGURATION ", " XML_CIB_TAG_NODES
", " XML_CIB_TAG_RESOURCES ", " XML_CIB_TAG_CONSTRAINTS
", " XML_CIB_TAG_CRMCONFIG ", " XML_CIB_TAG_RSCCONFIG ",\n"
INDENT " " XML_CIB_TAG_OPCONFIG ", " XML_CIB_TAG_ACLS
", " XML_TAG_FENCING_TOPOLOGY ", " XML_CIB_TAG_TAGS
", " XML_CIB_TAG_ALERTS ", " XML_CIB_TAG_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, "
"\"/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION
"/" XML_CIB_TAG_RESOURCES "/" XML_CIB_TAG_INCARNATION
"[@" XML_ATTR_ID "='dummy-clone']"
"/" XML_CIB_TAG_RESOURCE "[@" XML_ATTR_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]" },
{ "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 " XML_CIB_TAG_CRMCONFIG "\n\n"
"Query all '" XML_RSC_ATTR_TARGET_ROLE "' settings:\n\n"
"\t# cibadmin --query --xpath "
"\"//" XML_CIB_TAG_NVPAIR
"[@" XML_NVPAIR_ATTR_NAME "='" XML_RSC_ATTR_TARGET_ROLE"']\""
"\n\n"
"Remove all '" XML_RSC_ATTR_MANAGED "' settings:\n\n"
"\t# cibadmin --delete-all --xpath "
"\"//" XML_CIB_TAG_NVPAIR
"[@" XML_NVPAIR_ATTR_NAME "='" XML_RSC_ATTR_MANAGED "']\"\n\n"
"Remove the resource named 'old':\n\n"
"\t# cibadmin --delete --xml-text "
"'<" XML_CIB_TAG_RESOURCE " " XML_ATTR_ID "=\"old\"/>'\n\n"
"Remove all resources from the configuration:\n\n"
"\t# cibadmin --replace --scope " XML_CIB_TAG_RESOURCES
" --xml-text '<" XML_CIB_TAG_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 " XML_CIB_TAG_CONSTRAINTS " section of configuration with "
"contents of $HOME/constraints.xml:\n\n"
"\t# cibadmin --replace --scope " XML_CIB_TAG_CONSTRAINTS
" --xml-file $HOME/constraints.xml\n\n"
"Increase configuration version to prevent old configurations from "
"being loaded accidentally:\n\n"
"\t# cibadmin --modify --xml-text "
- "'<" XML_TAG_CIB " " XML_ATTR_GENERATION_ADMIN
- "=\"" XML_ATTR_GENERATION_ADMIN "++\"/>'\n\n"
+ "'<" XML_TAG_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, "<command>");
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 <op> failures <XML>
*
* 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
char *buf = NULL;
output = createEmptyCib(1);
crm_xml_add(output, PCMK_XA_VALIDATE_WITH, options.validate_with);
buf = dump_xml_formatted(output);
fprintf(stdout, "%s", buf);
free(buf);
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 = filename2xml(options.input_file);
source = options.input_file;
} else if (options.input_xml != NULL) {
input = string2xml(options.input_xml);
source = "input string";
} else if (options.input_stdin) {
source = "STDIN";
input = stdin2xml();
} 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 = calculate_on_disk_digest(input);
fprintf(stderr, "Digest: ");
fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
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);
fprintf(stderr, "Versioned (%s) digest: ", version);
fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
free(digest);
goto done;
}
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 (rc > 0) {
/* 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 if ((rc == -pcmk_err_schema_unchanged)
&& (strcmp(options.cib_action,
PCMK__CIB_REQUEST_UPGRADE) == 0)) {
report_schema_unchanged();
} else if (rc < 0) {
rc = pcmk_legacy2rc(rc);
crm_err("Call failed: %s", pcmk_rc_str(rc));
fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
if (rc == pcmk_rc_schema_validation) {
if (strcmp(options.cib_action, PCMK__CIB_REQUEST_UPGRADE) == 0) {
xmlNode *obj = NULL;
int version = 0;
if (the_cib->cmds->query(the_cib, NULL, &obj,
options.cmd_options) == pcmk_ok) {
update_validation(&obj, &version, 0, TRUE, FALSE);
}
free_xml(obj);
} else if (output) {
validate_xml_verbose(output);
}
}
exit_code = pcmk_rc2exitc(rc);
}
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);
free_xml(input);
free_xml(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, XML_TAG_CIB)) {
xmlNode *status = pcmk_find_cib_element(input, XML_CIB_TAG_STATUS);
if (status == NULL) {
create_xml_node(input, XML_CIB_TAG_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 788ad998fd..235524a81b 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,380 +1,380 @@
/*
* 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 <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output_internal.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
#define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
"or apply such an output as a patch"
struct {
gboolean apply;
gboolean as_cib;
gboolean no_version;
gboolean raw_1;
gboolean raw_2;
gboolean use_stdin;
char *xml_file_1;
char *xml_file_2;
} options;
gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static GOptionEntry original_xml_entries[] = {
{ "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_1,
"XML is contained in the named file",
"FILE" },
{ "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
"XML is contained in the supplied string",
"STRING" },
{ NULL }
};
static GOptionEntry operation_entries[] = {
{ "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_2,
"Compare the original XML to the contents of the named file",
"FILE" },
{ "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
"Compare the original XML with the contents of the supplied string",
"STRING" },
{ "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
"Patch the original XML with the contents of the named file",
"FILE" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
"Compare/patch the inputs as a CIB (includes versions details)",
NULL },
{ "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
"",
NULL },
{ "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
"Generate the difference without versions details",
NULL },
{ NULL }
};
gboolean
new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_2 = TRUE;
pcmk__str_update(&options.xml_file_2, optarg);
return TRUE;
}
gboolean
original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_1 = TRUE;
pcmk__str_update(&options.xml_file_1, optarg);
return TRUE;
}
gboolean
patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.apply = TRUE;
pcmk__str_update(&options.xml_file_2, optarg);
return TRUE;
}
static void
print_patch(xmlNode *patch)
{
char *buffer = dump_xml_formatted(patch);
printf("%s", buffer);
free(buffer);
fflush(stdout);
}
// \return Standard Pacemaker return code
static int
apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
{
xmlNode *output = copy_xml(input);
int rc = xml_apply_patchset(output, patch, as_cib);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc));
free_xml(output);
return rc;
}
if (output != NULL) {
const char *version;
char *buffer;
print_patch(output);
version = crm_element_value(output, PCMK_XA_CRM_FEATURE_SET);
buffer = calculate_xml_versioned_digest(output, FALSE, TRUE, version);
crm_trace("Digest: %s", pcmk__s(buffer, "<null>\n"));
free(buffer);
free_xml(output);
}
return pcmk_rc_ok;
}
static void
log_patch_cib_versions(xmlNode *patch)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
xml_patch_versions(patch, add, del);
fmt = crm_element_value(patch, 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 = find_xml_node(patch, "version", FALSE);
if (version_xml) {
free_xml(version_xml);
}
} else {
int i = 0;
const char *tags[] = {
XML_TAG_DIFF_REMOVED,
XML_TAG_DIFF_ADDED,
};
for (i = 0; i < PCMK__NELEM(tags); i++) {
xmlNode *tmp = NULL;
int lpc;
tmp = find_xml_node(patch, tags[i], FALSE);
if (tmp) {
for (lpc = 0; lpc < nvfields; lpc++) {
xml_remove_prop(tmp, vfields[lpc]);
}
tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE);
if (tmp) {
for (lpc = 0; lpc < nvfields; lpc++) {
xml_remove_prop(tmp, vfields[lpc]);
}
}
}
}
}
}
// \return Standard Pacemaker return code
static int
generate_patch(xmlNode *object_1, xmlNode *object_2, const char *xml_file_2,
gboolean as_cib, gboolean no_version)
{
const char *vfields[] = {
- XML_ATTR_GENERATION_ADMIN,
+ PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
XML_ATTR_NUMUPDATES,
};
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);
free_xml(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 = string2xml(options.xml_file_1);
} else if (options.use_stdin) {
fprintf(stderr, "Input first XML fragment:");
object_1 = stdin2xml();
} else if (options.xml_file_1 != NULL) {
object_1 = filename2xml(options.xml_file_1);
}
if (options.raw_2) {
object_2 = string2xml(options.xml_file_2);
} else if (options.use_stdin) {
fprintf(stderr, "Input second XML fragment:");
object_2 = stdin2xml();
} else if (options.xml_file_2 != NULL) {
object_2 = filename2xml(options.xml_file_2);
}
if (object_1 == NULL) {
fprintf(stderr, "Could not parse the first XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (object_2 == NULL) {
fprintf(stderr, "Could not parse the second XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (options.apply) {
rc = apply_patch(object_1, object_2, options.as_cib);
} else {
rc = generate_patch(object_1, object_2, options.xml_file_2, options.as_cib, options.no_version);
}
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
free(options.xml_file_1);
free(options.xml_file_2);
free_xml(object_1);
free_xml(object_2);
pcmk__output_and_clear_error(&error, NULL);
crm_exit(exit_code);
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:19 PM (4 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665490
Default Alt Text
(388 KB)

Event Timeline