Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3686566
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
181 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c
index 382a94f888..8c7e95f7de 100644
--- a/daemons/based/based_callbacks.c
+++ b/daemons/based/based_callbacks.c
@@ -1,1878 +1,1887 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <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;
struct digest_data {
char *nodes;
char *alerts;
char *status;
};
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,
- xmlNode **reply, xmlNode **cib_diff,
- bool privileged);
+ 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;
}
/*!
* \internal
* \brief Free a <tt>struct digest_data</tt> object's members
*
* \param[in,out] digests Object whose members to free
*
* \note This does not free the object itself, which may be stack-allocated.
*/
static void
free_digests(struct digest_data *digests)
{
if (digests != NULL) {
free(digests->nodes);
free(digests->alerts);
free(digests->status);
}
}
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);
based_discard_transaction(client);
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(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);
if (pcmk_is_set(call_options, cib_transaction)) {
// Append request to client's transaction
const char *call_id = crm_element_value(op_request, F_CIB_CALLID);
int rc = based_extend_transaction(cib_client, op_request, privileged);
xmlNode *reply = create_cib_reply(op, call_id, cib_client->id,
call_options, rc, NULL);
if (rc != pcmk_rc_ok) {
crm_warn("Could not extend transaction for client %s: %s",
pcmk__client_name(cib_client), pcmk_rc_str(rc));
}
do_local_notify(reply, cib_client->id,
pcmk_is_set(call_options, cib_sync_call), false);
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 if (pcmk__str_eq(type, T_CIB_REPLACE_NOTIFY, pcmk__str_casei)) {
bit = cib_notify_replace;
} else {
status = CRM_EX_INVALID_PARAM;
}
if (bit != 0) {
if (on_off) {
pcmk__set_client_flags(cib_client, bit);
} else {
pcmk__clear_client_flags(cib_client, bit);
}
}
pcmk__ipc_send_ack(cib_client, id, flags, "ack", NULL, status);
return;
}
cib_process_request(op_request, privileged, cib_client);
}
int32_t
cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged)
{
uint32_t id = 0;
uint32_t flags = 0;
int call_options = 0;
pcmk__client_t *cib_client = pcmk__find_client(c);
xmlNode *op_request = pcmk__client_data2xml(cib_client, data, &id, &flags);
if (op_request) {
crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options);
}
if (op_request == NULL) {
crm_trace("Invalid message from %p", c);
pcmk__ipc_send_ack(cib_client, id, flags, "nack", NULL, CRM_EX_PROTOCOL);
return 0;
} else if(cib_client == NULL) {
crm_trace("Invalid client %p", c);
return 0;
}
if (pcmk_is_set(call_options, cib_sync_call)) {
CRM_LOG_ASSERT(flags & crm_ipc_client_response);
CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */
cib_client->request_id = id; /* Reply only to the last one */
}
if (cib_client->name == NULL) {
const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME);
if (value == NULL) {
cib_client->name = pcmk__itoa(cib_client->pid);
} else {
cib_client->name = strdup(value);
if (crm_is_daemon_name(value)) {
pcmk__set_client_flags(cib_client, cib_is_daemon);
}
}
}
/* Allow cluster daemons more leeway before being evicted */
if (pcmk_is_set(cib_client->flags, cib_is_daemon)) {
const char *qmax = cib_config_lookup("cluster-ipc-limit");
if (pcmk__set_client_queue_max(cib_client, qmax)) {
crm_trace("IPC threshold for client %s[%u] is now %u",
pcmk__client_name(cib_client), cib_client->pid,
cib_client->queue_max);
}
}
crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id);
crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name);
CRM_LOG_ASSERT(cib_client->user != NULL);
pcmk__update_acl_user(op_request, F_CIB_USER, cib_client->user);
cib_common_callback_worker(id, flags, op_request, cib_client, privileged);
free_xml(op_request);
return 0;
}
static uint64_t ping_seq = 0;
static char *ping_digest = NULL;
static bool ping_modified_since = FALSE;
static gboolean
cib_digester_cb(gpointer data)
{
if (based_is_primary) {
char buffer[32];
xmlNode *ping = create_xml_node(NULL, "ping");
ping_seq++;
free(ping_digest);
ping_digest = NULL;
ping_modified_since = FALSE;
snprintf(buffer, 32, "%" PRIu64, ping_seq);
crm_trace("Requesting peer digests (%s)", buffer);
crm_xml_add(ping, F_TYPE, "cib");
crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING);
crm_xml_add(ping, F_CIB_PING_ID, buffer);
crm_xml_add(ping, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
send_cluster_message(NULL, crm_msg_cib, ping, TRUE);
free_xml(ping);
}
return FALSE;
}
static void
process_ping_reply(xmlNode *reply)
{
uint64_t seq = 0;
const char *host = crm_element_value(reply, F_ORIG);
xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA);
const char *seq_s = crm_element_value(pong, F_CIB_PING_ID);
const char *digest = crm_element_value(pong, XML_ATTR_DIGEST);
if (seq_s == NULL) {
crm_debug("Ignoring ping reply with no " F_CIB_PING_ID);
return;
} else {
long long seq_ll;
if (pcmk__scan_ll(seq_s, &seq_ll, 0LL) != pcmk_rc_ok) {
return;
}
seq = (uint64_t) seq_ll;
}
if(digest == NULL) {
crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host);
} else if(seq != ping_seq) {
crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host);
} else if(ping_modified_since) {
crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host);
} else {
const char *version = crm_element_value(pong, XML_ATTR_CRM_VERSION);
if(ping_digest == NULL) {
crm_trace("Calculating new digest");
ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version);
}
crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest);
if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) {
xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA);
crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p",
crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN),
crm_element_value(the_cib, XML_ATTR_GENERATION),
crm_element_value(the_cib, XML_ATTR_NUMUPDATES),
ping_digest, host,
remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_",
remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_",
remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_",
digest, remote_cib);
if(remote_cib && remote_cib->children) {
// Additional debug
xml_calculate_changes(the_cib, remote_cib);
pcmk__output_set_log_level(logger_out, LOG_INFO);
pcmk__xml_show_changes(logger_out, 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)
{
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 (pcmk_is_set(call_options, cib_transaction)) {
/* Process locally and don't notify local client.
*
* Reaching cib_process_request() with cib_transaction set means we're
* committing a transaction (always local and atomic).
*/
*process = TRUE;
*needs_reply = FALSE;
*local_notify = FALSE;
*needs_forward = FALSE;
return;
}
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 %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 (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) {
/* sync_our_cib() sets F_CIB_ISREPLY */
if (reply_to) {
delegated = reply_to;
}
goto skip_is_reply;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL,
pcmk__str_none)) {
// Nothing to do
} else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
process_ping_reply(request);
return FALSE;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) {
/* Only the DC (node with the oldest software) should process
* this operation if F_CIB_SCHEMA_MAX is unset
*
* If the DC is happy it will then send out another
* PCMK__CIB_REQUEST_UPGRADE which will tell all nodes to do the actual
* upgrade.
*
* Except this time F_CIB_SCHEMA_MAX will be set which puts a
* limit on how far newer nodes will go
*/
const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX);
const char *upgrade_rc = crm_element_value(request, F_CIB_UPGRADE_RC);
crm_trace("Parsing %s operation%s for %s with max=%s and upgrade_rc=%s",
op, (is_reply? " reply" : ""),
(based_is_primary? "primary" : "secondary"),
(max? max : "none"), (upgrade_rc? upgrade_rc : "none"));
if (upgrade_rc != NULL) {
// Our upgrade request was rejected by DC, notify clients of result
crm_xml_add(request, F_CIB_RC, upgrade_rc);
} else if ((max == NULL) && based_is_primary) {
/* We are the DC, check if this upgrade is allowed */
goto skip_is_reply;
} else if(max) {
/* Ok, go ahead and upgrade to 'max' */
goto skip_is_reply;
} else {
// Ignore broadcast client requests when we're not DC
return FALSE;
}
} else if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
crm_info("Detected legacy %s global update from %s", op, originator);
send_sync_request(NULL);
legacy_mode = TRUE;
return FALSE;
} else if (is_reply
&& 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)) {
// @COMPAT: Legacy handling
crm_debug("Legacy handling of %s message from %s", op, originator);
*local_notify = FALSE;
if (reply_to == NULL) {
*process = TRUE;
}
return *process;
}
if(is_reply) {
crm_trace("Handling %s reply sent from %s to local clients", op, originator);
*process = FALSE;
*needs_reply = FALSE;
*local_notify = TRUE;
return TRUE;
}
skip_is_reply:
*process = TRUE;
*needs_reply = FALSE;
*local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei);
host = crm_element_value(request, F_CIB_HOST);
if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) {
crm_trace("Processing %s request sent to us from %s", op, originator);
*needs_reply = TRUE;
return TRUE;
} else if (host != NULL) {
/* this is for a specific instance and we're not it */
crm_trace("Ignoring %s operation for instance on %s", op, host);
return FALSE;
} else if(is_reply == FALSE && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) {
*needs_reply = TRUE;
}
crm_trace("Processing %s request sent to everyone by %s/%s on %s %s", op,
crm_element_value(request, F_CIB_CLIENTNAME),
crm_element_value(request, F_CIB_CALLID),
originator, (*local_notify)?"(notify)":"");
return TRUE;
}
static gboolean
parse_peer_options(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, XML_ATTR_DIGEST);
crm_element_value_int(result_diff, "format", &format);
cib_diff_version_details(result_diff,
&diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates,
&diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);
crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s",
diff_del_admin_epoch, diff_del_epoch, diff_del_updates,
diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest);
crm_xml_add(msg, F_CIB_ISREPLY, originator);
pcmk__xe_set_bool_attr(msg, F_CIB_GLOBAL_UPDATE, true);
crm_xml_add(msg, F_CIB_OPERATION, PCMK__CIB_REQUEST_APPLY_PATCH);
crm_xml_add(msg, F_CIB_USER, CRM_DAEMON_USER);
if (format == 1) {
CRM_ASSERT(digest != NULL);
}
add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff);
crm_log_xml_explicit(msg, "copy");
return send_cluster_message(NULL, crm_msg_cib, msg, TRUE);
} else if (originator != NULL) {
/* send reply via HA to originating node */
crm_trace("Sending request result to %s only", originator);
crm_xml_add(msg, F_CIB_ISREPLY, originator);
return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE);
}
return FALSE;
}
/*!
* \internal
* \brief Handle an IPC or CPG message containing a request
*
* \param[in,out] request Request XML
* \param[in] privileged Whether privileged commands may be run
* (see cib_server_ops[] definition)
* \param[in] cib_client IPC client that sent request (or NULL if CPG)
*
* \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 = 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 (pcmk_is_set(call_options, cib_transaction)) {
/* We're committing a transaction now, processing each request. If
* cib_op_attr_transaction is not set, based_extend_transaction() should
* have blocked the request from being added to the transaction.
*/
CRM_CHECK(pcmk_is_set(operation->flags, cib_op_attr_transaction),
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;
}
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);
- rc = cib_process_command(request, operation, &op_reply, &result_diff,
- privileged);
+ 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",
the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0",
the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0");
finished = time(NULL);
if ((finished - now) > 3) {
crm_trace("%s operation took %lds to complete", op, (long)(finished - now));
crm_write_blackbox(0, NULL);
}
if (op_reply == NULL && (needs_reply || local_notify)) {
crm_err("Unexpected NULL reply to message");
crm_log_xml_err(request, "null reply");
needs_reply = FALSE;
local_notify = FALSE;
}
}
if (is_update && !cib_legacy_mode()) {
crm_trace("Completed pre-sync update from %s/%s/%s%s",
originator ? originator : "local", client_name, call_id,
local_notify?" with local notification":"");
} else if (!needs_reply || stand_alone) {
// This was a non-originating secondary update
crm_trace("Completed update as secondary");
} else if (cib_legacy_mode() &&
rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) {
gboolean broadcast = FALSE;
cib_local_bcast_num++;
crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num);
broadcast = send_peer_reply(request, result_diff, originator, TRUE);
if (broadcast && client_id && local_notify && op_reply) {
/* If we have been asked to sync the reply,
* and a bcast msg has gone out, we queue the local notify
* until we know the bcast message has been received */
local_notify = FALSE;
crm_trace("Queuing local %ssync notification for %s",
(call_options & cib_sync_call) ? "" : "a-", client_id);
queue_local_notify(op_reply, client_id,
pcmk_is_set(call_options, cib_sync_call),
(cib_client == NULL));
op_reply = NULL; /* the reply is queued, so don't free here */
}
} else if ((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 *root = NULL;
if (type == cib__op_apply_patch) {
if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
root = get_message_xml(request, F_CIB_UPDATE_DIFF);
} else {
root = get_message_xml(request, F_CIB_CALLDATA);
}
*section = NULL;
} else {
root = get_message_xml(request, F_CIB_CALLDATA);
*section = crm_element_value(request, F_CIB_SECTION);
}
if (root == NULL) {
return NULL;
}
if (pcmk__str_any_of(crm_element_name(root), F_CRM_DATA, F_CIB_CALLDATA,
NULL)) {
root = first_named_child(root, XML_TAG_CIB);
}
// Grab the specified section
if ((root != NULL) && (*section != NULL)
&& pcmk__str_eq(crm_element_name(root), XML_TAG_CIB, pcmk__str_none)) {
root = pcmk_find_cib_element(root, *section);
}
return root;
}
static char *
calculate_section_digest(const char *xpath, xmlNode * xml_obj)
{
xmlNode *xml_section = NULL;
if (xml_obj == NULL) {
return NULL;
}
xml_section = get_xpath_object(xpath, xml_obj, LOG_TRACE);
if (xml_section == NULL) {
return NULL;
}
return calculate_xml_versioned_digest(xml_section, FALSE, TRUE, CRM_FEATURE_SET);
}
#define XPATH_CONFIG "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION
#define XPATH_NODES XPATH_CONFIG "/" XML_CIB_TAG_NODES
#define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS
#define XPATH_STATUS "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS
/*!
* \internal
* \brief Calculate digests of CIB sections
*
* \param[in] cib CIB to get digests from
* \param[out] digests Where to store digests
*
* \note The caller is responsible for freeing the output argument's members
* using \p free_digests().
*/
static void
get_digests(xmlNode *cib, struct digest_data *digests)
{
digests->nodes = calculate_section_digest(XPATH_NODES, cib);
digests->alerts = calculate_section_digest(XPATH_ALERTS, cib);
digests->status = calculate_section_digest(XPATH_STATUS, cib);
}
/*!
* \internal
* \brief Determine which CIB sections were changed by an operation
*
* \param[in] before Digests before the operation
* \param[in] after Digests after the operation
*
* \return Group of <tt>enum cib_change_section_info</tt> flags indicating which
* sections have changed
*/
static uint32_t
get_change_sections(const struct digest_data *before,
const struct digest_data *after)
{
uint32_t change_sections = cib_change_section_none;
if (!pcmk__str_eq(before->nodes, after->nodes, pcmk__str_none)) {
pcmk__set_change_section(change_sections, cib_change_section_nodes);
}
if (!pcmk__str_eq(before->alerts, after->alerts, pcmk__str_none)) {
pcmk__set_change_section(change_sections, cib_change_section_alerts);
}
if (!pcmk__str_eq(before->status, after->status, pcmk__str_none)) {
pcmk__set_change_section(change_sections, cib_change_section_status);
}
return change_sections;
}
/*!
* \internal
* \brief Check whether a CIB replace notification should be sent
*
* A CIB replace notification should be sent after an operation if the operation
* is of an appropriate type, notifications are not disabled, and any of certain
* CIB sections has changed.
*
* \param[in] before_digests Digests before operation
* \param[in] after_cib Result CIB after operation
* \param[in] operation CIB operation
* \param[in] call_options Group of <tt>enum cib_call_options</tt> flags
* \param[out] change_sections Group of <tt>enum cib_change_section_info</tt>
* flags indicating which sections have changed
*
* \return \p true if a replace notification should be sent, or \p false
* otherwise
*/
static bool
should_replace_notify(const struct digest_data *before_digests,
xmlNode *after_cib, const cib_operation_t *operation,
int call_options, uint32_t *change_sections)
{
struct digest_data after_digests = { 0, };
*change_sections = cib_change_section_none;
if (!pcmk_is_set(operation->flags, cib_op_attr_replaces)
|| pcmk_is_set(call_options, cib_inhibit_notify)) {
return false;
}
get_digests(after_cib, &after_digests);
crm_trace("after-digest %s:%s:%s",
pcmk__s(after_digests.nodes, "(null)"),
pcmk__s(after_digests.alerts, "(null)"),
pcmk__s(after_digests.status, "(null)"));
*change_sections = get_change_sections(before_digests, &after_digests);
free_digests(&after_digests);
return (*change_sections != cib_change_section_none);
}
// 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,
- xmlNode **reply, xmlNode **cib_diff, bool privileged)
+ 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 *origin = 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;
struct digest_data before_digests = { 0, };
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, §ion);
if (!pcmk_is_set(operation->flags, cib_op_attr_modifies)) {
- rc = cib_perform_op(op, call_options, operation->fn, true, section,
+ 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;
}
// Calculate the digests of relevant sections before the operation
if (pcmk_is_set(operation->flags, cib_op_attr_replaces)
&& !pcmk_any_flags_set(call_options,
cib_dryrun|cib_inhibit_notify|cib_transaction)) {
get_digests(the_cib, &before_digests);
crm_trace("before-digest %s:%s:%s",
pcmk__s(before_digests.nodes, "(null)"),
pcmk__s(before_digests.alerts, "(null)"),
pcmk__s(before_digests.status, "(null)"));
}
// result_cib must not be modified after cib_perform_op() returns
- rc = cib_perform_op(op, call_options, operation->fn, false, section,
+ 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, "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)) {
uint32_t change_sections = cib_change_section_none;
if (result_cib != the_cib) {
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);
}
if (should_replace_notify(&before_digests, result_cib, operation,
call_options, &change_sections)) {
// @TODO: Should update argument be result_cib instead of the_cib?
cib_replace_notify(op, rc, call_id, client_id, client_name, origin,
the_cib, *cib_diff, change_sections);
}
/* Commit-transaction is special. We process it and activate the result
* only locally, but we still need to sync the updated CIB to all nodes.
* However, if we run the sync within cib_process_commit_transaction(),
* it will be rejected locally due to an older epoch in the replacement
* CIB. cib_perform_op() updates the epoch via xml_create_patchset()
* after cib_process_commit_transaction() has already returned.
*/
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT,
pcmk__str_none)) {
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, origin, input,
*cib_diff);
}
pcmk__output_set_log_level(logger_out, LOG_TRACE);
logger_out->message(logger_out, "xml-patchset", *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);
}
free_digests(&before_digests);
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) {
terminate_cib(__func__, 0);
return;
}
crm_info("Sending disconnect notification to %d peers...", active);
leaving = create_xml_node(NULL, "exit-notification");
crm_xml_add(leaving, F_TYPE, "cib");
crm_xml_add(leaving, F_CIB_OPERATION, PCMK__CIB_REQUEST_SHUTDOWN);
send_cluster_message(NULL, crm_msg_cib, leaving, TRUE);
free_xml(leaving);
g_timeout_add(EXIT_ESCALATION_MS, cib_force_exit, NULL);
}
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 (logger_out != NULL) {
logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
pcmk__output_free(logger_out);
logger_out = NULL;
}
if (fast > 0) {
/* Quit fast on error */
pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
crm_exit(fast);
} else if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) {
/* Quit via returning from the main loop. If fast == -1, we skip the
* disconnect here, and it will be done when the main loop returns
* (this allows the peer status callback to avoid messing with the
* peer caches).
*/
if (fast == 0) {
crm_cluster_disconnect(crm_cluster);
}
g_main_loop_quit(mainloop);
} else {
/* Quit via clean exit. Even the peer status callback can disconnect
* here, because we're not returning control to the caller. */
crm_cluster_disconnect(crm_cluster);
pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm);
crm_exit(CRM_EX_OK);
}
}
diff --git a/daemons/based/based_operation.c b/daemons/based/based_operation.c
index 38fe086b06..78a79c77f3 100644
--- a/daemons/based/based_operation.c
+++ b/daemons/based/based_operation.c
@@ -1,187 +1,82 @@
/*
* Copyright 2008-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU 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 <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/cluster.h>
#include <crm/common/xml.h>
#include <pacemaker-based.h>
-static const cib_operation_t cib_server_ops[] = {
- {
- PCMK__CIB_REQUEST_QUERY, cib__op_query,
- cib_op_attr_none,
- cib_process_query
- },
- {
- PCMK__CIB_REQUEST_MODIFY, cib__op_modify,
- cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction,
- cib_process_modify
- },
- {
- PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch,
- cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction,
- cib_server_process_diff
- },
- {
- 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,
- cib_process_replace_svr
- },
- {
- PCMK__CIB_REQUEST_CREATE, cib__op_create,
- cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction,
- cib_process_create
- },
- {
- PCMK__CIB_REQUEST_DELETE, cib__op_delete,
- cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction,
- cib_process_delete
- },
- {
- PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all,
- cib_op_attr_privileged,
- cib_process_sync
- },
- {
- PCMK__CIB_REQUEST_BUMP, cib__op_bump,
- cib_op_attr_modifies|cib_op_attr_privileged|cib_op_attr_transaction,
- cib_process_bump
- },
- {
- PCMK__CIB_REQUEST_ERASE, cib__op_erase,
- cib_op_attr_modifies
- |cib_op_attr_privileged
- |cib_op_attr_replaces
- |cib_op_attr_transaction,
- cib_process_erase
- },
- {
- PCMK__CIB_REQUEST_NOOP, cib__op_noop,
- cib_op_attr_none,
- cib_process_noop
- },
- {
- PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete,
- cib_op_attr_modifies|cib_op_attr_privileged,
- cib_process_delete_absolute
- },
- {
- PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade,
- cib_op_attr_modifies
- |cib_op_attr_privileged
- |cib_op_attr_writes_through
- |cib_op_attr_transaction,
- cib_process_upgrade_server
- },
- {
- PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary,
- cib_op_attr_privileged|cib_op_attr_local,
- cib_process_readwrite
- },
- {
- PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one,
- cib_op_attr_privileged,
- cib_process_sync_one
- },
- {
- // @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,
- cib_process_readwrite
- },
- {
- PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary,
- cib_op_attr_privileged,
- cib_process_readwrite
- },
- {
- PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown,
- cib_op_attr_privileged,
- cib_process_shutdown_req
- },
- {
- CRM_OP_PING, cib__op_ping,
- cib_op_attr_none,
- cib_process_ping
- },
+static const cib__op_fn_t cib_op_functions[] = {
+ [cib__op_abs_delete] = cib_process_delete_absolute,
+ [cib__op_apply_patch] = cib_server_process_diff,
+ [cib__op_bump] = cib_process_bump,
+ [cib__op_create] = cib_process_create,
+ [cib__op_delete] = cib_process_delete,
+ [cib__op_erase] = cib_process_erase,
+ [cib__op_is_primary] = cib_process_readwrite,
+ [cib__op_modify] = cib_process_modify,
+ [cib__op_noop] = cib_process_noop,
+ [cib__op_ping] = cib_process_ping,
+ [cib__op_primary] = cib_process_readwrite,
+ [cib__op_query] = cib_process_query,
+ [cib__op_replace] = cib_process_replace_svr,
+ [cib__op_secondary] = cib_process_readwrite,
+ [cib__op_shutdown] = cib_process_shutdown_req,
+ [cib__op_sync_all] = cib_process_sync,
+ [cib__op_sync_one] = cib_process_sync_one,
+ [cib__op_upgrade] = cib_process_upgrade_server,
/* PCMK__CIB_REQUEST_*_TRANSACT requests must be processed locally because
* they depend on the client table. Requests that manage transactions on
* other nodes would likely be problematic in many other ways as well.
*/
- {
- PCMK__CIB_REQUEST_INIT_TRANSACT, cib__op_init_transact,
- cib_op_attr_privileged|cib_op_attr_local,
- cib_process_init_transaction,
- },
- {
- PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
- cib_op_attr_modifies
- |cib_op_attr_privileged
- |cib_op_attr_local
- |cib_op_attr_replaces
- |cib_op_attr_writes_through,
- cib_process_commit_transaction,
- },
- {
- PCMK__CIB_REQUEST_DISCARD_TRANSACT, cib__op_discard_transact,
- cib_op_attr_privileged|cib_op_attr_local,
- cib_process_discard_transaction,
- },
+ [cib__op_init_transact] = cib_process_init_transaction,
+ [cib__op_commit_transact] = cib_process_commit_transaction,
+ [cib__op_discard_transact] = cib_process_discard_transaction,
};
-int
-cib_get_operation(const char *op, const cib_operation_t **operation)
+/*!
+ * \internal
+ * \brief Get the function that performs a given server-side CIB operation
+ *
+ * \param[in] operation Operation whose function to look up
+ *
+ * \return Function that performs \p operation within \c pacemaker-based
+ */
+cib__op_fn_t
+based_get_op_function(const cib_operation_t *operation)
{
- static GHashTable *operation_hash = NULL;
-
- CRM_ASSERT((op != NULL) && (operation != NULL));
-
- if (operation_hash == NULL) {
- operation_hash = pcmk__strkey_table(NULL, NULL);
-
- for (int lpc = 0; lpc < PCMK__NELEM(cib_server_ops); lpc++) {
- const cib_operation_t *oper = &(cib_server_ops[lpc]);
-
- g_hash_table_insert(operation_hash, (gpointer) oper->name,
- (gpointer) oper);
- }
- }
+ enum cib__op_type type = operation->type;
- *operation = g_hash_table_lookup(operation_hash, op);
+ CRM_ASSERT(type >= 0);
- if (*operation == NULL) {
- crm_err("Operation %s is not valid", op);
- return -EINVAL;
+ if (type >= PCMK__NELEM(cib_op_functions)) {
+ return NULL;
}
- return pcmk_ok;
+ return cib_op_functions[type];
}
diff --git a/daemons/based/based_transaction.c b/daemons/based/based_transaction.c
index d90d1b4420..d9234dd8db 100644
--- a/daemons/based/based_transaction.c
+++ b/daemons/based/based_transaction.c
@@ -1,371 +1,370 @@
/*
* Copyright 2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <glib.h>
#include <libxml/tree.h>
#include "pacemaker-based.h"
struct request_data {
pcmk__client_t *client;
xmlNodePtr request;
bool privileged;
};
/* Table of uncommitted CIB transactions
* key: client ID (const char *), value: GQueue of struct request_data
*/
static GHashTable *transactions = NULL;
/*!
* \internal
* \brief Create a new a CIB request data object
*
* \param[in] client CIB client
* \param[in] request CIB request
* \param[in] privileged If \p true, the request has write privileges
*/
static struct request_data *
create_request_data(pcmk__client_t *client, xmlNodePtr request,
bool privileged)
{
struct request_data *data = calloc(1, sizeof(struct request_data));
if (data == NULL) {
return NULL;
}
/* Caller owns client and request. The client's transaction is always
* discarded before the client is freed, so we can safely use it when
* processing requests in the transaction. The request is freed before we
* use it, so make a copy.
*/
data->client = client;
data->request = copy_xml(request);
data->privileged = privileged;
return data;
}
/*!
* \internal
* \brief Free a CIB request data object
*
* \param[in,out] data Request data object to free
*/
static void
free_request_data(gpointer data)
{
struct request_data *req_data = (struct request_data *) data;
if (req_data != NULL) {
// req_data doesn't own req_data->client
free_xml(req_data->request);
free(req_data);
}
}
/*!
* \internal
* \brief Free a CIB transaction and the requests it contains
*
* \param[in,out] data Transaction to free
*/
static inline void
free_transaction(gpointer data)
{
g_queue_free_full((GQueue *) data, (GDestroyNotify) free_request_data);
}
/*!
* \internal
* \brief Get a CIB client's uncommitted transaction, if any
*
* \param[in] client Client to look up
*
* \return The client's uncommitted transaction, if any
*
* \note The caller must not free the return value directly. Transactions must
* be freed by \p based_discard_transaction() or by
* \p based_free_transaction_table().
*/
static inline GQueue *
get_transaction(const pcmk__client_t *client)
{
if (transactions == NULL) {
return NULL;
}
return g_hash_table_lookup(transactions, client->id);
}
/*!
* \internal
* \brief Create a new transaction for a given CIB client
*
* \param[in] client Client to initiate a transaction for
*
* \return Standard Pacemaker return code
*/
int
based_init_transaction(const pcmk__client_t *client)
{
CRM_ASSERT(client != NULL);
if (client->id == NULL) {
crm_warn("Can't initiate transaction for client without id");
return EINVAL;
}
// A client can have at most one transaction at a time
if (get_transaction(client) != NULL) {
return pcmk_rc_already;
}
crm_trace("Initiating transaction for client %s (%s)",
pcmk__client_name(client), client->id);
if (transactions == NULL) {
transactions = pcmk__strkey_table(NULL, free_transaction);
}
g_hash_table_insert(transactions, client->id, g_queue_new());
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Validate that 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);
+ int rc = cib__get_operation(op, &operation);
- rc = pcmk_legacy2rc(rc);
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 transaction", op);
return EOPNOTSUPP;
}
if (!pcmk__str_eq(host, OUR_NODENAME,
pcmk__str_casei|pcmk__str_null_matches)) {
crm_err("Operation targeting another node (%s) is not supported in CIB "
"transaction",
host);
return EOPNOTSUPP;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Add a new CIB request to an existing transaction
*
* \param[in] client Client whose transaction to extend
* \param[in] request CIB request
* \param[in] privileged If \p true, the request has write privileges
*
* \return Standard Pacemaker return code
*/
int
based_extend_transaction(pcmk__client_t *client, xmlNodePtr request,
bool privileged)
{
struct request_data *data = NULL;
GQueue *transaction = NULL;
int rc = pcmk_rc_ok;
CRM_ASSERT((client != NULL) && (request != NULL));
transaction = get_transaction(client);
if (transaction == NULL) {
return pcmk_rc_no_transaction;
}
rc = validate_transaction_request(request);
if (rc != pcmk_rc_ok) {
return rc;
}
data = create_request_data(client, request, privileged);
if (data == NULL) {
return ENOMEM;
}
g_queue_push_tail(transaction, data);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Free a CIB client's transaction (if any) and its requests
*
* \param[in] client Client whose transaction to discard
*/
void
based_discard_transaction(const pcmk__client_t *client)
{
bool found = false;
CRM_ASSERT(client != NULL);
if (transactions != NULL) {
found = g_hash_table_remove(transactions, client->id);
}
crm_trace("%s for client %s (%s)",
(found? "Found and removed transaction" : "No transaction found"),
pcmk__client_name(client),
pcmk__s(client->id, "unidentified"));
}
/*!
* \internal
* \brief Process requests in a transaction
*
* Stop when a request fails or when all requests have been processed.
*
* \param[in,out] transaction Transaction to process
* \param[in] client_name Client name (for logging only)
* \param[in] client_id Client ID (for logging only)
*
* \return Standard Pacemaker return code
*/
static int
process_transaction_requests(GQueue *transaction, const char *client_name,
const char *client_id)
{
for (struct request_data *data = g_queue_pop_head(transaction);
data != NULL; data = g_queue_pop_head(transaction)) {
const char *op = crm_element_value(data->request, F_CIB_OPERATION);
int rc = cib_process_request(data->request, data->privileged,
data->client);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Aborting CIB transaction for client %s (%s) due to failed "
"%s request: %s",
client_name, client_id, op, pcmk_rc_str(rc));
crm_log_xml_info(data->request, "Failed request");
free_request_data(data);
return rc;
}
crm_trace("Applied %s request to transaction working CIB for client %s "
"(%s)",
op, client_name, client_id);
crm_log_xml_trace(data->request, "Successful request");
free_request_data(data);
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Commit a given CIB client's transaction to a working CIB copy
*
* \param[in] client Client whose transaction to commit
* \param[in,out] result_cib Where to store result CIB
*
* \return Standard Pacemaker return code
*
* \note This function is expected to be called only by
* \p cib_process_commit_transaction().
* \note \p result_cib is expected to be a copy of the current CIB as created by
* \p cib_perform_op().
* \note The caller is responsible for activating and syncing \p result_cib on
* success, and for freeing it on failure.
*/
int
based_commit_transaction(const pcmk__client_t *client, xmlNodePtr *result_cib)
{
GQueue *transaction = NULL;
const char *client_name = NULL;
const char *client_id = NULL;
xmlNodePtr saved_cib = the_cib;
int rc = pcmk_rc_ok;
CRM_ASSERT((client != NULL) && (result_cib != NULL));
/* *result_cib should be a copy of the_cib (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 != the_cib),
*result_cib = copy_xml(the_cib));
transaction = get_transaction(client);
if (transaction == NULL) {
return pcmk_rc_no_transaction;
}
client_name = pcmk__client_name(client);
client_id = pcmk__s(client->id, "unidentified");
crm_trace("Committing transaction for client %s (%s) to working CIB",
client_name, client_id);
// Apply all changes to a working copy of the CIB
the_cib = *result_cib;
rc = process_transaction_requests(transaction, client_name, client_id);
crm_trace("Transaction commit %s for client %s (%s); discarding queue",
((rc != pcmk_rc_ok)? "succeeded" : "failed"), client_name,
client_id);
// Free the transaction and (if aborted) free any remaining requests
based_discard_transaction(client);
/* Some request types (for example, erase) may have freed the_cib (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 the_cib.
*/
*result_cib = the_cib;
// Point the_cib back to the unchanged original copy
the_cib = saved_cib;
return rc;
}
/*!
* \internal
* \brief Free the transaction table and any uncommitted transactions
*/
void
based_free_transaction_table(void)
{
if (transactions != NULL) {
g_hash_table_destroy(transactions);
}
}
diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h
index a7eafebf36..128a202a50 100644
--- a/daemons/based/pacemaker-based.h
+++ b/daemons/based/pacemaker-based.h
@@ -1,153 +1,153 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PACEMAKER_BASED__H
# define PACEMAKER_BASED__H
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <glib.h>
#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <libxml/tree.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/common/xml.h>
#include <crm/cluster.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/mainloop.h>
#include <crm/cib/internal.h>
#include "based_transaction.h"
#ifdef HAVE_GNUTLS_GNUTLS_H
# include <gnutls/gnutls.h>
#endif
#define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->uname)
// CIB-specific client flags
enum cib_client_flags {
// Notifications
cib_notify_pre = (UINT64_C(1) << 0),
cib_notify_post = (UINT64_C(1) << 1),
cib_notify_replace = (UINT64_C(1) << 2),
cib_notify_confirm = (UINT64_C(1) << 3),
cib_notify_diff = (UINT64_C(1) << 4),
// Whether client is another cluster daemon
cib_is_daemon = (UINT64_C(1) << 12),
};
extern bool based_is_primary;
extern GHashTable *config_hash;
extern xmlNode *the_cib;
extern crm_trigger_t *cib_writer;
extern gboolean cib_writes_enabled;
extern GMainLoop *mainloop;
extern crm_cluster_t *crm_cluster;
extern GHashTable *local_notify_queue;
extern gboolean legacy_mode;
extern gboolean stand_alone;
extern gboolean cib_shutdown_flag;
extern gchar *cib_root;
extern int cib_status;
extern pcmk__output_t *logger_out;
extern struct qb_ipcs_service_handlers ipc_ro_callbacks;
extern struct qb_ipcs_service_handlers ipc_rw_callbacks;
extern qb_ipcs_service_t *ipcs_ro;
extern qb_ipcs_service_t *ipcs_rw;
extern qb_ipcs_service_t *ipcs_shm;
void cib_peer_callback(xmlNode *msg, void *private_data);
void cib_common_callback_worker(uint32_t id, uint32_t flags,
xmlNode *op_request, pcmk__client_t *cib_client,
gboolean privileged);
int cib_process_request(xmlNode *request, gboolean privileged,
const pcmk__client_t *cib_client);
void cib_shutdown(int nsig);
void terminate_cib(const char *caller, int fast);
gboolean cib_legacy_mode(void);
gboolean uninitializeCib(void);
xmlNode *readCibXmlFile(const char *dir, const char *file,
gboolean discard_status);
int activateCibXml(xmlNode *doc, gboolean to_disk, const char *op);
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);
int cib_process_noop(const char *op, int options, const char *section,
xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer);
int cib_process_ping(const char *op, int options, const char *section,
xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer);
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 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 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 cib_process_sync(const char *op, int options, const char *section,
xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer);
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);
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);
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 cib_process_init_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer);
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);
int cib_process_discard_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer);
void send_sync_request(const char *host);
int sync_our_cib(xmlNode *request, gboolean all);
-int cib_get_operation(const char *op, const cib_operation_t **operation);
+cib__op_fn_t based_get_op_function(const cib_operation_t *operation);
void cib_diff_notify(const char *op, int result, const char *call_id,
const char *client_id, const char *client_name,
const char *origin, xmlNode *update, xmlNode *diff);
void cib_replace_notify(const char *op, int result, const char *call_id,
const char *client_id, const char *client_name,
const char *origin, xmlNode *update, xmlNode *diff,
uint32_t change_section);
static inline const char *
cib_config_lookup(const char *opt)
{
return g_hash_table_lookup(config_hash, opt);
}
#endif // PACEMAKER_BASED__H
diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h
index 35aff95fa0..c8649bdb52 100644
--- a/include/crm/cib/internal.h
+++ b/include/crm/cib/internal.h
@@ -1,332 +1,333 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef CIB_INTERNAL__H
# define CIB_INTERNAL__H
# include <crm/cib.h>
# include <crm/common/ipc_internal.h>
# include <crm/common/output_internal.h>
// Request types for CIB manager IPC/CPG
#define PCMK__CIB_REQUEST_SECONDARY "cib_slave"
#define PCMK__CIB_REQUEST_PRIMARY "cib_master"
#define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync"
#define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one"
#define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster"
#define PCMK__CIB_REQUEST_BUMP "cib_bump"
#define PCMK__CIB_REQUEST_QUERY "cib_query"
#define PCMK__CIB_REQUEST_CREATE "cib_create"
#define PCMK__CIB_REQUEST_MODIFY "cib_modify"
#define PCMK__CIB_REQUEST_DELETE "cib_delete"
#define PCMK__CIB_REQUEST_ERASE "cib_erase"
#define PCMK__CIB_REQUEST_REPLACE "cib_replace"
#define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff"
#define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade"
#define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt"
#define PCMK__CIB_REQUEST_NOOP "noop"
#define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req"
#define PCMK__CIB_REQUEST_INIT_TRANSACT "cib_init_transact"
#define PCMK__CIB_REQUEST_COMMIT_TRANSACT "cib_commit_transact"
#define PCMK__CIB_REQUEST_DISCARD_TRANSACT "cib_discard_transact"
# define F_CIB_CLIENTID "cib_clientid"
# define F_CIB_CALLOPTS "cib_callopt"
# define F_CIB_CALLID "cib_callid"
# define F_CIB_CALLDATA "cib_calldata"
# define F_CIB_OPERATION "cib_op"
# define F_CIB_ISREPLY "cib_isreplyto"
# define F_CIB_SECTION "cib_section"
# define F_CIB_HOST "cib_host"
# define F_CIB_RC "cib_rc"
# define F_CIB_UPGRADE_RC "cib_upgrade_rc"
# define F_CIB_DELEGATED "cib_delegated_from"
# define F_CIB_OBJID "cib_object"
# define F_CIB_OBJTYPE "cib_object_type"
# define F_CIB_EXISTING "cib_existing_object"
# define F_CIB_SEENCOUNT "cib_seen"
# define F_CIB_TIMEOUT "cib_timeout"
# define F_CIB_UPDATE "cib_update"
# define F_CIB_GLOBAL_UPDATE "cib_update"
# define F_CIB_UPDATE_RESULT "cib_update_result"
# define F_CIB_CLIENTNAME "cib_clientname"
# define F_CIB_NOTIFY_TYPE "cib_notify_type"
# define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate"
# define F_CIB_UPDATE_DIFF "cib_update_diff"
# define F_CIB_USER "cib_user"
# define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id"
# define F_CIB_PING_ID "cib_ping_id"
# define F_CIB_SCHEMA_MAX "cib_schema_max"
# define F_CIB_CHANGE_SECTION "cib_change_section"
# define T_CIB "cib"
# define T_CIB_NOTIFY "cib_notify"
/* notify sub-types */
# define T_CIB_PRE_NOTIFY "cib_pre_notify"
# define T_CIB_POST_NOTIFY "cib_post_notify"
# define T_CIB_UPDATE_CONFIRM "cib_update_confirmation"
# define T_CIB_REPLACE_NOTIFY "cib_refresh_notify"
/*!
* \internal
* \enum cib_change_section_info
* \brief Flags to indicate which sections of the CIB have changed
*/
enum cib_change_section_info {
cib_change_section_none = 0, //!< No sections have changed
cib_change_section_nodes = (1 << 0), //!< The nodes section has changed
cib_change_section_alerts = (1 << 1), //!< The alerts section has changed
cib_change_section_status = (1 << 2), //!< The status section has changed
};
/*!
* \internal
* \enum cib_op_attr
* \brief Flags for CIB operation attributes
*/
enum cib_op_attr {
cib_op_attr_none = 0, //!< No special attributes
cib_op_attr_modifies = (1 << 1), //!< Modifies CIB
cib_op_attr_privileged = (1 << 2), //!< Requires privileges
cib_op_attr_local = (1 << 3), //!< Must only be processed locally
cib_op_attr_replaces = (1 << 4), //!< Replaces CIB
cib_op_attr_writes_through = (1 << 5), //!< Writes to disk on success
cib_op_attr_transaction = (1 << 6), //!< Supported in a transaction
};
/*!
* \internal
* \enum cib__op_type
* \brief Types of CIB operations
*/
enum cib__op_type {
cib__op_abs_delete,
cib__op_apply_patch,
cib__op_bump,
cib__op_create,
cib__op_delete,
cib__op_erase,
cib__op_is_primary,
cib__op_modify,
cib__op_noop,
cib__op_ping,
cib__op_primary,
cib__op_query,
cib__op_replace,
cib__op_secondary,
cib__op_shutdown,
cib__op_sync_all,
cib__op_sync_one,
cib__op_upgrade,
// @TODO: Refactor transactions and remove these
cib__op_init_transact,
cib__op_commit_transact,
cib__op_discard_transact,
};
/*!
* \internal
* \brief Set given <tt>enum cib_change_section_info</tt> flags
*
* \param[in,out] flags_orig Group of flags to update
* \param[in] flags_to_set Flags to clear from \p flags_orig
*/
#define pcmk__set_change_section(flags_orig, flags_to_set) do { \
flags_orig = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
"CIB change section", \
"change_section", flags_orig, \
flags_to_set, #flags_to_set); \
} while (0)
gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates);
gboolean cib_read_config(GHashTable * options, xmlNode * current_cib);
typedef int (*cib__op_fn_t)(const char *, int, const char *, xmlNode *,
xmlNode *, xmlNode *, xmlNode **, xmlNode **);
typedef struct cib_operation_s {
const char *name;
enum cib__op_type type;
uint32_t flags; //!< Group of <tt>enum cib_op_attr</tt> flags
- cib__op_fn_t fn;
} cib_operation_t;
typedef struct cib_notify_client_s {
const char *event;
const char *obj_id; /* implement one day */
const char *obj_type; /* implement one day */
void (*callback) (const char *event, xmlNode * msg);
} cib_notify_client_t;
typedef struct cib_callback_client_s {
void (*callback) (xmlNode *, int, int, xmlNode *, void *);
const char *id;
void *user_data;
gboolean only_success;
struct timer_rec_s *timer;
void (*free_func)(void *);
} cib_callback_client_t;
struct timer_rec_s {
int call_id;
int timeout;
guint ref;
cib_t *cib;
};
#define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \
cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \
(flags_to_set), #flags_to_set); \
} while (0)
#define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \
cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \
(flags_to_clear), #flags_to_clear); \
} while (0)
cib_t *cib_new_variant(void);
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);
xmlNode *cib_create_op(int call_id, const char *op, const char *host,
const char *section, xmlNode * data, int call_options,
const char *user_name);
void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc);
void cib_native_notify(gpointer data, gpointer user_data);
+int cib__get_operation(const char *op, const cib_operation_t **operation);
+
int cib_process_query(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_erase(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_bump(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_create(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_modify(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_delete(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_diff(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
/*!
* \internal
* \brief Query or modify a CIB
*
* \param[in] op PCMK__CIB_REQUEST_* operation to be performed
* \param[in] options Flag set of \c cib_call_options
* \param[in] section XPath to query or modify
* \param[in] req unused
* \param[in] input Portion of CIB to modify (used with
* PCMK__CIB_REQUEST_CREATE,
* PCMK__CIB_REQUEST_MODIFY, and
* PCMK__CIB_REQUEST_REPLACE)
* \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY)
* \param[in,out] result_cib CIB copy to make changes in (used with
* PCMK__CIB_REQUEST_CREATE,
* PCMK__CIB_REQUEST_MODIFY,
* PCMK__CIB_REQUEST_DELETE, and
* PCMK__CIB_REQUEST_REPLACE)
* \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY)
*
* \return Legacy Pacemaker return code
*/
int cib_process_xpath(const char *op, int options, const char *section,
const xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode ** answer);
bool cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff);
int cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name);
int cib_file_read_and_verify(const char *filename, const char *sigfile,
xmlNode **root);
int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename);
void cib__set_output(cib_t *cib, pcmk__output_t *out);
cib_callback_client_t* cib__lookup_id (int call_id);
/*!
* \internal
* \brief Connect to, query, and optionally disconnect from the CIB
*
* Open a read-write connection to the CIB manager if an already connected
* client is not passed in. Then query the CIB and store the resulting XML.
* Finally, disconnect if the CIB connection isn't being returned to the caller.
*
* \param[in,out] out Output object (may be \p NULL)
* \param[in,out] cib If not \p NULL, where to store CIB connection
* \param[out] cib_object Where to store query result
*
* \return Standard Pacemaker return code
*
* \note If \p cib is not \p NULL, the caller is responsible for freeing \p *cib
* using \p cib_delete().
* \note If \p *cib points to an existing \p cib_t object, this function will
* reuse it instead of creating a new one. If the existing client is
* already connected, the connection will be reused, even if it's
* read-only.
*/
int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object);
int cib__clean_up_connection(cib_t **cib);
int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, const char *user_name,
const char *node_type);
int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section,
const char *node_uuid, const char *set_type, const char *set_name,
const char *attr_id, const char *attr_name, const char *user_name,
xmlNode **result);
int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, const char *user_name);
#endif
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index dd1d9f3bd8..51edfb21e0 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,1419 +1,1341 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <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;
-// key: op name (const char *) -> value: operation (const cib_operation_t *)
-static GHashTable *op_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;
GQueue *transaction;
} cib_file_opaque_t;
static int cib_file_extend_transaction(cib_t *cib,
const cib_operation_t *operation,
xmlNode *request);
static void cib_file_discard_transaction(cib_t *cib);
static int cib_file_process_init_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input,
xmlNode *existing_cib,
xmlNode **result_cib,
xmlNode **answer);
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);
static int cib_file_process_discard_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);
- /* There's no clean way to free the tables at program exit time. We can't
- * create a cleanup function in this file and add it to crm_exit(), which is
- * in libcrmcommon. libcrmcommon doesn't link against libcib.
- *
- * As a hack for now, free the common tables when there are no more clients.
- *
- * @COMPAT: If libcib and libcrmcommon are merged, add this to crm_exit().
+ /* @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;
-
- if (op_table != NULL) {
- g_hash_table_destroy(op_table);
- op_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);
}
-/* This table is adapted from cib_server_ops in pacemaker-based. Flags that are
- * not used in cib_file.c (for example, cib_op_attr_replaces) are not currently
- * included. Also, cib_file.c does not use prepare or cleanup functions.
- *
- * @TODO: Try to create a shared table that merges cib_server_ops with
- * cib_file_ops. We would want a new cib_op_attr_file enum value (for an
- * operation that supports running against a file). We can easily ignore the
- * prepare and cleanup functions.
- *
- * The main challenge is that the fn member (cib__op_fn_t) may differ depending
- * on whether we're processing the request on the server side or with the
- * file-based client. Processor functions defined in pacemaker-based shouldn't
- * be declared in libcib, but we need them accessible via the table somehow.
- *
- * Maybe use an enum that indexes into an array of cib__op_fn_t functions. That
- * would be obnoxiously similar to the call_type (index) argument that we
- * eliminated in c13bf8b2.
- */
-static const cib_operation_t cib_file_ops[] = {
- {
- PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_diff
- },
- {
- PCMK__CIB_REQUEST_BUMP, cib__op_bump,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_bump
- },
- {
- PCMK__CIB_REQUEST_CREATE, cib__op_create,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_create
- },
- {
- PCMK__CIB_REQUEST_DELETE, cib__op_delete,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_delete
- },
- {
- PCMK__CIB_REQUEST_ERASE, cib__op_erase,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_erase
- },
- {
- PCMK__CIB_REQUEST_MODIFY, cib__op_modify,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_modify
- },
- {
- PCMK__CIB_REQUEST_QUERY, cib__op_query,
- cib_op_attr_none,
- cib_process_query
- },
- {
- PCMK__CIB_REQUEST_REPLACE, cib__op_replace,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_replace
- },
- {
- PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade,
- cib_op_attr_modifies|cib_op_attr_transaction,
- cib_process_upgrade
- },
- {
- PCMK__CIB_REQUEST_INIT_TRANSACT, cib__op_init_transact,
- cib_op_attr_none,
- cib_file_process_init_transaction
- },
- {
- PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
- cib_op_attr_modifies,
- cib_file_process_commit_transaction
- },
- {
- PCMK__CIB_REQUEST_DISCARD_TRANSACT, cib__op_discard_transact,
- cib_op_attr_none,
- cib_file_process_discard_transaction
- },
+static const cib__op_fn_t cib_op_functions[] = {
+ [cib__op_apply_patch] = cib_process_diff,
+ [cib__op_bump] = cib_process_bump,
+ [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__op_init_transact] = cib_file_process_init_transaction,
+ [cib__op_commit_transact] = cib_file_process_commit_transaction,
+ [cib__op_discard_transact] = cib_file_process_discard_transaction,
};
/* 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)
-static int
-get_operation(const char *op, const cib_operation_t **operation)
+/*!
+ * \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)
{
- CRM_CHECK(op != NULL, return -EINVAL);
-
- if (op_table == NULL) {
- op_table = pcmk__strkey_table(NULL, NULL);
+ enum cib__op_type type = operation->type;
- for (int lpc = 0; lpc < PCMK__NELEM(cib_file_ops); lpc++) {
- const cib_operation_t *oper = &(cib_file_ops[lpc]);
+ CRM_ASSERT(type >= 0);
- g_hash_table_insert(op_table, (gpointer) oper->name,
- (gpointer) oper);
- }
- }
-
- *operation = g_hash_table_lookup(op_table, op);
-
- if (*operation == NULL) {
- /* @COMPAT: EOPNOTSUPP makes more sense but would change existing
- * behavior. If we merge the server and file ops tables in the future,
- * then EINVAL makes sense: if we didn't find the op, it doesn't exist.
- * Only an internal bug gets us to that point.
- */
- crm_err("Operation %s is invalid or unsupported for CIB file clients",
- op);
- return -EPROTONOSUPPORT;
+ if (type >= PCMK__NELEM(cib_op_functions)) {
+ return NULL;
}
- return pcmk_ok;
+ 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;
- rc = get_operation(op, &operation);
- if (rc != pcmk_ok) {
- goto done;
- }
+ // 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 cib_prepare_common()
if ((section != NULL) && (data != NULL)
&& pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) {
data = pcmk_find_cib_element(data, section);
}
- rc = cib_perform_op(op, call_options, operation->fn, read_only, 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)) {
goto done;
}
if (rc == -pcmk_err_schema_validation) {
validate_xml_verbose(result_cib);
} else if ((rc == pcmk_ok) && !read_only) {
pcmk__output_t *out = NULL;
rc = pcmk_rc2legacy(pcmk__log_output_new(&out));
CRM_CHECK(rc == pcmk_ok, goto done);
pcmk__output_set_log_level(out, LOG_DEBUG);
rc = out->message(out, "xml-patchset", cib_diff);
out->finish(out, pcmk_rc2exitc(rc), true, NULL);
pcmk__output_free(out);
rc = pcmk_ok;
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 = get_operation(op, &operation);
+ rc = cib__get_operation(op, &operation);
+ rc = pcmk_rc2legacy(rc);
if (rc != pcmk_ok) {
- return rc;
+ // @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->call_id++;
cib__set_call_options(call_options, "file operation", cib_no_mtime);
request = cib_create_op(cib->call_id, op, host, section, data, call_options,
user_name);
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_file_extend_transaction(cib, operation, request);
if (rc != pcmk_rc_ok) {
crm_warn("Could not extend transaction for CIB file client: %s",
pcmk_rc_str(rc));
}
rc = pcmk_rc2legacy(rc);
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, XML_ATTR_VALIDATION);
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_file_discard_transaction(cib);
/* 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);
} 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, XML_ATTR_GENERATION);
const char *admin_epoch = crm_element_value(cib_root,
XML_ATTR_GENERATION_ADMIN);
/* 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 Create a new transaction for a given CIB file client
*
* \param[in,out] cib CIB file client
*
* \return Standard Pacemaker return code
*/
static int
cib_file_init_transaction(cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
// A client can have at most one transaction at a time
if (private->transaction != NULL) {
return pcmk_rc_already;
}
crm_trace("Initiating transaction for CIB file client (%s) on file '%s'",
private->id, private->filename);
private->transaction = g_queue_new();
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Add a new CIB request to an existing transaction
*
* \param[in,out] cib CIB file client
* \param[in] operation CIB operation
* \param[in] request CIB request
*
* \return Standard Pacemaker return code
*/
static int
cib_file_extend_transaction(cib_t *cib, const cib_operation_t *operation,
xmlNode *request)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (private->transaction == NULL) {
return pcmk_rc_no_transaction;
}
-
if (!pcmk_is_set(operation->flags, cib_op_attr_transaction)) {
crm_err("Operation '%s' is not supported in CIB transaction",
operation->name);
return EOPNOTSUPP;
}
-
+ if (file_get_op_function(operation) == NULL) {
+ crm_err("Operation %s is not supported by CIB file clients",
+ operation->name);
+ return EOPNOTSUPP;
+ }
g_queue_push_tail(private->transaction, copy_xml(request));
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Free a CIB file client's transaction (if any) and its requests
*
* \param[in,out] cib CIB file client
*/
static void
cib_file_discard_transaction(cib_t *cib)
{
bool found = false;
cib_file_opaque_t *private = cib->variant_opaque;
if (private->transaction != NULL) {
found = true;
g_queue_free_full(private->transaction, (GDestroyNotify) free_xml);
private->transaction = NULL;
}
crm_trace("%s for CIB file client (%s) on file '%s'",
(found? "Discarded transaction" : "No transaction found"),
private->id, private->filename);
}
/*!
* \internal
* \brief Process requests in a CIB file client's transaction
*
* Stop when a request fails or when all requests have been processed.
*
* \param[in,out] cib CIB file client
*
* \return Standard Pacemaker return code
*/
static int
cib_file_process_transaction_requests(cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
GQueue *transaction = private->transaction;
for (xmlNode *request = g_queue_pop_head(transaction); request != NULL;
request = g_queue_pop_head(transaction)) {
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");
free_xml(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");
free_xml(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,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 **result_cib)
{
int rc = pcmk_rc_ok;
cib_file_opaque_t *private = cib->variant_opaque;
xmlNode *saved_cib = private->cib_xml;
/* *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));
if (private->transaction == NULL) {
return pcmk_rc_no_transaction;
}
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);
crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'; "
"discarding queue",
((rc != pcmk_rc_ok)? "succeeded" : "failed"),
private->id, private->filename);
// Free the transaction and (if aborted) free any remaining requests
cib_file_discard_transaction(cib);
/* 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_init_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_init_transaction(cib);
if (rc != pcmk_rc_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
crm_err("Could not initiate transaction for CIB file client (%s) on "
"file '%s': %s",
private->id, private->filename, pcmk_rc_str(rc));
}
return pcmk_rc2legacy(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, 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);
}
static int
cib_file_process_discard_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
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);
cib_file_discard_transaction(cib);
return pcmk_ok;
}
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
index 3be0649b97..9cd962deb6 100644
--- a/lib/cib/cib_ops.c
+++ b/lib/cib/cib_ops.c
@@ -1,876 +1,1025 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU 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_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_*_TRANSACT requests must be processed locally because
+ * they depend on the client table. Requests that manage transactions on
+ * other nodes would likely be problematic in many other ways as well.
+ */
+ {
+ PCMK__CIB_REQUEST_INIT_TRANSACT, cib__op_init_transact,
+ cib_op_attr_privileged|cib_op_attr_local
+ },
+ {
+ PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
+ cib_op_attr_modifies
+ |cib_op_attr_privileged
+ |cib_op_attr_local
+ |cib_op_attr_replaces
+ |cib_op_attr_writes_through
+ },
+ {
+ PCMK__CIB_REQUEST_DISCARD_TRANSACT, cib__op_discard_transact,
+ cib_op_attr_privileged|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) {
const char *tag = TYPE(obj_root);
xmlNode *shallow = create_xml_node(*answer, tag);
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);
*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, XML_ATTR_VALIDATION);
*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, XML_ATTR_GENERATION, 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, XML_ATTR_GENERATION), ""));
*answer = NULL;
update_counter(*result_cib, XML_ATTR_GENERATION, 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)
{
const char *tag = 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);
}
*answer = NULL;
if (input == NULL) {
return -EINVAL;
}
tag = crm_element_name(input);
if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(tag, section, pcmk__str_casei)) {
section = NULL;
}
if (pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) {
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, XML_ATTR_DIGEST);
if (digest) {
const char *version = crm_element_value(req, XML_ATTR_CRM_VERSION);
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;
} else if (replace_admin_epoch > admin_epoch) {
/* no more checks */
} else if (replace_epoch < epoch) {
reason = XML_ATTR_GENERATION;
} 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__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
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;
}
}
if(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 = crm_element_name(update);
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) {
xmlNode *remove = NULL;
int last = 0, lpc = 0, len = 0;
len = strlen(replace);
while (lpc <= len) {
if (replace[lpc] == ',' || replace[lpc] == 0) {
char *replace_item = NULL;
if (last == lpc) {
/* nothing to do */
last = lpc + 1;
goto incr;
}
replace_item = strndup(replace + last, lpc - last);
remove = find_xml_node(target, replace_item, FALSE);
if (remove != NULL) {
crm_trace("Replacing node <%s> in <%s>",
replace_item, crm_element_name(target));
free_xml(remove);
remove = NULL;
}
free(replace_item);
last = lpc + 1;
}
incr:
lpc++;
}
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>", crm_element_name(a_child),
((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>",
crm_element_name(a_child),
((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 = crm_element_name(new_obj);
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, TYPE(target));
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__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) {
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__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
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) && xml_has_children(failed)) {
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, "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, XML_ATTR_GENERATION) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, XML_ATTR_GENERATION_ADMIN) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, XML_ATTR_VALIDATION) != NULL) {
config_changes = true;
goto done;
}
if (crm_element_value(top, XML_ATTR_CRM_VERSION) != 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) {
const char *tag = TYPE(match);
xmlNode *shallow = create_xml_node(*answer, tag);
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;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Apr 21, 12:10 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1664850
Default Alt Text
(181 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment